mppx 0.8.6 → 0.8.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/cli/internal.js +2 -2
  3. package/dist/cli/internal.js.map +1 -1
  4. package/dist/cli/plugins/evm.d.ts +2 -0
  5. package/dist/cli/plugins/evm.d.ts.map +1 -0
  6. package/dist/cli/plugins/evm.js +51 -0
  7. package/dist/cli/plugins/evm.js.map +1 -0
  8. package/dist/cli/plugins/index.d.ts +1 -0
  9. package/dist/cli/plugins/index.d.ts.map +1 -1
  10. package/dist/cli/plugins/index.js +1 -0
  11. package/dist/cli/plugins/index.js.map +1 -1
  12. package/dist/cli/plugins/stripe.d.ts.map +1 -1
  13. package/dist/cli/plugins/stripe.js +3 -2
  14. package/dist/cli/plugins/stripe.js.map +1 -1
  15. package/dist/cli/validate/challenge.d.ts +2 -0
  16. package/dist/cli/validate/challenge.d.ts.map +1 -1
  17. package/dist/cli/validate/challenge.js +191 -84
  18. package/dist/cli/validate/challenge.js.map +1 -1
  19. package/dist/cli/validate/discovery.d.ts +1 -0
  20. package/dist/cli/validate/discovery.d.ts.map +1 -1
  21. package/dist/cli/validate/discovery.js +99 -35
  22. package/dist/cli/validate/discovery.js.map +1 -1
  23. package/dist/cli/validate/helpers.d.ts +14 -1
  24. package/dist/cli/validate/helpers.d.ts.map +1 -1
  25. package/dist/cli/validate/helpers.js +0 -13
  26. package/dist/cli/validate/helpers.js.map +1 -1
  27. package/dist/cli/validate/index.d.ts +1 -0
  28. package/dist/cli/validate/index.d.ts.map +1 -1
  29. package/dist/cli/validate/index.js +102 -153
  30. package/dist/cli/validate/index.js.map +1 -1
  31. package/dist/cli/validate/payment.d.ts +4 -1
  32. package/dist/cli/validate/payment.d.ts.map +1 -1
  33. package/dist/cli/validate/payment.js +312 -106
  34. package/dist/cli/validate/payment.js.map +1 -1
  35. package/dist/evm/Chains.d.ts +4 -0
  36. package/dist/evm/Chains.d.ts.map +1 -1
  37. package/dist/evm/Chains.js +4 -0
  38. package/dist/evm/Chains.js.map +1 -1
  39. package/dist/internal/AcceptPayment.d.ts.map +1 -1
  40. package/dist/internal/AcceptPayment.js +7 -1
  41. package/dist/internal/AcceptPayment.js.map +1 -1
  42. package/dist/stripe/server/internal/html.gen.d.ts +1 -1
  43. package/dist/stripe/server/internal/html.gen.d.ts.map +1 -1
  44. package/dist/stripe/server/internal/html.gen.js +1 -1
  45. package/dist/stripe/server/internal/html.gen.js.map +1 -1
  46. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  47. package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
  48. package/dist/tempo/server/internal/html.gen.js +1 -1
  49. package/dist/tempo/server/internal/html.gen.js.map +1 -1
  50. package/dist/tempo/server/internal/request-body.d.ts.map +1 -1
  51. package/dist/tempo/server/internal/request-body.js +2 -0
  52. package/dist/tempo/server/internal/request-body.js.map +1 -1
  53. package/dist/tempo/session/server/ChannelStore.js +4 -4
  54. package/dist/tempo/session/server/ChannelStore.js.map +1 -1
  55. package/dist/tempo/session/server/CredentialVerification.d.ts +8 -0
  56. package/dist/tempo/session/server/CredentialVerification.d.ts.map +1 -1
  57. package/dist/tempo/session/server/CredentialVerification.js +21 -1
  58. package/dist/tempo/session/server/CredentialVerification.js.map +1 -1
  59. package/dist/tempo/session/server/Session.d.ts.map +1 -1
  60. package/dist/tempo/session/server/Session.js +1 -0
  61. package/dist/tempo/session/server/Session.js.map +1 -1
  62. package/dist/validation/core.d.ts +75 -0
  63. package/dist/validation/core.d.ts.map +1 -0
  64. package/dist/validation/core.js +269 -0
  65. package/dist/validation/core.js.map +1 -0
  66. package/dist/validation/index.d.ts +3 -0
  67. package/dist/validation/index.d.ts.map +1 -0
  68. package/dist/validation/index.js +2 -0
  69. package/dist/validation/index.js.map +1 -0
  70. package/dist/x402/Assets.d.ts +9 -0
  71. package/dist/x402/Assets.d.ts.map +1 -1
  72. package/dist/x402/Assets.js +38 -0
  73. package/dist/x402/Assets.js.map +1 -1
  74. package/package.json +9 -4
  75. package/src/cli/internal.ts +2 -2
  76. package/src/cli/plugins/evm.ts +55 -0
  77. package/src/cli/plugins/index.ts +1 -0
  78. package/src/cli/plugins/stripe.ts +3 -2
  79. package/src/cli/validate/challenge.ts +345 -127
  80. package/src/cli/validate/discovery.test.ts +307 -0
  81. package/src/cli/validate/discovery.ts +104 -38
  82. package/src/cli/validate/helpers.ts +15 -13
  83. package/src/cli/validate/index.ts +121 -242
  84. package/src/cli/validate/payment.ts +413 -144
  85. package/src/cli/validate.test.ts +237 -3
  86. package/src/client/internal/Fetch.test.ts +142 -2
  87. package/src/evm/Chains.ts +6 -0
  88. package/src/evm/PublicInterface.test-d.ts +7 -0
  89. package/src/internal/AcceptPayment.test.ts +45 -0
  90. package/src/internal/AcceptPayment.ts +11 -1
  91. package/src/stripe/server/internal/html/package.json +1 -1
  92. package/src/stripe/server/internal/html.gen.ts +1 -1
  93. package/src/tempo/server/internal/html/package.json +1 -1
  94. package/src/tempo/server/internal/html.gen.ts +1 -1
  95. package/src/tempo/server/internal/request-body.test.ts +9 -0
  96. package/src/tempo/server/internal/request-body.ts +2 -0
  97. package/src/tempo/session/server/ChannelStore.test.ts +17 -0
  98. package/src/tempo/session/server/ChannelStore.ts +4 -4
  99. package/src/tempo/session/server/CredentialVerification.test.ts +118 -1
  100. package/src/tempo/session/server/CredentialVerification.ts +30 -0
  101. package/src/tempo/session/server/Session.integration.test.ts +5 -0
  102. package/src/tempo/session/server/Session.test.ts +119 -53
  103. package/src/tempo/session/server/Session.ts +1 -0
  104. package/src/validation/core.ts +374 -0
  105. package/src/validation/index.ts +11 -0
  106. package/src/x402/Assets.test.ts +42 -0
  107. package/src/x402/Assets.ts +40 -0
  108. package/src/x402/Exact.localnet.test.ts +475 -0
