mppx 0.6.25 → 0.6.27

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 (96) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/Method.d.ts +6 -4
  3. package/dist/Method.d.ts.map +1 -1
  4. package/dist/Method.js +2 -1
  5. package/dist/Method.js.map +1 -1
  6. package/dist/cli/cli.js +1 -1
  7. package/dist/cli/cli.js.map +1 -1
  8. package/dist/cli/utils.js +2 -2
  9. package/dist/cli/utils.js.map +1 -1
  10. package/dist/middlewares/internal/mppx.d.ts +3 -2
  11. package/dist/middlewares/internal/mppx.d.ts.map +1 -1
  12. package/dist/middlewares/internal/mppx.js +1 -0
  13. package/dist/middlewares/internal/mppx.js.map +1 -1
  14. package/dist/server/Mppx.d.ts +8 -3
  15. package/dist/server/Mppx.d.ts.map +1 -1
  16. package/dist/server/Mppx.js +4 -1
  17. package/dist/server/Mppx.js.map +1 -1
  18. package/dist/stripe/server/Charge.d.ts +1 -1
  19. package/dist/stripe/server/Charge.d.ts.map +1 -1
  20. package/dist/stripe/server/Methods.d.ts +1 -1
  21. package/dist/stripe/server/Methods.d.ts.map +1 -1
  22. package/dist/stripe/server/internal/html.gen.d.ts +1 -1
  23. package/dist/stripe/server/internal/html.gen.js +1 -1
  24. package/dist/tempo/Methods.d.ts +3 -2
  25. package/dist/tempo/Methods.d.ts.map +1 -1
  26. package/dist/tempo/Methods.js +13 -4
  27. package/dist/tempo/Methods.js.map +1 -1
  28. package/dist/tempo/client/ChannelOps.d.ts.map +1 -1
  29. package/dist/tempo/client/ChannelOps.js +7 -2
  30. package/dist/tempo/client/ChannelOps.js.map +1 -1
  31. package/dist/tempo/client/Charge.d.ts.map +1 -1
  32. package/dist/tempo/client/Charge.js +6 -4
  33. package/dist/tempo/client/Charge.js.map +1 -1
  34. package/dist/tempo/client/Session.js +1 -1
  35. package/dist/tempo/client/Session.js.map +1 -1
  36. package/dist/tempo/client/Subscription.d.ts +3 -2
  37. package/dist/tempo/client/Subscription.d.ts.map +1 -1
  38. package/dist/tempo/client/Subscription.js +1 -1
  39. package/dist/tempo/client/Subscription.js.map +1 -1
  40. package/dist/tempo/server/Charge.d.ts +1 -1
  41. package/dist/tempo/server/Charge.d.ts.map +1 -1
  42. package/dist/tempo/server/Charge.js +1 -1
  43. package/dist/tempo/server/Charge.js.map +1 -1
  44. package/dist/tempo/server/Methods.d.ts +2 -2
  45. package/dist/tempo/server/Methods.d.ts.map +1 -1
  46. package/dist/tempo/server/Session.d.ts +1 -1
  47. package/dist/tempo/server/Session.d.ts.map +1 -1
  48. package/dist/tempo/server/Session.js +1 -1
  49. package/dist/tempo/server/Session.js.map +1 -1
  50. package/dist/tempo/server/Subscription.d.ts +28 -12
  51. package/dist/tempo/server/Subscription.d.ts.map +1 -1
  52. package/dist/tempo/server/Subscription.js +83 -31
  53. package/dist/tempo/server/Subscription.js.map +1 -1
  54. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  55. package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
  56. package/dist/tempo/server/internal/html.gen.js +1 -1
  57. package/dist/tempo/server/internal/html.gen.js.map +1 -1
  58. package/dist/tempo/session/Chain.d.ts.map +1 -1
  59. package/dist/tempo/session/Chain.js +6 -1
  60. package/dist/tempo/session/Chain.js.map +1 -1
  61. package/dist/tempo/subscription/KeyAuthorization.d.ts +3 -14
  62. package/dist/tempo/subscription/KeyAuthorization.d.ts.map +1 -1
  63. package/dist/tempo/subscription/KeyAuthorization.js +11 -20
  64. package/dist/tempo/subscription/KeyAuthorization.js.map +1 -1
  65. package/dist/tempo/subscription/Types.d.ts +2 -2
  66. package/dist/tempo/subscription/Types.d.ts.map +1 -1
  67. package/package.json +3 -3
  68. package/src/Method.ts +21 -5
  69. package/src/cli/cli.ts +1 -1
  70. package/src/cli/utils.test.ts +1 -1
  71. package/src/cli/utils.ts +2 -2
  72. package/src/middlewares/internal/mppx.test.ts +24 -0
  73. package/src/middlewares/internal/mppx.ts +6 -2
  74. package/src/server/Mppx.ts +17 -4
  75. package/src/stripe/server/internal/html.gen.ts +1 -1
  76. package/src/tempo/Methods.test.ts +17 -0
  77. package/src/tempo/Methods.ts +12 -4
  78. package/src/tempo/client/ChannelOps.ts +6 -2
  79. package/src/tempo/client/Charge.ts +5 -4
  80. package/src/tempo/client/Session.ts +1 -1
  81. package/src/tempo/client/Subscription.test.ts +5 -7
  82. package/src/tempo/client/Subscription.ts +1 -1
  83. package/src/tempo/server/Charge.ts +1 -1
  84. package/src/tempo/server/Session.ts +1 -1
  85. package/src/tempo/server/Subscription.test.ts +2 -5
  86. package/src/tempo/server/Subscription.ts +157 -68
  87. package/src/tempo/server/internal/html/main.ts +1 -1
  88. package/src/tempo/server/internal/html/package.json +2 -2
  89. package/src/tempo/server/internal/html.gen.ts +1 -1
  90. package/src/tempo/session/Chain.test.ts +16 -8
  91. package/src/tempo/session/Chain.ts +6 -1
  92. package/src/tempo/subscription/KeyAuthorization.test.ts +13 -4
  93. package/src/tempo/subscription/KeyAuthorization.ts +11 -20
  94. package/src/tempo/subscription/Types.ts +2 -2
  95. package/src/viem/Account.test.ts +1 -1
  96. package/src/viem/Client.test.ts +1 -1
