Skip to content

[SDK] Add Bridge.tokens() function to retrieve supported tokens #7240

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .changeset/bridge-tokens-function.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
"thirdweb": patch
---

Add `Bridge.tokens()` function to retrieve supported Universal Bridge tokens

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.

```typescript
import { Bridge } from "thirdweb";

// Get all supported tokens
const tokens = await Bridge.tokens({
client: thirdwebClient,
});

// Filter tokens by chain and symbol
const ethTokens = await Bridge.tokens({
chainId: 1,
symbol: "USDC",
limit: 50,
client: thirdwebClient,
});
```
90 changes: 90 additions & 0 deletions packages/thirdweb/src/bridge/Token.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { describe, expect, it } from "vitest";
import { TEST_CLIENT } from "~test/test-clients.js";
import { tokens } from "./Token.js";

describe.runIf(process.env.TW_SECRET_KEY)("tokens", () => {
it("should fetch tokens", async () => {
// Setup
const client = TEST_CLIENT;

// Test
const result = await tokens({ client });

// Verify
expect(result).toBeInstanceOf(Array);

// Basic structure validation
if (result.length > 0) {
const token = result[0];
expect(token).toBeDefined();
expect(token).toHaveProperty("chainId");
expect(token).toHaveProperty("address");
expect(token).toHaveProperty("decimals");
expect(token).toHaveProperty("symbol");
expect(token).toHaveProperty("name");
expect(token).toHaveProperty("priceUsd");

if (token) {
expect(typeof token.chainId).toBe("number");
expect(typeof token.address).toBe("string");
expect(typeof token.decimals).toBe("number");
expect(typeof token.symbol).toBe("string");
expect(typeof token.name).toBe("string");
expect(typeof token.priceUsd).toBe("number");
}
}
});

it("should filter tokens by chainId", async () => {
// Setup
const client = TEST_CLIENT;

// Test
const result = await tokens({
client,
chainId: 1,
});

// Verify
expect(result).toBeInstanceOf(Array);

// All tokens should have chainId 1
for (const token of result) {
expect(token.chainId).toBe(1);
}
});

it("should respect limit parameter", async () => {
// Setup
const client = TEST_CLIENT;

// Test
const result = await tokens({
client,
limit: 5,
});

// Verify
expect(result).toBeInstanceOf(Array);
expect(result.length).toBeLessThanOrEqual(5);
});

it("should filter tokens by symbol", async () => {
// Setup
const client = TEST_CLIENT;

// Test
const result = await tokens({
client,
symbol: "ETH",
});

// Verify
expect(result).toBeInstanceOf(Array);

// All tokens should have symbol "ETH"
for (const token of result) {
expect(token.symbol).toContain("ETH");
}
});
});
184 changes: 184 additions & 0 deletions packages/thirdweb/src/bridge/Token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import type { ThirdwebClient } from "../client/client.js";
import { getThirdwebBaseUrl } from "../utils/domains.js";
import { getClientFetch } from "../utils/fetch.js";
import { ApiError } from "./types/Errors.js";
import type { Token } from "./types/Token.js";

/**
* Retrieves supported Universal Bridge tokens based on the provided filters.
*
* When multiple filters are specified, a token must satisfy all filters to be included (it acts as an AND operator).
*
* @example
* ```typescript
* import { Bridge } from "thirdweb";
*
* const tokens = await Bridge.tokens({
* client: thirdwebClient,
* });
* ```
*
* Returned tokens might look something like:
* ```typescript
* [
* {
* chainId: 1,
* address: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
* decimals: 18,
* symbol: "ETH",
* name: "Ethereum",
* iconUri: "https://assets.relay.link/icons/1/light.png",
* priceUsd: 2000.50
* },
* {
* chainId: 1,
* address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
* decimals: 6,
* symbol: "USDC",
* name: "USD Coin",
* iconUri: "https://assets.coingecko.com/coins/images/6319/large/USD_Coin_icon.png",
* priceUsd: 1.00
* }
* ]
* ```
*
* You can filter for specific chains or tokens:
* ```typescript
* import { Bridge } from "thirdweb";
*
* // Get all tokens on Ethereum mainnet
* const ethTokens = await Bridge.tokens({
* chainId: 1,
* client: thirdwebClient,
* });
* ```
*
* You can search for tokens by symbol or name:
* ```typescript
* import { Bridge } from "thirdweb";
*
* // Search for USDC tokens
* const usdcTokens = await Bridge.tokens({
* symbol: "USDC",
* client: thirdwebClient,
* });
*
* // Search for tokens by name
* const ethereumTokens = await Bridge.tokens({
* name: "Ethereum",
* client: thirdwebClient,
* });
* ```
*
* You can filter by a specific token address:
* ```typescript
* import { Bridge } from "thirdweb";
*
* // Get a specific token
* const token = await Bridge.tokens({
* tokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
* client: thirdwebClient,
* });
* ```
*
* The returned tokens will be limited based on the API. You can paginate through the results using the `limit` and `offset` parameters:
* ```typescript
* import { Bridge } from "thirdweb";
*
* // Get the first 50 tokens
* const tokens = await Bridge.tokens({
* limit: 50,
* offset: 0,
* client: thirdwebClient,
* });
*
* // Get the next 50 tokens
* const nextTokens = await Bridge.tokens({
* limit: 50,
* offset: 50,
* client: thirdwebClient,
* });
* ```
*
* @param options - The options for retrieving tokens.
* @param options.client - Your thirdweb client.
* @param options.chainId - Filter by a specific chain ID.
* @param options.tokenAddress - Filter by a specific token address.
* @param options.symbol - Filter by token symbol.
* @param options.name - Filter by token name.
* @param options.limit - Number of tokens to return (min: 1, default: 100).
* @param options.offset - Number of tokens to skip (min: 0, default: 0).
*
* @returns A promise that resolves to an array of tokens.
*
* @throws Will throw an error if there is an issue fetching the tokens.
* @bridge
* @beta
*/
export async function tokens(options: tokens.Options): Promise<tokens.Result> {
const { client, chainId, tokenAddress, symbol, name, limit, offset } =
options;

const clientFetch = getClientFetch(client);
const url = new URL(`${getThirdwebBaseUrl("bridge")}/v1/tokens`);

if (chainId !== null && chainId !== undefined) {
url.searchParams.set("chainId", chainId.toString());
}
if (tokenAddress) {
url.searchParams.set("tokenAddress", tokenAddress);
}
if (symbol) {
url.searchParams.set("symbol", symbol);
}
if (name) {
url.searchParams.set("name", name);
}

Check warning on line 136 in packages/thirdweb/src/bridge/Token.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/bridge/Token.ts#L135-L136

Added lines #L135 - L136 were not covered by tests
if (limit !== undefined) {
url.searchParams.set("limit", limit.toString());
}
if (offset !== null && offset !== undefined) {
url.searchParams.set("offset", offset.toString());
}

Check warning on line 142 in packages/thirdweb/src/bridge/Token.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/bridge/Token.ts#L141-L142

Added lines #L141 - L142 were not covered by tests

const response = await clientFetch(url.toString());
if (!response.ok) {
const errorJson = await response.json();
throw new ApiError({
code: errorJson.code || "UNKNOWN_ERROR",
message: errorJson.message || response.statusText,
correlationId: errorJson.correlationId || undefined,
statusCode: response.status,
});
}

Check warning on line 153 in packages/thirdweb/src/bridge/Token.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/bridge/Token.ts#L146-L153

Added lines #L146 - L153 were not covered by tests

const { data }: { data: Token[] } = await response.json();
return data;
}

