mppx 0.7.0 → 0.8.0

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 (278) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +20 -11
  3. package/dist/Challenge.d.ts.map +1 -1
  4. package/dist/Challenge.js +18 -6
  5. package/dist/Challenge.js.map +1 -1
  6. package/dist/Mcp.d.ts +3 -0
  7. package/dist/Mcp.d.ts.map +1 -1
  8. package/dist/Mcp.js +2 -0
  9. package/dist/Mcp.js.map +1 -1
  10. package/dist/PaymentRequest.d.ts +10 -10
  11. package/dist/PaymentRequest.js +8 -8
  12. package/dist/client/Mppx.js +2 -2
  13. package/dist/client/Mppx.js.map +1 -1
  14. package/dist/client/Transport.d.ts +11 -16
  15. package/dist/client/Transport.d.ts.map +1 -1
  16. package/dist/client/Transport.js +55 -75
  17. package/dist/client/Transport.js.map +1 -1
  18. package/dist/client/index.d.ts +3 -0
  19. package/dist/client/index.d.ts.map +1 -1
  20. package/dist/client/index.js +1 -0
  21. package/dist/client/index.js.map +1 -1
  22. package/dist/client/internal/Fetch.d.ts.map +1 -1
  23. package/dist/client/internal/Fetch.js +46 -7
  24. package/dist/client/internal/Fetch.js.map +1 -1
  25. package/dist/client/internal/protocols/Mcp.d.ts +7 -0
  26. package/dist/client/internal/protocols/Mcp.d.ts.map +1 -0
  27. package/dist/client/internal/protocols/Mcp.js +159 -0
  28. package/dist/client/internal/protocols/Mcp.js.map +1 -0
  29. package/dist/client/internal/protocols/Mpp.d.ts +4 -0
  30. package/dist/client/internal/protocols/Mpp.d.ts.map +1 -0
  31. package/dist/client/internal/protocols/Mpp.js +18 -0
  32. package/dist/client/internal/protocols/Mpp.js.map +1 -0
  33. package/dist/client/internal/protocols/Protocol.d.ts +10 -0
  34. package/dist/client/internal/protocols/Protocol.d.ts.map +1 -0
  35. package/dist/client/internal/protocols/Protocol.js +2 -0
  36. package/dist/client/internal/protocols/Protocol.js.map +1 -0
  37. package/dist/client/internal/protocols/Shared.d.ts +5 -0
  38. package/dist/client/internal/protocols/Shared.d.ts.map +1 -0
  39. package/dist/client/internal/protocols/Shared.js +20 -0
  40. package/dist/client/internal/protocols/Shared.js.map +1 -0
  41. package/dist/client/internal/protocols/X402.d.ts +8 -0
  42. package/dist/client/internal/protocols/X402.d.ts.map +1 -0
  43. package/dist/client/internal/protocols/X402.js +39 -0
  44. package/dist/client/internal/protocols/X402.js.map +1 -0
  45. package/dist/evm/client/index.d.ts +1 -0
  46. package/dist/evm/client/index.d.ts.map +1 -1
  47. package/dist/evm/client/index.js +1 -0
  48. package/dist/evm/client/index.js.map +1 -1
  49. package/dist/evm/index.d.ts +2 -0
  50. package/dist/evm/index.d.ts.map +1 -1
  51. package/dist/evm/index.js +2 -0
  52. package/dist/evm/index.js.map +1 -1
  53. package/dist/evm/server/index.d.ts +1 -0
  54. package/dist/evm/server/index.d.ts.map +1 -1
  55. package/dist/evm/server/index.js +1 -0
  56. package/dist/evm/server/index.js.map +1 -1
  57. package/dist/mcp/client/McpClient.d.ts +101 -0
  58. package/dist/mcp/client/McpClient.d.ts.map +1 -0
  59. package/dist/mcp/client/McpClient.js +162 -0
  60. package/dist/mcp/client/McpClient.js.map +1 -0
  61. package/dist/mcp/client/index.d.ts.map +1 -0
  62. package/dist/mcp/client/index.js.map +1 -0
  63. package/dist/mcp/server/Transport.d.ts.map +1 -0
  64. package/dist/mcp/server/Transport.js.map +1 -0
  65. package/dist/mcp/server/index.d.ts.map +1 -0
  66. package/dist/mcp/server/index.js.map +1 -0
  67. package/dist/server/Mppx.d.ts +1 -1
  68. package/dist/server/Mppx.d.ts.map +1 -1
  69. package/dist/server/Mppx.js +9 -0
  70. package/dist/server/Mppx.js.map +1 -1
  71. package/dist/server/Transport.d.ts +1 -1
  72. package/dist/server/Transport.d.ts.map +1 -1
  73. package/dist/server/Transport.js +1 -1
  74. package/dist/server/Transport.js.map +1 -1
  75. package/dist/stripe/server/internal/html.gen.d.ts +1 -1
  76. package/dist/stripe/server/internal/html.gen.d.ts.map +1 -1
  77. package/dist/stripe/server/internal/html.gen.js +1 -1
  78. package/dist/stripe/server/internal/html.gen.js.map +1 -1
  79. package/dist/tempo/Proof.d.ts +85 -1
  80. package/dist/tempo/Proof.d.ts.map +1 -1
  81. package/dist/tempo/Proof.js +35 -0
  82. package/dist/tempo/Proof.js.map +1 -1
  83. package/dist/tempo/client/Charge.d.ts +13 -1
  84. package/dist/tempo/client/Charge.d.ts.map +1 -1
  85. package/dist/tempo/client/Charge.js +38 -25
  86. package/dist/tempo/client/Charge.js.map +1 -1
  87. package/dist/tempo/client/Methods.d.ts +5 -3
  88. package/dist/tempo/client/Methods.d.ts.map +1 -1
  89. package/dist/tempo/client/Methods.js +4 -2
  90. package/dist/tempo/client/Methods.js.map +1 -1
  91. package/dist/tempo/client/ResolveAccount.d.ts +40 -0
  92. package/dist/tempo/client/ResolveAccount.d.ts.map +1 -0
  93. package/dist/tempo/client/ResolveAccount.js +2 -0
  94. package/dist/tempo/client/ResolveAccount.js.map +1 -0
  95. package/dist/tempo/internal/fee-payer.d.ts +9 -1
  96. package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
  97. package/dist/tempo/internal/fee-payer.js +35 -6
  98. package/dist/tempo/internal/fee-payer.js.map +1 -1
  99. package/dist/tempo/internal/proof.d.ts +71 -5
  100. package/dist/tempo/internal/proof.d.ts.map +1 -1
  101. package/dist/tempo/internal/proof.js +42 -6
  102. package/dist/tempo/internal/proof.js.map +1 -1
  103. package/dist/tempo/legacy/client/SessionManager.d.ts.map +1 -1
  104. package/dist/tempo/legacy/client/SessionManager.js +10 -3
  105. package/dist/tempo/legacy/client/SessionManager.js.map +1 -1
  106. package/dist/tempo/server/Charge.d.ts.map +1 -1
  107. package/dist/tempo/server/Charge.js +42 -18
  108. package/dist/tempo/server/Charge.js.map +1 -1
  109. package/dist/tempo/server/Methods.d.ts +4 -2
  110. package/dist/tempo/server/Methods.d.ts.map +1 -1
  111. package/dist/tempo/server/Methods.js +4 -2
  112. package/dist/tempo/server/Methods.js.map +1 -1
  113. package/dist/tempo/server/Subscription.d.ts +10 -0
  114. package/dist/tempo/server/Subscription.d.ts.map +1 -1
  115. package/dist/tempo/server/Subscription.js +135 -23
  116. package/dist/tempo/server/Subscription.js.map +1 -1
  117. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  118. package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
  119. package/dist/tempo/server/internal/html.gen.js +1 -1
  120. package/dist/tempo/server/internal/html.gen.js.map +1 -1
  121. package/dist/tempo/session/client/ChannelOps.d.ts +2 -3
  122. package/dist/tempo/session/client/ChannelOps.d.ts.map +1 -1
  123. package/dist/tempo/session/client/ChannelOps.js +7 -10
  124. package/dist/tempo/session/client/ChannelOps.js.map +1 -1
  125. package/dist/tempo/session/client/ChannelStore.d.ts +51 -0
  126. package/dist/tempo/session/client/ChannelStore.d.ts.map +1 -0
  127. package/dist/tempo/session/client/ChannelStore.js +63 -0
  128. package/dist/tempo/session/client/ChannelStore.js.map +1 -0
  129. package/dist/tempo/session/client/CredentialState.d.ts +7 -24
  130. package/dist/tempo/session/client/CredentialState.d.ts.map +1 -1
  131. package/dist/tempo/session/client/CredentialState.js +51 -49
  132. package/dist/tempo/session/client/CredentialState.js.map +1 -1
  133. package/dist/tempo/session/client/Session.d.ts +8 -2
  134. package/dist/tempo/session/client/Session.d.ts.map +1 -1
  135. package/dist/tempo/session/client/Session.js +22 -8
  136. package/dist/tempo/session/client/Session.js.map +1 -1
  137. package/dist/tempo/session/client/SessionManager.d.ts +4 -40
  138. package/dist/tempo/session/client/SessionManager.d.ts.map +1 -1
  139. package/dist/tempo/session/client/SessionManager.js +124 -174
  140. package/dist/tempo/session/client/SessionManager.js.map +1 -1
  141. package/dist/tempo/session/client/index.d.ts +3 -4
  142. package/dist/tempo/session/client/index.d.ts.map +1 -1
  143. package/dist/tempo/session/client/index.js +1 -0
  144. package/dist/tempo/session/client/index.js.map +1 -1
  145. package/dist/tempo/session/precompile/Voucher.d.ts +3 -3
  146. package/dist/tempo/session/precompile/Voucher.d.ts.map +1 -1
  147. package/dist/tempo/session/precompile/Voucher.js +24 -25
  148. package/dist/tempo/session/precompile/Voucher.js.map +1 -1
  149. package/dist/tempo/session/server/Settlement.d.ts.map +1 -1
  150. package/dist/tempo/session/server/Settlement.js +4 -2
  151. package/dist/tempo/session/server/Settlement.js.map +1 -1
  152. package/dist/tempo/session/server/Sse.d.ts.map +1 -1
  153. package/dist/tempo/session/server/Sse.js.map +1 -1
  154. package/dist/tempo/session/server/Ws.d.ts.map +1 -1
  155. package/dist/tempo/session/server/Ws.js.map +1 -1
  156. package/dist/tempo/subscription/KeyAuthorization.d.ts +712 -1
  157. package/dist/tempo/subscription/KeyAuthorization.d.ts.map +1 -1
  158. package/dist/tempo/subscription/Store.d.ts +2 -0
  159. package/dist/tempo/subscription/Store.d.ts.map +1 -1
  160. package/dist/tempo/subscription/Store.js +16 -1
  161. package/dist/tempo/subscription/Store.js.map +1 -1
  162. package/dist/x402/index.d.ts +1 -0
  163. package/dist/x402/index.d.ts.map +1 -1
  164. package/dist/x402/index.js +1 -0
  165. package/dist/x402/index.js.map +1 -1
  166. package/package.json +21 -10
  167. package/src/Challenge.test.ts +40 -0
  168. package/src/Challenge.ts +19 -6
  169. package/src/Mcp.ts +4 -0
  170. package/src/PaymentRequest.ts +10 -10
  171. package/src/cli/cli.test.ts +15 -15
  172. package/src/client/Mppx.test-d.ts +21 -1
  173. package/src/client/Mppx.test.ts +1 -1
  174. package/src/client/Mppx.ts +2 -2
  175. package/src/client/Transport.test.ts +225 -178
  176. package/src/client/Transport.ts +77 -83
  177. package/src/client/index.ts +14 -0
  178. package/src/client/internal/Fetch.test.ts +207 -2
  179. package/src/client/internal/Fetch.ts +52 -6
  180. package/src/client/internal/protocols/Mcp.test.ts +220 -0
  181. package/src/client/internal/protocols/Mcp.ts +162 -0
  182. package/src/client/internal/protocols/Mpp.ts +21 -0
  183. package/src/client/internal/protocols/Protocol.ts +10 -0
  184. package/src/client/internal/protocols/Shared.ts +25 -0
  185. package/src/client/internal/protocols/X402.ts +42 -0
  186. package/src/discovery/OpenApi.test.ts +1 -1
  187. package/src/evm/PublicInterface.test-d.ts +1 -1
  188. package/src/evm/client/index.ts +1 -0
  189. package/src/evm/index.ts +2 -0
  190. package/src/evm/server/Charge.test.ts +1 -1
  191. package/src/evm/server/index.ts +1 -0
  192. package/src/{mcp-sdk → mcp}/client/McpClient.integration.test.ts +10 -4
  193. package/src/{mcp-sdk → mcp}/client/McpClient.test-d.ts +45 -18
  194. package/src/{mcp-sdk → mcp}/client/McpClient.test.ts +211 -5
  195. package/src/mcp/client/McpClient.ts +307 -0
  196. package/src/{mcp-sdk → mcp}/client/McpClient.unit.test.ts +9 -5
  197. package/src/middlewares/elysia.test.ts +1 -1
  198. package/src/middlewares/express.test.ts +1 -1
  199. package/src/middlewares/hono.test.ts +1 -1
  200. package/src/middlewares/internal/mppx.test.ts +1 -1
  201. package/src/middlewares/nextjs.test.ts +1 -1
  202. package/src/proxy/Proxy.test.ts +1 -1
  203. package/src/proxy/services/anthropic.test.ts +1 -1
  204. package/src/proxy/services/openai.test.ts +1 -1
  205. package/src/proxy/services/stripe.test.ts +1 -1
  206. package/src/server/Mppx.authorize.test.ts +1 -1
  207. package/src/server/Mppx.test-d.ts +1 -1
  208. package/src/server/Mppx.test.ts +20 -2
  209. package/src/server/Mppx.ts +14 -1
  210. package/src/server/Transport.test.ts +6 -6
  211. package/src/server/Transport.ts +1 -1
  212. package/src/stripe/Charge.integration.test.ts +1 -1
  213. package/src/stripe/client/Charge.test.ts +1 -1
  214. package/src/stripe/server/Charge.test.ts +1 -1
  215. package/src/stripe/server/internal/html/package.json +1 -1
  216. package/src/stripe/server/internal/html.gen.ts +1 -1
  217. package/src/tempo/Proof.conformance.test.ts +146 -0
  218. package/src/tempo/Proof.test-d.ts +15 -0
  219. package/src/tempo/Proof.ts +52 -1
  220. package/src/tempo/Subscription.integration.test.ts +1 -1
  221. package/src/tempo/client/Charge.test.ts +173 -0
  222. package/src/tempo/client/Charge.ts +65 -36
  223. package/src/tempo/client/Methods.ts +4 -2
  224. package/src/tempo/client/ResolveAccount.ts +46 -0
  225. package/src/tempo/internal/fee-payer.test.ts +65 -10
  226. package/src/tempo/internal/fee-payer.ts +42 -6
  227. package/src/tempo/internal/proof.test.ts +12 -4
  228. package/src/tempo/internal/proof.ts +55 -6
  229. package/src/tempo/legacy/client/SessionManager.ts +11 -3
  230. package/src/tempo/legacy/server/Session.test.ts +91 -26
  231. package/src/tempo/server/Charge.test.ts +388 -17
  232. package/src/tempo/server/Charge.ts +46 -24
  233. package/src/tempo/server/Methods.ts +4 -2
  234. package/src/tempo/server/Subscription.test.ts +465 -3
  235. package/src/tempo/server/Subscription.ts +174 -19
  236. package/src/tempo/server/internal/html/package.json +2 -2
  237. package/src/tempo/server/internal/html.gen.ts +1 -1
  238. package/src/tempo/session/client/ChannelOps.ts +5 -19
  239. package/src/tempo/session/client/ChannelStore.ts +111 -0
  240. package/src/tempo/session/client/CredentialState.test.ts +206 -62
  241. package/src/tempo/session/client/CredentialState.ts +58 -73
  242. package/src/tempo/session/client/Session.test.ts +41 -1
  243. package/src/tempo/session/client/Session.ts +36 -10
  244. package/src/tempo/session/client/SessionManager.test.ts +154 -65
  245. package/src/tempo/session/client/SessionManager.ts +141 -235
  246. package/src/tempo/session/client/index.ts +8 -5
  247. package/src/tempo/session/precompile/Voucher.test.ts +45 -7
  248. package/src/tempo/session/precompile/Voucher.ts +27 -25
  249. package/src/tempo/session/server/Session.test.ts +4 -4
  250. package/src/tempo/session/server/Settlement.test.ts +88 -1
  251. package/src/tempo/session/server/Settlement.ts +2 -1
  252. package/src/tempo/session/server/Sse.ts +0 -2
  253. package/src/tempo/session/server/Ws.ts +0 -4
  254. package/src/tempo/subscription/Store.ts +27 -9
  255. package/src/x402/Exact.e2e.test.ts +1 -1
  256. package/src/x402/PublicInterface.test-d.ts +1 -1
  257. package/src/x402/index.ts +1 -0
  258. package/dist/mcp-sdk/client/McpClient.d.ts +0 -85
  259. package/dist/mcp-sdk/client/McpClient.d.ts.map +0 -1
  260. package/dist/mcp-sdk/client/McpClient.js +0 -118
  261. package/dist/mcp-sdk/client/McpClient.js.map +0 -1
  262. package/dist/mcp-sdk/client/index.d.ts.map +0 -1
  263. package/dist/mcp-sdk/client/index.js.map +0 -1
  264. package/dist/mcp-sdk/server/Transport.d.ts.map +0 -1
  265. package/dist/mcp-sdk/server/Transport.js.map +0 -1
  266. package/dist/mcp-sdk/server/index.d.ts.map +0 -1
  267. package/dist/mcp-sdk/server/index.js.map +0 -1
  268. package/src/mcp-sdk/client/McpClient.ts +0 -228
  269. /package/dist/{mcp-sdk → mcp}/client/index.d.ts +0 -0
  270. /package/dist/{mcp-sdk → mcp}/client/index.js +0 -0
  271. /package/dist/{mcp-sdk → mcp}/server/Transport.d.ts +0 -0
  272. /package/dist/{mcp-sdk → mcp}/server/Transport.js +0 -0
  273. /package/dist/{mcp-sdk → mcp}/server/index.d.ts +0 -0
  274. /package/dist/{mcp-sdk → mcp}/server/index.js +0 -0
  275. /package/src/{mcp-sdk → mcp}/client/index.ts +0 -0
  276. /package/src/{mcp-sdk → mcp}/server/Transport.test.ts +0 -0
  277. /package/src/{mcp-sdk → mcp}/server/Transport.ts +0 -0
  278. /package/src/{mcp-sdk → mcp}/server/index.ts +0 -0
