mppx 0.8.5 → 0.8.7

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 (134) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/Receipt.d.ts +20 -2
  3. package/dist/Receipt.d.ts.map +1 -1
  4. package/dist/Receipt.js +19 -12
  5. package/dist/Receipt.js.map +1 -1
  6. package/dist/cli/cli.d.ts.map +1 -1
  7. package/dist/cli/cli.js +6 -4
  8. package/dist/cli/cli.js.map +1 -1
  9. package/dist/cli/internal.js +2 -2
  10. package/dist/cli/internal.js.map +1 -1
  11. package/dist/cli/plugins/evm.d.ts +2 -0
  12. package/dist/cli/plugins/evm.d.ts.map +1 -0
  13. package/dist/cli/plugins/evm.js +51 -0
  14. package/dist/cli/plugins/evm.js.map +1 -0
  15. package/dist/cli/plugins/index.d.ts +1 -0
  16. package/dist/cli/plugins/index.d.ts.map +1 -1
  17. package/dist/cli/plugins/index.js +1 -0
  18. package/dist/cli/plugins/index.js.map +1 -1
  19. package/dist/cli/plugins/tempo.d.ts.map +1 -1
  20. package/dist/cli/plugins/tempo.js +4 -2
  21. package/dist/cli/plugins/tempo.js.map +1 -1
  22. package/dist/cli/validate/challenge.d.ts +2 -0
  23. package/dist/cli/validate/challenge.d.ts.map +1 -1
  24. package/dist/cli/validate/challenge.js +184 -78
  25. package/dist/cli/validate/challenge.js.map +1 -1
  26. package/dist/cli/validate/discovery.d.ts +1 -0
  27. package/dist/cli/validate/discovery.d.ts.map +1 -1
  28. package/dist/cli/validate/discovery.js +99 -35
  29. package/dist/cli/validate/discovery.js.map +1 -1
  30. package/dist/cli/validate/helpers.d.ts +14 -1
  31. package/dist/cli/validate/helpers.d.ts.map +1 -1
  32. package/dist/cli/validate/helpers.js +0 -13
  33. package/dist/cli/validate/helpers.js.map +1 -1
  34. package/dist/cli/validate/index.d.ts +1 -0
  35. package/dist/cli/validate/index.d.ts.map +1 -1
  36. package/dist/cli/validate/index.js +98 -151
  37. package/dist/cli/validate/index.js.map +1 -1
  38. package/dist/cli/validate/payment.d.ts +1 -0
  39. package/dist/cli/validate/payment.d.ts.map +1 -1
  40. package/dist/cli/validate/payment.js +215 -144
  41. package/dist/cli/validate/payment.js.map +1 -1
  42. package/dist/client/internal/Fetch.d.ts.map +1 -1
  43. package/dist/client/internal/Fetch.js +6 -1
  44. package/dist/client/internal/Fetch.js.map +1 -1
  45. package/dist/evm/Chains.d.ts +4 -0
  46. package/dist/evm/Chains.d.ts.map +1 -1
  47. package/dist/evm/Chains.js +4 -0
  48. package/dist/evm/Chains.js.map +1 -1
  49. package/dist/internal/AcceptPayment.d.ts.map +1 -1
  50. package/dist/internal/AcceptPayment.js +7 -1
  51. package/dist/internal/AcceptPayment.js.map +1 -1
  52. package/dist/stripe/server/internal/html.gen.d.ts +1 -1
  53. package/dist/stripe/server/internal/html.gen.d.ts.map +1 -1
  54. package/dist/stripe/server/internal/html.gen.js +1 -1
  55. package/dist/stripe/server/internal/html.gen.js.map +1 -1
  56. package/dist/tempo/client/Charge.d.ts.map +1 -1
  57. package/dist/tempo/client/Charge.js +6 -2
  58. package/dist/tempo/client/Charge.js.map +1 -1
  59. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  60. package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
  61. package/dist/tempo/server/internal/html.gen.js +1 -1
  62. package/dist/tempo/server/internal/html.gen.js.map +1 -1
  63. package/dist/tempo/server/internal/request-body.d.ts.map +1 -1
  64. package/dist/tempo/server/internal/request-body.js +2 -0
  65. package/dist/tempo/server/internal/request-body.js.map +1 -1
  66. package/dist/tempo/session/precompile/Chain.d.ts +26 -2
  67. package/dist/tempo/session/precompile/Chain.d.ts.map +1 -1
  68. package/dist/tempo/session/precompile/Chain.js +38 -5
  69. package/dist/tempo/session/precompile/Chain.js.map +1 -1
  70. package/dist/tempo/session/server/ChannelStore.js +4 -4
  71. package/dist/tempo/session/server/ChannelStore.js.map +1 -1
  72. package/dist/tempo/session/server/CredentialVerification.d.ts +8 -0
  73. package/dist/tempo/session/server/CredentialVerification.d.ts.map +1 -1
  74. package/dist/tempo/session/server/CredentialVerification.js +21 -1
  75. package/dist/tempo/session/server/CredentialVerification.js.map +1 -1
  76. package/dist/tempo/session/server/Session.d.ts.map +1 -1
  77. package/dist/tempo/session/server/Session.js +1 -0
  78. package/dist/tempo/session/server/Session.js.map +1 -1
  79. package/dist/validation/core.d.ts +79 -0
  80. package/dist/validation/core.d.ts.map +1 -0
  81. package/dist/validation/core.js +256 -0
  82. package/dist/validation/core.js.map +1 -0
  83. package/dist/validation/index.d.ts +3 -0
  84. package/dist/validation/index.d.ts.map +1 -0
  85. package/dist/validation/index.js +2 -0
  86. package/dist/validation/index.js.map +1 -0
  87. package/dist/x402/Assets.d.ts +9 -0
  88. package/dist/x402/Assets.d.ts.map +1 -1
  89. package/dist/x402/Assets.js +38 -0
  90. package/dist/x402/Assets.js.map +1 -1
  91. package/package.json +9 -4
  92. package/src/Receipt.test.ts +53 -0
  93. package/src/Receipt.ts +26 -13
  94. package/src/cli/cli.test.ts +41 -5
  95. package/src/cli/cli.ts +7 -4
  96. package/src/cli/internal.ts +2 -2
  97. package/src/cli/plugins/evm.ts +55 -0
  98. package/src/cli/plugins/index.ts +1 -0
  99. package/src/cli/plugins/tempo.ts +6 -2
  100. package/src/cli/validate/challenge.ts +334 -121
  101. package/src/cli/validate/discovery.test.ts +307 -0
  102. package/src/cli/validate/discovery.ts +104 -38
  103. package/src/cli/validate/helpers.ts +15 -13
  104. package/src/cli/validate/index.ts +117 -240
  105. package/src/cli/validate/payment.ts +261 -178
  106. package/src/cli/validate.test.ts +143 -3
  107. package/src/client/internal/Fetch.test.ts +186 -2
  108. package/src/client/internal/Fetch.ts +5 -1
  109. package/src/evm/Chains.ts +6 -0
  110. package/src/evm/PublicInterface.test-d.ts +7 -0
  111. package/src/internal/AcceptPayment.test.ts +45 -0
  112. package/src/internal/AcceptPayment.ts +11 -1
  113. package/src/stripe/server/internal/html/package.json +1 -1
  114. package/src/stripe/server/internal/html.gen.ts +1 -1
  115. package/src/tempo/client/Charge.test.ts +61 -0
  116. package/src/tempo/client/Charge.ts +6 -1
  117. package/src/tempo/server/internal/html/package.json +1 -1
  118. package/src/tempo/server/internal/html.gen.ts +1 -1
  119. package/src/tempo/server/internal/request-body.test.ts +9 -0
  120. package/src/tempo/server/internal/request-body.ts +2 -0
  121. package/src/tempo/session/precompile/Chain.test.ts +121 -2
  122. package/src/tempo/session/precompile/Chain.ts +56 -6
  123. package/src/tempo/session/server/ChannelStore.test.ts +17 -0
  124. package/src/tempo/session/server/ChannelStore.ts +4 -4
  125. package/src/tempo/session/server/CredentialVerification.test.ts +118 -1
  126. package/src/tempo/session/server/CredentialVerification.ts +30 -0
  127. package/src/tempo/session/server/Session.integration.test.ts +5 -0
  128. package/src/tempo/session/server/Session.test.ts +119 -53
  129. package/src/tempo/session/server/Session.ts +1 -0
  130. package/src/validation/core.ts +348 -0
  131. package/src/validation/index.ts +11 -0
  132. package/src/x402/Assets.test.ts +42 -0
  133. package/src/x402/Assets.ts +40 -0
  134. package/src/x402/Exact.localnet.test.ts +475 -0
