Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ In this guide, we'll show you how to purchase 0.01 Base ETH from USD in Typescri
<Step title='Install the Connect SDK'>
<InstallTabs
npm="npm i thirdweb"
yarn="yarn install thirdweb"
yarn="yarn add thirdweb"
pnpm="pnpm i thirdweb"
/>
<Step title='Get Your Client ID'>
Expand Down
214 changes: 214 additions & 0 deletions apps/portal/src/app/connect/pay/guides/cross-chain-swapping/page.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import {
createMetadata,
Callout,
DocImage,
InstallTabs,
Steps,
Step,
} from "@doc";
import OnrampStepOne from "../../assets/avax-to-usd.png";

export const metadata = createMetadata({
image: {
title: "thirdweb Universal Bridge - Cross-Chain Swapping",
icon: "thirdweb",
},
title: "thirdweb Universal Bridge - Cross-Chain Swapping | thirdweb",
description:
"Learn how to build a custom cross-chain swapping experience with thirdweb Universal Bridge.",
});

# Leverage Cross-Chain Swaps with Universal Bridge

Learn how to enable your users to swap from any asset to any other with thirdweb's Universal Bridge.

In this guide, we'll show you how to purchase 10 USDC on Optimism in Typescript.

---

<Steps>
<Step title='Install the Connect SDK'>
<InstallTabs
npm="npm i thirdweb"
yarn="yarn add thirdweb"
pnpm="pnpm i thirdweb"
/>
<Step title='Get Your Client ID'>