@@ -0,0 +1,374 @@
1
+ import type { Chain } from 'viem'
2
+ import * as viemChains from 'viem/chains'
3
+
4
+ import { validateChallenge, validateErrorHandling } from '../cli/validate/challenge.js'
5
+ import {
6
+ buildUrl,
7
+ extractEndpointsFromDiscovery,
8
+ extractRequestBodyFromDiscovery,
9
+ fetchDiscoveryDoc,
10
+ } from '../cli/validate/discovery.js'
11
+ import type { CheckResult, EndpointSpec, PathParameter } from '../cli/validate/helpers.js'
12
+ import {
13
+ isValidIntegerAmount,
14
+ parseEndpointArg,
15
+ resolveBodyForEndpoint,
16
+ } from '../cli/validate/helpers.js'
17
+ import { validatePaymentFlow } from '../cli/validate/payment.js'
18
+ import { validate as validateDiscoveryDoc } from '../discovery/Validate.js'
19
+
20
+ export { buildUrl }
21
+ export type { CheckResult, EndpointSpec, PathParameter }
22
+
23
+ export type ValidateOptions = {
24
+ url: string
25
+ endpoint?: string | undefined
26
+ body?: string | undefined
27
+ query?: string[] | undefined
28
+ verbose?: boolean | undefined
29
+ yes?: boolean | undefined
30
+ skipPayment?: boolean | undefined
31
+ interactive?: boolean | undefined
32
+ discoveryPath?: string | undefined
33
+ onPaymentResults?: (results: CheckResult[]) => void
34
+ }
35
+
36
+ export type DiscoveryResult = {
37
+ found: boolean
38
+ valid: boolean
39
+ endpoints: EndpointSpec[]
40
+ checks: CheckResult[]
41
+ doc: Record<string, unknown> | null
42
+ }
43
+
44
+ export type EndpointValidationResult = {
45
+ method: string
46
+ path: string
47
+ challenge: CheckResult[]
48
+ errorHandling: CheckResult[]
49
+ payment: CheckResult[]
50
+ }
51
+
52
+ export type ValidateResult = {
53
+ url: string
54
+ discovery: DiscoveryResult
55
+ endpoints: EndpointValidationResult[]
56
+ summary: { passed: number; failed: number; warnings: number; skipped: number }
57
+ suggestions: string[]
58
+ }
59
+
60
+ export type ValidateEvent =
61
+ | { phase: 'discovery'; discovery: DiscoveryResult; results: CheckResult[] }
62
+ | { phase: 'endpoint'; endpoint: EndpointSpec }
63
+ | {
64
+ phase: 'challenge'
65
+ endpoint: EndpointSpec
66
+ results: CheckResult[]
67
+ isMpp: boolean
68
+ isTestnet: boolean
69
+ isMainnet: boolean
70
+ isNonMppPayment: boolean
71
+ isMalformedChallenge: boolean
72
+ methods: string[]
73
+ }
74
+ | { phase: 'errorHandling'; endpoint: EndpointSpec; results: CheckResult[] }
75
+ | {
76
+ phase: 'payment'
77
+ endpoint: EndpointSpec
78
+ results: CheckResult[]
79
+ succeeded: boolean
80
+ body?: string | undefined
81
+ }
82
+
83
+ /** Streams validation results as each phase completes. */
84
+ export async function* validateStream(options: ValidateOptions): AsyncGenerator<ValidateEvent> {
85
+ const baseUrl = options.url.replace(/\/$/, '').replace(/\/openapi\.json$/i, '')
86
+ const verbose = options.verbose ?? false
87
+
88
+ const discovery = await runDiscovery(baseUrl, options)
89
+ yield { phase: 'discovery', discovery, results: discovery.checks }
90
+
91
+ for (const endpoint of discovery.endpoints) {
92
+ yield { phase: 'endpoint', endpoint }
93
+
94
+ let body: string | undefined
95
+ if (options.endpoint) {
96
+ body = options.body
97
+ } else {
98
+ body = resolveBodyForEndpoint(options.body, endpoint.path)
99
+ if (!body && discovery.doc) {
100
+ body = extractRequestBodyFromDiscovery(discovery.doc, endpoint)
101
+ }
102
+ }
103
+
104
+ const {
105
+ results: challengeResults,
106
+ resolvedBody,
107
+ challenges: parsedChallenges,
108
+ } = await validateChallenge(baseUrl, endpoint, verbose, {
109
+ body,
110
+ query: options.query,
111
+ discoveryDoc: discovery.doc ?? undefined,
112
+ })
113
+ const effectiveBody = resolvedBody ?? body
114
+ const isMpp = challengeResults.some(
115
+ (r) => r.severity === 'pass' && r.label === 'Challenge parseable',
116
+ )
117
+ const chainIds = (parsedChallenges ?? [])
118
+ .filter((ch) => ch.method !== 'stripe')
119
+ .map((ch) => ((ch.request as Record<string, unknown>).methodDetails as any)?.chainId)
120
+ .filter((id): id is number => typeof id === 'number')
121
+ const isTestnet = chainIds.some(chainIsTestnet)
122
+ const isMainnet = chainIds.some((id) => !chainIsTestnet(id))
123
+ const isNonMppPayment =
124
+ !isMpp && challengeResults.some((r) => r.label === 'Not an MPP endpoint')
125
+ const hasPaymentScheme = challengeResults.some(
126
+ (r) => r.severity === 'pass' && r.label === 'WWW-Authenticate header present',
127
+ )
128
+ const challengeFailed = challengeResults.some(
129
+ (r) => r.severity === 'fail' && r.label === 'Challenge parseable',
130
+ )
131
+ const isMalformedChallenge = !isMpp && hasPaymentScheme && challengeFailed
132
+
133
+ const methods = parsedChallenges?.map((c) => c.method) ?? []
134
+
135
+ yield {
136
+ phase: 'challenge',
137
+ endpoint,
138
+ results: challengeResults,
139
+ isMpp,
140
+ isTestnet,
141
+ isMainnet,
142
+ isNonMppPayment,
143
+ isMalformedChallenge,
144
+ methods,
145
+ }
146
+
147
+ if (isMpp) {
148
+ const errorResults = await validateErrorHandling(baseUrl, endpoint, {
149
+ body: effectiveBody,
150
+ query: options.query,
151
+ })
152
+ yield { phase: 'errorHandling', endpoint, results: errorResults }
153
+
154
+ if (!options.skipPayment) {
155
+ const onResults = options.onPaymentResults
156
+ const paymentResults = await validatePaymentFlow(baseUrl, endpoint, verbose, {
157
+ body: effectiveBody,
158
+ query: options.query,
159
+ yes: options.yes,
160
+ silent: !onResults,
161
+ interactive: options.interactive,
162
+ ...(onResults && { onResults }),
163
+ })
164
+ const succeeded = paymentResults.some(
165
+ (r) => r.severity === 'pass' && r.label === 'Payment: successful',
166
+ )
167
+ yield { phase: 'payment', endpoint, results: paymentResults, succeeded }
168
+ }
169
+ }
170
+ }
171
+ }
172
+
173
+ /** Runs the full validation suite and returns all results as a batch. */
174
+ export async function validate(options: ValidateOptions): Promise<ValidateResult> {
175
+ const baseUrl = options.url.replace(/\/$/, '').replace(/\/openapi\.json$/i, '')
176
+ let discovery: DiscoveryResult | undefined
177
+ const endpointResults: EndpointValidationResult[] = []
178
+ let current: EndpointValidationResult | undefined
179
+
180
+ let sawTestnet = false
181
+ let sawMainnet = false
182
+
183
+ for await (const event of validateStream(options)) {
184
+ switch (event.phase) {
185
+ case 'discovery':
186
+ discovery = event.discovery
187
+ break
188
+ case 'endpoint':
189
+ current = {
190
+ method: event.endpoint.method,
191
+ path: event.endpoint.path,
192
+ challenge: [],
193
+ errorHandling: [],
194
+ payment: [],
195
+ }
196
+ endpointResults.push(current)
197
+ break
198
+ case 'challenge':
199
+ if (current) current.challenge = event.results
200
+ if (event.isMpp) {
201
+ if (event.isTestnet) sawTestnet = true
202
+ if (event.isMainnet) sawMainnet = true
203
+ }
204
+ break
205
+ case 'errorHandling':
206
+ if (current) current.errorHandling = event.results
207
+ break
208
+ case 'payment':
209
+ if (current) current.payment = event.results
210
+ break
211
+ }
212
+ }
213
+
214
+ const summary = { passed: 0, failed: 0, warnings: 0, skipped: 0 }
215
+ if (discovery) {
216
+ for (const r of discovery.checks) {
217
+ if (r.severity === 'pass') summary.passed++
218
+ else if (r.severity === 'fail') summary.failed++
219
+ else if (r.severity === 'warn') summary.warnings++
220
+ else if (r.severity === 'skip') summary.skipped++
221
+ }
222
+ }
223
+ for (const ep of endpointResults) {
224
+ for (const r of [...ep.challenge, ...ep.errorHandling, ...ep.payment]) {
225
+ if (r.severity === 'pass') summary.passed++
226
+ else if (r.severity === 'fail') summary.failed++
227
+ else if (r.severity === 'warn') summary.warnings++
228
+ else if (r.severity === 'skip') summary.skipped++
229
+ }
230
+ }
231
+
232
+ if (!discovery) throw new Error('Discovery phase did not complete')
233
+
234
+ const suggestions = computeSuggestions(endpointResults, { sawTestnet, sawMainnet })
235
+
236
+ return {
237
+ url: baseUrl,
238
+ discovery,
239
+ endpoints: endpointResults,
240
+ summary,
241
+ suggestions,
242
+ }
243
+ }
244
+
245
+ function computeSuggestions(
246
+ endpoints: EndpointValidationResult[],
247
+ flags: { sawTestnet: boolean; sawMainnet: boolean },
248
+ ): string[] {
249
+ const steps: string[] = []
250
+ const allResults = endpoints.flatMap((ep) => [...ep.challenge, ...ep.payment])
251
+
252
+ const sawCryptoMainnet = flags.sawMainnet
253
+ const sawCryptoTestnet = flags.sawTestnet
254
+ const sawStripeLive = allResults.some(
255
+ (r) => r.label.includes('[stripe]') && r.label.includes('livemode'),
256
+ )
257
+
258
+ if (sawCryptoMainnet && !sawCryptoTestnet) {
259
+ steps.push(
260
+ 'Validate your testnet server to test without real funds (Tempo wallets are automatically provisioned).',
261
+ )
262
+ } else if (sawCryptoTestnet && !sawCryptoMainnet) {
263
+ steps.push('Validate your mainnet server to confirm real payments work end-to-end.')
264
+ }
265
+ if (sawStripeLive) {
266
+ steps.push(
267
+ 'Validate your testmode Stripe server to test card payments end-to-end (auto-pays with test card).',
268
+ )
269
+ }
270
+
271
+ return steps
272
+ }
273
+
274
+ const allChains = Object.values(viemChains) as Chain[]
275
+
276
+ function chainIsTestnet(chainId: number): boolean {
277
+ return allChains.find((c) => c.id === chainId)?.testnet === true
278
+ }
279
+
280
+ async function runDiscovery(baseUrl: string, options: ValidateOptions): Promise<DiscoveryResult> {
281
+ const checks: CheckResult[] = []
282
+ let endpoints: EndpointSpec[] = []
283
+ let discoveryDoc: Record<string, unknown> | null = null
284
+ let found = false
285
+ let valid = false
286
+
287
+ // Try the user's path first, then root, then /api.
288
+ const origin = new URL(baseUrl).origin
289
+ let candidates: string[]
290
+ if (options.discoveryPath) {
291
+ candidates = [
292
+ new URL(options.discoveryPath, baseUrl + '/').href.replace(/\/openapi\.json$/i, ''),
293
+ ]
294
+ } else {
295
+ candidates = [baseUrl, origin, `${origin}/api`]
296
+ candidates = [...new Set(candidates)]
297
+ }
298
+ let discoveryResult: Awaited<ReturnType<typeof fetchDiscoveryDoc>> | undefined
299
+ const attemptedErrors: string[] = []
300
+ for (const candidate of candidates) {
301
+ discoveryResult = await fetchDiscoveryDoc(candidate)
302
+ if ('error' in discoveryResult) {
303
+ attemptedErrors.push(`${candidate}/openapi.json: ${discoveryResult.error}`)
304
+ } else {
305
+ break
306
+ }
307
+ }
308
+
309
+ if (!discoveryResult || 'error' in discoveryResult) {
310
+ checks.push({
311
+ label: 'Document found',
312
+ detail: attemptedErrors.join('; '),
313
+ hint: 'MPP servers must serve an OpenAPI document at /openapi.json with x-payment-info extensions.',
314
+ severity: 'fail',
315
+ })
316
+ } else {
317
+ found = true
318
+ checks.push({ label: 'Document found and parseable', severity: 'pass' })
319
+
320
+ const issues = validateDiscoveryDoc(discoveryResult.doc)
321
+ const errors = issues.filter((i) => i.severity === 'error')
322
+ const warnings = issues.filter((i) => i.severity === 'warning')
323
+
324
+ if (errors.length > 0) {
325
+ checks.push({
326
+ label: 'Valid OpenAPI structure',
327
+ detail: `${errors.length} error(s)`,
328
+ severity: 'fail',
329
+ })
330
+ for (const e of errors) {
331
+ checks.push({ label: e.message, detail: e.path, severity: 'fail' })
332
+ }
333
+ } else {
334
+ valid = true
335
+ checks.push({ label: 'Valid OpenAPI structure', severity: 'pass' })
336
+ }
337
+
338
+ for (const w of warnings) {
339
+ checks.push({ label: w.message, detail: w.path, severity: 'warn' })
340
+ }
341
+
342
+ discoveryDoc = discoveryResult.doc as Record<string, unknown>
343
+ }
344
+
345
+ if (options.endpoint) {
346
+ const parsed = parseEndpointArg(options.endpoint)
347
+ if (parsed) endpoints.push(parsed)
348
+ } else if (discoveryDoc) {
349
+ endpoints = extractEndpointsFromDiscovery(discoveryDoc)
350
+
351
+ const NO_AMOUNT = BigInt('999999999999999999')
352
+ endpoints.sort((a, b) => {
353
+ const aAmt = isValidIntegerAmount(a.amount) ? BigInt(a.amount!) : NO_AMOUNT
354
+ const bAmt = isValidIntegerAmount(b.amount) ? BigInt(b.amount!) : NO_AMOUNT
355
+ return aAmt < bAmt ? -1 : aAmt > bAmt ? 1 : 0
356
+ })
357
+
358
+ if (endpoints.length > 0) {
359
+ checks.push({
360
+ label: 'Paid endpoints found',
361
+ detail: `${endpoints.length} endpoint(s)`,
362
+ severity: 'pass',
363
+ })
364
+ } else {
365
+ checks.push({
366
+ label: 'Paid endpoints found',
367
+ detail: 'No endpoints with x-payment-info',
368
+ severity: 'warn',
369
+ })
370
+ }
371
+ }
372
+
373
+ return { found, valid, endpoints, checks, doc: discoveryDoc }
374
+ }
@@ -0,0 +1,11 @@
1
+ export { buildUrl, validate, validateStream } from './core.js'
2
+ export type {
3
+ CheckResult,
4
+ DiscoveryResult,
5
+ EndpointSpec,
6
+ EndpointValidationResult,
7
+ PathParameter,
8
+ ValidateEvent,
9
+ ValidateOptions,
10
+ ValidateResult,
11
+ } from './core.js'
@@ -208,4 +208,46 @@ describe('x402 assets', () => {
208
208
  },
209
209
  })
