diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/connect/universal-bridge/layout.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/connect/universal-bridge/layout.tsx index 217d3d474e5..c603ac9a4f0 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/connect/universal-bridge/layout.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/connect/universal-bridge/layout.tsx @@ -33,7 +33,7 @@ export default async function Layout(props: { tokens via cross-chain routing.{" "} Learn more diff --git a/apps/portal/src/app/pay/get-started/page.mdx b/apps/portal/src/app/pay/get-started/page.mdx index 8d597f85ff8..816f8b44bf6 100644 --- a/apps/portal/src/app/pay/get-started/page.mdx +++ b/apps/portal/src/app/pay/get-started/page.mdx @@ -5,9 +5,12 @@ import { Steps, Step, InstallTabs, + Tabs, + TabsList, + TabsTrigger, + TabsContent, } from "@doc"; -import GetStartedSend from "../assets/get-started-send.png"; -import GetStartedEmbed from "../assets/get-started-embed.png"; + export const metadata = createMetadata({ image: { title: "thirdweb Universal Bridge - Get Started", @@ -15,21 +18,19 @@ export const metadata = createMetadata({ }, title: "Universal Bridge Implementation Guide — thirdweb docs", description: - "Learn how to implement all-in-one web3 payment solution, Universal bridge: the technical step-by-step process", + "Learn how to implement cross-chain payments, bridging, and onramps with the Universal Bridge SDK", }); # Get Started -Learn how to add onramps and crypto purchases to your application. To see which integration is right for you, refer to our [integrations overview](/connect/pay/overview#integration-options). - -The following guide uses our React SDK. You can also learn how to integrate Universal Bridge with our [Unity SDK](https://portal.thirdweb.com/unity/pay/getbuywithcryptoquote). +Learn how to integrate Universal Bridge into your application for cross-chain payments, bridging, swapping, and fiat onramps. This guide covers the core SDK modules and practical implementation examples. --- ## Installation - + -Log in to [the thirdweb dashboard](https://thirdweb.com/team). Navigate to the **Settings** page and create an API key to get your **Client ID**. You'll need your Client ID to interact with the Connect SDK. +Log in to [the thirdweb dashboard](https://thirdweb.com/team). Navigate to the **Settings** page and create an API key to get your **Client ID**. You'll need your Client ID to interact with the Universal Bridge. + + + + +```typescript +import { createThirdwebClient } from "thirdweb"; + +const client = createThirdwebClient({ + clientId: "your_client_id" +}); +``` + + + + +Get a quote and prepare your first cross-chain swap: + +```typescript +import { Bridge, NATIVE_TOKEN_ADDRESS, toWei } from "thirdweb"; + +const prepared = await Bridge.Buy.prepare({ + originChainId: 1, + originTokenAddress: NATIVE_TOKEN_ADDRESS, + destinationChainId: 137, + destinationTokenAddress: NATIVE_TOKEN_ADDRESS, + amount: toWei("0.1"), + sender: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709", + receiver: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709", + client, +}); +``` + --- -## Option 1: ConnectButton +## Recipes -Universal Bridge is available by default with our `ConnectButton` component. When users log in with Connect, they'll be able to onramp and purchase crypto directly from the logged in Connect interface. You can read more about `ConnectButton` [in this guide](/connect/sign-in/ConnectButton). + + +Bridge & Swap +Transfers +Fiat Onramps +Route Discovery + -```tsx -import { ThirdwebProvider, ConnectButton } from "thirdweb/react"; + -const client = createThirdwebClient({ clientId: your_client_id }); +Use the `Buy` module to purchase tokens on any supported chain using tokens from another chain: -export default function App() { - return ( - - - - ); +```typescript +import { Bridge, NATIVE_TOKEN_ADDRESS, toWei } from "thirdweb"; + +// Step 1: Get a quote for the purchase +const quote = await Bridge.Buy.quote({ + originChainId: 1, // Ethereum + originTokenAddress: NATIVE_TOKEN_ADDRESS, // ETH + destinationChainId: 137, // Polygon + destinationTokenAddress: NATIVE_TOKEN_ADDRESS, // MATIC + amount: toWei("0.1"), // 0.1 MATIC + client, +}); + +console.log(`Need ${quote.originAmount} origin tokens`); + +// Step 2: Prepare a Swap +const prepared = await Bridge.Buy.prepare({ + originChainId: 1, + originTokenAddress: NATIVE_TOKEN_ADDRESS, + destinationChainId: 137, + destinationTokenAddress: NATIVE_TOKEN_ADDRESS, + amount: toWei("0.1"), + sender: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709", + receiver: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709", + client, +}); + +// Step 3: Execute transactions +for (const step of prepared.steps) { + for (const transaction of step.transactions) { + const result = await sendTransaction({ + transaction, + account: wallet.account, + }); + console.log("Transaction sent:", result.transactionHash); + } } ``` -Our `ConnectButton` has a number of [customization options](/connect/pay/customization/connectbutton) so you can tailor it to your experience. +**Best for:** NFT purchases, token swaps across chains, DeFi interactions ---- + -## Option 2: Embed + -The `PayEmbed` allows users to onramp and purchase crypto directly from your application interface. +Use the `Transfer` module for same-chain or cross-chain token transfers: -```tsx -import { ThirdwebProvider, PayEmbed } from "thirdweb/react"; +```typescript +import { Bridge, NATIVE_TOKEN_ADDRESS, toWei } from "thirdweb"; -const client = createThirdwebClient({ clientId: your_client_id }); +// Prepare transfer +const transfer = await Bridge.Transfer.prepare({ + chainId: 1, // Ethereum + tokenAddress: NATIVE_TOKEN_ADDRESS, + amount: toWei("0.05"), // 0.05 ETH + sender: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709", + receiver: "0x742d35Cc6634C0532925a3b8D400A1B5000d3ae5", + client, +}); -export default function App() { - return ( - - - - ); +// Execute transfer transactions +for (const step of transfer.steps) { + for (const transaction of step.transactions) { + const result = await sendTransaction({ + transaction, + account: wallet.account, + }); + } } ``` -The embedded component will be displayed like so in your application interface: +**Best for:** P2P payments, payroll, direct transfers - + -And voila! Your users can now onramp and convert crypto directly from your application. + -Our `PayEmbed` has a number of [customization options](/connect/pay/customization/payembed) so you can tailor it to your experience. +Use the `Onramp` module to convert fiat currency to crypto via supported providers: + +```typescript +import { Bridge, NATIVE_TOKEN_ADDRESS, toWei } from "thirdweb"; + +// Prepare onramp session +const onramp = await Bridge.Onramp.prepare({ + client, + onramp: "stripe", // or "coinbase", "transak" + chainId: 1, + tokenAddress: NATIVE_TOKEN_ADDRESS, + receiver: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709", + amount: toWei("0.1"), // 0.1 ETH + country: "US", +}); + +console.log(`Cost: $${onramp.currencyAmount} ${onramp.currency}`); + +// Redirect user to complete purchase +window.location.href = onramp.link; +``` + +**Best for:** New users entering crypto, applications requiring fiat payments + + + + + +Use the `routes` function to discover available bridging paths: + +```typescript +import { Bridge, NATIVE_TOKEN_ADDRESS } from "thirdweb"; + +// Find all routes from Ethereum ETH +const allRoutes = await Bridge.routes({ + originChainId: 1, + originTokenAddress: NATIVE_TOKEN_ADDRESS, + client, +}); + +// Find specific routes with pagination +const filteredRoutes = await Bridge.routes({ + originChainId: 1, + originTokenAddress: NATIVE_TOKEN_ADDRESS, + destinationChainId: 137, + limit: 10, + sortBy: "popularity", + client, +}); + +console.log(`Found ${filteredRoutes.length} routes`); +``` + +**Best for:** Building route selectors, showing available options to users + + + + --- -## Option 3: Send a Transaction with Pay - -Universal Bridge is enabled by default for any contract call sent with `sendTransaction`. It will automatically be invoked when a user does not have enough funds to complete the transaction. - - - ℹ️ `sendTransaction` is available in [Connect SDK v5](/typescript/v5) or - above. - - -```tsx -import { useSendTransaction } from "thirdweb/react"; -import { mintTo } from "thirdweb/extensions/erc721"; - -const { mutate: sendTx, data: transactionResult } = useSendTransaction(); - -const onClick = () => { - const transaction = mintTo({ - contract, - to: "0x...", - nft: { - name: "NFT Name", - description: "NFT Description", - image: "https://example.com/image.png", - }, - }); - sendTx(transaction); -}; +## Transaction Status Tracking + +Monitor the progress of your Universal Bridge transactions: + +```typescript +import { Bridge } from "thirdweb"; + +// Check transaction status +const status = await Bridge.status({ + transactionHash: "0x5959b9321ec581640db531b80bac53cbd968f3d34fc6cb1d5f4ea75f26df2ad7", + chainId: 137, + client, +}); + +switch (status.status) { + case "COMPLETED": + console.log("Bridge completed!"); + console.log("Final amount:", status.destinationAmount); + break; + case "PENDING": + console.log("Still processing..."); + break; + case "FAILED": + console.log("Transaction failed"); + break; + case "NOT_FOUND": + console.log("Transaction not found"); + break; +} ``` -When a user clicks this button, Universal Bridge will perform gas estimation to determine if the user has a sufficient balance. If their balance is sufficient, the transaction will execute normally. If their balance is not sufficient, the following modal will pop up asking them to either onramp funds or convert crypto to the required amount: +--- + +## Error Handling + +Universal Bridge functions throw `ApiError` for failed requests: + +```typescript +import { Bridge, ApiError } from "thirdweb"; + +try { + const quote = await Bridge.Buy.quote({ + originChainId: 1, + originTokenAddress: NATIVE_TOKEN_ADDRESS, + destinationChainId: 999999, // Invalid chain + destinationTokenAddress: NATIVE_TOKEN_ADDRESS, + amount: toWei("0.1"), + client, + }); +} catch (error) { + if (error instanceof ApiError) { + console.log("API Error:", error.message); + console.log("Status Code:", error.statusCode); + console.log("Correlation ID:", error.correlationId); + } +} +``` - +--- -Once a user onramps or converts their funds to the required token, the user will be prompted once more to confirm the transaction. The transaction will then execute as expected. +## Next Steps -For deeper customization of the Universal Bridge transaction modal, you can refer to our [sendTransaction customization guide](/connect/pay/customization/send-transaction). +- **[API Reference](https://bridge.thirdweb.com/reference)** - Complete REST API documentation +- **[SDK Reference](/typescript/v5/buy/quote)** - TypeScript SDK function reference +- **[Webhooks](/pay/webhooks)** - Set up real-time notifications +- **[Customization Guides](/pay/customization)** - Advanced configuration options +- **[Playground](https://playground.thirdweb.com/connect/pay)** - Interactive testing environment diff --git a/apps/portal/src/app/pay/guides/accept-direct-payments/page.mdx b/apps/portal/src/app/pay/guides/accept-direct-payments/page.mdx deleted file mode 100644 index d55d7710209..00000000000 --- a/apps/portal/src/app/pay/guides/accept-direct-payments/page.mdx +++ /dev/null @@ -1,104 +0,0 @@ -import { createMetadata, Callout, DocImage, Steps, Step } from "@doc"; -import DirectPaymentsFlow from "../../assets/direct-payments-flow.png"; - -export const metadata = createMetadata({ - image: { - title: "thirdweb Universal Bridge - Accept Direct Payments", - icon: "thirdweb", - }, - title: "thirdweb Universal Bridge - Accept Direct Payments | thirdweb", - description: - "Combine Universal Bridge and Engine to create a smooth point of sale experience on any chain", -}); - -# Accept Direct Payments with Universal Bridge & Engine - -Learn how to accept fiat and crypto from your customers with Universal Bridge and trigger Engine actions, like minting NFTs or transferring tokens to user wallets. - -### Requirements - -- [A thirdweb Client ID](https://portal.thirdweb.com/typescript/v5/client) -- [An Engine Instance](https://thirdweb.com/team/~/~/engine) - -### Purchase Flow Overview - -Before jumping in, it's important to understand the full system, from initial purchase to the eventual transfer of assets. This is a four step process (with an optional fifth): - - - -1. **User Initiates Purchase:** User sends specified funds (ETH, MATIC, USD, etc.) to your wallet via thirdweb Universal Bridge. - - Your wallet can be any business wallet you choose or your Engine backend wallet. - - Use the `purchaseData` field to pass any item data needed in the webhook. - E.g., `{ nftId: 1 }`. -2. **Universal Bridge Processes Purchase:** thirdweb Universal Bridge processes the transaction and sends a [Purchase Complete webhook](/connect/pay/webhooks#purchase-complete) to your dapp’s server when the transaction is complete. -3. **Your Server Calls Engine:** Your server processes the webhook and sends a contract call to the thirdweb Engine backend wallet to perform the item purchase. -4. **Engine Executes the Transaction:** Engine backend wallet executes the transaction needed to deliver the token(s) to the user’s address. -5. **(Optional) User Is Notified :** Your server confirms the completed Engine transaction and notifies the user. - -Now, let's get started. - - - - - 1. If you haven't already, log in and [deploy an Engine - instance](https://thirdweb.com/team/~/~/engine) - - 2. Create or import a backend wallet for Engine in [your Engine dashboard](https://thirdweb.com/team/~/~/engine). - - 3. Send the required funds to the backend wallet - * For example, if purchasing with Arb Nova USDC, then your backend wallet should hold this ERC20 token. - * If your backend wallet holds an ERC20 token, ensure to add a native token balance as well to cover gas costs. For example, for a transaction on Arbitrum Nova, your backend wallet should hold both Arb Nova USDC (for payment) and Arb Nova ETH (for gas). - * Your backend wallet should hold enough liquidity to handle any anticipated purchasing volume. - - 4. Make a plan to monitor and keep the float wallet topped up to avoid any issues with the service. - - - 1. [Integrate thirdweb Universal Bridge](https://portal.thirdweb.com/connect/pay/build-a-custom-experience) on the client side to enable fiat and crypto payments. - - - 2. Specify the required parameters for your Universal Bridge transaction. - - ```ts - const quote = await getBuyWithFiatQuote({ - client: client, // thirdweb client - fromCurrencySymbol: "USD", // fiat currency symbol - toChainId: arbnova.id, // arbitrum nova chain id - toAmount: "100", // amount of token to buy - toTokenAddress: USDC_TOKEN_ADDRESS, // address of payment token - fromAddress: "", - toAddress: "", - purchaseData: { - nftId: 1 - } - }); - ``` - - |Parameter|Type|Description| - |----|--|--| - |fromCurrencySymbol|`string`|The fiat currency symbol you'd like to accept (currently limited to USD and EUR) | - |toChainId|`string`|[Chain ID](https://thirdweb.com/chainlist) for the destination token.| - |toTokenAddress|`string`|Address of the destination token.| - |toAmount|`string`|The price of the asset denominated in your desination token.| - |fromAddress|`string`|The user's wallet address.| - |toAddress|`string`|The wallet you'd like funds to be sent to. This can be your business wallet or an Engine backend wallet. | - |purchaseData|`Object`|Any details you need to pass in order to track your transaction. If this is a secondary sale, you may want to include details like `{ nftId: 1, orderId: 1 }` | - - - - 1. Log in & create a “Purchase Complete” webhook in your Team > Connect > Universal Bridge > Webhooks page. - 2. The [Universal Bridge webhook POST response](/connect/pay/webhooks#purchase-complete) will include the BuyWithCryptoStatus or BuyWithFiatStatus in the body. - * This will also include the purchaseData you previously passed (metadata identifying the customer information, etc.). - * A request will be sent on every status change. - 3. Listen for [completed purchase statuses](https://portal.thirdweb.com/connect/pay/build-a-custom-experience#Poll%20for%20Transaction%20Status). Note that if this is a two-step BuyWithFiat transaction (onramp, then swap), you should also listen for `CRYPTO_SWAP_COMPLETED` - 4. When a purchase is completed, determine the purchaser, order and validate the amounts. - - The fromAddress in the status will show purchasing address - - The purchaseData will contain more information you previously for the purchase details - - Validate the information. For example, 100 USDC was sent to your wallet. - 5. Send a contract call to Engine for the float wallet to purchase the item - - For example, send a transaction to the float wallet for `mintTo(fromAddress)` - - For secondary purchases, like a marketplace direct listing, you can use the appropriate Engine endpoint, passing the ultimate recipient of the NFT as the buyer - 6. Confirm the purchase was successful. - - - -Once you've completed all of these steps, you'll be ready to offer a great purchase UX for your users. diff --git a/apps/portal/src/app/pay/guides/build-a-custom-experience/page.mdx b/apps/portal/src/app/pay/guides/build-a-custom-experience/page.mdx deleted file mode 100644 index 1efe463a460..00000000000 --- a/apps/portal/src/app/pay/guides/build-a-custom-experience/page.mdx +++ /dev/null @@ -1,320 +0,0 @@ -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 - Custom Experience", - icon: "thirdweb", - }, - title: "thirdweb Universal Bridge - Buy With Fiat - Custom Experience | thirdweb", - description: - "Learn how to build a custom onramp experience with thirdweb Universal Bridge.", -}); - -# Build a Custom Fiat Experience - -Learn how to enable your users to purchase your application’s token with any fiat payment method with our headless flow. - -In this guide, we'll show you how to purchase 0.01 Base ETH from USD in Typescript. - - - Our `PayEmbed` component handles the complexity of the process detailed below - and can be integrated in minutes. We encourage you to check out the [PayEmbed - guide here](/connect/pay/get-started#option-2-embed-pay). - - ---- - - - - - - -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. - - - - -Buying with fiat can require one or two steps depending on your destination token: - -If the destination token _*can*_ be bought directly with fiat, your users can onramp directly to their destination token. - -If the destination token _*can not*_ be bought directly with fiat, your users will need to onramp to an intermediate token, then convert the onramp token to the destination token. - -For example, when Buying Base ETH: - -1. Users will receive Avalance AVAX ( native token ) in exchange for USD -2. Users will be prompted to convert Avalanche AVAX to Base ETH - - - -This process requires your user's wallet address. Refer to [this guide](https://portal.thirdweb.com/typescript/v5/connecting-wallets) to learn how to connect a wallet. - -```tsx -import { getBuyWithFiatQuote } from "thirdweb/pay"; -import { NATIVE_TOKEN_ADDRESS } from "thirdweb"; -import { base } from "thirdweb/chains"; - -// create a thirdweb client -const client = createThirdwebClient({ - clientId: "", -}); - -// Get a quote for buying 0.01 Base ETH with USD -const quote = await getBuyWithFiatQuote({ - client: client, // thirdweb client - fromCurrencySymbol: "USD", // fiat currency symbol - toChainId: base.id, // base chain id - toAmount: "0.01", // amount of token to buy - toTokenAddress: NATIVE_TOKEN_ADDRESS, // native token - toAddress: "0x...", // user's wallet address -}); - -// display quote information to user -console.log(quote.fromCurrencyWithFees); -console.log(quote.processingFees); -console.log(quote.onRampToken); -console.log(quote.toToken); -console.log(quote.estimatedDurationSeconds); -// etc... -``` - -The `quote` object contains detailed transaction information including the estimated time, processing fees, amount of fiat currency required, and more that you can display in your application. - - - -The `quote` object contains `quote.onRampToken` and `quote.toToken` objects containing intermediate and destination token information. - -If `quote.onRampToken` is not the same as `quote.toToken`, then your users will need to onramp to intermediary token before arriving at their destination token. - -You can use `isSwapRequiredPostOnramp` to check this - -```tsx -import { isSwapRequiredPostOnramp } from "thirdweb/pay"; - -const hasTwoSteps = isSwapRequiredPostOnramp(quote); - -if (hasTwoSteps) { - // display the two steps to the user -} -``` - - - -Once you have a `quote` from `getBuyWithFiatQuote`, you can open a new tab with `quote.onRampLink` to show the onramp experience. This onramp experience handles all regulatory requirements, know your customer (KYC) verifications, and sanctions screening. - -After they've KYC'd (if required), customers have the option of saving payment methods, KYC data, and wallet information in the onramp, which makes the returning onramp experience much faster. - -Your users will be able to purchase the `quote.onRampToken` with the specified fiat currency. - -```ts -window.open(quote.onRampLink, "_blank"); -``` - - - -When you open the `quote.onRampLink` in a new tab, you can begin polling for the onramp transaction status in your app by calling `getBuyWithFiatStatus`. - -`getBuyWithFiatStatus` requires an `intentId` which you can get from `quote` object. - -`getBuyWithFiatStatus` requires passing an `intentId` which you can get from `quote` object. - -There are a number of transactions statuses: - -```tsx -// Keep calling the below code at some regular intervals to poll the status - -const fiatStatus = await getBuyWithFiatStatus({ - client: client, // thirdweb client - intentId: quote.intentId, // pass intentId from quote -}); - -if (fiatStatus.status === "NOT_FOUND") { - // invalid intentId - // Show error in your page -} - -if (fiatStatus.status === "NONE") { - // No information available yet - // Show "loading" status on your page - // keep polling -} - -if (fiatStatus.status === "PENDING_PAYMENT") { - // Payment is in progress in the on-ramp provider - // Show "loading" status on your page - // keep polling -} - -if (fiatStatus.status === "PENDING_ON_RAMP_TRANSFER") { - // payment is done, on-ramp process has not started - // show "loading" status on your page - // keep polling -} - -if (fiatStatus.status === "ON_RAMP_TRANSFER_IN_PROGRESS") { - // on-ramp provider is doing on-ramp with fiat currency - // show "loading" status on your page - // keep polling -} - -if (fiatStatus.status === "ON_RAMP_TRANSFER_FAILED") { - // on-ramp provider failed to do onramp - // show error in your UI - // STOP polling -} - -if (fiatStatus.status === "ON_RAMP_TRANSFER_COMPLETED") { - // if only on-ramp is required - process is done! - if (!hasTwoSteps) { - // show "success" - // Stop polling - // That's it! - } else { - // Wait for "CRYPTO_SWAP_REQUIRED" state to convert tokens - // Show "loading" status on your page - // Stop polling - } -} - -if (fiatStatus.status === "CRYPTO_SWAP_REQUIRED") { - // go to step 5 - // Show UI for Buy with Crypto quote.onRampToken to quote.toToken - // Stop polling -} -``` - - - -This step is only relevant when a crypto-to-crypto purchase is required after perfmorming an onramp to an intermediary token. - -In this case, you can use `getPostOnRampQuote` to get a quote to convert the intermediary token to the destination token. - -```tsx -// when fiatStatus.status === "CRYPTO_SWAP_REQUIRED" -// and hasTwoStep === true - -const swapQuote = await getPostOnRampQuote({ - client: client, - buyWithFiatStatus: fiatStatus, -}); - -if (!swapQuote) { - // invalid fiatStatus status -} else { - // Go to step 6 to kick off the "Swap" flow -} -``` - - - -Executing Buy With Crypto may involve either a single step or 2 steps - -If your source token is an ERC-20 token, an approval step is required before executing the Buy With Crypto transaction. - -You can check if approval is required by checking `quote.approval` - -```tsx -import { sendTransaction, waitForReceipt } from "thirdweb"; - -const account = wallet.getAccount(); - -// If approval is required, send the approval transaction -// show a button to user to request approval and send the transaction on click -if (quote.approval) { - // request spending tokens from wallet - const approveTxResult = await sendTransaction({ - transaction: quote.approval, // approval transaction - account: account, // account from user's connected wallet - }); - - await waitForReceipt(approveTxResult); -} - -// The above step may result in error if user rejects or transaction fails -// If it results in error, it needs to be done again until its successful - -// Once the approval is done, you can send the buyWithCrypto transaction -// show a button to user to request sending buyWithCrypto transaction and send the transaction on click -const buyWithCryptoTxResult = await sendTransaction({ - transaction: quote.transactionRequest, - account: account, -}); - -await waitForReceipt(buyWithCryptoTxResult); - -// Save the buy with crypto transaction hash for polling the status as mentioned in step 7 -const buyWithCryptoTxHash = buyWithCryptoTxResult.transactionHash; -``` - - - -Once you've initiated your Buy With Crypto transaction, you'll want to track the status. You can notify users throughout this journey by checking for the following statuses: - -```tsx -import { getBuyWithCryptoStatus } from 'thirdweb/pay' - -// Keep calling the below code at regular intervals if the status is in a pending state - -const swapStatus = await getBuyWithCryptoStatus({ - client: client, - transactionHash: swapTxHash, -}}); - -if (swapStatus.status === "NOT_FOUND") { -// invalid swap tx -// Show error in your page -// Stop polling -} - -if (swapStatus.status === "NONE") { -// No information available yet -// show "loading" in UI -// Keep polling -} - -if (swapStatus.status === "FAILED") { -// swap failed -// show "error" in UI - show a retry option in UI -// Stop polling -} - -if (swapStatus.status === "COMPLETED") { -// swap completed -// show "success" in UI -// Stop polling -} - -if (swapStatus.status === "PENDING") { -// swap is in progress -// show "loading" in UI -// Keep polling -} - -``` - - - - ---- - -### Build a Custom Experience in React - -If you are using React, we provide Hooks for each of the functions mentioned above: - -| React Hook | Typescript | -| :------------------- | :------------------- | -| useBuyWithFiatQuote | getBuyWithFiatQuote | -| usePostOnrampQuote | getPostOnrampQuote | -| useBuyWithFiatStatus | getBuyWithFiatStatus | diff --git a/apps/portal/src/app/pay/guides/cross-chain-swapping/page.mdx b/apps/portal/src/app/pay/guides/cross-chain-swapping/page.mdx index 30da5bba16f..975ad2284c8 100644 --- a/apps/portal/src/app/pay/guides/cross-chain-swapping/page.mdx +++ b/apps/portal/src/app/pay/guides/cross-chain-swapping/page.mdx @@ -93,7 +93,7 @@ const buyQuote = await Bridge.Buy.quote({ originTokenAddress: NATIVE_TOKEN_ADDRESS, destinationChainId: 10, // Optimism destinationTokenAddress: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85", // Optimism USDC - buyAmountWei: 10000000n, // 10 USDC + amount: 10000000n, // 10 USDC client: thirdwebClient, }); @@ -117,98 +117,108 @@ const preparedBuy = await Bridge.Buy.prepare({ 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) + amount: 10000000n, // 10 USDC + sender: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709", // Your user's wallet address + receiver: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709", // 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}`); +// The prepared quote contains the steps and transactions you need to execute +console.log(`Steps: ${preparedBuy.steps.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. +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 `steps` array containing all the transactions you need to execute to complete the swap. -To execute the swap, we'll need to send all transactions in the `transactions` array one after the other. - - - Currently, the `transactions` array does not include approvals. You'll need to execute any necessary approvals prior to executing the swap transactions. - +To execute the swap, we'll need to send all transactions in each step one after the other. The SDK automatically includes any necessary approvals in the transactions array. ```tsx -import { sendTransaction, waitForReceipt } from "thirdweb"; +import { sendAndConfirmTransaction } 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) + amount: 10000000n, // 10 USDC + sender: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709", // Your user's wallet address + receiver: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709", // 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"); -}; +// Execute all transactions in sequence +for (const step of preparedBuy.steps) { + for (const transaction of step.transactions) { + // Handle approvals first + if (transaction.action === "approval") { + const result = await sendAndConfirmTransaction({ + transaction, + account: wallet.account + }); + console.log("Approval sent:", result.transactionHash); + } else { + // Execute the main swap transaction + const result = await sendAndConfirmTransaction({ + transaction, + account: wallet.account + }); + console.log("Swap transaction sent:", result.transactionHash); + + // Wait for completion before continuing + let swapStatus; + do { + swapStatus = await Bridge.status({ + transactionHash: result.transactionHash, + chainId: transaction.chainId, + client: thirdwebClient, + }); + if (swapStatus.status === "PENDING") { + await new Promise(resolve => setTimeout(resolve, 3000)); // Wait 3 seconds + } + } while (swapStatus.status === "PENDING"); + + if (swapStatus.status === "FAILED") { + throw new Error("Swap transaction failed"); + } + } + } +} ``` - - 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. + The transactions come pre-configured with all necessary parameters including gas estimation. Each transaction has an `action` property that indicates whether it's an "approval" or the main swap transaction. -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. +You can get the status of any swap using 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. +When sending transactions in a prepared quote, you **must** use `Bridge.status` to confirm `COMPLETED` status before moving to the next transaction. This ensures both origin and destination transactions have completed successfully. -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 + transactionHash: "0x5959b9321ec581640db531b80bac53cbd968f3d34fc6cb1d5f4ea75f26df2ad7", + chainId: 137, // 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") { +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") { +} else if (bridgeStatus.status === "PENDING") { console.log("Bridge transaction is still pending..."); -} else { +} else if (bridgeStatus.status === "FAILED") { console.log("Bridge transaction failed"); +} else { + console.log("Transaction not found"); } ``` + diff --git a/apps/portal/src/app/pay/guides/nft-checkout/page.mdx b/apps/portal/src/app/pay/guides/nft-checkout/page.mdx new file mode 100644 index 00000000000..317bb365b2c --- /dev/null +++ b/apps/portal/src/app/pay/guides/nft-checkout/page.mdx @@ -0,0 +1,706 @@ +import { + createMetadata, + Callout, + DocImage, + InstallTabs, + Steps, + Step, + Tabs, + TabsList, + TabsTrigger, + TabsContent, +} from "@doc"; + +export const metadata = createMetadata({ + image: { + title: "thirdweb Universal Bridge - NFT Checkout", + icon: "thirdweb", + }, + title: "thirdweb Universal Bridge - NFT Checkout | thirdweb", + description: + "Learn how to build a complete NFT checkout flow with in-app wallet login, onramp, and minting using Universal Bridge.", +}); + +# NFT Checkout with Universal Bridge + +Learn how to build a seamless NFT checkout experience that combines in-app wallet authentication, fiat-to-crypto onramps, and NFT minting. This tutorial demonstrates a complete user flow where users can purchase NFTs directly with fiat currency. + +This pattern is perfect for NFT marketplaces, gaming platforms, and any application where you want to minimize friction for new crypto users. + +--- + + + + + + + +Configure your client and prepare an NFT contract for minting: + +```typescript +import { createThirdwebClient } from "thirdweb"; +import { getContract } from "thirdweb"; +import { sepolia } from "thirdweb/chains"; // Using testnet for demo + +const client = createThirdwebClient({ + clientId: "your_client_id" +}); + +// Your NFT contract (ERC721 or ERC1155) +const nftContract = getContract({ + client, + chain: sepolia, + address: "0x...", // Your NFT contract address +}); + +// NFT pricing configuration +const NFT_PRICE_ETH = "0.01"; // 0.01 ETH per NFT +const NFT_METADATA = { + name: "Awesome NFT Collection", + description: "A limited edition NFT with exclusive benefits", + image: "https://your-domain.com/nft-image.png", + attributes: [ + { trait_type: "Rarity", value: "Rare" }, + { trait_type: "Collection", value: "Genesis" }, + ], +}; +``` + + + For production, deploy your NFT contract on mainnet and update the chain configuration. You can use thirdweb's contract deployment tools to create ERC721 or ERC1155 contracts easily. + + + + + +Set up seamless wallet authentication for new users: + +```typescript +import { inAppWallet } from "thirdweb/wallets/in-app"; +import { ConnectButton, useActiveAccount, useConnect } from "thirdweb/react"; + +// Configure in-app wallet for seamless onboarding +const wallet = inAppWallet({ + auth: { + options: ["google", "apple", "facebook", "email"], + }, +}); + +// Authentication component +function WalletAuth({ onConnect }: { onConnect: (address: string) => void }) { + const account = useActiveAccount(); + const { connect } = useConnect(); + + const handleConnect = async () => { + try { + const connectedAccount = await connect(async () => { + const account = await wallet.connect({ + client, + strategy: "google", // or other auth methods + }); + return account; + }); + + console.log("Wallet connected:", connectedAccount.address); + onConnect(connectedAccount.address); + } catch (error) { + console.error("Connection failed:", error); + } + }; + + if (account) { + onConnect(account.address); // Auto-proceed when wallet is connected + return ( +
+

✅ Wallet Connected

+

+ {account.address.slice(0, 6)}...{account.address.slice(-4)} +

+
+ ); + } + + return ( +
+ +

+ New to crypto? No worries! Sign in with your existing account. +

+
+ ); +} +``` + +
+ + +Create the main checkout component that handles the complete purchase flow: + +```typescript +import { Bridge, NATIVE_TOKEN_ADDRESS, toWei, sendTransaction } from "thirdweb"; +import { prepareContractCall } from "thirdweb"; +import { mintTo } from "thirdweb/extensions/erc721"; +import { useState, useEffect } from "react"; + +interface CheckoutState { + step: "connect" | "payment" | "processing" | "success" | "error"; + selectedProvider?: "stripe" | "coinbase" | "transak"; + onrampSession?: any; + mintTxHash?: string; + error?: string; +} + +function NFTCheckout() { + const [state, setState] = useState({ step: "connect" }); + const account = useActiveAccount(); + + const handleWalletConnect = (address: string) => { + setState({ step: "payment" }); + }; + + const handlePaymentSelect = async (provider: "stripe" | "coinbase" | "transak") => { + if (!account) return; + + setState({ step: "processing", selectedProvider: provider }); + + try { + // Start onramp process + const onrampSession = await Bridge.Onramp.prepare({ + client, + onramp: provider, + chainId: sepolia.id, + tokenAddress: NATIVE_TOKEN_ADDRESS, + receiver: account.address, + amount: toWei(NFT_PRICE_ETH), + currency: "USD", + country: "US", // You should detect this based on user's location + }); + + console.log("Onramp session created:", onrampSession.id); + setState({ + step: "processing", + selectedProvider: provider, + onrampSession + }); + + // Store session info for monitoring + localStorage.setItem("currentOnrampSession", JSON.stringify({ + id: onrampSession.id, + provider, + timestamp: Date.now(), + })); + + // Open onramp in new window/tab + window.open(onrampSession.link, "_blank"); + + // Start monitoring the session + monitorOnrampAndMint(onrampSession.id); + + } catch (error) { + console.error("Failed to start payment:", error); + setState({ + step: "error", + error: error instanceof Error ? error.message : "Payment failed" + }); + } + }; + + const monitorOnrampAndMint = async (sessionId: string) => { + try { + const status = await Bridge.Onramp.status({ + sessionId, + client, + }); + + switch (status.status) { + case "COMPLETED": + console.log("Payment completed! Now minting NFT..."); + await mintNFT(); + break; + + case "PENDING": + console.log("Payment still in progress..."); + // Check again in 10 seconds + setTimeout(() => monitorOnrampAndMint(sessionId), 10000); + break; + + case "FAILED": + console.log("Payment failed:", status.error); + setState({ + step: "error", + error: "Payment failed. Please try again." + }); + break; + + case "CANCELLED": + console.log("Payment was cancelled"); + setState({ step: "payment" }); // Return to payment selection + break; + } + } catch (error) { + console.error("Failed to check payment status:", error); + setState({ + step: "error", + error: "Failed to check payment status" + }); + } + }; + + const mintNFT = async () => { + if (!account) return; + + try { + // Prepare NFT mint transaction + const mintTransaction = mintTo({ + contract: nftContract, + to: account.address, + nft: NFT_METADATA, + }); + + // Execute mint transaction + const result = await sendTransaction({ + transaction: mintTransaction, + account, + }); + + console.log("NFT minted successfully:", result.transactionHash); + setState({ + step: "success", + mintTxHash: result.transactionHash + }); + + // Clean up session storage + localStorage.removeItem("currentOnrampSession"); + + } catch (error) { + console.error("Minting failed:", error); + setState({ + step: "error", + error: error instanceof Error ? error.message : "Minting failed" + }); + } + }; + + return ( +
+
+ {NFT_METADATA.name} +

{NFT_METADATA.name}

+

{NFT_METADATA.description}

+
+ {NFT_PRICE_ETH} ETH (~$30 USD) +
+
+ + {/* Step Progress Indicator */} +
+ {["connect", "payment", "processing", "success"].map((step, index) => ( +
= index + ? "bg-blue-500" + : "bg-gray-200" + }`} + /> + ))} +
+ + {/* Step Content */} + {state.step === "connect" && ( + + )} + + {state.step === "payment" && ( + + )} + + {state.step === "processing" && ( + + )} + + {state.step === "success" && ( + + )} + + {state.step === "error" && ( + setState({ step: "payment" })} + /> + )} +
+ ); +} +``` + + + + +Build the payment options component that shows provider choices: + +```typescript +interface PaymentOptionsProps { + onSelect: (provider: "stripe" | "coinbase" | "transak") => void; +} + +function PaymentOptions({ onSelect }: PaymentOptionsProps) { + const paymentMethods = [ + { + provider: "stripe" as const, + name: "Stripe", + description: "Credit Card, Apple Pay, Google Pay", + logo: "💳", + fees: "3.5% + $0.30", + popular: true, + }, + { + provider: "coinbase" as const, + name: "Coinbase Pay", + description: "Bank Transfer, Debit Card", + logo: "🟦", + fees: "1% (Bank) / 3.9% (Card)", + popular: false, + }, + { + provider: "transak" as const, + name: "Transak", + description: "Multiple Payment Options", + logo: "🌐", + fees: "0.99% - 5.5%", + popular: false, + }, + ]; + + return ( +
+
+

Choose Payment Method

+

+ Select how you'd like to pay for your NFT +

+
+ +
+ {paymentMethods.map((method) => ( + + ))} +
+ +
+
+ 🔒 + Secure Payment Processing +
+

+ All payments are processed securely through industry-leading providers. + Your payment information is never stored on our servers. +

+
+
+ ); +} + +function ProcessingStep({ provider }: { provider: "stripe" | "coinbase" | "transak" }) { + const [currentStep, setCurrentStep] = useState<"payment" | "confirmation" | "minting">("payment"); + + // Auto-progress through steps for demo purposes + useEffect(() => { + const timer1 = setTimeout(() => setCurrentStep("confirmation"), 5000); + const timer2 = setTimeout(() => setCurrentStep("minting"), 10000); + + return () => { + clearTimeout(timer1); + clearTimeout(timer2); + }; + }, []); + + const providerInfo = { + stripe: { name: "Stripe", logo: "💳", color: "blue" }, + coinbase: { name: "Coinbase Pay", logo: "🟦", color: "blue" }, + transak: { name: "Transak", logo: "🌐", color: "green" }, + }; + + const info = providerInfo[provider]; + + return ( +
+
+
{info.logo}
+

Processing with {info.name}

+
+ + {/* Progress Steps */} +
+
+
+ {currentStep === "payment" ? "1" : "✓"} +
+
+
Complete Payment
+
+ {currentStep === "payment" ? "Finish your purchase in the payment window" : "Payment completed successfully"} +
+
+
+ +
+
+ {currentStep === "confirmation" ? "2" : currentStep === "minting" ? "✓" : "2"} +
+
+
Confirming Transaction
+
+ {currentStep === "confirmation" ? "Verifying your payment on the blockchain" : + currentStep === "minting" ? "Transaction confirmed" : "Waiting for payment completion"} +
+
+
+ +
+
+ 3 +
+
+
Minting Your NFT
+
+ {currentStep === "minting" ? "Creating your unique NFT on the blockchain" : "Preparing to mint your NFT"} +
+
+
+
+ + {currentStep === "payment" && ( +
+

+ Complete your payment in the {info.name} window to continue. + This page will automatically update when payment is confirmed. +

+
+ )} + + {currentStep === "minting" && ( +
+
+

+ Almost done! Your NFT is being created on the blockchain. + This usually takes 30-60 seconds. +

+
+ )} +
+ ); +} +``` +
+ + +Implement the final steps of the user journey: + +```typescript +import { sepolia } from "thirdweb/chains"; + +function SuccessStep({ + txHash, + nftMetadata +}: { + txHash: string; + nftMetadata: typeof NFT_METADATA; +}) { + const explorerUrl = `${sepolia.blockExplorers?.[0]?.url}/tx/${txHash}`; + + return ( +
+
🎉
+
+

NFT Minted Successfully!

+

+ Congratulations! Your NFT has been minted and is now in your wallet. +

+
+ +
+

{nftMetadata.name}

+

{nftMetadata.description}

+ + View on Block Explorer + +
+ +
+ + +
+
+ ); +} + +function ErrorStep({ + error, + onRetry +}: { + error: string; + onRetry: () => void; +}) { + return ( +
+
+
+

Something went wrong

+

+ We couldn't complete your NFT purchase. Please try again. +

+
+ + Error details + +

+ {error} +

+
+
+ +
+ + +
+
+ ); +} +``` + +
+ + +Put it all together in your main application: + +```typescript +import { ThirdwebProvider } from "thirdweb/react"; +// Import all the components we defined earlier +import { NFTCheckout, WalletAuth, PaymentOptions, ProcessingStep, SuccessStep, ErrorStep } from "./nft-checkout-components"; + +function App() { + return ( + +
+
+
+

+ NFT Marketplace +

+

+ Buy NFTs seamlessly with fiat currency +

+
+ + + +
+

Powered by thirdweb Universal Bridge

+
+
+
+
+ ); +} + +export default App; +``` +
+ \ No newline at end of file diff --git a/apps/portal/src/app/pay/guides/onramp-integration/page.mdx b/apps/portal/src/app/pay/guides/onramp-integration/page.mdx new file mode 100644 index 00000000000..a3fe549ea0f --- /dev/null +++ b/apps/portal/src/app/pay/guides/onramp-integration/page.mdx @@ -0,0 +1,199 @@ +import { + createMetadata, + Callout, + DocImage, + InstallTabs, + Steps, + Step, + Tabs, + TabsList, + TabsTrigger, + TabsContent, +} from "@doc"; + +export const metadata = createMetadata({ + image: { + title: "thirdweb Universal Bridge - Onramp Integration", + icon: "thirdweb", + }, + title: "thirdweb Universal Bridge - Onramp Integration | thirdweb", + description: + "Learn how to integrate fiat-to-crypto onramps using Stripe, Coinbase, and Transak with Universal Bridge.", +}); + +# Fiat-to-Crypto Onramp Integration + +Learn how to integrate seamless fiat-to-crypto onramps using Universal Bridge. This guide covers integration with Stripe, Coinbase, and Transak providers, enabling your users to purchase crypto directly with fiat currency. + +Universal Bridge's onramp functionality provides a unified interface across multiple providers, automatic routing to the best rates, and comprehensive status tracking. + +--- + + + + + + + +Configure your client and understand the available onramp providers: + +```typescript +import { createThirdwebClient } from "thirdweb"; +import { Bridge, NATIVE_TOKEN_ADDRESS } from "thirdweb"; + +const client = createThirdwebClient({ + clientId: "your_client_id" +}); +``` + + + + +Create a basic onramp experience for your users: + + + +Stripe +Coinbase +Transak + + + + +```typescript +import { Bridge, NATIVE_TOKEN_ADDRESS, toWei } from "thirdweb"; + + const onrampSession = await Bridge.Onramp.prepare({ + client, + onramp: "stripe", + chainId: 1, // Ethereum + tokenAddress: NATIVE_TOKEN_ADDRESS, // ETH + receiver: userAddress, + amount: toWei("0.1"), // 0.1 ETH + currency: "USD", + country: "US", // User's country - important for compliance + }); + + window.open(onrampSession.link); +``` + + + + + +```typescript +import { Bridge, NATIVE_TOKEN_ADDRESS, toWei } from "thirdweb"; + +async function createCoinbaseOnramp(userAddress: string) { + try { + const onrampSession = await Bridge.Onramp.prepare({ + client, + onramp: "coinbase", + chainId: 8453, // Base (Coinbase's L2) + tokenAddress: NATIVE_TOKEN_ADDRESS, // ETH on Base + receiver: userAddress, + amount: toWei("0.05"), // 0.05 ETH + currency: "USD", + country: "US", + }); + + console.log("Coinbase onramp session:", onrampSession.id); + console.log("Estimated cost:", `$${onrampSession.currencyAmount}`); + + // Redirect to Coinbase onramp + window.location.href = onrampSession.link; + + return onrampSession; + } catch (error) { + console.error("Failed to create Coinbase onramp:", error); + throw error; + } +} +``` + + + + +```typescript +import { Bridge, NATIVE_TOKEN_ADDRESS, toWei } from "thirdweb"; + +async function createTransakOnramp(userAddress: string) { + try { + const onrampSession = await Bridge.Onramp.prepare({ + client, + onramp: "transak", + chainId: 137, // Polygon + tokenAddress: NATIVE_TOKEN_ADDRESS, // MATIC + receiver: userAddress, + amount: toWei("10"), // 10 MATIC + currency: "USD", + country: "US", + }); + + // Redirect to Transak + window.open(onrampSession.link); + + return onrampSession; + } catch (error) { + console.error("Failed to create Transak onramp:", error); + throw error; + } +} +``` + + + + + +Monitor onramp transactions and handle completion: + +```typescript +import { Bridge } from "thirdweb"; + +// Monitor onramp status + const status = await Bridge.Onramp.status({ + sessionId, + client, + }); + + switch (status.status) { + case "COMPLETED": + console.log("Onramp completed successfully!"); + console.log("Transaction hash:", status.transactionHash); + console.log("Amount received:", status.destinationAmount); + // Update your UI to show success + break; + + case "PENDING": + console.log("Onramp in progress..."); + // Show loading state to user + setTimeout(() => monitorOnrampStatus(sessionId), 10000); // Check again in 10s + break; + + case "FAILED": + console.log("Onramp failed:", status.error); + // Show error message to user + break; + + case "CANCELLED": + console.log("Onramp was cancelled by user"); + // Handle cancellation + break; + + default: + console.log("Unknown status:", status.status); + } +``` + + + +## Next Steps + +- **[Onramp Providers](/pay/onramp-providers)** - Detailed provider comparison and features +- **[Webhooks](/pay/webhooks)** - Set up real-time onramp status notifications +- **[Testing](/pay/testing-pay)** - Test onramp flows in development mode +- **[Universal Bridge API](https://bridge.thirdweb.com/reference)** - Complete API reference diff --git a/apps/portal/src/app/pay/guides/smart-accounts/page.mdx b/apps/portal/src/app/pay/guides/smart-accounts/page.mdx new file mode 100644 index 00000000000..7319b7c19c3 --- /dev/null +++ b/apps/portal/src/app/pay/guides/smart-accounts/page.mdx @@ -0,0 +1,154 @@ +import { + createMetadata, + Callout, + DocImage, + InstallTabs, + Steps, + Step, +} from "@doc"; + +export const metadata = createMetadata({ + image: { + title: "thirdweb Universal Bridge - Smart Accounts", + icon: "thirdweb", + }, + title: "thirdweb Universal Bridge - Smart Accounts | thirdweb", + description: + "Learn how to use EIP7702 smart accounts with batched transactions for seamless Universal Bridge experiences.", +}); + +# Smart Accounts with Universal Bridge + +Learn how to leverage EIP7702 smart accounts to batch Universal Bridge transactions for optimal user experience. This approach enables gasless transactions, batched execution, and seamless cross-chain operations. + +Smart accounts with EIP7702 allow you to upgrade any EOA (Externally Owned Account) into a smart account without code changes, enabling advanced features like transaction batching and gas sponsorship. + +--- + + + + + + + +Configure an in-app wallet with EIP7702 execution mode to enable smart account features: + +```typescript +import { createThirdwebClient } from "thirdweb"; +import { inAppWallet } from "thirdweb/wallets/in-app"; + +const client = createThirdwebClient({ + clientId: "your_client_id" +}); + +// Create in-app wallet with EIP7702 smart account features +const wallet = inAppWallet({ + executionMode: { + mode: "EIP7702", + sponsorGas: true, // Enable gas sponsorship + }, +}); + +// Connect the wallet +const account = await wallet.connect({ + client, + strategy: "google", // or other auth strategies +}); +``` + + + EIP7702 execution mode upgrades your EOA into a smart account, enabling batched transactions, gas sponsorship, and other advanced features without requiring contract deployment. + + + + + +Use Universal Bridge to prepare transactions, then batch them per step (since each step may be on a different chain): + +```typescript +import { Bridge, NATIVE_TOKEN_ADDRESS, toWei } from "thirdweb"; +import { sendBatchTransaction } from "thirdweb"; + +const bridgePreparation = await Bridge.Buy.prepare({ + originChainId: 1, // Ethereum + originTokenAddress: NATIVE_TOKEN_ADDRESS, + destinationChainId: 137, // Polygon + destinationTokenAddress: NATIVE_TOKEN_ADDRESS, + amount: toWei("0.1"), // 0.1 MATIC + sender: account.address, + receiver: account.address, + client, +}); +``` + + + **Key Rule**: You can only batch transactions that are on the same chain. Since Universal Bridge steps may span multiple chains, you must batch transactions within each step separately, not across steps. + + + + + +Execute transactions in batches per step (same chain), proceeding through steps sequentially: + +```typescript +import { sendBatchTransaction, waitForReceipt } from "thirdweb"; +// Execute each step's transactions as a batch (same chain) +for (let i = 0; i < bridgePreparation.steps.length; i++) { + const step = bridgePreparation.steps[i]; + + // Batch all transactions within this step (they're all on the same chain) + const stepBatchResult = await sendBatchTransaction({ + transactions: step.transactions, + account, + }); + + console.log(`Step ${i + 1} batch sent:`, stepBatchResult.transactionHash); + + const receipt = await waitForReceipt({ + client, + chain: step.transactions[0].chain, + transactionHash: stepBatchResult.transactionHash, + }); + + console.log(`Step ${i + 1} completed:`, receipt.status); + + // Monitor bridge completion status for this step + let bridgeStatus; + do { + bridgeStatus = await Bridge.status({ + transactionHash: stepBatchResult.transactionHash, + chainId: step.transactions[0].chainId, + client, + }); + + if (bridgeStatus.status === "PENDING") { + console.log(`Bridge step ${i + 1} still pending...`); + await new Promise((resolve) => setTimeout(resolve, 5000)); + } + } while (bridgeStatus.status === "PENDING"); + + if (bridgeStatus.status === "COMPLETED") { + console.log(`Bridge step ${i + 1} completed!`); + } else if (bridgeStatus.status === "FAILED") { + throw new Error(`Bridge step ${i + 1} failed`); + } +} +``` + + + With EIP7702 smart accounts, transactions within each step execute atomically. Steps execute sequentially, ensuring proper cross-chain coordination. Gas sponsorship applies to all batched transactions. + + + + + +## Next Steps + +- **[EIP7702 Documentation](/connect/account-abstraction/overview)** - Learn more about smart account features +- **[Batching Transactions](/typescript/v5/account-abstraction/batching-transactions)** - Explore advanced batching patterns +- **[Gas Sponsorship](/connect/account-abstraction/sponsoring-gas)** - Set up custom gas sponsorship rules +- **[Universal Bridge API](/pay/webhooks)** - Monitor cross-chain operations with webhooks diff --git a/apps/portal/src/app/pay/page.mdx b/apps/portal/src/app/pay/page.mdx index c3235322a96..fb35102c83a 100644 --- a/apps/portal/src/app/pay/page.mdx +++ b/apps/portal/src/app/pay/page.mdx @@ -1,7 +1,7 @@ import { createMetadata, DocImage, Grid, SDKCard, FeatureCard } from "@doc"; import PayOverviewImage from "./assets/pay-overview.png"; import SupportedChains from "../_images/supported-chains.png"; -import {RocketIcon, ArrowLeftRightIcon, WalletIcon, PencilIcon, ShieldCheckIcon, PiggyBankIcon, GlobeIcon, ComponentIcon} from "lucide-react"; +import {RocketIcon, ArrowLeftRightIcon, WalletIcon, PencilIcon, ShieldCheckIcon, PiggyBankIcon, GlobeIcon, ComponentIcon, CodeIcon, ApiIcon, WebhookIcon} from "lucide-react"; export const metadata = createMetadata({ image: { @@ -10,7 +10,7 @@ export const metadata = createMetadata({ }, title: "thirdweb Universal Bridge Docs: Web3 Payments, On-ramping, bridging & swapping", description: - "Learn everything about thirdweb’s web3 payments solution, Universal Bridge. Technical docs on onramping, bridging + swapping.", + "Learn everything about thirdweb's web3 payments solution, Universal Bridge. Technical docs on onramping, bridging + swapping.", }); # Universal Bridge @@ -25,50 +25,56 @@ Universal Bridge allows you to create both simple and advanced payment flows for className="my-4 grid gap-2 md:grid-cols-2 lg:grid-cols-2 " > } + title="Cross-Chain Asset Routing" + description="Intelligent multi-hop routing to find optimal paths between any supported tokens and chains." + iconUrl={} /> } + title="Fiat-to-Crypto Onramps" + description="Direct integration with Stripe, Coinbase, and Transak for seamless fiat onramps." + iconUrl={} /> + } - /> - } /> - } - /> - + } + title="Global Coverage" + description="Support for 160+ countries and regions for fiat onramps and cross-chain transactions." + iconUrl={} /> - } + + } /> - } + + } />
+## SDK Modules + +The Universal Bridge SDK is organized into several modules, each handling specific functionality: + +| Module | Purpose | Key Functions | +|--------|---------|---------------| +| **Buy** | Cross-chain token purchasing | `quote()`, `prepare()` | +| **Sell** | Token selling and swapping | `quote()`, `prepare()` | +| **Transfer** | Same-chain and cross-chain transfers | `prepare()` | +| **Onramp** | Fiat-to-crypto conversion | `prepare()`, `status()` | +| **Routes** | Route discovery and filtering | `routes()` | +| **Status** | Transaction status tracking | `status()` | + ## Supported Chains Universal Bridge is supported on select EVM compatible chains. To view the full list, visit [thirdweb chainlist](https://thirdweb.com/chainlist?service=pay). @@ -83,12 +89,71 @@ Universal Bridge is supported on select EVM compatible chains. To view the full | Stripe | https://docs.stripe.com/crypto/onramp | | Coinbase | https://docs.cdp.coinbase.com/onramp/docs/payment-methods/ | -## Integration Options +## Implementation Approaches + +| APPROACH | DESCRIPTION | BEST FOR | +| -------- | ----------- | -------- | +| **[TypeScript SDK](https://portal.thirdweb.com/typescript/v5/buy/quote)** | Full-featured SDK with quote, prepare, and status functions | Developers building custom applications with complete control | +| **[REST API](https://bridge.thirdweb.com/reference)** | Direct API access for any programming language | Backend services, mobile apps, or non-JavaScript environments | +| **[Webhooks](https://portal.thirdweb.com/pay/webhooks)** | Real-time transaction status notifications | Applications requiring instant completion updates | +| **[Playground](https://playground.thirdweb.com/connect/pay)** | Interactive testing environment | Testing and prototyping bridge functionality | + +## Quick Start Examples + +### Basic Cross-Chain Purchase +```typescript +import { Bridge, NATIVE_TOKEN_ADDRESS } from "thirdweb"; + +// Get a quote for purchasing ETH on Optimism using ETH on Mainnet +const quote = await Bridge.Buy.quote({ + originChainId: 1, // Ethereum Mainnet + originTokenAddress: NATIVE_TOKEN_ADDRESS, + destinationChainId: 10, // Optimism + destinationTokenAddress: NATIVE_TOKEN_ADDRESS, + amount: toWei("0.01"), + client: thirdwebClient, +}); + +// Prepare finalized transactions +const prepared = await Bridge.Buy.prepare({ + originChainId: 1, + originTokenAddress: NATIVE_TOKEN_ADDRESS, + destinationChainId: 10, + destinationTokenAddress: NATIVE_TOKEN_ADDRESS, + amount: toWei("0.01"), + sender: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709", + receiver: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709", + client: thirdwebClient, +}); +``` + +### Fiat Onramp +```typescript +// Prepare a Stripe onramp to purchase ETH +const onramp = await Bridge.Onramp.prepare({ + client: thirdwebClient, + onramp: "stripe", + chainId: 1, + tokenAddress: NATIVE_TOKEN_ADDRESS, + receiver: "0x2a4f24F935Eb178e3e7BA9B53A5Ee6d8407C0709", + amount: toWei("10"), + country: "US", +}); + +// Redirect user to onramp.link to complete purchase +``` + +### Status Tracking +```typescript +// Check transaction status +const status = await Bridge.status({ + transactionHash: "0x5959b9321ec581640db531b80bac53cbd968f3d34fc6cb1d5f4ea75f26df2ad7", + chainId: 137, + client: thirdwebClient, +}); -| OPTION | BEST FOR | -| --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | -| [Buy Crypto](https://playground.thirdweb.com/connect/pay/) | Developers who want onramp and crypto purchase experiences directly in their application. | -| [Transactions](https://playground.thirdweb.com/connect/pay/transactions) | Developers who want users to onramp or purchase crypto directly into a transaction. Great for minting and NFT purchase flows. | -| [In-App Purchases](https://playground.thirdweb.com/connect/pay/commerce) | Developers who want to take payments from Fiat or Crypto directly to a seller wallet | -| [SDK](https://portal.thirdweb.com/typescript/v5/buy/quote) | Build your own UI with the SDK. | -| [API](https://playground.thirdweb.com/connect/pay/backend) | Control the full experience with the API. | +if (status.status === "COMPLETED") { + console.log("Bridge completed!"); + console.log("Final amount:", status.destinationAmount); +} +``` diff --git a/apps/portal/src/app/pay/sidebar.tsx b/apps/portal/src/app/pay/sidebar.tsx index bb0163ff0e0..c6d13bf074c 100644 --- a/apps/portal/src/app/pay/sidebar.tsx +++ b/apps/portal/src/app/pay/sidebar.tsx @@ -5,7 +5,6 @@ import { BracesIcon, CircleDollarSignIcon, CodeIcon, - FlaskConicalIcon, MessageCircleQuestionIcon, PaletteIcon, RocketIcon, @@ -45,16 +44,12 @@ export const sidebar: SideBar = { icon: , links: [ { - name: "ConnectButton", - href: `${paySlug}/get-started#option-1-connectbutton`, - }, - { - name: "Embed", - href: `${paySlug}/get-started#option-2-embed-pay`, + name: "Installation", + href: `${paySlug}/get-started#installation`, }, { - name: "Send a Transaction", - href: `${paySlug}/get-started#option-3-send-a-transaction-with-pay`, + name: "Recipes", + href: `${paySlug}/get-started#recipes`, }, ], }, @@ -64,16 +59,20 @@ export const sidebar: SideBar = { icon: , links: [ { - name: "Accept Direct Payments", - href: `${paySlug}/guides/accept-direct-payments`, + name: "Cross-Chain Swapping", + href: `${paySlug}/guides/cross-chain-swapping`, }, { - name: "Build a Custom Onramp Experience", - href: `${paySlug}/guides/build-a-custom-experience`, + name: "Swap with Smart Accounts", + href: `${paySlug}/guides/smart-accounts`, }, { - name: "Cross-Chain Swapping", - href: `${paySlug}/guides/cross-chain-swapping`, + name: "Fiat Onramp", + href: `${paySlug}/guides/onramp-integration`, + }, + { + name: "NFT Checkout", + href: `${paySlug}/guides/nft-checkout`, }, ], }, @@ -117,11 +116,6 @@ export const sidebar: SideBar = { href: `${paySlug}/webhooks`, icon: , }, - { - name: "Developer Mode", - href: `${paySlug}/testing-pay`, - icon: , - }, { name: "FAQs", href: `${paySlug}/faqs`, diff --git a/apps/portal/src/app/pay/testing-pay/page.mdx b/apps/portal/src/app/pay/testing-pay/page.mdx deleted file mode 100644 index f4b17494272..00000000000 --- a/apps/portal/src/app/pay/testing-pay/page.mdx +++ /dev/null @@ -1,151 +0,0 @@ -import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; -import { createMetadata, Callout } from "@doc"; - -export const metadata = createMetadata({ - image: { - title: "thirdweb Universal Bridge - Test Mode", - icon: "thirdweb", - }, - title: "Enable Test Mode for Universal bridge — thirdweb docs", - description: "Learn how to enable test mode for web3 payments with the thirdweb Universal bridge feature in our technical docs", -}); - -# Enable Test Mode - -Developers can turn on Test Mode to test both fiat-to-crypto transactions and crypto-to-crypto transactions on supported testnet chains. It is possible to test both at the same time or separately. - -## Buy With Fiat - -By setting `testMode` to `true` for Buy With Fiat, you can enable test experiences for our underlying providers (Coinbase, Stripe, and Transak). - - - - - ConnectButton - PayEmbed - sendTransaction - - - - -```tsx - -``` - - - - - -```tsx - -``` - - - - - -```tsx -const { mutate: sendTransaction } = useSendTransaction({ - payModal: { - buyWithFiat: { - testMode: true, // defaults to false - }, - }, -}); -``` - - - - - -For Coinbase onramp, you can turn on "Mocked mode" when launching the Onramp experience. - -At the bottom of the Onramp modal where it says "Secured by Coinbase", click 10 times on the word "Secured." This will navigate you to Onramp's Debug Menu. There, navigate to the Actions tab click on the toggle for "Enable Mocked Buy and Send." You can now exit the Debug Menu and proceed with mock buys and sends. Remember to click on the "Enable Mocked Buy and Send" toggle once you are done to enable actual buys and sends. - -For other providers, you can use the following information when using any provider in test mode: - -| Provider | Card Type | Name | Number | Expiration Date | CVV | Other Info | -| -------- | ------------ | ----------- | ------------------- | --------------- | ------------ | ---------------------------------------------------------------- | -| Stripe | Visa | Any Name | 4242 4242 4242 4242 | Any Future Date | Any 3 Digits | SSN: 000000000, Address line 1: address_full_match, OTP : 000000 | -| Stripe | Visa (Debit) | Any Name | 4000 0566 5566 5556 | Any Future Date | Any 3 Digits | SSN: 000000000, Address line 1: address_full_match, OTP : 000000 | -| Transak | Visa | Any Name | 4111 1111 1111 1111 | 10/33 | 123 | Payment Authorization 3Ds screen password: **Checkout1!** | - -For Stripe testing, you can view more test cards [here](https://docs.stripe.com/testing). - -For Transak testing, you can view detailed information [here](https://docs.transak.com/docs/test-credentials#card-payments). - -## Buy With Crypto - -Buy With Crypto test mode will show only Testnets and native test currencies. If you have a custom that you would like supported in test mode, [please reach out to us](https://thirdweb.com/contact-us). You can see a list of supported testnets on the [thirdweb Chainlist page](https://thirdweb.com/chainlist?service=pay&type=testnet). - - - - - ConnectButton - PayEmbed - sendTransaction - - - - -```tsx - -``` - - - - - -```tsx - -``` - - - - - -```tsx -const { mutate: sendTransaction } = useSendTransaction({ - payModal: { - buyWithCrypto: { - testMode: true, // defaults to false - }, - }, -}); -``` - - - -