@@ -498,10 +498,12 @@ describe.runIf(isLocalnet)('on-chain', () => {
498
498
  { to: currency, data: approveData },
499
499
  { to: escrowContract, data: openData },
500
500
  ],
501
- feePayer: true,
502
501
  feeToken: currency,
502
+ nonceKey: 'expiring',
503
+ validBefore: Math.floor(Date.now() / 1_000) + 25,
503
504
  } as never)
504
- prepared.gas = prepared.gas! + 5_000n
505
+ prepared.gas = (prepared.gas ?? 0n) + 5_000n
506
+ ;(prepared as Record<string, unknown>).feePayer = true
505
507
  const serializedTransaction = await signTransaction(client, prepared as never)
506
508
 
507
509
  await broadcastOpenTransaction({
@@ -561,10 +563,12 @@ describe.runIf(isLocalnet)('on-chain', () => {
561
563
  { to: escrowContract, data: openData },
562
564
  { to: escrowContract, data: smuggledOpenData },
563
565
  ],
564
- feePayer: true,
565
566
  feeToken: currency,
567
+ nonceKey: 'expiring',
568
+ validBefore: Math.floor(Date.now() / 1_000) + 25,
566
569
  } as never)
567
- prepared.gas = prepared.gas! + 5_000n
570
+ prepared.gas = (prepared.gas ?? 0n) + 5_000n
571
+ ;(prepared as Record<string, unknown>).feePayer = true
568
572
 
569
573
  const serializedTransaction = await signTransaction(client, prepared as never)
570
574
 
@@ -1006,10 +1010,12 @@ describe.runIf(isLocalnet)('on-chain', () => {
1006
1010
  { to: currency, data: approveData },
1007
1011
  { to: escrowContract, data: topUpData },
1008
1012
  ],
1009
- feePayer: true,
1010
1013
  feeToken: currency,
1014
+ nonceKey: 'expiring',
1015
+ validBefore: Math.floor(Date.now() / 1_000) + 25,
1011
1016
  } as never)
1012
- prepared.gas = prepared.gas! + 5_000n
1017
+ prepared.gas = (prepared.gas ?? 0n) + 5_000n
1018
+ ;(prepared as Record<string, unknown>).feePayer = true
1013
1019
  const serializedTransaction = await signTransaction(client, prepared as never)
1014
1020
 
