11import type { Abi } from "abitype" ;
22import { toFunctionSelector } from "viem/utils" ;
3- import { ChainIdToNetwork , type Money , moneySchema } from "x402/types" ;
43import { getCachedChain } from "../chains/utils.js" ;
54import type { ThirdwebClient } from "../client/client.js" ;
65import { resolveContractAbi } from "../contract/actions/resolve-abi.js" ;
76import { getContract } from "../contract/contract.js" ;
87import { isPermitSupported } from "../extensions/erc20/__generated__/IERC20Permit/write/permit.js" ;
98import { isTransferWithAuthorizationSupported } from "../extensions/erc20/__generated__/USDC/write/transferWithAuthorization.js" ;
10- import { getAddress } from "../utils/address.js" ;
11- import { toUnits } from "../utils/units.js" ;
129import { decodePayment } from "./encode.js" ;
13- import type { ThirdwebX402Facilitator } from "./facilitator.js" ;
1410import {
15- networkToChainId ,
11+ networkToCaip2ChainId ,
1612 type RequestedPaymentPayload ,
1713 type RequestedPaymentRequirements ,
1814} from "./schemas.js" ;
1915import {
20- type DefaultAsset ,
2116 type ERC20TokenAmount ,
2217 type PaymentArgs ,
2318 type PaymentRequiredResult ,
@@ -50,95 +45,24 @@ export async function decodePaymentRequest(
5045 method,
5146 paymentData,
5247 } = args ;
53- const {
54- description,
55- mimeType,
56- maxTimeoutSeconds,
57- inputSchema,
58- outputSchema,
59- errorMessages,
60- discoverable,
61- } = routeConfig ;
48+ const { errorMessages } = routeConfig ;
6249
63- let chainId : number ;
64- try {
65- chainId = networkToChainId ( network ) ;
66- } catch ( error ) {
67- return {
68- status : 402 ,
69- responseHeaders : { "Content-Type" : "application/json" } ,
70- responseBody : {
71- x402Version,
72- error :
73- error instanceof Error
74- ? error . message
75- : `Invalid network: ${ network } ` ,
76- accepts : [ ] ,
77- } ,
78- } ;
79- }
80-
81- const atomicAmountForAsset = await processPriceToAtomicAmount (
50+ const paymentRequirementsResult = await facilitator . accepts ( {
51+ resourceUrl,
52+ method,
53+ network,
8254 price,
83- chainId ,
84- facilitator ,
85- ) ;
86- if ( "error" in atomicAmountForAsset ) {
87- return {
88- status : 402 ,
89- responseHeaders : { "Content-Type" : "application/json" } ,
90- responseBody : {
91- x402Version,
92- error : atomicAmountForAsset . error ,
93- accepts : [ ] ,
94- } ,
95- } ;
96- }
97- const { maxAmountRequired, asset } = atomicAmountForAsset ;
98-
99- const paymentRequirements : RequestedPaymentRequirements [ ] = [ ] ;
100-
101- const mappedNetwork = ChainIdToNetwork [ chainId ] ;
102- paymentRequirements . push ( {
103- scheme : "exact" ,
104- network : mappedNetwork ? mappedNetwork : `eip155:${ chainId } ` ,
105- maxAmountRequired,
106- resource : resourceUrl ,
107- description : description ?? "" ,
108- mimeType : mimeType ?? "application/json" ,
109- payTo : getAddress ( facilitator . address ) , // always pay to the facilitator address first
110- maxTimeoutSeconds : maxTimeoutSeconds ?? 86400 ,
111- asset : getAddress ( asset . address ) ,
112- outputSchema : {
113- input : {
114- type : "http" ,
115- method,
116- discoverable : discoverable ?? true ,
117- ...inputSchema ,
118- } ,
119- output : outputSchema ,
120- } ,
121- extra : {
122- recipientAddress : payTo , // input payTo is the final recipient address
123- ...( ( asset as ERC20TokenAmount [ "asset" ] ) . eip712 ?? { } ) ,
124- } ,
55+ routeConfig,
56+ payTo,
12557 } ) ;
12658
127- // Check for payment header
59+ // Check for payment header, if none, return the payment requirements
12860 if ( ! paymentData ) {
129- return {
130- status : 402 ,
131- responseHeaders : {
132- "Content-Type" : "application/json" ,
133- } ,
134- responseBody : {
135- x402Version,
136- error : errorMessages ?. paymentRequired || "X-PAYMENT header is required" ,
137- accepts : paymentRequirements ,
138- } ,
139- } ;
61+ return paymentRequirementsResult ;
14062 }
14163
64+ const paymentRequirements = paymentRequirementsResult . responseBody . accepts ;
65+
14266 // decode b64 payment
14367 let decodedPayment : RequestedPaymentPayload ;
14468 try {
@@ -163,8 +87,8 @@ export async function decodePaymentRequest(
16387 const selectedPaymentRequirements = paymentRequirements . find (
16488 ( value ) =>
16589 value . scheme === decodedPayment . scheme &&
166- networkToChainId ( value . network ) ===
167- networkToChainId ( decodedPayment . network ) ,
90+ networkToCaip2ChainId ( value . network ) ===
91+ networkToCaip2ChainId ( decodedPayment . network ) ,
16892 ) ;
16993 if ( ! selectedPaymentRequirements ) {
17094 return {
@@ -190,86 +114,6 @@ export async function decodePaymentRequest(
190114 } ;
191115}
192116
193- /**
194- * Parses the amount from the given price
195- *
196- * @param price - The price to parse
197- * @param network - The network to get the default asset for
198- * @returns The parsed amount or an error message
199- */
200- async function processPriceToAtomicAmount (
201- price : Money | ERC20TokenAmount ,
202- chainId : number ,
203- facilitator : ThirdwebX402Facilitator ,
204- ) : Promise <
205- { maxAmountRequired : string ; asset : DefaultAsset } | { error : string }
206- > {
207- // Handle USDC amount (string) or token amount (ERC20TokenAmount)
208- let maxAmountRequired : string ;
209- let asset : DefaultAsset ;
210-
211- if ( typeof price === "string" || typeof price === "number" ) {
212- // USDC amount in dollars
213- const parsedAmount = moneySchema . safeParse ( price ) ;
214- if ( ! parsedAmount . success ) {
215- return {
216- error : `Invalid price (price: ${ price } ). Must be in the form "$3.10", 0.10, "0.001", ${ parsedAmount . error } ` ,
217- } ;
218- }
219- const parsedUsdAmount = parsedAmount . data ;
220- const defaultAsset = await getDefaultAsset ( chainId , facilitator ) ;
221- if ( ! defaultAsset ) {
222- return {
223- error : `Unable to get default asset on chain ${ chainId } . Please specify an asset in the payment requirements.` ,
224- } ;
225- }
226- asset = defaultAsset ;
227- maxAmountRequired = toUnits (
228- parsedUsdAmount . toString ( ) ,
229- defaultAsset . decimals ,
230- ) . toString ( ) ;
231- } else {
232- // Token amount in atomic units
233- maxAmountRequired = price . amount ;
234- const tokenExtras = await getOrDetectTokenExtras ( {
235- facilitator,
236- partialAsset : price . asset ,
237- chainId,
238- } ) ;
239- if ( ! tokenExtras ) {
240- return {
241- 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.` ,
242- } ;
243- }
244- asset = {
245- address : price . asset . address ,
246- decimals : tokenExtras . decimals ,
247- eip712 : {
248- name : tokenExtras . name ,
249- version : tokenExtras . version ,
250- primaryType : tokenExtras . primaryType ,
251- } ,
252- } ;
253- }
254-
255- return {
256- maxAmountRequired,
257- asset,
258- } ;
259- }
260-
261- async function getDefaultAsset (
262- chainId : number ,
263- facilitator : ThirdwebX402Facilitator ,
264- ) : Promise < DefaultAsset | undefined > {
265- const supportedAssets = await facilitator . supported ( ) ;
266- const matchingAsset = supportedAssets . kinds . find (
267- ( supported ) => networkToChainId ( supported . network ) === chainId ,
268- ) ;
269- const assetConfig = matchingAsset ?. extra ?. defaultAsset as DefaultAsset ;
270- return assetConfig ;
271- }
272-
273117export async function getSupportedSignatureType ( args : {
274118 client : ThirdwebClient ;
275119 asset : string ;
@@ -309,61 +153,3 @@ export async function getSupportedSignatureType(args: {
309153 }
310154 return undefined ;
311155}
312-
313- async function getOrDetectTokenExtras ( args : {
314- facilitator : ThirdwebX402Facilitator ;
315- partialAsset : ERC20TokenAmount [ "asset" ] ;
316- chainId : number ;
317- } ) : Promise <
318- | {
319- name : string ;
320- version : string ;
321- decimals : number ;
322- primaryType : SupportedSignatureType ;
323- }
324- | undefined
325- > {
326- const { facilitator, partialAsset, chainId } = args ;
327- if (
328- partialAsset . eip712 ?. name &&
329- partialAsset . eip712 ?. version &&
330- partialAsset . decimals !== undefined
331- ) {
332- return {
333- name : partialAsset . eip712 . name ,
334- version : partialAsset . eip712 . version ,
335- decimals : partialAsset . decimals ,
336- primaryType : partialAsset . eip712 . primaryType ,
337- } ;
338- }
339- // read from facilitator
340- const response = await facilitator
341- . supported ( {
342- chainId,
343- tokenAddress : partialAsset . address ,
344- } )
345- . catch ( ( ) => {
346- return {
347- kinds : [ ] ,
348- } ;
349- } ) ;
350-
351- const exactScheme = response . kinds ?. find ( ( kind ) => kind . scheme === "exact" ) ;
352- if ( ! exactScheme ) {
353- return undefined ;
354- }
355- const supportedAsset = exactScheme . extra ?. supportedAssets ?. find (
356- ( asset ) =>
357- asset . address . toLowerCase ( ) === partialAsset . address . toLowerCase ( ) ,
358- ) ;
359- if ( ! supportedAsset ) {
360- return undefined ;
361- }
362-
363- return {
364- name : supportedAsset . eip712 . name ,
365- version : supportedAsset . eip712 . version ,
366- decimals : supportedAsset . decimals ,
367- primaryType : supportedAsset . eip712 . primaryType as SupportedSignatureType ,
368- } ;
369- }
0 commit comments