210
210
  })
211
+
212
+ test('exports Celo USDC metadata', () => {
213
+ expect(Assets.isAsset(Assets.celo.USDC)).toBe(true)
214
+ expect(Assets.celo.USDC).toMatchObject({
215
+ address: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
216
+ decimals: 6,
217
+ network: 'eip155:42220',
218
+ transfer: {
219
+ name: 'USDC',
220
+ type: 'eip3009',
221
+ version: '2',
222
+ },
223
+ })
224
+ })
225
+
226
+ test('exports Celo USDT metadata', () => {
227
+ expect(Assets.isAsset(Assets.celo.USDT)).toBe(true)
228
+ expect(Assets.celo.USDT).toMatchObject({
229
+ address: '0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e',
230
+ decimals: 6,
231
+ network: 'eip155:42220',
232
+ transfer: {
233
+ name: 'Tether USD',
234
+ type: 'eip3009',
235
+ version: '1',
236
+ },
237
+ })
238
+ })
239
+
240
+ test('exports Celo Sepolia USDC metadata', () => {
241
+ expect(Assets.isAsset(Assets.celoSepolia.USDC)).toBe(true)
242
+ expect(Assets.celoSepolia.USDC).toMatchObject({
243
+ address: '0x01C5C0122039549AD1493B8220cABEdD739BC44E',
244
+ decimals: 6,
245
+ network: 'eip155:11142220',
246
+ transfer: {
247
+ name: 'USDC',
248
+ type: 'eip3009',
249
+ version: '2',
250
+ },
251
+ })
252
+ })
211
253
  })
