Skip to content

Commit ad8cc9a

Browse files
[SDK] Add Bridge.tokens() function to retrieve supported tokens (#7240)
1 parent 4e93539 commit ad8cc9a

File tree

7 files changed

+335
-66
lines changed

7 files changed

+335
-66
lines changed

.changeset/bridge-tokens-function.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
Add `Bridge.tokens()` function to retrieve supported Universal Bridge tokens
6+
7+
New function allows fetching and filtering tokens supported by the Universal Bridge service. Supports filtering by chain ID, token address, symbol, name, and includes pagination with limit/offset parameters.
8+
9+
```typescript
10+
import { Bridge } from "thirdweb";
11+
12+
// Get all supported tokens
13+
const tokens = await Bridge.tokens({
14+
client: thirdwebClient,
15+
});
16+
17+
// Filter tokens by chain and symbol
18+
const ethTokens = await Bridge.tokens({
19+
chainId: 1,
20+
symbol: "USDC",
21+
limit: 50,
22+
client: thirdwebClient,
23+
});
24+
```
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { describe, expect, it } from "vitest";
2+
import { TEST_CLIENT } from "~test/test-clients.js";
3+
import { tokens } from "./Token.js";
4+
5+
describe.runIf(process.env.TW_SECRET_KEY)("tokens", () => {
6+
it("should fetch tokens", async () => {
7+
// Setup
8+
const client = TEST_CLIENT;
9+
10+
// Test
11+
const result = await tokens({ client });
12+
13+
// Verify
14+
expect(result).toBeInstanceOf(Array);
15+
16+
// Basic structure validation
17+
if (result.length > 0) {
18+
const token = result[0];
19+
expect(token).toBeDefined();
20+
expect(token).toHaveProperty("chainId");
21+
expect(token).toHaveProperty("address");
22+
expect(token).toHaveProperty("decimals");
23+
expect(token).toHaveProperty("symbol");
24+
expect(token).toHaveProperty("name");
25+
expect(token).toHaveProperty("priceUsd");
26+
27+
if (token) {
28+
expect(typeof token.chainId).toBe("number");
29+
expect(typeof token.address).toBe("string");
30+
expect(typeof token.decimals).toBe("number");
31+
expect(typeof token.symbol).toBe("string");
32+
expect(typeof token.name).toBe("string");
33+
expect(typeof token.priceUsd).toBe("number");
34+
}
35+
}
36+
});
37+
38+
it("should filter tokens by chainId", async () => {
39+
// Setup
40+
const client = TEST_CLIENT;
41+
42+
// Test
43+
const result = await tokens({
44+
client,
45+
chainId: 1,
46+
});
47+
48+
// Verify
49+
expect(result).toBeInstanceOf(Array);
50+
51+
// All tokens should have chainId 1
52+
for (const token of result) {
53+
expect(token.chainId).toBe(1);
54+
}
55+
});
56+
57+
it("should respect limit parameter", async () => {
58+
// Setup
59+
const client = TEST_CLIENT;
60+
61+
// Test
62+
const result = await tokens({
63+
client,
64+
limit: 5,
65+
});
66+
67+
// Verify
68+
expect(result).toBeInstanceOf(Array);
69+
expect(result.length).toBeLessThanOrEqual(5);
70+
});
71+
72+
it("should filter tokens by symbol", async () => {
73+
// Setup
74+
const client = TEST_CLIENT;
75+
76+
// Test
77+
const result = await tokens({
78+
client,
79+
symbol: "ETH",
80+
});
81+
82+
// Verify
83+
expect(result).toBeInstanceOf(Array);
84+
85+
// All tokens should have symbol "ETH"
86+
for (const token of result) {
87+
expect(token.symbol).toContain("ETH");
88+
}
89+
});
90+
});

packages/thirdweb/src/bridge/Token.ts

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import type { ThirdwebClient } from "../client/client.js";
2+
import { getThirdwebBaseUrl } from "../utils/domains.js";
3+
import { getClientFetch } from "../utils/fetch.js";
4+
import { ApiError } from "./types/Errors.js";
5+
import type { Token } from "./types/Token.js";
6+
7+
/**
8+
* Retrieves supported Universal Bridge tokens based on the provided filters.
9+
*
10+
* When multiple filters are specified, a token must satisfy all filters to be included (it acts as an AND operator).
11+
*
12+
* @example
13+
* ```typescript
14+
* import { Bridge } from "thirdweb";
15+
*
16+
* const tokens = await Bridge.tokens({
17+
* client: thirdwebClient,
18+
* });
19+
* ```
20+
*
21+
* Returned tokens might look something like:
22+
* ```typescript
23+
* [
24+
* {
25+
* chainId: 1,
26+
* address: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
27+
* decimals: 18,
28+
* symbol: "ETH",
29+
* name: "Ethereum",
30+
* iconUri: "https://assets.relay.link/icons/1/light.png",
31+
* priceUsd: 2000.50
32+
* },
33+
* {
34+
* chainId: 1,
35+
* address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
36+
* decimals: 6,
37+
* symbol: "USDC",
38+
* name: "USD Coin",
39+
* iconUri: "https://assets.coingecko.com/coins/images/6319/large/USD_Coin_icon.png",
40+
* priceUsd: 1.00
41+
* }
42+
* ]
43+
* ```
44+
*
45+
* You can filter for specific chains or tokens:
46+
* ```typescript
47+
* import { Bridge } from "thirdweb";
48+
*
49+
* // Get all tokens on Ethereum mainnet
50+
* const ethTokens = await Bridge.tokens({
51+
* chainId: 1,
52+
* client: thirdwebClient,
53+
* });
54+
* ```
55+
*
56+
* You can search for tokens by symbol or name:
57+
* ```typescript
58+
* import { Bridge } from "thirdweb";
59+
*
60+
* // Search for USDC tokens
61+
* const usdcTokens = await Bridge.tokens({
62+
* symbol: "USDC",
63+
* client: thirdwebClient,
64+
* });
65+
*
66+
* // Search for tokens by name
67+
* const ethereumTokens = await Bridge.tokens({
68+
* name: "Ethereum",
69+
* client: thirdwebClient,
70+
* });
71+
* ```
72+
*
73+
* You can filter by a specific token address:
74+
* ```typescript
75+
* import { Bridge } from "thirdweb";
76+
*
77+
* // Get a specific token
78+
* const token = await Bridge.tokens({
79+
* tokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
80+
* client: thirdwebClient,
81+
* });
82+
* ```
83+
*
84+
* The returned tokens will be limited based on the API. You can paginate through the results using the `limit` and `offset` parameters:
85+
* ```typescript
86+
* import { Bridge } from "thirdweb";
87+
*
88+
* // Get the first 50 tokens
89+
* const tokens = await Bridge.tokens({
90+
* limit: 50,
91+
* offset: 0,
92+
* client: thirdwebClient,
93+
* });
94+
*
95+
* // Get the next 50 tokens
96+
* const nextTokens = await Bridge.tokens({
97+
* limit: 50,
98+
* offset: 50,
99+
* client: thirdwebClient,
100+
* });
101+
* ```
102+
*
103+
* @param options - The options for retrieving tokens.
104+
* @param options.client - Your thirdweb client.
105+
* @param options.chainId - Filter by a specific chain ID.
106+
* @param options.tokenAddress - Filter by a specific token address.
107+
* @param options.symbol - Filter by token symbol.
108+
* @param options.name - Filter by token name.
109+
* @param options.limit - Number of tokens to return (min: 1, default: 100).
110+
* @param options.offset - Number of tokens to skip (min: 0, default: 0).
111+
*
112+
* @returns A promise that resolves to an array of tokens.
113+
*
114+
* @throws Will throw an error if there is an issue fetching the tokens.
115+
* @bridge
116+
* @beta
117+
*/
118+
export async function tokens(options: tokens.Options): Promise<tokens.Result> {
119+
const { client, chainId, tokenAddress, symbol, name, limit, offset } =
120+
options;
121+
122+
const clientFetch = getClientFetch(client);
123+
const url = new URL(`${getThirdwebBaseUrl("bridge")}/v1/tokens`);
124+
125+
if (chainId !== null && chainId !== undefined) {
126+
url.searchParams.set("chainId", chainId.toString());
127+
}
128+
if (tokenAddress) {
129+
url.searchParams.set("tokenAddress", tokenAddress);
130+
}
131+
if (symbol) {
132+
url.searchParams.set("symbol", symbol);
133+
}
134+
if (name) {
135+
url.searchParams.set("name", name);
136+
}
137+
if (limit !== undefined) {
138+
url.searchParams.set("limit", limit.toString());
139+
}
140+
if (offset !== null && offset !== undefined) {
141+
url.searchParams.set("offset", offset.toString());
142+
}
143+
144+
const response = await clientFetch(url.toString());
145+
if (!response.ok) {
146+
const errorJson = await response.json();
147+
throw new ApiError({
148+
code: errorJson.code || "UNKNOWN_ERROR",
149+
message: errorJson.message || response.statusText,
150+
correlationId: errorJson.correlationId || undefined,
151+
statusCode: response.status,
152+
});
153+
}
154+
155+
const { data }: { data: Token[] } = await response.json();
156+
return data;
157+
}
158+
159+
export declare namespace tokens {
160+
/**
161+
* Input parameters for {@link Bridge.tokens}.
162+
*/
163+
type Options = {
164+
/** Your {@link ThirdwebClient} instance. */
165+
client: ThirdwebClient;
166+
/** Filter by a specific chain ID. */
167+
chainId?: number | null;
168+
/** Filter by a specific token address. */
169+
tokenAddress?: string;
170+
/** Filter by token symbol. */
171+
symbol?: string;
172+
/** Filter by token name. */
173+
name?: string;
174+
/** Number of tokens to return (min: 1, default: 100). */
175+
limit?: number;
176+
/** Number of tokens to skip (min: 0, default: 0). */
177+
offset?: number | null;
178+
};
179+
180+
/**
181+
* The result returned from {@link Bridge.tokens}.
182+
*/
183+
type Result = Token[];
184+
}

packages/thirdweb/src/bridge/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export * as Webhook from "./Webhook.js";
66
export { status } from "./Status.js";
77
export { routes } from "./Routes.js";
88
export { chains } from "./Chains.js";
9+
export { tokens } from "./Token.js";
910
export { parse } from "./Webhook.js";
1011

1112
export type { Chain } from "./types/Chain.js";

packages/thirdweb/src/pay/convert/cryptoToFiat.ts

Lines changed: 6 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
1-
import { getV1TokensPrice } from "@thirdweb-dev/insight";
21
import type { Address } from "abitype";
32
import type { Chain } from "../../chains/types.js";
43
import type { ThirdwebClient } from "../../client/client.js";
54
import { NATIVE_TOKEN_ADDRESS } from "../../constants/addresses.js";
65
import { getBytecode } from "../../contract/actions/get-bytecode.js";
76
import { getContract } from "../../contract/contract.js";
87
import { isAddress } from "../../utils/address.js";
9-
import { getThirdwebDomains } from "../../utils/domains.js";
10-
import { getClientFetch } from "../../utils/fetch.js";
11-
import { stringify } from "../../utils/json.js";
12-
import { withCache } from "../../utils/promise/withCache.js";
8+
import { getTokenPrice } from "./get-token.js";
139
import type { SupportedFiatCurrency } from "./type.js";
1410

1511
/**
@@ -63,7 +59,7 @@ export type ConvertCryptoToFiatParams = {
6359
export async function convertCryptoToFiat(
6460
options: ConvertCryptoToFiatParams,
6561
): Promise<{ result: number }> {
66-
const { client, fromTokenAddress, to, chain, fromAmount } = options;
62+
const { client, fromTokenAddress, chain, fromAmount } = options;
6763
if (Number(fromAmount) === 0) {
6864
return { result: 0 };
6965
}
@@ -96,34 +92,11 @@ export async function convertCryptoToFiat(
9692
}
9793
}
9894

99-
const result = await withCache(
100-
() =>
101-
getV1TokensPrice({
102-
baseUrl: `https://${getThirdwebDomains().insight}`,
103-
fetch: getClientFetch(client),
104-
query: {
105-
address: fromTokenAddress,
106-
chain_id: [chain.id],
107-
},
108-
}),
109-
{
110-
cacheKey: `convert-fiat-to-crypto-${fromTokenAddress}-${chain.id}`,
111-
cacheTime: 1000 * 60, // 1 minute cache
112-
},
113-
);
114-
115-
if (result.error) {
116-
throw new Error(
117-
`Failed to fetch ${to} value for token (${fromTokenAddress}) on chainId: ${chain.id} - ${result.response.status} ${result.response.statusText} - ${result.error ? stringify(result.error) : "Unknown error"}`,
118-
);
119-
}
120-
121-
const firstResult = result.data?.data[0];
122-
123-
if (!firstResult) {
95+
const price = await getTokenPrice(client, fromTokenAddress, chain.id);
96+
if (!price) {
12497
throw new Error(
125-
`Failed to fetch ${to} value for token (${fromTokenAddress}) on chainId: ${chain.id}`,
98+
`Error: Failed to fetch price for token ${fromTokenAddress} on chainId: ${chain.id}`,
12699
);
127100
}
128-
return { result: firstResult.price_usd * fromAmount };
101+
return { result: price * fromAmount };
129102
}

0 commit comments

Comments
 (0)