diff --git a/.changeset/tired-rice-kiss.md b/.changeset/tired-rice-kiss.md
new file mode 100644
index 00000000000..f37c5ea9410
--- /dev/null
+++ b/.changeset/tired-rice-kiss.md
@@ -0,0 +1,5 @@
+---
+"thirdweb": patch
+---
+
+Payment link support in PayEmbed
diff --git a/apps/dashboard/src/@/api/universal-bridge/links.ts b/apps/dashboard/src/@/api/universal-bridge/links.ts
new file mode 100644
index 00000000000..634bf5df6b6
--- /dev/null
+++ b/apps/dashboard/src/@/api/universal-bridge/links.ts
@@ -0,0 +1,45 @@
+import "server-only";
+
+import { DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/server-envs";
+import { UB_BASE_URL } from "./constants";
+
+type PaymentLink = {
+ clientId: string;
+ label?: string;
+ receiver: string;
+ destinationToken: {
+ address: string;
+ symbol: string;
+ decimals: number;
+ chainId: number;
+ };
+ amount: bigint | undefined;
+ purchaseData: unknown;
+};
+
+export async function getPaymentLink(props: {
+ paymentId: string;
+}) {
+ const res = await fetch(`${UB_BASE_URL}/v1/links/${props.paymentId}`, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ "x-secret-key": DASHBOARD_THIRDWEB_SECRET_KEY,
+ },
+ });
+
+ if (!res.ok) {
+ const text = await res.text();
+ throw new Error(text);
+ }
+
+ const { data } = await res.json();
+ return {
+ clientId: data.clientId,
+ label: data.label,
+ receiver: data.receiver,
+ destinationToken: data.destinationToken,
+ amount: data.amount ? BigInt(data.amount) : undefined,
+ purchaseData: data.purchaseData,
+ } as PaymentLink;
+}
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/_components/TotalSponsoredCard.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/_components/TotalSponsoredCard.tsx
index 3e97642d0bf..a93280a2066 100644
--- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/_components/TotalSponsoredCard.tsx
+++ b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/_components/TotalSponsoredCard.tsx
@@ -118,9 +118,9 @@ export async function TotalSponsoredChartCardUI({
className={className}
// Get the trend from the last two COMPLETE periods
trendFn={(data, key) =>
- data.filter((d) => (d[key] as number) > 0).length >= 3
+ data.filter((d) => (d[key] as number) > 0).length >= 2
? ((data[data.length - 2]?.[key] as number) ?? 0) /
- ((data[data.length - 3]?.[key] as number) ?? 0) -
+ ((data[0]?.[key] as number) ?? 0) -
1
: undefined
}
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/_components/TransactionsCard.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/_components/TransactionsCard.tsx
index 9a8891f89c8..9eccf19ba6e 100644
--- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/_components/TransactionsCard.tsx
+++ b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/_components/TransactionsCard.tsx
@@ -119,9 +119,9 @@ export async function TransactionsChartCardUI({
className={className}
// Get the trend from the last two COMPLETE periods
trendFn={(data, key) =>
- data.filter((d) => (d[key] as number) > 0).length >= 3
+ data.filter((d) => (d[key] as number) > 0).length >= 2
? ((data[data.length - 2]?.[key] as number) ?? 0) /
- ((data[data.length - 3]?.[key] as number) ?? 0) -
+ ((data[0]?.[key] as number) ?? 0) -
1
: undefined
}
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/analytics/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/analytics/page.tsx
index c655b14eef1..da1521eb5df 100644
--- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/analytics/page.tsx
+++ b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/analytics/page.tsx
@@ -320,14 +320,16 @@ function AppHighlightsCard({
"totalVolume"
}
data={timeSeriesData}
- aggregateFn={(_data, key) =>
- timeSeriesData.reduce((acc, curr) => acc + curr[key], 0)
- }
- // Get the trend from the last two COMPLETE periods
+ aggregateFn={(_data, key) => {
+ if (key === "activeUsers") {
+ return Math.max(...timeSeriesData.map((d) => d[key]));
+ }
+ return timeSeriesData.reduce((acc, curr) => acc + curr[key], 0);
+ }}
trendFn={(data, key) =>
- data.filter((d) => (d[key] as number) > 0).length >= 3
+ data.filter((d) => (d[key] as number) > 0).length >= 2
? ((data[data.length - 2]?.[key] as number) ?? 0) /
- ((data[data.length - 3]?.[key] as number) ?? 0) -
+ ((data[0]?.[key] as number) ?? 0) -
1
: undefined
}
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/page.tsx
index ec128cca392..08c32654dbe 100644
--- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/page.tsx
+++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/page.tsx
@@ -376,17 +376,16 @@ function AppHighlightsCard({
activeChart={chartKey}
queryKey="appHighlights"
data={timeSeriesData}
- aggregateFn={(_data, key) =>
- // If there is only one data point, use that one, otherwise use the previous
- timeSeriesData.filter((d) => (d[key] as number) > 0).length >= 2
- ? timeSeriesData[timeSeriesData.length - 2]?.[key]
- : timeSeriesData[timeSeriesData.length - 1]?.[key]
- }
- // Get the trend from the last two COMPLETE periods
+ aggregateFn={(_data, key) => {
+ if (key === "activeUsers") {
+ return Math.max(...timeSeriesData.map((d) => d[key]));
+ }
+ return timeSeriesData.reduce((acc, curr) => acc + curr[key], 0);
+ }}
trendFn={(data, key) =>
- data.filter((d) => (d[key] as number) > 0).length >= 3
+ data.filter((d) => (d[key] as number) > 0).length >= 2
? ((data[data.length - 2]?.[key] as number) ?? 0) /
- ((data[data.length - 3]?.[key] as number) ?? 0) -
+ ((data[0]?.[key] as number) ?? 0) -
1
: undefined
}
diff --git a/apps/dashboard/src/app/nebula-app/(app)/utils/nebulaThirdwebClient.ts b/apps/dashboard/src/app/nebula-app/(app)/utils/nebulaThirdwebClient.ts
index 66f4ddd4e67..431a05f0636 100644
--- a/apps/dashboard/src/app/nebula-app/(app)/utils/nebulaThirdwebClient.ts
+++ b/apps/dashboard/src/app/nebula-app/(app)/utils/nebulaThirdwebClient.ts
@@ -3,6 +3,7 @@ import {
NEXT_PUBLIC_NEBULA_APP_CLIENT_ID,
} from "@/constants/public-envs";
import {
+ THIRDWEB_BRIDGE_URL,
THIRDWEB_BUNDLER_DOMAIN,
THIRDWEB_INAPP_WALLET_DOMAIN,
THIRDWEB_INSIGHT_API_DOMAIN,
@@ -27,6 +28,7 @@ function getNebulaThirdwebClient() {
social: THIRDWEB_SOCIAL_API_DOMAIN,
bundler: THIRDWEB_BUNDLER_DOMAIN,
insight: THIRDWEB_INSIGHT_API_DOMAIN,
+ bridge: THIRDWEB_BRIDGE_URL,
});
}
diff --git a/apps/dashboard/src/app/pay/[id]/page.tsx b/apps/dashboard/src/app/pay/[id]/page.tsx
new file mode 100644
index 00000000000..bfee49a94ec
--- /dev/null
+++ b/apps/dashboard/src/app/pay/[id]/page.tsx
@@ -0,0 +1,69 @@
+import { getPaymentLink } from "@/api/universal-bridge/links";
+import type { Metadata } from "next";
+import { defineChain, getContract } from "thirdweb";
+import { getCurrencyMetadata } from "thirdweb/extensions/erc20";
+import { checksumAddress } from "thirdweb/utils";
+import { getClientThirdwebClient } from "../../../@/constants/thirdweb-client.client";
+import { PayPageEmbed } from "../components/client/PayPageEmbed.client";
+
+const title = "thirdweb Pay";
+const description = "Fast, secure, and simple payments.";
+
+export const metadata: Metadata = {
+ title,
+ description,
+ openGraph: {
+ title,
+ description,
+ },
+};
+
+export default async function PayPage({
+ params,
+ searchParams,
+}: {
+ params: Promise<{ id: string }>;
+ searchParams: Promise<{ redirectUri?: string; theme?: "light" | "dark" }>;
+}) {
+ const { id } = await params;
+ const { redirectUri, theme } = await searchParams;
+
+ const paymentLink = await getPaymentLink({
+ paymentId: id,
+ });
+
+ const tokenContract = getContract({
+ client: getClientThirdwebClient(undefined), // for this RPC call, use the dashboard client
+ // eslint-disable-next-line no-restricted-syntax
+ chain: defineChain(Number(paymentLink.destinationToken.chainId)),
+ address: paymentLink.destinationToken.address,
+ });
+ const {
+ symbol,
+ decimals,
+ name: tokenName,
+ } = await getCurrencyMetadata({
+ contract: tokenContract,
+ });
+ const token = {
+ symbol,
+ decimals,
+ name: tokenName,
+ address: checksumAddress(paymentLink.destinationToken.address),
+ chainId: Number(paymentLink.destinationToken.chainId),
+ };
+
+ return (
+
+ );
+}
diff --git a/apps/dashboard/src/app/pay/components/client/PayPageEmbed.client.tsx b/apps/dashboard/src/app/pay/components/client/PayPageEmbed.client.tsx
index e7a96dbaa4f..ac565320564 100644
--- a/apps/dashboard/src/app/pay/components/client/PayPageEmbed.client.tsx
+++ b/apps/dashboard/src/app/pay/components/client/PayPageEmbed.client.tsx
@@ -1,36 +1,26 @@
"use client";
-import {
- THIRDWEB_ANALYTICS_DOMAIN,
- THIRDWEB_BUNDLER_DOMAIN,
- THIRDWEB_INAPP_WALLET_DOMAIN,
- THIRDWEB_INSIGHT_API_DOMAIN,
- THIRDWEB_PAY_DOMAIN,
- THIRDWEB_RPC_DOMAIN,
- THIRDWEB_SOCIAL_API_DOMAIN,
- THIRDWEB_STORAGE_DOMAIN,
-} from "constants/urls";
+import { payAppThirdwebClient } from "app/pay/constants";
import { useV5DashboardChain } from "lib/v5-adapter";
-import { getVercelEnv } from "lib/vercel-utils";
import { useTheme } from "next-themes";
-import { useEffect, useMemo } from "react";
-import { NATIVE_TOKEN_ADDRESS, createThirdwebClient, toTokens } from "thirdweb";
+import { useEffect } from "react";
+import { NATIVE_TOKEN_ADDRESS, toTokens } from "thirdweb";
import { AutoConnect, PayEmbed } from "thirdweb/react";
-import { setThirdwebDomains } from "thirdweb/utils";
export function PayPageEmbed({
chainId,
recipientAddress,
+ paymentLinkId,
amount,
token,
name,
image,
redirectUri,
- clientId,
theme,
}: {
chainId: number;
recipientAddress: string;
- amount: bigint;
+ paymentLinkId?: string;
+ amount?: bigint;
token: { name: string; symbol: string; address: string; decimals: number };
name?: string;
image?: string;
@@ -46,30 +36,15 @@ export function PayPageEmbed({
setTheme(theme);
}
}, [theme, setTheme]);
-
- const client = useMemo(() => {
- if (getVercelEnv() !== "production") {
- setThirdwebDomains({
- rpc: THIRDWEB_RPC_DOMAIN,
- pay: THIRDWEB_PAY_DOMAIN,
- storage: THIRDWEB_STORAGE_DOMAIN,
- insight: THIRDWEB_INSIGHT_API_DOMAIN,
- analytics: THIRDWEB_ANALYTICS_DOMAIN,
- inAppWallet: THIRDWEB_INAPP_WALLET_DOMAIN,
- bundler: THIRDWEB_BUNDLER_DOMAIN,
- social: THIRDWEB_SOCIAL_API_DOMAIN,
- });
- }
- return createThirdwebClient({ clientId });
- }, [clientId]);
const chain = useV5DashboardChain(chainId);
return (
<>
-
+
{
diff --git a/apps/dashboard/src/app/pay/components/client/PaymentLinkForm.client.tsx b/apps/dashboard/src/app/pay/components/client/PaymentLinkForm.client.tsx
index 2fc705d54cd..201d1bf1d86 100644
--- a/apps/dashboard/src/app/pay/components/client/PaymentLinkForm.client.tsx
+++ b/apps/dashboard/src/app/pay/components/client/PaymentLinkForm.client.tsx
@@ -7,8 +7,8 @@ import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
-import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
import { cn } from "@/lib/utils";
+import { payAppThirdwebClient } from "app/pay/constants";
import { ChevronDownIcon, CreditCardIcon } from "lucide-react";
import { useCallback, useMemo, useState } from "react";
import { toast } from "sonner";
@@ -21,20 +21,8 @@ import {
} from "thirdweb";
import { getCurrencyMetadata } from "thirdweb/extensions/erc20";
import { resolveScheme, upload } from "thirdweb/storage";
-import { setThirdwebDomains } from "thirdweb/utils";
import { FileInput } from "../../../../components/shared/FileInput";
-import {
- THIRDWEB_ANALYTICS_DOMAIN,
- THIRDWEB_BUNDLER_DOMAIN,
- THIRDWEB_INAPP_WALLET_DOMAIN,
- THIRDWEB_INSIGHT_API_DOMAIN,
- THIRDWEB_PAY_DOMAIN,
- THIRDWEB_RPC_DOMAIN,
- THIRDWEB_SOCIAL_API_DOMAIN,
- THIRDWEB_STORAGE_DOMAIN,
-} from "../../../../constants/urls";
import { resolveEns } from "../../../../lib/ens";
-import { getVercelEnv } from "../../../../lib/vercel-utils";
export function PaymentLinkForm() {
const [chainId, setChainId] = useState();
@@ -49,22 +37,6 @@ export function PaymentLinkForm() {
const [showAdvanced, setShowAdvanced] = useState(false);
const [paymentUrl, setPaymentUrl] = useState("");
- const client = useMemo(() => {
- if (getVercelEnv() !== "production") {
- setThirdwebDomains({
- rpc: THIRDWEB_RPC_DOMAIN,
- pay: THIRDWEB_PAY_DOMAIN,
- storage: THIRDWEB_STORAGE_DOMAIN,
- insight: THIRDWEB_INSIGHT_API_DOMAIN,
- analytics: THIRDWEB_ANALYTICS_DOMAIN,
- inAppWallet: THIRDWEB_INAPP_WALLET_DOMAIN,
- bundler: THIRDWEB_BUNDLER_DOMAIN,
- social: THIRDWEB_SOCIAL_API_DOMAIN,
- });
- }
- return getClientThirdwebClient();
- }, []);
-
const isFormComplete = useMemo(() => {
return chainId && recipientAddress && tokenAddressWithChain && amount;
}, [chainId, recipientAddress, tokenAddressWithChain, amount]);
@@ -115,7 +87,7 @@ export function PaymentLinkForm() {
}
const inputs = await parseInputs(
- client,
+ payAppThirdwebClient,
chainId,
tokenAddressWithChain,
recipientAddress,
@@ -128,7 +100,7 @@ export function PaymentLinkForm() {
recipientAddress: inputs.recipientAddress,
tokenAddress: inputs.tokenAddress,
amount: inputs.amount.toString(),
- clientId: client.clientId,
+ clientId: payAppThirdwebClient.clientId,
});
// Add title as name parameter if provided
@@ -150,15 +122,7 @@ export function PaymentLinkForm() {
setIsLoading(false);
}
},
- [
- amount,
- chainId,
- client,
- imageUri,
- recipientAddress,
- title,
- tokenAddressWithChain,
- ],
+ [amount, chainId, imageUri, recipientAddress, title, tokenAddressWithChain],
);
const handlePreview = useCallback(async () => {
@@ -169,7 +133,7 @@ export function PaymentLinkForm() {
try {
const inputs = await parseInputs(
- client,
+ payAppThirdwebClient,
chainId,
tokenAddressWithChain,
recipientAddress,
@@ -181,7 +145,7 @@ export function PaymentLinkForm() {
recipientAddress: inputs.recipientAddress,
tokenAddress: inputs.tokenAddress,
amount: inputs.amount.toString(),
- clientId: client.clientId,
+ clientId: payAppThirdwebClient.clientId,
});
// Add title as name parameter if provided
@@ -201,7 +165,6 @@ export function PaymentLinkForm() {
}, [
amount,
chainId,
- client,
imageUri,
recipientAddress,
title,
@@ -230,7 +193,7 @@ export function PaymentLinkForm() {
chainId={chainId}
onChange={setChainId}
disableTestnets
- client={client}
+ client={payAppThirdwebClient}
className="w-full"
/>
@@ -244,7 +207,7 @@ export function PaymentLinkForm() {
chainId={chainId ?? undefined}
onChange={setTokenAddressWithChain}
className="w-full"
- client={client}
+ client={payAppThirdwebClient}
disabled={!chainId}
enabled={!!chainId}
/>
diff --git a/apps/dashboard/src/app/pay/constants.ts b/apps/dashboard/src/app/pay/constants.ts
new file mode 100644
index 00000000000..eaac2b835d3
--- /dev/null
+++ b/apps/dashboard/src/app/pay/constants.ts
@@ -0,0 +1,45 @@
+import {
+ NET_PUBLIC_DASHBOARD_THIRDWEB_CLIENT_ID,
+ NEXT_PUBLIC_IPFS_GATEWAY_URL,
+} from "@/constants/public-envs";
+import {
+ THIRDWEB_BRIDGE_URL,
+ THIRDWEB_BUNDLER_DOMAIN,
+ THIRDWEB_INAPP_WALLET_DOMAIN,
+ THIRDWEB_INSIGHT_API_DOMAIN,
+ THIRDWEB_PAY_DOMAIN,
+ THIRDWEB_RPC_DOMAIN,
+ THIRDWEB_SOCIAL_API_DOMAIN,
+ THIRDWEB_STORAGE_DOMAIN,
+} from "constants/urls";
+import { getVercelEnv } from "lib/vercel-utils";
+import { createThirdwebClient } from "thirdweb";
+import { setThirdwebDomains } from "thirdweb/utils";
+
+function getPayThirdwebClient() {
+ if (getVercelEnv() !== "production") {
+ // if not on production: run this when creating a client to set the domains
+ setThirdwebDomains({
+ rpc: THIRDWEB_RPC_DOMAIN,
+ inAppWallet: THIRDWEB_INAPP_WALLET_DOMAIN,
+ pay: THIRDWEB_PAY_DOMAIN,
+ storage: THIRDWEB_STORAGE_DOMAIN,
+ social: THIRDWEB_SOCIAL_API_DOMAIN,
+ bundler: THIRDWEB_BUNDLER_DOMAIN,
+ insight: THIRDWEB_INSIGHT_API_DOMAIN,
+ bridge: THIRDWEB_BRIDGE_URL,
+ });
+ }
+
+ return createThirdwebClient({
+ secretKey: undefined,
+ clientId: NET_PUBLIC_DASHBOARD_THIRDWEB_CLIENT_ID,
+ config: {
+ storage: {
+ gatewayUrl: NEXT_PUBLIC_IPFS_GATEWAY_URL,
+ },
+ },
+ });
+}
+
+export const payAppThirdwebClient = getPayThirdwebClient();
diff --git a/apps/dashboard/src/app/pay/page.tsx b/apps/dashboard/src/app/pay/page.tsx
index 7424055b731..03169ea5489 100644
--- a/apps/dashboard/src/app/pay/page.tsx
+++ b/apps/dashboard/src/app/pay/page.tsx
@@ -2,10 +2,10 @@ import type { Metadata } from "next";
import { createThirdwebClient, defineChain, getContract } from "thirdweb";
import { getCurrencyMetadata } from "thirdweb/extensions/erc20";
import { checksumAddress } from "thirdweb/utils";
-import { getClientThirdwebClient } from "../../@/constants/thirdweb-client.client";
import { PayPageEmbed } from "./components/client/PayPageEmbed.client";
import { PaymentLinkForm } from "./components/client/PaymentLinkForm.client";
import type { PayParams } from "./components/types";
+import { payAppThirdwebClient } from "./constants";
const title = "thirdweb Pay";
const description = "Fast, secure, and simple payments.";
@@ -19,7 +19,7 @@ export const metadata: Metadata = {
},
};
-export default async function RoutesPage({
+export default async function PayPage({
searchParams,
}: { searchParams: Promise }) {
const params = await searchParams;
@@ -58,10 +58,10 @@ export default async function RoutesPage({
const client =
params.clientId && !Array.isArray(params.clientId)
? createThirdwebClient({ clientId: params.clientId })
- : getClientThirdwebClient(undefined);
+ : payAppThirdwebClient;
const tokenContract = getContract({
- client: getClientThirdwebClient(undefined), // for this RPC call, use the dashboard client
+ client: payAppThirdwebClient,
// eslint-disable-next-line no-restricted-syntax
chain: defineChain(Number(params.chainId)),
address: params.tokenAddress,
diff --git a/apps/dashboard/src/constants/urls.ts b/apps/dashboard/src/constants/urls.ts
index 33bec3ea48d..de45290961c 100644
--- a/apps/dashboard/src/constants/urls.ts
+++ b/apps/dashboard/src/constants/urls.ts
@@ -24,8 +24,5 @@ export const THIRDWEB_BUNDLER_DOMAIN =
export const THIRDWEB_INSIGHT_API_DOMAIN =
process.env.NEXT_PUBLIC_INSIGHT_API_URL || "insight.thirdweb-dev.com";
-export const THIRDWEB_ANALYTICS_DOMAIN =
- process.env.NEXT_PUBLIC_ANALYTICS_URL || "c.thirdweb-dev.com";
-
export const THIRDWEB_BRIDGE_URL =
process.env.NEXT_PUBLIC_BRIDGE_URL || "bridge.thirdweb-dev.com";
diff --git a/packages/thirdweb/src/bridge/Buy.ts b/packages/thirdweb/src/bridge/Buy.ts
index b4e2ffbc5f7..da7b0c571fe 100644
--- a/packages/thirdweb/src/bridge/Buy.ts
+++ b/packages/thirdweb/src/bridge/Buy.ts
@@ -332,6 +332,7 @@ export async function prepare(
amount,
purchaseData,
maxSteps,
+ paymentLinkId,
} = options;
const clientFetch = getClientFetch(client);
@@ -353,6 +354,7 @@ export async function prepare(
receiver,
purchaseData,
maxSteps,
+ paymentLinkId,
}),
});
if (!response.ok) {
@@ -402,6 +404,10 @@ export declare namespace prepare {
client: ThirdwebClient;
purchaseData?: unknown;
maxSteps?: number;
+ /**
+ * @hidden
+ */
+ paymentLinkId?: string;
};
type Result = PreparedQuote & {
diff --git a/packages/thirdweb/src/bridge/Onramp.ts b/packages/thirdweb/src/bridge/Onramp.ts
index 8add6929c72..b3b20dbf17d 100644
--- a/packages/thirdweb/src/bridge/Onramp.ts
+++ b/packages/thirdweb/src/bridge/Onramp.ts
@@ -51,6 +51,7 @@ interface OnrampApiRequestBody {
currency?: string;
maxSteps?: number;
excludeChainIds?: string;
+ paymentLinkId?: string;
}
/**
@@ -142,6 +143,7 @@ export async function prepare(
currency,
maxSteps,
excludeChainIds,
+ paymentLinkId,
} = options;
const clientFetch = getClientFetch(client);
@@ -180,6 +182,9 @@ export async function prepare(
? excludeChainIds.join(",")
: excludeChainIds;
}
+ if (paymentLinkId !== undefined) {
+ apiRequestBody.paymentLinkId = paymentLinkId;
+ }
const response = await clientFetch(url, {
method: "POST",
@@ -238,6 +243,10 @@ export declare namespace prepare {
currency?: string;
maxSteps?: number;
excludeChainIds?: string | string[];
+ /**
+ * @hidden
+ */
+ paymentLinkId?: string;
};
export type Result = OnrampPrepareQuoteResponseData;
diff --git a/packages/thirdweb/src/bridge/Sell.ts b/packages/thirdweb/src/bridge/Sell.ts
index 4ab038f0379..af4775aceb6 100644
--- a/packages/thirdweb/src/bridge/Sell.ts
+++ b/packages/thirdweb/src/bridge/Sell.ts
@@ -323,6 +323,7 @@ export async function prepare(
client,
purchaseData,
maxSteps,
+ paymentLinkId,
} = options;
const clientFetch = getClientFetch(client);
@@ -344,6 +345,7 @@ export async function prepare(
receiver,
purchaseData,
maxSteps,
+ paymentLinkId,
}),
});
if (!response.ok) {
@@ -395,6 +397,10 @@ export declare namespace prepare {
client: ThirdwebClient;
purchaseData?: unknown;
maxSteps?: number;
+ /**
+ * @hidden
+ */
+ paymentLinkId?: string;
};
type Result = PreparedQuote & {
diff --git a/packages/thirdweb/src/bridge/Transfer.ts b/packages/thirdweb/src/bridge/Transfer.ts
index 6ffb4fbe14c..06a6e402cf3 100644
--- a/packages/thirdweb/src/bridge/Transfer.ts
+++ b/packages/thirdweb/src/bridge/Transfer.ts
@@ -189,6 +189,7 @@ export async function prepare(
amount,
purchaseData,
feePayer,
+ paymentLinkId,
} = options;
const clientFetch = getClientFetch(client);
@@ -208,6 +209,7 @@ export async function prepare(
receiver,
purchaseData,
feePayer,
+ paymentLinkId,
}),
});
if (!response.ok) {
@@ -254,6 +256,10 @@ export declare namespace prepare {
client: ThirdwebClient;
purchaseData?: unknown;
feePayer?: "sender" | "receiver";
+ /**
+ * @hidden
+ */
+ paymentLinkId?: string;
};
type Result = PreparedQuote & {
diff --git a/packages/thirdweb/src/pay/buyWithCrypto/getQuote.ts b/packages/thirdweb/src/pay/buyWithCrypto/getQuote.ts
index 6945f0d30f8..42f13a4436b 100644
--- a/packages/thirdweb/src/pay/buyWithCrypto/getQuote.ts
+++ b/packages/thirdweb/src/pay/buyWithCrypto/getQuote.ts
@@ -83,6 +83,11 @@ export type GetBuyWithCryptoQuoteParams = {
* For example, if you want to allow a maximum slippage of 0.5%, you should specify `50` bps.
*/
maxSlippageBPS?: number;
+
+ /**
+ * @hidden
+ */
+ paymentLinkId?: string;
} & (
| {
/**
@@ -203,6 +208,7 @@ export async function getBuyWithCryptoQuote(
amount: amount,
purchaseData: params.purchaseData,
client: params.client,
+ paymentLinkId: params.paymentLinkId,
});
} else if (params.fromAmount) {
const originTokenContract = getContract({
@@ -224,6 +230,7 @@ export async function getBuyWithCryptoQuote(
amount: amount,
purchaseData: params.purchaseData,
client: params.client,
+ paymentLinkId: params.paymentLinkId,
});
}
throw new Error(
diff --git a/packages/thirdweb/src/pay/buyWithCrypto/getTransfer.ts b/packages/thirdweb/src/pay/buyWithCrypto/getTransfer.ts
index b2ed4c7d367..c4b6d0b8d68 100644
--- a/packages/thirdweb/src/pay/buyWithCrypto/getTransfer.ts
+++ b/packages/thirdweb/src/pay/buyWithCrypto/getTransfer.ts
@@ -60,6 +60,11 @@ export type GetBuyWithCryptoTransferParams = {
* For direct transfers, specify who will pay for the transfer fee. Can be "sender" or "receiver".
*/
feePayer?: "sender" | "receiver";
+
+ /**
+ * @hidden
+ */
+ paymentLinkId?: string;
};
/**
@@ -127,6 +132,7 @@ export async function getBuyWithCryptoTransfer(
receiver: params.toAddress,
client: params.client,
feePayer: params.feePayer,
+ paymentLinkId: params.paymentLinkId,
});
const firstStep = quote.steps[0];
diff --git a/packages/thirdweb/src/pay/buyWithFiat/getPostOnRampQuote.ts b/packages/thirdweb/src/pay/buyWithFiat/getPostOnRampQuote.ts
index e40a905285e..bce67e9e287 100644
--- a/packages/thirdweb/src/pay/buyWithFiat/getPostOnRampQuote.ts
+++ b/packages/thirdweb/src/pay/buyWithFiat/getPostOnRampQuote.ts
@@ -21,6 +21,11 @@ export type GetPostOnRampQuoteParams = {
* The "Buy with fiat" transaction status object returned by [`getBuyWithFiatStatus`](https://portal.thirdweb.com/typescript/v5/getBuyWithFiatStatus) function
*/
buyWithFiatStatus: BuyWithFiatStatus;
+
+ /**
+ * @hidden
+ */
+ paymentLinkId?: string;
};
/**
@@ -61,6 +66,7 @@ export type GetPostOnRampQuoteParams = {
export async function getPostOnRampQuote({
client,
buyWithFiatStatus,
+ paymentLinkId,
}: GetPostOnRampQuoteParams): Promise {
if (buyWithFiatStatus.status === "NOT_FOUND") {
throw new Error("Invalid buyWithFiatStatus");
@@ -77,5 +83,6 @@ export async function getPostOnRampQuote({
toChainId: buyWithFiatStatus.quote.toToken.chainId,
toTokenAddress: buyWithFiatStatus.quote.toToken.tokenAddress,
toAmount: buyWithFiatStatus.quote.estimatedToTokenAmount,
+ paymentLinkId: paymentLinkId,
});
}
diff --git a/packages/thirdweb/src/pay/buyWithFiat/getQuote.ts b/packages/thirdweb/src/pay/buyWithFiat/getQuote.ts
index 0c070cd8452..4639eb3076b 100644
--- a/packages/thirdweb/src/pay/buyWithFiat/getQuote.ts
+++ b/packages/thirdweb/src/pay/buyWithFiat/getQuote.ts
@@ -95,6 +95,11 @@ export type GetBuyWithFiatQuoteParams = {
* By default, we choose a recommended provider based on the location of the user, KYC status, and currency.
*/
preferredProvider?: FiatProvider;
+
+ /**
+ * @hidden
+ */
+ paymentLinkId?: string;
};
/**
@@ -321,6 +326,7 @@ export async function getBuyWithFiatQuote(
currency: params.fromCurrencySymbol,
maxSteps: 2,
onrampTokenAddress: NATIVE_TOKEN_ADDRESS, // force onramp to native token to avoid missing gas issues
+ paymentLinkId: params.paymentLinkId,
});
// Determine tokens based on steps rules
diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.tsx
index 178e0da83ce..61567c33c3b 100644
--- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.tsx
+++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.tsx
@@ -83,6 +83,7 @@ export type BuyScreenProps = {
connectOptions: PayEmbedConnectOptions | undefined;
hiddenWallets?: WalletId[];
isEmbed: boolean;
+ paymentLinkId?: string;
};
/**
@@ -151,6 +152,7 @@ type BuyScreenContentProps = {
hiddenWallets?: WalletId[];
connectOptions: PayEmbedConnectOptions | undefined;
isEmbed: boolean;
+ paymentLinkId?: string;
};
/**
@@ -347,6 +349,7 @@ function BuyScreenContent(props: BuyScreenContentProps) {
{
setScreen({
@@ -395,6 +398,7 @@ function BuyScreenContent(props: BuyScreenContentProps) {
});
}}
onSuccess={onSwapSuccess}
+ paymentLinkId={props.paymentLinkId}
/>
);
}
@@ -530,6 +534,7 @@ function BuyScreenContent(props: BuyScreenContentProps) {
{screen.id === "buy-with-crypto" && activeAccount && (
)}
diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/FiatScreenContent.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/FiatScreenContent.tsx
index 5dc2c4804f9..ff230e0c38e 100644
--- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/FiatScreenContent.tsx
+++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/FiatScreenContent.tsx
@@ -50,6 +50,7 @@ export function FiatScreenContent(props: {
payer: PayerInfo;
setTokenAmount: (amount: string) => void;
setHasEditedAmount: (hasEdited: boolean) => void;
+ paymentLinkId: undefined | string;
}) {
const {
toToken,
@@ -60,6 +61,7 @@ export function FiatScreenContent(props: {
toChain,
showCurrencySelector,
selectedCurrency,
+ paymentLinkId,
} = props;
const defaultRecipientAddress = (
props.payOptions as Extract
@@ -99,6 +101,7 @@ export function FiatScreenContent(props: {
purchaseData: props.payOptions.purchaseData,
fromAddress: payer.account.address,
preferredProvider: preferredProvider,
+ paymentLinkId: paymentLinkId,
}
: undefined,
);
diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/OnRampScreen.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/OnRampScreen.tsx
index 7f69f31f824..8f6637426b8 100644
--- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/OnRampScreen.tsx
+++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/OnRampScreen.tsx
@@ -71,6 +71,7 @@ export function OnRampScreen(props: {
payer: PayerInfo;
onSuccess: (status: BuyWithFiatStatus) => void;
receiverAddress: string;
+ paymentLinkId?: string;
}) {
const connectedWallets = useConnectedWallets();
const isAutoMode = isInAppSigner({
@@ -85,6 +86,7 @@ export function OnRampScreen(props: {
payer: props.payer,
theme: props.theme,
isAutoMode,
+ paymentLinkId: props.paymentLinkId,
});
const firstStepChainId = state.steps[0]?.step.token.chainId;
return (
@@ -288,6 +290,7 @@ function useOnRampScreenState(props: {
payer: PayerInfo;
theme: "light" | "dark";
isAutoMode?: boolean;
+ paymentLinkId?: string;
}): OnRampScreenState {
const onRampSteps = getOnRampSteps(props.quote);
const [currentStepIndex, setCurrentStepIndex] = useState(0);
@@ -322,6 +325,7 @@ function useOnRampScreenState(props: {
client: props.client,
payer: props.payer,
isFiatFlow: true,
+ paymentLinkId: props.paymentLinkId,
});
// Track swap status
@@ -615,6 +619,7 @@ function useSwapMutation(props: {
client: ThirdwebClient;
payer: PayerInfo;
isFiatFlow: boolean;
+ paymentLinkId?: string;
}) {
const queryClient = useQueryClient();
return useMutation({
@@ -646,6 +651,7 @@ function useSwapMutation(props: {
toTokenAddress: toToken.tokenAddress,
fromAddress: account.address,
toAddress: account.address,
+ paymentLinkId: props.paymentLinkId,
client: props.client,
});
diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/SwapScreenContent.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/SwapScreenContent.tsx
index f9ac5e1df2b..9c91c2087f2 100644
--- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/SwapScreenContent.tsx
+++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/SwapScreenContent.tsx
@@ -55,6 +55,7 @@ export function SwapScreenContent(props: {
setTokenAmount: (amount: string) => void;
setHasEditedAmount: (hasEdited: boolean) => void;
disableTokenSelection: boolean;
+ paymentLinkId: undefined | string;
}) {
const {
setScreen,
@@ -121,6 +122,7 @@ export function SwapScreenContent(props: {
toAmount: tokenAmount,
client,
purchaseData: payOptions.purchaseData,
+ paymentLinkId: props.paymentLinkId,
}
: undefined;
diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/TransferConfirmationScreen.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/TransferConfirmationScreen.tsx
index b85b8399b78..1118c8cc296 100644
--- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/TransferConfirmationScreen.tsx
+++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/TransferConfirmationScreen.tsx
@@ -52,6 +52,7 @@ type TransferConfirmationScreenProps = {
transactionMode?: boolean;
payOptions?: PayUIOptions;
onSuccess: ((status: BuyWithCryptoStatus) => void) | undefined;
+ paymentLinkId: undefined | string;
};
export function TransferConfirmationScreen(
@@ -70,6 +71,7 @@ export function TransferConfirmationScreen(
transactionMode,
setTransactionHash,
payOptions,
+ paymentLinkId,
} = props;
const [step, setStep] = useState<"approve" | "transfer" | "execute">(
"transfer",
@@ -105,6 +107,7 @@ export function TransferConfirmationScreen(
payOptions?.mode === "direct_payment"
? payOptions.paymentInfo.feePayer
: undefined,
+ paymentLinkId: paymentLinkId,
});
return transferResponse;
},
diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/TransferFlow.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/TransferFlow.tsx
index 40630e8b2d7..ba70459892f 100644
--- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/TransferFlow.tsx
+++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/TransferFlow.tsx
@@ -23,6 +23,7 @@ type TransferFlowProps = {
tokenAmount: string;
transactionMode?: boolean;
payOptions?: PayUIOptions;
+ paymentLinkId: undefined | string;
};
export function TransferFlow(props: TransferFlowProps) {
diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/useSwapSupportedChains.ts b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/useSwapSupportedChains.ts
index ab359eae59c..2f61f5e6416 100644
--- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/useSwapSupportedChains.ts
+++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/useSwapSupportedChains.ts
@@ -36,7 +36,7 @@ async function fetchBuySupportedDestinations({
originTokenAddress,
maxSteps: 1,
sortBy: "popularity",
- limit: 1000000,
+ limit: 1_000_000,
});
const tokens = new Set();
const chains = new Set();
diff --git a/packages/thirdweb/src/react/web/ui/PayEmbed.tsx b/packages/thirdweb/src/react/web/ui/PayEmbed.tsx
index b123511b02c..cea1ada5907 100644
--- a/packages/thirdweb/src/react/web/ui/PayEmbed.tsx
+++ b/packages/thirdweb/src/react/web/ui/PayEmbed.tsx
@@ -144,6 +144,11 @@ export type PayEmbedProps = {
style?: React.CSSProperties;
className?: string;
+
+ /**
+ * @hidden
+ */
+ paymentLinkId?: string;
};
/**
@@ -365,6 +370,7 @@ export function PayEmbed(props: PayEmbedProps) {
client={props.client}
connectLocale={localeQuery.data}
hiddenWallets={props.hiddenWallets}
+ paymentLinkId={props.paymentLinkId}
payOptions={
props.payOptions || {
mode: "fund_wallet",