@@ -91,6 +91,46 @@ export const baseSepolia = {
91
91
  }),
92
92
  } as const
93
93
 
94
+ /** Celo network known assets. */
95
+ export const celo = {
96
+ USDC: define({
97
+ address: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
98
+ decimals: 6,
99
+ network: 'eip155:42220',
100
+ transfer: {
101
+ // Celo USDC signs with the shorter EIP-712 domain name.
102
+ name: 'USDC',
103
+ type: 'eip3009',
104
+ version: '2',
105
+ },
106
+ }),
107
+ USDT: define({
108
+ address: '0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e',
109
+ decimals: 6,
110
+ network: 'eip155:42220',
111
+ transfer: {
112
+ // Celo USDT signs with domain version "1", unlike Circle's FiatToken "2".
113
+ name: 'Tether USD',
114
+ type: 'eip3009',
115
+ version: '1',
116
+ },
117
+ }),
118
+ } as const
119
+
120
+ /** Celo Sepolia known assets. */
121
+ export const celoSepolia = {
122
+ USDC: define({
123
+ address: '0x01C5C0122039549AD1493B8220cABEdD739BC44E',
124
+ decimals: 6,
125
+ network: 'eip155:11142220',
126
+ transfer: {
127
+ name: 'USDC',
128
+ type: 'eip3009',
129
+ version: '2',
130
+ },
131
+ }),
132
+ } as const
133
+
94
134
  /** Returns true when a value is known x402 asset metadata. */
95
135
  export function isAsset(value: unknown): value is KnownAsset {
96
136
  if (typeof value !== 'object' || value === null) return false