Skip to content

Commit 9e8c94a

Browse files
gregfromstlclaude
andcommitted
[TOOL-3687] Add Universal Routes and Status bridge functions
This adds Routes and Status functions to the Universal module for querying available bridge routes and checking transaction status. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent eaf6fdb commit 9e8c94a

File tree

10 files changed

+512
-6
lines changed

10 files changed

+512
-6
lines changed
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
export * as Buy from "../universal/Buy.js";
2-
export * as Sell from "../universal/Sell.js";
1+
export * from "../universal/index.js";

packages/thirdweb/src/universal/Buy.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { UNIVERSAL_BRIDGE_URL } from "./constants.js";
1717
* destinationChainId: 10,
1818
* destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
1919
* buyAmountWei: toWei("0.01"),
20-
* client: client,
20+
* client: thirdwebClient,
2121
* });
2222
* ```
2323
*
@@ -131,7 +131,7 @@ export declare namespace quote {
131131
* destinationChainId: 10,
132132
* destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
133133
* buyAmountWei: toWei("0.01"),
134-
* client: client,
134+
* client: thirdwebClient,
135135
* });
136136
* ```
137137
*
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { describe, expect, it } from "vitest";
2+
import { TEST_CLIENT } from "~test/test-clients.js";
3+
import * as Universal from "./index.js";
4+
5+
describe("Universal.routes", () => {
6+
it("should get routes", async () => {
7+
const routes = await Universal.routes({
8+
client: TEST_CLIENT,
9+
});
10+
11+
expect(routes.slice(0, 5)).toMatchInlineSnapshot(`
12+
[
13+
{
14+
"destinationToken": {
15+
"address": "0x12c88a3C30A7AaBC1dd7f2c08a97145F5DCcD830",
16+
"chainId": 1,
17+
"decimals": 18,
18+
"iconUri": "https://assets.coingecko.com/coins/images/37207/standard/G.jpg",
19+
"name": "G7",
20+
"symbol": "G7",
21+
},
22+
"originToken": {
23+
"address": "0x7d8b6CEc10165119c4Ac7843a1e02184789585D8",
24+
"chainId": 33979,
25+
"decimals": 18,
26+
"iconUri": "https://assets.relay.link/icons/currencies/sipher.png",
27+
"name": "Sipher",
28+
"symbol": "SIPHER",
29+
},
30+
},
31+
{
32+
"destinationToken": {
33+
"address": "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
34+
"chainId": 1,
35+
"decimals": 8,
36+
"iconUri": "https://coin-images.coingecko.com/coins/images/7598/large/wrapped_bitcoin_wbtc.png?1696507857",
37+
"name": "Wrapped BTC",
38+
"symbol": "WBTC",
39+
},
40+
"originToken": {
41+
"address": "0x7d8b6CEc10165119c4Ac7843a1e02184789585D8",
42+
"chainId": 33979,
43+
"decimals": 18,
44+
"iconUri": "https://assets.relay.link/icons/currencies/sipher.png",
45+
"name": "Sipher",
46+
"symbol": "SIPHER",
47+
},
48+
},
49+
{
50+
"destinationToken": {
51+
"address": "0x429F0d8233e517f9acf6F0C8293BF35804063a83",
52+
"chainId": 1,
53+
"decimals": 18,
54+
"iconUri": "https://assets.coingecko.com/coins/images/53319/standard/powerloom-200px.png",
55+
"name": "Powerloom Token",
56+
"symbol": "POWER",
57+
},
58+
"originToken": {
59+
"address": "0x7d8b6CEc10165119c4Ac7843a1e02184789585D8",
60+
"chainId": 33979,
61+
"decimals": 18,
62+
"iconUri": "https://assets.relay.link/icons/currencies/sipher.png",
63+
"name": "Sipher",
64+
"symbol": "SIPHER",
65+
},
66+
},
67+
{
68+
"destinationToken": {
69+
"address": "0x4C1746A800D224393fE2470C70A35717eD4eA5F1",
70+
"chainId": 1,
71+
"decimals": 18,
72+
"iconUri": "https://assets.coingecko.com/coins/images/53623/standard/plume-token.png?1736896935",
73+
"name": "PLUME",
74+
"symbol": "PLUME",
75+
},
76+
"originToken": {
77+
"address": "0x7d8b6CEc10165119c4Ac7843a1e02184789585D8",
78+
"chainId": 33979,
79+
"decimals": 18,
80+
"iconUri": "https://assets.relay.link/icons/currencies/sipher.png",
81+
"name": "Sipher",
82+
"symbol": "SIPHER",
83+
},
84+
},
85+
{
86+
"destinationToken": {
87+
"address": "0x4d224452801ACEd8B2F0aebE155379bb5D594381",
88+
"chainId": 1,
89+
"decimals": 18,
90+
"iconUri": "https://coin-images.coingecko.com/coins/images/24383/large/apecoin.jpg?1696523566",
91+
"name": "ApeCoin",
92+
"symbol": "APE",
93+
},
94+
"originToken": {
95+
"address": "0x7d8b6CEc10165119c4Ac7843a1e02184789585D8",
96+
"chainId": 33979,
97+
"decimals": 18,
98+
"iconUri": "https://assets.relay.link/icons/currencies/sipher.png",
99+
"name": "Sipher",
100+
"symbol": "SIPHER",
101+
},
102+
},
103+
]
104+
`);
105+
});
106+
});
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import type { Hex as ox__Hex, Address as ox__Address } from "ox";
2+
import type { ThirdwebClient } from "../client/client.js";
3+
import { getClientFetch } from "../utils/fetch.js";
4+
import { UNIVERSAL_BRIDGE_URL } from "./constants.js";
5+
import type { Route } from "./types/Route.js";
6+
7+
/**
8+
* Retrieves supported Universal Bridge routes based on the provided filters.
9+
*
10+
* When multiple filters are specified, a route must satisfy all filters to be included (it acts as an AND operator).
11+
*
12+
* @example
13+
* ```typescript
14+
* import { Universal } from "thirdweb";
15+
*
16+
* const routes = await Universal.routes({
17+
* client: thirdwebClient,
18+
* });
19+
* ```
20+
*
21+
* Returned routes might look something like:
22+
* ```typescript
23+
* [
24+
* {
25+
* destinationToken: {
26+
* address: "0x12c88a3C30A7AaBC1dd7f2c08a97145F5DCcD830",
27+
* chainId: 1,
28+
* decimals: 18,
29+
* iconUri: "https://assets.coingecko.com/coins/images/37207/standard/G.jpg",
30+
* name: "G7",
31+
* symbol: "G7",
32+
* },
33+
* originToken: {
34+
* address: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
35+
* chainId: 480,
36+
* decimals: 18,
37+
* iconUri: "https://assets.relay.link/icons/1/light.png",
38+
* name: "Ether",
39+
* symbol: "ETH",
40+
* }
41+
* },
42+
* {
43+
* destinationToken: {
44+
* address: "0x4d224452801ACEd8B2F0aebE155379bb5D594381",
45+
* chainId: 1,
46+
* decimals: 18,
47+
* iconUri: "https://coin-images.coingecko.com/coins/images/24383/large/apecoin.jpg?1696523566",
48+
* name: "ApeCoin",
49+
* symbol: "APE",
50+
* },
51+
* originToken: {
52+
* address: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
53+
* chainId: 480,
54+
* decimals: 18,
55+
* iconUri: "https://assets.relay.link/icons/1/light.png",
56+
* name: "Ether",
57+
* symbol: "ETH",
58+
* }
59+
* }
60+
* ]
61+
* ```
62+
*
63+
* You can filter for specific chains or tokens:
64+
* ```typescript
65+
* import { Universal } from "thirdweb";
66+
*
67+
* // Get all routes starting from mainnet ETH
68+
* const routes = await Universal.routes({
69+
* originChainId: 1,
70+
* originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
71+
* client: thirdwebClient,
72+
* });
73+
* ```
74+
*
75+
* The returned routes will be limited based on the API. You can paginate through the results using the `limit` and `offset` parameters:
76+
* ```typescript
77+
* import { Universal } from "thirdweb";
78+
*
79+
* // Get the first 10 routes starting from mainnet ETH
80+
* const routes = await Universal.routes({
81+
* originChainId: 1,
82+
* originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
83+
* limit: 10,
84+
* offset: 0,
85+
* client: thirdwebClient,
86+
* });
87+
* ```
88+
*
89+
* @param options - The options for the quote.
90+
* @param options.client - Your thirdweb client.
91+
* @param options.originChainId - Filter by a specific origin chain ID.
92+
* @param options.originTokenAddress - Filter by a specific origin token address.
93+
* @param options.destinationChainId - Filter by a specific destination chain ID.
94+
* @param options.destinationTokenAddress - Filter by a specific destination token address.
95+
* @param options.transactionHash - Filter by a specific transaction hash.
96+
* @param options.limit - Limit the number of routes returned.
97+
* @param options.offset - Offset the number of routes returned.
98+
*
99+
* @returns A promise that resolves to an array of routes.
100+
*
101+
* @throws Will throw an error if there is an issue fetching the routes.
102+
*/
103+
export async function routes(options: routes.Options): Promise<routes.Result> {
104+
const {
105+
client,
106+
originChainId,
107+
originTokenAddress,
108+
destinationChainId,
109+
destinationTokenAddress,
110+
limit,
111+
offset,
112+
} = options;
113+
114+
const clientFetch = getClientFetch(client);
115+
const url = new URL(`${UNIVERSAL_BRIDGE_URL}/routes`);
116+
if (originChainId) {
117+
url.searchParams.set("originChainId", originChainId.toString());
118+
}
119+
if (originTokenAddress) {
120+
url.searchParams.set("originTokenAddress", originTokenAddress);
121+
}
122+
if (destinationChainId) {
123+
url.searchParams.set("destinationChainId", destinationChainId.toString());
124+
}
125+
if (destinationTokenAddress) {
126+
url.searchParams.set("destinationTokenAddress", destinationTokenAddress);
127+
}
128+
if (limit) {
129+
url.searchParams.set("limit", limit.toString());
130+
}
131+
if (offset) {
132+
url.searchParams.set("offset", offset.toString());
133+
}
134+
135+
const response = await clientFetch(url.toString());
136+
if (!response.ok) {
137+
const errorJson = await response.json();
138+
throw new Error(`${errorJson.code}: ${errorJson.message}`);
139+
}
140+
141+
const { data }: { data: Route[] } = await response.json();
142+
return data;
143+
}
144+
145+
export declare namespace routes {
146+
type Options = {
147+
client: ThirdwebClient;
148+
originChainId?: number;
149+
originTokenAddress?: ox__Address.Address;
150+
destinationChainId?: number;
151+
destinationTokenAddress?: ox__Address.Address;
152+
transactionHash?: ox__Hex.Hex;
153+
limit?: number;
154+
offset?: number;
155+
};
156+
157+
type Result = Route[];
158+
}

