diff --git a/.changeset/fluffy-pigs-drive.md b/.changeset/fluffy-pigs-drive.md new file mode 100644 index 00000000000..5f20b4b5b03 --- /dev/null +++ b/.changeset/fluffy-pigs-drive.md @@ -0,0 +1,95 @@ +--- +"thirdweb": minor +--- + +Enhanced SDK Bridge functionality with the following key updates: + +1. **Breaking Change:** Standardized parameter naming in bridge functions: + - Changed `buyAmountWei` to `amount` in Buy functions + - Changed `sellAmountWei` to `amount` in Sell functions + + Example: + ```ts + // Before + const buyQuote = await buy.quote({ + originChainId: 1, + originTokenAddress: NATIVE_TOKEN_ADDRESS, + destinationChainId: 10, + destinationTokenAddress: NATIVE_TOKEN_ADDRESS, + buyAmountWei: toWei("0.01"), + client: thirdwebClient, + }); + + // After + const buyQuote = await buy.quote({ + originChainId: 1, + originTokenAddress: NATIVE_TOKEN_ADDRESS, + destinationChainId: 10, + destinationTokenAddress: NATIVE_TOKEN_ADDRESS, + amount: toWei("0.01"), + client: thirdwebClient, + }); + ``` + +2. **Enhanced Quote Structure:** Added `steps` array to buy/sell quote responses with detailed token information: + ```ts + // Steps contains detailed information about each step in a cross-chain transaction + steps: [ + { + originToken: { + chainId: 1, + address: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + symbol: "ETH", + name: "Ethereum", + decimals: 18, + priceUsd: 2000, + iconUri: "https://..." + }, + destinationToken: { + chainId: 10, + address: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + symbol: "ETH", + name: "Ethereum", + decimals: 18, + priceUsd: 2000, + iconUri: "https://..." + }, + originAmount: 1000000000000000000n, + destinationAmount: 9980000000000000000n, + estimatedExecutionTimeMs: 1000, + transactions: [/* transactions for this step */] + } + ] + ``` + +3. **Added Purchase Data Support:** Added optional `purchaseData` parameter to Buy and Sell functions: + ```ts + // Example with purchaseData + const quote = await buy.prepare({ + originChainId: 1, + originTokenAddress: NATIVE_TOKEN_ADDRESS, + destinationChainId: 10, + destinationTokenAddress: NATIVE_TOKEN_ADDRESS, + amount: toWei("0.01"), + sender: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709", + receiver: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709", + purchaseData: { + foo: "bar", + }, + client: thirdwebClient, + }); + ``` + +4. **Enhanced Status Responses:** Status responses now include the `purchaseData` field that was provided during the initial transaction: + ```ts + // Status response includes purchaseData + { + status: "COMPLETED", + // ...other status fields + purchaseData: { + foo: "bar" + } + } + ``` + +5. **Updated API Interactions:** Changed from query parameters to JSON body for prepare functions to accommodate complex data. \ No newline at end of file diff --git a/packages/thirdweb/src/bridge/Buy.test.ts b/packages/thirdweb/src/bridge/Buy.test.ts index 5b01db9610e..cbe48b57449 100644 --- a/packages/thirdweb/src/bridge/Buy.test.ts +++ b/packages/thirdweb/src/bridge/Buy.test.ts @@ -10,13 +10,14 @@ describe.runIf(process.env.TW_SECRET_KEY)("Bridge.Buy.quote", () => { originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", destinationChainId: 10, destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", - buyAmountWei: toWei("0.01"), + amount: toWei("0.01"), client: TEST_CLIENT, }); expect(quote).toBeDefined(); expect(quote.destinationAmount).toEqual(toWei("0.01")); expect(quote.intent).toBeDefined(); + expect(quote.steps.length).toBeGreaterThan(0); }); it("should surface any errors", async () => { @@ -26,7 +27,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("Bridge.Buy.quote", () => { originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", destinationChainId: 444, destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", - buyAmountWei: toWei("1000000000"), + amount: toWei("1000000000"), client: TEST_CLIENT, }), ).rejects.toThrowError(); @@ -40,16 +41,20 @@ describe.runIf(process.env.TW_SECRET_KEY)("Bridge.Buy.prepare", () => { originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", destinationChainId: 10, destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", - buyAmountWei: toWei("0.01"), + amount: toWei("0.01"), sender: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709", receiver: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709", client: TEST_CLIENT, + purchaseData: { + foo: "bar", + }, }); expect(quote).toBeDefined(); expect(quote.destinationAmount).toEqual(toWei("0.01")); - expect(quote.transactions).toBeDefined(); - expect(quote.transactions.length).toBeGreaterThan(0); + for (const step of quote.steps) { + expect(step.transactions.length).toBeGreaterThan(0); + } expect(quote.intent).toBeDefined(); }); @@ -60,7 +65,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("Bridge.Buy.prepare", () => { originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", destinationChainId: 444, destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", - buyAmountWei: toWei("1000000000"), + amount: toWei("1000000000"), sender: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709", receiver: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709", client: TEST_CLIENT, diff --git a/packages/thirdweb/src/bridge/Buy.ts b/packages/thirdweb/src/bridge/Buy.ts index 0c145e45c70..05e5b0c5160 100644 --- a/packages/thirdweb/src/bridge/Buy.ts +++ b/packages/thirdweb/src/bridge/Buy.ts @@ -17,7 +17,7 @@ import type { PreparedQuote, Quote } from "./types/Quote.js"; * originTokenAddress: NATIVE_TOKEN_ADDRESS, * destinationChainId: 10, * destinationTokenAddress: NATIVE_TOKEN_ADDRESS, - * buyAmountWei: toWei("0.01"), + * amount: toWei("0.01"), * client: thirdwebClient, * }); * ``` @@ -30,12 +30,37 @@ import type { PreparedQuote, Quote } from "./types/Quote.js"; * blockNumber: 22026509n, * timestamp: 1741730936680, * estimatedExecutionTimeMs: 1000 + * steps: [ + * { + * originToken: { + * chainId: 1, + * address: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + * symbol: "ETH", + * name: "Ethereum", + * decimals: 18, + * priceUsd: 0.0025, + * iconUri: "https://..." + * }, + * destinationToken: { + * chainId: 10, + * address: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + * symbol: "ETH", + * name: "Ethereum", + * decimals: 18, + * priceUsd: 0.0025, + * iconUri: "https://..." + * }, + * originAmount: 10000026098875381n, + * destinationAmount: 1000000000000000000n, + * estimatedExecutionTimeMs: 1000 + * } + * ], * intent: { * originChainId: 1, * originTokenAddress: NATIVE_TOKEN_ADDRESS, * destinationChainId: 10, * destinationTokenAddress: NATIVE_TOKEN_ADDRESS, - * buyAmountWei: 1000000000000000000n + * amount: 1000000000000000000n * } * } * ``` @@ -50,7 +75,7 @@ import type { PreparedQuote, Quote } from "./types/Quote.js"; * @param options.originTokenAddress - The address of the origin token. * @param options.destinationChainId - The chain ID of the destination token. * @param options.destinationTokenAddress - The address of the destination token. - * @param options.buyAmountWei - The amount of the origin token to buy. + * @param options.amount - The amount of the destination token to receive. * @param options.client - Your thirdweb client. * * @returns A promise that resolves to a non-finalized quote for the requested buy. @@ -65,9 +90,10 @@ export async function quote(options: quote.Options): Promise { originTokenAddress, destinationChainId, destinationTokenAddress, - buyAmountWei, client, } = options; + const amount = + "buyAmountWei" in options ? options.buyAmountWei : options.amount; const clientFetch = getClientFetch(client); const url = new URL(`${UNIVERSAL_BRIDGE_URL}/buy/quote`); @@ -75,7 +101,7 @@ export async function quote(options: quote.Options): Promise { url.searchParams.set("originTokenAddress", originTokenAddress); url.searchParams.set("destinationChainId", destinationChainId.toString()); url.searchParams.set("destinationTokenAddress", destinationTokenAddress); - url.searchParams.set("buyAmountWei", buyAmountWei.toString()); + url.searchParams.set("buyAmountWei", amount.toString()); const response = await clientFetch(url.toString()); if (!response.ok) { @@ -92,12 +118,14 @@ export async function quote(options: quote.Options): Promise { blockNumber: data.blockNumber ? BigInt(data.blockNumber) : undefined, timestamp: data.timestamp, estimatedExecutionTimeMs: data.estimatedExecutionTimeMs, + steps: data.steps, intent: { originChainId, originTokenAddress, destinationChainId, destinationTokenAddress, - buyAmountWei, + buyAmountWei: amount, + amount, }, }; } @@ -108,9 +136,15 @@ export declare namespace quote { originTokenAddress: ox__Address.Address; destinationChainId: number; destinationTokenAddress: ox__Address.Address; - buyAmountWei: bigint; client: ThirdwebClient; - }; + } & ( + | { + buyAmountWei: bigint; + } + | { + amount: bigint; + } + ); type Result = Quote & { intent: { @@ -119,6 +153,7 @@ export declare namespace quote { destinationChainId: number; destinationTokenAddress: ox__Address.Address; buyAmountWei: bigint; + amount: bigint; }; }; } @@ -135,7 +170,7 @@ export declare namespace quote { * originTokenAddress: NATIVE_TOKEN_ADDRESS, * destinationChainId: 10, * destinationTokenAddress: NATIVE_TOKEN_ADDRESS, - * buyAmountWei: toWei("0.01"), + * amount: toWei("0.01"), * client: thirdwebClient, * }); * ``` @@ -148,31 +183,56 @@ export declare namespace quote { * blockNumber: 22026509n, * timestamp: 1741730936680, * estimatedExecutionTimeMs: 1000 - * transactions: [ - * { - * action: "approval", - * id: "0x", - * to: "0x...", - * data: "0x...", - * chainId: 10, - * type: "eip1559" - * }, + * steps: [ * { - * action: "buy", - * to: "0x...", - * value: 10000026098875381n, - * data: "0x...", - * chainId: 10, - * type: "eip1559" + * originToken: { + * chainId: 1, + * address: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + * symbol: "ETH", + * name: "Ethereum", + * decimals: 18, + * priceUsd: 2000, + * iconUri: "https://..." + * }, + * destinationToken: { + * chainId: 10, + * address: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + * symbol: "ETH", + * name: "Ethereum", + * decimals: 18, + * priceUsd: 2000, + * iconUri: "https://..." + * }, + * originAmount: 10000026098875381n, + * destinationAmount: 1000000000000000000n, + * estimatedExecutionTimeMs: 1000 + * transactions: [ + * { + * action: "approval", + * id: "0x", + * to: "0x...", + * data: "0x...", + * chainId: 10, + * type: "eip1559" + * }, + * { + * action: "buy", + * to: "0x...", + * value: 10000026098875381n, + * data: "0x...", + * chainId: 10, + * type: "eip1559" + * } + * ] * } * ], * expiration: 1741730936680, * intent: { * originChainId: 1, - * originTokenAddress: NATIVE_TOKEN_ADDRESS, + * originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", * destinationChainId: 10, - * destinationTokenAddress: NATIVE_TOKEN_ADDRESS, - * buyAmountWei: 1000000000000000000n + * destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + * amount: 1000000000000000000n * } * } * ``` @@ -192,12 +252,13 @@ export declare namespace quote { * @param options.originTokenAddress - The address of the origin token. * @param options.destinationChainId - The chain ID of the destination token. * @param options.destinationTokenAddress - The address of the destination token. - * @param options.buyAmountWei - The amount of the origin token to buy. + * @param options.amount - The amount of the destination token to receive. * @param options.sender - The address of the sender. * @param options.receiver - The address of the recipient. + * @param options.purchaseData - Arbitrary data to be passed to the purchase function and included with any webhooks or status calls. * @param options.client - Your thirdweb client. * - * @returns A promise that resolves to a non-finalized quote for the requested buy. + * @returns A promise that resolves to a finalized quote and transactions for the requested buy. * * @throws Will throw an error if there is an issue fetching the quote. * @bridge Buy @@ -211,23 +272,32 @@ export async function prepare( originTokenAddress, destinationChainId, destinationTokenAddress, - buyAmountWei, sender, receiver, client, + amount, + purchaseData, } = options; const clientFetch = getClientFetch(client); const url = new URL(`${UNIVERSAL_BRIDGE_URL}/buy/prepare`); - url.searchParams.set("originChainId", originChainId.toString()); - url.searchParams.set("originTokenAddress", originTokenAddress); - url.searchParams.set("destinationChainId", destinationChainId.toString()); - url.searchParams.set("destinationTokenAddress", destinationTokenAddress); - url.searchParams.set("buyAmountWei", buyAmountWei.toString()); - url.searchParams.set("sender", sender); - url.searchParams.set("receiver", receiver); - const response = await clientFetch(url.toString()); + const response = await clientFetch(url.toString(), { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + buyAmountWei: amount.toString(), + originChainId: originChainId.toString(), + originTokenAddress, + destinationChainId: destinationChainId.toString(), + destinationTokenAddress, + sender, + receiver, + purchaseData, + }), + }); if (!response.ok) { const errorJson = await response.json(); throw new Error( @@ -242,19 +312,21 @@ export async function prepare( blockNumber: data.blockNumber ? BigInt(data.blockNumber) : undefined, timestamp: data.timestamp, estimatedExecutionTimeMs: data.estimatedExecutionTimeMs, - transactions: data.transactions.map((transaction) => ({ - ...transaction, - value: transaction.value ? BigInt(transaction.value) : undefined, - client, - chain: defineChain(transaction.chainId), + steps: data.steps.map((step) => ({ + ...step, + transactions: step.transactions.map((transaction) => ({ + ...transaction, + value: transaction.value ? BigInt(transaction.value) : undefined, + client, + chain: defineChain(transaction.chainId), + })), })), - expiration: data.expiration, intent: { originChainId, originTokenAddress, destinationChainId, destinationTokenAddress, - buyAmountWei, + amount, }, }; } @@ -265,10 +337,11 @@ export declare namespace prepare { originTokenAddress: ox__Address.Address; destinationChainId: number; destinationTokenAddress: ox__Address.Address; - buyAmountWei: bigint; sender: ox__Address.Address; receiver: ox__Address.Address; + amount: bigint; client: ThirdwebClient; + purchaseData?: unknown; }; type Result = PreparedQuote & { @@ -277,7 +350,8 @@ export declare namespace prepare { originTokenAddress: ox__Address.Address; destinationChainId: number; destinationTokenAddress: ox__Address.Address; - buyAmountWei: bigint; + amount: bigint; + purchaseData?: unknown; }; }; } diff --git a/packages/thirdweb/src/bridge/Routes.ts b/packages/thirdweb/src/bridge/Routes.ts index 046d323ef4d..fd31a718f7b 100644 --- a/packages/thirdweb/src/bridge/Routes.ts +++ b/packages/thirdweb/src/bridge/Routes.ts @@ -93,6 +93,7 @@ import type { Route } from "./types/Route.js"; * @param options.destinationChainId - Filter by a specific destination chain ID. * @param options.destinationTokenAddress - Filter by a specific destination token address. * @param options.transactionHash - Filter by a specific transaction hash. + * @param options.maxSteps - Limit the number of steps returned. * @param options.limit - Limit the number of routes returned. * @param options.offset - Offset the number of routes returned. * @@ -109,6 +110,7 @@ export async function routes(options: routes.Options): Promise { originTokenAddress, destinationChainId, destinationTokenAddress, + maxSteps, limit, offset, } = options; @@ -127,6 +129,9 @@ export async function routes(options: routes.Options): Promise { if (destinationTokenAddress) { url.searchParams.set("destinationTokenAddress", destinationTokenAddress); } + if (maxSteps) { + url.searchParams.set("maxSteps", maxSteps.toString()); + } if (limit) { url.searchParams.set("limit", limit.toString()); } @@ -152,6 +157,7 @@ export declare namespace routes { destinationChainId?: number; destinationTokenAddress?: ox__Address.Address; transactionHash?: ox__Hex.Hex; + maxSteps?: number; limit?: number; offset?: number; }; diff --git a/packages/thirdweb/src/bridge/Sell.test.ts b/packages/thirdweb/src/bridge/Sell.test.ts index f2c6809b171..e761e40f7ea 100644 --- a/packages/thirdweb/src/bridge/Sell.test.ts +++ b/packages/thirdweb/src/bridge/Sell.test.ts @@ -10,13 +10,14 @@ describe.runIf(process.env.TW_SECRET_KEY)("Bridge.Sell.quote", () => { originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", destinationChainId: 10, destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", - sellAmountWei: toWei("0.01"), + amount: toWei("0.01"), client: TEST_CLIENT, }); expect(quote).toBeDefined(); expect(quote.originAmount).toEqual(toWei("0.01")); expect(quote.intent).toBeDefined(); + expect(quote.steps.length).toBeGreaterThan(0); }); it("should surface any errors", async () => { @@ -26,7 +27,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("Bridge.Sell.quote", () => { originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", destinationChainId: 444, destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", - sellAmountWei: toWei("1000000000"), + amount: toWei("1000000000"), client: TEST_CLIENT, }), ).rejects.toThrowError(); @@ -40,7 +41,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("Bridge.Sell.prepare", () => { originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", destinationChainId: 10, destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", - sellAmountWei: toWei("0.01"), + amount: toWei("0.01"), sender: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709", receiver: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709", client: TEST_CLIENT, @@ -48,8 +49,9 @@ describe.runIf(process.env.TW_SECRET_KEY)("Bridge.Sell.prepare", () => { expect(quote).toBeDefined(); expect(quote.originAmount).toEqual(toWei("0.01")); - expect(quote.transactions).toBeDefined(); - expect(quote.transactions.length).toBeGreaterThan(0); + for (const step of quote.steps) { + expect(step.transactions.length).toBeGreaterThan(0); + } expect(quote.intent).toBeDefined(); }); @@ -60,7 +62,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("Bridge.Sell.prepare", () => { originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", destinationChainId: 444, destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", - sellAmountWei: toWei("1000000000"), + amount: toWei("1000000000"), sender: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709", receiver: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709", client: TEST_CLIENT, diff --git a/packages/thirdweb/src/bridge/Sell.ts b/packages/thirdweb/src/bridge/Sell.ts index 288df5db033..d646f475f3f 100644 --- a/packages/thirdweb/src/bridge/Sell.ts +++ b/packages/thirdweb/src/bridge/Sell.ts @@ -17,7 +17,7 @@ import type { PreparedQuote, Quote } from "./types/Quote.js"; * originTokenAddress: NATIVE_TOKEN_ADDRESS, * destinationChainId: 10, * destinationTokenAddress: NATIVE_TOKEN_ADDRESS, - * sellAmountWei: toWei("0.01"), + * amount: toWei("0.01"), * client: thirdwebClient, * }); * ``` @@ -30,12 +30,37 @@ import type { PreparedQuote, Quote } from "./types/Quote.js"; * blockNumber: 22026509n, * timestamp: 1741730936680, * estimatedExecutionTimeMs: 1000 + * steps: [ + * { + * originToken: { + * chainId: 1, + * address: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + * symbol: "ETH", + * name: "Ethereum", + * decimals: 18, + * priceUsd: 2000, + * iconUri: "https://..." + * }, + * destinationToken: { + * chainId: 10, + * address: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + * symbol: "ETH", + * name: "Ethereum", + * decimals: 18, + * priceUsd: 2000, + * iconUri: "https://..." + * }, + * originAmount: 1000000000000000000n, + * destinationAmount: 99999979011973735n, + * estimatedExecutionTimeMs: 1000 + * } + * ], * intent: { * originChainId: 1, - * originTokenAddress: NATIVE_TOKEN_ADDRESS, + * originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", * destinationChainId: 10, - * destinationTokenAddress: NATIVE_TOKEN_ADDRESS, - * sellAmountWei: 1000000000000000000n + * destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + * amount: 1000000000000000000n * } * } * ``` @@ -50,7 +75,7 @@ import type { PreparedQuote, Quote } from "./types/Quote.js"; * @param options.originTokenAddress - The address of the origin token. * @param options.destinationChainId - The chain ID of the destination token. * @param options.destinationTokenAddress - The address of the destination token. - * @param options.sellAmountWei - The amount of the origin token to sell. + * @param options.amount - The amount of the origin token to sell. * @param options.client - Your thirdweb client. * * @returns A promise that resolves to a non-finalized quote for the requested sell. @@ -65,7 +90,7 @@ export async function quote(options: quote.Options): Promise { originTokenAddress, destinationChainId, destinationTokenAddress, - sellAmountWei, + amount, client, } = options; @@ -75,7 +100,7 @@ export async function quote(options: quote.Options): Promise { url.searchParams.set("originTokenAddress", originTokenAddress); url.searchParams.set("destinationChainId", destinationChainId.toString()); url.searchParams.set("destinationTokenAddress", destinationTokenAddress); - url.searchParams.set("sellAmountWei", sellAmountWei.toString()); + url.searchParams.set("sellAmountWei", amount.toString()); const response = await clientFetch(url.toString()); if (!response.ok) { @@ -92,12 +117,13 @@ export async function quote(options: quote.Options): Promise { blockNumber: data.blockNumber ? BigInt(data.blockNumber) : undefined, timestamp: data.timestamp, estimatedExecutionTimeMs: data.estimatedExecutionTimeMs, + steps: data.steps, intent: { originChainId, originTokenAddress, destinationChainId, destinationTokenAddress, - sellAmountWei, + amount, }, }; } @@ -108,7 +134,7 @@ export declare namespace quote { originTokenAddress: ox__Address.Address; destinationChainId: number; destinationTokenAddress: ox__Address.Address; - sellAmountWei: bigint; + amount: bigint; client: ThirdwebClient; }; @@ -118,7 +144,7 @@ export declare namespace quote { originTokenAddress: ox__Address.Address; destinationChainId: number; destinationTokenAddress: ox__Address.Address; - sellAmountWei: bigint; + amount: bigint; }; }; } @@ -135,7 +161,7 @@ export declare namespace quote { * originTokenAddress: NATIVE_TOKEN_ADDRESS, * destinationChainId: 10, * destinationTokenAddress: NATIVE_TOKEN_ADDRESS, - * sellAmountWei: toWei("0.01"), + * amount: toWei("0.01"), * client: thirdwebClient, * }); * ``` @@ -148,24 +174,48 @@ export declare namespace quote { * blockNumber: 22026509n, * timestamp: 1741730936680, * estimatedExecutionTimeMs: 1000 - * transactions: [ - * { - * id: "0x...", - * action: "approval", - * to: "0x...", - * data: "0x...", - * chainId: 10, - * type: "eip1559" - * }, + * steps: [ * { - * id: "0x...", - * action: "sell", - * to: "0x...", - * value: 9980000000000000000n, - * data: "0x...", - * chainId: 10, - * type: "eip1559" + * originToken: { + * chainId: 1, + * address: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + * symbol: "ETH", + * name: "Ethereum", + * decimals: 18, + * priceUsd: 2000, + * iconUri: "https://..." + * }, + * destinationToken: { + * chainId: 10, + * address: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + * symbol: "ETH", + * name: "Ethereum", + * decimals: 18, + * priceUsd: 2000, + * iconUri: "https://..." + * }, + * originAmount: 1000000000000000000n, + * destinationAmount: 9980000000000000000n, + * estimatedExecutionTimeMs: 1000 * } + * transactions: [ + * { + * id: "0x...", + * action: "approval", + * to: "0x...", + * data: "0x...", + * chainId: 10, + * type: "eip1559" + * }, + * { + * id: "0x...", + * action: "sell", + * to: "0x...", + * data: "0x...", + * chainId: 10, + * type: "eip1559" + * } + * ], * ], * expiration: 1741730936680, * intent: { @@ -173,7 +223,7 @@ export declare namespace quote { * originTokenAddress: NATIVE_TOKEN_ADDRESS, * destinationChainId: 10, * destinationTokenAddress: NATIVE_TOKEN_ADDRESS, - * sellAmountWei: 1000000000000000000n + * amount: 1000000000000000000n * } * } * ``` @@ -193,12 +243,13 @@ export declare namespace quote { * @param options.originTokenAddress - The address of the origin token. * @param options.destinationChainId - The chain ID of the destination token. * @param options.destinationTokenAddress - The address of the destination token. - * @param options.sellAmountWei - The amount of the origin token to sell. + * @param options.amount - The amount of the origin token to sell. * @param options.sender - The address of the sender. * @param options.receiver - The address of the recipient. + * @param options.purchaseData - Arbitrary data to be passed to the purchase function and included with any webhooks or status calls. * @param options.client - Your thirdweb client. * - * @returns A promise that resolves to a non-finalized quote for the requested buy. + * @returns A promise that resolves to a finalized quote and transactions for the requested sell. * * @throws Will throw an error if there is an issue fetching the quote. * @bridge Sell @@ -212,23 +263,32 @@ export async function prepare( originTokenAddress, destinationChainId, destinationTokenAddress, - sellAmountWei, + amount, sender, receiver, client, + purchaseData, } = options; const clientFetch = getClientFetch(client); const url = new URL(`${UNIVERSAL_BRIDGE_URL}/sell/prepare`); - url.searchParams.set("originChainId", originChainId.toString()); - url.searchParams.set("originTokenAddress", originTokenAddress); - url.searchParams.set("destinationChainId", destinationChainId.toString()); - url.searchParams.set("destinationTokenAddress", destinationTokenAddress); - url.searchParams.set("sellAmountWei", sellAmountWei.toString()); - url.searchParams.set("sender", sender); - url.searchParams.set("receiver", receiver); - const response = await clientFetch(url.toString()); + const response = await clientFetch(url.toString(), { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + sellAmountWei: amount.toString(), + originChainId: originChainId.toString(), + originTokenAddress, + destinationChainId: destinationChainId.toString(), + destinationTokenAddress, + sender, + receiver, + purchaseData, + }), + }); if (!response.ok) { const errorJson = await response.json(); throw new Error( @@ -243,11 +303,14 @@ export async function prepare( blockNumber: data.blockNumber ? BigInt(data.blockNumber) : undefined, timestamp: data.timestamp, estimatedExecutionTimeMs: data.estimatedExecutionTimeMs, - transactions: data.transactions.map((transaction) => ({ - ...transaction, - value: transaction.value ? BigInt(transaction.value) : undefined, - client, - chain: defineChain(transaction.chainId), + steps: data.steps.map((step) => ({ + ...step, + transactions: step.transactions.map((transaction) => ({ + ...transaction, + value: transaction.value ? BigInt(transaction.value) : undefined, + client, + chain: defineChain(transaction.chainId), + })), })), expiration: data.expiration, intent: { @@ -255,7 +318,8 @@ export async function prepare( originTokenAddress, destinationChainId, destinationTokenAddress, - sellAmountWei, + amount, + purchaseData, }, }; } @@ -266,10 +330,11 @@ export declare namespace prepare { originTokenAddress: ox__Address.Address; destinationChainId: number; destinationTokenAddress: ox__Address.Address; - sellAmountWei: bigint; + amount: bigint; sender: ox__Address.Address; receiver: ox__Address.Address; client: ThirdwebClient; + purchaseData?: unknown; }; type Result = PreparedQuote & { @@ -278,7 +343,8 @@ export declare namespace prepare { originTokenAddress: ox__Address.Address; destinationChainId: number; destinationTokenAddress: ox__Address.Address; - sellAmountWei: bigint; + amount: bigint; + purchaseData?: unknown; }; }; } diff --git a/packages/thirdweb/src/bridge/Status.test.ts b/packages/thirdweb/src/bridge/Status.test.ts index 9d6f0304b9c..46021f08b30 100644 --- a/packages/thirdweb/src/bridge/Status.test.ts +++ b/packages/thirdweb/src/bridge/Status.test.ts @@ -7,8 +7,8 @@ describe.runIf(process.env.TW_SECRET_KEY)("Bridge.status", () => { it("should handle successful status", async () => { const result = await status({ transactionHash: - "0x7bedc4693e899fe81a22dac11301e77a12a6e772834bba5b698baf3ebcf86f7a", - chainId: 8453, + "0x5959b9321ec581640db531b80bac53cbd968f3d34fc6cb1d5f4ea75f26df2ad7", + chainId: 137, client: TEST_CLIENT, }); @@ -16,21 +16,24 @@ describe.runIf(process.env.TW_SECRET_KEY)("Bridge.status", () => { expect(result.status).toBe("COMPLETED"); expect(result).toMatchInlineSnapshot(` { - "destinationAmount": 500000n, - "destinationChainId": 466, - "destinationTokenAddress": "0x675C3ce7F43b00045a4Dab954AF36160fb57cB45", - "originAmount": 524750n, - "originChainId": 8453, - "originTokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + "destinationAmount": 502590n, + "destinationChainId": 8453, + "destinationTokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + "originAmount": 507688n, + "originChainId": 137, + "originTokenAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", + "purchaseData": { + "name": "Greg", + }, "status": "COMPLETED", "transactions": [ { - "chainId": 8453, - "transactionHash": "0x7bedc4693e899fe81a22dac11301e77a12a6e772834bba5b698baf3ebcf86f7a", + "chainId": 137, + "transactionHash": "0x5959b9321ec581640db531b80bac53cbd968f3d34fc6cb1d5f4ea75f26df2ad7", }, { - "chainId": 466, - "transactionHash": "0xb0de713fbe44b7939b3c9cfa02c0233ea659d1163cc4462462e12eef57bc17f1", + "chainId": 8453, + "transactionHash": "0xa3fa708d9f8e3bf4f97bb2bc04d4f6f7d27b13eb82fa29fc8596e433ed16295d", }, ], } diff --git a/packages/thirdweb/src/bridge/Status.ts b/packages/thirdweb/src/bridge/Status.ts index dd75ddc689f..cc59e124a6e 100644 --- a/packages/thirdweb/src/bridge/Status.ts +++ b/packages/thirdweb/src/bridge/Status.ts @@ -40,7 +40,10 @@ import type { Status } from "./types/Status.js"; * chainId: 2741, * transactionHash: '0xa70a82f42330f54be95a542e1fcfe6ed2dd9f07fb8c82ae67afb4342319f7433' * } - * ] + * ], + * purchaseData: { + * foo: "bar" + * } * } * ``` * @@ -153,6 +156,7 @@ export async function status(options: status.Options): Promise { originTokenAddress: data.originTokenAddress, destinationTokenAddress: data.destinationTokenAddress, transactions: data.transactions, + purchaseData: data.purchaseData, }; } diff --git a/packages/thirdweb/src/bridge/types/Quote.ts b/packages/thirdweb/src/bridge/types/Quote.ts index ffa51dbfdd3..38c6d24ea4a 100644 --- a/packages/thirdweb/src/bridge/types/Quote.ts +++ b/packages/thirdweb/src/bridge/types/Quote.ts @@ -23,30 +23,99 @@ export type Quote = { * The estimated execution time in milliseconds. */ estimatedExecutionTimeMs?: number | undefined; + /** + * The steps required to complete the quote. + */ + steps: Array<{ + originToken: { + chainId: number; + address: ox__Hex.Hex; + symbol: string; + name: string; + decimals: number; + priceUsd: number; + iconUri: string; + }; + destinationToken: { + chainId: number; + address: ox__Hex.Hex; + symbol: string; + name: string; + decimals: number; + priceUsd: number; + iconUri: string; + }; + originAmount: bigint; + destinationAmount: bigint; + estimatedExecutionTimeMs: number; + }>; }; -export type PreparedQuote = Quote & { +export type PreparedQuote = { + /** + * The input amount (in wei) including fees to be paid. + */ + originAmount: bigint; + /** + * The output amount (in wei) to be received. + */ + destinationAmount: bigint; + /** + * The blocknumber this quote was generated at. + */ + blockNumber?: bigint; + /** + * The timestamp this quote was generated at. + */ + timestamp: number; + /** + * The estimated execution time in milliseconds. + */ + estimatedExecutionTimeMs?: number | undefined; /** * The expiration timestamp for the quote. All transactions must be executed before this timestamp to guarantee successful execution at the specified price. */ expiration?: number | undefined; /** - * A series of [ox](https://oxlib.sh) EIP-1559 transactions that must be executed in sequential order to fulfill the complete route. - */ - transactions: Array<{ - data: ox__Hex.Hex; - to: ox__Hex.Hex; - value?: bigint | undefined; - chainId: number; - /** - * The action this transaction performs. This can be "approval", "transfer", "buy", or "sell". - */ - action: "approval" | "transfer" | "buy" | "sell"; - /** - * The transaction ID, used for tracking purposes. - */ - id: ox__Hex.Hex; - client: ThirdwebClient; - chain: Chain; + * A series of steps required to complete the quote, along with the transactions to execute in order. + */ + steps: Array<{ + originToken: { + chainId: number; + address: ox__Hex.Hex; + symbol: string; + name: string; + decimals: number; + priceUsd: number; + iconUri: string; + }; + destinationToken: { + chainId: number; + address: ox__Hex.Hex; + symbol: string; + name: string; + decimals: number; + priceUsd: number; + iconUri: string; + }; + originAmount: bigint; + destinationAmount: bigint; + estimatedExecutionTimeMs: number; + transactions: Array<{ + data: ox__Hex.Hex; + to: ox__Hex.Hex; + value?: bigint | undefined; + chainId: number; + /** + * The action this transaction performs. This can be "approval", "transfer", "buy", or "sell". + */ + action: "approval" | "transfer" | "buy" | "sell"; + /** + * The transaction ID, used for tracking purposes. + */ + id: ox__Hex.Hex; + client: ThirdwebClient; + chain: Chain; + }>; }>; }; diff --git a/packages/thirdweb/src/bridge/types/Status.ts b/packages/thirdweb/src/bridge/types/Status.ts index d58be311844..5d08a88c5d7 100644 --- a/packages/thirdweb/src/bridge/types/Status.ts +++ b/packages/thirdweb/src/bridge/types/Status.ts @@ -13,6 +13,7 @@ export type Status = chainId: number; transactionHash: ox__Hex.Hex; }>; + purchaseData?: unknown; } | { status: "PENDING";