1015
1021
  await broadcastTopUpTransaction({
@@ -1069,10 +1075,12 @@ describe.runIf(isLocalnet)('on-chain', () => {
1069
1075
  { to: escrowContract, data: topUpData },
1070
1076
  { to: escrowContract, data: smuggledTopUpData },
1071
1077
  ],
1072
- feePayer: true,
1073
1078
  feeToken: currency,
1079
+ nonceKey: 'expiring',
1080
+ validBefore: Math.floor(Date.now() / 1_000) + 25,
1074
1081
  } as never)
1075
- prepared.gas = prepared.gas! + 5_000n
1082
+ prepared.gas = (prepared.gas ?? 0n) + 5_000n
1083
+ ;(prepared as Record<string, unknown>).feePayer = true
1076
1084
 
1077
1085
  const serializedTransaction = await signTransaction(client, prepared as never)
1078
1086
 
@@ -258,9 +258,14 @@ async function sendFeePayerTx(
258
258
  const prepared = await prepareTransactionRequest(client, {
259
259
  account,
260
260
  calls: [{ to, data }],
261
- feePayer: true,
262
261
  ...(feeToken ? { feeToken } : {}),
262
+ nonceKey: 'expiring',
263
+ validBefore: Math.floor(Date.now() / 1_000) + 25,
263
264
  } as never)
265
+ // Estimate before enabling fee-payer mode so Tempo includes sender
266
+ // signature verification costs in the gas budget.
267
+ prepared.gas = (prepared.gas ?? 0n) + 5_000n
268
+ ;(prepared as Record<string, unknown>).feePayer = true
264
269
 
265
270
  const serialized = (await signTransaction(client, {
266
271
  ...prepared,
@@ -11,6 +11,8 @@ import {
11
11
  toSubscriptionExpiryDate,
12
12
  toSubscriptionExpirySeconds,
13
13
  toSubscriptionPeriodSeconds,
14
+ transferSelector,
15
+ transferWithMemoSelector,
14
16
  verifySubscriptionKeyAuthorization,
15
17
  } from './KeyAuthorization.js'
16
18
  import type { SubscriptionAccessKey } from './Types.js'
@@ -89,13 +91,12 @@ describe('tempo subscription key authorization', () => {
89
91
  const request = parseRequest()
90
92
 
91
93
  expect(getSubscriptionScopes(request)).toMatchObject([
92
- { address: currency, recipients: [recipient] },
93
- { address: currency, recipients: [recipient] },
94
+ { address: currency, recipients: [recipient], selector: transferWithMemoSelector },
94
95
  ])
95
96
  expect(getSubscriptionRpcAllowedCalls(request)).toMatchObject([
96
97
  {
97
98
  target: currency,
98
- selectorRules: [{ recipients: [recipient] }, { recipients: [recipient] }],
99
+ selectorRules: [{ recipients: [recipient], selector: transferWithMemoSelector }],
99
100
  },
100
101
  ])
101
102
  })
@@ -158,7 +159,10 @@ describe('tempo subscription key authorization', () => {
158
159
  const authorization = KeyAuthorization.deserialize(payload.signature)
159
160
  const transferOnly = KeyAuthorization.serialize({
160
161
  ...authorization,
161
- scopes: authorization.scopes?.slice(0, 1),
162
+ scopes: authorization.scopes?.map((scope) => ({
163
+ ...scope,
164
+ selector: transferSelector,
165
+ })),
162
166
  })
163
167
 
164
168
  expect(() =>
@@ -183,6 +187,11 @@ describe('tempo subscription key authorization', () => {
183
187
  ).toThrow('subscription period cannot be represented exactly by this Tempo client')
184
188
  })
185
189
 
190
+ test('accepts dev-prefixed subscription periods for development and tests', () => {
191
+ expect(toSubscriptionPeriodSeconds({ periodCount: '15', periodUnit: 'dev_second' })).toBe(15)
192
+ expect(toSubscriptionPeriodSeconds({ periodCount: '120', periodUnit: 'dev_second' })).toBe(120)
193
+ })
194
+
186
195
  test('rejects subscription expiries that cannot be represented by Tempo key authorizations', () => {
187
196
  expect(() =>
188
197
  toSubscriptionExpirySeconds(toSubscriptionExpiryDate('2026-01-01T00:00:00.500Z')),
@@ -12,12 +12,15 @@ import type {
12
12
  /** 4-byte selector for TIP-20 `transfer(address,uint256)`. */
13
13
  export const transferSelector = '0xa9059cbb'
14
14
 
15
- /** 4-byte selector for TIP-20 `transferWithMemo(address,uint256,bytes)`. */
15
+ /** 4-byte selector for TIP-20 `transferWithMemo(address,uint256,bytes32)`. */
16
16
  export const transferWithMemoSelector = '0x95777d59'
17
17
 
18
18
  const uint64Max = (1n << 64n) - 1n
19
- const secondsPerDay = 86_400n
20
- const secondsPerWeek = 604_800n
19
+ const subscriptionPeriodUnitSeconds = {
20
+ dev_second: 1n,
21
+ day: 86_400n,
22
+ week: 604_800n,
23
+ } satisfies Record<SubscriptionPeriodUnit, bigint>
21
24
 
22
25
  type SubscriptionRequest = ReturnType<typeof Methods.subscription.schema.request.parse>
23
26
  type Authorization = KeyAuthorization.KeyAuthorization
@@ -63,11 +66,11 @@ export function toSubscriptionPeriodSeconds(request: {
63
66
  if (!/^[1-9]\d*$/.test(request.periodCount)) {
64
67
  throw new VerificationFailedError({ reason: 'periodCount is invalid' })
65
68
  }
66
- if (request.periodUnit !== 'day' && request.periodUnit !== 'week') {
69
+ const unitSeconds = subscriptionPeriodUnitSeconds[request.periodUnit]
70
+ if (unitSeconds === undefined) {
67
71
  throw new VerificationFailedError({ reason: 'periodUnit is invalid' })
68
72
  }
69
73
 
70
- const unitSeconds = request.periodUnit === 'day' ? secondsPerDay : secondsPerWeek
71
74
  const value = BigInt(request.periodCount) * unitSeconds
72
75
  if (value > uint64Max) {
73
76
  throw new VerificationFailedError({
@@ -113,11 +116,6 @@ export function getSubscriptionScopes(
113
116
  const currency = normalizeAddress(request.currency, 'currency')
114
117
  const recipient = normalizeAddress(request.recipient, 'recipient')
115
118
  return [
116
- {
117
- address: currency,
118
- selector: transferSelector,
119
- recipients: [recipient],
120
- },
121
119
  {
122
120
  address: currency,
123
121
  selector: transferWithMemoSelector,
@@ -130,15 +128,11 @@ export function getSubscriptionScopes(
130
128
  export function getSubscriptionRpcAllowedCalls(
131
129
  request: Pick<SubscriptionRequest, 'currency' | 'recipient'>,
132
130
  ) {
133
- const [transfer, transferWithMemo] = getSubscriptionScopes(request)
131
+ const [transferWithMemo] = getSubscriptionScopes(request)
134
132
  return [
135
133
  {
136
134
  target: normalizeAddress(request.currency, 'currency'),
137
135
  selectorRules: [
138
- {
139
- selector: transfer.selector,
140
- recipients: transfer.recipients,
141
- },
142
136
  {
143
137
  selector: transferWithMemo.selector,
144
138
  recipients: transferWithMemo.recipients,
@@ -325,9 +319,9 @@ function assertAuthorizationScopes(
325
319
  scopes: readonly KeyAuthorization.Scope[] | undefined,
326
320
  request: Pick<SubscriptionRequest, 'currency' | 'recipient'>,
327
321
  ) {
328
- if (!scopes || scopes.length < 1 || scopes.length > 2) {
322
+ if (!scopes || scopes.length !== 1) {
329
323
  throw new VerificationFailedError({
330
- reason: 'keyAuthorization must contain recipient-scoped transfer calls',
324
+ reason: 'keyAuthorization must contain a recipient-scoped transferWithMemo call',
331
325
  })
332
326
  }
333
327
 
@@ -353,9 +347,6 @@ function assertAuthorizationScopes(
353
347
  }
354
348
  }
355
349
 
356
- if (!seen.has(transferSelector)) {
357
- throw new VerificationFailedError({ reason: 'keyAuthorization must allow transfer' })
358
- }
359
350
  if (!seen.has(transferWithMemoSelector)) {
360
351
  throw new VerificationFailedError({ reason: 'keyAuthorization must allow transferWithMemo' })
361
352
  }
@@ -1,7 +1,7 @@
1
1
  import type { Address } from 'viem'
2
2
 
3
- /** Tempo-supported subscription period units. The shared intent also defines `month`, but Tempo cannot represent calendar-month periods exactly. */
4
- export type SubscriptionPeriodUnit = 'day' | 'week'
3
+ /** Tempo-supported subscription period units. Use `day` or `week` in production; `dev_*` units are for development and tests. */
4
+ export type SubscriptionPeriodUnit = 'dev_second' | 'day' | 'week'
5
5
 
6
6
  /** Access key information used to authorize recurring Tempo payments. */
7
7
  export type SubscriptionAccessKey = {
@@ -1,6 +1,6 @@
1
1
  import { createClient, http } from 'viem'
2
2
  import { privateKeyToAccount } from 'viem/accounts'
3
- import { mainnet } from 'viem/chains'
3
+ import { tempo as mainnet } from 'viem/tempo/chains'
4
4
  import { describe, expect, test } from 'vp/test'
5
5
 
6
6
  import * as Account from './Account.js'
@@ -1,8 +1,8 @@
1
1
  import { createClient, custom, defineChain, type Hex } from 'viem'
2
2
  import { privateKeyToAccount } from 'viem/accounts'
3
3
  import { signTransaction } from 'viem/actions'
4
- import { tempoLocalnet } from 'viem/chains'
5
4
  import { Account as TempoAccount, Transaction } from 'viem/tempo'
5
+ import { tempoLocalnet } from 'viem/tempo/chains'
6
6
  import { describe, expect, test } from 'vp/test'
7
7
 
8
8
  import * as Client from './Client.js'