tapimo 0.6.0 → 0.7.1

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 (288) 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 +47 -39
  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 +8050 -8019
  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 +669 -669
  48. package/dist/apps/data/routes/fee-amm.d.ts +157 -143
  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 +30 -30
  57. package/dist/apps/data/routes/receipts.d.ts +8 -8
  58. package/dist/apps/data/routes/rpc.d.ts +31 -31
  59. package/dist/apps/data/routes/tokenlist.d.ts.map +1 -1
  60. package/dist/apps/data/routes/tokenlist.js +12 -3
  61. package/dist/apps/data/routes/tokenlist.js.map +1 -1
  62. package/dist/apps/data/routes/tokens.d.ts +4 -4
  63. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  64. package/dist/apps/data/routes/tokens.js +5 -1
  65. package/dist/apps/data/routes/tokens.js.map +1 -1
  66. package/dist/apps/data/routes/transactions.d.ts +6 -6
  67. package/dist/apps/data/routes/transfers.d.ts +101 -90
  68. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  69. package/dist/apps/data/routes/transfers.js +226 -141
  70. package/dist/apps/data/routes/transfers.js.map +1 -1
  71. package/dist/apps/data/routes/valuation.d.ts +2 -2
  72. package/dist/apps/data/routes/valuation.js +3 -3
  73. package/dist/apps/data/routes/valuation.js.map +1 -1
  74. package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
  75. package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -1
  76. package/dist/apps/data/routes/verified-tokens.js +34 -9
  77. package/dist/apps/data/routes/verified-tokens.js.map +1 -1
  78. package/dist/apps/data/routes/webhooks.d.ts +2 -2
  79. package/dist/apps/data/routes/webhooks.js +14 -6
  80. package/dist/apps/data/routes/webhooks.js.map +1 -1
  81. package/dist/apps/index.d.ts +1 -0
  82. package/dist/apps/index.d.ts.map +1 -1
  83. package/dist/apps/index.js +1 -0
  84. package/dist/apps/index.js.map +1 -1
  85. package/dist/apps/management/App.d.ts +460 -523
  86. package/dist/apps/management/App.d.ts.map +1 -1
  87. package/dist/apps/management/routes/api-keys.d.ts +7 -23
  88. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  89. package/dist/apps/management/routes/api-keys.js +20 -8
  90. package/dist/apps/management/routes/api-keys.js.map +1 -1
  91. package/dist/apps/management/routes/billing.d.ts +106 -151
  92. package/dist/apps/management/routes/billing.d.ts.map +1 -1
  93. package/dist/apps/management/routes/billing.js +28 -28
  94. package/dist/apps/management/routes/billing.js.map +1 -1
  95. package/dist/apps/management/routes/faucet.d.ts +48 -34
  96. package/dist/apps/management/routes/faucet.d.ts.map +1 -1
  97. package/dist/apps/management/routes/faucet.js +33 -18
  98. package/dist/apps/management/routes/faucet.js.map +1 -1
  99. package/dist/apps/management/routes/invitations.d.ts +131 -131
  100. package/dist/apps/management/routes/invitations.d.ts.map +1 -1
  101. package/dist/apps/management/routes/invitations.js +0 -3
  102. package/dist/apps/management/routes/invitations.js.map +1 -1
  103. package/dist/apps/management/routes/me.d.ts +6 -8
  104. package/dist/apps/management/routes/me.d.ts.map +1 -1
  105. package/dist/apps/management/routes/me.js +0 -4
  106. package/dist/apps/management/routes/me.js.map +1 -1
  107. package/dist/apps/management/routes/members.d.ts +73 -73
  108. package/dist/apps/management/routes/orgs.d.ts +94 -94
  109. package/dist/apps/management/routes/orgs.d.ts.map +1 -1
  110. package/dist/apps/management/routes/orgs.js +5 -0
  111. package/dist/apps/management/routes/orgs.js.map +1 -1
  112. package/dist/apps/management/routes/usage.d.ts +72 -72
  113. package/dist/apps/management/routes/verified-token-requests.d.ts +94 -94
  114. package/dist/apps/management/routes/webhooks.d.ts +1 -1
  115. package/dist/apps/mpp/App.d.ts +363 -0
  116. package/dist/apps/mpp/App.d.ts.map +1 -0
  117. package/dist/apps/mpp/App.js +72 -0
  118. package/dist/apps/mpp/App.js.map +1 -0
  119. package/dist/apps/mpp/Mpp.d.ts +178 -0
  120. package/dist/apps/mpp/Mpp.d.ts.map +1 -0
  121. package/dist/apps/mpp/Mpp.js +183 -0
  122. package/dist/apps/mpp/Mpp.js.map +1 -0
  123. package/dist/apps/mpp/index.d.ts +3 -0
  124. package/dist/apps/mpp/index.d.ts.map +1 -0
  125. package/dist/apps/mpp/index.js +3 -0
  126. package/dist/apps/mpp/index.js.map +1 -0
  127. package/dist/apps/relay/App.d.ts.map +1 -1
  128. package/dist/apps/relay/App.js +57 -10
  129. package/dist/apps/relay/App.js.map +1 -1
  130. package/dist/apps/relay/Sponsorships.js +1 -1
  131. package/dist/apps/relay/Sponsorships.js.map +1 -1
  132. package/dist/db/Db.d.ts +2 -0
  133. package/dist/db/Db.d.ts.map +1 -1
  134. package/dist/db/Db.js +27 -0
  135. package/dist/db/Db.js.map +1 -1
  136. package/dist/db/Schema.d.ts +12 -0
  137. package/dist/db/Schema.d.ts.map +1 -1
  138. package/dist/db/Schema.js +11 -0
  139. package/dist/db/Schema.js.map +1 -1
  140. package/dist/db/tables/enabledBillingSources.d.ts +73 -0
  141. package/dist/db/tables/enabledBillingSources.d.ts.map +1 -0
  142. package/dist/db/tables/enabledBillingSources.js +69 -0
  143. package/dist/db/tables/enabledBillingSources.js.map +1 -0
  144. package/dist/db/tables/invitations.d.ts +1 -12
  145. package/dist/db/tables/invitations.d.ts.map +1 -1
  146. package/dist/db/tables/invitations.js +3 -23
  147. package/dist/db/tables/invitations.js.map +1 -1
  148. package/dist/db/tables/organizations.d.ts +5 -2
  149. package/dist/db/tables/organizations.d.ts.map +1 -1
  150. package/dist/db/tables/organizations.js +32 -2
  151. package/dist/db/tables/organizations.js.map +1 -1
  152. package/dist/db/tables/sponsoredTransactions.d.ts +2 -0
  153. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  154. package/dist/db/tables/sponsoredTransactions.js +13 -3
  155. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  156. package/dist/handlers/internal/utils.d.ts +1 -16
  157. package/dist/handlers/internal/utils.d.ts.map +1 -1
  158. package/dist/handlers/internal/utils.js +17 -6
  159. package/dist/handlers/internal/utils.js.map +1 -1
  160. package/dist/handlers/relay.d.ts.map +1 -1
  161. package/dist/handlers/relay.js +37 -5
  162. package/dist/handlers/relay.js.map +1 -1
  163. package/dist/internal/Auth.d.ts +0 -32
  164. package/dist/internal/Auth.d.ts.map +1 -1
  165. package/dist/internal/Auth.js +3 -51
  166. package/dist/internal/Auth.js.map +1 -1
  167. package/dist/internal/EdgeCache.d.ts +1 -1
  168. package/dist/internal/EdgeCache.d.ts.map +1 -1
  169. package/dist/internal/EdgeCache.js +3 -2
  170. package/dist/internal/EdgeCache.js.map +1 -1
  171. package/dist/internal/FundingQuotes.d.ts +78 -32
  172. package/dist/internal/FundingQuotes.d.ts.map +1 -1
  173. package/dist/internal/FundingQuotes.js +45 -8
  174. package/dist/internal/FundingQuotes.js.map +1 -1
  175. package/dist/internal/Log.d.ts +18 -4
  176. package/dist/internal/Log.d.ts.map +1 -1
  177. package/dist/internal/Log.js +50 -3
  178. package/dist/internal/Log.js.map +1 -1
  179. package/dist/internal/MetricSink.d.ts.map +1 -1
  180. package/dist/internal/MetricSink.js +15 -0
  181. package/dist/internal/MetricSink.js.map +1 -1
  182. package/dist/internal/Response.d.ts +2 -2
  183. package/dist/internal/Response.d.ts.map +1 -1
  184. package/dist/internal/Response.js +12 -4
  185. package/dist/internal/Response.js.map +1 -1
  186. package/dist/internal/Schema.d.ts +2 -2
  187. package/dist/internal/Schema.d.ts.map +1 -1
  188. package/dist/internal/Schema.js +4 -2
  189. package/dist/internal/Schema.js.map +1 -1
  190. package/dist/internal/Viem.d.ts +1 -1
  191. package/package.json +7 -2
  192. package/src/App.test.ts +9 -4
  193. package/src/App.ts +14 -2
  194. package/src/Client.test-d.ts +109 -28
  195. package/src/Scope.ts +5 -0
  196. package/src/admin/App.test.ts +4 -0
  197. package/src/admin/apps/early-access.ts +6 -5
  198. package/src/admin/apps/organizations.test.ts +86 -2
  199. package/src/admin/apps/organizations.ts +155 -31
  200. package/src/admin/apps/scopes.test.ts +5 -0
  201. package/src/admin/ui/src/lib/organizations.test.ts +81 -1
  202. package/src/admin/ui/src/lib/organizations.ts +92 -0
  203. package/src/admin/ui/src/routes/api-keys.tsx +1 -1
  204. package/src/admin/ui/src/routes/early-access.tsx +8 -3
  205. package/src/admin/ui/src/routes/organizations_.$orgId.tsx +103 -0
  206. package/src/admin/ui.generated.ts +49 -39
  207. package/src/analytics/Analytics.test.ts +83 -0
  208. package/src/analytics/Analytics.ts +20 -0
  209. package/src/analytics/tables/requestEvents.ts +60 -7
  210. package/src/apps/composition.test.ts +11 -2
  211. package/src/apps/data/App.test.ts +14 -2
  212. package/src/apps/data/FundingQuoteProviders.test.ts +0 -5
  213. package/src/apps/data/FundingQuoteProviders.ts +2 -29
  214. package/src/apps/data/FxOracle.test.ts +2 -2
  215. package/src/apps/data/FxOracle.ts +2 -2
  216. package/src/apps/data/routes/activities.test.ts +6 -2
  217. package/src/apps/data/routes/activities.ts +39 -1
  218. package/src/apps/data/routes/balances.test.ts +6 -2
  219. package/src/apps/data/routes/exchanges.test.ts +7 -3
  220. package/src/apps/data/routes/fee-amm.test.ts +47 -23
  221. package/src/apps/data/routes/fee-amm.ts +98 -119
  222. package/src/apps/data/routes/funding.test.ts +29 -8
  223. package/src/apps/data/routes/funding.ts +42 -48
  224. package/src/apps/data/routes/receipts.test.ts +6 -2
  225. package/src/apps/data/routes/tokenlist.test.ts +15 -0
  226. package/src/apps/data/routes/tokenlist.ts +18 -5
  227. package/src/apps/data/routes/tokens.ts +5 -1
  228. package/src/apps/data/routes/transfers.test.ts +203 -35
  229. package/src/apps/data/routes/transfers.ts +294 -172
  230. package/src/apps/data/routes/valuation.test.ts +18 -3
  231. package/src/apps/data/routes/valuation.ts +4 -4
  232. package/src/apps/data/routes/verified-tokens.test.ts +51 -0
  233. package/src/apps/data/routes/verified-tokens.ts +52 -15
  234. package/src/apps/data/routes/webhooks.test.ts +56 -0
  235. package/src/apps/data/routes/webhooks.ts +17 -8
  236. package/src/apps/index.ts +1 -0
  237. package/src/apps/management/routes/api-keys.test.ts +2 -75
  238. package/src/apps/management/routes/api-keys.ts +30 -7
  239. package/src/apps/management/routes/billing.test.ts +106 -85
  240. package/src/apps/management/routes/billing.ts +32 -21
  241. package/src/apps/management/routes/faucet.test.ts +11 -3
  242. package/src/apps/management/routes/faucet.ts +46 -30
  243. package/src/apps/management/routes/invitations.test.ts +0 -172
  244. package/src/apps/management/routes/invitations.ts +0 -3
  245. package/src/apps/management/routes/me.test.ts +3 -145
  246. package/src/apps/management/routes/me.ts +0 -6
  247. package/src/apps/management/routes/orgs.test.ts +45 -2
  248. package/src/apps/management/routes/orgs.ts +6 -0
  249. package/src/apps/management/routes/scopes.test.ts +1 -0
  250. package/src/apps/mpp/App.ts +86 -0
  251. package/src/apps/mpp/Mpp.test.ts +193 -0
  252. package/src/apps/mpp/Mpp.ts +353 -0
  253. package/src/apps/mpp/index.ts +2 -0
  254. package/src/apps/mpp/internal/Adapter.ts +11 -0
  255. package/src/apps/mpp/internal/Error.ts +9 -0
  256. package/src/apps/mpp/internal/Idempotency.ts +7 -0
  257. package/src/apps/mpp/internal/Screening.ts +10 -0
  258. package/src/apps/relay/App.test.ts +90 -8
  259. package/src/apps/relay/App.ts +62 -10
  260. package/src/apps/relay/Sponsorships.test.ts +2 -0
  261. package/src/apps/relay/Sponsorships.ts +1 -1
  262. package/src/db/Db.test.ts +2 -2
  263. package/src/db/Db.ts +30 -0
  264. package/src/db/Schema.ts +15 -0
  265. package/src/db/tables/enabledBillingSources.test.ts +303 -0
  266. package/src/db/tables/enabledBillingSources.ts +118 -0
  267. package/src/db/tables/invitations.ts +4 -26
  268. package/src/db/tables/organizations.test.ts +191 -0
  269. package/src/db/tables/organizations.ts +44 -3
  270. package/src/db/tables/sponsoredTransactions.test.ts +41 -2
  271. package/src/db/tables/sponsoredTransactions.ts +15 -3
  272. package/src/handlers/internal/utils.ts +22 -6
  273. package/src/handlers/relay.errors.test.ts +153 -0
  274. package/src/handlers/relay.ts +42 -7
  275. package/src/internal/Auth.test.ts +3 -3
  276. package/src/internal/Auth.ts +3 -63
  277. package/src/internal/EdgeCache.test.ts +18 -5
  278. package/src/internal/EdgeCache.ts +4 -2
  279. package/src/internal/FundingQuotes.test.ts +18 -5
  280. package/src/internal/FundingQuotes.ts +46 -11
  281. package/src/internal/Log.test.ts +91 -0
  282. package/src/internal/Log.ts +69 -5
  283. package/src/internal/MetricSink.test.ts +55 -0
  284. package/src/internal/MetricSink.ts +14 -0
  285. package/src/internal/Response.test.ts +28 -0
  286. package/src/internal/Response.ts +12 -4
  287. package/src/internal/Schema.test.ts +13 -0
  288. package/src/internal/Schema.ts +14 -7
