Skip to content

Commit 654f879

Browse files
committed
[SDK] Feature: Adds Universal Bridge (#6464)
<!-- ## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes" If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000): ## Notes for the reviewer Anything important to call out? Be sure to also clarify these in your comments. ## How to test Unit tests, playground, etc. --> <!-- start pr-codex --> --- ## PR-Codex overview This PR introduces a new `Bridge` module to the `thirdweb` SDK, enabling users to perform token buy and sell operations across chains, along with route discovery and transaction status checks. ### Detailed summary - Added `Bridge` module for Universal Bridge operations. - Implemented `Bridge.Buy` and `Bridge.Sell` for token transactions. - Created `quote` and `prepare` functions for both buy and sell operations. - Introduced `routes` function to discover available bridge routes. - Added `status` function to check transaction statuses. - Defined TypeScript types: `Route`, `Status`, `Quote`, `PreparedQuote`. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent 279fb91 commit 654f879

File tree

18 files changed

+1527
-57
lines changed

18 files changed

+1527
-57
lines changed

.changeset/clear-olives-know.md

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
---
2+
"thirdweb": minor
3+
---
4+
5+
Adds a new `Bridge` module to the thirdweb SDK to access the Universal Bridge.
6+
7+
## Features
8+
9+
### Buy & Sell Operations
10+
11+
The Bridge module makes it easy to buy and sell tokens across chains:
12+
13+
- `Bridge.Buy` - For specifying the destination amount you want to receive
14+
- `Bridge.Sell` - For specifying the origin amount you want to send
15+
16+
Each operation provides two functions:
17+
1. `quote` - Get an estimate without connecting a wallet
18+
2. `prepare` - Get a finalized quote with transaction data
19+
20+
#### Buy Example
21+
22+
```typescript
23+
import { Bridge, toWei, NATIVE_TOKEN_ADDRESS } from "thirdweb";
24+
25+
// First, get a quote to see approximately how much you'll pay
26+
const buyQuote = await Bridge.Buy.quote({
27+
originChainId: 1, // Ethereum
28+
originTokenAddress: NATIVE_TOKEN_ADDRESS,
29+
destinationChainId: 10, // Optimism
30+
destinationTokenAddress: NATIVE_TOKEN_ADDRESS,
31+
buyAmountWei: toWei("0.01"), // I want to receive 0.01 ETH on Optimism
32+
client: thirdwebClient,
33+
});
34+
35+
console.log(`To get ${buyQuote.destinationAmount} wei on destination chain, you need to pay ${buyQuote.originAmount} wei`);
36+
37+
// When ready to execute, prepare the transaction
38+
const preparedBuy = await Bridge.Buy.prepare({
39+
originChainId: 1,
40+
originTokenAddress: NATIVE_TOKEN_ADDRESS,
41+
destinationChainId: 10,
42+
destinationTokenAddress: NATIVE_TOKEN_ADDRESS,
43+
buyAmountWei: toWei("0.01"),
44+
sender: "0x...", // Your wallet address
45+
receiver: "0x...", // Recipient address (can be the same as sender)
46+
client: thirdwebClient,
47+
});
48+
49+
// The prepared quote contains the transactions you need to execute
50+
console.log(`Transactions to execute: ${preparedBuy.transactions.length}`);
51+
```
52+
53+
#### Sell Example
54+
55+
```typescript
56+
import { Bridge, toWei } from "thirdweb";
57+
58+
// First, get a quote to see approximately how much you'll receive
59+
const sellQuote = await Bridge.Sell.quote({
60+
originChainId: 1, // Ethereum
61+
originTokenAddress: NATIVE_TOKEN_ADDRESS,
62+
destinationChainId: 10, // Optimism
63+
destinationTokenAddress: NATIVE_TOKEN_ADDRESS,
64+
sellAmountWei: toWei("0.01"), // I want to sell 0.01 ETH from Ethereum
65+
client: thirdwebClient,
66+
});
67+
68+
console.log(`If you send ${sellQuote.originAmount} wei, you'll receive approximately ${sellQuote.destinationAmount} wei`);
69+
70+
// When ready to execute, prepare the transaction
71+
const preparedSell = await Bridge.Sell.prepare({
72+
originChainId: 1,
73+
originTokenAddress: NATIVE_TOKEN_ADDRESS,
74+
destinationChainId: 10,
75+
destinationTokenAddress: NATIVE_TOKEN_ADDRESS,
76+
sellAmountWei: toWei("0.01"),
77+
sender: "0x...", // Your wallet address
78+
receiver: "0x...", // Recipient address (can be the same as sender)
79+
client: thirdwebClient,
80+
});
81+
82+
// Execute the transactions in sequence
83+
for (const tx of preparedSell.transactions) {
84+
// Send the transaction using your wallet
85+
// Wait for it to be mined
86+
}
87+
```
88+
89+
### Bridge Routes
90+
91+
You can discover available bridge routes using the `routes` function:
92+
93+
```typescript
94+
import { Bridge, NATIVE_TOKEN_ADDRESS } from "thirdweb";
95+
96+
// Get all available routes
97+
const allRoutes = await Bridge.routes({
98+
client: thirdwebClient,
99+
});
100+
101+
// Filter routes for a specific token or chain
102+
const filteredRoutes = await Bridge.routes({
103+
originChainId: 1, // From Ethereum
104+
originTokenAddress: NATIVE_TOKEN_ADDRESS,
105+
destinationChainId: 10, // To Optimism
106+
client: thirdwebClient,
107+
});
108+
109+
// Paginate through routes
110+
const paginatedRoutes = await Bridge.routes({
111+
limit: 10,
112+
offset: 0,
113+
client: thirdwebClient,
114+
});
115+
```
116+
117+
### Bridge Transaction Status
118+
119+
After executing bridge transactions, you can check their status:
120+
121+
```typescript
122+
import { Bridge } from "thirdweb";
123+
124+
// Check the status of a bridge transaction
125+
const bridgeStatus = await Bridge.status({
126+
transactionHash: "0xe199ef82a0b6215221536e18ec512813c1aa10b4f5ed0d4dfdfcd703578da56d",
127+
chainId: 8453, // The chain ID where the transaction was initiated
128+
client: thirdwebClient,
129+
});
130+
131+
// The status will be one of: "COMPLETED", "PENDING", "FAILED", or "NOT_FOUND"
132+
if (bridgeStatus.status === "completed") {
133+
console.log(`
134+
Bridge completed!
135+
Sent: ${bridgeStatus.originAmount} wei on chain ${bridgeStatus.originChainId}
136+
Received: ${bridgeStatus.destinationAmount} wei on chain ${bridgeStatus.destinationChainId}
137+
`);
138+
} else if (bridgeStatus.status === "pending") {
139+
console.log("Bridge transaction is still pending...");
140+
} else {
141+
console.log("Bridge transaction failed");
142+
}
143+
```
144+
145+
## Error Handling
146+
147+
The Bridge module provides consistent error handling with descriptive error messages:
148+
149+
```typescript
150+
try {
151+
await Bridge.Buy.quote({
152+
// ...params
153+
});
154+
} catch (error) {
155+
// Errors will have the format: "ErrorCode | Error message details"
156+
console.error(error.message); // e.g. "AmountTooHigh | The provided amount is too high for the requested route."
157+
}
158+
```
159+
160+
## Types
161+
162+
The Bridge module exports the following TypeScript types:
163+
164+
- `Route` - Describes a bridge route between chains and tokens
165+
- `Status` - Represents the status of a bridge transaction
166+
- `Quote` - Contains quote information for a bridge transaction
167+
- `PreparedQuote` - Extends Quote with transaction data
168+
169+
## Integration
170+
171+
The Bridge module is accessible as a top-level export:
172+
173+
```typescript
174+
import { Bridge } from "thirdweb";
175+
```
176+
177+
Use `Bridge.Buy`, `Bridge.Sell`, `Bridge.routes`, and `Bridge.status` to access the corresponding functionality.

packages/thirdweb/package.json

Lines changed: 25 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -128,67 +128,35 @@
128128
"import": "./dist/esm/exports/ai.js",
129129
"default": "./dist/cjs/exports/ai.js"
130130
},
131+
"./bridge": {
132+
"types": "./dist/types/exports/bridge.d.ts",
133+
"import": "./dist/esm/exports/bridge.js",
134+
"default": "./dist/cjs/exports/bridge.js"
135+
},
131136
"./package.json": "./package.json"
132137
},
133138
"typesVersions": {
134139
"*": {
135-
"adapters/*": [
136-
"./dist/types/exports/adapters/*.d.ts"
137-
],
138-
"auth": [
139-
"./dist/types/exports/auth.d.ts"
140-
],
141-
"chains": [
142-
"./dist/types/exports/chains.d.ts"
143-
],
144-
"contract": [
145-
"./dist/types/exports/contract.d.ts"
146-
],
147-
"deploys": [
148-
"./dist/types/exports/deploys.d.ts"
149-
],
150-
"event": [
151-
"./dist/types/exports/event.d.ts"
152-
],
153-
"extensions/*": [
154-
"./dist/types/exports/extensions/*.d.ts"
155-
],
156-
"pay": [
157-
"./dist/types/exports/pay.d.ts"
158-
],
159-
"react": [
160-
"./dist/types/exports/react.d.ts"
161-
],
162-
"react-native": [
163-
"./dist/types/exports/react-native.d.ts"
164-
],
165-
"rpc": [
166-
"./dist/types/exports/rpc.d.ts"
167-
],
168-
"storage": [
169-
"./dist/types/exports/storage.d.ts"
170-
],
171-
"transaction": [
172-
"./dist/types/exports/transaction.d.ts"
173-
],
174-
"utils": [
175-
"./dist/types/exports/utils.d.ts"
176-
],
177-
"wallets": [
178-
"./dist/types/exports/wallets.d.ts"
179-
],
180-
"wallets/*": [
181-
"./dist/types/exports/wallets/*.d.ts"
182-
],
183-
"modules": [
184-
"./dist/types/exports/modules.d.ts"
185-
],
186-
"social": [
187-
"./dist/types/exports/social.d.ts"
188-
],
189-
"ai": [
190-
"./dist/types/exports/ai.d.ts"
191-
]
140+
"adapters/*": ["./dist/types/exports/adapters/*.d.ts"],
141+
"auth": ["./dist/types/exports/auth.d.ts"],
142+
"chains": ["./dist/types/exports/chains.d.ts"],
143+
"contract": ["./dist/types/exports/contract.d.ts"],
144+
"deploys": ["./dist/types/exports/deploys.d.ts"],
145+
"event": ["./dist/types/exports/event.d.ts"],
146+
"extensions/*": ["./dist/types/exports/extensions/*.d.ts"],
147+
"pay": ["./dist/types/exports/pay.d.ts"],
148+
"react": ["./dist/types/exports/react.d.ts"],
149+
"react-native": ["./dist/types/exports/react-native.d.ts"],
150+
"rpc": ["./dist/types/exports/rpc.d.ts"],
151+
"storage": ["./dist/types/exports/storage.d.ts"],
152+
"transaction": ["./dist/types/exports/transaction.d.ts"],
153+
"utils": ["./dist/types/exports/utils.d.ts"],
154+
"wallets": ["./dist/types/exports/wallets.d.ts"],
155+
"wallets/*": ["./dist/types/exports/wallets/*.d.ts"],
156+
"modules": ["./dist/types/exports/modules.d.ts"],
157+
"social": ["./dist/types/exports/social.d.ts"],
158+
"ai": ["./dist/types/exports/ai.d.ts"],
159+
"bridge": ["./dist/types/exports/bridge.d.ts"]
192160
}
193161
},
194162
"browser": {
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { toWei } from "src/utils/units.js";
2+
import { describe, expect, it } from "vitest";
3+
import { TEST_CLIENT } from "~test/test-clients.js";
4+
import * as Buy from "./Buy.js";
5+
6+
describe("Bridge.Buy.quote", () => {
7+
it("should get a valid quote", async () => {
8+
const quote = await Buy.quote({
9+
originChainId: 1,
10+
originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
11+
destinationChainId: 10,
12+
destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
13+
buyAmountWei: toWei("0.01"),
14+
client: TEST_CLIENT,
15+
});
16+
17+
expect(quote).toBeDefined();
18+
expect(quote.destinationAmount).toEqual(toWei("0.01"));
19+
expect(quote.intent).toBeDefined();
20+
});
21+
22+
it("should surface any errors", async () => {
23+
await expect(
24+
Buy.quote({
25+
originChainId: 1,
26+
originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
27+
destinationChainId: 10,
28+
destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
29+
buyAmountWei: toWei("1000000000"),
30+
client: TEST_CLIENT,
31+
}),
32+
).rejects.toThrowErrorMatchingInlineSnapshot(
33+
`[Error: AMOUNT_TOO_HIGH | The provided amount is too high for the requested route.]`,
34+
);
35+
});
36+
});
37+
38+
describe("Bridge.Buy.prepare", () => {
39+
it("should get a valid prepared quote", async () => {
40+
const quote = await Buy.prepare({
41+
originChainId: 1,
42+
originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
43+
destinationChainId: 10,
44+
destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
45+
buyAmountWei: toWei("0.01"),
46+
sender: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
47+
receiver: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
48+
client: TEST_CLIENT,
49+
});
50+
51+
expect(quote).toBeDefined();
52+
expect(quote.destinationAmount).toEqual(toWei("0.01"));
53+
expect(quote.transactions).toBeDefined();
54+
expect(quote.transactions.length).toBeGreaterThan(0);
55+
expect(quote.intent).toBeDefined();
56+
});
57+
58+
it("should surface any errors", async () => {
59+
await expect(
60+
Buy.prepare({
61+
originChainId: 1,
62+
originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
63+
destinationChainId: 10,
64+
destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
65+
buyAmountWei: toWei("1000000000"),
66+
sender: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
67+
receiver: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
68+
client: TEST_CLIENT,
69+
}),
70+
).rejects.toThrowErrorMatchingInlineSnapshot(
71+
`[Error: AMOUNT_TOO_HIGH | The provided amount is too high for the requested route.]`,
72+
);
73+
});
74+
});

0 commit comments

Comments
 (0)