tapimo 0.6.0 → 0.7.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 (250) hide show
  1. package/dist/App.d.ts +8641 -8263
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +9 -1
  4. package/dist/App.js.map +1 -1
  5. package/dist/Scope.d.ts +4 -0
  6. package/dist/Scope.d.ts.map +1 -1
  7. package/dist/Scope.js +5 -0
  8. package/dist/Scope.js.map +1 -1
  9. package/dist/admin/App.d.ts +112 -11
  10. package/dist/admin/App.d.ts.map +1 -1
  11. package/dist/admin/apps/api-keys.d.ts +2 -1
  12. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  13. package/dist/admin/apps/early-access.d.ts +2 -2
  14. package/dist/admin/apps/early-access.d.ts.map +1 -1
  15. package/dist/admin/apps/early-access.js +5 -5
  16. package/dist/admin/apps/early-access.js.map +1 -1
  17. package/dist/admin/apps/organizations.d.ts +124 -1
  18. package/dist/admin/apps/organizations.d.ts.map +1 -1
  19. package/dist/admin/apps/organizations.js +97 -6
  20. package/dist/admin/apps/organizations.js.map +1 -1
  21. package/dist/admin/ui.generated.d.ts +1 -1
  22. package/dist/admin/ui.generated.d.ts.map +1 -1
  23. package/dist/admin/ui.generated.js +95 -87
  24. package/dist/admin/ui.generated.js.map +1 -1
  25. package/dist/analytics/Analytics.d.ts.map +1 -1
  26. package/dist/analytics/Analytics.js +20 -0
  27. package/dist/analytics/Analytics.js.map +1 -1
  28. package/dist/analytics/tables/requestEvents.d.ts +18 -0
  29. package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
  30. package/dist/analytics/tables/requestEvents.js +36 -7
  31. package/dist/analytics/tables/requestEvents.js.map +1 -1
  32. package/dist/apps/data/App.d.ts +5016 -4985
  33. package/dist/apps/data/App.d.ts.map +1 -1
  34. package/dist/apps/data/FundingQuoteProviders.js +2 -15
  35. package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
  36. package/dist/apps/data/FxOracle.d.ts +1 -1
  37. package/dist/apps/data/FxOracle.d.ts.map +1 -1
  38. package/dist/apps/data/FxOracle.js +1 -1
  39. package/dist/apps/data/FxOracle.js.map +1 -1
  40. package/dist/apps/data/routes/activities.d.ts +27 -27
  41. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  42. package/dist/apps/data/routes/activities.js +21 -1
  43. package/dist/apps/data/routes/activities.js.map +1 -1
  44. package/dist/apps/data/routes/balances.d.ts +58 -58
  45. package/dist/apps/data/routes/blocks.d.ts +39 -39
  46. package/dist/apps/data/routes/coingecko.d.ts +176 -176
  47. package/dist/apps/data/routes/exchanges.d.ts +113 -113
  48. package/dist/apps/data/routes/fee-amm.d.ts +105 -91
  49. package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
  50. package/dist/apps/data/routes/fee-amm.js +76 -91
  51. package/dist/apps/data/routes/fee-amm.js.map +1 -1
  52. package/dist/apps/data/routes/funding.d.ts +72 -34
  53. package/dist/apps/data/routes/funding.d.ts.map +1 -1
  54. package/dist/apps/data/routes/funding.js +34 -26
  55. package/dist/apps/data/routes/funding.js.map +1 -1
  56. package/dist/apps/data/routes/indexer.d.ts +5 -5
  57. package/dist/apps/data/routes/receipts.d.ts +6 -6
  58. package/dist/apps/data/routes/rpc.d.ts +31 -31
  59. package/dist/apps/data/routes/tokens.d.ts +2 -2
  60. package/dist/apps/data/routes/tokens.js +1 -1
  61. package/dist/apps/data/routes/transactions.d.ts +4 -4
  62. package/dist/apps/data/routes/transfers.d.ts +99 -88
  63. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  64. package/dist/apps/data/routes/transfers.js +226 -141
  65. package/dist/apps/data/routes/transfers.js.map +1 -1
  66. package/dist/apps/data/routes/valuation.d.ts +34 -34
  67. package/dist/apps/data/routes/valuation.js +3 -3
  68. package/dist/apps/data/routes/valuation.js.map +1 -1
  69. package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
  70. package/dist/apps/data/routes/webhooks.d.ts +366 -366
  71. package/dist/apps/index.d.ts +1 -0
  72. package/dist/apps/index.d.ts.map +1 -1
  73. package/dist/apps/index.js +1 -0
  74. package/dist/apps/index.js.map +1 -1
  75. package/dist/apps/management/App.d.ts +460 -523
  76. package/dist/apps/management/App.d.ts.map +1 -1
  77. package/dist/apps/management/routes/api-keys.d.ts +7 -23
  78. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  79. package/dist/apps/management/routes/api-keys.js +20 -8
  80. package/dist/apps/management/routes/api-keys.js.map +1 -1
  81. package/dist/apps/management/routes/billing.d.ts +106 -151
  82. package/dist/apps/management/routes/billing.d.ts.map +1 -1
  83. package/dist/apps/management/routes/billing.js +28 -28
  84. package/dist/apps/management/routes/billing.js.map +1 -1
  85. package/dist/apps/management/routes/faucet.d.ts +40 -26
  86. package/dist/apps/management/routes/faucet.d.ts.map +1 -1
  87. package/dist/apps/management/routes/faucet.js +33 -18
  88. package/dist/apps/management/routes/faucet.js.map +1 -1
  89. package/dist/apps/management/routes/invitations.d.ts +131 -131
  90. package/dist/apps/management/routes/invitations.d.ts.map +1 -1
  91. package/dist/apps/management/routes/invitations.js +0 -3
  92. package/dist/apps/management/routes/invitations.js.map +1 -1
  93. package/dist/apps/management/routes/me.d.ts +6 -8
  94. package/dist/apps/management/routes/me.d.ts.map +1 -1
  95. package/dist/apps/management/routes/me.js +0 -4
  96. package/dist/apps/management/routes/me.js.map +1 -1
  97. package/dist/apps/management/routes/members.d.ts +45 -45
  98. package/dist/apps/management/routes/orgs.d.ts +94 -94
  99. package/dist/apps/management/routes/orgs.d.ts.map +1 -1
  100. package/dist/apps/management/routes/orgs.js +5 -0
  101. package/dist/apps/management/routes/orgs.js.map +1 -1
  102. package/dist/apps/management/routes/usage.d.ts +32 -32
  103. package/dist/apps/management/routes/verified-token-requests.d.ts +94 -94
  104. package/dist/apps/management/routes/webhooks.d.ts +1 -1
  105. package/dist/apps/mpp/App.d.ts +363 -0
  106. package/dist/apps/mpp/App.d.ts.map +1 -0
  107. package/dist/apps/mpp/App.js +72 -0
  108. package/dist/apps/mpp/App.js.map +1 -0
  109. package/dist/apps/mpp/Mpp.d.ts +178 -0
  110. package/dist/apps/mpp/Mpp.d.ts.map +1 -0
  111. package/dist/apps/mpp/Mpp.js +183 -0
  112. package/dist/apps/mpp/Mpp.js.map +1 -0
  113. package/dist/apps/mpp/index.d.ts +3 -0
  114. package/dist/apps/mpp/index.d.ts.map +1 -0
  115. package/dist/apps/mpp/index.js +3 -0
  116. package/dist/apps/mpp/index.js.map +1 -0
  117. package/dist/apps/relay/App.d.ts.map +1 -1
  118. package/dist/apps/relay/App.js +56 -9
  119. package/dist/apps/relay/App.js.map +1 -1
  120. package/dist/db/Db.d.ts +2 -0
  121. package/dist/db/Db.d.ts.map +1 -1
  122. package/dist/db/Db.js +27 -0
  123. package/dist/db/Db.js.map +1 -1
  124. package/dist/db/Schema.d.ts +12 -0
  125. package/dist/db/Schema.d.ts.map +1 -1
  126. package/dist/db/Schema.js +11 -0
  127. package/dist/db/Schema.js.map +1 -1
  128. package/dist/db/tables/enabledBillingSources.d.ts +73 -0
  129. package/dist/db/tables/enabledBillingSources.d.ts.map +1 -0
  130. package/dist/db/tables/enabledBillingSources.js +69 -0
  131. package/dist/db/tables/enabledBillingSources.js.map +1 -0
  132. package/dist/db/tables/invitations.d.ts +1 -12
  133. package/dist/db/tables/invitations.d.ts.map +1 -1
  134. package/dist/db/tables/invitations.js +3 -23
  135. package/dist/db/tables/invitations.js.map +1 -1
  136. package/dist/db/tables/organizations.d.ts +5 -2
  137. package/dist/db/tables/organizations.d.ts.map +1 -1
  138. package/dist/db/tables/organizations.js +13 -2
  139. package/dist/db/tables/organizations.js.map +1 -1
  140. package/dist/handlers/internal/utils.d.ts +1 -16
  141. package/dist/handlers/internal/utils.d.ts.map +1 -1
  142. package/dist/handlers/internal/utils.js +17 -6
  143. package/dist/handlers/internal/utils.js.map +1 -1
  144. package/dist/handlers/relay.d.ts.map +1 -1
  145. package/dist/handlers/relay.js +37 -5
  146. package/dist/handlers/relay.js.map +1 -1
  147. package/dist/internal/Auth.d.ts +0 -32
  148. package/dist/internal/Auth.d.ts.map +1 -1
  149. package/dist/internal/Auth.js +3 -51
  150. package/dist/internal/Auth.js.map +1 -1
  151. package/dist/internal/FundingQuotes.d.ts +78 -32
  152. package/dist/internal/FundingQuotes.d.ts.map +1 -1
  153. package/dist/internal/FundingQuotes.js +45 -8
  154. package/dist/internal/FundingQuotes.js.map +1 -1
  155. package/dist/internal/Log.d.ts +18 -4
  156. package/dist/internal/Log.d.ts.map +1 -1
  157. package/dist/internal/Log.js +50 -3
  158. package/dist/internal/Log.js.map +1 -1
  159. package/dist/internal/MetricSink.d.ts.map +1 -1
  160. package/dist/internal/MetricSink.js +15 -0
  161. package/dist/internal/MetricSink.js.map +1 -1
  162. package/dist/internal/Response.d.ts +2 -2
  163. package/dist/internal/Response.d.ts.map +1 -1
  164. package/dist/internal/Response.js +12 -4
  165. package/dist/internal/Response.js.map +1 -1
  166. package/dist/internal/Viem.d.ts +1 -1
  167. package/package.json +7 -2
  168. package/src/App.test.ts +9 -4
  169. package/src/App.ts +14 -2
  170. package/src/Client.test-d.ts +109 -28
  171. package/src/Scope.ts +5 -0
  172. package/src/admin/App.test.ts +4 -0
  173. package/src/admin/apps/early-access.ts +6 -5
  174. package/src/admin/apps/organizations.test.ts +86 -2
  175. package/src/admin/apps/organizations.ts +155 -31
  176. package/src/admin/apps/scopes.test.ts +5 -0
  177. package/src/admin/ui/src/lib/organizations.test.ts +81 -1
  178. package/src/admin/ui/src/lib/organizations.ts +92 -0
  179. package/src/admin/ui/src/routes/api-keys.tsx +1 -1
  180. package/src/admin/ui/src/routes/early-access.tsx +8 -3
  181. package/src/admin/ui/src/routes/organizations_.$orgId.tsx +103 -0
  182. package/src/admin/ui.generated.ts +95 -109
  183. package/src/analytics/Analytics.test.ts +83 -0
  184. package/src/analytics/Analytics.ts +20 -0
  185. package/src/analytics/tables/requestEvents.ts +60 -7
  186. package/src/apps/composition.test.ts +11 -2
  187. package/src/apps/data/App.test.ts +1 -1
  188. package/src/apps/data/FundingQuoteProviders.test.ts +0 -5
  189. package/src/apps/data/FundingQuoteProviders.ts +2 -29
  190. package/src/apps/data/FxOracle.test.ts +2 -2
  191. package/src/apps/data/FxOracle.ts +2 -2
  192. package/src/apps/data/routes/activities.test.ts +6 -2
  193. package/src/apps/data/routes/activities.ts +39 -1
  194. package/src/apps/data/routes/balances.test.ts +6 -2
  195. package/src/apps/data/routes/exchanges.test.ts +7 -3
  196. package/src/apps/data/routes/fee-amm.test.ts +47 -23
  197. package/src/apps/data/routes/fee-amm.ts +98 -119
  198. package/src/apps/data/routes/funding.test.ts +29 -8
  199. package/src/apps/data/routes/funding.ts +42 -48
  200. package/src/apps/data/routes/receipts.test.ts +6 -2
  201. package/src/apps/data/routes/tokens.ts +1 -1
  202. package/src/apps/data/routes/transfers.test.ts +203 -35
  203. package/src/apps/data/routes/transfers.ts +294 -172
  204. package/src/apps/data/routes/valuation.test.ts +18 -3
  205. package/src/apps/data/routes/valuation.ts +4 -4
  206. package/src/apps/index.ts +1 -0
  207. package/src/apps/management/routes/api-keys.test.ts +2 -75
  208. package/src/apps/management/routes/api-keys.ts +30 -7
  209. package/src/apps/management/routes/billing.test.ts +106 -85
  210. package/src/apps/management/routes/billing.ts +32 -21
  211. package/src/apps/management/routes/faucet.test.ts +11 -3
  212. package/src/apps/management/routes/faucet.ts +46 -30
  213. package/src/apps/management/routes/invitations.test.ts +0 -172
  214. package/src/apps/management/routes/invitations.ts +0 -3
  215. package/src/apps/management/routes/me.test.ts +3 -145
  216. package/src/apps/management/routes/me.ts +0 -6
  217. package/src/apps/management/routes/orgs.test.ts +45 -2
  218. package/src/apps/management/routes/orgs.ts +6 -0
  219. package/src/apps/management/routes/scopes.test.ts +1 -0
  220. package/src/apps/mpp/App.ts +86 -0
  221. package/src/apps/mpp/Mpp.test.ts +193 -0
  222. package/src/apps/mpp/Mpp.ts +353 -0
  223. package/src/apps/mpp/index.ts +2 -0
  224. package/src/apps/mpp/internal/Adapter.ts +11 -0
  225. package/src/apps/mpp/internal/Error.ts +9 -0
  226. package/src/apps/mpp/internal/Idempotency.ts +7 -0
  227. package/src/apps/mpp/internal/Screening.ts +10 -0
  228. package/src/apps/relay/App.test.ts +88 -6
  229. package/src/apps/relay/App.ts +61 -9
  230. package/src/db/Db.test.ts +2 -2
  231. package/src/db/Db.ts +30 -0
  232. package/src/db/Schema.ts +15 -0
  233. package/src/db/tables/enabledBillingSources.test.ts +303 -0
  234. package/src/db/tables/enabledBillingSources.ts +118 -0
  235. package/src/db/tables/invitations.ts +4 -26
  236. package/src/db/tables/organizations.test.ts +103 -0
  237. package/src/db/tables/organizations.ts +18 -2
  238. package/src/handlers/internal/utils.ts +22 -6
  239. package/src/handlers/relay.errors.test.ts +153 -0
  240. package/src/handlers/relay.ts +42 -7
  241. package/src/internal/Auth.test.ts +3 -3
  242. package/src/internal/Auth.ts +3 -63
  243. package/src/internal/FundingQuotes.test.ts +18 -5
  244. package/src/internal/FundingQuotes.ts +46 -11
  245. package/src/internal/Log.test.ts +91 -0
  246. package/src/internal/Log.ts +69 -5
  247. package/src/internal/MetricSink.test.ts +55 -0
  248. package/src/internal/MetricSink.ts +14 -0
  249. package/src/internal/Response.test.ts +28 -0
  250. package/src/internal/Response.ts +12 -4
