Skip to content

Commit d29c06d

Browse files
committed
move over some utility read only endpoints to API
1 parent e78a984 commit d29c06d

File tree

11 files changed

+218
-179
lines changed

11 files changed

+218
-179
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,4 @@ storybook-static
3434
tsconfig.tsbuildinfo
3535
.cursor
3636
apps/dashboard/node-compile-cache
37+
.tmp/

biome.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@
3535
}
3636
}
3737
},
38+
"formatter": {
39+
"enabled": true,
40+
"indentStyle": "space"
41+
},
3842
"includes": ["package.json"]
3943
}
4044
]

packages/thirdweb/biome.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"$schema": "https://biomejs.dev/schemas/2.0.6/schema.json",
33
"extends": "//",
4+
"root": false,
45
"overrides": [
56
{
67
"assist": {
@@ -10,6 +11,10 @@
1011
}
1112
}
1213
},
14+
"formatter": {
15+
"enabled": true,
16+
"indentStyle": "space"
17+
},
1318
"includes": ["package.json"]
1419
}
1520
]

packages/thirdweb/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"@radix-ui/react-tooltip": "1.2.7",
2424
"@storybook/react": "9.0.15",
2525
"@tanstack/react-query": "5.81.5",
26+
"@thirdweb-dev/api": "workspace:*",
2627
"@thirdweb-dev/engine": "workspace:*",
2728
"@thirdweb-dev/insight": "workspace:*",
2829
"@walletconnect/sign-client": "2.20.1",

packages/thirdweb/src/extensions/common/read/getContractMetadata.test.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,17 @@ describe.runIf(process.env.TW_SECRET_KEY)("shared.getContractMetadata", () => {
77
const metadata = await getContractMetadata({
88
contract: USDT_CONTRACT,
99
});
10-
expect(metadata).toMatchInlineSnapshot(`
11-
{
12-
"name": "Tether USD",
13-
"symbol": "USDT",
14-
}
15-
`);
10+
11+
// Test the existing interface that consumers expect (from the original snapshot)
12+
expect(metadata).toMatchObject({
13+
name: "Tether USD",
14+
symbol: "USDT",
15+
});
16+
17+
// Ensure the required properties exist
18+
expect(metadata).toHaveProperty("name");
19+
expect(metadata).toHaveProperty("symbol");
20+
expect(typeof metadata.name).toBe("string");
21+
expect(typeof metadata.symbol).toBe("string");
1622
});
1723
});

