);
}
+
+function DotsBackgroundPattern(props: { className?: string }) {
+ return (
+
+ );
+}
diff --git a/apps/nebula/src/app/(app)/components/EmptyStateChatPageContent.tsx b/apps/nebula/src/app/(app)/components/EmptyStateChatPageContent.tsx
index fb81fe4db52..1288a27b253 100644
--- a/apps/nebula/src/app/(app)/components/EmptyStateChatPageContent.tsx
+++ b/apps/nebula/src/app/(app)/components/EmptyStateChatPageContent.tsx
@@ -23,7 +23,7 @@ export function EmptyStateChatPageContent(props: {
onLoginClick: undefined | (() => void);
}) {
return (
-
+
{props.showAurora && (
)}
diff --git a/apps/playground-web/next.config.mjs b/apps/playground-web/next.config.mjs
index 48675364033..b6f239b9857 100644
--- a/apps/playground-web/next.config.mjs
+++ b/apps/playground-web/next.config.mjs
@@ -148,12 +148,12 @@ const nextConfig = {
permanent: false,
},
{
- source: "/insight",
+ source: "/insight/:path*",
destination: "https://insight.thirdweb.com/reference",
permanent: false,
},
{
- source: "/payments/backend",
+ source: "/payments/backend/:path*",
destination: "/reference#tag/payments",
permanent: false,
},
diff --git a/apps/playground-web/package.json b/apps/playground-web/package.json
index 38c353e8bb1..35764a3b1fd 100644
--- a/apps/playground-web/package.json
+++ b/apps/playground-web/package.json
@@ -48,7 +48,6 @@
"thirdweb": "workspace:*",
"use-debounce": "^10.0.5",
"use-stick-to-bottom": "^1.1.1",
- "x402-next": "^0.6.1",
"zod": "3.25.75"
},
"devDependencies": {
diff --git a/apps/playground-web/src/app/bridge/swap-widget/components/right-section.tsx b/apps/playground-web/src/app/bridge/swap-widget/components/right-section.tsx
index 97431df203b..6441d31e26d 100644
--- a/apps/playground-web/src/app/bridge/swap-widget/components/right-section.tsx
+++ b/apps/playground-web/src/app/bridge/swap-widget/components/right-section.tsx
@@ -59,7 +59,9 @@ export function RightSection(props: { options: SwapWidgetPlaygroundOptions }) {
prefill={props.options.prefill}
currency={props.options.currency}
showThirdwebBranding={props.options.showThirdwebBranding}
- key={JSON.stringify(props.options)}
+ key={JSON.stringify({
+ prefill: props.options.prefill,
+ })}
persistTokenSelections={false}
/>
)}
diff --git a/apps/playground-web/src/app/data/pages-metadata.ts b/apps/playground-web/src/app/data/pages-metadata.ts
index 70ab97af026..744c89365fe 100644
--- a/apps/playground-web/src/app/data/pages-metadata.ts
+++ b/apps/playground-web/src/app/data/pages-metadata.ts
@@ -4,6 +4,7 @@ import {
BotIcon,
BoxIcon,
BringToFrontIcon,
+ CircleDollarSignIcon,
CircleUserIcon,
CreditCardIcon,
DollarSignIcon,
@@ -186,6 +187,13 @@ export const paymentsFeatureCards: FeatureCardMetadata[] = [
description:
"Enable users to pay for onchain transactions with fiat or crypto",
},
+ {
+ icon: CircleDollarSignIcon,
+ title: "x402",
+ link: "/payments/x402",
+ description:
+ "Use the x402 payment protocol to pay for API calls using any web3 wallet",
+ },
];
export const accountAbstractionsFeatureCards: FeatureCardMetadata[] = [
diff --git a/apps/playground-web/src/app/payments/page.tsx b/apps/playground-web/src/app/payments/page.tsx
new file mode 100644
index 00000000000..ee256d1ee10
--- /dev/null
+++ b/apps/playground-web/src/app/payments/page.tsx
@@ -0,0 +1,14 @@
+import { OverviewPage } from "@/components/blocks/OverviewPage";
+import { PayIcon } from "@/icons/PayIcon";
+import { paymentsFeatureCards } from "../data/pages-metadata";
+
+export default function Page() {
+ return (
+
+ );
+}
diff --git a/apps/playground-web/src/app/payments/x402/components/x402-client-preview.tsx b/apps/playground-web/src/app/payments/x402/components/x402-client-preview.tsx
index 55597040afd..53767c02265 100644
--- a/apps/playground-web/src/app/payments/x402/components/x402-client-preview.tsx
+++ b/apps/playground-web/src/app/payments/x402/components/x402-client-preview.tsx
@@ -3,7 +3,7 @@
import { useMutation } from "@tanstack/react-query";
import { CodeClient } from "@workspace/ui/components/code/code.client";
import { CodeIcon, LockIcon } from "lucide-react";
-import { baseSepolia } from "thirdweb/chains";
+import { arbitrumSepolia } from "thirdweb/chains";
import {
ConnectButton,
getDefaultToken,
@@ -15,7 +15,7 @@ import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { THIRDWEB_CLIENT } from "../../../../lib/client";
-const chain = baseSepolia;
+const chain = arbitrumSepolia;
const token = getDefaultToken(chain, "USDC");
export function X402ClientPreview() {
diff --git a/apps/playground-web/src/app/payments/x402/page.tsx b/apps/playground-web/src/app/payments/x402/page.tsx
index 427813f5b4f..51e38809c34 100644
--- a/apps/playground-web/src/app/payments/x402/page.tsx
+++ b/apps/playground-web/src/app/payments/x402/page.tsx
@@ -46,8 +46,8 @@ function ServerCodeExample() {
Next.js Server Code Example
- Use any x402 middleware + the thirdweb facilitator to settle
- transactions with our server wallet.
+ Create middleware with the thirdweb facilitator to settle transactions
+ with your server wallet.
@@ -57,28 +57,41 @@ function ServerCodeExample() {
className="h-full rounded-none border-none"
code={`// src/middleware.ts
-import { facilitator } from "thirdweb/x402";
+import { facilitator, settlePayment } from "thirdweb/x402";
import { createThirdwebClient } from "thirdweb";
-import { paymentMiddleware } from "x402-next";
const client = createThirdwebClient({ secretKey: "your-secret-key" });
+const thirdwebX402Facilitator = facilitator({
+ client,
+ serverWalletAddress: "0xYourWalletAddress",
+});
-export const middleware = paymentMiddleware(
- "0xYourWalletAddress",
- {
- "/api/paid-endpoint": {
- price: "$0.01",
- network: "base-sepolia",
- config: {
- description: "Access to paid content",
- },
- },
- },
- facilitator({
- client,
- serverWalletAddress: "0xYourServerWalletAddress",
- }),
-);
+export async function middleware(request: NextRequest) {
+ const method = request.method.toUpperCase();
+ const resourceUrl = request.nextUrl.toString();
+ const paymentData = request.headers.get("X-PAYMENT");
+
+ const result = await settlePayment({
+ resourceUrl,
+ method,
+ paymentData,
+ payTo: "0xYourWalletAddress",
+ network: "eip155:11155111", // or any other chain id
+ price: "$0.01", // can also be a ERC20 token amount
+ facilitator: thirdwebX402Facilitator,
+ });
+
+ if (result.status === 200) {
+ // payment successful, execute the request
+ return NextResponse.next();
+ }
+
+ // otherwise, request payment
+ return NextResponse.json(result.responseBody, {
+ status: result.status,
+ headers: result.responseHeaders,
+ });
+}
// Configure which paths the middleware should run on
export const config = {
diff --git a/apps/playground-web/src/components/blocks/OverviewPage.tsx b/apps/playground-web/src/components/blocks/OverviewPage.tsx
index 9d7733e9591..b36a87793d5 100644
--- a/apps/playground-web/src/components/blocks/OverviewPage.tsx
+++ b/apps/playground-web/src/components/blocks/OverviewPage.tsx
@@ -19,7 +19,7 @@ export function OverviewPage(props: {
{props.title}
-
+
{props.description}
diff --git a/apps/playground-web/src/middleware.ts b/apps/playground-web/src/middleware.ts
index 047222743fe..eb74b1046ca 100644
--- a/apps/playground-web/src/middleware.ts
+++ b/apps/playground-web/src/middleware.ts
@@ -1,34 +1,59 @@
+import { type NextRequest, NextResponse } from "next/server";
import { createThirdwebClient } from "thirdweb";
-import { facilitator } from "thirdweb/x402";
-import { paymentMiddleware } from "x402-next";
+import { arbitrumSepolia } from "thirdweb/chains";
+import { facilitator, settlePayment } from "thirdweb/x402";
const client = createThirdwebClient({
secretKey: process.env.THIRDWEB_SECRET_KEY as string,
});
+const chain = arbitrumSepolia;
const BACKEND_WALLET_ADDRESS = process.env.ENGINE_BACKEND_WALLET as string;
const ENGINE_VAULT_ACCESS_TOKEN = process.env
.ENGINE_VAULT_ACCESS_TOKEN as string;
const API_URL = `https://${process.env.NEXT_PUBLIC_API_URL || "api.thirdweb.com"}`;
-export const middleware = paymentMiddleware(
- "0xdd99b75f095d0c4d5112aCe938e4e6ed962fb024",
- {
- "/api/paywall": {
- price: "$0.01",
- network: "base-sepolia",
- config: {
- description: "Access to paid content",
- },
+const twFacilitator = facilitator({
+ baseUrl: `${API_URL}/v1/payments/x402`,
+ client,
+ serverWalletAddress: BACKEND_WALLET_ADDRESS,
+ vaultAccessToken: ENGINE_VAULT_ACCESS_TOKEN,
+});
+
+export async function middleware(request: NextRequest) {
+ const pathname = request.nextUrl.pathname;
+ const method = request.method.toUpperCase();
+ const resourceUrl = `${request.nextUrl.protocol}//${request.nextUrl.host}${pathname}`;
+ const paymentData = request.headers.get("X-PAYMENT");
+
+ const result = await settlePayment({
+ resourceUrl,
+ method,
+ paymentData,
+ payTo: "0xdd99b75f095d0c4d5112aCe938e4e6ed962fb024",
+ network: `eip155:${chain.id}`,
+ price: "$0.01",
+ routeConfig: {
+ description: "Access to paid content",
},
- },
- facilitator({
- baseUrl: `${API_URL}/v1/payments/x402`,
- client,
- serverWalletAddress: BACKEND_WALLET_ADDRESS,
- vaultAccessToken: ENGINE_VAULT_ACCESS_TOKEN,
- }),
-);
+ facilitator: twFacilitator,
+ });
+
+ if (result.status === 200) {
+ // payment successful, execute the request
+ const response = NextResponse.next();
+ for (const [key, value] of Object.entries(result.responseHeaders)) {
+ response.headers.set(key, value);
+ }
+ return response;
+ }
+
+ // otherwise, request payment
+ return NextResponse.json(result.responseBody, {
+ status: result.status,
+ headers: result.responseHeaders,
+ });
+}
// Configure which paths the middleware should run on
export const config = {
diff --git a/apps/portal/src/app/bridge/swap/page.mdx b/apps/portal/src/app/bridge/swap/page.mdx
index 41ee17c68ce..8d5afd6bbc1 100644
--- a/apps/portal/src/app/bridge/swap/page.mdx
+++ b/apps/portal/src/app/bridge/swap/page.mdx
@@ -13,6 +13,7 @@ import {
TypeScriptIcon,
} from "@/icons";
import SwapWidgetImage from "./swap-dark.png";
+import SwapWidgetImageLight from "./swap-light.png";
export const metadata = createMetadata({
image: {
@@ -55,7 +56,12 @@ function Example() {
}
```
+
+
+
+
+
## Live Playground
diff --git a/apps/portal/src/app/bridge/swap/swap-dark.png b/apps/portal/src/app/bridge/swap/swap-dark.png
index 48ca746d6f8..267571d55b9 100644
Binary files a/apps/portal/src/app/bridge/swap/swap-dark.png and b/apps/portal/src/app/bridge/swap/swap-dark.png differ
diff --git a/apps/portal/src/app/bridge/swap/swap-light.png b/apps/portal/src/app/bridge/swap/swap-light.png
new file mode 100644
index 00000000000..26ceef282d0
Binary files /dev/null and b/apps/portal/src/app/bridge/swap/swap-light.png differ
diff --git a/apps/portal/src/app/payments/x402/page.mdx b/apps/portal/src/app/payments/x402/page.mdx
index 935a5853955..f3b008d029f 100644
--- a/apps/portal/src/app/payments/x402/page.mdx
+++ b/apps/portal/src/app/payments/x402/page.mdx
@@ -41,40 +41,61 @@ const response = await fetchWithPay('https://api.example.com/paid-endpoint');
## Server Side
-To make your API calls payable, you can use any x402 middleware library like x402-hono, x402-next, x402-express, etc.
+To make your API calls payable, you can use the `settlePayment` function in a middleware or in your endpoint directly.
-Then, use the `facilitator` configuratino function settle transactions with your thirdweb server wallet gaslessly and pass it to the middleware.
+Use the `facilitator` configuration function settle transactions with your thirdweb server wallet gaslessly and pass it to the `settlePayment` function.
-Here's an example with Next.js:
+Here's an example with a Next.js middleware:
```typescript
import { createThirdwebClient } from "thirdweb";
-import { facilitator } from "thirdweb/x402";
-import { paymentMiddleware } from "x402-next";
+import { facilitator, settlePayment } from "thirdweb/x402";
-const client = createThirdwebClient({
- secretKey: process.env.THIRDWEB_SECRET_KEY as string,
+const client = createThirdwebClient({ secretKey: "your-secret-key" });
+const thirdwebX402Facilitator = facilitator({
+ client,
+ serverWalletAddress: "0xYourWalletAddress",
});
-export const middleware = paymentMiddleware(
- "0xdd99b75f095d0c4d5112aCe938e4e6ed962fb024",
- {
- "/api/paid-endpoint": {
- price: "$0.01",
- network: "base-sepolia",
- config: {
- description: "Access to paid content",
- },
+export async function middleware(request: NextRequest) {
+ const method = request.method.toUpperCase();
+ const resourceUrl = request.nextUrl.toString();
+ const paymentData = request.headers.get("X-PAYMENT");
+
+ const result = await settlePayment({
+ resourceUrl,
+ method,
+ paymentData,
+ payTo: "0xYourWalletAddress",
+ network: "eip155:1", // or any other chain id in CAIP2 format: "eip155:
"
+ price: "$0.01", // can also be a ERC20 token amount
+ routeConfig: {
+ description: "Access to paid content",
},
- },
- facilitator({
- client,
- serverWalletAddress: "0x1234567890123456789012345678901234567890",
- }),
-);
+ facilitator: thirdwebX402Facilitator,
+ });
+
+ if (result.status === 200) {
+ // payment successful, execute the request
+ const response = NextResponse.next();
+ // optionally set the response headers back to the client
+ for (const [key, value] of Object.entries(result.responseHeaders)) {
+ response.headers.set(key, value);
+ }
+ return response;
+ }
+
+ // otherwise, request payment
+ return NextResponse.json(result.responseBody, {
+ status: result.status,
+ headers: result.responseHeaders,
+ });
+}
// Configure which paths the middleware should run on
export const config = {
matcher: ["/api/paid-endpoint"],
};
```
+
+You can also use the `verifyPayment` function to verify the payment before settling it. This lets you do the work that requires payment first and then settle the payment.
diff --git a/packages/nebula/CHANGELOG.md b/packages/nebula/CHANGELOG.md
index dc40e081be1..75f9fe25bd0 100644
--- a/packages/nebula/CHANGELOG.md
+++ b/packages/nebula/CHANGELOG.md
@@ -1,5 +1,26 @@
# @thirdweb-dev/nebula
+## 0.2.54
+
+### Patch Changes
+
+- Updated dependencies [[`e1cccd7`](https://github.com/thirdweb-dev/js/commit/e1cccd7a10447943c4b31f34e09a94d2ff5ee826)]:
+ - thirdweb@5.108.1
+
+## 0.2.53
+
+### Patch Changes
+
+- Updated dependencies [[`a94f229`](https://github.com/thirdweb-dev/js/commit/a94f22928a662a5aff7a203fc2d383d9fa0907ec), [`5249cb7`](https://github.com/thirdweb-dev/js/commit/5249cb7409a8486346fe428f824c81dd90845555)]:
+ - thirdweb@5.108.0
+
+## 0.2.52
+
+### Patch Changes
+
+- Updated dependencies [[`93f913c`](https://github.com/thirdweb-dev/js/commit/93f913c614ebbe3db350872bdcff264c07155ce2), [`a85ef0b`](https://github.com/thirdweb-dev/js/commit/a85ef0b222797d38ccd31e72fafda82ceb1faefa)]:
+ - thirdweb@5.107.1
+
## 0.2.51
### Patch Changes
diff --git a/packages/nebula/package.json b/packages/nebula/package.json
index 807b654566b..007fd972294 100644
--- a/packages/nebula/package.json
+++ b/packages/nebula/package.json
@@ -57,5 +57,5 @@
"type": "module",
"types": "./dist/types/exports/thirdweb.d.ts",
"typings": "./dist/types/exports/thirdweb.d.ts",
- "version": "0.2.51"
+ "version": "0.2.54"
}
diff --git a/packages/thirdweb/CHANGELOG.md b/packages/thirdweb/CHANGELOG.md
index e5659c5908f..a66245b2223 100644
--- a/packages/thirdweb/CHANGELOG.md
+++ b/packages/thirdweb/CHANGELOG.md
@@ -1,5 +1,37 @@
# thirdweb
+## 5.108.1
+
+### Patch Changes
+
+- [#8108](https://github.com/thirdweb-dev/js/pull/8108) [`e1cccd7`](https://github.com/thirdweb-dev/js/commit/e1cccd7a10447943c4b31f34e09a94d2ff5ee826) Thanks [@gregfromstl](https://github.com/gregfromstl)! - Displays the failure error messages on the BuyWidget
+
+## 5.108.0
+
+### Minor Changes
+
+- [#8091](https://github.com/thirdweb-dev/js/pull/8091) [`5249cb7`](https://github.com/thirdweb-dev/js/commit/5249cb7409a8486346fe428f824c81dd90845555) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Accept arbitrary chain ids for x402 payments with new settlePayment() and verifyPayment() backend utility functions
+
+### Patch Changes
+
+- [#8100](https://github.com/thirdweb-dev/js/pull/8100) [`a94f229`](https://github.com/thirdweb-dev/js/commit/a94f22928a662a5aff7a203fc2d383d9fa0907ec) Thanks [@MananTank](https://github.com/MananTank)! - Update the `onSuccess`, `onError`, and `onCancel` callback props of the `BuyWidget` to be called with the `quote` object
+
+ ```tsx
+ console.log("Swap completed:", quote)}
+ onError={(error, quote) => console.error("Swap failed:", error, quote)}
+ onCancel={(quote) => console.log("Swap cancelled:", quote)}
+ />
+ ```
+
+## 5.107.1
+
+### Patch Changes
+
+- [#8080](https://github.com/thirdweb-dev/js/pull/8080) [`93f913c`](https://github.com/thirdweb-dev/js/commit/93f913c614ebbe3db350872bdcff264c07155ce2) Thanks [@MananTank](https://github.com/MananTank)! - SwapWidget UI improvements
+
+- [#8092](https://github.com/thirdweb-dev/js/pull/8092) [`a85ef0b`](https://github.com/thirdweb-dev/js/commit/a85ef0b222797d38ccd31e72fafda82ceb1faefa) Thanks [@0xFirekeeper](https://github.com/0xFirekeeper)! - add zephyr testnet to pre-1559 chains
+
## 5.107.0
### Minor Changes
diff --git a/packages/thirdweb/package.json b/packages/thirdweb/package.json
index 5fbca7b7f5d..f4230e6ef26 100644
--- a/packages/thirdweb/package.json
+++ b/packages/thirdweb/package.json
@@ -427,5 +427,5 @@
}
},
"typings": "./dist/types/exports/thirdweb.d.ts",
- "version": "5.107.0"
+ "version": "5.108.1"
}
diff --git a/packages/thirdweb/src/bridge/Token.ts b/packages/thirdweb/src/bridge/Token.ts
index 4c4398328e2..c21f70ba7a6 100644
--- a/packages/thirdweb/src/bridge/Token.ts
+++ b/packages/thirdweb/src/bridge/Token.ts
@@ -141,6 +141,8 @@ export async function tokens<
limit,
offset,
includePrices,
+ sortBy,
+ query,
} = options;
const clientFetch = getClientFetch(client);
@@ -167,6 +169,13 @@ export async function tokens<
if (includePrices !== undefined) {
url.searchParams.set("includePrices", includePrices.toString());
}
+ if (sortBy !== undefined) {
+ url.searchParams.set("sortBy", sortBy);
+ }
+
+ if (query !== undefined) {
+ url.searchParams.set("query", query);
+ }
const response = await clientFetch(url.toString());
if (!response.ok) {
@@ -204,6 +213,10 @@ export declare namespace tokens {
offset?: number | null;
/** Whether or not to include prices for the tokens. Setting this to false will speed up the request. */
includePrices?: IncludePrices;
+ /** Sort by a specific field. */
+ sortBy?: "newest" | "oldest" | "volume" | "market_cap";
+ /** search for tokens by token name or symbol */
+ query?: string;
};
/**
diff --git a/packages/thirdweb/src/bridge/types/Token.ts b/packages/thirdweb/src/bridge/types/Token.ts
index c7588f657f2..04f184f4ae5 100644
--- a/packages/thirdweb/src/bridge/types/Token.ts
+++ b/packages/thirdweb/src/bridge/types/Token.ts
@@ -7,6 +7,8 @@ export type Token = {
symbol: string;
name: string;
iconUri?: string;
+ marketCapUsd?: number;
+ volume24hUsd?: number;
};
export type TokenWithPrices = Token & {
diff --git a/packages/thirdweb/src/exports/x402.ts b/packages/thirdweb/src/exports/x402.ts
index ee3f36de0bd..7b16b43f5f1 100644
--- a/packages/thirdweb/src/exports/x402.ts
+++ b/packages/thirdweb/src/exports/x402.ts
@@ -1,5 +1,13 @@
+export { decodePayment, encodePayment } from "../x402/encode.js";
export {
facilitator,
type ThirdwebX402FacilitatorConfig,
} from "../x402/facilitator.js";
export { wrapFetchWithPayment } from "../x402/fetchWithPayment.js";
+export { settlePayment } from "../x402/settle-payment.js";
+export type {
+ PaymentArgs,
+ SettlePaymentResult,
+ VerifyPaymentResult,
+} from "../x402/types.js";
+export { verifyPayment } from "../x402/verify-payment.js";
diff --git a/packages/thirdweb/src/gas/fee-data.ts b/packages/thirdweb/src/gas/fee-data.ts
index fa721426d9b..bd4aa003d8e 100644
--- a/packages/thirdweb/src/gas/fee-data.ts
+++ b/packages/thirdweb/src/gas/fee-data.ts
@@ -47,6 +47,7 @@ const FORCE_GAS_PRICE_CHAIN_IDS = [
2020, // Ronin Mainnet
2021, // Ronin Testnet (Saigon)
98866, // Plume mainnet
+ 1417429182, // Wilderworld Zephyr Testnet
];
/**
diff --git a/packages/thirdweb/src/react/core/design-system/index.ts b/packages/thirdweb/src/react/core/design-system/index.ts
index 023270dde16..64ca0076207 100644
--- a/packages/thirdweb/src/react/core/design-system/index.ts
+++ b/packages/thirdweb/src/react/core/design-system/index.ts
@@ -200,6 +200,7 @@ export const iconSize = {
"4xl": "128",
lg: "32",
md: "24",
+ "sm+": "20",
sm: "16",
xl: "48",
xs: "12",
diff --git a/packages/thirdweb/src/react/core/hooks/contract/useContractEvents.ts b/packages/thirdweb/src/react/core/hooks/contract/useContractEvents.ts
index 1045fc43cf6..fd7cb2e78c0 100644
--- a/packages/thirdweb/src/react/core/hooks/contract/useContractEvents.ts
+++ b/packages/thirdweb/src/react/core/hooks/contract/useContractEvents.ts
@@ -37,7 +37,7 @@ type UseContractEventsOptions<
*
* ### Using event extensions
*
- * The `thirdweb/extesions` export contains event definitions for many popular contracts.
+ * The `thirdweb/extensions` export contains event definitions for many popular contracts.
* You can use these event definitions to watch for specific events with a type-safe API.
*
* ```jsx
diff --git a/packages/thirdweb/src/react/core/hooks/contract/useWaitForReceipt.ts b/packages/thirdweb/src/react/core/hooks/contract/useWaitForReceipt.ts
index 085cb607a82..9339d6f60a8 100644
--- a/packages/thirdweb/src/react/core/hooks/contract/useWaitForReceipt.ts
+++ b/packages/thirdweb/src/react/core/hooks/contract/useWaitForReceipt.ts
@@ -32,7 +32,7 @@ export function useWaitForReceipt(
},
queryKey: [
"waitForReceipt",
- // TODO: here chain can be undfined so we go to a `-1` chain but this feels wrong
+ // TODO: here chain can be undefined so we go to a `-1` chain but this feels wrong
options?.chain.id || -1,
options?.transactionHash,
] as const,
diff --git a/packages/thirdweb/src/react/core/utils/defaultTokens.ts b/packages/thirdweb/src/react/core/utils/defaultTokens.ts
index ed92560b5ac..eaae86fb549 100644
--- a/packages/thirdweb/src/react/core/utils/defaultTokens.ts
+++ b/packages/thirdweb/src/react/core/utils/defaultTokens.ts
@@ -289,15 +289,9 @@ const DEFAULT_TOKENS = {
symbol: "USDC",
},
],
- "421613": [
+ "421614": [
{
- address: "0xe39Ab88f8A4777030A534146A9Ca3B52bd5D43A3",
- icon: wrappedEthIcon,
- name: "Wrapped Ether",
- symbol: "WETH",
- },
- {
- address: "0xfd064A18f3BF249cf1f87FC203E90D8f650f2d63",
+ address: "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d",
icon: usdcIcon,
name: "USD Coin",
symbol: "USDC",
diff --git a/packages/thirdweb/src/react/web/ui/Bridge/BridgeOrchestrator.tsx b/packages/thirdweb/src/react/web/ui/Bridge/BridgeOrchestrator.tsx
index 8371bd1ba6d..7b29dafca23 100644
--- a/packages/thirdweb/src/react/web/ui/Bridge/BridgeOrchestrator.tsx
+++ b/packages/thirdweb/src/react/web/ui/Bridge/BridgeOrchestrator.tsx
@@ -82,17 +82,17 @@ export interface BridgeOrchestratorProps {
/**
* Called when the flow is completed successfully
*/
- onComplete: () => void;
+ onComplete: (quote: BridgePrepareResult) => void;
/**
* Called when the flow encounters an error
*/
- onError: (error: Error) => void;
+ onError: (error: Error, quote: BridgePrepareResult | undefined) => void;
/**
* Called when the user cancels the flow
*/
- onCancel: () => void;
+ onCancel: (quote: BridgePrepareResult | undefined) => void;
/**
* Connect options for wallet connection
@@ -189,19 +189,22 @@ export function BridgeOrchestrator({
}, [send, uiOptions.mode]);
// Handle post-buy transaction completion
- const handlePostBuyTransactionComplete = useCallback(() => {
- onComplete?.();
- send({ type: "RESET" });
- }, [onComplete, send]);
+ const handlePostBuyTransactionComplete = useCallback(
+ (quote: BridgePrepareResult) => {
+ onComplete?.(quote);
+ send({ type: "RESET" });
+ },
+ [onComplete, send],
+ );
// Handle errors
const handleError = useCallback(
(error: Error) => {
console.error(error);
- onError?.(error);
+ onError?.(error, state.context.quote);
send({ error, type: "ERROR_OCCURRED" });
},
- [onError, send],
+ [onError, send, state.context.quote],
);
// Handle payment method selection
@@ -227,10 +230,13 @@ export function BridgeOrchestrator({
// Handle execution complete
const handleExecutionComplete = useCallback(
- (completedStatuses: CompletedStatusResult[]) => {
+ (
+ completedStatuses: CompletedStatusResult[],
+ quote: BridgePrepareResult,
+ ) => {
send({ completedStatuses, type: "EXECUTION_COMPLETE" });
if (uiOptions.mode !== "transaction") {
- onComplete?.();
+ onComplete?.(quote);
}
},
[send, onComplete, uiOptions.mode],
@@ -241,6 +247,8 @@ export function BridgeOrchestrator({
send({ type: "RETRY" });
}, [send]);
+ const quote = state.context.quote;
+
// Handle requirements resolved from FundWallet and DirectPayment
const handleRequirementsResolved = useCallback(
(amount: string, token: TokenWithPrices, receiverAddress: Address) => {
@@ -263,7 +271,7 @@ export function BridgeOrchestrator({
error={state.context.currentError}
onCancel={() => {
send({ type: "RESET" });
- onCancel?.();
+ onCancel?.(quote);
}}
onRetry={handleRetry}
/>
@@ -369,31 +377,33 @@ export function BridgeOrchestrator({
/>
)}
- {state.value === "execute" &&
- state.context.quote &&
- state.context.request && (
- {
- send({ type: "BACK" });
- }}
- onCancel={onCancel}
- onComplete={handleExecutionComplete}
- request={state.context.request}
- wallet={state.context.selectedPaymentMethod?.payerWallet}
- windowAdapter={webWindowAdapter}
- />
- )}
+ {state.value === "execute" && quote && state.context.request && (
+ {
+ send({ type: "BACK" });
+ }}
+ onCancel={() => {
+ onCancel(quote);
+ }}
+ onComplete={(completedStatuses) => {
+ handleExecutionComplete(completedStatuses, quote);
+ }}
+ request={state.context.request}
+ wallet={state.context.selectedPaymentMethod?.payerWallet}
+ windowAdapter={webWindowAdapter}
+ />
+ )}
{state.value === "success" &&
- state.context.quote &&
+ quote &&
state.context.completedStatuses && (
handlePostBuyTransactionComplete(quote)}
onTxSent={() => {
// Do nothing
}}
diff --git a/packages/thirdweb/src/react/web/ui/Bridge/BuyWidget.tsx b/packages/thirdweb/src/react/web/ui/Bridge/BuyWidget.tsx
index e7beb51f858..e0aa02f54f8 100644
--- a/packages/thirdweb/src/react/web/ui/Bridge/BuyWidget.tsx
+++ b/packages/thirdweb/src/react/web/ui/Bridge/BuyWidget.tsx
@@ -23,6 +23,7 @@ import { CustomThemeProvider } from "../../../core/design-system/CustomThemeProv
import type { Theme } from "../../../core/design-system/index.js";
import type { SiweAuthOptions } from "../../../core/hooks/auth/useSiweAuth.js";
import type { ConnectButton_connectModalOptions } from "../../../core/hooks/connection/ConnectButtonProps.js";
+import type { BridgePrepareResult } from "../../../core/hooks/useBridgePrepare.js";
import type { SupportedTokens } from "../../../core/utils/defaultTokens.js";
import { useConnectLocale } from "../ConnectWallet/locale/getConnectLocale.js";
import { EmbedContainer } from "../ConnectWallet/Modal/ConnectEmbed.js";
@@ -32,6 +33,11 @@ import type { LocaleId } from "../types.js";
import { BridgeOrchestrator, type UIOptions } from "./BridgeOrchestrator.js";
import { UnsupportedTokenScreen } from "./UnsupportedTokenScreen.js";
+type BuyOrOnrampPrepareResult = Extract<
+ BridgePrepareResult,
+ { type: "buy" | "onramp" }
+>;
+
export type BuyWidgetProps = {
/**
* Customize the supported tokens that users can pay with.
@@ -155,17 +161,17 @@ export type BuyWidgetProps = {
/**
* Callback triggered when the purchase is successful.
*/
- onSuccess?: () => void;
+ onSuccess?: (quote: BuyOrOnrampPrepareResult) => void;
/**
* Callback triggered when the purchase encounters an error.
*/
- onError?: (error: Error) => void;
+ onError?: (error: Error, quote: BuyOrOnrampPrepareResult | undefined) => void;
/**
* Callback triggered when the user cancels the purchase.
*/
- onCancel?: () => void;
+ onCancel?: (quote: BuyOrOnrampPrepareResult | undefined) => void;
/**
* @hidden
@@ -447,14 +453,23 @@ export function BuyWidget(props: BuyWidgetProps) {
client={props.client}
connectLocale={localeQuery.data}
connectOptions={props.connectOptions}
- onCancel={() => {
- props.onCancel?.();
+ onCancel={(quote) => {
+ // type guard
+ if (quote?.type === "buy" || quote?.type === "onramp") {
+ props.onCancel?.(quote);
+ }
}}
- onComplete={() => {
- props.onSuccess?.();
+ onComplete={(quote) => {
+ // type guard
+ if (quote?.type === "buy" || quote?.type === "onramp") {
+ props.onSuccess?.(quote);
+ }
}}
- onError={(err: Error) => {
- props.onError?.(err);
+ onError={(err: Error, quote) => {
+ // type guard
+ if (quote?.type === "buy" || quote?.type === "onramp") {
+ props.onError?.(err, quote);
+ }
}}
paymentLinkId={props.paymentLinkId}
paymentMethods={props.paymentMethods}
diff --git a/packages/thirdweb/src/react/web/ui/Bridge/StepRunner.tsx b/packages/thirdweb/src/react/web/ui/Bridge/StepRunner.tsx
index 0bc9057ab30..d2c289eaab1 100644
--- a/packages/thirdweb/src/react/web/ui/Bridge/StepRunner.tsx
+++ b/packages/thirdweb/src/react/web/ui/Bridge/StepRunner.tsx
@@ -376,9 +376,20 @@ export function StepRunner({
-
- Keep this window open until all
-
transactions are complete.
+
+ {error ? (
+ error.message || "An error occurred. Please try again."
+ ) : (
+ <>
+ Keep this window open until all
+
transactions are complete.
+ >
+ )}
diff --git a/packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SearchInput.tsx b/packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SearchInput.tsx
index 969fdade1b6..482f45ef6bf 100644
--- a/packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SearchInput.tsx
+++ b/packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SearchInput.tsx
@@ -36,6 +36,7 @@ export function SearchInput(props: {
variant="outline"
placeholder={props.placeholder}
value={props.value}
+ sm
style={{
paddingLeft: "44px",
}}
diff --git a/packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx b/packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx
index caaa4c036c9..226b83a339a 100644
--- a/packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx
+++ b/packages/thirdweb/src/react/web/ui/Bridge/swap-widget/SwapWidget.tsx
@@ -162,6 +162,10 @@ export type SwapWidgetProps = {
* @default true
*/
persistTokenSelections?: boolean;
+ /**
+ * Called when the user disconnects the active wallet
+ */
+ onDisconnect?: () => void;
};
/**
@@ -325,46 +329,11 @@ function SwapWidgetContent(props: SwapWidgetProps) {
});
const [buyToken, setBuyToken] = useState(() => {
- if (props.prefill?.buyToken) {
- return {
- tokenAddress:
- props.prefill.buyToken.tokenAddress ||
- getAddress(NATIVE_TOKEN_ADDRESS),
- chainId: props.prefill.buyToken.chainId,
- };
- }
-
- if (!isPersistEnabled) {
- return undefined;
- }
-
- const lastUsedBuyToken = getLastUsedTokens()?.buyToken;
-
- // the token that will be set as initial value of sell token
- const sellToken = getInitialSellToken(
- props.prefill,
- getLastUsedTokens()?.sellToken,
- );
-
- // if both tokens are same, ignore "buyToken", keep "sellToken"
- if (
- lastUsedBuyToken &&
- sellToken &&
- lastUsedBuyToken.tokenAddress.toLowerCase() ===
- sellToken.tokenAddress.toLowerCase() &&
- lastUsedBuyToken.chainId === sellToken.chainId
- ) {
- return undefined;
- }
-
- return lastUsedBuyToken;
+ return getInitialTokens(props.prefill, isPersistEnabled).buyToken;
});
const [sellToken, setSellToken] = useState(() => {
- return getInitialSellToken(
- props.prefill,
- isPersistEnabled ? getLastUsedTokens()?.sellToken : undefined,
- );
+ return getInitialTokens(props.prefill, isPersistEnabled).sellToken;
});
// persist selections to localStorage whenever they change
@@ -394,6 +363,7 @@ function SwapWidgetContent(props: SwapWidgetProps) {
if (screen.id === "1:swap-ui" || !activeWalletInfo) {
return (
1000) {
- return (
-
- {compactFormatter.format(Number(props.value))}
-
- );
- }
- const [integerPart, fractionPart] = props.value.split(".");
-
- return (
-
-
- {integerPart}
-
-
- .{fractionPart || "00"}
-
-
- );
-}
-
-const compactFormatter = new Intl.NumberFormat("en-US", {
- notation: "compact",
- maximumFractionDigits: 2,
-});
diff --git a/packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-chain.tsx b/packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-chain.tsx
index 270b5e8e927..04f0f8909a5 100644
--- a/packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-chain.tsx
+++ b/packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-chain.tsx
@@ -4,6 +4,7 @@ import type { ThirdwebClient } from "../../../../../client/client.js";
import {
fontSize,
iconSize,
+ radius,
spacing,
} from "../../../../core/design-system/index.js";
import { Container, Line, ModalHeader } from "../../components/basic.js";
@@ -21,6 +22,7 @@ type SelectBuyTokenProps = {
client: ThirdwebClient;
onSelectChain: (chain: BridgeChain) => void;
selectedChain: BridgeChain | undefined;
+ isMobile: boolean;
};
/**
@@ -56,11 +58,15 @@ export function SelectBridgeChainUI(
});
return (
-
-
-
-
-
+
+ {props.isMobile && (
+ <>
+
+
+
+
+ >
+ )}
@@ -79,10 +85,12 @@ export function SelectBridgeChainUI(
props.onSelectChain(chain)}
isSelected={chain.chainId === props.selectedChain?.chainId}
+ isMobile={props.isMobile}
/>
))}
@@ -119,7 +128,7 @@ export function SelectBridgeChainUI(
)}
-
+
);
}
@@ -144,6 +153,7 @@ function ChainButton(props: {
client: ThirdwebClient;
onClick: () => void;
isSelected: boolean;
+ isMobile: boolean;
}) {
return (
diff --git a/packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx b/packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx
index a96362495c8..e943d9af534 100644
--- a/packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx
+++ b/packages/thirdweb/src/react/web/ui/Bridge/swap-widget/select-token-ui.tsx
@@ -1,9 +1,9 @@
-import { DiscIcon } from "@radix-ui/react-icons";
import { useMemo, useState } from "react";
import type { Token } from "../../../../../bridge/index.js";
import type { BridgeChain } from "../../../../../bridge/types/Chain.js";
import type { ThirdwebClient } from "../../../../../client/client.js";
import { toTokens } from "../../../../../utils/units.js";
+import { useCustomTheme } from "../../../../core/design-system/CustomThemeProvider.js";
import {
fontSize,
iconSize,
@@ -12,15 +12,15 @@ import {
} from "../../../../core/design-system/index.js";
import { CoinsIcon } from "../../ConnectWallet/icons/CoinsIcon.js";
import { WalletDotIcon } from "../../ConnectWallet/icons/WalletDotIcon.js";
-import { formatTokenAmount } from "../../ConnectWallet/screens/formatTokenBalance.js";
-import { Container, Line, ModalHeader } from "../../components/basic.js";
+import { Container, noScrollBar } from "../../components/basic.js";
import { Button } from "../../components/buttons.js";
import { Img } from "../../components/Img.js";
import { Skeleton } from "../../components/Skeleton.js";
import { Spacer } from "../../components/Spacer.js";
import { Spinner } from "../../components/Spinner.js";
import { Text } from "../../components/text.js";
-import { DecimalRenderer } from "./common.js";
+import { StyledDiv } from "../../design-system/elements.js";
+import { useIsMobile } from "../../hooks/useisMobile.js";
import { SearchInput } from "./SearchInput.js";
import { SelectChainButton } from "./SelectChainButton.js";
import { SelectBridgeChain } from "./select-chain.js";
@@ -31,6 +31,7 @@ import {
useTokenBalances,
useTokens,
} from "./use-tokens.js";
+import { tokenAmountFormatter } from "./utils.js";
/**
* @internal
@@ -138,6 +139,7 @@ function SelectTokenUI(
showMore: (() => void) | undefined;
},
) {
+ const isMobile = useIsMobile();
const [screen, setScreen] = useState<"select-chain" | "select-token">(
"select-token",
);
@@ -180,184 +182,70 @@ function SelectTokenUI(
});
}, [otherTokens]);
- const noTokensFound =
- !props.isFetching &&
- sortedOtherTokens.length === 0 &&
- props.ownedTokens.length === 0;
-
- if (screen === "select-token") {
+ if (!isMobile) {
return (
-