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
@@ -6,6 +6,7 @@ import * as ApiKeys from '../../ApiKeys.js'
6
6
  import * as core_Billing from '../../apps/management/Billing.js'
7
7
  import * as BillingSettings from '../../db/tables/billingSettings.js'
8
8
  import * as Db from '../../db/Db.js'
9
+ import * as EnabledBillingSources from '../../db/tables/enabledBillingSources.js'
9
10
  import * as Memberships from '../../db/tables/memberships.js'
10
11
  import * as Organizations from '../../db/tables/organizations.js'
11
12
  import * as Projects from '../../db/tables/projects.js'
@@ -26,6 +27,21 @@ export namespace schema {
26
27
  .string()
27
28
  .check(z.describe('An ISO 8601 timestamp.'), z.meta({ examples: ['2026-01-01T00:00:00.000Z'] }))
28
29
 
30
+ const enabledBillingSources = z
31
+ .array(z.enum(EnabledBillingSources.sources))
32
+ .check(
33
+ z.describe('Billing-source types available for new setup.'),
34
+ z.meta({ examples: [['stripe', 'tempo']] }),
35
+ )
36
+
37
+ const organizationId = z
38
+ .string()
39
+ .check(
40
+ z.minLength(1),
41
+ z.describe('Stable organization id.'),
42
+ z.meta({ examples: ['org_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
43
+ )
44
+
29
45
  const Organization = Schema.describe(
30
46
  z.object({
31
47
  createdAt: timestamp,
@@ -118,13 +134,7 @@ export namespace schema {
118
134
  /** Path parameters identifying an organization. */
119
135
  export const Params = z
120
136
  .object({
121
- orgId: z
122
- .string()
123
- .check(
124
- z.minLength(1),
125
- z.describe('Stable organization id.'),
126
- z.meta({ examples: ['org_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
127
- ),
137
+ orgId: organizationId,
128
138
  })
129
139
  .check(z.describe('Path parameters for one organization.'))
130
140
 
@@ -252,29 +262,58 @@ export namespace schema {
252
262
  /** Detailed organization lookup response. */
253
263
  export const Response = Schema.describe(
254
264
  z.extend(listOrganizations.OrganizationSummary, {
255
- billing: z.object({ production: Billing, sandbox: Billing }).check(
256
- z.describe('Billing state split by environment.'),
257
- z.meta({
258
- examples: [
259
- {
260
- production: {
261
- settings: null,
262
- source: {
263
- status: 'active',
264
- stripeCustomerId: 'cus_abc123',
265
- updatedAt: '2026-01-01T00:00:00.000Z',
265
+ billing: z
266
+ .object({
267
+ enabledSources: enabledBillingSources,
268
+ production: Billing,
269
+ sandbox: Billing,
270
+ })
271
+ .check(
272
+ z.describe('Organization-wide availability and environment billing state.'),
273
+ z.meta({
274
+ examples: [
275
+ {
276
+ enabledSources: ['stripe', 'tempo'],
277
+ production: {
278
+ settings: null,
279
+ source: {
280
+ status: 'active',
281
+ stripeCustomerId: 'cus_abc123',
282
+ updatedAt: '2026-01-01T00:00:00.000Z',
283
+ },
266
284
  },
285
+ sandbox: { settings: null, source: null },
267
286
  },
268
- sandbox: { settings: null, source: null },
269
- },
270
- ],
271
- }),
272
- ),
287
+ ],
288
+ }),
289
+ ),
273
290
  }),
274
291
  'Detailed organization information for administrators.',
275
292
  )
276
293
  }
277
294
 
295
+ /** Schemas for the setBillingSource operation. */
296
+ export namespace setBillingSource {
297
+ /** Path parameters identifying an organization billing source. */
298
+ export const Params = z
299
+ .object({
300
+ orgId: organizationId,
301
+ source: z
302
+ .enum(EnabledBillingSources.sources)
303
+ .check(
304
+ z.describe('Billing-source type to enable or disable.'),
305
+ z.meta({ examples: ['stripe'] }),
306
+ ),
307
+ })
308
+ .check(z.describe('Path parameters for one organization billing source.'))
309
+
310
+ /** Current enabled billing sources. */
311
+ export const Response = Schema.describe(
312
+ z.object({ data: enabledBillingSources }),
313
+ 'Current enabled billing sources for the organization.',
314
+ )
315
+ }
316
+
278
317
  /** Schemas for the listOrganizationMembers operation. */
279
318
  export namespace listOrganizationMembers {
280
319
  /** Query parameters for paginating organization members. */
@@ -502,7 +541,7 @@ export namespace schema {
502
541
  }
503
542
  }
504
543
 
505
- /** Read-only organization lookup routes for administrators. */
544
+ /** Organization lookup and support routes for administrators. */
506
545
  export function organizations() {
507
546
  return new Hono<App.Environment>()
508
547
  .get(
@@ -576,17 +615,24 @@ export function organizations() {
576
615
  const organization = await Organizations.getSummary(db, orgId)
577
616
  if (!organization) return organizationNotFound(c)
578
617
 
579
- const [productionSettings, productionSource, sandboxSettings, sandboxSource] =
580
- await Promise.all([
581
- BillingSettings.get(db, orgId, 'production'),
582
- StripeCustomers.get(db, orgId, 'production'),
583
- BillingSettings.get(db, orgId, 'sandbox'),
584
- StripeCustomers.get(db, orgId, 'sandbox'),
585
- ])
618
+ const [
619
+ enabledSources,
620
+ productionSettings,
621
+ productionSource,
622
+ sandboxSettings,
623
+ sandboxSource,
624
+ ] = await Promise.all([
625
+ EnabledBillingSources.listByOrg(db, orgId),
626
+ BillingSettings.get(db, orgId, 'production'),
627
+ StripeCustomers.get(db, orgId, 'production'),
628
+ BillingSettings.get(db, orgId, 'sandbox'),
629
+ StripeCustomers.get(db, orgId, 'sandbox'),
630
+ ])
586
631
  return c.json(
587
632
  Response.validated(schema.getOrganization.Response, {
588
633
  ...organization,
589
634
  billing: {
635
+ enabledSources: enabledSources.map((record) => record.source),
590
636
  production: serializeBilling(productionSettings, productionSource),
591
637
  sandbox: serializeBilling(sandboxSettings, sandboxSource),
592
638
  },
@@ -598,6 +644,84 @@ export function organizations() {
598
644
  }
599
645
  },
600
646
  )
647
+ .put(
648
+ '/:orgId/billing-sources/:source',
649
+ OpenApi.validate('param', schema.setBillingSource.Params, {
650
+ code: 'param_invalid',
651
+ message: 'Invalid path parameters',
652
+ }),
653
+ OpenApi.describeRoute({
654
+ operationId: 'enableAdminOrganizationBillingSource',
655
+ responses: OpenApi.responses({
656
+ errors: { 404: 'Organization not found.' },
657
+ success: {
658
+ description: 'Current enabled billing sources.',
659
+ schema: schema.setBillingSource.Response,
660
+ },
661
+ }),
662
+ summary: 'Enable billing source',
663
+ tags: ['Organizations'],
664
+ }),
665
+ async (c) => {
666
+ const { orgId, source } = c.req.valid('param')
667
+ const db = Db.get(c.get('db'))
668
+ try {
669
+ if (!(await Organizations.get(db, orgId))) return organizationNotFound(c)
670
+ await EnabledBillingSources.add(db, {
671
+ createdBy: c.get('identity')!.email,
672
+ orgId,
673
+ source,
674
+ })
675
+ return c.json(
676
+ Response.validated(schema.setBillingSource.Response, {
677
+ data: (await EnabledBillingSources.listByOrg(db, orgId)).map(
678
+ (record) => record.source,
679
+ ),
680
+ }),
681
+ 200,
682
+ )
683
+ } catch (cause) {
684
+ return Response.upstream(c, cause)
685
+ }
686
+ },
687
+ )
688
+ .delete(
689
+ '/:orgId/billing-sources/:source',
690
+ OpenApi.validate('param', schema.setBillingSource.Params, {
691
+ code: 'param_invalid',
692
+ message: 'Invalid path parameters',
693
+ }),
694
+ OpenApi.describeRoute({
695
+ operationId: 'disableAdminOrganizationBillingSource',
696
+ responses: OpenApi.responses({
697
+ errors: { 404: 'Organization not found.' },
698
+ success: {
699
+ description: 'Current enabled billing sources.',
700
+ schema: schema.setBillingSource.Response,
701
+ },
702
+ }),
703
+ summary: 'Disable billing source',
704
+ tags: ['Organizations'],
705
+ }),
706
+ async (c) => {
707
+ const { orgId, source } = c.req.valid('param')
708
+ const db = Db.get(c.get('db'))
709
+ try {
710
+ if (!(await Organizations.get(db, orgId))) return organizationNotFound(c)
711
+ await EnabledBillingSources.remove(db, { orgId, source })
712
+ return c.json(
713
+ Response.validated(schema.setBillingSource.Response, {
714
+ data: (await EnabledBillingSources.listByOrg(db, orgId)).map(
715
+ (record) => record.source,
716
+ ),
717
+ }),
718
+ 200,
719
+ )
720
+ } catch (cause) {
721
+ return Response.upstream(c, cause)
722
+ }
723
+ },
724
+ )
601
725
  .get(
602
726
  '/:orgId/members',
603
727
  OpenApi.validate('param', schema.getOrganization.Params, {
@@ -34,6 +34,11 @@ describe('GET /scopes', () => {
34
34
  "scope": "management:write",
35
35
  "selfServe": true,
36
36
  },
37
+ {
38
+ "description": "Validate and broadcast MPP payment credentials through the Tempo relay.",
39
+ "scope": "mpp:write",
40
+ "selfServe": true,
41
+ },
37
42
  {
38
43
  "description": "Fill and submit transactions through the relay.",
39
44
  "scope": "rpc-relay:read",
@@ -1,9 +1,11 @@
1
1
  import { QueryClient } from '@tanstack/react-query'
2
2
  import { createMemoryHistory, createRouter } from '@tanstack/react-router'
3
+ import { createElement } from 'react'
4
+ import { renderToStaticMarkup } from 'react-dom/server'
3
5
  import { describe, expect, it, vi } from 'vite-plus/test'
4
6
 
5
7
  import { routeTree } from '../routeTree.gen'
6
- import { Route as DetailRoute } from '../routes/organizations_.$orgId'
8
+ import { BillingSourcesCard, Route as DetailRoute } from '../routes/organizations_.$orgId'
7
9
  import { Route as ListRoute } from '../routes/organizations'
8
10
  import * as Organizations from './organizations'
9
11
 
@@ -49,6 +51,84 @@ describe('searchParameters', () => {
49
51
  })
50
52
  })
51
53
 
54
+ describe('withEnabledSources', () => {
55
+ it('replaces only organization-wide billing availability', () => {
56
+ const detail = {
57
+ billing: {
58
+ enabledSources: [],
59
+ production: { settings: null, source: null },
60
+ sandbox: { settings: null, source: null },
61
+ },
62
+ createdAt: '2026-01-01T00:00:00.000Z',
63
+ createdBy: null,
64
+ id: 'org_example',
65
+ memberCount: 0,
66
+ name: 'Example',
67
+ projectCount: 0,
68
+ updatedAt: '2026-01-01T00:00:00.000Z',
69
+ userId: null,
70
+ } satisfies Organizations.Detail
71
+
72
+ const updated = Organizations.withEnabledSources({
73
+ detail,
74
+ enabledSources: ['stripe', 'tempo'],
75
+ })
76
+
77
+ expect({
78
+ original: detail.billing.enabledSources,
79
+ productionPreserved: updated.billing.production === detail.billing.production,
80
+ updated: updated.billing.enabledSources,
81
+ }).toMatchInlineSnapshot(`
82
+ {
83
+ "original": [],
84
+ "productionPreserved": true,
85
+ "updated": [
86
+ "stripe",
87
+ "tempo",
88
+ ],
89
+ }
90
+ `)
91
+ })
92
+ })
93
+
94
+ describe('BillingSourcesCard', () => {
95
+ it('renders source state and the setup-only warning', () => {
96
+ const markup = renderToStaticMarkup(
97
+ createElement(BillingSourcesCard, {
98
+ enabledSources: ['stripe'],
99
+ error: null,
100
+ onChange() {},
101
+ pending: false,
102
+ }),
103
+ )
104
+
105
+ function isChecked(label: string) {
106
+ const marker = `aria-label="${label} billing source"`
107
+ const markerIndex = markup.indexOf(marker)
108
+ if (markerIndex === -1) return undefined
109
+ const tag = markup.slice(
110
+ markup.lastIndexOf('<', markerIndex),
111
+ markup.indexOf('>', markerIndex),
112
+ )
113
+ return tag.includes('aria-checked="true"')
114
+ }
115
+
116
+ expect({
117
+ stripe: isChecked('Stripe'),
118
+ tempo: isChecked('Tempo'),
119
+ warning: markup.includes(
120
+ 'Disabling a source only blocks new setup. Existing billing connections continue to work.',
121
+ ),
122
+ }).toMatchInlineSnapshot(`
123
+ {
124
+ "stripe": true,
125
+ "tempo": false,
126
+ "warning": true,
127
+ }
128
+ `)
129
+ })
130
+ })
131
+
52
132
  describe('organization routes', () => {
53
133
  it('matches a direct detail URL without the list route as a parent', () => {
54
134
  const router = createRouter({
@@ -6,6 +6,10 @@ import { client } from './client'
6
6
 
7
7
  type ListResponse = InferResponseType<(typeof client)['organizations']['$get'], 200>
8
8
  type DetailResponse = InferResponseType<(typeof client)['organizations'][':orgId']['$get'], 200>
9
+ type BillingSourcesResponse = InferResponseType<
10
+ (typeof client)['organizations'][':orgId']['billing-sources'][':source']['$put'],
11
+ 200
12
+ >
9
13
  type MembersResponse = InferResponseType<
10
14
  (typeof client)['organizations'][':orgId']['members']['$get'],
11
15
  200
@@ -37,6 +41,9 @@ export type Organization = ListResponse['data'][number]
37
41
  /** Detailed organization information, including billing state. */
38
42
  export type Detail = DetailResponse
39
43
 
44
+ /** An organization-wide billing source. */
45
+ export type BillingSource = Detail['billing']['enabledSources'][number]
46
+
40
47
  /** An organization member with resolved identity. */
41
48
  export type Member = MembersResponse['data'][number]
42
49
 
@@ -138,6 +145,91 @@ export namespace get {
138
145
  }
139
146
  }
140
147
 
148
+ /** Returns organization detail with its enabled billing sources replaced. */
149
+ export function withEnabledSources(options: withEnabledSources.Options): Detail {
150
+ return {
151
+ ...options.detail,
152
+ billing: {
153
+ ...options.detail.billing,
154
+ enabledSources: options.enabledSources,
155
+ },
156
+ }
157
+ }
158
+
159
+ export declare namespace withEnabledSources {
160
+ /** Inputs for replacing enabled billing sources in cached organization detail. */
161
+ type Options = {
162
+ /** Organization detail to update. */
163
+ detail: Detail
164
+ /** Current enabled billing sources. */
165
+ enabledSources: Detail['billing']['enabledSources']
166
+ }
167
+ }
168
+
169
+ /** Enable an organization-wide billing source. */
170
+ export namespace enableBillingSource {
171
+ /** Billing source to enable for an organization. */
172
+ export type Input = {
173
+ /** Organization id. */
174
+ orgId: string
175
+ /** Billing source to enable. */
176
+ source: BillingSource
177
+ }
178
+
179
+ /** Enables a billing source and returns the current enabled sources. */
180
+ export async function fn(options: Input) {
181
+ const response = await client.organizations[':orgId']['billing-sources'][':source'].$put({
182
+ param: options,
183
+ })
184
+ if (!response.ok) throw new Error('Failed to enable billing source.')
185
+ return (await response.json()) as BillingSourcesResponse
186
+ }
187
+
188
+ /** Creates a mutation that enables a billing source. */
189
+ export function useMutation(orgId: string) {
190
+ const queryClient = ReactQuery.useQueryClient()
191
+ return ReactQuery.useMutation({
192
+ mutationFn: (source: BillingSource) => fn({ orgId, source }),
193
+ onSuccess: (response) =>
194
+ queryClient.setQueryData(get.key(orgId), (detail: Detail | undefined) =>
195
+ detail ? withEnabledSources({ detail, enabledSources: response.data }) : detail,
196
+ ),
197
+ })
198
+ }
199
+ }
200
+
201
+ /** Disable an organization-wide billing source. */
202
+ export namespace disableBillingSource {
203
+ /** Billing source to disable for an organization. */
204
+ export type Input = {
205
+ /** Organization id. */
206
+ orgId: string
207
+ /** Billing source to disable. */
208
+ source: BillingSource
209
+ }
210
+
211
+ /** Disables a billing source and returns the current enabled sources. */
212
+ export async function fn(options: Input) {
213
+ const response = await client.organizations[':orgId']['billing-sources'][':source'].$delete({
214
+ param: options,
215
+ })
216
+ if (!response.ok) throw new Error('Failed to disable billing source.')
217
+ return (await response.json()) as BillingSourcesResponse
218
+ }
219
+
220
+ /** Creates a mutation that disables a billing source. */
221
+ export function useMutation(orgId: string) {
222
+ const queryClient = ReactQuery.useQueryClient()
223
+ return ReactQuery.useMutation({
224
+ mutationFn: (source: BillingSource) => fn({ orgId, source }),
225
+ onSuccess: (response) =>
226
+ queryClient.setQueryData(get.key(orgId), (detail: Detail | undefined) =>
227
+ detail ? withEnabledSources({ detail, enabledSources: response.data }) : detail,
228
+ ),
229
+ })
230
+ }
231
+ }
232
+
141
233
  /** List members for one organization. */
142
234
  export namespace members {
143
235
  export function key(orgId: string) {
@@ -218,7 +218,7 @@ function ProvisionForm(props: {
218
218
  </select>
219
219
  </div>
220
220
  <Input
221
- defaultValue="100"
221
+ defaultValue="10000"
222
222
  label="Rate limit"
223
223
  min={0}
224
224
  name="rateLimit"
@@ -21,7 +21,7 @@ function RouteComponent() {
21
21
  <Dialog.Root onOpenChange={setOpen} open={open}>
22
22
  <Page
23
23
  title="Early Access"
24
- description="Manage which accounts have early access"
24
+ description="Accounts whose new organizations receive early access features by default."
25
25
  action={
26
26
  <Dialog.Trigger
27
27
  render={
@@ -69,7 +69,7 @@ function AddEntryForm(props: {
69
69
  <>
70
70
  <Dialog.Title>Add Entry</Dialog.Title>
71
71
  <Dialog.Description>
72
- Grant early access to a whole domain or a single email
72
+ Grant early access features to new organizations created by a whole domain or a single email
73
73
  </Dialog.Description>
74
74
  <form className="mt-6 flex flex-col gap-8" onSubmit={onSubmit}>
75
75
  <Fields className="gap-6" variant="default">
@@ -122,7 +122,12 @@ function EntryRow(props: { entry: EarlyAccess.Entry }) {
122
122
  const remove = EarlyAccess.remove.useMutation()
123
123
 
124
124
  function onRemove() {
125
- if (!confirm(`Remove ${entry.entry}? Matching users lose early access.`)) return
125
+ if (
126
+ !confirm(
127
+ `Remove ${entry.entry}? Future organizations created by matching users start without early access features. Existing organizations keep their enabled billing sources.`,
128
+ )
129
+ )
130
+ return
126
131
  remove.mutate({ entry: entry.entry }, { onError: (error) => alert(error.message) })
127
132
  }
128
133
 
@@ -1,3 +1,4 @@
1
+ import { Switch } from '@base-ui/react/switch'
1
2
  import {
2
3
  type UseQueryResult,
3
4
  useInfiniteQuery,
@@ -30,6 +31,8 @@ function PageComponent() {
30
31
  const { period: period_search, query } = Route.useSearch()
31
32
  const navigate = useNavigate({ from: Route.fullPath })
32
33
  const period = period_search ?? '7d'
34
+ const disableBillingSource = Organizations.disableBillingSource.useMutation(orgId)
35
+ const enableBillingSource = Organizations.enableBillingSource.useMutation(orgId)
33
36
  const organizationQuery = useSuspenseQuery(Organizations.get.options(orgId))
34
37
  const organization = organizationQuery.data
35
38
  const members = useInfiniteQuery(Organizations.members.options(orgId))
@@ -54,6 +57,8 @@ function PageComponent() {
54
57
  const updateComplete = operationalQueries.every((result) => result.dataUpdatedAt > 0)
55
58
  const updateError = operationalQueries.some((result) => result.error)
56
59
  const refreshing = operationalQueries.some((result) => result.isFetching)
60
+ const billingSourceError = disableBillingSource.error ?? enableBillingSource.error
61
+ const billingSourcePending = disableBillingSource.isPending || enableBillingSource.isPending
57
62
 
58
63
  function refresh() {
59
64
  void Promise.all([
@@ -67,6 +72,16 @@ function PageComponent() {
67
72
  ])
68
73
  }
69
74
 
75
+ function setBillingSource(options: BillingSourceChange) {
76
+ if (options.enabled) {
77
+ disableBillingSource.reset()
78
+ enableBillingSource.mutate(options.source)
79
+ } else {
80
+ enableBillingSource.reset()
81
+ disableBillingSource.mutate(options.source)
82
+ }
83
+ }
84
+
70
85
  return (
71
86
  <Page
72
87
  title={organization.name}
@@ -176,6 +191,12 @@ function PageComponent() {
176
191
 
177
192
  <section className="flex flex-col gap-3">
178
193
  <h2 className="text-heading-18 text-foreground">Billing</h2>
194
+ <BillingSourcesCard
195
+ enabledSources={organization.billing.enabledSources}
196
+ error={billingSourceError}
197
+ onChange={setBillingSource}
198
+ pending={billingSourcePending}
199
+ />
179
200
  <div className="grid gap-3 md:grid-cols-2">
180
201
  <BillingCard
181
202
  environment="Production"
@@ -550,6 +571,88 @@ declare namespace SectionError {
550
571
  type Props = { error: Error; onRetry: () => unknown }
551
572
  }
552
573
 
574
+ type BillingSourceChange = {
575
+ /** Whether the source should be enabled. */
576
+ enabled: boolean
577
+ /** Billing source being changed. */
578
+ source: Organizations.BillingSource
579
+ }
580
+
581
+ const billingSourceOptions = [
582
+ {
583
+ description: 'Allow the organization to set up Stripe as a billing method.',
584
+ label: 'Stripe',
585
+ source: 'stripe',
586
+ },
587
+ {
588
+ description: 'Allow the organization to set up a Tempo account as a billing method.',
589
+ label: 'Tempo',
590
+ source: 'tempo',
591
+ },
592
+ ] as const
593
+
594
+ /** Organization-wide controls for billing-source setup. */
595
+ export function BillingSourcesCard(options: BillingSourcesCard.Props) {
596
+ return (
597
+ <Card>
598
+ <div className="p-4">
599
+ <h3 className="text-label-14 text-foreground">Enabled sources</h3>
600
+ <p className="mt-1 text-copy-13 text-foreground-secondary">
601
+ Disabling a source only blocks new setup. Existing billing connections continue to work.
602
+ </p>
603
+ </div>
604
+ <div className="divide-y divide-border border-t border-border">
605
+ {billingSourceOptions.map((source) => {
606
+ const enabled = options.enabledSources.includes(source.source)
607
+ return (
608
+ <div className="flex items-center justify-between gap-4 p-4" key={source.source}>
609
+ <div className="min-w-0">
610
+ <div className="flex items-center gap-2">
611
+ <span className="text-label-14 text-foreground">{source.label}</span>
612
+ <span className="text-label-12 text-foreground-secondary">
613
+ {enabled ? 'Enabled' : 'Disabled'}
614
+ </span>
615
+ </div>
616
+ <p className="mt-1 text-copy-13 text-foreground-secondary">{source.description}</p>
617
+ </div>
618
+ <Switch.Root
619
+ aria-label={`${source.label} billing source`}
620
+ checked={enabled}
621
+ className="flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border border-border bg-secondary p-0.5 outline-none transition-colors hover:bg-secondary-hover focus-visible:border-focus data-[checked]:bg-invert disabled:cursor-not-allowed disabled:opacity-50"
622
+ disabled={options.pending}
623
+ onCheckedChange={(checked) =>
624
+ options.onChange({ enabled: checked, source: source.source })
625
+ }
626
+ >
627
+ <Switch.Thumb className="block size-4 translate-x-0 rounded-full bg-foreground-secondary transition-transform data-[checked]:translate-x-4 data-[checked]:bg-on-invert" />
628
+ </Switch.Root>
629
+ </div>
630
+ )
631
+ })}
632
+ </div>
633
+ {options.error ? (
634
+ <p className="border-t border-border p-4 text-copy-13 text-negative" role="alert">
635
+ {options.error.message}
636
+ </p>
637
+ ) : null}
638
+ </Card>
639
+ )
640
+ }
641
+
642
+ export declare namespace BillingSourcesCard {
643
+ /** Billing-source control properties. */
644
+ type Props = {
645
+ /** Currently enabled billing sources. */
646
+ enabledSources: readonly Organizations.BillingSource[]
647
+ /** Latest mutation error. */
648
+ error: Error | null
649
+ /** Handles a requested source change. */
650
+ onChange: (options: BillingSourceChange) => void
651
+ /** Whether a source change is pending. */
652
+ pending: boolean
653
+ }
654
+ }
655
+
553
656
  function BillingCard(options: BillingCard.Props) {
554
657
  const { settings, source } = options.record
555
658
  const remaining =