From 845963f505c6d439ca9f5db4955936c2a8ab21e8 Mon Sep 17 00:00:00 2001 From: gregfromstl Date: Mon, 5 May 2025 20:06:04 -0700 Subject: [PATCH 1/3] feat: add maxSteps to quote functions --- packages/thirdweb/src/bridge/Buy.test.ts | 36 +++++++++++++ packages/thirdweb/src/bridge/Buy.ts | 62 +++++++++++++++++++++-- packages/thirdweb/src/bridge/Sell.test.ts | 36 +++++++++++++ packages/thirdweb/src/bridge/Sell.ts | 56 +++++++++++++++++++- 4 files changed, 185 insertions(+), 5 deletions(-) diff --git a/packages/thirdweb/src/bridge/Buy.test.ts b/packages/thirdweb/src/bridge/Buy.test.ts index cbe48b57449..917561a4872 100644 --- a/packages/thirdweb/src/bridge/Buy.test.ts +++ b/packages/thirdweb/src/bridge/Buy.test.ts @@ -32,6 +32,23 @@ describe.runIf(process.env.TW_SECRET_KEY)("Bridge.Buy.quote", () => { }), ).rejects.toThrowError(); }); + + it("should limit quotes to routes with a certain number of steps", async () => { + const quote = await Buy.quote({ + originChainId: 1, + originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + destinationChainId: 10, + destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + amount: toWei("0.01"), + maxSteps: 2, + client: TEST_CLIENT, + }); + + expect(quote).toBeDefined(); + expect(quote.destinationAmount).toEqual(toWei("0.01")); + expect(quote.intent).toBeDefined(); + expect(quote.steps.length).toBeLessThanOrEqual(2); + }); }); describe.runIf(process.env.TW_SECRET_KEY)("Bridge.Buy.prepare", () => { @@ -72,4 +89,23 @@ describe.runIf(process.env.TW_SECRET_KEY)("Bridge.Buy.prepare", () => { }), ).rejects.toThrowError(); }); + + it("should limit quotes to routes with a certain number of steps", async () => { + const quote = await Buy.prepare({ + originChainId: 1, + originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + destinationChainId: 10, + destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + amount: toWei("0.01"), + maxSteps: 2, + sender: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709", + receiver: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709", + client: TEST_CLIENT, + }); + + expect(quote).toBeDefined(); + expect(quote.destinationAmount).toEqual(toWei("0.01")); + expect(quote.steps.length).toBeLessThanOrEqual(2); + expect(quote.intent).toBeDefined(); + }); }); diff --git a/packages/thirdweb/src/bridge/Buy.ts b/packages/thirdweb/src/bridge/Buy.ts index 05e5b0c5160..69b1580bd9c 100644 --- a/packages/thirdweb/src/bridge/Buy.ts +++ b/packages/thirdweb/src/bridge/Buy.ts @@ -4,6 +4,7 @@ import type { ThirdwebClient } from "../client/client.js"; import { getClientFetch } from "../utils/fetch.js"; import { UNIVERSAL_BRIDGE_URL } from "./constants.js"; import type { PreparedQuote, Quote } from "./types/Quote.js"; +import { stringify } from "../utils/json.js"; /** * Retrieves a Universal Bridge quote for the provided buy intent. The quote will specify the necessary `originAmount` to receive the desired `destinationAmount`, which is specified with the `buyAmountWei` option. @@ -70,6 +71,20 @@ import type { PreparedQuote, Quote } from "./types/Quote.js"; * * You can access this functions input and output types with `Buy.quote.Options` and `Buy.quote.Result`, respectively. * + * To limit quotes to routes that have a certain number of steps involved, use the `maxSteps` option. + * + * ```ts + * const quote = await Bridge.Buy.quote({ + * originChainId: 1, + * originTokenAddress: NATIVE_TOKEN_ADDRESS, + * destinationChainId: 10, + * destinationTokenAddress: NATIVE_TOKEN_ADDRESS, + * amount: toWei("0.01"), + * maxSteps: 2, // Will only return a quote for routes with 2 or fewer steps + * client: thirdwebClient, + * }); + * ``` + * * @param options - The options for the quote. * @param options.originChainId - The chain ID of the origin token. * @param options.originTokenAddress - The address of the origin token. @@ -91,6 +106,7 @@ export async function quote(options: quote.Options): Promise { destinationChainId, destinationTokenAddress, client, + maxSteps, } = options; const amount = "buyAmountWei" in options ? options.buyAmountWei : options.amount; @@ -102,6 +118,9 @@ export async function quote(options: quote.Options): Promise { url.searchParams.set("destinationChainId", destinationChainId.toString()); url.searchParams.set("destinationTokenAddress", destinationTokenAddress); url.searchParams.set("buyAmountWei", amount.toString()); + if (maxSteps) { + url.searchParams.set("maxSteps", maxSteps.toString()); + } const response = await clientFetch(url.toString()); if (!response.ok) { @@ -137,14 +156,15 @@ export declare namespace quote { destinationChainId: number; destinationTokenAddress: ox__Address.Address; client: ThirdwebClient; + maxSteps?: number; } & ( - | { + | { buyAmountWei: bigint; } - | { + | { amount: bigint; } - ); + ); type Result = Quote & { intent: { @@ -247,6 +267,37 @@ export declare namespace quote { * * You can access this functions input and output types with `Buy.prepare.Options` and `Buy.prepare.Result`, respectively. * + * You can include arbitrary data to be included on any webhooks and status responses with the `purchaseData` option. + * + * ```ts + * const quote = await Bridge.Buy.prepare({ + * originChainId: 1, + * originTokenAddress: NATIVE_TOKEN_ADDRESS, + * destinationChainId: 10, + * destinationTokenAddress: NATIVE_TOKEN_ADDRESS, + * amount: toWei("0.01"), + * purchaseData: { + * size: "large", + * shippingAddress: "123 Main St, New York, NY 10001", + * }, + * client: thirdwebClient, + * }); + * ``` + * + * To limit quotes to routes that have a certain number of steps involved, use the `maxSteps` option. + * + * ```ts + * const quote = await Bridge.Buy.prepare({ + * originChainId: 1, + * originTokenAddress: NATIVE_TOKEN_ADDRESS, + * destinationChainId: 10, + * destinationTokenAddress: NATIVE_TOKEN_ADDRESS, + * amount: toWei("0.01"), + * maxSteps: 2, // Will only return a quote for routes with 2 or fewer steps + * client: thirdwebClient, + * }); + * ``` + * * @param options - The options for the quote. * @param options.originChainId - The chain ID of the origin token. * @param options.originTokenAddress - The address of the origin token. @@ -277,6 +328,7 @@ export async function prepare( client, amount, purchaseData, + maxSteps, } = options; const clientFetch = getClientFetch(client); @@ -287,7 +339,7 @@ export async function prepare( headers: { "Content-Type": "application/json", }, - body: JSON.stringify({ + body: stringify({ buyAmountWei: amount.toString(), originChainId: originChainId.toString(), originTokenAddress, @@ -296,6 +348,7 @@ export async function prepare( sender, receiver, purchaseData, + maxSteps, }), }); if (!response.ok) { @@ -342,6 +395,7 @@ export declare namespace prepare { amount: bigint; client: ThirdwebClient; purchaseData?: unknown; + maxSteps?: number; }; type Result = PreparedQuote & { diff --git a/packages/thirdweb/src/bridge/Sell.test.ts b/packages/thirdweb/src/bridge/Sell.test.ts index e761e40f7ea..67d8b37fabb 100644 --- a/packages/thirdweb/src/bridge/Sell.test.ts +++ b/packages/thirdweb/src/bridge/Sell.test.ts @@ -32,6 +32,23 @@ describe.runIf(process.env.TW_SECRET_KEY)("Bridge.Sell.quote", () => { }), ).rejects.toThrowError(); }); + + it("should limit quotes to routes with a certain number of steps", async () => { + const quote = await Sell.quote({ + originChainId: 1, + originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + destinationChainId: 10, + destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + amount: toWei("0.01"), + maxSteps: 2, + client: TEST_CLIENT, + }); + + expect(quote).toBeDefined(); + expect(quote.originAmount).toEqual(toWei("0.01")); + expect(quote.intent).toBeDefined(); + expect(quote.steps.length).toBeLessThanOrEqual(2); + }); }); describe.runIf(process.env.TW_SECRET_KEY)("Bridge.Sell.prepare", () => { @@ -69,4 +86,23 @@ describe.runIf(process.env.TW_SECRET_KEY)("Bridge.Sell.prepare", () => { }), ).rejects.toThrowError(); }); + + it("should limit quotes to routes with a certain number of steps", async () => { + const quote = await Sell.prepare({ + originChainId: 1, + originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + destinationChainId: 10, + destinationTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + amount: toWei("0.01"), + maxSteps: 2, + sender: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709", + receiver: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709", + client: TEST_CLIENT, + }); + + expect(quote).toBeDefined(); + expect(quote.originAmount).toEqual(toWei("0.01")); + expect(quote.steps.length).toBeLessThanOrEqual(2); + expect(quote.intent).toBeDefined(); + }); }); diff --git a/packages/thirdweb/src/bridge/Sell.ts b/packages/thirdweb/src/bridge/Sell.ts index d646f475f3f..33b57e99b4f 100644 --- a/packages/thirdweb/src/bridge/Sell.ts +++ b/packages/thirdweb/src/bridge/Sell.ts @@ -4,6 +4,7 @@ import type { ThirdwebClient } from "../client/client.js"; import { getClientFetch } from "../utils/fetch.js"; import { UNIVERSAL_BRIDGE_URL } from "./constants.js"; import type { PreparedQuote, Quote } from "./types/Quote.js"; +import { stringify } from "../utils/json.js"; /** * Retrieves a Universal Bridge quote for the provided sell intent. The quote will specify the expected `destinationAmount` that will be received in exchange for the specified `originAmount`, which is specified with the `sellAmountWei` option. @@ -70,6 +71,20 @@ import type { PreparedQuote, Quote } from "./types/Quote.js"; * * You can access this functions input and output types with `Sell.quote.Options` and `Sell.quote.Result`, respectively. * + * To limit quotes to routes that have a certain number of steps involved, use the `maxSteps` option. + * + * ```ts + * const quote = await Bridge.Sell.quote({ + * originChainId: 1, + * originTokenAddress: NATIVE_TOKEN_ADDRESS, + * destinationChainId: 10, + * destinationTokenAddress: NATIVE_TOKEN_ADDRESS, + * amount: toWei("0.01"), + * maxSteps: 2, // Will only return a quote for routes with 2 or fewer steps + * client: thirdwebClient, + * }); + * ``` + * * @param options - The options for the quote. * @param options.originChainId - The chain ID of the origin token. * @param options.originTokenAddress - The address of the origin token. @@ -92,6 +107,7 @@ export async function quote(options: quote.Options): Promise { destinationTokenAddress, amount, client, + maxSteps, } = options; const clientFetch = getClientFetch(client); @@ -101,6 +117,9 @@ export async function quote(options: quote.Options): Promise { url.searchParams.set("destinationChainId", destinationChainId.toString()); url.searchParams.set("destinationTokenAddress", destinationTokenAddress); url.searchParams.set("sellAmountWei", amount.toString()); + if (typeof maxSteps !== "undefined") { + url.searchParams.set("maxSteps", maxSteps.toString()); + } const response = await clientFetch(url.toString()); if (!response.ok) { @@ -136,6 +155,7 @@ export declare namespace quote { destinationTokenAddress: ox__Address.Address; amount: bigint; client: ThirdwebClient; + maxSteps?: number; }; type Result = Quote & { @@ -238,6 +258,37 @@ export declare namespace quote { * * You can access this functions input and output types with `Sell.prepare.Options` and `Sell.prepare.Result`, respectively. * + * You can include arbitrary data to be included on any webhooks and status responses with the `purchaseData` option. + * + * ```ts + * const quote = await Bridge.Sell.prepare({ + * originChainId: 1, + * originTokenAddress: NATIVE_TOKEN_ADDRESS, + * destinationChainId: 10, + * destinationTokenAddress: NATIVE_TOKEN_ADDRESS, + * amount: toWei("0.01"), + * purchaseData: { + * size: "large", + * shippingAddress: "123 Main St, New York, NY 10001", + * }, + * client: thirdwebClient, + * }); + * ``` + * + * To limit quotes to routes that have a certain number of steps involved, use the `maxSteps` option. + * + * ```ts + * const quote = await Bridge.Sell.prepare({ + * originChainId: 1, + * originTokenAddress: NATIVE_TOKEN_ADDRESS, + * destinationChainId: 10, + * destinationTokenAddress: NATIVE_TOKEN_ADDRESS, + * amount: toWei("0.01"), + * maxSteps: 2, // Will only return a quote for routes with 2 or fewer steps + * client: thirdwebClient, + * }); + * ``` + * * @param options - The options for the quote. * @param options.originChainId - The chain ID of the origin token. * @param options.originTokenAddress - The address of the origin token. @@ -268,6 +319,7 @@ export async function prepare( receiver, client, purchaseData, + maxSteps, } = options; const clientFetch = getClientFetch(client); @@ -278,7 +330,7 @@ export async function prepare( headers: { "Content-Type": "application/json", }, - body: JSON.stringify({ + body: stringify({ sellAmountWei: amount.toString(), originChainId: originChainId.toString(), originTokenAddress, @@ -287,6 +339,7 @@ export async function prepare( sender, receiver, purchaseData, + maxSteps, }), }); if (!response.ok) { @@ -335,6 +388,7 @@ export declare namespace prepare { receiver: ox__Address.Address; client: ThirdwebClient; purchaseData?: unknown; + maxSteps?: number; }; type Result = PreparedQuote & { From 26df50c089bacb456733a6b17dedc981e006af82 Mon Sep 17 00:00:00 2001 From: gregfromstl Date: Mon, 5 May 2025 20:11:12 -0700 Subject: [PATCH 2/3] changeset --- .changeset/forty-states-shake.md | 47 ++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .changeset/forty-states-shake.md diff --git a/.changeset/forty-states-shake.md b/.changeset/forty-states-shake.md new file mode 100644 index 00000000000..317a62aba25 --- /dev/null +++ b/.changeset/forty-states-shake.md @@ -0,0 +1,47 @@ +--- +"thirdweb": patch +--- + +Adds the `maxSteps` option to Buy.quote, Buy.prepare, Sell.quote, and Sell.prepare functions. This allows users to limit quotes to routes with a specific number of steps or fewer. For example: + +```ts +const quote = await bridge.Buy.quote({ + originChainId: 1, + originTokenAddress: "0x...", + destinationChainId: 137, + destinationTokenAddress: "0x...", + amount: 1000000n, + maxSteps: 2 +}); + +const preparedQuote = await bridge.Buy.prepare({ + originChainId: 1, + originTokenAddress: "0x...", + destinationChainId: 137, + destinationTokenAddress: "0x...", + amount: 1000000n, + sender: "0x...", + receiver: "0x...", + maxSteps: 2 +}); + +const quote = await bridge.Sell.quote({ + originChainId: 1, + originTokenAddress: "0x...", + destinationChainId: 137, + destinationTokenAddress: "0x...", + amount: 1000000n, + maxSteps: 3 +}); + +const preparedQuote = await bridge.Sell.prepare({ + originChainId: 1, + originTokenAddress: "0x...", + destinationChainId: 137, + destinationTokenAddress: "0x...", + amount: 1000000n, + sender: "0x...", + receiver: "0x...", + maxSteps: 3 +}); +``` From 0fd69029684b009b446c6fbebb4c55288e5afb24 Mon Sep 17 00:00:00 2001 From: gregfromstl Date: Mon, 5 May 2025 20:12:20 -0700 Subject: [PATCH 3/3] lint --- packages/thirdweb/src/bridge/Buy.ts | 8 ++++---- packages/thirdweb/src/bridge/Sell.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/thirdweb/src/bridge/Buy.ts b/packages/thirdweb/src/bridge/Buy.ts index 69b1580bd9c..4590b597a07 100644 --- a/packages/thirdweb/src/bridge/Buy.ts +++ b/packages/thirdweb/src/bridge/Buy.ts @@ -2,9 +2,9 @@ import type { Address as ox__Address } from "ox"; import { defineChain } from "../chains/utils.js"; import type { ThirdwebClient } from "../client/client.js"; import { getClientFetch } from "../utils/fetch.js"; +import { stringify } from "../utils/json.js"; import { UNIVERSAL_BRIDGE_URL } from "./constants.js"; import type { PreparedQuote, Quote } from "./types/Quote.js"; -import { stringify } from "../utils/json.js"; /** * Retrieves a Universal Bridge quote for the provided buy intent. The quote will specify the necessary `originAmount` to receive the desired `destinationAmount`, which is specified with the `buyAmountWei` option. @@ -158,13 +158,13 @@ export declare namespace quote { client: ThirdwebClient; maxSteps?: number; } & ( - | { + | { buyAmountWei: bigint; } - | { + | { amount: bigint; } - ); + ); type Result = Quote & { intent: { diff --git a/packages/thirdweb/src/bridge/Sell.ts b/packages/thirdweb/src/bridge/Sell.ts index 33b57e99b4f..e651c240426 100644 --- a/packages/thirdweb/src/bridge/Sell.ts +++ b/packages/thirdweb/src/bridge/Sell.ts @@ -2,9 +2,9 @@ import type { Address as ox__Address } from "ox"; import { defineChain } from "../chains/utils.js"; import type { ThirdwebClient } from "../client/client.js"; import { getClientFetch } from "../utils/fetch.js"; +import { stringify } from "../utils/json.js"; import { UNIVERSAL_BRIDGE_URL } from "./constants.js"; import type { PreparedQuote, Quote } from "./types/Quote.js"; -import { stringify } from "../utils/json.js"; /** * Retrieves a Universal Bridge quote for the provided sell intent. The quote will specify the expected `destinationAmount` that will be received in exchange for the specified `originAmount`, which is specified with the `sellAmountWei` option.