Skip to content

Commit a1e06b2

Browse files
fixes
1 parent 2461aa4 commit a1e06b2

File tree

11 files changed

+190
-48
lines changed

11 files changed

+190
-48
lines changed

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/transactions/server-wallets/wallet-table/wallet-table-ui.client.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ export function ServerWalletsTableUI({
215215
<PaginationContent>
216216
<PaginationItem>
217217
<Link
218-
href={`/team/${teamSlug}/${project.slug}/transactions/server-wallets?page=${
218+
href={`/team/${teamSlug}/${project.slug}/transactions?page=${
219219
currentPage > 1 ? currentPage - 1 : 1
220220
}`}
221221
legacyBehavior
@@ -232,7 +232,7 @@ export function ServerWalletsTableUI({
232232
(pageNumber) => (
233233
<PaginationItem key={`page-${pageNumber}`}>
234234
<Link
235-
href={`/team/${teamSlug}/${project.slug}/transactions/server-wallets?page=${pageNumber}`}
235+
href={`/team/${teamSlug}/${project.slug}/transactions?page=${pageNumber}`}
236236
passHref
237237
>
238238
<PaginationLink isActive={currentPage === pageNumber}>
@@ -244,7 +244,7 @@ export function ServerWalletsTableUI({
244244
)}
245245
<PaginationItem>
246246
<Link
247-
href={`/team/${teamSlug}/${project.slug}/transactions/server-wallets?page=${
247+
href={`/team/${teamSlug}/${project.slug}/transactions?page=${
248248
currentPage < totalPages ? currentPage + 1 : totalPages
249249
}`}
250250
passHref

apps/playground-web/src/app/api/paywall/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ export const maxDuration = 300;
55
export async function GET(_req: Request) {
66
return NextResponse.json({
77
success: true,
8-
message: "Congratulations! You have accessed the protected route.",
8+
message: "Payment successful. You have accessed the protected route.",
99
});
1010
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// const chain = arbitrumSepolia;
2+
3+
import { arbitrumSepolia } from "thirdweb/chains";
4+
import { getDefaultToken } from "thirdweb/react";
5+
6+
export const chain = arbitrumSepolia;
7+
export const token = getDefaultToken(chain, "USDC")!;
8+
// export const chain = base;
9+
// export const token = {
10+
// address: "0x0578d8A44db98B23BF096A382e016e29a5Ce0ffe",
11+
// decimals: 18,
12+
// name: "Higher",
13+
// symbol: "HIGHER",
14+
// version: "1",
15+
// };
16+
// export const token = {
17+
// address: "0xfdcC3dd6671eaB0709A4C0f3F53De9a333d80798",
18+
// decimals: 18,
19+
// name: "Stable Coin",
20+
// symbol: "SBC",
21+
// version: "1",
22+
// // primaryType: "Permit",
23+
// }
24+
// export const chain = defineChain(3338);
25+
// export const token = {
26+
// address: "0xbbA60da06c2c5424f03f7434542280FCAd453d10",
27+
// decimals: 6,
28+
// name: "USDC",
29+
// symbol: "USDC",
30+
// version: "2",
31+
// }

apps/playground-web/src/app/payments/x402/components/x402-client-preview.tsx

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,19 @@
11
"use client";
22

33
import { useMutation } from "@tanstack/react-query";
4+
import { Badge } from "@workspace/ui/components/badge";
45
import { CodeClient } from "@workspace/ui/components/code/code.client";
56
import { CodeIcon, LockIcon } from "lucide-react";
6-
import { arbitrumSepolia } from "thirdweb/chains";
77
import {
88
ConnectButton,
9-
getDefaultToken,
109
useActiveAccount,
1110
useActiveWallet,
1211
} from "thirdweb/react";
1312
import { wrapFetchWithPayment } from "thirdweb/x402";
1413
import { Button } from "@/components/ui/button";
1514
import { Card } from "@/components/ui/card";
1615
import { THIRDWEB_CLIENT } from "../../../../lib/client";
17-
18-
const chain = arbitrumSepolia;
19-
const token = getDefaultToken(chain, "USDC");
16+
import { chain, token } from "./constants";
2017

2118
export function X402ClientPreview() {
2219
const activeWallet = useActiveWallet();
@@ -30,8 +27,21 @@ export function X402ClientPreview() {
3027
fetch,
3128
THIRDWEB_CLIENT,
3229
activeWallet,
30+
BigInt(1 * 10 ** 18),
3331
);
34-
const response = await fetchWithPay("/api/paywall");
32+
const searchParams = new URLSearchParams();
33+
searchParams.set("chainId", chain.id.toString());
34+
searchParams.set("payTo", activeWallet.getAccount()?.address || "");
35+
// TODO (402): dynamic from playground config
36+
// if (token) {
37+
// searchParams.set("amount", "0.01");
38+
// searchParams.set("tokenAddress", token.address);
39+
// searchParams.set("decimals", token.decimals.toString());
40+
// }
41+
const url =
42+
"/api/paywall" +
43+
(searchParams.size > 0 ? "?" + searchParams.toString() : "");
44+
const response = await fetchWithPay(url.toString());
3545
return response.json();
3646
},
3747
});
@@ -47,18 +57,20 @@ export function X402ClientPreview() {
4757
chain={chain}
4858
detailsButton={{
4959
displayBalanceToken: {
50-
[chain.id]: token!.address,
60+
[chain.id]: token.address,
5161
},
5262
}}
5363
supportedTokens={{
54-
[chain.id]: [token!],
64+
[chain.id]: [token],
5565
}}
5666
/>
5767
<Card className="p-6">
5868
<div className="flex items-center gap-3 mb-4">
5969
<LockIcon className="w-5 h-5 text-muted-foreground" />
6070
<span className="text-lg font-medium">Paid API Call</span>
61-
<span className="text-xl font-bold text-red-600">$0.01</span>
71+
<Badge variant="success">
72+
<span className="text-xl font-bold">0.1 {token.symbol}</span>
73+
</Badge>
6274
</div>
6375

6476
<Button
@@ -67,19 +79,25 @@ export function X402ClientPreview() {
6779
size="lg"
6880
disabled={paidApiCall.isPending || !activeAccount}
6981
>
70-
Pay Now
82+
Access Premium Content
7183
</Button>
7284
<p className="text-sm text-muted-foreground">
73-
{" "}
74-
<a
75-
className="underline"
76-
href={"https://faucet.circle.com/"}
77-
target="_blank"
78-
rel="noopener noreferrer"
79-
>
80-
Click here to get USDC on {chain.name}
81-
</a>
85+
Pay for access with {token.symbol} on{" "}
86+
{chain.name || `chain ${chain.id}`}
8287
</p>
88+
{chain.testnet && token.symbol.toLowerCase() === "usdc" && (
89+
<p className="text-sm text-muted-foreground">
90+
{" "}
91+
<a
92+
className="underline"
93+
href={"https://faucet.circle.com/"}
94+
target="_blank"
95+
rel="noopener noreferrer"
96+
>
97+
Click here to get testnet {token.symbol} on {chain.name}
98+
</a>
99+
</p>
100+
)}
83101
</Card>
84102
<Card className="p-6">
85103
<div className="flex items-center gap-3 mb-2">

apps/playground-web/src/middleware.ts

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
import { type NextRequest, NextResponse } from "next/server";
2-
import { createThirdwebClient } from "thirdweb";
3-
import { arbitrumSepolia } from "thirdweb/chains";
2+
import { createThirdwebClient, defineChain } from "thirdweb";
43
import { facilitator, settlePayment } from "thirdweb/x402";
54

65
const client = createThirdwebClient({
76
secretKey: process.env.THIRDWEB_SECRET_KEY as string,
87
});
98

10-
const chain = arbitrumSepolia;
119
const BACKEND_WALLET_ADDRESS = process.env.ENGINE_BACKEND_WALLET as string;
10+
// const BACKEND_WALLET_ADDRESS = process.env.ENGINE_BACKEND_SMART_WALLET as string;
1211
const ENGINE_VAULT_ACCESS_TOKEN = process.env
1312
.ENGINE_VAULT_ACCESS_TOKEN as string;
1413
const API_URL = `https://${process.env.NEXT_PUBLIC_API_URL || "api.thirdweb.com"}`;
15-
1614
const twFacilitator = facilitator({
1715
baseUrl: `${API_URL}/v1/payments/x402`,
1816
client,
@@ -25,14 +23,41 @@ export async function middleware(request: NextRequest) {
2523
const method = request.method.toUpperCase();
2624
const resourceUrl = `${request.nextUrl.protocol}//${request.nextUrl.host}${pathname}`;
2725
const paymentData = request.headers.get("X-PAYMENT");
26+
const queryParams = request.nextUrl.searchParams;
27+
28+
const chainId = queryParams.get("chainId");
29+
const payTo = queryParams.get("payTo");
30+
31+
if (!chainId || !payTo) {
32+
return NextResponse.json(
33+
{ error: "Missing required parameters" },
34+
{ status: 400 },
35+
);
36+
}
37+
38+
// TODO (402): dynamic from playground config
39+
// const amount = queryParams.get("amount");
40+
// const tokenAddress = queryParams.get("tokenAddress");
41+
// const decimals = queryParams.get("decimals");
2842

2943
const result = await settlePayment({
3044
resourceUrl,
3145
method,
3246
paymentData,
33-
payTo: "0xdd99b75f095d0c4d5112aCe938e4e6ed962fb024",
34-
network: chain,
47+
payTo: payTo as `0x${string}`,
48+
network: defineChain(Number(chainId)),
3549
price: "$0.01",
50+
// price: {
51+
// amount: toUnits(amount as string, parseInt(decimals as string)).toString(),
52+
// asset: {
53+
// address: tokenAddress as `0x${string}`,
54+
// decimals: decimals ? parseInt(decimals) : token.decimals,
55+
// eip712: {
56+
// name: token.name,
57+
// version: token.version,
58+
// },
59+
// },
60+
// },
3661
routeConfig: {
3762
description: "Access to paid content",
3863
},

packages/thirdweb/src/exports/x402.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export { decodePayment, encodePayment } from "../x402/encode.js";
22
export {
33
facilitator,
4+
type ThirdwebX402Facilitator,
45
type ThirdwebX402FacilitatorConfig,
56
} from "../x402/facilitator.js";
67
export { wrapFetchWithPayment } from "../x402/fetchWithPayment.js";

packages/thirdweb/src/x402/common.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Abi } from "abitype";
22
import { toFunctionSelector } from "viem/utils";
3-
import { type ERC20TokenAmount, type Money, moneySchema } from "x402/types";
3+
import { type Money, moneySchema } from "x402/types";
44
import { getCachedChain } from "../chains/utils.js";
55
import type { ThirdwebClient } from "../client/client.js";
66
import { resolveContractAbi } from "../contract/actions/resolve-abi.js";
@@ -16,6 +16,7 @@ import {
1616
type RequestedPaymentRequirements,
1717
} from "./schemas.js";
1818
import {
19+
type ERC20TokenAmount,
1920
type PaymentArgs,
2021
type PaymentRequiredResult,
2122
x402Version,
@@ -116,7 +117,7 @@ export async function decodePaymentRequest(
116117
},
117118
extra: {
118119
facilitatorAddress: facilitator.address,
119-
...(asset as ERC20TokenAmount["asset"]).eip712,
120+
...((asset as ERC20TokenAmount["asset"]).eip712 ?? {}),
120121
},
121122
});
122123

@@ -247,15 +248,33 @@ async function getDefaultAsset(
247248
}
248249

249250
export type SupportedAuthorizationMethods = {
250-
hasPermit: boolean;
251-
hasTransferWithAuthorization: boolean;
251+
usePermit: boolean;
252+
useTransferWithAuthorization: boolean;
252253
};
253254

254255
export async function detectSupportedAuthorizationMethods(args: {
255256
client: ThirdwebClient;
256257
asset: string;
257258
chainId: number;
259+
eip712Extras: ERC20TokenAmount["asset"]["eip712"] | undefined;
258260
}): Promise<SupportedAuthorizationMethods> {
261+
const primaryType = args.eip712Extras?.primaryType;
262+
263+
if (primaryType === "Permit") {
264+
return {
265+
usePermit: true,
266+
useTransferWithAuthorization: false,
267+
};
268+
}
269+
270+
if (primaryType === "TransferWithAuthorization") {
271+
return {
272+
usePermit: false,
273+
useTransferWithAuthorization: true,
274+
};
275+
}
276+
277+
// not specified, so we need to detect it
259278
const abi = await resolveContractAbi<Abi>(
260279
getContract({
261280
client: args.client,
@@ -274,7 +293,7 @@ export async function detectSupportedAuthorizationMethods(args: {
274293
isTransferWithAuthorizationSupported(selectors);
275294

276295
return {
277-
hasPermit,
278-
hasTransferWithAuthorization,
296+
usePermit: hasPermit && !hasTransferWithAuthorization, // only use permit if transfer with authorization is unsupported
297+
useTransferWithAuthorization: hasTransferWithAuthorization,
279298
};
280299
}

packages/thirdweb/src/x402/facilitator.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,26 @@ export type ThirdwebX402FacilitatorConfig = {
1515
baseUrl?: string;
1616
};
1717

18+
export type ThirdwebX402Facilitator = {
19+
url: `${string}://${string}`;
20+
address: string;
21+
createAuthHeaders: () => Promise<{
22+
verify: Record<string, string>;
23+
settle: Record<string, string>;
24+
supported: Record<string, string>;
25+
list: Record<string, string>;
26+
}>;
27+
verify: (
28+
payload: RequestedPaymentPayload,
29+
paymentRequirements: RequestedPaymentRequirements,
30+
) => Promise<VerifyResponse>;
31+
settle: (
32+
payload: RequestedPaymentPayload,
33+
paymentRequirements: RequestedPaymentRequirements,
34+
) => Promise<FacilitatorSettleResponse>;
35+
supported: () => Promise<SupportedPaymentKindsResponse>;
36+
};
37+
1838
const DEFAULT_BASE_URL = "https://api.thirdweb.com/v1/payments/x402";
1939

2040
/**
@@ -56,7 +76,9 @@ const DEFAULT_BASE_URL = "https://api.thirdweb.com/v1/payments/x402";
5676
*
5777
* @bridge x402
5878
*/
59-
export function facilitator(config: ThirdwebX402FacilitatorConfig) {
79+
export function facilitator(
80+
config: ThirdwebX402FacilitatorConfig,
81+
): ThirdwebX402Facilitator {
6082
const secretKey = config.client.secretKey;
6183
if (!secretKey) {
6284
throw new Error("Client secret key is required for the x402 facilitator");

packages/thirdweb/src/x402/fetchWithPayment.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ export function wrapFetchWithPayment(
106106
client,
107107
account,
108108
selectedPaymentRequirements,
109+
x402Version,
109110
);
110111

111112
const initParams = init || {};

0 commit comments

Comments
 (0)