export declare namespace tokens {
/**
* Input parameters for {@link Bridge.tokens}.
*/
type Options = {
/** Your {@link ThirdwebClient} instance. */
client: ThirdwebClient;
/** Filter by a specific chain ID. */
chainId?: number | null;
/** Filter by a specific token address. */
tokenAddress?: string;
/** Filter by token symbol. */
symbol?: string;
/** Filter by token name. */
name?: string;
/** Number of tokens to return (min: 1, default: 100). */
limit?: number;
/** Number of tokens to skip (min: 0, default: 0). */
offset?: number | null;
};

/**
* The result returned from {@link Bridge.tokens}.
*/
type Result = Token[];
}
1 change: 1 addition & 0 deletions packages/thirdweb/src/bridge/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * as Webhook from "./Webhook.js";
export { status } from "./Status.js";
export { routes } from "./Routes.js";
export { chains } from "./Chains.js";
export { tokens } from "./Token.js";
export { parse } from "./Webhook.js";

export type { Chain } from "./types/Chain.js";
Expand Down
39 changes: 6 additions & 33 deletions packages/thirdweb/src/pay/convert/cryptoToFiat.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import { getV1TokensPrice } from "@thirdweb-dev/insight";
import type { Address } from "abitype";
import type { Chain } from "../../chains/types.js";
import type { ThirdwebClient } from "../../client/client.js";
import { NATIVE_TOKEN_ADDRESS } from "../../constants/addresses.js";
import { getBytecode } from "../../contract/actions/get-bytecode.js";
import { getContract } from "../../contract/contract.js";
import { isAddress } from "../../utils/address.js";
import { getThirdwebDomains } from "../../utils/domains.js";
import { getClientFetch } from "../../utils/fetch.js";
import { stringify } from "../../utils/json.js";
import { withCache } from "../../utils/promise/withCache.js";
import { getTokenPrice } from "./get-token.js";
import type { SupportedFiatCurrency } from "./type.js";

/**
Expand Down Expand Up @@ -63,7 +59,7 @@
export async function convertCryptoToFiat(
options: ConvertCryptoToFiatParams,
): Promise<{ result: number }> {
const { client, fromTokenAddress, to, chain, fromAmount } = options;
const { client, fromTokenAddress, chain, fromAmount } = options;
if (Number(fromAmount) === 0) {
return { result: 0 };
}
Expand Down Expand Up @@ -96,34 +92,11 @@
}
}

const result = await withCache(
() =>
getV1TokensPrice({
baseUrl: `https://${getThirdwebDomains().insight}`,
fetch: getClientFetch(client),
query: {
address: fromTokenAddress,
chain_id: [chain.id],
},
}),
{
cacheKey: `convert-fiat-to-crypto-${fromTokenAddress}-${chain.id}`,
cacheTime: 1000 * 60, // 1 minute cache
},
);

if (result.error) {
throw new Error(
`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"}`,
);
}

const firstResult = result.data?.data[0];

if (!firstResult) {
const price = await getTokenPrice(client, fromTokenAddress, chain.id);
if (!price) {
throw new Error(
`Failed to fetch ${to} value for token (${fromTokenAddress}) on chainId: ${chain.id}`,
`Error: Failed to fetch price for token ${fromTokenAddress} on chainId: ${chain.id}`,

Check warning on line 98 in packages/thirdweb/src/pay/convert/cryptoToFiat.ts

View check run for this annotation

Codecov / codecov/patch

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

Added line #L98 was not covered by tests
);
}
return { result: firstResult.price_usd * fromAmount };
return { result: price * fromAmount };
}
Loading
Loading