Skip to content

Commit 6c917bf

Browse files
committed
test: add unit test for backend wallet
2 parents cbb1ac9 + e01193a commit 6c917bf

File tree

148 files changed

+2578
-583
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

148 files changed

+2578
-583
lines changed

.changeset/afraid-mails-sip.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": minor
3+
---
4+
5+
Feature: Adds getAdminAccount to inAppWallet interface for AA ecosystem wallets

.changeset/little-beds-dress.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
Add onTimeout callback to useAutoConnect

.changeset/pink-gorillas-try.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
SDK: Removed co.lobstr from the available wallets (an unsupported non-EVM wallet)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
SDK: Gracefully ignore chain with no chain ID in `fromEip1193Provider`

apps/dashboard/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
"ioredis": "^5.4.1",
6969
"ipaddr.js": "^2.2.0",
7070
"lucide-react": "0.468.0",
71-
"next": "15.1.0",
71+
"next": "15.1.3",
7272
"next-plausible": "^3.12.4",
7373
"next-seo": "^6.5.0",
7474
"next-themes": "^0.4.4",

apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/claim-button.tsx

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { GemIcon } from "lucide-react";
1717
import { useState } from "react";
1818
import { useForm } from "react-hook-form";
1919
import { toast } from "sonner";
20-
import { type ThirdwebContract, ZERO_ADDRESS } from "thirdweb";
20+
import { type ThirdwebContract, ZERO_ADDRESS, isAddress } from "thirdweb";
2121
import { getApprovalForTransaction } from "thirdweb/extensions/erc20";
2222
import { claimTo } from "thirdweb/extensions/erc721";
2323
import { useActiveAccount, useSendAndConfirmTransaction } from "thirdweb/react";
@@ -40,7 +40,7 @@ export const NFTClaimButton: React.FC<NFTClaimButtonProps> = ({
4040
}) => {
4141
const trackEvent = useTrack();
4242
const address = useActiveAccount()?.address;
43-
const { register, handleSubmit, formState } = useForm({
43+
const { register, handleSubmit, formState, setValue } = useForm({
4444
defaultValues: { amount: "1", to: address },
4545
});
4646
const { errors } = formState;
@@ -67,7 +67,24 @@ export const NFTClaimButton: React.FC<NFTClaimButtonProps> = ({
6767
<div className="flex w-full flex-col gap-6 md:flex-row">
6868
<FormControl isRequired isInvalid={!!errors.to}>
6969
<FormLabel>To Address</FormLabel>
70-
<Input placeholder={ZERO_ADDRESS} {...register("to")} />
70+
<Input
71+
placeholder={ZERO_ADDRESS}
72+
{...register("to", {
73+
validate: (value) => {
74+
if (!value) {
75+
return "Enter a recipient address";
76+
}
77+
if (!isAddress(value.trim())) {
78+
return "Invalid EVM address";
79+
}
80+
},
81+
onChange: (e) => {
82+
setValue("to", e.target.value.trim(), {
83+
shouldValidate: true,
84+
});
85+
},
86+
})}
87+
/>
7188
<FormHelperText>Enter the address to claim to.</FormHelperText>
7289
<FormErrorMessage>{errors.to?.message}</FormErrorMessage>
7390
</FormControl>
@@ -115,7 +132,7 @@ export const NFTClaimButton: React.FC<NFTClaimButtonProps> = ({
115132

116133
const transaction = claimTo({
117134
contract,
118-
to: d.to,
135+
to: d.to.trim(),
119136
quantity: BigInt(d.amount),
120137
from: account.address,
121138
});

apps/dashboard/src/app/team/[team_slug]/(team)/~/analytics/page.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,10 @@ function UsersChartCard({
257257
}
258258
data={timeSeriesData}
259259
aggregateFn={(_data, key) =>
260-
timeSeriesData[timeSeriesData.length - 2]?.[key]
260+
// If there is only one data point, use that one, otherwise use the previous
261+
timeSeriesData.filter((d) => (d[key] as number) > 0).length >= 2
262+
? timeSeriesData[timeSeriesData.length - 2]?.[key]
263+
: timeSeriesData[timeSeriesData.length - 1]?.[key]
261264
}
262265
// Get the trend from the last two COMPLETE periods
263266
trendFn={(data, key) =>

apps/dashboard/src/app/team/[team_slug]/[project_slug]/page.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,10 @@ function UsersChartCard({
279279
queryKey="usersChart"
280280
data={timeSeriesData}
281281
aggregateFn={(_data, key) =>
282-
timeSeriesData[timeSeriesData.length - 2]?.[key]
282+
// If there is only one data point, use that one, otherwise use the previous
283+
timeSeriesData.filter((d) => (d[key] as number) > 0).length >= 2
284+
? timeSeriesData[timeSeriesData.length - 2]?.[key]
285+
: timeSeriesData[timeSeriesData.length - 1]?.[key]
283286
}
284287
// Get the trend from the last two COMPLETE periods
285288
trendFn={(data, key) =>
Lines changed: 9 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { Badge } from "@/components/ui/badge";
22
import { CodeClient } from "@/components/ui/code/code.client";
3-
import { useContractSources } from "contract-ui/hooks/useContractSources";
4-
import { useMemo } from "react";
3+
import { useContractFunctionComment } from "contract-ui/hooks/useContractFunctionComment";
54
import type { ThirdwebContract } from "thirdweb";
65

76
/**
@@ -11,66 +10,24 @@ export default function ContractFunctionComment({
1110
contract,
1211
functionName,
1312
}: { contract: ThirdwebContract; functionName: string }) {
14-
const sourceQuery = useContractSources(contract);
15-
const comment = useMemo(() => {
16-
if (!sourceQuery.data?.length) {
17-
return null;
18-
}
19-
const file = sourceQuery.data.find((item) =>
20-
item.source.includes(functionName),
21-
);
22-
if (!file) {
23-
return null;
24-
}
25-
return extractFunctionComment(file.source, functionName);
26-
}, [sourceQuery.data, functionName]);
13+
const query = useContractFunctionComment(contract, functionName);
2714

28-
if (sourceQuery.isLoading) {
15+
if (query.isLoading) {
2916
return null;
3017
}
31-
if (!comment) {
18+
if (!query.data) {
3219
return null;
3320
}
3421
return (
3522
<>
3623
<p className="mt-6">
3724
About this function <Badge>Beta</Badge>
3825
</p>
39-
<CodeClient lang="solidity" code={comment} />
26+
<CodeClient
27+
lang="wikitext"
28+
code={query.data}
29+
copyButtonClassName="hidden"
30+
/>
4031
</>
4132
);
4233
}
43-
44-
function extractFunctionComment(
45-
// Tthe whole code from the solidity file containing (possibly) the function
46-
solidityCode: string,
47-
functionName: string,
48-
): string | null {
49-
// Regular expression to match function declarations and their preceding comments
50-
// This regex now captures both single-line (//) and multi-line (/** */) comments
51-
const functionRegex =
52-
/(?:\/\/[^\n]*|\/\*\*[\s\S]*?\*\/)\s*function\s+(\w+)\s*\(/g;
53-
54-
while (true) {
55-
const match = functionRegex.exec(solidityCode);
56-
if (match === null) {
57-
return null;
58-
}
59-
const [fullMatch, name] = match;
60-
if (!fullMatch || !fullMatch.length) {
61-
return null;
62-
}
63-
if (name === functionName) {
64-
// Extract the comment part
65-
const comment = (fullMatch.split("function")[0] || "").trim();
66-
if (!comment) {
67-
return null;
68-
}
69-
70-
if (/^[^a-zA-Z0-9]+$/.test(comment)) {
71-
return null;
72-
}
73-
return comment;
74-
}
75-
}
76-
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import { useThirdwebClient } from "@/constants/thirdweb.client";
2+
import { useQuery } from "@tanstack/react-query";
3+
import type { ThirdwebContract } from "thirdweb";
4+
import { getCompilerMetadata } from "thirdweb/contract";
5+
import { download } from "thirdweb/storage";
6+
7+
/**
8+
* Try to extract the description (or comment) about a contract's method from our contract metadata endpoint
9+
*
10+
* An example of a contract that has both userdoc and devdoc:
11+
* https://contract.thirdweb.com/metadata/1/0x303a465B659cBB0ab36eE643eA362c509EEb5213
12+
*/
13+
export function useContractFunctionComment(
14+
contract: ThirdwebContract,
15+
functionName: string,
16+
) {
17+
const client = useThirdwebClient();
18+
return useQuery({
19+
queryKey: [
20+
"contract-function-comment",
21+
contract?.chain.id || "",
22+
contract?.address || "",
23+
functionName,
24+
],
25+
queryFn: async (): Promise<string> => {
26+
const data = await getCompilerMetadata(contract);
27+
let comment = "";
28+
/**
29+
* If the response data contains userdoc and/or devdoc
30+
* we always prioritize using them. parsing the comment using regex should
31+
* always be the last resort
32+
*/
33+
if (data.metadata.output.devdoc?.methods) {
34+
const keys = Object.keys(data.metadata.output.devdoc.methods);
35+
const matchingKey = keys.find(
36+
(rawKey) =>
37+
rawKey.startsWith(functionName) &&
38+
rawKey.split("(")[0] === functionName,
39+
);
40+
const devDocContent = matchingKey
41+
? data.metadata.output.devdoc.methods[matchingKey]?.details
42+
: undefined;
43+
if (devDocContent) {
44+
comment += `@dev-doc: ${devDocContent}\n`;
45+
}
46+
}
47+
if (data.metadata.output.userdoc?.methods) {
48+
const keys = Object.keys(data.metadata.output.userdoc.methods);
49+
const matchingKey = keys.find(
50+
(rawKey) =>
51+
rawKey.startsWith(functionName) &&
52+
rawKey.split("(")[0] === functionName,
53+
);
54+
const userDocContent = matchingKey
55+
? data.metadata.output.userdoc.methods[matchingKey]?.notice
56+
: undefined;
57+
if (userDocContent) {
58+
comment += `@user-doc: ${userDocContent}\n`;
59+
}
60+
}
61+
if (comment) {
62+
return comment;
63+
}
64+
if (!data.metadata.sources) {
65+
return "";
66+
}
67+
const sources = await Promise.all(
68+
Object.entries(data.metadata.sources).map(async ([path, info]) => {
69+
if ("content" in info) {
70+
return {
71+
filename: path,
72+
source: info.content || "Could not find source for this file",
73+
};
74+
}
75+
const urls = info.urls;
76+
const ipfsLink = urls
77+
? urls.find((url) => url.includes("ipfs"))
78+
: undefined;
79+
if (ipfsLink) {
80+
const ipfsHash = ipfsLink.split("ipfs/")[1];
81+
const source = await download({
82+
uri: `ipfs://${ipfsHash}`,
83+
client,
84+
})
85+
.then((r) => r.text())
86+
.catch(() => "Failed to fetch source from IPFS");
87+
return {
88+
filename: path,
89+
source,
90+
};
91+
}
92+
return {
93+
filename: path,
94+
source: "Could not find source for this file",
95+
};
96+
}),
97+
);
98+
const file = sources.find((item) => item.source.includes(functionName));
99+
if (!file) {
100+
return "";
101+
}
102+
return extractFunctionComment(file.source, functionName);
103+
},
104+
});
105+
}
106+
107+
function extractFunctionComment(
108+
// The whole code from the solidity file containing (possibly) the function
109+
solidityCode: string,
110+
functionName: string,
111+
): string {
112+
// Regular expression to match function declarations and their preceding comments
113+
// This regex now captures both single-line (//) and multi-line (/** */) comments
114+
const functionRegex =
115+
/(?:\/\/[^\n]*|\/\*\*[\s\S]*?\*\/)\s*function\s+(\w+)\s*\(/g;
116+
117+
while (true) {
118+
const match = functionRegex.exec(solidityCode);
119+
if (match === null) {
120+
return "";
121+
}
122+
const [fullMatch, name] = match;
123+
if (!fullMatch || !fullMatch.length) {
124+
return "";
125+
}
126+
if (name === functionName) {
127+
// Extract the comment part
128+
const comment = (fullMatch.split("function")[0] || "").trim();
129+
if (!comment) {
130+
return "";
131+
}
132+
133+
if (/^[^a-zA-Z0-9]+$/.test(comment)) {
134+
return "";
135+
}
136+
return comment;
137+
}
138+
}
139+
}

0 commit comments

Comments
 (0)