packages/thirdweb/src/universal/Sell.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { UNIVERSAL_BRIDGE_URL } from "./constants.js";
1717
* destinationChainId: 10,
1818
* destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
1919
* sellAmountWei: toWei("0.01"),
20-
* client: client,
20+
* client: thirdwebClient,
2121
* });
2222
* ```
2323
*
@@ -131,7 +131,7 @@ export declare namespace quote {
131131
* destinationChainId: 10,
132132
* destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
133133
* sellAmountWei: toWei("0.01"),
134-
* client: client,
134+
* client: thirdwebClient,
135135
* });
136136
* ```
137137
*
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { describe, expect, it } from "vitest";
2+
import { TEST_CLIENT } from "~test/test-clients.js";
3+
import * as Universal from "./index.js";
4+
5+
describe("Universal.status", () => {
6+
it("should get a valid status", async () => {
7+
const status = await Universal.status({
8+
transactionHash:
9+
"0xe199ef82a0b6215221536e18ec512813c1aa10b4f5ed0d4dfdfcd703578da56d",
10+
chainId: 8453,
11+
client: TEST_CLIENT,
12+
});
13+
14+
expect(status).toMatchInlineSnapshot(`
15+
{
16+
"destinationAmount": 188625148000000n,
17+
"destinationChainId": 2741,
18+
"destinationTokenAddress": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
19+
"originAmount": 200000000000000n,
20+
"originChainId": 8453,
21+
"originTokenAddress": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
22+
"status": "completed",
23+
"transactions": [
24+
{
25+
"chainId": 8453,
26+
"transactionHash": "0xe199ef82a0b6215221536e18ec512813c1aa10b4f5ed0d4dfdfcd703578da56d",
27+
},
28+
{
29+
"chainId": 2741,
30+
"transactionHash": "0xa70a82f42330f54be95a542e1fcfe6ed2dd9f07fb8c82ae67afb4342319f7433",
31+
},
32+
],
33+
}
34+
`);
35+
});
36+
});

0 commit comments

Comments
 (0)