Log in to the [thirdweb dashboard](https://thirdweb.com/team). Click on Create New > Project to get your **Client ID**. You'll need your Client ID to interact with the Connect SDK.

</Step>
</Step>
<Step title='Find available routes'>
Before your user can select which tokens they'd like to swap, they'll need to find available routes.

You can do this using the `routes` function in our `Bridge` namespace. You or your users can filter by origin and destination chains and/or tokens. Pagination is also built-in to the function.

```tsx
import { Bridge, NATIVE_TOKEN_ADDRESS } from "thirdweb";

// Get all available routes
const allRoutes = await Bridge.routes({
client: thirdwebClient,
});

// Filter routes for a specific token or chain
const filteredRoutes = await Bridge.routes({
originChainId: 1, // From Ethereum
originTokenAddress: NATIVE_TOKEN_ADDRESS,
destinationChainId: 10, // To Optimism
client: thirdwebClient,
});

// Paginate through routes
const paginatedRoutes = await Bridge.routes({
limit: 10,
offset: 0,
client: thirdwebClient,
});
```

This will return an array of `Route` objects, which will include information such as `symbol`, `address`, and `chainId` for both the origin and destination tokens.

</Step>
<Step title='Get a quote'>
Once you know which routes are available, you can retrieve a quote to show the user how much they can expect to pay for a given swap.

In this example, we'll use the `Buy.quote` function to get a quote for buying 10 USDC on Optimism for Base ETH.

<Callout variant="info">
The `Buy` namespace is purpose-built for when you want to obtain a specific amount of the output token.
If you have a specific input amount and are flexible on the output amount, you can use the `Sell` namespace.
Learn more about sells [here](https://portal.thirdweb.com/references/typescript/v5/sell/prepare).
</Callout>

Quote allows us to get an expected amount before the user has connected their wallet. This quote won't come with executable transactions, and won't be a guaranteed price.


```tsx
import { Bridge, NATIVE_TOKEN_ADDRESS } from "thirdweb";

const buyQuote = await Bridge.Buy.quote({
originChainId: 8453, // Base
originTokenAddress: NATIVE_TOKEN_ADDRESS,
destinationChainId: 10, // Optimism
destinationTokenAddress: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85", // Optimism USDC
buyAmountWei: 10000000n, // 10 USDC
client: thirdwebClient,
});

console.log(
`To get ${buyQuote.destinationAmount} wei on destination chain, you need to pay ${buyQuote.originAmount} wei`,
);
```

This will return a `Quote` object, which will include the `originAmount` and `destinationAmount` in wei, along with some more useful information about the predicted quote.
</Step>
<Step title='Get the prepared transaction'>
Now that we know how much the user can expect to pay, we can have them connect their wallet and execute the swap.

To get a prepared quote, we'll use the `Buy.prepare` function. The key difference with this function is it requires a `sender` and `receiver` to be specified.

```tsx
import { Bridge, NATIVE_TOKEN_ADDRESS } from "thirdweb";

const preparedBuy = await Bridge.Buy.prepare({
originChainId: 8453, // Base
originTokenAddress: NATIVE_TOKEN_ADDRESS,
destinationChainId: 10, // Optimism
destinationTokenAddress: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85", // Optimism USDC
buyAmountWei: 10000000n, // 10 USDC
sender: "0x...", // Your user's wallet address
receiver: "0x...", // Recipient address (can be the same as sender)
client: thirdwebClient,
});

// The prepared quote contains the transactions you need to execute
console.log(`Transactions to execute: ${preparedBuy.transactions.length}`);
```

This will return a `PreparedQuote` object. It will look very similar to the `Quote` you received in the previous step, but it will include a `transactions` array. This array will contain the transactions you need to execute to complete the swap.
</Step>
<Step title='Execute the swap'>
To execute the swap, we'll need to send all transactions in the `transactions` array one after the other.

<Callout variant="warning">
Currently, the `transactions` array does not include approvals. You'll need to execute any necessary approvals prior to executing the swap transactions.
</Callout>

```tsx
import { sendTransaction, waitForReceipt } from "thirdweb";

const preparedBuy = await Bridge.Buy.prepare({
originChainId: 8453, // Base
originTokenAddress: NATIVE_TOKEN_ADDRESS,
destinationChainId: 10, // Optimism
destinationTokenAddress: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85", // Optimism USDC
buyAmountWei: 10000000n, // 10 USDC
sender: "0x...", // Your user's wallet address
receiver: "0x...", // Recipient address (can be the same as sender)
client: thirdwebClient,
});

};

for (const transaction of preparedBuy.transactions) {
const tx = prepareTransaction({
to: transaction.to as string,
value: BigInt(transaction.value ?? 0n),
data: transaction.data,
chain: defineChain(transaction.chainId),
client
});

const result = await sendAndConfirmTransaction({ transaction: tx, account });
let swapStatus;
do {
swapStatus = await Bridge.status({
transactionHash: result.transactionHash,
client,
});
} while (swapStatus.status !== "COMPLETED");
};
```

<Callout variant="info">
The returned transactions follow the [Ox](https://oxlib.sh/) standard [TransactionEnvelopeEip1559](https://oxlib.sh/guides/transaction-envelopes)
format. This is a simple transaction with `data`, `to`, `value`, and `chainId` properties. **Gas is not included in the transaction, and should
be estimated separately if necessary.** Normally, the thirdweb SDK will handle this for you.
</Callout>

</Step>
<Step title='Getting the swap status'>
You'll notice in the previous step we call `Bridge.status` to get the status of the swap. We can get the status of any past swap using just the transaction hash and chain ID of its origin transaction.

When sending the transactions in a prepared quote, you **must** use `Bridge.status` to get the `COMPLETED` status before moving on to the next transaction. This is because `status` waits for both the origin
and destination transactions to complete. Waiting for the origin transaction receipt is not sufficient since the funds might not have arrived on the destination chain yet.

The `status` will also return all transactions (origin and destination) involved in the swap.
```tsx
import { Bridge } from "thirdweb";

// Check the status of a bridge transaction
const bridgeStatus = await Bridge.status({
transactionHash:
"0xe199ef82a0b6215221536e18ec512813c1aa10b4f5ed0d4dfdfcd703578da56d",
chainId: 8453, // The chain ID where the transaction was initiated
client: thirdwebClient,
});

// The status will be one of: "COMPLETED", "PENDING", "FAILED", or "NOT_FOUND"
if (bridgeStatus.status === "completed") {
console.log(`
Bridge completed!
Sent: ${bridgeStatus.originAmount} wei on chain ${bridgeStatus.originChainId}
Received: ${bridgeStatus.destinationAmount} wei on chain ${bridgeStatus.destinationChainId}
`);
} else if (bridgeStatus.status === "pending") {
console.log("Bridge transaction is still pending...");
} else {
console.log("Bridge transaction failed");
}
```
</Step>
</Steps>
6 changes: 5 additions & 1 deletion apps/portal/src/app/connect/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -314,9 +314,13 @@ export const sidebar: SideBar = {
href: `${paySlug}/guides/accept-direct-payments`,
},
{
name: "Build a Custom Experience",
name: "Build a Custom Onramp Experience",
href: `${paySlug}/guides/build-a-custom-experience`,
},
{
name: "Cross-Chain Swapping",
href: `${paySlug}/guides/cross-chain-swapping`,
},
],
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,11 @@ export function getSidebarLinkGroups(doc: TransformedDoc, path: string) {
return tag === "@modules";
});

const bridge = docs.filter((d) => {
const [tag] = getCustomTag(d) || [];
return tag === "@bridge";
});

// sort extensions into their own groups
if (extensions.length) {
const extensionGroups = extensions.reduce(
Expand Down Expand Up @@ -247,9 +252,68 @@ export function getSidebarLinkGroups(doc: TransformedDoc, path: string) {
}
}

if (bridge.length) {
const bridgeGroups = bridge.reduce(
(acc, d) => {
const [, moduleName] = getCustomTag(d) || [];
if (moduleName) {
if (!acc[moduleName]) {
acc[moduleName] = [];
}
acc[moduleName]?.push(d);
}
return acc;
},
{} as Record<string, SomeDoc[]>,
);
const bridgeLinkGroups: {
name: string;
href: string;
links?: { name: string; href?: string }[];
}[] = Object.entries(bridgeGroups)
.filter(([namespaceName]) => namespaceName.toLowerCase() !== "common")
.map(([namespaceName, docs]) => {
const links = docs.map((d) => ({
name: d.name,
href: getLink(`${path}/${namespaceName.toLowerCase()}/${d.name}`),
}));
return {
name: namespaceName,
href: "",
links,
};
});

// Add the top-level functions
for (const group of Object.entries(bridgeGroups).filter(
([namespaceName]) => namespaceName.toLowerCase() === "common",
)) {
const docs = group[1];
for (const doc of docs) {
bridgeLinkGroups.push({
name: doc.name,
href: getLink(`${path}/${doc.name}`),
});
}
}

if (!linkGroups.find((group) => group.name === name)) {
linkGroups.push({
name: name,
href: getLink(`${path}/${key}`),
links: [{ name: "Universal Bridge", links: bridgeLinkGroups }],
isCollapsible: false,
});
} else {
linkGroups
.find((group) => group.name === name)
?.links.push({ name: "Universal Bridge", links: bridgeLinkGroups });
}
}

const nonExtensions = docs.filter((d) => {
const [tag] = getCustomTag(d) || [];
return tag !== "@extension" && tag !== "@modules";
return tag !== "@extension" && tag !== "@modules" && tag !== "@bridge";
});

// sort into groups
Expand Down
10 changes: 8 additions & 2 deletions apps/portal/src/app/references/components/TDoc/utils/slugs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,17 @@ export function getSlugToDocMap(doc: TransformedDoc) {
const extensionBlockTag = v.signatures
?.find((s) =>
s.blockTags?.some(
(tag) => tag.tag === "@extension" || tag.tag === "@modules",
(tag) =>
tag.tag === "@extension" ||
tag.tag === "@modules" ||
tag.tag === "@bridge",
),
)
?.blockTags?.find(
(tag) => tag.tag === "@extension" || tag.tag === "@modules",
(tag) =>
tag.tag === "@extension" ||
tag.tag === "@modules" ||
tag.tag === "@bridge",
);

if (extensionBlockTag) {
Expand Down
4 changes: 2 additions & 2 deletions packages/thirdweb/src/bridge/Buy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ import type { PreparedQuote, Quote } from "./types/Quote.js";
* @returns A promise that resolves to a non-finalized quote for the requested buy.
*
* @throws Will throw an error if there is an issue fetching the quote.
* @bridge
* @bridge Buy
* @beta
*/
export async function quote(options: quote.Options): Promise<quote.Result> {
Expand Down Expand Up @@ -188,7 +188,7 @@ export declare namespace quote {
* @returns A promise that resolves to a non-finalized quote for the requested buy.
*
* @throws Will throw an error if there is an issue fetching the quote.
* @bridge
* @bridge Buy
* @beta
*/
export async function prepare(
Expand Down
4 changes: 2 additions & 2 deletions packages/thirdweb/src/bridge/Sell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ import type { PreparedQuote, Quote } from "./types/Quote.js";
* @returns A promise that resolves to a non-finalized quote for the requested sell.
*
* @throws Will throw an error if there is an issue fetching the quote.
* @bridge
* @bridge Sell
* @beta
*/
export async function quote(options: quote.Options): Promise<quote.Result> {
Expand Down Expand Up @@ -188,7 +188,7 @@ export declare namespace quote {
* @returns A promise that resolves to a non-finalized quote for the requested buy.
*
* @throws Will throw an error if there is an issue fetching the quote.
* @bridge
* @bridge Sell
* @beta
*/
export async function prepare(
Expand Down
2 changes: 1 addition & 1 deletion packages/thirdweb/src/bridge/Status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { Status } from "./types/Status.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.
*
+ * The returned status will include both the origin and destination transactions and any finalized amounts for the route.
* The returned status will include both the origin and destination transactions and any finalized amounts for the route.
*
* @example
* ```typescript
Expand Down
Loading