Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc0295487b | ||
|
|
90b779107d | ||
|
|
afe187de95 | ||
|
|
759b93beff | ||
|
|
8e357b3cb3 | ||
|
|
6c318f83d6 | ||
|
|
f790c9fae9 | ||
|
|
bcbb3f540a | ||
|
|
931c730a08 | ||
|
|
e0c83121c2 | ||
|
|
a82a31e5ad | ||
|
|
3d235e218a | ||
|
|
c08fb1356e | ||
|
|
68cca6f992 | ||
|
|
e0abf2b273 | ||
|
|
abf0bbaf35 | ||
|
|
23cb3bd98c |
@@ -188,9 +188,16 @@ jobs:
|
||||
- name: Build Packages
|
||||
run: pnpm build
|
||||
|
||||
- name: Report bundle size
|
||||
- name: Report bundle size (thirdweb)
|
||||
uses: andresz1/size-limit-action@94bc357df29c36c8f8d50ea497c3e225c3c95d1d # v1.8.0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
package_manager: pnpm
|
||||
directory: packages/thirdweb
|
||||
directory: packages/thirdweb
|
||||
|
||||
- name: Report bundle size (nexus)
|
||||
uses: andresz1/size-limit-action@94bc357df29c36c8f8d50ea497c3e225c3c95d1d # v1.8.0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
package_manager: pnpm
|
||||
directory: packages/nexus
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.1 MiB |
@@ -95,7 +95,7 @@ function SkeletonBarChart(props: {
|
||||
fill={`url(#${fillAreaSkeletonId})`}
|
||||
radius={8}
|
||||
stroke="hsl(var(--muted-foreground))"
|
||||
type="natural"
|
||||
type="monotone"
|
||||
/>
|
||||
</AreaChart>
|
||||
)}
|
||||
|
||||
@@ -172,7 +172,7 @@ export function ThirdwebAreaChart<TConfig extends ChartConfig>(
|
||||
key={key}
|
||||
stackId={props.variant !== "stacked" ? undefined : "a"}
|
||||
stroke={`var(--color-${key})`}
|
||||
type="natural"
|
||||
type="monotone"
|
||||
/>
|
||||
),
|
||||
)}
|
||||
|
||||
@@ -5,6 +5,8 @@ const customClaimAmounts: Record<number, number> = {
|
||||
631571: 0.1,
|
||||
// Sophon testnet
|
||||
531050104: 5,
|
||||
// Arc testnet
|
||||
5042002: 1,
|
||||
};
|
||||
|
||||
const defaultClaimAmount = 0.01;
|
||||
|
||||
@@ -21,8 +21,8 @@ export function getSDKTheme(theme: "light" | "dark"): Theme {
|
||||
primaryButtonText: "hsl(var(--inverted-foreground))",
|
||||
primaryText: "hsl(var(--foreground))",
|
||||
scrollbarBg: "hsl(var(--muted))",
|
||||
secondaryButtonBg: "hsl(var(--secondary))",
|
||||
secondaryButtonHoverBg: "hsl(var(--secondary)/80%)",
|
||||
secondaryButtonBg: "hsl(var(--secondary)/70%)",
|
||||
secondaryButtonHoverBg: "hsl(var(--secondary))",
|
||||
secondaryButtonText: "hsl(var(--secondary-foreground))",
|
||||
secondaryIconColor: "hsl(var(--secondary-foreground))",
|
||||
secondaryIconHoverBg: "hsl(var(--accent))",
|
||||
@@ -33,7 +33,7 @@ export function getSDKTheme(theme: "light" | "dark"): Theme {
|
||||
separatorLine: "hsl(var(--border))",
|
||||
skeletonBg: "hsl(var(--secondary-foreground)/15%)",
|
||||
success: "hsl(var(--success-text))",
|
||||
tertiaryBg: "hsl(var(--muted)/30%)",
|
||||
tertiaryBg: "hsl(var(--muted)/50%)",
|
||||
tooltipBg: "hsl(var(--popover))",
|
||||
tooltipText: "hsl(var(--popover-foreground))",
|
||||
},
|
||||
|
||||
-1
@@ -59,7 +59,6 @@ export function ErrorRate({ datapoints }: { datapoints: ResultItem[] }) {
|
||||
key={tag}
|
||||
stackId="a"
|
||||
stroke={`var(--color-${tag})`}
|
||||
type="natural"
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
||||
+70
-13
@@ -40,6 +40,13 @@ import {
|
||||
PaginationNext,
|
||||
PaginationPrevious,
|
||||
} from "@/components/ui/pagination";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import {
|
||||
@@ -57,6 +64,7 @@ import { useV5DashboardChain } from "@/hooks/chains/v5-adapter";
|
||||
import { WalletProductIcon } from "@/icons/WalletProductIcon";
|
||||
import { useDashboardRouter } from "@/lib/DashboardRouter";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { fetchSolanaBalance } from "../lib/getSolanaBalance";
|
||||
import { updateDefaultProjectWallet } from "../lib/vault.client";
|
||||
import { CreateServerWallet } from "../server-wallets/components/create-server-wallet.client";
|
||||
import type { Wallet as EVMWallet } from "../server-wallets/wallet-table/types";
|
||||
@@ -79,6 +87,7 @@ interface ServerWalletsTableProps {
|
||||
teamSlug: string;
|
||||
client: ThirdwebClient;
|
||||
solanaPermissionError?: boolean;
|
||||
authToken: string;
|
||||
}
|
||||
|
||||
export function ServerWalletsTable(props: ServerWalletsTableProps) {
|
||||
@@ -95,10 +104,14 @@ export function ServerWalletsTable(props: ServerWalletsTableProps) {
|
||||
solanaTotalPages,
|
||||
client,
|
||||
solanaPermissionError,
|
||||
authToken,
|
||||
} = props;
|
||||
|
||||
const [activeChain, setActiveChain] = useState<WalletChain>("evm");
|
||||
const [selectedChainId, setSelectedChainId] = useState<number>(1);
|
||||
const [selectedSolanaChain, setSelectedSolanaChain] = useState<
|
||||
"solana:mainnet" | "solana:devnet"
|
||||
>("solana:mainnet");
|
||||
const [showSmartAccount, setShowSmartAccount] = useState(false);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
@@ -145,11 +158,33 @@ export function ServerWalletsTable(props: ServerWalletsTableProps) {
|
||||
</>
|
||||
)}
|
||||
{activeChain === "solana" && (
|
||||
<CreateSolanaWallet
|
||||
project={project}
|
||||
teamSlug={teamSlug}
|
||||
disabled={solanaPermissionError}
|
||||
/>
|
||||
<>
|
||||
<CreateSolanaWallet
|
||||
project={project}
|
||||
teamSlug={teamSlug}
|
||||
disabled={solanaPermissionError}
|
||||
/>
|
||||
<Select
|
||||
value={selectedSolanaChain}
|
||||
onValueChange={(value) =>
|
||||
setSelectedSolanaChain(
|
||||
value as "solana:mainnet" | "solana:devnet",
|
||||
)
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="w-fit min-w-[180px] rounded-full bg-background hover:bg-accent/50">
|
||||
<SelectValue placeholder="Select network" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="rounded-xl">
|
||||
<SelectItem value="solana:mainnet" className="rounded-lg">
|
||||
Solana Mainnet
|
||||
</SelectItem>
|
||||
<SelectItem value="solana:devnet" className="rounded-lg">
|
||||
Solana Devnet
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -278,6 +313,8 @@ export function ServerWalletsTable(props: ServerWalletsTableProps) {
|
||||
project={project}
|
||||
teamSlug={teamSlug}
|
||||
client={client}
|
||||
authToken={authToken}
|
||||
chainId={selectedSolanaChain}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
@@ -507,11 +544,15 @@ function SolanaWalletRow({
|
||||
project,
|
||||
teamSlug,
|
||||
client,
|
||||
authToken,
|
||||
chainId,
|
||||
}: {
|
||||
wallet: SolanaWallet;
|
||||
project: Project;
|
||||
teamSlug: string;
|
||||
client: ThirdwebClient;
|
||||
authToken: string;
|
||||
chainId: "solana:mainnet" | "solana:devnet";
|
||||
}) {
|
||||
const engineService = project.services.find(
|
||||
(s) => s.name === "engineCloud",
|
||||
@@ -547,7 +588,12 @@ function SolanaWalletRow({
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<SolanaWalletBalance publicKey={wallet.publicKey} />
|
||||
<SolanaWalletBalance
|
||||
publicKey={wallet.publicKey}
|
||||
authToken={authToken}
|
||||
clientId={project.publishableKey}
|
||||
chainId={chainId}
|
||||
/>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
@@ -739,16 +785,27 @@ function WalletBalance({
|
||||
);
|
||||
}
|
||||
|
||||
function SolanaWalletBalance({ publicKey }: { publicKey: string }) {
|
||||
function SolanaWalletBalance({
|
||||
publicKey,
|
||||
authToken,
|
||||
clientId,
|
||||
chainId,
|
||||
}: {
|
||||
publicKey: string;
|
||||
authToken: string;
|
||||
clientId: string;
|
||||
chainId: "solana:mainnet" | "solana:devnet";
|
||||
}) {
|
||||
const balance = useQuery({
|
||||
queryFn: async () => {
|
||||
// TODO: Implement actual Solana balance fetching
|
||||
return {
|
||||
displayValue: "0",
|
||||
symbol: "SOL",
|
||||
};
|
||||
return await fetchSolanaBalance({
|
||||
publicKey,
|
||||
authToken,
|
||||
clientId,
|
||||
chainId,
|
||||
});
|
||||
},
|
||||
queryKey: ["solanaWalletBalance", publicKey],
|
||||
queryKey: ["solanaWalletBalance", publicKey, chainId],
|
||||
});
|
||||
|
||||
if (balance.isFetching) {
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
import { configure, getSolanaWalletBalance } from "@thirdweb-dev/api";
|
||||
import { THIRDWEB_API_HOST } from "@/constants/urls";
|
||||
|
||||
// Configure the API client to use the correct base URL
|
||||
configure({
|
||||
override: {
|
||||
baseUrl: THIRDWEB_API_HOST,
|
||||
},
|
||||
});
|
||||
|
||||
export async function fetchSolanaBalance({
|
||||
publicKey,
|
||||
authToken,
|
||||
clientId,
|
||||
chainId = "solana:mainnet",
|
||||
}: {
|
||||
publicKey: string;
|
||||
authToken: string;
|
||||
clientId: string;
|
||||
chainId?: "solana:mainnet" | "solana:devnet";
|
||||
}): Promise<{
|
||||
displayValue: string;
|
||||
symbol: string;
|
||||
value: string;
|
||||
decimals: number;
|
||||
} | null> {
|
||||
try {
|
||||
const response = await getSolanaWalletBalance({
|
||||
path: {
|
||||
address: publicKey,
|
||||
},
|
||||
query: {
|
||||
chainId,
|
||||
},
|
||||
headers: {
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
"Content-Type": "application/json",
|
||||
"x-client-id": clientId,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.error || !response.data) {
|
||||
console.error(
|
||||
"Error fetching Solana balance:",
|
||||
response.error || "No data returned",
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
displayValue: response.data.result.displayValue,
|
||||
symbol: "SOL",
|
||||
value: response.data.result.value,
|
||||
decimals: response.data.result.decimals,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error fetching Solana balance:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+1
@@ -215,6 +215,7 @@ export default async function TransactionsAnalyticsPage(props: {
|
||||
solanaWallets={solanaAccounts.data.items}
|
||||
teamSlug={params.team_slug}
|
||||
solanaPermissionError={isSolanaPermissionError}
|
||||
authToken={authToken}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
+2
-2
@@ -70,8 +70,8 @@ export function CreateSolanaWallet(props: {
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
variant="default"
|
||||
className="gap-2 rounded-full bg-foreground hover:bg-foreground/90"
|
||||
variant="outline"
|
||||
className="gap-1.5 rounded-full bg-background text-foreground"
|
||||
disabled={props.disabled}
|
||||
>
|
||||
<PlusIcon className="size-4" />
|
||||
|
||||
+1
@@ -202,6 +202,7 @@ export default async function Page(props: {
|
||||
solanaWallets={solanaAccounts.data.items}
|
||||
teamSlug={params.team_slug}
|
||||
solanaPermissionError={isSolanaPermissionError || false}
|
||||
authToken={authToken}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
"use client";
|
||||
import { Img } from "@workspace/ui/components/img";
|
||||
import { MoonIcon, SunIcon } from "lucide-react";
|
||||
import { useTheme } from "next-themes";
|
||||
import { ClientOnly } from "@/components/blocks/client-only";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { resolveSchemeWithErrorHandler } from "@/utils/resolveSchemeWithErrorHandler";
|
||||
import { payAppThirdwebClient } from "../constants";
|
||||
|
||||
export function PayIdPageHeader(props: {
|
||||
projectName: string;
|
||||
projectIcon: string | undefined;
|
||||
}) {
|
||||
return (
|
||||
<div className="border-b border-border/70">
|
||||
<header className="container flex max-w-7xl justify-between py-4">
|
||||
<div className="flex items-center gap-3">
|
||||
{props.projectIcon && (
|
||||
<Img
|
||||
src={
|
||||
resolveSchemeWithErrorHandler({
|
||||
uri: props.projectIcon,
|
||||
client: payAppThirdwebClient,
|
||||
}) || ""
|
||||
}
|
||||
alt=""
|
||||
className="rounded-full size-6 object-cover"
|
||||
/>
|
||||
)}
|
||||
|
||||
<h2 className="text-xl font-semibold tracking-tight">
|
||||
{props.projectName}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 lg:gap-5">
|
||||
<ToggleThemeButton />
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ToggleThemeButton(props: { className?: string }) {
|
||||
const { setTheme, theme } = useTheme();
|
||||
|
||||
return (
|
||||
<ClientOnly
|
||||
ssr={<Skeleton className="size-[36px] rounded-full border bg-accent" />}
|
||||
>
|
||||
<Button
|
||||
aria-label="Toggle theme"
|
||||
className={cn(
|
||||
"h-auto w-auto rounded-full p-2 text-muted-foreground hover:text-foreground",
|
||||
props.className,
|
||||
)}
|
||||
onClick={() => {
|
||||
setTheme(theme === "dark" ? "light" : "dark");
|
||||
}}
|
||||
variant="ghost"
|
||||
>
|
||||
{theme === "light" ? (
|
||||
<SunIcon className="size-5 " />
|
||||
) : (
|
||||
<MoonIcon className="size-5 " />
|
||||
)}
|
||||
</Button>
|
||||
</ClientOnly>
|
||||
);
|
||||
}
|
||||
@@ -1,21 +1,16 @@
|
||||
import { ShieldCheckIcon } from "lucide-react";
|
||||
import { cn } from "@workspace/ui/lib/utils";
|
||||
import type { Metadata } from "next";
|
||||
import { ThemeProvider } from "next-themes";
|
||||
import { Bridge, defineChain, toTokens } from "thirdweb";
|
||||
import { getChainMetadata } from "thirdweb/chains";
|
||||
import { shortenAddress } from "thirdweb/utils";
|
||||
import { Bridge } from "thirdweb";
|
||||
import { getPaymentLink } from "@/api/universal-bridge/links";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
|
||||
import {
|
||||
API_SERVER_SECRET,
|
||||
DASHBOARD_THIRDWEB_SECRET_KEY,
|
||||
} from "@/constants/server-envs";
|
||||
import { getConfiguredThirdwebClient } from "@/constants/thirdweb.server";
|
||||
import { resolveEns } from "@/lib/ens";
|
||||
import { resolveSchemeWithErrorHandler } from "@/utils/resolveSchemeWithErrorHandler";
|
||||
import { PayPageWidget } from "../components/client/PayPageWidget.client";
|
||||
import { payAppThirdwebClient } from "../constants";
|
||||
import { PayIdPageHeader } from "./header";
|
||||
|
||||
const title = "thirdweb Pay";
|
||||
const description = "Fast, secure, and simple payments.";
|
||||
@@ -54,26 +49,10 @@ export default async function PayPage({
|
||||
tokenAddress: paymentLink.destinationToken.address,
|
||||
});
|
||||
|
||||
const chainPromise = getChainMetadata(
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
defineChain(Number(paymentLink.destinationToken.chainId)),
|
||||
);
|
||||
|
||||
const recipientPromise = resolveEns(
|
||||
paymentLink.receiver,
|
||||
getConfiguredThirdwebClient({
|
||||
secretKey: DASHBOARD_THIRDWEB_SECRET_KEY,
|
||||
teamId: undefined,
|
||||
}),
|
||||
);
|
||||
|
||||
const [tokens, projectMetadata, chain, recipientEnsOrAddress] =
|
||||
await Promise.all([
|
||||
tokensPromise,
|
||||
projectMetadataPromise,
|
||||
chainPromise,
|
||||
recipientPromise,
|
||||
]);
|
||||
const [tokens, projectMetadata] = await Promise.all([
|
||||
tokensPromise,
|
||||
projectMetadataPromise,
|
||||
]);
|
||||
|
||||
const token = tokens[0];
|
||||
if (!token) {
|
||||
@@ -81,144 +60,33 @@ export default async function PayPage({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative flex h-dvh w-full items-center justify-center">
|
||||
<div className="relative flex flex-col min-h-dvh w-full">
|
||||
<ThemeProvider
|
||||
forcedTheme={theme === "light" ? "light" : "dark"}
|
||||
defaultTheme={theme === "light" ? "light" : "dark"}
|
||||
attribute="class"
|
||||
disableTransitionOnChange
|
||||
enableSystem={false}
|
||||
>
|
||||
<div className="flex z-10 flex-col lg:flex-row h-full w-full">
|
||||
<header className="min-w-full lg:min-w-[500px] border-b lg:border-r lg:h-full bg-card flex flex-col gap-4 items-start p-4 lg:p-8">
|
||||
<div>
|
||||
<div className="flex flex-row items-center justify-start gap-4">
|
||||
{projectMetadata.image && (
|
||||
<img
|
||||
src={
|
||||
resolveSchemeWithErrorHandler({
|
||||
uri: projectMetadata.image,
|
||||
client: payAppThirdwebClient,
|
||||
}) || ""
|
||||
}
|
||||
alt={projectMetadata.name}
|
||||
width={25}
|
||||
height={25}
|
||||
className="rounded-full overflow-hidden"
|
||||
/>
|
||||
)}
|
||||
<h2 className="text-xl font-bold">{projectMetadata.name}</h2>
|
||||
</div>
|
||||
{projectMetadata.description && (
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
{projectMetadata.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<PayIdPageHeader
|
||||
projectIcon={projectMetadata.image || undefined}
|
||||
projectName={projectMetadata.name}
|
||||
/>
|
||||
|
||||
<div className="hidden lg:block my-4 w-full">
|
||||
{paymentLink.amount && (
|
||||
<div className="flex flex-col gap-1 w-full my-4">
|
||||
<span className="text-muted-foreground text-xs">Details</span>
|
||||
<div className="font-medium flex-row flex justify-between items-center w-full">
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
{token.iconUri && (
|
||||
<img
|
||||
src={resolveSchemeWithErrorHandler({
|
||||
uri: token.iconUri,
|
||||
client: getConfiguredThirdwebClient({
|
||||
secretKey: DASHBOARD_THIRDWEB_SECRET_KEY,
|
||||
teamId: undefined,
|
||||
}),
|
||||
})}
|
||||
alt={token.name}
|
||||
width={25}
|
||||
height={25}
|
||||
className="size-5 rounded-full overflow-hidden"
|
||||
/>
|
||||
)}
|
||||
{toTokens(BigInt(paymentLink.amount), token.decimals)}{" "}
|
||||
{token.symbol}
|
||||
</div>
|
||||
{token.prices.USD && (
|
||||
<span>
|
||||
$
|
||||
{(
|
||||
Number(token.prices.USD) *
|
||||
Number(
|
||||
toTokens(
|
||||
BigInt(paymentLink.amount),
|
||||
token.decimals,
|
||||
),
|
||||
)
|
||||
).toFixed(2)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{chain && (
|
||||
<div className="flex flex-col gap-1 w-full my-4">
|
||||
<span className="text-muted-foreground text-xs">Network</span>
|
||||
<div className="font-medium flex-row flex justify-between items-center w-full">
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
{chain.icon?.url && (
|
||||
<img
|
||||
src={resolveSchemeWithErrorHandler({
|
||||
uri: chain.icon.url,
|
||||
client: getConfiguredThirdwebClient({
|
||||
secretKey: DASHBOARD_THIRDWEB_SECRET_KEY,
|
||||
teamId: undefined,
|
||||
}),
|
||||
})}
|
||||
alt={chain.name}
|
||||
width={chain.icon.width}
|
||||
height={chain.icon.height}
|
||||
className="size-5 rounded-full overflow-hidden"
|
||||
/>
|
||||
)}
|
||||
{chain.name}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{recipientEnsOrAddress.ensName ||
|
||||
(recipientEnsOrAddress.address && (
|
||||
<div className="flex flex-col gap-1 w-full my-4">
|
||||
<span className="text-muted-foreground text-xs">
|
||||
Seller
|
||||
</span>
|
||||
<div className="font-medium flex-row flex justify-between items-center w-full">
|
||||
{recipientEnsOrAddress.ensName ??
|
||||
shortenAddress(recipientEnsOrAddress.address)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-auto hidden lg:block">
|
||||
<Badge className="flex items-center gap-1.5 bg-purple-100 text-purple-800 border-purple-200 dark:bg-purple-950 dark:text-purple-300 dark:border-purple-800">
|
||||
<ShieldCheckIcon className="size-3" />
|
||||
Secured by thirdweb
|
||||
</Badge>
|
||||
</div>
|
||||
</header>
|
||||
<main className="flex justify-center py-12 w-full items-center grow">
|
||||
<PayPageWidget
|
||||
amount={
|
||||
paymentLink.amount ? BigInt(paymentLink.amount) : undefined
|
||||
}
|
||||
chainId={Number(paymentLink.destinationToken.chainId)}
|
||||
clientId={undefined} // Payment links don't need to use the same client ID to be executed
|
||||
image={paymentLink.imageUrl}
|
||||
name={paymentLink.title}
|
||||
paymentLinkId={id}
|
||||
purchaseData={paymentLink.purchaseData}
|
||||
recipientAddress={paymentLink.receiver}
|
||||
redirectUri={redirectUri}
|
||||
token={token}
|
||||
/>
|
||||
</main>
|
||||
</div>
|
||||
<main className="flex justify-center py-12 w-full items-center grow overflow-hidden relative">
|
||||
<DotsBackgroundPattern />
|
||||
<PayPageWidget
|
||||
amount={paymentLink.amount ? BigInt(paymentLink.amount) : undefined}
|
||||
chainId={Number(paymentLink.destinationToken.chainId)}
|
||||
clientId={undefined} // Payment links don't need to use the same client ID to be executed
|
||||
image={paymentLink.imageUrl}
|
||||
name={paymentLink.title}
|
||||
paymentLinkId={id}
|
||||
purchaseData={paymentLink.purchaseData}
|
||||
recipientAddress={paymentLink.receiver}
|
||||
redirectUri={redirectUri}
|
||||
token={token}
|
||||
/>
|
||||
</main>
|
||||
</ThemeProvider>
|
||||
</div>
|
||||
);
|
||||
@@ -237,7 +105,24 @@ async function getProjectMetadata(clientId: string) {
|
||||
}
|
||||
|
||||
const { data } = (await response.json()) as {
|
||||
data: { name: string; image: string | null; description: string | null };
|
||||
data: { name: string; image: string | null };
|
||||
};
|
||||
return data;
|
||||
}
|
||||
|
||||
function DotsBackgroundPattern(props: { className?: string }) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"pointer-events-none absolute -inset-x-36 -inset-y-24 text-foreground/20 dark:text-muted-foreground/15 hidden lg:block",
|
||||
props.className,
|
||||
)}
|
||||
style={{
|
||||
backgroundImage: "radial-gradient(currentColor 1px, transparent 1px)",
|
||||
backgroundSize: "24px 24px",
|
||||
maskImage:
|
||||
"radial-gradient(ellipse 100% 100% at 50% 50%, black 30%, transparent 50%)",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"use client";
|
||||
import { cn } from "@workspace/ui/lib/utils";
|
||||
import { payAppThirdwebClient } from "app/pay/constants";
|
||||
import { useTheme } from "next-themes";
|
||||
import { createThirdwebClient, NATIVE_TOKEN_ADDRESS, toTokens } from "thirdweb";
|
||||
@@ -63,7 +64,12 @@ export function PayPageWidget({
|
||||
client={
|
||||
clientId ? createThirdwebClient({ clientId }) : payAppThirdwebClient
|
||||
}
|
||||
image={image}
|
||||
className={cn(
|
||||
"shadow-xl",
|
||||
!image &&
|
||||
"[&_.tw-header-image]:invert dark:[&_.tw-header-image]:invert-0",
|
||||
)}
|
||||
image={image || "/assets/pay/general-pay.png"}
|
||||
name={name}
|
||||
onSuccess={() => {
|
||||
reportPaymentLinkBuySuccessful();
|
||||
|
||||
@@ -41,7 +41,7 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
const amount = queryParams.get("amount") || "0.01";
|
||||
const payTo = queryParams.get("payTo") ?? undefined;
|
||||
const tokenAddress = queryParams.get("tokenAddress") || token.address;
|
||||
const tokenAddress = queryParams.get("tokenAddress");
|
||||
const decimals = queryParams.get("decimals") || token.decimals.toString();
|
||||
const waitUntil =
|
||||
(queryParams.get("waitUntil") as "simulated" | "submitted" | "confirmed") ||
|
||||
@@ -53,13 +53,15 @@ export async function GET(request: NextRequest) {
|
||||
paymentData,
|
||||
network: defineChain(Number(chainId)),
|
||||
payTo,
|
||||
price: {
|
||||
amount: toUnits(amount, parseInt(decimals)).toString(),
|
||||
asset: {
|
||||
address: tokenAddress as `0x${string}`,
|
||||
decimals: decimals ? parseInt(decimals) : token.decimals,
|
||||
},
|
||||
},
|
||||
price: tokenAddress
|
||||
? {
|
||||
amount: toUnits(amount, parseInt(decimals)).toString(),
|
||||
asset: {
|
||||
address: tokenAddress as `0x${string}`,
|
||||
decimals: decimals ? parseInt(decimals) : token.decimals,
|
||||
},
|
||||
}
|
||||
: amount,
|
||||
routeConfig: {
|
||||
description: "Access to paid content",
|
||||
},
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# wagmi-inapp
|
||||
|
||||
## 0.0.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`6c318f8`](https://github.com/thirdweb-dev/js/commit/6c318f83d63aa8acce46da666e43638e410f799c), [`759b93b`](https://github.com/thirdweb-dev/js/commit/759b93beff7ab10a3a2fa3b9dd25ca628efb9267), [`8e357b3`](https://github.com/thirdweb-dev/js/commit/8e357b3cb39dcc7a8595a83224098a9c44585389)]:
|
||||
- thirdweb@5.110.6
|
||||
- @thirdweb-dev/wagmi-adapter@0.2.174
|
||||
|
||||
## 0.0.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`c08fb13`](https://github.com/thirdweb-dev/js/commit/c08fb1356e77be9eeb0569905fcb2ab76a516dc3), [`bcbb3f5`](https://github.com/thirdweb-dev/js/commit/bcbb3f540a8eab2b02f9261b779bd2aad3464598), [`e0c8312`](https://github.com/thirdweb-dev/js/commit/e0c83121c201eb2dbdfa6a6b1bbb637f5ad2a21d)]:
|
||||
- thirdweb@5.110.5
|
||||
- @thirdweb-dev/wagmi-adapter@0.2.173
|
||||
|
||||
## 0.0.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`23cb3bd`](https://github.com/thirdweb-dev/js/commit/23cb3bd98c5390e64f45da5a222042cdc76bd7f4), [`abf0bba`](https://github.com/thirdweb-dev/js/commit/abf0bbaf359c8de5b0ed0d481e3791c4b7cbf92b), [`e0abf2b`](https://github.com/thirdweb-dev/js/commit/e0abf2b2738661fda3c69551d78231cfac4b0767)]:
|
||||
- thirdweb@5.110.4
|
||||
- @thirdweb-dev/wagmi-adapter@0.2.172
|
||||
|
||||
## 0.0.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "wagmi-inapp",
|
||||
"private": true,
|
||||
"version": "0.0.16",
|
||||
"version": "0.0.19",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @thirdweb-dev/api
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#8338](https://github.com/thirdweb-dev/js/pull/8338) [`90b7791`](https://github.com/thirdweb-dev/js/commit/90b779107d3cb726adeca6a3f885129c12599c6a) Thanks [@0xFirekeeper](https://github.com/0xFirekeeper)! - added solana token balances endpoint
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/api",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.2",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/thirdweb-dev/js.git#main"
|
||||
|
||||
@@ -66,6 +66,9 @@ import type {
|
||||
GetSolanaTransactionData,
|
||||
GetSolanaTransactionErrors,
|
||||
GetSolanaTransactionResponses,
|
||||
GetSolanaWalletBalanceData,
|
||||
GetSolanaWalletBalanceErrors,
|
||||
GetSolanaWalletBalanceResponses,
|
||||
GetTokenOwnersData,
|
||||
GetTokenOwnersErrors,
|
||||
GetTokenOwnersResponses,
|
||||
@@ -1590,6 +1593,31 @@ export const createSolanaWallet = <ThrowOnError extends boolean = false>(
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get Solana Wallet Balance
|
||||
* Get the SOL or SPL token balance for a Solana wallet on a specific Solana network.
|
||||
*
|
||||
* **Authentication**: Pass `x-client-id` for frontend usage from allowlisted origins or `x-secret-key` for backend usage.
|
||||
*/
|
||||
export const getSolanaWalletBalance = <ThrowOnError extends boolean = false>(
|
||||
options: Options<GetSolanaWalletBalanceData, ThrowOnError>,
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).get<
|
||||
GetSolanaWalletBalanceResponses,
|
||||
GetSolanaWalletBalanceErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
security: [
|
||||
{
|
||||
name: "x-client-id",
|
||||
type: "apiKey",
|
||||
},
|
||||
],
|
||||
url: "/v1/solana/wallets/{address}/balance",
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Sign Solana Message
|
||||
* Sign an arbitrary message with a Solana wallet. Supports both text and hexadecimal message formats with automatic format detection.
|
||||
|
||||
@@ -2135,6 +2135,10 @@ export type GetWalletTransactionsData = {
|
||||
address: string;
|
||||
};
|
||||
query: {
|
||||
/**
|
||||
* Chain ID(s) to request transaction data for. You can specify multiple chain IDs by repeating the parameter, up to a maximum of 50. Example: ?chainId=1&chainId=137
|
||||
*/
|
||||
chainId: Array<number>;
|
||||
/**
|
||||
* Filter by block timestamp (Unix timestamp) greater than or equal to this value
|
||||
*/
|
||||
@@ -2171,10 +2175,6 @@ export type GetWalletTransactionsData = {
|
||||
* Sort order: 'asc' for ascending, 'desc' for descending
|
||||
*/
|
||||
sortOrder?: "asc" | "desc";
|
||||
/**
|
||||
* Chain ID(s) to request transaction data for. You can specify multiple chain IDs by repeating the parameter, up to a maximum of 50. Example: ?chainId=1&chainId=137
|
||||
*/
|
||||
chainId: Array<number>;
|
||||
};
|
||||
url: "/v1/wallets/{address}/transactions";
|
||||
};
|
||||
@@ -3234,11 +3234,11 @@ export type WriteContractErrors = {
|
||||
/**
|
||||
* Payment ID
|
||||
*/
|
||||
id: string;
|
||||
id?: string;
|
||||
/**
|
||||
* Bridge quote for completing the payment
|
||||
*/
|
||||
quote: {
|
||||
quote?: {
|
||||
/**
|
||||
* Block number when quote was generated
|
||||
*/
|
||||
@@ -4314,11 +4314,11 @@ export type SendTransactionsErrors = {
|
||||
/**
|
||||
* Payment ID
|
||||
*/
|
||||
id: string;
|
||||
id?: string;
|
||||
/**
|
||||
* Bridge quote for completing the payment
|
||||
*/
|
||||
quote: {
|
||||
quote?: {
|
||||
/**
|
||||
* Block number when quote was generated
|
||||
*/
|
||||
@@ -4725,11 +4725,11 @@ export type PaymentsPurchaseErrors = {
|
||||
/**
|
||||
* Payment ID
|
||||
*/
|
||||
id: string;
|
||||
id?: string;
|
||||
/**
|
||||
* Bridge quote for completing the payment
|
||||
*/
|
||||
quote: {
|
||||
quote?: {
|
||||
/**
|
||||
* Block number when quote was generated
|
||||
*/
|
||||
@@ -4914,17 +4914,7 @@ export type VerifyX402PaymentData = {
|
||||
paymentPayload: {
|
||||
x402Version: number;
|
||||
scheme: "exact";
|
||||
network:
|
||||
| "base-sepolia"
|
||||
| "base"
|
||||
| "avalanche-fuji"
|
||||
| "avalanche"
|
||||
| "iotex"
|
||||
| "solana-devnet"
|
||||
| "solana"
|
||||
| "sei"
|
||||
| "sei-testnet"
|
||||
| string;
|
||||
network: string;
|
||||
payload:
|
||||
| {
|
||||
signature: string;
|
||||
@@ -4943,17 +4933,7 @@ export type VerifyX402PaymentData = {
|
||||
};
|
||||
paymentRequirements: {
|
||||
scheme: "exact";
|
||||
network:
|
||||
| "base-sepolia"
|
||||
| "base"
|
||||
| "avalanche-fuji"
|
||||
| "avalanche"
|
||||
| "iotex"
|
||||
| "solana-devnet"
|
||||
| "solana"
|
||||
| "sei"
|
||||
| "sei-testnet"
|
||||
| string;
|
||||
network: string;
|
||||
maxAmountRequired: string;
|
||||
resource: string;
|
||||
description: string;
|
||||
@@ -5015,7 +4995,6 @@ export type VerifyX402PaymentResponses = {
|
||||
| "invalid_exact_svm_payload_transaction_instruction_not_spl_token_transfer_checked"
|
||||
| "invalid_exact_svm_payload_transaction_instruction_not_token_2022_transfer_checked"
|
||||
| "invalid_exact_svm_payload_transaction_not_a_transfer_instruction"
|
||||
| "invalid_exact_svm_payload_transaction_cannot_derive_receiver_ata"
|
||||
| "invalid_exact_svm_payload_transaction_receiver_ata_not_found"
|
||||
| "invalid_exact_svm_payload_transaction_sender_ata_not_found"
|
||||
| "invalid_exact_svm_payload_transaction_simulation_failed"
|
||||
@@ -5049,17 +5028,7 @@ export type SettleX402PaymentData = {
|
||||
paymentPayload: {
|
||||
x402Version: number;
|
||||
scheme: "exact";
|
||||
network:
|
||||
| "base-sepolia"
|
||||
| "base"
|
||||
| "avalanche-fuji"
|
||||
| "avalanche"
|
||||
| "iotex"
|
||||
| "solana-devnet"
|
||||
| "solana"
|
||||
| "sei"
|
||||
| "sei-testnet"
|
||||
| string;
|
||||
network: string;
|
||||
payload:
|
||||
| {
|
||||
signature: string;
|
||||
@@ -5078,17 +5047,7 @@ export type SettleX402PaymentData = {
|
||||
};
|
||||
paymentRequirements: {
|
||||
scheme: "exact";
|
||||
network:
|
||||
| "base-sepolia"
|
||||
| "base"
|
||||
| "avalanche-fuji"
|
||||
| "avalanche"
|
||||
| "iotex"
|
||||
| "solana-devnet"
|
||||
| "solana"
|
||||
| "sei"
|
||||
| "sei-testnet"
|
||||
| string;
|
||||
network: string;
|
||||
maxAmountRequired: string;
|
||||
resource: string;
|
||||
description: string;
|
||||
@@ -5154,7 +5113,6 @@ export type SettleX402PaymentResponses = {
|
||||
| "invalid_exact_svm_payload_transaction_instruction_not_spl_token_transfer_checked"
|
||||
| "invalid_exact_svm_payload_transaction_instruction_not_token_2022_transfer_checked"
|
||||
| "invalid_exact_svm_payload_transaction_not_a_transfer_instruction"
|
||||
| "invalid_exact_svm_payload_transaction_cannot_derive_receiver_ata"
|
||||
| "invalid_exact_svm_payload_transaction_receiver_ata_not_found"
|
||||
| "invalid_exact_svm_payload_transaction_sender_ata_not_found"
|
||||
| "invalid_exact_svm_payload_transaction_simulation_failed"
|
||||
@@ -5183,7 +5141,10 @@ export type SettleX402PaymentResponses = {
|
||||
| "solana-devnet"
|
||||
| "solana"
|
||||
| "sei"
|
||||
| "sei-testnet";
|
||||
| "sei-testnet"
|
||||
| "polygon"
|
||||
| "polygon-amoy"
|
||||
| "peaq";
|
||||
errorMessage?: string;
|
||||
};
|
||||
};
|
||||
@@ -5226,17 +5187,7 @@ export type SupportedX402PaymentsResponses = {
|
||||
kinds: Array<{
|
||||
x402Version: 1;
|
||||
scheme: "exact";
|
||||
network:
|
||||
| "base-sepolia"
|
||||
| "base"
|
||||
| "avalanche-fuji"
|
||||
| "avalanche"
|
||||
| "iotex"
|
||||
| "solana-devnet"
|
||||
| "solana"
|
||||
| "sei"
|
||||
| "sei-testnet"
|
||||
| string;
|
||||
network: string;
|
||||
extra?: {
|
||||
defaultAsset?: {
|
||||
/**
|
||||
@@ -5340,11 +5291,11 @@ export type FetchWithPaymentErrors = {
|
||||
/**
|
||||
* Payment ID
|
||||
*/
|
||||
id: string;
|
||||
id?: string;
|
||||
/**
|
||||
* Bridge quote for completing the payment
|
||||
*/
|
||||
quote: {
|
||||
quote?: {
|
||||
/**
|
||||
* Block number when quote was generated
|
||||
*/
|
||||
@@ -5552,17 +5503,7 @@ export type ListPayableServicesResponses = {
|
||||
x402Version: number;
|
||||
accepts: Array<{
|
||||
scheme: "exact";
|
||||
network:
|
||||
| "base-sepolia"
|
||||
| "base"
|
||||
| "avalanche-fuji"
|
||||
| "avalanche"
|
||||
| "iotex"
|
||||
| "solana-devnet"
|
||||
| "solana"
|
||||
| "sei"
|
||||
| "sei-testnet"
|
||||
| string;
|
||||
network: string;
|
||||
maxAmountRequired: string;
|
||||
resource: string;
|
||||
description: string;
|
||||
@@ -6298,11 +6239,11 @@ export type BridgeSwapErrors = {
|
||||
/**
|
||||
* Payment ID
|
||||
*/
|
||||
id: string;
|
||||
id?: string;
|
||||
/**
|
||||
* Bridge quote for completing the payment
|
||||
*/
|
||||
quote: {
|
||||
quote?: {
|
||||
/**
|
||||
* Block number when quote was generated
|
||||
*/
|
||||
@@ -6640,6 +6581,74 @@ export type CreateSolanaWalletResponses = {
|
||||
export type CreateSolanaWalletResponse =
|
||||
CreateSolanaWalletResponses[keyof CreateSolanaWalletResponses];
|
||||
|
||||
export type GetSolanaWalletBalanceData = {
|
||||
body?: never;
|
||||
path: {
|
||||
/**
|
||||
* Public key of the Solana wallet.
|
||||
*/
|
||||
address: string;
|
||||
};
|
||||
query: {
|
||||
/**
|
||||
* Solana network to query. Choose either solana:mainnet or solana:devnet.
|
||||
*/
|
||||
chainId: "solana:mainnet" | "solana:devnet";
|
||||
/**
|
||||
* SPL token mint address. Omit to retrieve native SOL balance.
|
||||
*/
|
||||
tokenAddress?: string;
|
||||
};
|
||||
url: "/v1/solana/wallets/{address}/balance";
|
||||
};
|
||||
|
||||
export type GetSolanaWalletBalanceErrors = {
|
||||
/**
|
||||
* Invalid request parameters. This occurs when the wallet address or token mint is invalid, or the request mixes a token mint with multiple networks.
|
||||
*/
|
||||
400: unknown;
|
||||
/**
|
||||
* Authentication required. Include `x-client-id` or `x-secret-key` headers.
|
||||
*/
|
||||
401: unknown;
|
||||
/**
|
||||
* Internal server error. This may occur due to RPC connectivity issues or unexpected failures.
|
||||
*/
|
||||
500: unknown;
|
||||
};
|
||||
|
||||
export type GetSolanaWalletBalanceResponses = {
|
||||
/**
|
||||
* Wallet balance retrieved successfully for the requested Solana network.
|
||||
*/
|
||||
200: {
|
||||
/**
|
||||
* Balance data for the requested Solana network.
|
||||
*/
|
||||
result: {
|
||||
/**
|
||||
* Requested Solana network.
|
||||
*/
|
||||
chainId: "solana:mainnet" | "solana:devnet";
|
||||
/**
|
||||
* Number of decimals used by the token.
|
||||
*/
|
||||
decimals: number;
|
||||
/**
|
||||
* Human-readable balance formatted using token decimals.
|
||||
*/
|
||||
displayValue: string;
|
||||
/**
|
||||
* Raw balance value expressed in base units (lamports).
|
||||
*/
|
||||
value: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export type GetSolanaWalletBalanceResponse =
|
||||
GetSolanaWalletBalanceResponses[keyof GetSolanaWalletBalanceResponses];
|
||||
|
||||
export type SignSolanaMessageData = {
|
||||
/**
|
||||
* Request payload for signing an arbitrary Solana message.
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
# @thirdweb-dev/nebula
|
||||
|
||||
## 0.2.77
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`6c318f8`](https://github.com/thirdweb-dev/js/commit/6c318f83d63aa8acce46da666e43638e410f799c), [`759b93b`](https://github.com/thirdweb-dev/js/commit/759b93beff7ab10a3a2fa3b9dd25ca628efb9267), [`8e357b3`](https://github.com/thirdweb-dev/js/commit/8e357b3cb39dcc7a8595a83224098a9c44585389)]:
|
||||
- thirdweb@5.110.6
|
||||
|
||||
## 0.2.76
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`c08fb13`](https://github.com/thirdweb-dev/js/commit/c08fb1356e77be9eeb0569905fcb2ab76a516dc3), [`bcbb3f5`](https://github.com/thirdweb-dev/js/commit/bcbb3f540a8eab2b02f9261b779bd2aad3464598), [`e0c8312`](https://github.com/thirdweb-dev/js/commit/e0c83121c201eb2dbdfa6a6b1bbb637f5ad2a21d)]:
|
||||
- thirdweb@5.110.5
|
||||
|
||||
## 0.2.75
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`23cb3bd`](https://github.com/thirdweb-dev/js/commit/23cb3bd98c5390e64f45da5a222042cdc76bd7f4), [`abf0bba`](https://github.com/thirdweb-dev/js/commit/abf0bbaf359c8de5b0ed0d481e3791c4b7cbf92b), [`e0abf2b`](https://github.com/thirdweb-dev/js/commit/e0abf2b2738661fda3c69551d78231cfac4b0767)]:
|
||||
- thirdweb@5.110.4
|
||||
|
||||
## 0.2.74
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -57,5 +57,5 @@
|
||||
"type": "module",
|
||||
"types": "./dist/types/exports/thirdweb.d.ts",
|
||||
"typings": "./dist/types/exports/thirdweb.d.ts",
|
||||
"version": "0.2.74"
|
||||
"version": "0.2.77"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
[
|
||||
{
|
||||
"import": "*",
|
||||
"limit": "110 kB",
|
||||
"name": "@thirdweb-dev/nexus (esm)",
|
||||
"path": "./dist/esm/exports/nexus.js"
|
||||
},
|
||||
{
|
||||
"limit": "350 kB",
|
||||
"name": "@thirdweb-dev/nexus (cjs)",
|
||||
"path": "./dist/cjs/exports/nexus.js"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,7 @@
|
||||
# @thirdweb-dev/nexus
|
||||
|
||||
## 0.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#8332](https://github.com/thirdweb-dev/js/pull/8332) [`8e357b3`](https://github.com/thirdweb-dev/js/commit/8e357b3cb39dcc7a8595a83224098a9c44585389) Thanks [@jnsdls](https://github.com/jnsdls)! - initial release
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.0.6/schema.json",
|
||||
"extends": "//",
|
||||
"overrides": [
|
||||
{
|
||||
"assist": {
|
||||
"actions": {
|
||||
"source": {
|
||||
"useSortedKeys": "off"
|
||||
}
|
||||
}
|
||||
},
|
||||
"includes": ["package.json"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/knip@5/schema.json",
|
||||
"entry": ["src/exports/**"],
|
||||
"ignore": ["src/**/__generated__/**", "**/*.bench.ts"],
|
||||
"ignoreBinaries": ["printf"],
|
||||
"ignoreDependencies": ["tslib"],
|
||||
"project": ["src/**/*.{ts,tsx}"],
|
||||
"rules": {
|
||||
"enumMembers": "off",
|
||||
"optionalPeerDependencies": "off"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"browser": {
|
||||
"crypto": false
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/thirdweb-dev/js/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"x402": "0.7.0",
|
||||
"zod": "3.25.75"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.0.6",
|
||||
"@size-limit/preset-small-lib": "11.2.0",
|
||||
"knip": "5.60.2",
|
||||
"rimraf": "6.0.1",
|
||||
"size-limit": "11.2.0",
|
||||
"typescript": "5.8.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/types/exports/nexus.d.ts",
|
||||
"import": "./dist/esm/exports/nexus.js",
|
||||
"default": "./dist/cjs/exports/nexus.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist/*",
|
||||
"src/*",
|
||||
"!**/*.tsbuildinfo",
|
||||
"!**/*.test.ts",
|
||||
"!**/*.test.tsx",
|
||||
"!**/*.test.ts.snap",
|
||||
"!**/*.test-d.ts",
|
||||
"!**/*.bench.ts",
|
||||
"!tsconfig.build.json"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"main": "./dist/cjs/exports/nexus.js",
|
||||
"module": "./dist/esm/exports/nexus.js",
|
||||
"name": "@thirdweb-dev/nexus",
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5.0.4"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/thirdweb-dev/js.git#main"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "pnpm clean && pnpm build:types && pnpm build:cjs && pnpm build:esm",
|
||||
"build:cjs": "tsc --noCheck --project ./tsconfig.build.json --module commonjs --outDir ./dist/cjs --verbatimModuleSyntax false && printf '{\"type\":\"commonjs\"}' > ./dist/cjs/package.json",
|
||||
"build:esm": "tsc --noCheck --project ./tsconfig.build.json --module es2020 --outDir ./dist/esm && printf '{\"type\": \"module\",\"sideEffects\":false}' > ./dist/esm/package.json",
|
||||
"build:types": "tsc --project ./tsconfig.build.json --module nodenext --moduleResolution nodenext --declarationDir ./dist/types --emitDeclarationOnly --declaration --declarationMap",
|
||||
"clean": "rimraf dist",
|
||||
"dev": "tsc --project ./tsconfig.build.json --module nodenext --moduleResolution nodenext --outDir ./dist/esm --watch",
|
||||
"dev:cjs": "printf '{\"type\":\"commonjs\"}' > ./dist/cjs/package.json && tsc --noCheck --project ./tsconfig.build.json --module commonjs --outDir ./dist/cjs --verbatimModuleSyntax false --watch",
|
||||
"dev:esm": "printf '{\"type\": \"module\",\"sideEffects\":false}' > ./dist/esm/package.json && tsc --noCheck --project ./tsconfig.build.json --module es2020 --outDir ./dist/esm --watch",
|
||||
"fix": "biome check ./src --fix",
|
||||
"format": "biome format ./src --write",
|
||||
"knip": "knip",
|
||||
"lint": "knip && biome check ./src && tsc --project ./tsconfig.build.json --module nodenext --moduleResolution nodenext --noEmit",
|
||||
"size": "size-limit",
|
||||
"typecheck": "tsc --project ./tsconfig.build.json --module nodenext --moduleResolution nodenext --noEmit"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"types": "./dist/types/exports/nexus.d.ts",
|
||||
"typings": "./dist/types/exports/nexus.d.ts",
|
||||
"version": "0.1.0"
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
import { ChainIdToNetwork, type Money, moneySchema } from "x402/types";
|
||||
import { decodePayment } from "./encode.js";
|
||||
import type { ThirdwebX402Facilitator } from "./facilitator.js";
|
||||
import {
|
||||
networkToChainId,
|
||||
type RequestedPaymentPayload,
|
||||
type RequestedPaymentRequirements,
|
||||
} from "./schemas.js";
|
||||
import {
|
||||
type DefaultAsset,
|
||||
type ERC20TokenAmount,
|
||||
type PaymentArgs,
|
||||
type PaymentRequiredResult,
|
||||
type SupportedSignatureType,
|
||||
x402Version,
|
||||
} from "./types.js";
|
||||
import { toUnits } from "./utils.js";
|
||||
|
||||
type GetPaymentRequirementsResult = {
|
||||
status: 200;
|
||||
paymentRequirements: RequestedPaymentRequirements[];
|
||||
selectedPaymentRequirements: RequestedPaymentRequirements;
|
||||
decodedPayment: RequestedPaymentPayload;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decodes a payment request and returns the payment requirements, selected payment requirements, and decoded payment
|
||||
* @param args
|
||||
* @returns The payment requirements, selected payment requirements, and decoded payment
|
||||
*/
|
||||
export async function decodePaymentRequest(
|
||||
args: PaymentArgs,
|
||||
): Promise<GetPaymentRequirementsResult | PaymentRequiredResult> {
|
||||
const {
|
||||
price,
|
||||
network,
|
||||
facilitator,
|
||||
payTo,
|
||||
resourceUrl,
|
||||
routeConfig = {},
|
||||
method,
|
||||
paymentData,
|
||||
} = args;
|
||||
const {
|
||||
description,
|
||||
mimeType,
|
||||
maxTimeoutSeconds,
|
||||
inputSchema,
|
||||
outputSchema,
|
||||
errorMessages,
|
||||
discoverable,
|
||||
} = routeConfig;
|
||||
|
||||
let chainId: number;
|
||||
try {
|
||||
chainId = networkToChainId(network);
|
||||
} catch (error) {
|
||||
return {
|
||||
status: 402,
|
||||
responseHeaders: { "Content-Type": "application/json" },
|
||||
responseBody: {
|
||||
x402Version,
|
||||
error:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: `Invalid network: ${network}`,
|
||||
accepts: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const atomicAmountForAsset = await processPriceToAtomicAmount(
|
||||
price,
|
||||
chainId,
|
||||
facilitator,
|
||||
);
|
||||
if ("error" in atomicAmountForAsset) {
|
||||
return {
|
||||
status: 402,
|
||||
responseHeaders: { "Content-Type": "application/json" },
|
||||
responseBody: {
|
||||
x402Version,
|
||||
error: atomicAmountForAsset.error,
|
||||
accepts: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
const { maxAmountRequired, asset } = atomicAmountForAsset;
|
||||
|
||||
const paymentRequirements: RequestedPaymentRequirements[] = [];
|
||||
|
||||
const mappedNetwork = ChainIdToNetwork[chainId];
|
||||
paymentRequirements.push({
|
||||
scheme: "exact",
|
||||
network: mappedNetwork ? mappedNetwork : `eip155:${chainId}`,
|
||||
maxAmountRequired,
|
||||
resource: resourceUrl,
|
||||
description: description ?? "",
|
||||
mimeType: mimeType ?? "application/json",
|
||||
payTo: facilitator.address as `0x${string}`, // always pay to the facilitator address first
|
||||
maxTimeoutSeconds: maxTimeoutSeconds ?? 86400,
|
||||
asset: asset.address as `0x${string}`,
|
||||
outputSchema: {
|
||||
input: {
|
||||
type: "http",
|
||||
method,
|
||||
discoverable: discoverable ?? true,
|
||||
...inputSchema,
|
||||
},
|
||||
output: outputSchema,
|
||||
},
|
||||
extra: {
|
||||
recipientAddress: payTo, // input payTo is the final recipient address
|
||||
...((asset as ERC20TokenAmount["asset"]).eip712 ?? {}),
|
||||
},
|
||||
});
|
||||
|
||||
// Check for payment header
|
||||
if (!paymentData) {
|
||||
return {
|
||||
status: 402,
|
||||
responseHeaders: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
responseBody: {
|
||||
x402Version,
|
||||
error: errorMessages?.paymentRequired || "X-PAYMENT header is required",
|
||||
accepts: paymentRequirements,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// decode b64 payment
|
||||
let decodedPayment: RequestedPaymentPayload;
|
||||
try {
|
||||
decodedPayment = decodePayment(paymentData);
|
||||
decodedPayment.x402Version = x402Version;
|
||||
} catch (error) {
|
||||
return {
|
||||
status: 402,
|
||||
responseHeaders: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
responseBody: {
|
||||
x402Version,
|
||||
error:
|
||||
errorMessages?.invalidPayment ||
|
||||
(error instanceof Error ? error.message : "Invalid payment"),
|
||||
accepts: paymentRequirements,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const selectedPaymentRequirements = paymentRequirements.find(
|
||||
(value) =>
|
||||
value.scheme === decodedPayment.scheme &&
|
||||
networkToChainId(value.network) ===
|
||||
networkToChainId(decodedPayment.network),
|
||||
);
|
||||
if (!selectedPaymentRequirements) {
|
||||
return {
|
||||
status: 402,
|
||||
responseHeaders: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
responseBody: {
|
||||
x402Version,
|
||||
error:
|
||||
errorMessages?.noMatchingRequirements ||
|
||||
"Unable to find matching payment requirements",
|
||||
accepts: paymentRequirements,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
paymentRequirements,
|
||||
decodedPayment,
|
||||
selectedPaymentRequirements,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the amount from the given price
|
||||
*
|
||||
* @param price - The price to parse
|
||||
* @param network - The network to get the default asset for
|
||||
* @returns The parsed amount or an error message
|
||||
*/
|
||||
async function processPriceToAtomicAmount(
|
||||
price: Money | ERC20TokenAmount,
|
||||
chainId: number,
|
||||
facilitator: ThirdwebX402Facilitator,
|
||||
): Promise<
|
||||
{ maxAmountRequired: string; asset: DefaultAsset } | { error: string }
|
||||
> {
|
||||
// Handle USDC amount (string) or token amount (ERC20TokenAmount)
|
||||
let maxAmountRequired: string;
|
||||
let asset: DefaultAsset;
|
||||
|
||||
if (typeof price === "string" || typeof price === "number") {
|
||||
// USDC amount in dollars
|
||||
const parsedAmount = moneySchema.safeParse(price);
|
||||
if (!parsedAmount.success) {
|
||||
return {
|
||||
error: `Invalid price (price: ${price}). Must be in the form "$3.10", 0.10, "0.001", ${parsedAmount.error}`,
|
||||
};
|
||||
}
|
||||
const parsedUsdAmount = parsedAmount.data;
|
||||
const defaultAsset = await getDefaultAsset(chainId, facilitator);
|
||||
if (!defaultAsset) {
|
||||
return {
|
||||
error: `Unable to get default asset on chain ${chainId}. Please specify an asset in the payment requirements.`,
|
||||
};
|
||||
}
|
||||
asset = defaultAsset;
|
||||
maxAmountRequired = toUnits(
|
||||
parsedUsdAmount.toString(),
|
||||
defaultAsset.decimals,
|
||||
).toString();
|
||||
} else {
|
||||
// Token amount in atomic units
|
||||
maxAmountRequired = price.amount;
|
||||
const tokenExtras = await getOrDetectTokenExtras({
|
||||
facilitator,
|
||||
partialAsset: price.asset,
|
||||
chainId,
|
||||
});
|
||||
if (!tokenExtras) {
|
||||
return {
|
||||
error: `Unable to find token information for ${price.asset.address} on chain ${chainId}. Please specify the asset decimals and eip712 information in the asset options.`,
|
||||
};
|
||||
}
|
||||
asset = {
|
||||
address: price.asset.address,
|
||||
decimals: tokenExtras.decimals,
|
||||
eip712: {
|
||||
name: tokenExtras.name,
|
||||
version: tokenExtras.version,
|
||||
primaryType: tokenExtras.primaryType,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
maxAmountRequired,
|
||||
asset,
|
||||
};
|
||||
}
|
||||
|
||||
async function getDefaultAsset(
|
||||
chainId: number,
|
||||
facilitator: ThirdwebX402Facilitator,
|
||||
): Promise<DefaultAsset | undefined> {
|
||||
const supportedAssets = await facilitator.supported();
|
||||
const matchingAsset = supportedAssets.kinds.find(
|
||||
(supported) => networkToChainId(supported.network) === chainId,
|
||||
);
|
||||
const assetConfig = matchingAsset?.extra?.defaultAsset as DefaultAsset;
|
||||
return assetConfig;
|
||||
}
|
||||
|
||||
async function getOrDetectTokenExtras(args: {
|
||||
facilitator: ThirdwebX402Facilitator;
|
||||
partialAsset: ERC20TokenAmount["asset"];
|
||||
chainId: number;
|
||||
}): Promise<
|
||||
| {
|
||||
name: string;
|
||||
version: string;
|
||||
decimals: number;
|
||||
primaryType: SupportedSignatureType;
|
||||
}
|
||||
| undefined
|
||||
> {
|
||||
const { facilitator, partialAsset, chainId } = args;
|
||||
if (
|
||||
partialAsset.eip712?.name &&
|
||||
partialAsset.eip712?.version &&
|
||||
partialAsset.decimals !== undefined
|
||||
) {
|
||||
return {
|
||||
name: partialAsset.eip712.name,
|
||||
version: partialAsset.eip712.version,
|
||||
decimals: partialAsset.decimals,
|
||||
primaryType: partialAsset.eip712.primaryType,
|
||||
};
|
||||
}
|
||||
// read from facilitator
|
||||
const response = await facilitator
|
||||
.supported({
|
||||
chainId,
|
||||
tokenAddress: partialAsset.address,
|
||||
})
|
||||
.catch(() => {
|
||||
return {
|
||||
kinds: [],
|
||||
};
|
||||
});
|
||||
|
||||
const exactScheme = response.kinds?.find((kind) => kind.scheme === "exact");
|
||||
if (!exactScheme) {
|
||||
return undefined;
|
||||
}
|
||||
const supportedAsset = exactScheme.extra?.supportedAssets?.find(
|
||||
(asset) =>
|
||||
asset.address.toLowerCase() === partialAsset.address.toLowerCase(),
|
||||
);
|
||||
if (!supportedAsset) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
name: supportedAsset.eip712.name,
|
||||
version: supportedAsset.eip712.version,
|
||||
decimals: supportedAsset.decimals,
|
||||
primaryType: supportedAsset.eip712.primaryType as SupportedSignatureType,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import type { ExactEvmPayload } from "x402/types";
|
||||
import {
|
||||
type RequestedPaymentPayload,
|
||||
RequestedPaymentPayloadSchema,
|
||||
} from "./schemas.js";
|
||||
|
||||
/**
|
||||
* Encodes a payment payload into a base64 string, ensuring bigint values are properly stringified
|
||||
*
|
||||
* @param payment - The payment payload to encode
|
||||
* @returns A base64 encoded string representation of the payment payload
|
||||
*/
|
||||
export function encodePayment(payment: RequestedPaymentPayload): string {
|
||||
let safe: RequestedPaymentPayload;
|
||||
|
||||
// evm
|
||||
const evmPayload = payment.payload as ExactEvmPayload;
|
||||
safe = {
|
||||
...payment,
|
||||
payload: {
|
||||
...evmPayload,
|
||||
authorization: Object.fromEntries(
|
||||
Object.entries(evmPayload.authorization).map(([key, value]) => [
|
||||
key,
|
||||
typeof value === "bigint" ? (value as bigint).toString() : value,
|
||||
]),
|
||||
) as ExactEvmPayload["authorization"],
|
||||
},
|
||||
};
|
||||
return safeBase64Encode(JSON.stringify(safe));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a base64 encoded payment string back into a PaymentPayload object
|
||||
*
|
||||
* @param payment - The base64 encoded payment string to decode
|
||||
* @returns The decoded and validated PaymentPayload object
|
||||
*/
|
||||
export function decodePayment(payment: string): RequestedPaymentPayload {
|
||||
const decoded = safeBase64Decode(payment);
|
||||
const parsed = JSON.parse(decoded);
|
||||
|
||||
const obj: RequestedPaymentPayload = {
|
||||
...parsed,
|
||||
payload: parsed.payload as ExactEvmPayload,
|
||||
};
|
||||
const validated = RequestedPaymentPayloadSchema.parse(obj);
|
||||
return validated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a string to base64 format
|
||||
*
|
||||
* @param data - The string to be encoded to base64
|
||||
* @returns The base64 encoded string
|
||||
*/
|
||||
export function safeBase64Encode(data: string): string {
|
||||
if (
|
||||
typeof globalThis !== "undefined" &&
|
||||
typeof globalThis.btoa === "function"
|
||||
) {
|
||||
return globalThis.btoa(data);
|
||||
}
|
||||
return Buffer.from(data).toString("base64");
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a base64 string back to its original format
|
||||
*
|
||||
* @param data - The base64 encoded string to be decoded
|
||||
* @returns The decoded string in UTF-8 format
|
||||
*/
|
||||
function safeBase64Decode(data: string): string {
|
||||
if (
|
||||
typeof globalThis !== "undefined" &&
|
||||
typeof globalThis.atob === "function"
|
||||
) {
|
||||
return globalThis.atob(data);
|
||||
}
|
||||
return Buffer.from(data, "base64").toString("utf-8");
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
export type {
|
||||
HTTPRequestStructure,
|
||||
Money,
|
||||
PaymentMiddlewareConfig,
|
||||
Resource,
|
||||
} from "x402/types";
|
||||
export { decodePayment, encodePayment } from "../encode.js";
|
||||
export {
|
||||
createFacilitator,
|
||||
type ThirdwebX402Facilitator,
|
||||
type ThirdwebX402FacilitatorConfig,
|
||||
type WaitUntil,
|
||||
} from "../facilitator.js";
|
||||
export { settlePayment } from "../settle-payment.js";
|
||||
export type {
|
||||
ERC20TokenAmount,
|
||||
PaymentArgs,
|
||||
PaymentRequiredResult,
|
||||
SettlePaymentArgs,
|
||||
SettlePaymentResult,
|
||||
SupportedSignatureType,
|
||||
VerifyPaymentResult,
|
||||
} from "../types.js";
|
||||
export { verifyPayment } from "../verify-payment.js";
|
||||
@@ -0,0 +1,233 @@
|
||||
import type { VerifyResponse } from "x402/types";
|
||||
import type {
|
||||
FacilitatorSettleResponse,
|
||||
FacilitatorSupportedResponse,
|
||||
FacilitatorVerifyResponse,
|
||||
RequestedPaymentPayload,
|
||||
RequestedPaymentRequirements,
|
||||
} from "./schemas.js";
|
||||
import { stringify } from "./utils.js";
|
||||
|
||||
export type WaitUntil = "simulated" | "submitted" | "confirmed";
|
||||
|
||||
export type ThirdwebX402FacilitatorConfig = {
|
||||
walletSecret: string;
|
||||
walletAddress: string;
|
||||
waitUntil?: WaitUntil;
|
||||
baseUrl?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* facilitator for the x402 payment protocol.
|
||||
* @public
|
||||
*/
|
||||
export type ThirdwebX402Facilitator = {
|
||||
url: `${string}://${string}`;
|
||||
address: string;
|
||||
createAuthHeaders: () => Promise<{
|
||||
verify: Record<string, string>;
|
||||
settle: Record<string, string>;
|
||||
supported: Record<string, string>;
|
||||
list: Record<string, string>;
|
||||
}>;
|
||||
verify: (
|
||||
payload: RequestedPaymentPayload,
|
||||
paymentRequirements: RequestedPaymentRequirements,
|
||||
) => Promise<FacilitatorVerifyResponse>;
|
||||
settle: (
|
||||
payload: RequestedPaymentPayload,
|
||||
paymentRequirements: RequestedPaymentRequirements,
|
||||
waitUntil?: WaitUntil,
|
||||
) => Promise<FacilitatorSettleResponse>;
|
||||
supported: (filters?: {
|
||||
chainId: number;
|
||||
tokenAddress?: string;
|
||||
}) => Promise<FacilitatorSupportedResponse>;
|
||||
};
|
||||
|
||||
const DEFAULT_BASE_URL = "https://nexus-api.thirdweb.com";
|
||||
|
||||
/**
|
||||
* Creates a facilitator for the x402 payment protocol.
|
||||
* You can use this with `settlePayment` or with any x402 middleware to enable settling transactions with your thirdweb server wallet.
|
||||
*
|
||||
* @param config - The configuration for the facilitator
|
||||
* @returns a x402 compatible FacilitatorConfig
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { createFacilitator } from "@thirdweb-dev/nexus";
|
||||
* import { paymentMiddleware } from 'x402-hono'
|
||||
*
|
||||
* const facilitator = createFacilitator({
|
||||
* walletSecret: <your-wallet-secret>,
|
||||
* walletAddress: <your-wallet-address>,
|
||||
* });
|
||||
*
|
||||
* // add the facilitator to any x402 payment middleware
|
||||
* const middleware = paymentMiddleware(
|
||||
* facilitator.address,
|
||||
* {
|
||||
* "/api/paywall": {
|
||||
* price: "$0.01",
|
||||
* network: "base-sepolia",
|
||||
* config: {
|
||||
* description: "Access to paid content",
|
||||
* },
|
||||
* },
|
||||
* },
|
||||
* facilitator,
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* #### Configuration Options
|
||||
*
|
||||
* ```ts
|
||||
* const thirdwebX402Facilitator = createFacilitator({
|
||||
* walletSecret: <your-wallet-secret>,
|
||||
* walletAddress: <your-wallet-address>,
|
||||
* // Optional: Wait behavior for settlements
|
||||
* // - "simulated": Only simulate the transaction (fastest)
|
||||
* // - "submitted": Wait until transaction is submitted
|
||||
* // - "confirmed": Wait for full on-chain confirmation (slowest, default)
|
||||
* waitUntil: "confirmed",
|
||||
* });
|
||||
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
export function createFacilitator(
|
||||
config: ThirdwebX402FacilitatorConfig,
|
||||
): ThirdwebX402Facilitator {
|
||||
if (!config.walletSecret) {
|
||||
throw new Error("Wallet secret is required for the x402 facilitator");
|
||||
}
|
||||
|
||||
if (!config.walletAddress) {
|
||||
throw new Error("Wallet address is required for the x402 facilitator");
|
||||
}
|
||||
|
||||
const BASE_URL = config.baseUrl ?? DEFAULT_BASE_URL;
|
||||
|
||||
const AUTH_HEADERS = {
|
||||
verify: {
|
||||
authorization: `Bearer ${config.walletSecret}`,
|
||||
},
|
||||
settle: {
|
||||
authorization: `Bearer ${config.walletSecret}`,
|
||||
},
|
||||
supported: {
|
||||
authorization: `Bearer ${config.walletSecret}`,
|
||||
},
|
||||
list: {
|
||||
authorization: `Bearer ${config.walletSecret}`,
|
||||
},
|
||||
} as const;
|
||||
|
||||
return {
|
||||
url: BASE_URL as `${string}://${string}`,
|
||||
address: config.walletAddress,
|
||||
createAuthHeaders: async () => AUTH_HEADERS,
|
||||
/**
|
||||
* Verifies a payment payload with the facilitator service
|
||||
*
|
||||
* @param payload - The payment payload to verify
|
||||
* @param paymentRequirements - The payment requirements to verify against
|
||||
* @returns A promise that resolves to the verification response
|
||||
*/
|
||||
async verify(
|
||||
payload: RequestedPaymentPayload,
|
||||
paymentRequirements: RequestedPaymentRequirements,
|
||||
): Promise<FacilitatorVerifyResponse> {
|
||||
let headers = { "Content-Type": "application/json" };
|
||||
|
||||
headers = { ...headers, ...AUTH_HEADERS.verify };
|
||||
|
||||
const res = await fetch(new URL("/verify", BASE_URL), {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: stringify({
|
||||
x402Version: payload.x402Version,
|
||||
paymentPayload: payload,
|
||||
paymentRequirements: paymentRequirements,
|
||||
}),
|
||||
});
|
||||
|
||||
if (res.status !== 200) {
|
||||
const text = `${res.statusText} ${await res.text()}`;
|
||||
throw new Error(`Failed to verify payment: ${res.status} ${text}`);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
return data as VerifyResponse;
|
||||
},
|
||||
|
||||
/**
|
||||
* Settles a payment with the facilitator service
|
||||
*
|
||||
* @param payload - The payment payload to settle
|
||||
* @param paymentRequirements - The payment requirements for the settlement
|
||||
* @returns A promise that resolves to the settlement response
|
||||
*/
|
||||
async settle(
|
||||
payload: RequestedPaymentPayload,
|
||||
paymentRequirements: RequestedPaymentRequirements,
|
||||
waitUntil?: WaitUntil,
|
||||
): Promise<FacilitatorSettleResponse> {
|
||||
let headers = { "Content-Type": "application/json" };
|
||||
headers = { ...headers, ...AUTH_HEADERS.settle };
|
||||
const waitUntilParam = waitUntil || config.waitUntil;
|
||||
|
||||
const res = await fetch(new URL("/settle", BASE_URL), {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: stringify({
|
||||
x402Version: payload.x402Version,
|
||||
paymentPayload: payload,
|
||||
paymentRequirements: paymentRequirements,
|
||||
...(waitUntilParam ? { waitUntil: waitUntilParam } : {}),
|
||||
}),
|
||||
});
|
||||
|
||||
if (res.status !== 200) {
|
||||
const text = `${res.statusText} ${await res.text()}`;
|
||||
throw new Error(`Failed to settle payment: ${res.status} ${text}`);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
return data as FacilitatorSettleResponse;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the supported payment kinds from the facilitator service.
|
||||
*
|
||||
* @returns A promise that resolves to the supported payment kinds
|
||||
*/
|
||||
async supported(filters?: {
|
||||
chainId: number;
|
||||
tokenAddress?: string;
|
||||
}): Promise<FacilitatorSupportedResponse> {
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
...AUTH_HEADERS.supported,
|
||||
};
|
||||
const supportedUrl = new URL("/supported", BASE_URL);
|
||||
if (filters?.chainId) {
|
||||
supportedUrl.searchParams.set("chainId", filters.chainId.toString());
|
||||
}
|
||||
if (filters?.tokenAddress) {
|
||||
supportedUrl.searchParams.set("tokenAddress", filters.tokenAddress);
|
||||
}
|
||||
const res = await fetch(supportedUrl, { headers });
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error(
|
||||
`Failed to get supported payment kinds: ${res.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
return data as FacilitatorSupportedResponse;
|
||||
},
|
||||
} as const satisfies ThirdwebX402Facilitator;
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import {
|
||||
EvmNetworkToChainId,
|
||||
type Network,
|
||||
PaymentPayloadSchema,
|
||||
PaymentRequirementsSchema,
|
||||
SettleResponseSchema,
|
||||
SupportedPaymentKindsResponseSchema,
|
||||
VerifyResponseSchema,
|
||||
} from "x402/types";
|
||||
import { z } from "zod";
|
||||
|
||||
const FacilitatorNetworkSchema = z.string();
|
||||
|
||||
export type FacilitatorNetwork = z.infer<typeof FacilitatorNetworkSchema>;
|
||||
|
||||
export const RequestedPaymentPayloadSchema = PaymentPayloadSchema.extend({
|
||||
network: FacilitatorNetworkSchema,
|
||||
});
|
||||
|
||||
export type RequestedPaymentPayload = z.infer<
|
||||
typeof RequestedPaymentPayloadSchema
|
||||
>;
|
||||
|
||||
const RequestedPaymentRequirementsSchema = PaymentRequirementsSchema.extend({
|
||||
network: FacilitatorNetworkSchema,
|
||||
});
|
||||
|
||||
export type RequestedPaymentRequirements = z.infer<
|
||||
typeof RequestedPaymentRequirementsSchema
|
||||
>;
|
||||
|
||||
const FacilitatorSettleResponseSchema = SettleResponseSchema.extend({
|
||||
network: FacilitatorNetworkSchema,
|
||||
errorMessage: z.string().optional(),
|
||||
});
|
||||
export type FacilitatorSettleResponse = z.infer<
|
||||
typeof FacilitatorSettleResponseSchema
|
||||
>;
|
||||
|
||||
const FacilitatorVerifyResponseSchema = VerifyResponseSchema.extend({
|
||||
errorMessage: z.string().optional(),
|
||||
});
|
||||
|
||||
export type FacilitatorVerifyResponse = z.infer<
|
||||
typeof FacilitatorVerifyResponseSchema
|
||||
>;
|
||||
|
||||
export const SupportedSignatureTypeSchema = z.enum([
|
||||
"TransferWithAuthorization",
|
||||
"Permit",
|
||||
]);
|
||||
|
||||
export const FacilitatorSupportedAssetSchema = z.object({
|
||||
address: z.string(),
|
||||
decimals: z.number(),
|
||||
eip712: z.object({
|
||||
name: z.string(),
|
||||
version: z.string(),
|
||||
primaryType: SupportedSignatureTypeSchema,
|
||||
}),
|
||||
});
|
||||
|
||||
const FacilitatorSupportedResponseSchema =
|
||||
SupportedPaymentKindsResponseSchema.extend({
|
||||
kinds: z.array(
|
||||
z.object({
|
||||
x402Version: z.literal(1),
|
||||
scheme: z.literal("exact"),
|
||||
network: FacilitatorNetworkSchema,
|
||||
extra: z
|
||||
.object({
|
||||
defaultAsset: FacilitatorSupportedAssetSchema.optional(),
|
||||
supportedAssets: z
|
||||
.array(FacilitatorSupportedAssetSchema)
|
||||
.optional(),
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
),
|
||||
}).describe("Supported payment kinds for this facilitator");
|
||||
|
||||
export type FacilitatorSupportedResponse = z.infer<
|
||||
typeof FacilitatorSupportedResponseSchema
|
||||
>;
|
||||
|
||||
export function networkToChainId(network: string): number {
|
||||
if (network.startsWith("eip155:")) {
|
||||
const chainId = parseInt(network.split(":")[1] ?? "0");
|
||||
if (!Number.isNaN(chainId) && chainId > 0) {
|
||||
return chainId;
|
||||
} else {
|
||||
throw new Error(`Invalid network: ${network}`);
|
||||
}
|
||||
}
|
||||
// attempt to parse it as just an integer
|
||||
const maybeChainId = parseInt(network);
|
||||
if (!Number.isNaN(maybeChainId) && maybeChainId > 0) {
|
||||
return maybeChainId;
|
||||
}
|
||||
const mappedChainId = EvmNetworkToChainId.get(network as Network);
|
||||
if (!mappedChainId) {
|
||||
throw new Error(`Invalid network: ${network}`);
|
||||
}
|
||||
// TODO (402): support solana networks
|
||||
if (mappedChainId === 101 || mappedChainId === 103) {
|
||||
throw new Error("Solana networks not supported yet.");
|
||||
}
|
||||
return mappedChainId;
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
import { decodePaymentRequest } from "./common.js";
|
||||
import { safeBase64Encode } from "./encode.js";
|
||||
import {
|
||||
type SettlePaymentArgs,
|
||||
type SettlePaymentResult,
|
||||
x402Version,
|
||||
} from "./types.js";
|
||||
import { stringify } from "./utils.js";
|
||||
|
||||
/**
|
||||
* Verifies and processes X402 payments for protected resources.
|
||||
*
|
||||
* This function implements the X402 payment protocol, verifying payment proofs
|
||||
* and settling payments through a facilitator service. It handles the complete
|
||||
* payment flow from validation to settlement.
|
||||
*
|
||||
* @param args - Configuration object containing payment verification parameters
|
||||
* @returns A promise that resolves to either a successful payment result (200) or payment required error (402)
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ### Next.js API route example
|
||||
*
|
||||
* ```ts
|
||||
* // Usage in a Next.js API route
|
||||
* import { settlePayment, createFacilitator } from "@thirdweb-dev/nexus";
|
||||
* import { arbitrumSepolia } from "thirdweb/chains";
|
||||
*
|
||||
* const facilitator = createFacilitator({
|
||||
* walletSecret: <your-wallet-secret>,
|
||||
* walletAddress: <your-wallet-address>,
|
||||
* });
|
||||
*
|
||||
* export async function GET(request: Request) {
|
||||
* const paymentData = request.headers.get("x-payment");
|
||||
*
|
||||
* // verify and process the payment
|
||||
* const result = await settlePayment({
|
||||
* resourceUrl: "https://api.example.com/premium-content",
|
||||
* method: "GET",
|
||||
* paymentData,
|
||||
* network: arbitrumSepolia, // or any other chain
|
||||
* price: "$0.10", // or { amount: "100000", asset: { address: "0x...", decimals: 6 } }
|
||||
* facilitator,
|
||||
* routeConfig: {
|
||||
* description: "Access to premium API content",
|
||||
* mimeType: "application/json",
|
||||
* maxTimeoutSeconds: 300,
|
||||
* },
|
||||
* });
|
||||
*
|
||||
* if (result.status === 200) {
|
||||
* // Payment verified and settled successfully
|
||||
* return Response.json({ data: "premium content" });
|
||||
* } else {
|
||||
* // Payment required
|
||||
* return Response.json(result.responseBody, {
|
||||
* status: result.status,
|
||||
* headers: result.responseHeaders,
|
||||
* });
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* ### Express middleware example
|
||||
*
|
||||
* ```ts
|
||||
* // Usage in Express middleware
|
||||
* import express from "express";
|
||||
* import { settlePayment, createFacilitator } from "@thirdweb-dev/nexus";
|
||||
* import { arbitrumSepolia } from "thirdweb/chains";
|
||||
*
|
||||
* const facilitator = createFacilitator({
|
||||
* walletSecret: <your-wallet-secret>,
|
||||
* walletAddress: <your-wallet-address>,
|
||||
* });
|
||||
*
|
||||
* const app = express();
|
||||
*
|
||||
* async function paymentMiddleware(req, res, next) {
|
||||
* // verify and process the payment
|
||||
* const result = await settlePayment({
|
||||
* resourceUrl: `${req.protocol}://${req.get('host')}${req.originalUrl}`,
|
||||
* method: req.method,
|
||||
* paymentData: req.headers["x-payment"],
|
||||
* network: arbitrumSepolia, // or any other chain
|
||||
* price: "$0.05",
|
||||
* waitUntil: "submitted",
|
||||
* facilitator,
|
||||
* });
|
||||
*
|
||||
* if (result.status === 200) {
|
||||
* // Set payment receipt headers and continue
|
||||
* Object.entries(result.responseHeaders).forEach(([key, value]) => {
|
||||
* res.setHeader(key, value);
|
||||
* });
|
||||
* next();
|
||||
* } else {
|
||||
* // Return payment required response
|
||||
* res.status(result.status)
|
||||
* .set(result.responseHeaders)
|
||||
* .json(result.responseBody);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* app.get("/api/premium", paymentMiddleware, (req, res) => {
|
||||
* res.json({ message: "This is premium content!" });
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
* @beta
|
||||
*/
|
||||
export async function settlePayment(
|
||||
args: SettlePaymentArgs,
|
||||
): Promise<SettlePaymentResult> {
|
||||
const { routeConfig = {}, facilitator } = args;
|
||||
const { errorMessages } = routeConfig;
|
||||
|
||||
const decodePaymentResult = await decodePaymentRequest(args);
|
||||
|
||||
if (decodePaymentResult.status !== 200) {
|
||||
return decodePaymentResult;
|
||||
}
|
||||
|
||||
const { selectedPaymentRequirements, decodedPayment, paymentRequirements } =
|
||||
decodePaymentResult;
|
||||
|
||||
try {
|
||||
const settlement = await facilitator.settle(
|
||||
decodedPayment,
|
||||
selectedPaymentRequirements,
|
||||
args.waitUntil,
|
||||
);
|
||||
|
||||
if (settlement.success) {
|
||||
return {
|
||||
status: 200,
|
||||
paymentReceipt: settlement,
|
||||
responseHeaders: {
|
||||
"Access-Control-Expose-Headers": "X-PAYMENT-RESPONSE",
|
||||
"X-PAYMENT-RESPONSE": safeBase64Encode(stringify(settlement)),
|
||||
},
|
||||
};
|
||||
} else {
|
||||
const error = settlement.errorReason || "Settlement error";
|
||||
return {
|
||||
status: 402,
|
||||
responseHeaders: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
responseBody: {
|
||||
x402Version,
|
||||
error,
|
||||
errorMessage:
|
||||
errorMessages?.settlementFailed || settlement.errorMessage,
|
||||
accepts: paymentRequirements,
|
||||
},
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
status: 402,
|
||||
responseHeaders: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
responseBody: {
|
||||
x402Version,
|
||||
error: "Settlement error",
|
||||
errorMessage:
|
||||
errorMessages?.settlementFailed ||
|
||||
(error instanceof Error ? error.message : undefined),
|
||||
accepts: paymentRequirements,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import type { Money, PaymentMiddlewareConfig } from "x402/types";
|
||||
import type z from "zod";
|
||||
import type { ThirdwebX402Facilitator, WaitUntil } from "./facilitator.js";
|
||||
import type {
|
||||
FacilitatorNetwork,
|
||||
FacilitatorSettleResponse,
|
||||
FacilitatorSupportedAssetSchema,
|
||||
RequestedPaymentPayload,
|
||||
RequestedPaymentRequirements,
|
||||
SupportedSignatureTypeSchema,
|
||||
} from "./schemas.js";
|
||||
|
||||
export const x402Version = 1;
|
||||
|
||||
/**
|
||||
* Configuration object for verifying or processing X402 payments.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type PaymentArgs = {
|
||||
/** The URL of the resource being protected by the payment */
|
||||
resourceUrl: string;
|
||||
/** The HTTP method used to access the resource */
|
||||
method: "GET" | "POST" | ({} & string);
|
||||
/** The payment data/proof provided by the client, typically from the X-PAYMENT header */
|
||||
paymentData?: string | null;
|
||||
/** The blockchain network where the payment should be processed */
|
||||
network: FacilitatorNetwork;
|
||||
/** The price for accessing the resource - either a USD amount (e.g., "$0.10") or a specific token amount */
|
||||
price: Money | ERC20TokenAmount;
|
||||
/** The payment facilitator instance used to verify and settle payments */
|
||||
facilitator: ThirdwebX402Facilitator;
|
||||
/** Optional configuration for the payment middleware route */
|
||||
routeConfig?: PaymentMiddlewareConfig;
|
||||
/** Optional recipient address to receive the payment if different from your facilitator address */
|
||||
payTo?: string;
|
||||
};
|
||||
|
||||
export type SettlePaymentArgs = PaymentArgs & {
|
||||
waitUntil?: WaitUntil;
|
||||
};
|
||||
|
||||
export type PaymentRequiredResult = {
|
||||
/** HTTP 402 - Payment Required, verification or processing failed or payment missing */
|
||||
status: 402;
|
||||
/** The error response body containing payment requirements */
|
||||
responseBody: {
|
||||
/** The X402 protocol version */
|
||||
x402Version: number;
|
||||
/** error code */
|
||||
error: string;
|
||||
/** Human-readable error message */
|
||||
errorMessage?: string;
|
||||
/** Array of acceptable payment methods and requirements */
|
||||
accepts: RequestedPaymentRequirements[];
|
||||
/** Optional payer address if verification partially succeeded */
|
||||
payer?: string;
|
||||
};
|
||||
/** Response headers for the error response */
|
||||
responseHeaders: Record<string, string>;
|
||||
};
|
||||
|
||||
/**
|
||||
* The result of a payment settlement operation.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type SettlePaymentResult =
|
||||
| {
|
||||
/** HTTP 200 - Payment was successfully processed */
|
||||
status: 200;
|
||||
/** Response headers including payment receipt information */
|
||||
responseHeaders: Record<string, string>;
|
||||
/** The payment receipt from the payment facilitator */
|
||||
paymentReceipt: FacilitatorSettleResponse;
|
||||
}
|
||||
| PaymentRequiredResult;
|
||||
|
||||
/**
|
||||
* The result of a payment verification operation.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type VerifyPaymentResult =
|
||||
| {
|
||||
/** HTTP 200 - Payment was successfully verified */
|
||||
status: 200;
|
||||
decodedPayment: RequestedPaymentPayload;
|
||||
selectedPaymentRequirements: RequestedPaymentRequirements;
|
||||
}
|
||||
| PaymentRequiredResult;
|
||||
|
||||
export type SupportedSignatureType = z.infer<
|
||||
typeof SupportedSignatureTypeSchema
|
||||
>;
|
||||
|
||||
export type ERC20TokenAmount = {
|
||||
amount: string;
|
||||
asset: {
|
||||
address: `0x${string}`;
|
||||
decimals?: number;
|
||||
eip712?: {
|
||||
name: string;
|
||||
version: string;
|
||||
primaryType: SupportedSignatureType;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export type DefaultAsset = z.infer<typeof FacilitatorSupportedAssetSchema>;
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Stringify a JSON object and convert all bigint values to string
|
||||
*
|
||||
* If you are getting this error: "Exception: Do not know how to serialize a BigInt",
|
||||
* you probably can use this function to parse the data.
|
||||
* Because bigint is not an accepted value of the JSON format.
|
||||
*
|
||||
* @returns An object with all bigint values converted to string
|
||||
* @example
|
||||
* ```ts
|
||||
* import { stringify } from "thirdweb/utils";
|
||||
* const obj = { tokenId: 0n };
|
||||
* const str = stringify(obj); // "{"tokenId":"0"}"
|
||||
* ```
|
||||
* @utils
|
||||
*/
|
||||
export function stringify(
|
||||
// biome-ignore lint/suspicious/noExplicitAny: JSON.stringify signature
|
||||
value: any,
|
||||
// biome-ignore lint/suspicious/noExplicitAny: JSON.stringify signature
|
||||
replacer?: ((this: any, key: string, value: any) => any) | null,
|
||||
space?: string | number,
|
||||
) {
|
||||
const res = JSON.stringify(
|
||||
value,
|
||||
(key, value_) => {
|
||||
const value__ = typeof value_ === "bigint" ? value_.toString() : value_;
|
||||
return typeof replacer === "function" ? replacer(key, value__) : value__;
|
||||
},
|
||||
space,
|
||||
);
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string representation of a number with decimal places to a BigInt representation.
|
||||
* @param tokens - The string representation of the number, including the integer and fraction parts.
|
||||
* @param decimals - The number of decimal places to include in the BigInt representation.
|
||||
* @returns The BigInt representation of the number.
|
||||
* @example
|
||||
* ```ts
|
||||
* import { toUnits } from "thirdweb/utils";
|
||||
* toUnits('1', 18)
|
||||
* // 1000000000000000000n
|
||||
* ```
|
||||
* @utils
|
||||
*/
|
||||
export function toUnits(tokens: string, decimals: number): bigint {
|
||||
if (tokens.includes("e")) {
|
||||
tokens = Number(tokens).toFixed(decimals);
|
||||
}
|
||||
|
||||
let [integerPart, fractionPart = ""] = tokens.split(".") as [string, string];
|
||||
const prefix = integerPart.startsWith("-") ? "-" : "";
|
||||
if (prefix) {
|
||||
integerPart = integerPart.slice(1);
|
||||
}
|
||||
|
||||
fractionPart = fractionPart.padEnd(decimals, "0"); // Ensure fraction part is at least 'decimals' long.
|
||||
|
||||
if (decimals === 0) {
|
||||
// Check if there's any fraction part that would necessitate rounding up the integer part.
|
||||
if (fractionPart[0] && Number.parseInt(fractionPart[0]) >= 5) {
|
||||
integerPart = (BigInt(integerPart) + 1n).toString();
|
||||
}
|
||||
fractionPart = ""; // No fraction part is needed when decimals === 0.
|
||||
} else {
|
||||
// When decimals > 0, handle potential rounding based on the digit right after the specified decimal places.
|
||||
if (fractionPart.length > decimals) {
|
||||
const roundingDigit = fractionPart[decimals];
|
||||
if (roundingDigit && Number.parseInt(roundingDigit, 10) >= 5) {
|
||||
// If rounding is needed, add 1 to the last included digit of the fraction part.
|
||||
const roundedFraction =
|
||||
BigInt(fractionPart.substring(0, decimals)) + 1n;
|
||||
fractionPart = roundedFraction.toString().padStart(decimals, "0");
|
||||
|
||||
if (fractionPart.length > decimals) {
|
||||
// If rounding the fraction results in a length increase (e.g., .999 -> 1.000), increment the integer part.
|
||||
integerPart = (BigInt(integerPart) + 1n).toString();
|
||||
// Adjust the fraction part if it's longer than the specified decimals due to rounding up.
|
||||
fractionPart = fractionPart.substring(fractionPart.length - decimals);
|
||||
}
|
||||
} else {
|
||||
// If no rounding is necessary, just truncate the fraction part to the specified number of decimals.
|
||||
fractionPart = fractionPart.substring(0, decimals);
|
||||
}
|
||||
}
|
||||
// If the fraction part is shorter than the specified decimals, it's already handled by padEnd() above.
|
||||
}
|
||||
|
||||
// Combine the integer and fraction parts into the final BigInt representation.
|
||||
return BigInt(`${prefix}${integerPart}${fractionPart}`);
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import { decodePaymentRequest } from "./common.js";
|
||||
import {
|
||||
type PaymentArgs,
|
||||
type VerifyPaymentResult,
|
||||
x402Version,
|
||||
} from "./types.js";
|
||||
|
||||
/**
|
||||
* Verifies X402 payments for protected resources. This function only verifies the payment,
|
||||
* you should use `settlePayment` to settle the payment.
|
||||
*
|
||||
* @param args - Configuration object containing payment verification parameters
|
||||
* @returns A promise that resolves to either a successful verification result (200) or payment required error (402)
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Usage in a Next.js API route
|
||||
* import { verifyPayment, createFacilitator } from "@thirdweb-dev/nexus";
|
||||
* import { arbitrumSepolia } from "thirdweb/chains";
|
||||
*
|
||||
* const facilitator = createFacilitator({
|
||||
* walletSecret: <your-wallet-secret>,
|
||||
* walletAddress: <your-wallet-address>,
|
||||
* });
|
||||
*
|
||||
* export async function GET(request: Request) {
|
||||
* const paymentData = request.headers.get("x-payment");
|
||||
*
|
||||
* const paymentArgs = {
|
||||
* resourceUrl: "https://api.example.com/premium-content",
|
||||
* method: "GET",
|
||||
* paymentData,
|
||||
* network: arbitrumSepolia, // or any other chain
|
||||
* price: "$0.10", // or { amount: "100000", asset: { address: "0x...", decimals: 6 } }
|
||||
* facilitator,
|
||||
* routeConfig: {
|
||||
* description: "Access to premium API content",
|
||||
* mimeType: "application/json",
|
||||
* maxTimeoutSeconds: 300,
|
||||
* },
|
||||
* };
|
||||
*
|
||||
* // verify the payment
|
||||
* const result = await verifyPayment(paymentArgs);
|
||||
*
|
||||
* if (result.status === 200) {
|
||||
* // Payment verified, but not settled yet
|
||||
* // you can do the work that requires payment first
|
||||
* const result = await doSomething();
|
||||
* // then settle the payment
|
||||
* const settleResult = await settlePayment(paymentArgs);
|
||||
*
|
||||
* // then return the result
|
||||
* return Response.json(result);
|
||||
* } else {
|
||||
* // verification failed, return payment required
|
||||
* return Response.json(result.responseBody, {
|
||||
* status: result.status,
|
||||
* headers: result.responseHeaders,
|
||||
* });
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
* @beta
|
||||
*/
|
||||
export async function verifyPayment(
|
||||
args: PaymentArgs,
|
||||
): Promise<VerifyPaymentResult> {
|
||||
const { routeConfig = {}, facilitator } = args;
|
||||
const { errorMessages } = routeConfig;
|
||||
|
||||
const decodePaymentResult = await decodePaymentRequest(args);
|
||||
|
||||
if (decodePaymentResult.status !== 200) {
|
||||
return decodePaymentResult;
|
||||
}
|
||||
|
||||
const { selectedPaymentRequirements, decodedPayment, paymentRequirements } =
|
||||
decodePaymentResult;
|
||||
|
||||
// Verify payment
|
||||
try {
|
||||
const verification = await facilitator.verify(
|
||||
decodedPayment,
|
||||
selectedPaymentRequirements,
|
||||
);
|
||||
|
||||
if (verification.isValid) {
|
||||
return {
|
||||
status: 200,
|
||||
decodedPayment,
|
||||
selectedPaymentRequirements,
|
||||
};
|
||||
} else {
|
||||
const error = verification.invalidReason || "Verification failed";
|
||||
return {
|
||||
status: 402,
|
||||
responseHeaders: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
responseBody: {
|
||||
x402Version,
|
||||
error: error,
|
||||
errorMessage:
|
||||
errorMessages?.verificationFailed || verification.errorMessage,
|
||||
accepts: paymentRequirements,
|
||||
},
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
status: 402,
|
||||
responseHeaders: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
responseBody: {
|
||||
x402Version,
|
||||
error: "Verification error",
|
||||
errorMessage:
|
||||
errorMessages?.verificationFailed ||
|
||||
(error instanceof Error ? error.message : undefined),
|
||||
accepts: paymentRequirements,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
// This tsconfig file contains the shared config for the build (tsconfig.build.json) and type checking (tsconfig.json) config.
|
||||
"compilerOptions": {
|
||||
// Incremental builds
|
||||
// NOTE: Enabling incremental builds speeds up `tsc`. Keep in mind though that it does not reliably bust the cache when the `tsconfig.json` file changes.
|
||||
"allowJs": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"checkJs": false,
|
||||
|
||||
// Interop constraints
|
||||
"esModuleInterop": false,
|
||||
"exactOptionalPropertyTypes": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"importHelpers": true,
|
||||
// Incremental builds
|
||||
// NOTE: Enabling incremental builds speeds up `tsc`. Keep in mind though that it does not reliably bust the cache when the `tsconfig.json` file changes.
|
||||
"incremental": false,
|
||||
|
||||
// jsx for "/react" portion
|
||||
"jsx": "react-jsx",
|
||||
"lib": [
|
||||
"ES2022", // By using ES2022 we get access to the `.cause` property on `Error` instances.
|
||||
"DOM" // We are adding `DOM` here to get the `fetch`, etc. types. This should be removed once these types are available via DefinitelyTyped.
|
||||
],
|
||||
"module": "NodeNext",
|
||||
|
||||
// Language and environment
|
||||
"moduleResolution": "NodeNext",
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitOverride": true,
|
||||
"noImplicitReturns": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
|
||||
// Skip type checking for node modules
|
||||
"skipLibCheck": true,
|
||||
|
||||
// Type checking
|
||||
"strict": true,
|
||||
"target": "ES2021",
|
||||
"useDefineForClassFields": true,
|
||||
"useUnknownInCatchVariables": true,
|
||||
"verbatimModuleSyntax": true
|
||||
},
|
||||
// This tsconfig file contains the shared config for the build (tsconfig.build.json) and type checking (tsconfig.json) config.
|
||||
"include": []
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "./src",
|
||||
"sourceMap": true
|
||||
},
|
||||
"exclude": [
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.test.tsx",
|
||||
"src/**/*.test-d.ts",
|
||||
"src/**/*.bench.ts",
|
||||
"src/**/*.macro.ts"
|
||||
],
|
||||
"extends": "./tsconfig.base.json",
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
// This configuration is used for local development and type checking.
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"outDir": "./dist",
|
||||
"paths": {
|
||||
"~test/*": ["./test/src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": [],
|
||||
"extends": "./tsconfig.base.json",
|
||||
"include": ["src", "test"]
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
"path": "./dist/esm/exports/thirdweb.js"
|
||||
},
|
||||
{
|
||||
"limit": "375 kB",
|
||||
"limit": "380 kB",
|
||||
"name": "thirdweb (cjs)",
|
||||
"path": "./dist/cjs/exports/thirdweb.js"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,35 @@
|
||||
# thirdweb
|
||||
|
||||
## 5.110.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#8334](https://github.com/thirdweb-dev/js/pull/8334) [`6c318f8`](https://github.com/thirdweb-dev/js/commit/6c318f83d63aa8acce46da666e43638e410f799c) Thanks [@kumaryash90](https://github.com/kumaryash90)! - Update implementations
|
||||
|
||||
- [#8306](https://github.com/thirdweb-dev/js/pull/8306) [`759b93b`](https://github.com/thirdweb-dev/js/commit/759b93beff7ab10a3a2fa3b9dd25ca628efb9267) Thanks [@alecananian](https://github.com/alecananian)! - Moved `@storybook/react` package to be a dev dependency
|
||||
|
||||
- [#8332](https://github.com/thirdweb-dev/js/pull/8332) [`8e357b3`](https://github.com/thirdweb-dev/js/commit/8e357b3cb39dcc7a8595a83224098a9c44585389) Thanks [@jnsdls](https://github.com/jnsdls)! - expose some useful erc20 extensions
|
||||
|
||||
## 5.110.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#8327](https://github.com/thirdweb-dev/js/pull/8327) [`c08fb13`](https://github.com/thirdweb-dev/js/commit/c08fb1356e77be9eeb0569905fcb2ab76a516dc3) Thanks [@MananTank](https://github.com/MananTank)! - Fix Safe wallet connection issues with WalletConnect
|
||||
|
||||
- [#8331](https://github.com/thirdweb-dev/js/pull/8331) [`bcbb3f5`](https://github.com/thirdweb-dev/js/commit/bcbb3f540a8eab2b02f9261b779bd2aad3464598) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Loosen network schema
|
||||
|
||||
- [#8325](https://github.com/thirdweb-dev/js/pull/8325) [`e0c8312`](https://github.com/thirdweb-dev/js/commit/e0c83121c201eb2dbdfa6a6b1bbb637f5ad2a21d) Thanks [@Yash094](https://github.com/Yash094)! - Add Arc testnet to chains package
|
||||
|
||||
## 5.110.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#8310](https://github.com/thirdweb-dev/js/pull/8310) [`23cb3bd`](https://github.com/thirdweb-dev/js/commit/23cb3bd98c5390e64f45da5a222042cdc76bd7f4) Thanks [@MananTank](https://github.com/MananTank)! - Minor UI adjustments in CheckoutWidget
|
||||
|
||||
- [#8317](https://github.com/thirdweb-dev/js/pull/8317) [`abf0bba`](https://github.com/thirdweb-dev/js/commit/abf0bbaf359c8de5b0ed0d481e3791c4b7cbf92b) Thanks [@0xFirekeeper](https://github.com/0xFirekeeper)! - updates default x402 transfer with authorization timeout
|
||||
|
||||
- [#8319](https://github.com/thirdweb-dev/js/pull/8319) [`e0abf2b`](https://github.com/thirdweb-dev/js/commit/e0abf2b2738661fda3c69551d78231cfac4b0767) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Update x402 types
|
||||
|
||||
## 5.110.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
"@radix-ui/react-focus-scope": "1.1.7",
|
||||
"@radix-ui/react-icons": "1.3.2",
|
||||
"@radix-ui/react-tooltip": "1.2.7",
|
||||
"@storybook/react": "9.0.15",
|
||||
"@tanstack/react-query": "5.81.5",
|
||||
"@thirdweb-dev/engine": "workspace:*",
|
||||
"@thirdweb-dev/insight": "workspace:*",
|
||||
@@ -40,7 +39,7 @@
|
||||
"toml": "3.0.0",
|
||||
"uqr": "0.1.2",
|
||||
"viem": "2.37.9",
|
||||
"x402": "0.6.1",
|
||||
"x402": "0.7.0",
|
||||
"zod": "3.25.75"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -57,6 +56,7 @@
|
||||
"@storybook/addon-docs": "9.0.15",
|
||||
"@storybook/addon-links": "9.0.15",
|
||||
"@storybook/addon-onboarding": "9.0.15",
|
||||
"@storybook/react": "9.0.15",
|
||||
"@storybook/react-vite": "9.0.15",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
@@ -430,5 +430,5 @@
|
||||
}
|
||||
},
|
||||
"typings": "./dist/types/exports/thirdweb.d.ts",
|
||||
"version": "5.110.3"
|
||||
"version": "5.110.6"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { defineChain } from "../utils.js";
|
||||
|
||||
/**
|
||||
* @chain
|
||||
*/
|
||||
export const arcTestnet = /* @__PURE__ */ defineChain({
|
||||
blockExplorers: [
|
||||
{
|
||||
name: "Arc Testnet Explorer",
|
||||
url: "https://testnet.arcscan.app",
|
||||
},
|
||||
],
|
||||
id: 5042002,
|
||||
name: "Arc Testnet",
|
||||
nativeCurrency: { decimals: 6, name: "USDC", symbol: "USDC" },
|
||||
testnet: true,
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
export const ZKSYNC_IMPLEMENTATIONS: Record<number, Record<string, string>> = {
|
||||
232: {
|
||||
MarketplaceV3: "0x9742f5ac11958cFAd151eBF0Fc31302fA409036E",
|
||||
MarketplaceV3: "0xB7387cF425eC8Da1b04b8E34De86245cb32bCA6D",
|
||||
},
|
||||
300: {
|
||||
MarketplaceV3: "0x58e0F289C7dD2025eBd0696d913ECC0fdc1CC8bc",
|
||||
@@ -18,7 +18,7 @@ export const ZKSYNC_IMPLEMENTATIONS: Record<number, Record<string, string>> = {
|
||||
MarketplaceV3: "0x2dA4Dd326A6482679547071be21f74685d730504",
|
||||
},
|
||||
37111: {
|
||||
MarketplaceV3: "0x56Abb6a3f25DCcdaDa106191053b1CC54C196DEE",
|
||||
MarketplaceV3: "0xF8fd1016420c2B7832714241d36Efe55D41df126",
|
||||
},
|
||||
50104: {
|
||||
MarketplaceV3: "0x292c324920d4120F928dA6f04548442fa10B8B0f",
|
||||
@@ -32,12 +32,12 @@ export const ZKSYNC_IMPLEMENTATIONS: Record<number, Record<string, string>> = {
|
||||
};
|
||||
|
||||
export const ZKSYNC_WETH: Record<number, string> = {
|
||||
232: "0xE5ecd226b3032910CEaa43ba92EE8232f8237553",
|
||||
232: "0x6bDc36E20D267Ff0dd6097799f82e78907105e2F",
|
||||
300: "0x0462C05457Fed440740Ff3696bDd2D0577411e34",
|
||||
324: "0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91",
|
||||
2741: "0x3439153EB7AF838Ad19d56E1571FBD09333C2809",
|
||||
11124: "0x9EDCde0257F2386Ce177C3a7FCdd97787F0D841d",
|
||||
37111: "0xaA91D645D7a6C1aeaa5988e0547267B77d33fe16",
|
||||
37111: "0xeee5a340Cdc9c179Db25dea45AcfD5FE8d4d3eB8",
|
||||
50104: "0x2b1A859dE6A55c553520D7780Bc5805712B128F9",
|
||||
555271: "0xb0b8b267d44c64BA6dD1Daf442949887c85199f6",
|
||||
531050104: "0x9B7EA63EF5062661570afe548e3AD691D5652527",
|
||||
|
||||
@@ -10,6 +10,7 @@ export { anvil } from "../chains/chain-definitions/anvil.js";
|
||||
export { arbitrum } from "../chains/chain-definitions/arbitrum.js";
|
||||
export { arbitrumNova } from "../chains/chain-definitions/arbitrum-nova.js";
|
||||
export { arbitrumSepolia } from "../chains/chain-definitions/arbitrum-sepolia.js";
|
||||
export { arcTestnet } from "../chains/chain-definitions/arc-testnet.js";
|
||||
export { assetChainTestnet } from "../chains/chain-definitions/assetchain-testnet.js";
|
||||
export { astriaEvmDusknet } from "../chains/chain-definitions/astria-evm-dusknet.js";
|
||||
export { avalanche } from "../chains/chain-definitions/avalanche.js";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export type { Abi } from "abitype";
|
||||
|
||||
export { formatCompilerMetadata } from "../contract/actions/compiler-metadata.js";
|
||||
export { getBytecode } from "../contract/actions/get-bytecode.js";
|
||||
export { getCompilerMetadata } from "../contract/actions/get-compiler-metadata.js";
|
||||
|
||||
@@ -17,10 +17,6 @@ export {
|
||||
getActiveClaimConditionId,
|
||||
isGetActiveClaimConditionIdSupported,
|
||||
} from "../../extensions/erc20/__generated__/IDropERC20/read/getActiveClaimConditionId.js";
|
||||
/**
|
||||
* DROPS extension for ERC20
|
||||
*/
|
||||
// READ
|
||||
export {
|
||||
getClaimConditionById,
|
||||
isGetClaimConditionByIdSupported,
|
||||
@@ -43,6 +39,15 @@ export {
|
||||
balanceOf,
|
||||
} from "../../extensions/erc20/__generated__/IERC20/read/balanceOf.js";
|
||||
export { totalSupply } from "../../extensions/erc20/__generated__/IERC20/read/totalSupply.js";
|
||||
/**
|
||||
* PERMIT extension for ERC20
|
||||
*/
|
||||
export { nonces } from "../../extensions/erc20/__generated__/IERC20Permit/read/nonces.js";
|
||||
export { isPermitSupported } from "../../extensions/erc20/__generated__/IERC20Permit/write/permit.js";
|
||||
/**
|
||||
* DROPS extension for ERC20
|
||||
*/
|
||||
// READ
|
||||
export {
|
||||
type TokensMintedEventFilters,
|
||||
tokensMintedEvent,
|
||||
@@ -63,6 +68,7 @@ export {
|
||||
type WithdrawParams,
|
||||
withdraw,
|
||||
} from "../../extensions/erc20/__generated__/IWETH/write/withdraw.js";
|
||||
export { isTransferWithAuthorizationSupported } from "../../extensions/erc20/__generated__/USDC/write/transferWithAuthorization.js";
|
||||
export {
|
||||
type CanClaimParams,
|
||||
type CanClaimResult,
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
export type { Money, PaymentMiddlewareConfig } from "x402/types";
|
||||
export type {
|
||||
HTTPRequestStructure,
|
||||
Money,
|
||||
PaymentMiddlewareConfig,
|
||||
Resource,
|
||||
} from "x402/types";
|
||||
export { decodePayment, encodePayment } from "../x402/encode.js";
|
||||
export {
|
||||
facilitator,
|
||||
|
||||
@@ -93,11 +93,11 @@ export function DirectPayment({
|
||||
>
|
||||
{/* Price section */}
|
||||
<Container
|
||||
center="y"
|
||||
flex="row"
|
||||
gap="3xs"
|
||||
style={{
|
||||
justifyContent: "space-between",
|
||||
alignItems: "end",
|
||||
}}
|
||||
>
|
||||
<FiatValue
|
||||
@@ -108,7 +108,7 @@ export function DirectPayment({
|
||||
size="xl"
|
||||
token={paymentInfo.token}
|
||||
tokenAmount={paymentInfo.amount}
|
||||
weight={600}
|
||||
weight={500}
|
||||
/>
|
||||
<Container flex="row" gap="3xs">
|
||||
<Text
|
||||
@@ -118,6 +118,9 @@ export function DirectPayment({
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: "0.07em",
|
||||
opacity: 0.7,
|
||||
}}
|
||||
>
|
||||
One-time payment
|
||||
@@ -125,11 +128,11 @@ export function DirectPayment({
|
||||
</Container>
|
||||
</Container>
|
||||
|
||||
<Spacer y="md" />
|
||||
<Spacer y="md+" />
|
||||
|
||||
<Line />
|
||||
<Line dashed />
|
||||
|
||||
<Spacer y="md" />
|
||||
<Spacer y="md+" />
|
||||
|
||||
<Container
|
||||
flex="row"
|
||||
@@ -141,18 +144,12 @@ export function DirectPayment({
|
||||
<Text color="secondaryText" size="sm">
|
||||
Price
|
||||
</Text>
|
||||
<Text
|
||||
color="primaryText"
|
||||
size="sm"
|
||||
style={{
|
||||
fontFamily: "monospace",
|
||||
}}
|
||||
>
|
||||
<Text color="primaryText" size="sm">
|
||||
{`${paymentInfo.amount} ${paymentInfo.token.symbol}`}
|
||||
</Text>
|
||||
</Container>
|
||||
|
||||
<Spacer y="xs" />
|
||||
<Spacer y="sm" />
|
||||
|
||||
{/* Network section */}
|
||||
<Container
|
||||
@@ -173,18 +170,15 @@ export function DirectPayment({
|
||||
color="primaryText"
|
||||
short
|
||||
size="sm"
|
||||
style={{
|
||||
fontFamily: "monospace",
|
||||
}}
|
||||
/>
|
||||
</Container>
|
||||
</Container>
|
||||
|
||||
<Spacer y="md" />
|
||||
<Spacer y="md+" />
|
||||
|
||||
<Line />
|
||||
<Line dashed />
|
||||
|
||||
<Spacer y="lg" />
|
||||
<Spacer y="md+" />
|
||||
|
||||
{/* Action button */}
|
||||
<Container flex="column">
|
||||
@@ -194,11 +188,11 @@ export function DirectPayment({
|
||||
|
||||
{showThirdwebBranding ? (
|
||||
<div>
|
||||
<Spacer y="md" />
|
||||
<Spacer y="md+" />
|
||||
<PoweredByThirdweb />
|
||||
</div>
|
||||
) : null}
|
||||
<Spacer y="md" />
|
||||
<Spacer y="md+" />
|
||||
</Container>
|
||||
</WithHeader>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ThirdwebClient } from "../../../../../client/client.js";
|
||||
import { resolveScheme } from "../../../../../utils/ipfs.js";
|
||||
import { useCustomTheme } from "../../../../core/design-system/CustomThemeProvider.js";
|
||||
import { radius } from "../../../../core/design-system/index.js";
|
||||
import { radius, spacing } from "../../../../core/design-system/index.js";
|
||||
import { Container } from "../../components/basic.js";
|
||||
import { Spacer } from "../../components/Spacer.js";
|
||||
import { Text } from "../../components/text.js";
|
||||
@@ -20,6 +20,7 @@ export function WithHeader(props: {
|
||||
{/* image */}
|
||||
{props.image && (
|
||||
<div
|
||||
className="tw-header-image"
|
||||
style={{
|
||||
aspectRatio: "16/9",
|
||||
backgroundColor: theme.colors.tertiaryBg,
|
||||
@@ -27,6 +28,7 @@ export function WithHeader(props: {
|
||||
backgroundPosition: "center",
|
||||
backgroundSize: "cover",
|
||||
borderRadius: `${radius.md} ${radius.md} 0 0`,
|
||||
marginBottom: spacing.xxs,
|
||||
overflow: "hidden",
|
||||
width: "100%",
|
||||
}}
|
||||
@@ -40,7 +42,7 @@ export function WithHeader(props: {
|
||||
<>
|
||||
{/* title */}
|
||||
{props.title && (
|
||||
<Text color="primaryText" size="lg" weight={600}>
|
||||
<Text color="primaryText" size="lg" weight={500} trackingTight>
|
||||
{props.title}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
@@ -45,7 +45,7 @@ export function FiatValue(
|
||||
}
|
||||
|
||||
return cryptoToFiatQuery.data?.result ? (
|
||||
<Text {...props}>
|
||||
<Text {...props} trackingTight>
|
||||
{formatCurrencyAmount(
|
||||
props.currency || "USD",
|
||||
cryptoToFiatQuery.data.result,
|
||||
|
||||
@@ -49,5 +49,7 @@ function formatMoney(
|
||||
return new Intl.NumberFormat(locale, {
|
||||
style: "currency",
|
||||
currency: currencyCode,
|
||||
maximumFractionDigits: 5,
|
||||
minimumFractionDigits: 0,
|
||||
}).format(value);
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ export async function connectWC(
|
||||
);
|
||||
const currentChainId = chainsToRequest[0]?.split(":")[1] || 1;
|
||||
const providerChainId = normalizeChainId(currentChainId);
|
||||
const account = firstAccountOn(provider.session, `eip155:1`); // grab the address from mainnet
|
||||
const account = firstAccountOn(provider.session, `eip155:1`); // grab the address from mainnet if available
|
||||
const address = account;
|
||||
if (!address) {
|
||||
throw new Error("No accounts found on provider.");
|
||||
@@ -291,7 +291,8 @@ function hasChainEnabled(session: WCSession, caip: string) {
|
||||
}
|
||||
function firstAccountOn(session: WCSession, caip: string): string | null {
|
||||
const ns = getNS(session);
|
||||
const hit = ns?.accounts?.find((a) => a.startsWith(`${caip}:`));
|
||||
const hit =
|
||||
ns?.accounts?.find((a) => a.startsWith(`${caip}:`)) || ns?.accounts[0];
|
||||
return hit ? (hit.split(":")[2] ?? null) : null;
|
||||
}
|
||||
function anyRoutableChain(session: WCSession): string | null {
|
||||
|
||||
@@ -107,7 +107,7 @@ export async function decodePaymentRequest(
|
||||
description: description ?? "",
|
||||
mimeType: mimeType ?? "application/json",
|
||||
payTo: getAddress(facilitator.address), // always pay to the facilitator address first
|
||||
maxTimeoutSeconds: maxTimeoutSeconds ?? 300,
|
||||
maxTimeoutSeconds: maxTimeoutSeconds ?? 86400,
|
||||
asset: getAddress(asset.address),
|
||||
outputSchema: {
|
||||
input: {
|
||||
|
||||
@@ -11,20 +11,7 @@ import {
|
||||
import { z } from "zod";
|
||||
import type { Chain } from "../chains/types.js";
|
||||
|
||||
const FacilitatorNetworkSchema = z.union([
|
||||
z.literal("base-sepolia"),
|
||||
z.literal("base"),
|
||||
z.literal("avalanche-fuji"),
|
||||
z.literal("avalanche"),
|
||||
z.literal("iotex"),
|
||||
z.literal("solana-devnet"),
|
||||
z.literal("solana"),
|
||||
z.literal("sei"),
|
||||
z.literal("sei-testnet"),
|
||||
z.string().refine((value) => value.startsWith("eip155:"), {
|
||||
message: "Invalid network",
|
||||
}),
|
||||
]);
|
||||
const FacilitatorNetworkSchema = z.string();
|
||||
|
||||
export type FacilitatorNetwork = z.infer<typeof FacilitatorNetworkSchema>;
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ function preparePaymentHeader(
|
||||
nonce: Hex,
|
||||
): UnsignedPaymentPayload {
|
||||
const validAfter = BigInt(
|
||||
Math.floor(Date.now() / 1000) - 600, // 10 minutes before
|
||||
Math.floor(Date.now() / 1000) - 86400, // 24h before in case weird block timestamp behavior
|
||||
).toString();
|
||||
const validBefore = BigInt(
|
||||
Math.floor(Date.now() / 1000 + paymentRequirements.maxTimeoutSeconds),
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @thirdweb-dev/wagmi-adapter
|
||||
|
||||
## 0.2.174
|
||||
|
||||
## 0.2.173
|
||||
|
||||
## 0.2.172
|
||||
|
||||
## 0.2.171
|
||||
|
||||
## 0.2.170
|
||||
|
||||
@@ -56,5 +56,5 @@
|
||||
"type": "module",
|
||||
"types": "./dist/types/exports/thirdweb.d.ts",
|
||||
"typings": "./dist/types/exports/thirdweb.d.ts",
|
||||
"version": "0.2.171"
|
||||
"version": "0.2.174"
|
||||
}
|
||||
|
||||
Generated
+1568
-1768
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -96,7 +96,7 @@
|
||||
"thirdweb-dashboard#dev": {
|
||||
"dependsOn": ["^build"]
|
||||
},
|
||||
"thirdweb#update-version": {
|
||||
"update-version": {
|
||||
"inputs": ["$TURBO_DEFAULT$", "package.json"],
|
||||
"outputs": ["src/version.ts"]
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user