packages/thirdweb/src/extensions/common/read/getContractMetadata.ts

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import {
2+
getContractMetadata as apiGetContractMetadata,
3+
configure,
4+
} from "@thirdweb-dev/api";
15
import type { BaseTransactionOptions } from "../../../transaction/types.js";
26
import { fetchContractMetadata } from "../../../utils/contract/fetchContractMetadata.js";
37
import { contractURI } from "../__generated__/IContractMetadata/read/contractURI.js";
@@ -25,6 +29,72 @@ export async function getContractMetadata(
2529
// biome-ignore lint/suspicious/noExplicitAny: TODO: fix any
2630
[key: string]: any;
2731
}> {
32+
// Configure the API client
33+
configure({
34+
clientId: options.contract.client.clientId,
35+
secretKey: options.contract.client.secretKey,
36+
});
37+
38+
try {
39+
// Try to get metadata from the API first
40+
const response = await apiGetContractMetadata({
41+
path: {
42+
chainId: options.contract.chain.id,
43+
address: options.contract.address,
44+
},
45+
});
46+
47+
if (response.data?.result?.output?.abi) {
48+
// Extract name and symbol from ABI or devdoc/userdoc
49+
const abi = response.data.result.output.abi;
50+
const devdoc = response.data.result.output.devdoc;
51+
const userdoc = response.data.result.output.userdoc;
52+
53+
// Try to find name and symbol from ABI functions
54+
let contractName = "";
55+
let contractSymbol = "";
56+
57+
if (Array.isArray(abi)) {
58+
const nameFunc = abi.find(
59+
(item: any) =>
60+
item.type === "function" &&
61+
item.name === "name" &&
62+
item.inputs?.length === 0,
63+
);
64+
const symbolFunc = abi.find(
65+
(item: any) =>
66+
item.type === "function" &&
67+
item.name === "symbol" &&
68+
item.inputs?.length === 0,
69+
);
70+
71+
if (nameFunc || symbolFunc) {
72+
// Fall back to RPC if we found name/symbol functions in ABI
73+
const [resolvedName, resolvedSymbol] = await Promise.all([
74+
nameFunc ? name(options).catch(() => null) : null,
75+
symbolFunc ? symbol(options).catch(() => null) : null,
76+
]);
77+
contractName = resolvedName || "";
78+
contractSymbol = resolvedSymbol || "";
79+
}
80+
}
81+
82+
return {
83+
name: contractName,
84+
symbol: contractSymbol,
85+
abi: abi,
86+
compiler: response.data.result.compiler,
87+
language: response.data.result.language,
88+
devdoc: devdoc,
89+
userdoc: userdoc,
90+
};
91+
}
92+
} catch (error) {
93+
// API failed, fall back to original implementation
94+
console.debug("Contract metadata API failed, falling back to RPC:", error);
95+
}
96+
97+
// Fallback to original RPC-based implementation
2898
const [resolvedMetadata, resolvedName, resolvedSymbol] = await Promise.all([
2999
contractURI(options)
30100
.then((uri) => {
@@ -41,7 +111,6 @@ export async function getContractMetadata(
41111
symbol(options).catch(() => null),
42112
]);
43113

44-
// TODO: basic parsing?
45114
return {
46115
...resolvedMetadata,
47116
name: resolvedMetadata?.name ?? resolvedName,
Lines changed: 21 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,45 @@
11
import { describe, expect, it } from "vitest";
2-
import { ANVIL_CHAIN, FORKED_ETHEREUM_CHAIN } from "~test/chains.js";
3-
import { TEST_CONTRACT_URI } from "~test/ipfs-uris.js";
42
import { TEST_CLIENT } from "~test/test-clients.js";
5-
import { TEST_ACCOUNT_D } from "~test/test-wallets.js";
6-
import { getContract } from "../../contract/contract.js";
7-
import { mintTo } from "../../extensions/erc20/write/mintTo.js";
8-
import { deployERC20Contract } from "../../extensions/prebuilts/deploy-erc20.js";
9-
import { sendAndConfirmTransaction } from "../../transaction/actions/send-and-confirm-transaction.js";
3+
import { baseSepolia } from "../../chains/chain-definitions/base-sepolia.js";
104
import { getTokenBalance } from "./getTokenBalance.js";
115

12-
const account = TEST_ACCOUNT_D;
6+
// Create a mock account for testing
7+
const testAccount = {
8+
address: "0x742d35Cc6645C0532b6C766684f4b4E99Bf87E8A", // Base deployer address
9+
};
1310

1411
describe.runIf(process.env.TW_SECRET_KEY)("getTokenBalance", () => {
1512
it("should work for native token", async () => {
1613
const result = await getTokenBalance({
17-
account,
18-
chain: FORKED_ETHEREUM_CHAIN,
14+
account: testAccount,
15+
chain: baseSepolia,
1916
client: TEST_CLIENT,
2017
});
2118

22-
expect(result).toStrictEqual({
23-
decimals: 18,
24-
displayValue: "10000",
25-
name: "Ether",
26-
symbol: "ETH",
27-
value: 10000000000000000000000n,
28-
});
19+
expect(result).toBeDefined();
20+
expect(result.decimals).toBe(18);
21+
expect(result.name).toBe("Sepolia Ether");
22+
expect(result.symbol).toBe("ETH");
23+
expect(result.value).toBeTypeOf("bigint");
24+
expect(result.displayValue).toBeTypeOf("string");
2925
});
3026

3127
it("should work for ERC20 token", async () => {
32-
const erc20Address = await deployERC20Contract({
33-
account,
34-
chain: ANVIL_CHAIN,
35-
client: TEST_CLIENT,
36-
params: {
37-
contractURI: TEST_CONTRACT_URI,
38-
name: "",
39-
},
40-
type: "TokenERC20",
41-
});
42-
const erc20Contract = getContract({
43-
address: erc20Address,
44-
chain: ANVIL_CHAIN,
45-
client: TEST_CLIENT,
46-
});
47-
48-
const amount = 1000;
49-
50-
// Mint some tokens
51-
const tx = mintTo({
52-
amount,
53-
contract: erc20Contract,
54-
to: account.address,
55-
});
56-
57-
await sendAndConfirmTransaction({
58-
account,
59-
transaction: tx,
60-
});
28+
// Use a known ERC20 token on Base Sepolia
29+
const usdcAddress = "0x036CbD53842c5426634e7929541eC2318f3dCF7e"; // USDC on Base Sepolia
6130

6231
const result = await getTokenBalance({
63-
account,
64-
chain: ANVIL_CHAIN,
32+
account: testAccount,
33+
chain: baseSepolia,
6534
client: TEST_CLIENT,
66-
tokenAddress: erc20Address,
35+
tokenAddress: usdcAddress,
6736
});
6837

69-
const expectedDecimal = 18;
7038
expect(result).toBeDefined();
71-
expect(result.decimals).toBe(expectedDecimal);
39+
expect(result.decimals).toBe(6); // USDC has 6 decimals
7240
expect(result.symbol).toBeDefined();
7341
expect(result.name).toBeDefined();
74-
expect(result.value).toBe(BigInt(amount) * 10n ** BigInt(expectedDecimal));
75-
expect(result.displayValue).toBe(amount.toString());
42+
expect(result.value).toBeTypeOf("bigint");
43+
expect(result.displayValue).toBeTypeOf("string");
7644
});
7745
});
Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
1-
import type { Chain } from "../../chains/types.js";
21
import {
3-
getChainDecimals,
4-
getChainNativeCurrencyName,
5-
getChainSymbol,
6-
} from "../../chains/utils.js";
2+
getWalletBalance as apiGetWalletBalance,
3+
configure,
4+
} from "@thirdweb-dev/api";
5+
import type { Chain } from "../../chains/types.js";
76
import type { ThirdwebClient } from "../../client/client.js";
8-
import { getContract } from "../../contract/contract.js";
9-
import { eth_getBalance } from "../../rpc/actions/eth_getBalance.js";
10-
import { getRpcClient } from "../../rpc/rpc.js";
11-
import { toTokens } from "../../utils/units.js";
127
import type { Account } from "../interfaces/wallet.js";
138

149
type GetTokenBalanceOptions = {
@@ -43,33 +38,40 @@ export async function getTokenBalance(
4338
options: GetTokenBalanceOptions,
4439
): Promise<GetTokenBalanceResult> {
4540
const { account, client, chain, tokenAddress } = options;
46-
// erc20 case
47-
if (tokenAddress) {
48-
// load balanceOf dynamically to avoid circular dependency
49-
const { getBalance } = await import(
50-
"../../extensions/erc20/read/getBalance.js"
51-
);
52-
return getBalance({
41+
42+
// Configure the API client with credentials from the thirdweb client
43+
configure({
44+
clientId: client.clientId,
45+
secretKey: client.secretKey,
46+
});
47+
48+
const response = await apiGetWalletBalance({
49+
path: {
5350
address: account.address,
54-
contract: getContract({ address: tokenAddress, chain, client }),
55-
});
51+
},
52+
query: {
53+
chainId: [chain.id],
54+
...(tokenAddress && { tokenAddress }),
55+
},
56+
});
57+
58+
if (!response.data?.result || response.data.result.length === 0) {
59+
throw new Error("No balance data returned from API");
5660
}
57-
// native token case
58-
const rpcRequest = getRpcClient({ chain, client });
5961

60-
const [nativeSymbol, nativeDecimals, nativeName, nativeBalance] =
61-
await Promise.all([
62-
getChainSymbol(chain),
63-
getChainDecimals(chain),
64-
getChainNativeCurrencyName(chain),
65-
eth_getBalance(rpcRequest, { address: account.address }),
66-
]);
62+
// Get the first result (should match our chain)
63+
const balanceData = response.data.result[0];
64+
65+
if (!balanceData) {
66+
throw new Error("Balance data not found for the specified chain");
67+
}
6768

69+
// Transform API response to match the existing GetTokenBalanceResult interface
6870
return {
69-
decimals: nativeDecimals,
70-
displayValue: toTokens(nativeBalance, nativeDecimals),
71-
name: nativeName,
72-
symbol: nativeSymbol,
73-
value: nativeBalance,
71+
decimals: balanceData.decimals,
72+
displayValue: balanceData.displayValue,
73+
name: balanceData.name,
74+
symbol: balanceData.symbol,
75+
value: BigInt(balanceData.value),
7476
};
7577
}

0 commit comments

Comments
 (0)