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",