Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/dirty-experts-kiss.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"thirdweb": patch
---

Improve token info discovery for x402 payments
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
"use client";

import type React from "react";
import { useId, useState } from "react";
import { defineChain } from "thirdweb/chains";
import { BridgeNetworkSelector } from "@/components/blocks/NetworkSelectors";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { TokenSelector } from "@/components/ui/TokenSelector";
import { THIRDWEB_CLIENT } from "@/lib/client";
import type { TokenMetadata } from "@/lib/types";
import type { X402PlaygroundOptions } from "./types";

export function X402LeftSection(props: {
options: X402PlaygroundOptions;
setOptions: React.Dispatch<React.SetStateAction<X402PlaygroundOptions>>;
}) {
const { options, setOptions } = props;

// Local state for chain and token selection
const [selectedChain, setSelectedChain] = useState<number | undefined>(() => {
return options.chain?.id;
});

const [selectedToken, setSelectedToken] = useState<
{ chainId: number; address: string } | undefined
>(() => {
if (options.tokenAddress && options.chain?.id) {
return {
address: options.tokenAddress,
chainId: options.chain.id,
};
}
return undefined;
});

const chainId = useId();
const tokenId = useId();
const amountId = useId();
const payToId = useId();

const handleChainChange = (chainId: number) => {
setSelectedChain(chainId);
// Clear token selection when chain changes
setSelectedToken(undefined);

setOptions((v) => ({
...v,
chain: defineChain(chainId),
tokenAddress: "0x0000000000000000000000000000000000000000" as const,
tokenSymbol: "",
tokenDecimals: 18,
}));
};

const handleTokenChange = (token: TokenMetadata) => {
setSelectedToken({
address: token.address,
chainId: selectedChain!,
});

setOptions((v) => ({
...v,
tokenAddress: token.address as `0x${string}`,
tokenSymbol: token.symbol ?? "",
tokenDecimals: token.decimals ?? 18,
}));
};

const handleAmountChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setOptions((v) => ({
...v,
amount: e.target.value,
}));
};

const handlePayToChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setOptions((v) => ({
...v,
payTo: e.target.value as `0x${string}`,
}));
};

return (
<div className="space-y-6">
<div>
<h2 className="mb-4 text-xl font-semibold">Configuration</h2>
<div className="space-y-4">
{/* Chain selection */}
<div className="flex flex-col gap-2">
<Label htmlFor={chainId}>Chain</Label>
<BridgeNetworkSelector
chainId={selectedChain}
onChange={handleChainChange}
placeholder="Select a chain"
className="bg-card"
/>
</div>

{/* Token selection - only show if chain is selected */}
{selectedChain && (
<div className="flex flex-col gap-2">
<Label htmlFor={tokenId}>Token</Label>
<TokenSelector
chainId={selectedChain}
client={THIRDWEB_CLIENT}
enabled={true}
onChange={handleTokenChange}
placeholder="Select a token"
selectedToken={selectedToken}
className="bg-card"
/>
</div>
)}

{/* Amount input */}
<div className="flex flex-col gap-2">
<Label htmlFor={amountId}>Amount</Label>
<Input
id={amountId}
type="text"
placeholder="0.01"
value={options.amount}
onChange={handleAmountChange}
className="bg-card"
/>
{options.tokenSymbol && (
<p className="text-sm text-muted-foreground">
Amount in {options.tokenSymbol}
</p>
)}
</div>

{/* Pay To input */}
<div className="flex flex-col gap-2">
<Label htmlFor={payToId}>Pay To Address</Label>
<Input
id={payToId}
type="text"
placeholder="0x..."
value={options.payTo}
onChange={handlePayToChange}
className="bg-card"
/>
<p className="text-sm text-muted-foreground">
The wallet address that will receive the payment
</p>
</div>
</div>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"use client";

import React, { useState } from "react";
import { useActiveAccount } from "thirdweb/react";
import { chain, token } from "./constants";
import type { X402PlaygroundOptions } from "./types";
import { X402LeftSection } from "./X402LeftSection";
import { X402RightSection } from "./X402RightSection";

const defaultOptions: X402PlaygroundOptions = {
chain: chain,
tokenAddress: token.address as `0x${string}`,
tokenSymbol: token.symbol,
tokenDecimals: token.decimals,
amount: "0.01",
payTo: "0x0000000000000000000000000000000000000000",
};

export function X402Playground() {
const [options, setOptions] = useState<X402PlaygroundOptions>(defaultOptions);
const activeAccount = useActiveAccount();

// Update payTo address when wallet connects, but only if it's still the default
React.useEffect(() => {
if (
activeAccount?.address &&
options.payTo === "0x0000000000000000000000000000000000000000"
) {
setOptions((prev) => ({
...prev,
payTo: activeAccount.address as `0x${string}`,
}));
}
}, [activeAccount?.address, options.payTo]);

return (
<div className="relative flex flex-col-reverse gap-6 xl:min-h-[900px] xl:flex-row xl:gap-6">
<div className="grow border-b pb-10 xl:mb-0 xl:border-r xl:border-b-0 xl:pr-6">
<X402LeftSection options={options} setOptions={setOptions} />
</div>
<X402RightSection options={options} />
</div>
);
}
Loading
Loading