@@ -1,6 +1,13 @@
1
1
  import { Base64 } from 'ox'
2
2
  import { KeyAuthorization } from 'ox/tempo'
3
- import { encodeFunctionData, isAddressEqual, type Address, type Client as ViemClient } from 'viem'
3
+ import {
4
+ encodeFunctionData,
5
+ isAddressEqual,
6
+ parseEventLogs,
7
+ type Address,
8
+ type Client as ViemClient,
9
+ type TransactionReceipt,
10
+ } from 'viem'
4
11
  import {
5
12
  call as viem_call,
6
13
  prepareTransactionRequest,
@@ -113,6 +120,8 @@ export function subscription<const parameters extends subscription.Parameters>(
113
120
  },
114
121
 
115
122
  async authorize({ input, request }) {
123
+ if (parameters.requireCredential) return undefined
124
+
116
125
  const resolved = await parameters.resolve({ input, request })
117
126
  if (!resolved) return undefined
118
127
 
@@ -182,8 +191,8 @@ export function subscription<const parameters extends subscription.Parameters>(
182
191
  const input = requestFromCaptured(capturedRequest)
183
192
  const resolved = await parameters.resolve({ input, request: parsedRequest })
184
193
  const existing = resolved ? await store.getByKey(resolved.key) : null
185
- const accessKey =
186
- resolved && !credential
194
+ const accessKey = !credential
195
+ ? resolved
187
196
  ? await resolveChallengeAccessKey({
188
197
  existing,
189
198
  input,
@@ -192,7 +201,10 @@ export function subscription<const parameters extends subscription.Parameters>(
192
201
  resolved,
193
202
  store,
194
203
  })
195
- : (credentialRequest?.methodDetails?.accessKey ?? parsedRequest.methodDetails?.accessKey)
204
+ : parameters.requireCredential && !parameters.activate
205
+ ? await createUnboundChallengeAccessKey({ store })
206
+ : undefined
207
+ : (credentialRequest?.methodDetails?.accessKey ?? parsedRequest.methodDetails?.accessKey)
196
208
  if (!accessKey) {
197
209
  throw new VerificationFailedError({ reason: 'subscription accessKey is missing' })
198
210
  }
@@ -218,16 +230,17 @@ export function subscription<const parameters extends subscription.Parameters>(
218
230
  challengeExpires: credential.challenge.expires,
219
231
  request: parsedRequest,
220
232
  })
221
- const resolved = await parameters.resolve({ input, request: parsedRequest })
222
-
223
- if (!resolved) {
224
- throw new VerificationFailedError({ reason: 'subscription could not be resolved' })
225
- }
226
233
  const challengeRequest = credential.challenge.request as SubscriptionRequest
227
- const accessKey =
228
- challengeRequest.methodDetails?.accessKey ??
229
- parsedRequest.methodDetails?.accessKey ??
230
- (await resolveAccessKey({ input, parameters, request: parsedRequest, resolved }))
234
+ let resolved: subscription.ResolvedSubscription | null = null
235
+ let accessKey =
236
+ challengeRequest.methodDetails?.accessKey ?? parsedRequest.methodDetails?.accessKey
237
+ if (!accessKey) {
238
+ resolved = await parameters.resolve({ input, request: parsedRequest })
239
+ if (!resolved) {
240
+ throw new VerificationFailedError({ reason: 'subscription could not be resolved' })
241
+ }
242
+ accessKey = await resolveAccessKey({ input, parameters, request: parsedRequest, resolved })
243
+ }
231
244
  if (!accessKey) {
232
245
  throw new VerificationFailedError({ reason: 'subscription accessKey is missing' })
233
246
  }
@@ -247,10 +260,20 @@ export function subscription<const parameters extends subscription.Parameters>(
247
260
  reason: 'credential source does not match signature',
248
261
  })
249
262
  }
263
+ resolved =
264
+ (await parameters.resolve({ input, request: parsedRequest, source: verified.source })) ??
265
+ resolved
266
+
267
+ if (!resolved) {
268
+ throw new VerificationFailedError({ reason: 'subscription could not be resolved' })
269
+ }
250
270
 
251
271
  const activation = await store.activate({
252
272
  challengeId: credential.challenge.id,
253
- isReusable: (subscription) => isReusableSubscription(subscription, parsedRequest),
273
+ isReusable: (subscription) =>
274
+ parameters.requireCredential
275
+ ? isActiveSubscriptionForRequest(subscription, parsedRequest)
276
+ : isReusableSubscription(subscription, parsedRequest),
254
277
  lookupKey: resolved.key,
255
278
  async create() {
256
279
  const activation = withSubscriptionAccessKey(
@@ -299,7 +322,50 @@ export function subscription<const parameters extends subscription.Parameters>(
299
322
  throw new VerificationFailedError({ reason: 'subscription activation claim mismatch' })
300
323
  }
301
324
  if (activation.status === 'existing') {
302
- return SubscriptionReceipt.fromRecord(activation.subscription)
325
+ const subscription = activation.subscription
326
+ assertSubscriptionPayer(subscription, verified.source, {
327
+ required: parameters.requireCredential,
328
+ })
329
+
330
+ const periodIndex = getPeriodIndex(subscription)
331
+ if (periodIndex > subscription.lastChargedPeriod) {
332
+ const renew = resolveRenewalHandler({
333
+ feePayer,
334
+ feePayerPolicy,
335
+ getClient,
336
+ parameters,
337
+ store,
338
+ subscription,
339
+ waitForConfirmation,
340
+ })
341
+ if (!renew) {
342
+ throw new VerificationFailedError({ reason: 'subscription renewal is required' })
343
+ }
344
+
345
+ const renewal = await settleRenewal({
346
+ expectedLookupKey: resolved.key,
347
+ periodIndex,
348
+ renew,
349
+ request: parsedRequest,
350
+ store,
351
+ subscription,
352
+ })
353
+ if (!renewal) {
354
+ throw new VerificationFailedError({ reason: 'subscription renewal failed' })
355
+ }
356
+ if (renewal.status === 'charged' || renewal.status === 'inFlight') {
357
+ return renewal.receipt
358
+ }
359
+
360
+ await parameters.hooks?.renewed?.({
361
+ periodIndex,
362
+ receipt: renewal.result.receipt,
363
+ subscription: renewal.result.subscription,
364
+ })
365
+ return renewal.result.receipt
366
+ }
367
+
368
+ return SubscriptionReceipt.fromRecord(subscription)
303
369
  }
304
370
 
305
371
  await parameters.hooks?.activated?.({
@@ -380,6 +446,18 @@ async function resolveChallengeAccessKey(parameters: {
380
446
  )
381
447
  }
382
448
 
449
+ async function createUnboundChallengeAccessKey(parameters: {
450
+ store: SubscriptionStore.SubscriptionStore
451
+ }) {
452
+ const accessKey = await parameters.store.getOrCreateAccessKey(
453
+ `challenge:${createSubscriptionId()}`,
454
+ )
455
+ return {
456
+ accessKeyAddress: accessKey.accessKeyAddress,
457
+ keyType: accessKey.keyType,
458
+ } satisfies SubscriptionAccessKey
459
+ }
460
+
383
461
  async function activateSubscription(parameters: {
384
462
  accessKey: SubscriptionAccessKey
385
463
  auto: {
@@ -567,14 +645,20 @@ function isActive(subscription: SubscriptionRecord): boolean {
567
645
  return new Date(subscription.subscriptionExpires).getTime() > Date.now()
568
646
  }
569
647
 
648
+ function isActiveSubscriptionForRequest(
649
+ subscription: SubscriptionRecord,
650
+ request: SubscriptionRequest,
651
+ ): boolean {
652
+ return isActive(subscription) && subscriptionMatchesRequest(subscription, request)
653
+ }
654
+
570
655
  function isReusableSubscription(
571
656
  subscription: SubscriptionRecord,
572
657
  request: SubscriptionRequest,
573
658
  ): boolean {
574
659
  return (
575
- isActive(subscription) &&
576
- getPeriodIndex(subscription) <= subscription.lastChargedPeriod &&
577
- subscriptionMatchesRequest(subscription, request)
660
+ isActiveSubscriptionForRequest(subscription, request) &&
661
+ getPeriodIndex(subscription) <= subscription.lastChargedPeriod
578
662
  )
579
663
  }
580
664
 
@@ -589,6 +673,25 @@ function subscriptionMatchesRequest(
589
673
  )
590
674
  }
591
675
 
676
+ function assertSubscriptionPayer(
677
+ subscription: SubscriptionRecord,
678
+ source: { address: Address; chainId: number },
679
+ options?: { required?: boolean | undefined },
680
+ ) {
681
+ if (!subscription.payer) {
682
+ if (options?.required) {
683
+ throw new VerificationFailedError({ reason: 'subscription payer is missing' })
684
+ }
685
+ return
686
+ }
687
+ if (
688
+ subscription.payer.chainId !== source.chainId ||
689
+ !isAddressEqual(subscription.payer.address, source.address)
690
+ ) {
691
+ throw new VerificationFailedError({ reason: 'subscription payer mismatch' })
692
+ }
693
+ }
694
+
592
695
  function comparableSubscriptionBinding(value: SubscriptionRecord | SubscriptionRequest) {
593
696
  const chainId =
594
697
  'chainId' in value ? value.chainId : (value as SubscriptionRequest).methodDetails?.chainId
@@ -804,7 +907,9 @@ async function submitSubscriptionPayment(parameters: {
804
907
  store,
805
908
  waitForConfirmation,
806
909
  } = parameters
807
- const stored = await store.getAccessKey(lookupKey)
910
+ const stored =
911
+ (await store.getAccessKey(lookupKey)) ??
912
+ (await store.getAccessKeyByAddress(accessKey.accessKeyAddress))
808
913
  if (!stored) {
809
914
  throw new VerificationFailedError({ reason: 'subscription access key is missing' })
810
915
  }
@@ -884,6 +989,8 @@ async function submitSubscriptionPayment(parameters: {
884
989
  } as never)
885
990
 
886
991
  if (!waitForConfirmation) {
992
+ // Optimistic mode has no receipt to inspect, so it cannot detect a T6
993
+ // (TIP-1028) held transfer. Use `waitForConfirmation: true` for T6-safe proof.
887
994
  return sendRawTransaction(client, {
888
995
  serializedTransaction: serializedTransaction as Transaction.TransactionSerializedTempo,
889
996
  })
@@ -897,9 +1004,50 @@ async function submitSubscriptionPayment(parameters: {
897
1004
  reason: `subscription transaction reverted: ${receipt.transactionHash}`,
898
1005
  })
899
1006
  }
1007
+ assertSubscriptionTransfer(receipt, {
1008
+ amount: BigInt(request.amount),
1009
+ currency: request.currency as Address,
1010
+ memo,
1011
+ recipient: request.recipient as Address,
1012
+ })
900
1013
  return receipt.transactionHash
901
1014
  }
902
1015
 
1016
+ /**
1017
+ * Asserts a confirmed subscription payment credited the recipient.
1018
+ *
1019
+ * Transaction success alone is not proof: under Tempo T6 (TIP-1028) a recipient
1020
+ * receive policy can hold the funds in `ReceivePolicyGuard` while the tx still
1021
+ * succeeds. Settlement always emits one `transferWithMemo(recipient, amount,
1022
+ * memo)`, so this requires a matching `TransferWithMemo` log on the expected
1023
+ * token. A held transfer fails because its `to` is the guard, and the memo
1024
+ * binding excludes unrelated transfer effects in the same receipt.
1025
+ */
1026
+ function assertSubscriptionTransfer(
1027
+ receipt: TransactionReceipt,
1028
+ parameters: { amount: bigint; currency: Address; memo: `0x${string}`; recipient: Address },
1029
+ ): void {
1030
+ const { amount, currency, memo, recipient } = parameters
1031
+ const credited = parseEventLogs({
1032
+ abi: Abis.tip20,
1033
+ eventName: 'TransferWithMemo',
1034
+ logs: receipt.logs,
1035
+ }).some(
1036
+ (log) =>
1037
+ isAddressEqual(log.address, currency) &&
1038
+ isAddressEqual(log.args.to, recipient) &&
1039
+ log.args.amount === amount &&
1040
+ log.args.memo.toLowerCase() === memo.toLowerCase(),
1041
+ )
1042
+ if (!credited) {
1043
+ throw new VerificationFailedError({
1044
+ reason:
1045
+ `subscription transfer was not credited to the recipient ` +
1046
+ `(funds may have been held by a receive policy): ${receipt.transactionHash}`,
1047
+ })
1048
+ }
1049
+ }
1050
+
903
1051
  function createSubscriptionId() {
904
1052
  const bytes = new Uint8Array(18)
905
1053
  globalThis.crypto.getRandomValues(bytes)
@@ -1054,6 +1202,11 @@ export declare namespace subscription {
1054
1202
  * Keeps concurrent renewal safe while allowing recovery from abandoned attempts.
1055
1203
  */
1056
1204
  renewalTimeoutMs?: number | undefined
1205
+ /**
1206
+ * Requires a fresh subscription Credential even when a subscription is active.
1207
+ * This binds access reuse to the stored payer instead of trusting request metadata alone.
1208
+ */
1209
+ requireCredential?: boolean | undefined
1057
1210
  /**
1058
1211
  * Override the fee-payer policy for sponsored subscription payments.
1059
1212
  * Useful when the access key + key authorization tx requires more gas
@@ -1101,6 +1254,8 @@ export declare namespace subscription {
1101
1254
  resolve: (parameters: {
1102
1255
  input: Request
1103
1256
  request: SubscriptionRequest
1257
+ /** Verified payer identity recovered from a signed subscription credential. */
1258
+ source?: { address: Address; chainId: number } | undefined
1104
1259
  }) => MaybePromise<ResolvedSubscription | null>
1105
1260
  renew?: (parameters: {
1106
1261
  /** Stable idempotency/reconciliation reference persisted before the renewal hook runs. */
@@ -3,8 +3,8 @@
3
3
  "private": true,
4
4
  "type": "module",
5
5
  "dependencies": {
6
- "accounts": "0.14.6",
6
+ "accounts": "0.14.9",
7
7
  "mppx": "workspace:*",
8
- "viem": "2.50.4"
8
+ "viem": "2.52.2"
9
9
  }
10
10
  }