@@ -0,0 +1,353 @@
1
+ import type { Context, TypedResponse } from 'hono'
2
+ import * as z from 'zod/mini'
3
+
4
+ import type * as App from '../../App.js'
5
+ import * as Response from '../../internal/Response.js'
6
+ import * as Schema from '../../internal/Schema.js'
7
+
8
+ /** Stable MPP relay failure codes. */
9
+ export const relayErrorCode = {
10
+ /** Credential or transaction already used. */
11
+ alreadyUsed: 'already_used',
12
+ /** Transaction broadcast or confirmation failed. */
13
+ broadcastFailed: 'broadcast_failed',
14
+ /** Challenge or payment authorization expired. */
15
+ expired: 'expired',
16
+ /** Payload, signature, parties, or payment requirements are invalid. */
17
+ invalidPayment: 'invalid_payment',
18
+ /** Sender lacks funds. */
19
+ insufficientFunds: 'insufficient_funds',
20
+ /** Payment violates relay or account policy. */
21
+ policyDenied: 'policy_denied',
22
+ /** Originator or recipient failed screening. */
23
+ screenRejected: 'screen_rejected',
24
+ /** Transaction simulation failed before broadcast. */
25
+ simulationFailed: 'simulation_failed',
26
+ /** Retry may succeed. */
27
+ temporarilyUnavailable: 'temporarily_unavailable',
28
+ /** Method, intent, network, or asset is unsupported. */
29
+ unsupported: 'unsupported',
30
+ /** Unexpected relay failure. */
31
+ unknown: 'unknown',
32
+ } as const
33
+
34
+ /** Stable MPP relay failure code. */
35
+ export type RelayErrorCode = (typeof relayErrorCode)[keyof typeof relayErrorCode]
36
+
37
+ /** MPP relay error response. */
38
+ export type RelayError = {
39
+ /** Stable machine-readable reason the relay rejected the credential. */
40
+ code: RelayErrorCode
41
+ /** Human-readable explanation of the relay result. */
42
+ message?: string | undefined
43
+ }
44
+
45
+ /** MPP challenge accepted with a relay credential payload. */
46
+ export type Challenge = {
47
+ /** Additional MPP challenge metadata, preserved verbatim for validation. */
48
+ [key: string]: unknown
49
+ /** Human-readable payment description. */
50
+ description?: string | undefined
51
+ /** When the payment challenge expires, as an ISO-8601 timestamp. */
52
+ expires?: string | undefined
53
+ /** Payment challenge identifier. */
54
+ id: string
55
+ /** MPP payment intent. */
56
+ intent: string
57
+ /** MPP payment method. */
58
+ method: string
59
+ /** Opaque data bound to the challenge by its issuer. */
60
+ opaque?: string | undefined
61
+ /** MPP realm that issued the challenge. */
62
+ realm: string
63
+ /** Payment request bound to the challenge. */
64
+ request: Record<string, unknown>
65
+ }
66
+
67
+ /** MPP challenge and credential payload accepted by the relay. */
68
+ export type RelayInput = {
69
+ /** Payment challenge supplied with the credential. */
70
+ challenge: Challenge
71
+ /** Method-specific MPP credential payload. */
72
+ payload: unknown
73
+ }
74
+
75
+ /** Broadcast MPP payment receipt. */
76
+ export type Receipt = {
77
+ /** Optional caller-provided payment reference. */
78
+ externalId?: string | undefined
79
+ /** Payment method that settled the credential. */
80
+ method: string
81
+ /** On-chain or payment-system settlement reference. */
82
+ reference: string
83
+ /** When the payment settled, as an ISO-8601 timestamp. */
84
+ timestamp: string
85
+ }
86
+
87
+ /** MPP credential validation response. */
88
+ export type VerifyResponse = { success: true } | { error: RelayError; success: false }
89
+
90
+ /** MPP credential broadcast response. */
91
+ export type BroadcastResponse =
92
+ | { receipt: Receipt; success: true }
93
+ | { error: RelayError; success: false }
94
+
95
+ /** Retryable response returned while relay settlement is disabled. */
96
+ export type Unavailable = {
97
+ error: { code: 'temporarily_unavailable'; message: 'MPP relay is not enabled yet.' }
98
+ success: false
99
+ }
100
+
101
+ function errorCode<const code extends RelayErrorCode>(value: code, description: string) {
102
+ return z.literal(value).check(z.describe(description), z.meta({ examples: [value] }))
103
+ }
104
+
105
+ /** MPP relay schemas. */
106
+ export namespace schema {
107
+ /** Stable MPP relay failure codes. */
108
+ export const ErrorCode = Schema.describe(
109
+ z.union([
110
+ errorCode(relayErrorCode.unsupported, 'Method, intent, network, or asset is unsupported.'),
111
+ errorCode(relayErrorCode.policyDenied, 'Payment violates relay or account policy.'),
112
+ errorCode(
113
+ relayErrorCode.invalidPayment,
114
+ 'Payload, signature, parties, or payment requirements are invalid.',
115
+ ),
116
+ errorCode(relayErrorCode.insufficientFunds, 'Sender lacks funds.'),
117
+ errorCode(relayErrorCode.expired, 'Challenge or payment authorization expired.'),
118
+ errorCode(relayErrorCode.alreadyUsed, 'Credential or transaction already used.'),
119
+ errorCode(relayErrorCode.screenRejected, 'Originator or recipient failed screening.'),
120
+ errorCode(relayErrorCode.simulationFailed, 'Transaction simulation failed before broadcast.'),
121
+ errorCode(relayErrorCode.broadcastFailed, 'Transaction broadcast or confirmation failed.'),
122
+ errorCode(relayErrorCode.temporarilyUnavailable, 'Retry may succeed.'),
123
+ errorCode(relayErrorCode.unknown, 'Unexpected relay failure.'),
124
+ ]),
125
+ 'A stable machine-readable reason the MPP relay rejected or could not process a payment.',
126
+ )
127
+
128
+ /** Error returned by the MPP relay. */
129
+ export const RelayError = Schema.describe(
130
+ z
131
+ .object({
132
+ code: ErrorCode,
133
+ message: z
134
+ .optional(z.string())
135
+ .check(
136
+ z.describe('A human-readable explanation of the relay result.'),
137
+ z.meta({ examples: ['MPP relay is not enabled yet.'] }),
138
+ ),
139
+ })
140
+ .check(
141
+ z.meta({
142
+ examples: [
143
+ {
144
+ code: relayErrorCode.temporarilyUnavailable,
145
+ message: 'MPP relay is not enabled yet.',
146
+ },
147
+ ],
148
+ }),
149
+ ),
150
+ 'An error returned by the MPP relay.',
151
+ )
152
+
153
+ /** MPP challenge accepted with a relay credential payload. */
154
+ export const Challenge = Schema.describe(
155
+ z.looseObject({
156
+ description: z
157
+ .optional(z.string())
158
+ .check(
159
+ z.describe('A human-readable description of the payment.'),
160
+ z.meta({ examples: ['Tempo API payment'] }),
161
+ ),
162
+ expires: z
163
+ .optional(z.string())
164
+ .check(
165
+ z.describe('When the payment challenge expires, as an ISO-8601 timestamp.'),
166
+ z.meta({ examples: ['2026-07-21T21:30:00.000Z'] }),
167
+ ),
168
+ id: z
169
+ .string()
170
+ .check(
171
+ z.describe('The payment challenge identifier.'),
172
+ z.meta({ examples: ['ch_01j3j1k2l3m4n5p6q7r8s9t0u'] }),
173
+ ),
174
+ intent: z
175
+ .string()
176
+ .check(z.describe('The MPP payment intent.'), z.meta({ examples: ['charge'] })),
177
+ method: z
178
+ .string()
179
+ .check(z.describe('The MPP payment method.'), z.meta({ examples: ['tempo'] })),
180
+ opaque: z
181
+ .optional(z.string())
182
+ .check(
183
+ z.describe('Opaque data bound to the challenge by its issuer.'),
184
+ z.meta({ examples: ['eyJhbGciOiJIUzI1NiJ9'] }),
185
+ ),
186
+ realm: z
187
+ .string()
188
+ .check(
189
+ z.describe('The MPP realm that issued the challenge.'),
190
+ z.meta({ examples: ['merchant.example'] }),
191
+ ),
192
+ request: z
193
+ .record(z.string(), z.unknown())
194
+ .check(
195
+ z.describe('The payment request bound to the challenge.'),
196
+ z.meta({ examples: [{ amount: '1', currency: 'USD' }] }),
197
+ ),
198
+ }),
199
+ 'An MPP payment challenge.',
200
+ )
201
+
202
+ /** Request accepted by MPP relay verification and broadcast routes. */
203
+ export const RelayInput = Schema.describe(
204
+ z.object({
205
+ challenge: Challenge,
206
+ payload: z
207
+ .custom<unknown>((value) => value !== undefined)
208
+ .check(
209
+ z.describe('The method-specific MPP credential payload.'),
210
+ z.meta({ examples: [{ signature: `0x${'aa'.repeat(65)}`, type: 'transaction' }] }),
211
+ ),
212
+ }),
213
+ 'An MPP payment credential and its challenge.',
214
+ )
215
+
216
+ /** Receipt returned after the relay broadcasts a payment. */
217
+ export const Receipt = Schema.describe(
218
+ z.object({
219
+ externalId: z
220
+ .optional(z.string())
221
+ .check(
222
+ z.describe('An optional caller-provided payment reference.'),
223
+ z.meta({ examples: ['pi_01j3j1k2l3m4n5p6q7r8s9t0u'] }),
224
+ ),
225
+ method: z
226
+ .string()
227
+ .check(
228
+ z.describe('The payment method that settled the credential.'),
229
+ z.meta({ examples: ['tempo'] }),
230
+ ),
231
+ reference: z
232
+ .string()
233
+ .check(
234
+ z.describe('The on-chain or payment-system reference for the settlement.'),
235
+ z.meta({ examples: [`0x${'22'.repeat(32)}`] }),
236
+ ),
237
+ timestamp: z
238
+ .string()
239
+ .check(
240
+ z.describe('When the payment settled, as an ISO-8601 timestamp.'),
241
+ z.meta({ examples: ['2026-07-20T18:30:00.000Z'] }),
242
+ ),
243
+ }),
244
+ 'A receipt for a broadcast MPP payment.',
245
+ )
246
+
247
+ /** Read-only MPP credential validation result. */
248
+ export const VerifyResponse = Schema.describe(
249
+ z.union([
250
+ z.object({
251
+ success: z
252
+ .literal(true)
253
+ .check(
254
+ z.describe('Whether the credential passed relay checks.'),
255
+ z.meta({ examples: [true] }),
256
+ ),
257
+ }),
258
+ z.object({
259
+ error: RelayError.check(
260
+ z.describe('The reason the credential did not pass relay checks.'),
261
+ z.meta({
262
+ examples: [
263
+ { code: relayErrorCode.invalidPayment, message: 'Credential signature is invalid.' },
264
+ ],
265
+ }),
266
+ ),
267
+ success: z
268
+ .literal(false)
269
+ .check(
270
+ z.describe('Whether the credential passed relay checks.'),
271
+ z.meta({ examples: [false] }),
272
+ ),
273
+ }),
274
+ ]),
275
+ 'The result of read-only MPP credential validation.',
276
+ )
277
+
278
+ /** MPP credential broadcast result. */
279
+ export const BroadcastResponse = Schema.describe(
280
+ z.union([
281
+ z.object({
282
+ receipt: Receipt.check(
283
+ z.describe('The payment settlement receipt.'),
284
+ z.meta({
285
+ examples: [
286
+ {
287
+ method: 'tempo',
288
+ reference: `0x${'22'.repeat(32)}`,
289
+ timestamp: '2026-07-20T18:30:00.000Z',
290
+ },
291
+ ],
292
+ }),
293
+ ),
294
+ success: z
295
+ .literal(true)
296
+ .check(
297
+ z.describe('Whether the relay broadcast the credential.'),
298
+ z.meta({ examples: [true] }),
299
+ ),
300
+ }),
301
+ z.object({
302
+ error: RelayError.check(
303
+ z.describe('The reason the relay did not broadcast the credential.'),
304
+ z.meta({
305
+ examples: [
306
+ { code: relayErrorCode.broadcastFailed, message: 'Transaction broadcast failed.' },
307
+ ],
308
+ }),
309
+ ),
310
+ success: z
311
+ .literal(false)
312
+ .check(
313
+ z.describe('Whether the relay broadcast the credential.'),
314
+ z.meta({ examples: [false] }),
315
+ ),
316
+ }),
317
+ ]),
318
+ 'The result of broadcasting an MPP credential.',
319
+ )
320
+
321
+ /** Temporary response returned while MPP relay settlement is disabled. */
322
+ export const Unavailable = Schema.describe(
323
+ z.object({
324
+ error: z.object({
325
+ code: z
326
+ .literal(relayErrorCode.temporarilyUnavailable)
327
+ .check(z.describe('The retryable relay failure code.')),
328
+ message: z
329
+ .literal('MPP relay is not enabled yet.')
330
+ .check(z.describe('The retryable relay failure message.')),
331
+ }),
332
+ success: z.literal(false).check(z.describe('Whether the relay processed the credential.')),
333
+ }),
334
+ 'A retryable response returned while MPP relay settlement is disabled.',
335
+ )
336
+ }
337
+
338
+ /** Returns the relay-disabled response while retaining its published success contract. */
339
+ export function unavailable<success>(
340
+ c: Context<App.Environment>,
341
+ ): TypedResponse<success, 200> | TypedResponse<Unavailable, 503> {
342
+ c.header('Retry-After', '60')
343
+ return c.json(
344
+ Response.validated(schema.Unavailable, {
345
+ error: {
346
+ code: relayErrorCode.temporarilyUnavailable,
347
+ message: 'MPP relay is not enabled yet.',
348
+ },
349
+ success: false,
350
+ }),
351
+ 503,
352
+ )
353
+ }
@@ -0,0 +1,2 @@
1
+ export { mpp } from './App.js'
2
+ export * as Mpp from './Mpp.js'
@@ -0,0 +1,11 @@
1
+ import type * as Mpp from '../Mpp.js'
2
+
3
+ /** Future MPPX lifecycle adapter. */
4
+ export type Adapter = {
5
+ /** Broadcasts a validated MPP credential. */
6
+ broadcast(input: Mpp.RelayInput): Promise<Mpp.BroadcastResponse>
7
+ /** Validates an MPP credential without broadcasting it. */
8
+ verify(input: Mpp.RelayInput): Promise<Mpp.VerifyResponse>
9
+ }
10
+
11
+ // TODO(mppx#597): implement with validateCredential and broadcastCredential.
@@ -0,0 +1,9 @@
1
+ import type * as Mpp from '../Mpp.js'
2
+
3
+ /** Future MPPX failure mapper. */
4
+ export type Mapper = {
5
+ /** Maps an MPPX failure to the stable public relay error contract. */
6
+ map(cause: unknown): Mpp.RelayError
7
+ }
8
+
9
+ // TODO(mppx#597): map validation, replay, simulation, and broadcast failures.
@@ -0,0 +1,7 @@
1
+ /** Future challenge-derived broadcast idempotency identity. */
2
+ export type Identity = {
3
+ /** MPP challenge identifier. */
4
+ challengeId: string
5
+ }
6
+
7
+ // TODO(mppx#597): persist terminal broadcast results in Durable Object state.
@@ -0,0 +1,10 @@
1
+ /** Future address-screening result. */
2
+ export type Result = { allowed: boolean }
3
+
4
+ /** Future address-screening provider. */
5
+ export type Provider = {
6
+ /** Screens one normalized account address on a Tempo chain. */
7
+ screen(options: { address: string; chainId: number }): Promise<Result>
8
+ }
9
+
10
+ // TODO(mppx#597): add concurrent cached Chainalysis screening behind this provider.
@@ -656,6 +656,19 @@ describe.skipIf(runtime.mode !== 'localnet')('behavior: sponsorship', () => {
656
656
  // Chain-free (`eth_signRawTransaction` signs locally; the sponsor fee token
657
657
  // short-circuits resolution), so the gate matrix runs in every mode.
658
658
  describe('behavior: mainnet billing gate', () => {
659
+ const fill = {
660
+ calls: [{ to: Tempo.accounts[8]!.address, value: '0x0' }],
661
+ capabilities: { balanceDiffs: false },
662
+ chainId: Hex.fromNumber(Viem.chainId.mainnet),
663
+ feePayer: true,
664
+ feeToken: '0x20c0000000000000000000000000000000000000',
665
+ from: senderAccount.address,
666
+ gas: Hex.fromNumber(100_000),
667
+ maxFeePerGas: Hex.fromNumber(1_000_000),
668
+ maxPriorityFeePerGas: '0x0',
669
+ nonce: '0x0',
670
+ } as const
671
+
659
672
  function createApp(db: Db.Source = TestApp.database()) {
660
673
  return TestApp.create({
661
674
  auth: { keys },
@@ -692,6 +705,64 @@ describe('behavior: mainnet billing gate', () => {
692
705
  expect(body.error?.data).toStrictEqual({ code: 'billing_required' })
693
706
  })
694
707
 
708
+ test('behavior: returns the billing reason for sponsor fills', async () => {
709
+ const body = await relayRequest(createApp(), {
710
+ method: 'eth_fillTransaction',
711
+ params: [fill],
712
+ token: 'secret_sponsor_prod',
713
+ })
714
+ expect(body.error).toStrictEqual({
715
+ code: -32602,
716
+ data: { code: 'billing_required' },
717
+ message: 'Billing required.',
718
+ })
719
+ })
720
+
721
+ test('behavior: relay fills still fall back to self-payment', async () => {
722
+ const server = await Relay.createServer(
723
+ RequestListener.fromFetchHandler(async (request) => {
724
+ type Rpc = {
725
+ id: number
726
+ method: string
727
+ params: readonly [Record<string, unknown>]
728
+ }
729
+ const rpc = (await request.json()) as Rpc
730
+ if (rpc.method !== 'eth_fillTransaction')
731
+ return Response.json({ id: rpc.id, jsonrpc: '2.0', result: '0x' })
732
+ return Response.json({
733
+ id: rpc.id,
734
+ jsonrpc: '2.0',
735
+ result: { capabilities: {}, tx: rpc.params[0] },
736
+ })
737
+ }),
738
+ )
739
+ try {
740
+ const body = await relayRequest(
741
+ TestApp.create({
742
+ auth: { keys },
743
+ db: TestApp.database(),
744
+ relay: { feePayer: { account: feePayerAccount } },
745
+ rpc: { url: () => server.url },
746
+ supportedChainIds: [Viem.chainId.mainnet],
747
+ }),
748
+ {
749
+ method: 'eth_fillTransaction',
750
+ mount: 'relay',
751
+ params: [fill],
752
+ token: 'secret_wildcard_prod',
753
+ },
754
+ )
755
+ type Result = {
756
+ capabilities?: { sponsored?: boolean | undefined } | undefined
757
+ }
758
+ const result = body.result as unknown as Result
759
+ expect(body.error).toBeUndefined()
760
+ expect(result.capabilities?.sponsored).toBe(false)
761
+ } finally {
762
+ await server.closeAsync()
763
+ }
764
+ })
765
+
695
766
  test('behavior: refuses mainnet sponsorship while billing is inactive', async () => {
696
767
  const db = TestApp.database()
697
768
  await StripeCustomers.create(db, { orgId: 'org_1', stripeCustomerId: 'cus_gate' })
@@ -1110,13 +1181,10 @@ async function signSponsorable(
1110
1181
  })
1111
1182
  }
1112
1183
 
1113
- /** Posts one JSON-RPC request to the composed app's sponsor mount. */
1114
- async function relayRequest(
1115
- app: ReturnType<typeof TestApp.create>,
1116
- options: { method: string; params: readonly unknown[]; token?: string | undefined },
1117
- ) {
1184
+ /** Posts one JSON-RPC request to a composed relay mount. */
1185
+ async function relayRequest(app: ReturnType<typeof TestApp.create>, options: relayRequest.Options) {
1118
1186
  const response = await app.fetch(
1119
- new Request('http://tempo-api.test/rpc/sponsor', {
1187
+ new Request(`http://tempo-api.test/rpc/${options.mount ?? 'sponsor'}`, {
1120
1188
  body: JSON.stringify({ id: 1, jsonrpc: '2.0', method: options.method, params: options.params }), // prettier-ignore
1121
1189
  headers: {
1122
1190
  'content-type': 'application/json',
@@ -1131,6 +1199,20 @@ async function relayRequest(
1131
1199
  }
1132
1200
  }
1133
1201
 
1202
+ declare namespace relayRequest {
1203
+ /** Options for one composed relay request. */
1204
+ type Options = {
1205
+ /** JSON-RPC method. */
1206
+ method: string
1207
+ /** Relay mount to call. */
1208
+ mount?: 'relay' | 'sponsor' | undefined
1209
+ /** JSON-RPC parameters. */
1210
+ params: readonly unknown[]
1211
+ /** API-key credential. */
1212
+ token?: string | undefined
1213
+ }
1214
+ }
1215
+
1134
1216
  // The full billing-to-invoice flow with nothing synthetic: a disposable node
1135
1217
  // runs under mainnet's chain id (dev consensus, funded dev alloc), so a real
1136
1218
  // card attach opens the real gate, a real sponsored transaction mines with a
@@ -8,6 +8,7 @@ import type * as App from '../../App.js'
8
8
  import * as BillingSettings from '../../db/tables/billingSettings.js'
9
9
  import * as Db from '../../db/Db.js'
10
10
  import * as Fees from '../../internal/Fees.js'
11
+ import * as Log from '../../internal/Log.js'
11
12
  import * as SponsoredTransactions from '../../db/tables/sponsoredTransactions.js'
12
13
  import * as Billing from '../management/Billing.js'
13
14
  import * as Handler from '../../Handler.js'
@@ -75,6 +76,9 @@ export declare namespace relay {
75
76
  // Builds request-aware RPC middleware. Viem clients are memoized upstream, so
76
77
  // constructing `Handler.relay` per request stays cheap.
77
78
  function middleware(options: middleware.Options) {
79
+ // The sponsor mount promises a fee-payer signature. The relay mount may
80
+ // fall back to self-payment when policy refuses sponsorship.
81
+ const sponsorshipRequired = options.path === 'rpc/sponsor'
78
82
  // Platform-wide per-transaction cap; org settings tighten it, never loosen.
79
83
  const txFeeLimit = options.feePayer
80
84
  ? Billing.toBaseUnits(options.feePayer.txFeeLimit ?? '1.00')
@@ -111,12 +115,20 @@ function middleware(options: middleware.Options) {
111
115
  // only mainnet: the relay resolves chainId from the request body, so
112
116
  // the query-string chain guard never sees it.
113
117
  const id = Number(transaction.chainId)
114
- if (!attribution || !sponsorable || !c.get('supportedChainIds').has(id)) return false
118
+ if (!attribution || !sponsorable || !c.get('supportedChainIds').has(id)) {
119
+ if (sponsorshipRequired)
120
+ throw new RpcResponse.InvalidParamsError({ message: 'Sponsorship rejected.' })
121
+ return false
122
+ }
115
123
  if (!Viem.isMainnet(id)) return true
116
124
  // Mainnet is production-only: a sandbox key resolves chainId from the
117
125
  // request body, so the auth-layer sandbox/mainnet guard never sees
118
126
  // it. Refuse here, or sandbox spend would sponsor mainnet unbilled.
119
- if (attribution.environment !== 'production') return false
127
+ if (attribution.environment !== 'production') {
128
+ if (sponsorshipRequired)
129
+ throw new RpcResponse.InvalidParamsError({ message: 'Sponsorship rejected.' })
130
+ return false
131
+ }
120
132
 
121
133
  // Enforcement reads hit the primary, never the cached replica: a
122
134
  // stale `past_due`, a lowered limit, or another isolate's pending
@@ -127,8 +139,22 @@ function middleware(options: middleware.Options) {
127
139
  // `past_due` gets its own reason: the caller must settle an invoice,
128
140
  // not attach a card.
129
141
  const billing = await Billing.status(db, attribution.orgId)
130
- if (billing === 'past_due') return 'billing_past_due'
131
- if (billing !== 'active') return 'billing_required'
142
+ if (billing === 'past_due') {
143
+ if (sponsorshipRequired)
144
+ throw new RpcResponse.InvalidParamsError({
145
+ data: { code: 'billing_past_due' },
146
+ message: 'Billing past due.',
147
+ })
148
+ return 'billing_past_due'
149
+ }
150
+ if (billing !== 'active') {
151
+ if (sponsorshipRequired)
152
+ throw new RpcResponse.InvalidParamsError({
153
+ data: { code: 'billing_required' },
154
+ message: 'Billing required.',
155
+ })
156
+ return 'billing_required'
157
+ }
132
158
 
133
159
  const feeToken = (() => {
134
160
  try {
@@ -144,21 +170,40 @@ function middleware(options: middleware.Options) {
144
170
  !verified ||
145
171
  verified.currency.toLowerCase() !== 'usd' ||
146
172
  verified.decimals !== Fees.tokenDecimals
147
- )
148
- // prettier-ignore
173
+ ) {
174
+ if (sponsorshipRequired)
175
+ throw new RpcResponse.InvalidParamsError({
176
+ data: { code: 'fee_token_unsupported' },
177
+ message: 'Fee token unsupported.',
178
+ })
149
179
  return 'fee_token_unsupported'
180
+ }
150
181
 
151
182
  // Fail closed: a transaction whose signed cap cannot be computed
152
183
  // cannot be bounded, so it never sponsors on mainnet.
153
184
  const feeMax = Fees.maxOf(transaction)
154
- if (feeMax === undefined) return 'tx_fee_limit_exceeded'
185
+ if (feeMax === undefined) {
186
+ if (sponsorshipRequired)
187
+ throw new RpcResponse.InvalidParamsError({
188
+ data: { code: 'tx_fee_limit_exceeded' },
189
+ message: 'Transaction fee limit exceeded.',
190
+ })
191
+ return 'tx_fee_limit_exceeded'
192
+ }
155
193
  const settings = await BillingSettings.get(db, attribution.orgId)
156
194
  const cap = (() => {
157
195
  if (settings?.txFeeLimit == null) return txFeeLimit
158
196
  const org = Billing.toBaseUnits(settings.txFeeLimit)
159
197
  return org < txFeeLimit ? org : txFeeLimit
160
198
  })()
161
- if (feeMax > cap) return 'tx_fee_limit_exceeded'
199
+ if (feeMax > cap) {
200
+ if (sponsorshipRequired)
201
+ throw new RpcResponse.InvalidParamsError({
202
+ data: { code: 'tx_fee_limit_exceeded' },
203
+ message: 'Transaction fee limit exceeded.',
204
+ })
205
+ return 'tx_fee_limit_exceeded'
206
+ }
162
207
  // The period spend limit is enforced atomically at recording time
163
208
  // (`onSponsored` → `SponsoredTransactions.reserve`); a check here
164
209
  // would be a check-then-act race a burst could slip through.
@@ -278,7 +323,14 @@ function middleware(options: middleware.Options) {
278
323
  }
279
324
  : {}),
280
325
  })
281
- return handler.fetch(c.req.raw)
326
+ const response = await handler.fetch(c.req.raw)
327
+ const body: unknown = await response
328
+ .clone()
329
+ .json()
330
+ .catch(() => undefined)
331
+ const rpc = Log.rpcErrors(body)
332
+ if (rpc) c.set('rpcResponse', rpc)
333
+ return response
282
334
  })
283
335
  }
284
336
 
package/src/db/Db.test.ts CHANGED
@@ -17,7 +17,7 @@ describe('postgres', () => {
17
17
  await db.close()
18
18
  })
19
19
 
20
- test('behavior: legacy invitation inserts retain early access during cutover', async () => {
20
+ test('behavior: legacy invitation inserts do not grant early access', async () => {
21
21
  const db = create()
22
22
  await db.migrate()
23
23
  await Organizations.create(db, { id: 'org_legacy', name: 'Legacy' })
@@ -30,7 +30,7 @@ describe('postgres', () => {
30
30
  )
31
31
  `.execute(db.kysely)
32
32
 
33
- expect((await Invitations.get(db, 'inv_legacy'))?.grantsEarlyAccess).toBe(true)
33
+ expect((await Invitations.get(db, 'inv_legacy'))?.grantsEarlyAccess).toBe(false)
34
34
  await db.close()
35
35
  })
36
36