package/src/App.ts CHANGED
@@ -29,14 +29,22 @@ import openrpcSpec from './openrpc.json' with { type: 'json' }
29
29
  import * as Metadata from './apps/metadata.js'
30
30
  import * as Data from './apps/data/App.js'
31
31
  import * as Management from './apps/management/App.js'
32
+ import * as Mpp from './apps/mpp/App.js'
33
+ import * as Relay from './apps/relay/App.js'
32
34
 
33
35
  export import data = Data.data
34
36
  export import management = Management.management
37
+ export import mpp = Mpp.mpp
38
+ export import relay = Relay.relay
35
39
 
36
40
  /** The chain-data route group; see {@link data}. */
37
41
  export type DataApp = ReturnType<typeof data>
38
42
  /** The management route group; see {@link management}. */
39
43
  export type ManagementApp = ReturnType<typeof management>
44
+ /** The MPP credential relay route group; see {@link mpp}. */
45
+ export type MppApp = ReturnType<typeof mpp>
46
+ /** The wallet relay route group; see {@link relay}. */
47
+ export type RelayApp = ReturnType<typeof relay>
40
48
 
41
49
  /** Hono environment variables set by the Tempo API app. */
42
50
  export type Environment = {
@@ -82,7 +90,7 @@ export type Environment = {
82
90
  }
83
91
 
84
92
  /**
85
- * The canonical fully-composed Tempo API app ({@link create} with both route
93
+ * The canonical fully-composed Tempo API app ({@link create} with all route
86
94
  * groups). {@link Client} and the reference worker target this type; a subset
87
95
  * consumer infers its own narrower type from its own composition chain.
88
96
  */
@@ -97,7 +105,11 @@ export function from<const app extends Hono<any, any>>(app: app, metadata: creat
97
105
  // `App` (and thus `Client`) reflect the full route surface; real consumers
98
106
  // compose explicitly.
99
107
  function compose(options: create.Options) {
100
- return create(options).route('/', data()).route('/', management())
108
+ return create(options)
109
+ .route('/', data())
110
+ .route('/', relay())
111
+ .route('/', mpp())
112
+ .route('/', management())
101
113
  }
102
114
 
103
115
  /** Node.js request listener for `node:http` and compatible interfaces. */
@@ -1,4 +1,5 @@
1
1
  import { Client } from 'tapimo'
2
+ import { Mpp as PublicMpp } from 'tapimo/mpp'
2
3
  import type * as z from 'zod/mini'
3
4
 
4
5
  import * as Activities from './apps/data/routes/activities.js'
@@ -12,6 +13,7 @@ import * as Funding from './apps/data/routes/funding.js'
12
13
  import * as Indexer from './apps/data/routes/indexer.js'
13
14
  import * as InviteLinks from './apps/management/routes/invite-links.js'
14
15
  import * as Me from './apps/management/routes/me.js'
16
+ import * as Mpp from './apps/mpp/Mpp.js'
15
17
  import * as Orgs from './apps/management/routes/orgs.js'
16
18
  import * as OrgWebhooks from './apps/management/routes/webhooks.js'
17
19
  import * as Receipts from './apps/data/routes/receipts.js'
@@ -28,6 +30,18 @@ import * as Webhooks from './apps/data/routes/webhooks.js'
28
30
  type Access400<code extends string> = code | 'api_key_malformed'
29
31
  type PaidAccessStatus = PublicAccessStatus | 402
30
32
  type PublicAccessStatus = 400 | 401 | 403 | 429
33
+ const mppInput = {
34
+ challenge: {
35
+ description: 'Tempo API payment',
36
+ id: 'ch_01j3j1k2l3m4n5p6q7r8s9t0u',
37
+ intent: 'charge',
38
+ method: 'tempo',
39
+ opaque: 'eyJhbGciOiJIUzI1NiJ9',
40
+ realm: 'merchant.example',
41
+ request: { amount: '1', currency: 'USD' },
42
+ },
43
+ payload: { signature: `0x${'aa'.repeat(65)}`, type: 'transaction' },
44
+ } satisfies Mpp.RelayInput
31
45
  /** A `Schema.TokenAmount` carrying its optional nested valuation. */
32
46
  type ValuedTokenAmount = {
33
47
  baseUnits: string
@@ -54,9 +68,49 @@ test('returns typed client', async () => {
54
68
 
55
69
  const typed: Client.Client = client
56
70
  void typed
71
+
72
+ expectTypeOf<PublicMpp.RelayInput>().toEqualTypeOf<Mpp.RelayInput>()
57
73
  })
58
74
 
59
75
  describe('narrows responses by status', () => {
76
+ test('POST /v1/mpp/verify', async () => {
77
+ const client = Client.create()
78
+ const res = await client.v1.mpp.verify.$post({ json: mppInput })
79
+
80
+ expectTypeOf(res.status).toEqualTypeOf<200 | 400 | 401 | 403 | 429 | 503>()
81
+
82
+ if (res.status === 200) {
83
+ const json = await res.json()
84
+ expectTypeOf(json).toEqualTypeOf<Mpp.VerifyResponse>()
85
+ return
86
+ }
87
+
88
+ if (res.status === 503) {
89
+ const json = await res.json()
90
+ expectTypeOf(json).toEqualTypeOf<Mpp.Unavailable>()
91
+ return
92
+ }
93
+ })
94
+
95
+ test('POST /v1/mpp/broadcast', async () => {
96
+ const client = Client.create()
97
+ const res = await client.v1.mpp.broadcast.$post({ json: mppInput })
98
+
99
+ expectTypeOf(res.status).toEqualTypeOf<200 | 400 | 401 | 403 | 429 | 503>()
100
+
101
+ if (res.status === 200) {
102
+ const json = await res.json()
103
+ expectTypeOf(json).toEqualTypeOf<Mpp.BroadcastResponse>()
104
+ return
105
+ }
106
+
107
+ if (res.status === 503) {
108
+ const json = await res.json()
109
+ expectTypeOf(json).toEqualTypeOf<Mpp.Unavailable>()
110
+ return
111
+ }
112
+ })
113
+
60
114
  test('GET /v1/blocks', async () => {
61
115
  const client = Client.create()
62
116
  const res = await client.v1.blocks.$get()
@@ -125,7 +179,6 @@ describe('narrows responses by status', () => {
125
179
  case 200: {
126
180
  const json = await res.json()
127
181
  expectTypeOf(json.id).toEqualTypeOf<z.output<typeof Me.schema.User>['id']>()
128
- expectTypeOf(json.tmp_earlyAccess).toEqualTypeOf<boolean>()
129
182
  void json
130
183
  return
131
184
  }
@@ -229,6 +282,14 @@ describe('narrows responses by status', () => {
229
282
  expectTypeOf(json.tokens).toEqualTypeOf<
230
283
  z.output<typeof Faucet.schema.FundResponse>['tokens']
231
284
  >()
285
+ expectTypeOf(json.tokens[0]!.amount).toEqualTypeOf<
286
+ z.output<typeof Faucet.schema.FundResponse>['tokens'][number]['amount']
287
+ >()
288
+ expectTypeOf(json.tokens[0]!.token.symbol).toEqualTypeOf<string>()
289
+ // @ts-expect-error token metadata is nested under `token`
290
+ void json.tokens[0]!.symbol
291
+ // @ts-expect-error quantities are nested under `amount`
292
+ void json.tokens[0]!.token.amount
232
293
  expectTypeOf(json.transactionHashes).toEqualTypeOf<
233
294
  z.output<typeof Faucet.schema.FundResponse>['transactionHashes']
234
295
  >()
@@ -250,21 +311,16 @@ describe('narrows responses by status', () => {
250
311
  param: { orgId: 'org_test' },
251
312
  })
252
313
 
253
- expectTypeOf(res.status).toEqualTypeOf<200 | 404 | 501 | 502 | PublicAccessStatus>()
314
+ expectTypeOf(res.status).toEqualTypeOf<200 | 404 | 502 | PublicAccessStatus>()
254
315
 
255
316
  switch (res.status) {
256
317
  case 200: {
257
318
  const json = await res.json()
319
+ expectTypeOf(json.enabledSources).toEqualTypeOf<('stripe' | 'tempo')[]>()
258
320
  expectTypeOf(json.status).toEqualTypeOf<z.output<typeof Billing.schema.Billing>['status']>()
259
321
  void json
260
322
  return
261
323
  }
262
- case 501: {
263
- const json = await res.json()
264
- expectTypeOf(json.error.code).toEqualTypeOf<'billing_unconfigured'>()
265
- void json
266
- return
267
- }
268
324
  }
269
325
  })
270
326
 
@@ -302,11 +358,12 @@ describe('narrows responses by status', () => {
302
358
  param: { orgId: 'org_test' },
303
359
  })
304
360
 
305
- expectTypeOf(res.status).toEqualTypeOf<200 | 404 | 501 | 502 | PublicAccessStatus>()
361
+ expectTypeOf(res.status).toEqualTypeOf<200 | 404 | 502 | PublicAccessStatus>()
306
362
 
307
363
  switch (res.status) {
308
364
  case 200: {
309
365
  const json = await res.json()
366
+ expectTypeOf(json.enabledSources).toEqualTypeOf<('stripe' | 'tempo')[]>()
310
367
  expectTypeOf(json.spendLimit).toEqualTypeOf<
311
368
  z.output<typeof Billing.schema.SpendLimit> | undefined
312
369
  >()
@@ -314,12 +371,6 @@ describe('narrows responses by status', () => {
314
371
  void json
315
372
  return
316
373
  }
317
- case 501: {
318
- const json = await res.json()
319
- expectTypeOf(json.error.code).toEqualTypeOf<'billing_unconfigured'>()
320
- void json
321
- return
322
- }
323
374
  }
324
375
  })
325
376
 
@@ -341,7 +392,7 @@ describe('narrows responses by status', () => {
341
392
  case 403: {
342
393
  const json = await res.json()
343
394
  expectTypeOf(json.error.code).toEqualTypeOf<
344
- 'api_key_forbidden' | 'early_access_required' | 'forbidden'
395
+ 'api_key_forbidden' | 'billing_source_disabled' | 'forbidden'
345
396
  >()
346
397
  void json
347
398
  return
@@ -644,9 +695,7 @@ describe('narrows responses by status', () => {
644
695
  }
645
696
  case 403: {
646
697
  const json = await res.json()
647
- expectTypeOf(json.error.code).toEqualTypeOf<
648
- 'early_access_required' | 'forbidden' | 'scope_not_issuable'
649
- >()
698
+ expectTypeOf(json.error.code).toEqualTypeOf<'forbidden' | 'scope_not_issuable'>()
650
699
  void json
651
700
  return
652
701
  }
@@ -1503,10 +1552,20 @@ describe('narrows responses by status', () => {
1503
1552
  expectTypeOf(json.data[0]!.attribution).toEqualTypeOf<
1504
1553
  z.output<typeof Transfers.schema.getTransfers.Transfer>['attribution']
1505
1554
  >()
1506
- // `valuation` is `null` for unverified or unpriceable legs.
1507
- expectTypeOf(json.data[0]!.sourceToken.valuation).toEqualTypeOf<
1508
- { amount: string; currency: string } | null | undefined
1509
- >()
1555
+ expectTypeOf(json.data[0]!.destinationAmount).toEqualTypeOf<ValuedTokenAmount | undefined>()
1556
+ expectTypeOf(json.data[0]!.sourceAmount).toEqualTypeOf<ValuedTokenAmount>()
1557
+ expectTypeOf(json.data[0]!.sourceToken.currency).toEqualTypeOf<string>()
1558
+ expectTypeOf(json.data[0]!.sourceToken.decimals).toEqualTypeOf<number>()
1559
+ expectTypeOf(json.data[0]!.sourceToken.name).toEqualTypeOf<string>()
1560
+ expectTypeOf(json.data[0]!.sourceToken.symbol).toEqualTypeOf<string>()
1561
+ // @ts-expect-error token references do not contain quantities
1562
+ void json.data[0]!.sourceToken.amount
1563
+ // @ts-expect-error valuations belong to amount fields
1564
+ void json.data[0]!.sourceToken.valuation
1565
+ // @ts-expect-error destination token references do not contain quantities
1566
+ void json.data[0]!.destinationToken?.amount
1567
+ // @ts-expect-error destination valuations belong to amount fields
1568
+ void json.data[0]!.destinationToken?.valuation
1510
1569
  // `meta` (the opt-in `include=totalCount` capped count) is optional.
1511
1570
  expectTypeOf(json.meta).toEqualTypeOf<
1512
1571
  z.output<typeof Transfers.schema.getTransfers.Response>['meta']
@@ -1547,14 +1606,32 @@ describe('narrows responses by status', () => {
1547
1606
  switch (res.status) {
1548
1607
  case 200: {
1549
1608
  const json = await res.json()
1609
+ expectTypeOf(json.data[0]!.destinationAmount).toEqualTypeOf<{
1610
+ baseUnits: string
1611
+ currency: string
1612
+ decimals: number
1613
+ formatted: string
1614
+ }>()
1615
+ expectTypeOf(json.data[0]!.destinationAmountMin).toEqualTypeOf<
1616
+ | {
1617
+ baseUnits: string
1618
+ currency: string
1619
+ decimals: number
1620
+ formatted: string
1621
+ }
1622
+ | undefined
1623
+ >()
1550
1624
  expectTypeOf(json.data[0]!.sourceAmount).toEqualTypeOf<
1551
1625
  z.output<typeof Funding.schema.quoteFunding.Response>['data'][number]['sourceAmount']
1552
1626
  >()
1627
+ // @ts-expect-error token references do not contain quantities
1628
+ void json.data[0]!.sourceToken.amount
1629
+ // @ts-expect-error provider details no longer duplicate destination amounts
1630
+ void json.data[0]!.quote.outputAmount
1553
1631
  expectTypeOf(json.data[0]!.provider.id).toEqualTypeOf<
1554
1632
  'across' | 'relay' | 'rhino' | 'squid' | 'symbiosis'
1555
1633
  >()
1556
1634
  expectTypeOf(json.data[0]!.quote.expiresAt).toEqualTypeOf<string | undefined>()
1557
- expectTypeOf(json.data[0]!.quote.outputAmount).toEqualTypeOf<string>()
1558
1635
  void json
1559
1636
  return
1560
1637
  }
@@ -2053,12 +2130,16 @@ describe('narrows responses by status', () => {
2053
2130
  expectTypeOf(json.data[0]!.userToken.address).toEqualTypeOf<
2054
2131
  z.output<typeof FeeAmm.schema.getFeeAmmPools.Token>['address']
2055
2132
  >()
2056
- expectTypeOf(json.data[0]!.userToken.amount).toEqualTypeOf<
2057
- z.output<typeof FeeAmm.schema.getFeeAmmPools.Token>['amount']
2133
+ expectTypeOf(json.data[0]!.userAmount).toEqualTypeOf<
2134
+ z.output<typeof FeeAmm.schema.getFeeAmmPools.Pool>['userAmount']
2058
2135
  >()
2059
- expectTypeOf(json.data[0]!.validatorToken.formatted).toEqualTypeOf<
2060
- z.output<typeof FeeAmm.schema.getFeeAmmPools.Token>['formatted']
2136
+ expectTypeOf(json.data[0]!.validatorAmount).toEqualTypeOf<
2137
+ z.output<typeof FeeAmm.schema.getFeeAmmPools.Pool>['validatorAmount']
2061
2138
  >()
2139
+ // @ts-expect-error token references do not contain quantities
2140
+ void json.data[0]!.userToken.amount
2141
+ // @ts-expect-error token references do not contain formatted quantities
2142
+ void json.data[0]!.validatorToken.formatted
2062
2143
  expectTypeOf(json.nextCursor).toEqualTypeOf<string | null>()
2063
2144
  void json
2064
2145
  return
package/src/Scope.ts CHANGED
@@ -49,6 +49,11 @@ export const catalog = [
49
49
  scope: 'management:write',
50
50
  selfServe: true,
51
51
  },
52
+ {
53
+ description: 'Validate and broadcast MPP payment credentials through the Tempo relay.',
54
+ scope: 'mpp:write',
55
+ selfServe: true,
56
+ },
52
57
  {
53
58
  description: 'Fill and submit transactions through the relay.',
54
59
  scope: 'rpc-relay:read',
@@ -20,6 +20,10 @@ describe('create', () => {
20
20
  new Request('http://admin/api-keys/key_x', { method: 'DELETE' }),
21
21
  new Request('http://admin/organizations'),
22
22
  new Request('http://admin/organizations/org_x'),
23
+ new Request('http://admin/organizations/org_x/billing-sources/stripe', { method: 'PUT' }),
24
+ new Request('http://admin/organizations/org_x/billing-sources/stripe', {
25
+ method: 'DELETE',
26
+ }),
23
27
  new Request('http://admin/organizations/org_x/members'),
24
28
  new Request('http://admin/organizations/org_x/projects'),
25
29
  new Request('http://admin/verified-tokens?chainId=42431', {
@@ -29,7 +29,7 @@ export namespace schema {
29
29
  z.object({
30
30
  data: z.array(Entry).check(z.describe('Allowlist entries, ordered by entry.')),
31
31
  }),
32
- 'Early-access allowlist entries.',
32
+ 'Accounts whose new organizations receive early access features by default.',
33
33
  )
34
34
  }
35
35
 
@@ -42,7 +42,7 @@ export namespace schema {
42
42
  z.trim(),
43
43
  z.toLowerCase(),
44
44
  z.regex(entryPattern, 'Must be a bare domain (example.org) or a full email (user@example.org).'), // prettier-ignore
45
- z.describe('Domain or exact email to grant early access; normalized lowercase.'),
45
+ z.describe('Domain or exact email whose new organizations receive early access features; normalized lowercase.'), // prettier-ignore
46
46
  ),
47
47
  })
48
48
  .check(z.describe('Request body for adding an allowlist entry.'))
@@ -77,8 +77,8 @@ export namespace schema {
77
77
  }
78
78
 
79
79
  /**
80
- * Management routes for the temporary early-access allowlist (`early_access`
81
- * table): entries are lowercase domains or exact emails.
80
+ * Management routes for the early-access allowlist (`early_access` table).
81
+ * Matching users receive Stripe by default when they create an organization.
82
82
  *
83
83
  * - `GET /` lists entries.
84
84
  * - `POST /` adds an entry (`409` on a duplicate).
@@ -92,7 +92,8 @@ export function earlyAccess() {
92
92
  operationId: 'listEarlyAccessEntries',
93
93
  responses: OpenApi.responses({
94
94
  success: {
95
- description: 'Early-access allowlist entries.',
95
+ description:
96
+ 'Accounts whose new organizations receive early access features by default.',
96
97
  schema: schema.listEarlyAccessEntries.Response,
97
98
  },
98
99
  }),
@@ -1,6 +1,7 @@
1
1
  import type * as Analytics from '../../analytics/Analytics.js'
2
2
  import * as ApiKeys from '../../ApiKeys.js'
3
3
  import * as BillingSettings from '../../db/tables/billingSettings.js'
4
+ import * as EnabledBillingSources from '../../db/tables/enabledBillingSources.js'
4
5
  import * as Memberships from '../../db/tables/memberships.js'
5
6
  import * as Organizations from '../../db/tables/organizations.js'
6
7
  import * as Projects from '../../db/tables/projects.js'
@@ -134,6 +135,16 @@ describe('GET /organizations/:orgId', () => {
134
135
  })
135
136
  await Users.setEmail(db, user.id, 'owner@tempo.xyz')
136
137
  const organization = await Organizations.createOwned(db, { name: 'Tempo', userId: user.id })
138
+ await EnabledBillingSources.add(db, {
139
+ createdBy: TestAdmin.identity.email,
140
+ orgId: organization.id,
141
+ source: 'stripe',
142
+ })
143
+ await EnabledBillingSources.add(db, {
144
+ createdBy: TestAdmin.identity.email,
145
+ orgId: organization.id,
146
+ source: 'tempo',
147
+ })
137
148
  await Projects.create(db, { name: 'API', orgId: organization.id })
138
149
  await BillingSettings.upsert(db, {
139
150
  environment: 'production',
@@ -161,6 +172,7 @@ describe('GET /organizations/:orgId', () => {
161
172
  expect(response.status).toBe(200)
162
173
  expect(body.memberCount).toBe(1)
163
174
  expect(body.projectCount).toBe(1)
175
+ expect(body.billing.enabledSources).toEqual(['stripe', 'tempo'])
164
176
  expect(body.billing.production).toMatchObject({
165
177
  settings: { spendLimit: '250', txFeeLimit: '0.50' },
166
178
  source: { status: 'active', stripeCustomerId: 'cus_admin_lookup' },
@@ -182,6 +194,7 @@ describe('GET /organizations/:orgId', () => {
182
194
 
183
195
  expect(body).toMatchObject({
184
196
  billing: {
197
+ enabledSources: [],
185
198
  production: { settings: null, source: null },
186
199
  sandbox: { settings: null, source: null },
187
200
  },
@@ -202,6 +215,78 @@ describe('GET /organizations/:orgId', () => {
202
215
  })
203
216
  })
204
217
 
218
+ describe('PUT/DELETE /organizations/:orgId/billing-sources/:source', () => {
219
+ test('enables and disables sources idempotently', async () => {
220
+ const { client, db } = TestAdmin.setup()
221
+ const organization = await Organizations.create(db, { name: 'Billing sources' })
222
+
223
+ for (const source of ['tempo', 'stripe', 'stripe'] as const) {
224
+ const response = await client.organizations[':orgId']['billing-sources'][':source'].$put({
225
+ param: { orgId: organization.id, source },
226
+ })
227
+ const body = await TestApp.json(response, AdminOrganizations.schema.setBillingSource.Response)
228
+ expect(response.status).toBe(200)
229
+ expect(body.data).toEqual(source === 'tempo' ? ['tempo'] : ['stripe', 'tempo'])
230
+ }
231
+
232
+ expect(await EnabledBillingSources.listByOrg(db, organization.id)).toMatchObject([
233
+ { createdBy: TestAdmin.identity.email, source: 'stripe' },
234
+ { createdBy: TestAdmin.identity.email, source: 'tempo' },
235
+ ])
236
+
237
+ for (const source of ['stripe', 'stripe'] as const) {
238
+ const response = await client.organizations[':orgId']['billing-sources'][':source'].$delete({
239
+ param: { orgId: organization.id, source },
240
+ })
241
+ const body = await TestApp.json(response, AdminOrganizations.schema.setBillingSource.Response)
242
+ expect(response.status).toBe(200)
243
+ expect(body.data).toEqual(['tempo'])
244
+ }
245
+
246
+ const detailResponse = await client.organizations[':orgId'].$get({
247
+ param: { orgId: organization.id },
248
+ })
249
+ const detail = await TestApp.json(
250
+ detailResponse,
251
+ AdminOrganizations.schema.getOrganization.Response,
252
+ )
253
+ expect(detail.billing.enabledSources).toEqual(['tempo'])
254
+ })
255
+
256
+ test('rejects unsupported sources and unknown organizations', async () => {
257
+ const { app, client } = TestAdmin.setup()
258
+ const invalid = await app.request('/organizations/org_example/billing-sources/card', {
259
+ method: 'PUT',
260
+ })
261
+ expect(invalid.status).toBe(400)
262
+
263
+ const missing = await client.organizations[':orgId']['billing-sources'][':source'].$put({
264
+ param: { orgId: 'org_missing', source: 'stripe' },
265
+ })
266
+ expect(missing.status).toBe(404)
267
+ })
268
+
269
+ test('records billing-source mutations in the admin audit log', async () => {
270
+ const { client, db } = TestAdmin.setup()
271
+ const organization = await Organizations.create(db, { name: 'Audited billing' })
272
+ await client.organizations[':orgId']['billing-sources'][':source'].$put({
273
+ param: { orgId: organization.id, source: 'tempo' },
274
+ })
275
+
276
+ const records = await db.kysely
277
+ .selectFrom('admin_audit_logs')
278
+ .selectAll()
279
+ .orderBy('createdAt', 'desc')
280
+ .execute()
281
+ expect(records[0]).toMatchObject({
282
+ actor: TestAdmin.identity.email,
283
+ method: 'PUT',
284
+ path: `/organizations/${organization.id}/billing-sources/tempo`,
285
+ status: 200,
286
+ })
287
+ })
288
+ })
289
+
205
290
  describe('GET /organizations/:orgId/members', () => {
206
291
  test('returns a bounded cursor page without duplicates', async () => {
207
292
  const { client, db } = TestAdmin.setup()
@@ -444,8 +529,7 @@ const usageAnalytics: Analytics.Analytics = {
444
529
  async insert() {},
445
530
  async migrate() {},
446
531
  async query(sql) {
447
- if (sql.includes('GROUP BY error_code'))
448
- return [{ code: 'upstream_error', requests: 2 }] as never[]
532
+ if (sql.includes('GROUP BY code')) return [{ code: 'upstream_error', requests: 2 }] as never[]
449
533
  if (sql.includes('GROUP BY route'))
450
534
  return [{ averageDurationMs: 12.5, errors: 2, requests: 20, route: '/v1/tokens' }] as never[]
451
535
  if (sql.includes('GROUP BY status')) return [{ requests: 2, status: 500 }] as never[]