package/src/Receipt.ts CHANGED
@@ -3,17 +3,7 @@ import { Base64 } from 'ox'
3
3
  import * as Constants from './Constants.js'
4
4
  import * as z from './zod.js'
5
5
 
6
- /**
7
- * Schema for a payment receipt.
8
- *
9
- * @example
10
- * ```ts
11
- * import { Receipt } from 'mppx'
12
- *
13
- * const receipt = Receipt.Schema.parse(data)
14
- * ```
15
- */
16
- export const Schema = z.object({
6
+ const shape = {
17
7
  /** Payment method used (e.g., "tempo", "stripe"). */
18
8
  method: z.string(),
19
9
  /** Method-specific reference (e.g., transaction hash). */
@@ -26,11 +16,34 @@ export const Schema = z.object({
26
16
  status: z.literal('success'),
27
17
  /** RFC 3339 settlement timestamp. */
28
18
  timestamp: z.datetime(),
29
- })
19
+ }
20
+
21
+ /** Base-field schema used only to derive the {@link Receipt} type without an index signature. */
22
+ const BaseSchema = z.object(shape)
23
+
24
+ /**
25
+ * Schema for a payment receipt.
26
+ *
27
+ * Method specifications may define additional receipt fields beyond the
28
+ * base set (per the core spec's Payment-Receipt section); unknown fields
29
+ * are preserved through parse/serialize round-trips rather than stripped.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * import { Receipt } from 'mppx'
34
+ *
35
+ * const receipt = Receipt.Schema.parse(data)
36
+ * ```
37
+ */
38
+ export const Schema = z.looseObject(shape)
30
39
 
31
40
  /**
32
41
  * Payment receipt returned after verification.
33
42
  *
43
+ * Method-specific extension fields are preserved at runtime but not part of
44
+ * this base type; method packages can type them via intersection
45
+ * (e.g. `Receipt.Receipt & { originTxHash: string }`).
46
+ *
34
47
  * @example
35
48
  * ```ts
36
49
  * import { Receipt } from 'mppx'
@@ -43,7 +56,7 @@ export const Schema = z.object({
43
56
  * }
44
57
  * ```
45
58
  */
46
- export type Receipt = z.infer<typeof Schema>
59
+ export type Receipt = z.infer<typeof BaseSchema>
47
60
 
48
61
  /**
49
62
  * Deserializes a Payment-Receipt header value to a receipt.
@@ -43,7 +43,7 @@ afterAll(() => {
43
43
  })
44
44
 
45
45
  async function serve(argv: string[], options?: { env?: Record<string, string | undefined> }) {
46
- let output = ''
46
+ const stdoutChunks: Buffer[] = []
47
47
  let stderr = ''
48
48
  let exitCode: number | undefined
49
49
  const saved: Record<string, string | undefined> = {}
@@ -58,7 +58,9 @@ async function serve(argv: string[], options?: { env?: Record<string, string | u
58
58
  const origLog = console.log
59
59
  const origError = console.error
60
60
  process.stdout.write = ((chunk: unknown) => {
61
- output += typeof chunk === 'string' ? chunk : String(chunk)
61
+ if (typeof chunk === 'string') stdoutChunks.push(Buffer.from(chunk))
62
+ else if (chunk instanceof Uint8Array) stdoutChunks.push(Buffer.from(chunk))
63
+ else stdoutChunks.push(Buffer.from(String(chunk)))
62
64
  return true
63
65
  }) as typeof process.stdout.write
64
66
  process.stderr.write = ((chunk: unknown) => {
@@ -66,7 +68,7 @@ async function serve(argv: string[], options?: { env?: Record<string, string | u
66
68
  return true
67
69
  }) as typeof process.stderr.write
68
70
  console.log = (...args: unknown[]) => {
69
- output += `${args.map(String).join(' ')}\n`
71
+ stdoutChunks.push(Buffer.from(`${args.map(String).join(' ')}\n`))
70
72
  }
71
73
  console.error = (...args: unknown[]) => {
72
74
  stderr += `${args.map(String).join(' ')}\n`
@@ -74,7 +76,7 @@ async function serve(argv: string[], options?: { env?: Record<string, string | u
74
76
  try {
75
77
  await cli.serve(argv, {
76
78
  stdout(s: string) {
77
- output += s
79
+ stdoutChunks.push(Buffer.from(s))
78
80
  },
79
81
  exit(code: number) {
80
82
  exitCode = code
@@ -90,7 +92,8 @@ async function serve(argv: string[], options?: { env?: Record<string, string | u
90
92
  else process.env[key] = value
91
93
  }
92
94
  }
93
- return { output, stderr, exitCode }
95
+ const stdoutBytes = Buffer.concat(stdoutChunks)
96
+ return { output: stdoutBytes.toString(), stderr, exitCode, stdoutBytes }
94
97
  }
95
98
 
96
99
  function createMockChargeMethod(name: string) {
@@ -430,6 +433,24 @@ describe('services', () => {
430
433
  })
431
434
  })
432
435
 
436
+ describe('request output', () => {
437
+ test('writes non-402 response bodies to stdout without text decoding', async () => {
438
+ const body = Buffer.from([0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x0a, 0x0a])
439
+ const httpServer = await Http.createServer((_req, res) => {
440
+ res.writeHead(200, { 'Content-Type': 'application/pdf' })
441
+ res.end(body)
442
+ })
443
+
444
+ try {
445
+ const { exitCode, stdoutBytes } = await serve([httpServer.url, '-s'])
446
+ expect(exitCode).toBeUndefined()
447
+ expect(stdoutBytes).toEqual(body)
448
+ } finally {
449
+ httpServer.close()
450
+ }
451
+ })
452
+ })
453
+
433
454
  describe('basic charge (examples/basic)', () => {
434
455
  test('happy path: makes payment and receives response', { timeout: 120_000 }, async () => {
435
456
  const { Actions } = await import('viem/tempo')
@@ -1819,6 +1840,21 @@ test('mppx --help', async () => {
1819
1840
  expect(output).toContain('sign')
1820
1841
  })
1821
1842
 
1843
+ describe('account fund help', () => {
1844
+ test('only advertises testnet network funding', async () => {
1845
+ const { output } = await serve(['account', 'fund', '--help'])
1846
+ expect(output).toContain('--network <testnet>')
1847
+ expect(output).not.toContain('mainnet|testnet')
1848
+ })
1849
+
1850
+ test('rejects mainnet network funding', async () => {
1851
+ const { exitCode, output } = await serve(['account', 'fund', '--network', 'mainnet'])
1852
+ expect(exitCode).toBe(1)
1853
+ expect(output).toContain('Invalid input')
1854
+ expect(output).toContain('testnet')
1855
+ })
1856
+ })
1857
+
1822
1858
  // ---------------------------------------------------------------------------
1823
1859
  // sign
1824
1860
  // ---------------------------------------------------------------------------
package/src/cli/cli.ts CHANGED
@@ -99,6 +99,10 @@ function outputResult<Data>(
99
99
  return undefined as unknown as Data
100
100
  }
101
101
 
102
+ async function writeResponseBody(response: Response) {
103
+ process.stdout.write(Buffer.from(await response.arrayBuffer()))
104
+ }
105
+
102
106
  function canReadCommandStdin() {
103
107
  if (process.stdin.isTTY !== false) return false
104
108
  return process.stdin.listenerCount('data') === 0 && process.stdin.listenerCount('readable') === 0
@@ -371,7 +375,7 @@ const cli = Cli.create('mppx', {
371
375
  exitCode: 22,
372
376
  })
373
377
  printResponseHeaders(challengeResponse, headerOpts)
374
- console.log((await challengeResponse.text()).replace(/\n+$/, ''))
378
+ await writeResponseBody(challengeResponse)
375
379
  return
376
380
  }
377
381
 
@@ -625,8 +629,7 @@ const cli = Cli.create('mppx', {
625
629
  } catch {}
626
630
  }
627
631
 
628
- const body = (await credentialResponse.text()).replace(/\n+$/, '')
629
- console.log(body)
632
+ await writeResponseBody(credentialResponse)
630
633
  }
631
634
  } catch (err) {
632
635
  // Re-throw IncurError so incur's error handler formats it properly
@@ -864,7 +867,7 @@ const account = Cli.create('account', {
864
867
  description: 'Fund account with testnet tokens',
865
868
  options: z.object({
866
869
  account: z.string().optional().describe('Account name (env: MPPX_ACCOUNT)'),
867
- network: z.enum(['mainnet', 'testnet']).optional().describe('Tempo network'),
870
+ network: z.enum(['testnet']).optional().describe('Tempo network'),
868
871
  rpcUrl: z.string().optional().describe('RPC endpoint (env: MPPX_RPC_URL)'),
869
872
  }),
870
873
  output: z.object({ account: z.string(), chain: z.string(), transactions: z.array(z.string()) }),
@@ -5,10 +5,10 @@ import type * as Challenge from '../Challenge.js'
5
5
  import * as AcceptPayment from '../internal/AcceptPayment.js'
6
6
  import type * as Method from '../Method.js'
7
7
  import type { Config } from './config.js'
8
- import { stripe as stripePlugin, tempo as tempoPlugin } from './plugins/index.js'
8
+ import { evm as evmPlugin, stripe as stripePlugin, tempo as tempoPlugin } from './plugins/index.js'
9
9
  import type { Plugin } from './plugins/plugin.js'
10
10
 
11
- const builtinPlugins: Plugin[] = [tempoPlugin(), stripePlugin()]
11
+ const builtinPlugins: Plugin[] = [tempoPlugin(), stripePlugin(), evmPlugin()]
12
12
 
13
13
  export function resolvePlugin(
14
14
  challenge: Challenge.Challenge,
@@ -0,0 +1,55 @@
1
+ import { type Address, type Chain, createClient, erc20Abi, http } from 'viem'
2
+ import { readContract } from 'viem/actions'
3
+ import * as viemChains from 'viem/chains'
4
+
5
+ import { evm as evmMethods, assets as evmAssets } from '../../evm/client/index.js'
6
+ import { resolveAccount } from '../account.js'
7
+ import { createPlugin } from './plugin.js'
8
+
9
+ export function evm() {
10
+ return createPlugin({
11
+ method: 'evm',
12
+
13
+ async setup({ challenge }) {
14
+ const request = challenge.request as Record<string, unknown>
15
+ const methodDetails = request.methodDetails as Record<string, unknown> | undefined
16
+ const chainId = methodDetails?.chainId as number | undefined
17
+ const chain = chainId ? resolveChain(chainId) : undefined
18
+ const currency = request.currency as string | undefined
19
+
20
+ const account = await resolveAccount()
21
+ // Known assets provide EIP-712 domain metadata (token name/version) needed for EIP-3009 signing
22
+ const knownCurrencies = [
23
+ ...Object.values(evmAssets.base),
24
+ ...Object.values(evmAssets.baseSepolia),
25
+ ...Object.values(evmAssets.celo),
26
+ ...Object.values(evmAssets.celoSepolia),
27
+ ]
28
+
29
+ let tokenSymbol = currency ?? ''
30
+ if (chain && currency) {
31
+ try {
32
+ const client = createClient({ chain, transport: http() })
33
+ const symbol = await readContract(client, {
34
+ address: currency as Address,
35
+ abi: erc20Abi,
36
+ functionName: 'symbol',
37
+ })
38
+ if (symbol) tokenSymbol = symbol
39
+ } catch {}
40
+ }
41
+
42
+ return {
43
+ tokenSymbol,
44
+ tokenDecimals: (methodDetails?.decimals as number | undefined) ?? 6,
45
+ explorerUrl: chain?.blockExplorers?.default?.url,
46
+ methods: [...evmMethods({ account, currencies: knownCurrencies })],
47
+ }
48
+ },
49
+ })
50
+ }
51
+
52
+ function resolveChain(chainId: number): Chain | undefined {
53
+ const all = Object.values(viemChains) as Chain[]
54
+ return all.find((c) => c.id === chainId)
55
+ }
@@ -1,3 +1,4 @@
1
+ export { evm } from './evm.js'
1
2
  export { createPlugin, type Plugin } from './plugin.js'
2
3
  export { stripe } from './stripe.js'
3
4
  export { tempo } from './tempo.js'
@@ -42,6 +42,11 @@ const booleanOption = z.union([
42
42
  z.literal('true').transform(() => true),
43
43
  z.literal('false').transform(() => false),
44
44
  ])
45
+
46
+ async function writeResponseBody(response: Response) {
47
+ process.stdout.write(Buffer.from(await response.arrayBuffer()))
48
+ }
49
+
45
50
  const tempoOptionSchema = z.object({
46
51
  autoSwap: z.optional(booleanOption),
47
52
  channel: z.optional(z.coerce.string()),
@@ -425,8 +430,7 @@ export function tempo() {
425
430
  })
426
431
  } else {
427
432
  // Non-SSE: print body, then close channel
428
- const body = (await credentialResponse.text()).replace(/\n+$/, '')
429
- console.log(body)
433
+ await writeResponseBody(credentialResponse)
430
434
 
431
435
  if (channelId && escrowContract && chainId) {
432
436
  if (confirmEnabled) info('\n')