tapimo 0.5.2 → 0.6.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 (299) hide show
  1. package/dist/App.d.ts +13987 -5755
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js.map +1 -1
  4. package/dist/admin/App.d.ts +910 -412
  5. package/dist/admin/App.d.ts.map +1 -1
  6. package/dist/admin/App.js +4 -0
  7. package/dist/admin/App.js.map +1 -1
  8. package/dist/admin/apps/early-access.d.ts +148 -0
  9. package/dist/admin/apps/early-access.d.ts.map +1 -0
  10. package/dist/admin/apps/early-access.js +133 -0
  11. package/dist/admin/apps/early-access.js.map +1 -0
  12. package/dist/admin/apps/verified-tokens.d.ts +643 -118
  13. package/dist/admin/apps/verified-tokens.d.ts.map +1 -1
  14. package/dist/admin/apps/verified-tokens.js +214 -1
  15. package/dist/admin/apps/verified-tokens.js.map +1 -1
  16. package/dist/admin/ui.generated.d.ts +1 -1
  17. package/dist/admin/ui.generated.d.ts.map +1 -1
  18. package/dist/admin/ui.generated.js +45 -33
  19. package/dist/admin/ui.generated.js.map +1 -1
  20. package/dist/apps/data/App.d.ts +3733 -2428
  21. package/dist/apps/data/App.d.ts.map +1 -1
  22. package/dist/apps/data/App.js +11 -6
  23. package/dist/apps/data/App.js.map +1 -1
  24. package/dist/apps/data/FundingQuoteProviders.d.ts +34 -1
  25. package/dist/apps/data/FundingQuoteProviders.d.ts.map +1 -1
  26. package/dist/apps/data/FundingQuoteProviders.js +193 -2
  27. package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
  28. package/dist/apps/data/FxOracle.d.ts +71 -0
  29. package/dist/apps/data/FxOracle.d.ts.map +1 -0
  30. package/dist/apps/data/FxOracle.js +97 -0
  31. package/dist/apps/data/FxOracle.js.map +1 -0
  32. package/dist/apps/data/index.d.ts +1 -0
  33. package/dist/apps/data/index.d.ts.map +1 -1
  34. package/dist/apps/data/index.js +1 -0
  35. package/dist/apps/data/index.js.map +1 -1
  36. package/dist/apps/data/routes/activities.d.ts +3575 -1358
  37. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  38. package/dist/apps/data/routes/activities.js +230 -88
  39. package/dist/apps/data/routes/activities.js.map +1 -1
  40. package/dist/apps/data/routes/addresses.d.ts +963 -342
  41. package/dist/apps/data/routes/addresses.d.ts.map +1 -1
  42. package/dist/apps/data/routes/addresses.js +11 -9
  43. package/dist/apps/data/routes/addresses.js.map +1 -1
  44. package/dist/apps/data/routes/balances.d.ts +108 -57
  45. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  46. package/dist/apps/data/routes/balances.js +88 -45
  47. package/dist/apps/data/routes/balances.js.map +1 -1
  48. package/dist/apps/data/routes/coingecko.d.ts +169 -169
  49. package/dist/apps/data/routes/exchanges.d.ts +952 -201
  50. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  51. package/dist/apps/data/routes/exchanges.js +518 -76
  52. package/dist/apps/data/routes/exchanges.js.map +1 -1
  53. package/dist/apps/data/routes/fee-amm.d.ts +24 -24
  54. package/dist/apps/data/routes/funding.d.ts +8 -2
  55. package/dist/apps/data/routes/funding.d.ts.map +1 -1
  56. package/dist/apps/data/routes/funding.js +32 -6
  57. package/dist/apps/data/routes/funding.js.map +1 -1
  58. package/dist/apps/data/routes/indexer.d.ts +5 -5
  59. package/dist/apps/data/routes/receipts.d.ts +392 -315
  60. package/dist/apps/data/routes/receipts.d.ts.map +1 -1
  61. package/dist/apps/data/routes/receipts.js +152 -77
  62. package/dist/apps/data/routes/receipts.js.map +1 -1
  63. package/dist/apps/data/routes/tokens.d.ts +166 -173
  64. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  65. package/dist/apps/data/routes/tokens.js +14 -12
  66. package/dist/apps/data/routes/tokens.js.map +1 -1
  67. package/dist/apps/data/routes/transactions.d.ts +277 -536
  68. package/dist/apps/data/routes/transactions.d.ts.map +1 -1
  69. package/dist/apps/data/routes/transactions.js +96 -77
  70. package/dist/apps/data/routes/transactions.js.map +1 -1
  71. package/dist/apps/data/routes/transfers.d.ts +82 -17
  72. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  73. package/dist/apps/data/routes/transfers.js +70 -13
  74. package/dist/apps/data/routes/transfers.js.map +1 -1
  75. package/dist/apps/data/routes/valuation.d.ts +307 -0
  76. package/dist/apps/data/routes/valuation.d.ts.map +1 -0
  77. package/dist/apps/data/routes/valuation.js +292 -0
  78. package/dist/apps/data/routes/valuation.js.map +1 -0
  79. package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -1
  80. package/dist/apps/data/routes/verified-tokens.js +7 -5
  81. package/dist/apps/data/routes/verified-tokens.js.map +1 -1
  82. package/dist/apps/data/routes/webhooks.d.ts +459 -479
  83. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  84. package/dist/apps/data/routes/webhooks.js +124 -25
  85. package/dist/apps/data/routes/webhooks.js.map +1 -1
  86. package/dist/apps/index.d.ts +1 -1
  87. package/dist/apps/index.d.ts.map +1 -1
  88. package/dist/apps/index.js +1 -1
  89. package/dist/apps/index.js.map +1 -1
  90. package/dist/apps/management/App.d.ts +8606 -1681
  91. package/dist/apps/management/App.d.ts.map +1 -1
  92. package/dist/apps/management/App.js +12 -1
  93. package/dist/apps/management/App.js.map +1 -1
  94. package/dist/apps/management/routes/api-keys.d.ts +23 -0
  95. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  96. package/dist/apps/management/routes/api-keys.js +5 -3
  97. package/dist/apps/management/routes/api-keys.js.map +1 -1
  98. package/dist/apps/management/routes/billing.d.ts +88 -48
  99. package/dist/apps/management/routes/billing.d.ts.map +1 -1
  100. package/dist/apps/management/routes/billing.js +10 -2
  101. package/dist/apps/management/routes/billing.js.map +1 -1
  102. package/dist/apps/management/routes/faucet.d.ts +218 -0
  103. package/dist/apps/management/routes/faucet.d.ts.map +1 -0
  104. package/dist/apps/management/routes/faucet.js +139 -0
  105. package/dist/apps/management/routes/faucet.js.map +1 -0
  106. package/dist/apps/management/routes/invitations.d.ts +121 -121
  107. package/dist/apps/management/routes/invitations.d.ts.map +1 -1
  108. package/dist/apps/management/routes/invitations.js +3 -0
  109. package/dist/apps/management/routes/invitations.js.map +1 -1
  110. package/dist/apps/management/routes/invite-links.d.ts +197 -168
  111. package/dist/apps/management/routes/invite-links.d.ts.map +1 -1
  112. package/dist/apps/management/routes/invite-links.js +26 -1
  113. package/dist/apps/management/routes/invite-links.js.map +1 -1
  114. package/dist/apps/management/routes/me.d.ts +2 -0
  115. package/dist/apps/management/routes/me.d.ts.map +1 -1
  116. package/dist/apps/management/routes/me.js +4 -0
  117. package/dist/apps/management/routes/me.js.map +1 -1
  118. package/dist/apps/management/routes/members.d.ts +49 -49
  119. package/dist/apps/management/routes/orgs.d.ts +91 -91
  120. package/dist/apps/management/routes/projects.d.ts +115 -115
  121. package/dist/apps/management/routes/usage.d.ts +35 -32
  122. package/dist/apps/management/routes/usage.d.ts.map +1 -1
  123. package/dist/apps/management/routes/usage.js +4 -0
  124. package/dist/apps/management/routes/usage.js.map +1 -1
  125. package/dist/apps/management/routes/verified-token-requests.d.ts +697 -0
  126. package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -0
  127. package/dist/apps/management/routes/verified-token-requests.js +271 -0
  128. package/dist/apps/management/routes/verified-token-requests.js.map +1 -0
  129. package/dist/apps/management/routes/webhooks.d.ts +6977 -0
  130. package/dist/apps/management/routes/webhooks.d.ts.map +1 -0
  131. package/dist/apps/management/routes/webhooks.js +573 -0
  132. package/dist/apps/management/routes/webhooks.js.map +1 -0
  133. package/dist/apps/mcp.d.ts +9 -2
  134. package/dist/apps/mcp.d.ts.map +1 -1
  135. package/dist/apps/mcp.js +9 -4
  136. package/dist/apps/mcp.js.map +1 -1
  137. package/dist/cli/Cli.d.ts.map +1 -1
  138. package/dist/cli/Cli.js +7 -2
  139. package/dist/cli/Cli.js.map +1 -1
  140. package/dist/db/Db.d.ts +4 -0
  141. package/dist/db/Db.d.ts.map +1 -1
  142. package/dist/db/Db.js +83 -0
  143. package/dist/db/Db.js.map +1 -1
  144. package/dist/db/Schema.d.ts +41 -0
  145. package/dist/db/Schema.d.ts.map +1 -1
  146. package/dist/db/Schema.js +45 -0
  147. package/dist/db/Schema.js.map +1 -1
  148. package/dist/db/tables/earlyAccess.d.ts +50 -0
  149. package/dist/db/tables/earlyAccess.d.ts.map +1 -0
  150. package/dist/db/tables/earlyAccess.js +63 -0
  151. package/dist/db/tables/earlyAccess.js.map +1 -0
  152. package/dist/db/tables/invitations.d.ts +12 -1
  153. package/dist/db/tables/invitations.d.ts.map +1 -1
  154. package/dist/db/tables/invitations.js +23 -0
  155. package/dist/db/tables/invitations.js.map +1 -1
  156. package/dist/db/tables/inviteLinks.d.ts +10 -2
  157. package/dist/db/tables/inviteLinks.d.ts.map +1 -1
  158. package/dist/db/tables/inviteLinks.js +10 -1
  159. package/dist/db/tables/inviteLinks.js.map +1 -1
  160. package/dist/db/tables/sponsoredTransactions.d.ts +2 -0
  161. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  162. package/dist/db/tables/sponsoredTransactions.js +2 -0
  163. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  164. package/dist/db/tables/verifiedTokenRequests.d.ts +94 -0
  165. package/dist/db/tables/verifiedTokenRequests.d.ts.map +1 -0
  166. package/dist/db/tables/verifiedTokenRequests.js +120 -0
  167. package/dist/db/tables/verifiedTokenRequests.js.map +1 -0
  168. package/dist/db/tables/webhookSubscriptions.d.ts +14 -0
  169. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  170. package/dist/db/tables/webhookSubscriptions.js +16 -0
  171. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  172. package/dist/internal/Auth.d.ts +43 -2
  173. package/dist/internal/Auth.d.ts.map +1 -1
  174. package/dist/internal/Auth.js +60 -45
  175. package/dist/internal/Auth.js.map +1 -1
  176. package/dist/internal/FundingQuotes.d.ts +69 -15
  177. package/dist/internal/FundingQuotes.d.ts.map +1 -1
  178. package/dist/internal/FundingQuotes.js +74 -25
  179. package/dist/internal/FundingQuotes.js.map +1 -1
  180. package/dist/internal/Log.d.ts +19 -1
  181. package/dist/internal/Log.d.ts.map +1 -1
  182. package/dist/internal/Log.js +2 -0
  183. package/dist/internal/Log.js.map +1 -1
  184. package/dist/internal/MetricSink.d.ts.map +1 -1
  185. package/dist/internal/MetricSink.js +9 -0
  186. package/dist/internal/MetricSink.js.map +1 -1
  187. package/dist/internal/OpenApi.d.ts +2 -2
  188. package/dist/internal/OpenApi.d.ts.map +1 -1
  189. package/dist/internal/OpenApi.js +3 -0
  190. package/dist/internal/OpenApi.js.map +1 -1
  191. package/dist/internal/Schema.d.ts +14 -5
  192. package/dist/internal/Schema.d.ts.map +1 -1
  193. package/dist/internal/Schema.js +23 -5
  194. package/dist/internal/Schema.js.map +1 -1
  195. package/dist/internal/Value.d.ts +18 -0
  196. package/dist/internal/Value.d.ts.map +1 -1
  197. package/dist/internal/Value.js +11 -1
  198. package/dist/internal/Value.js.map +1 -1
  199. package/dist/internal/VerifiedTokens.d.ts.map +1 -1
  200. package/dist/internal/VerifiedTokens.js +10 -1
  201. package/dist/internal/VerifiedTokens.js.map +1 -1
  202. package/dist/internal/Viem.d.ts +1 -1
  203. package/dist/internal/Webhooks.d.ts +24 -3
  204. package/dist/internal/Webhooks.d.ts.map +1 -1
  205. package/dist/internal/Webhooks.js +34 -8
  206. package/dist/internal/Webhooks.js.map +1 -1
  207. package/package.json +2 -2
  208. package/src/App.ts +9 -1
  209. package/src/Client.test-d.ts +638 -6
  210. package/src/admin/App.ts +4 -0
  211. package/src/admin/apps/early-access.test.ts +125 -0
  212. package/src/admin/apps/early-access.ts +175 -0
  213. package/src/admin/apps/verified-tokens.test.ts +201 -0
  214. package/src/admin/apps/verified-tokens.ts +532 -286
  215. package/src/admin/ui/plugin.ts +2 -1
  216. package/src/admin/ui/src/lib/early-access.ts +63 -0
  217. package/src/admin/ui/src/lib/verified-tokens.ts +79 -0
  218. package/src/admin/ui/src/routeTree.gen.ts +33 -2
  219. package/src/admin/ui/src/routes/__root.tsx +2 -1
  220. package/src/admin/ui/src/routes/early-access.tsx +157 -0
  221. package/src/admin/ui/src/routes/verified-tokens.tsx +230 -19
  222. package/src/admin/ui.generated.ts +48 -33
  223. package/src/apps/data/App.ts +22 -14
  224. package/src/apps/data/FundingQuoteProviders.test.ts +298 -0
  225. package/src/apps/data/FundingQuoteProviders.ts +257 -4
  226. package/src/apps/data/FxOracle.test.ts +118 -0
  227. package/src/apps/data/FxOracle.ts +153 -0
  228. package/src/apps/data/index.ts +1 -0
  229. package/src/apps/data/routes/activities.test.ts +173 -3
  230. package/src/apps/data/routes/activities.ts +403 -132
  231. package/src/apps/data/routes/addresses.ts +19 -9
  232. package/src/apps/data/routes/balances.test.ts +144 -11
  233. package/src/apps/data/routes/balances.ts +140 -51
  234. package/src/apps/data/routes/exchanges.test.ts +540 -9
  235. package/src/apps/data/routes/exchanges.ts +690 -103
  236. package/src/apps/data/routes/funding.test.ts +84 -0
  237. package/src/apps/data/routes/funding.ts +38 -6
  238. package/src/apps/data/routes/receipts.test.ts +125 -23
  239. package/src/apps/data/routes/receipts.ts +205 -100
  240. package/src/apps/data/routes/tokens.test.ts +43 -0
  241. package/src/apps/data/routes/tokens.ts +27 -12
  242. package/src/apps/data/routes/transactions.test.ts +72 -19
  243. package/src/apps/data/routes/transactions.ts +162 -92
  244. package/src/apps/data/routes/transfers.test.ts +146 -4
  245. package/src/apps/data/routes/transfers.ts +91 -13
  246. package/src/apps/data/routes/valuation.test.ts +422 -0
  247. package/src/apps/data/routes/valuation.ts +423 -0
  248. package/src/apps/data/routes/verified-tokens.ts +14 -2
  249. package/src/apps/data/routes/webhooks.test.ts +56 -8
  250. package/src/apps/data/routes/webhooks.ts +144 -27
  251. package/src/apps/index.ts +1 -1
  252. package/src/apps/management/App.ts +13 -0
  253. package/src/apps/management/routes/api-keys.test.ts +87 -0
  254. package/src/apps/management/routes/api-keys.ts +4 -3
  255. package/src/apps/management/routes/billing.test.ts +112 -1
  256. package/src/apps/management/routes/billing.ts +7 -2
  257. package/src/apps/management/routes/faucet.test.ts +79 -0
  258. package/src/apps/management/routes/faucet.ts +185 -0
  259. package/src/apps/management/routes/invitations.test.ts +172 -0
  260. package/src/apps/management/routes/invitations.ts +3 -0
  261. package/src/apps/management/routes/invite-links.test.ts +117 -0
  262. package/src/apps/management/routes/invite-links.ts +43 -1
  263. package/src/apps/management/routes/me.test.ts +140 -88
  264. package/src/apps/management/routes/me.ts +6 -0
  265. package/src/apps/management/routes/usage.test.ts +6 -0
  266. package/src/apps/management/routes/usage.ts +7 -0
  267. package/src/apps/management/routes/verified-token-requests.test.ts +377 -0
  268. package/src/apps/management/routes/verified-token-requests.ts +397 -0
  269. package/src/apps/management/routes/webhooks.test.ts +426 -0
  270. package/src/apps/management/routes/webhooks.ts +672 -0
  271. package/src/apps/mcp.test.ts +13 -0
  272. package/src/apps/mcp.ts +18 -5
  273. package/src/cli/Cli.test.ts +36 -0
  274. package/src/cli/Cli.ts +7 -2
  275. package/src/db/Db.test.ts +19 -0
  276. package/src/db/Db.ts +95 -0
  277. package/src/db/Schema.ts +53 -0
  278. package/src/db/tables/earlyAccess.ts +86 -0
  279. package/src/db/tables/invitations.ts +26 -1
  280. package/src/db/tables/inviteLinks.ts +22 -3
  281. package/src/db/tables/sponsoredTransactions.ts +4 -0
  282. package/src/db/tables/verifiedTokenRequests.test.ts +200 -0
  283. package/src/db/tables/verifiedTokenRequests.ts +182 -0
  284. package/src/db/tables/webhookSubscriptions.ts +31 -0
  285. package/src/internal/Auth.ts +73 -45
  286. package/src/internal/FundingQuotes.test.ts +143 -1
  287. package/src/internal/FundingQuotes.ts +95 -28
  288. package/src/internal/Log.test-d.ts +21 -0
  289. package/src/internal/Log.test.ts +50 -0
  290. package/src/internal/Log.ts +24 -1
  291. package/src/internal/MetricSink.test.ts +160 -0
  292. package/src/internal/MetricSink.ts +10 -0
  293. package/src/internal/OpenApi.ts +3 -0
  294. package/src/internal/Schema.ts +50 -5
  295. package/src/internal/Value.test.ts +20 -0
  296. package/src/internal/Value.ts +24 -1
  297. package/src/internal/VerifiedTokens.ts +9 -1
  298. package/src/internal/Webhooks.test.ts +140 -0
  299. package/src/internal/Webhooks.ts +53 -9
@@ -10,8 +10,34 @@ import * as Response from '../../../internal/Response.js'
10
10
  import * as Schema from '../../../internal/Schema.js'
11
11
  import type { Environment } from '../App.js'
12
12
 
13
+ // A bare domain (`example.org`), normalized before validation.
14
+ const domainPattern = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$/
15
+
13
16
  /** OpenAPI schemas owned by reusable invite links. */
14
17
  export namespace schema {
18
+ const allowedEmailDomains = z
19
+ .nullable(
20
+ z.readonly(
21
+ z
22
+ .array(
23
+ z
24
+ .string()
25
+ .check(
26
+ z.trim(),
27
+ z.toLowerCase(),
28
+ z.regex(domainPattern, 'Must be a bare domain (example.org).'),
29
+ ),
30
+ )
31
+ .check(
32
+ z.minLength(1),
33
+ z.overwrite((domains) => [...new Set(domains)]),
34
+ ),
35
+ ),
36
+ )
37
+ .check(
38
+ z.describe('Email domains allowed to redeem, or null for unrestricted.'),
39
+ z.meta({ examples: [['example.org'], null] }),
40
+ )
15
41
  const nullableExpiry = z
16
42
  .nullable(z.iso.datetime({ offset: true }))
17
43
  .check(
@@ -31,6 +57,7 @@ export namespace schema {
31
57
  /** Management representation, including the recopyable bearer id. */
32
58
  export const InviteLink = Schema.describe(
33
59
  z.object({
60
+ allowedEmailDomains,
34
61
  createdAt: z
35
62
  .string()
36
63
  .check(
@@ -146,6 +173,7 @@ export namespace schema {
146
173
  /** Create body. */
147
174
  export const CreateBody = z
148
175
  .object({
176
+ allowedEmailDomains: z.optional(allowedEmailDomains),
149
177
  expiresAt: z.optional(nullableExpiry),
150
178
  maxUses: z.optional(nullableUses),
151
179
  name: z.optional(
@@ -318,6 +346,7 @@ export function inviteLinks() {
318
346
  const body = c.req.valid('json')
319
347
  try {
320
348
  const record = await InviteLinks.create(Db.get(c.get('db')), {
349
+ allowedEmailDomains: body.allowedEmailDomains ?? null,
321
350
  createdBy: Auth.membership(c)?.userId ?? 'super_admin',
322
351
  expiresAt:
323
352
  body.expiresAt === undefined
@@ -498,7 +527,13 @@ export function inviteLinks() {
498
527
  OpenApi.describeRoute({
499
528
  operationId: 'acceptInviteLink',
500
529
  responses: OpenApi.responses({
501
- errors: { 403: 'A verified session email is required.', ...errors },
530
+ errors: {
531
+ 403: {
532
+ codes: ['forbidden', 'invite_link_email_forbidden'],
533
+ description: 'A verified session email from an allowed domain is required.',
534
+ },
535
+ ...errors,
536
+ },
502
537
  success: { description: 'Membership.', schema: schema.AcceptResponse },
503
538
  }),
504
539
  summary: 'Accept invite link',
@@ -531,6 +566,12 @@ export function inviteLinks() {
531
566
  200,
532
567
  )
533
568
  } catch (cause) {
569
+ if (cause instanceof InviteLinks.EmailDomainForbiddenError)
570
+ return Response.error(c, {
571
+ code: 'invite_link_email_forbidden',
572
+ message: 'Sign in with an email address on an allowed domain',
573
+ status: 403,
574
+ })
534
575
  return Response.upstream(c, cause)
535
576
  }
536
577
  },
@@ -539,6 +580,7 @@ export function inviteLinks() {
539
580
 
540
581
  function serialize(record: InviteLinks.Record) {
541
582
  return {
583
+ allowedEmailDomains: record.allowedEmailDomains,
542
584
  createdAt: record.createdAt,
543
585
  createdBy: record.createdBy,
544
586
  enabled: record.enabled,
@@ -4,6 +4,8 @@ import { exportJWK, generateKeyPair } from 'jose'
4
4
  import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
5
5
 
6
6
  import * as TestApp from '../../../../test/App.js'
7
+ import * as EarlyAccess from '../../../db/tables/earlyAccess.js'
8
+ import * as Users from '../../../db/tables/users.js'
7
9
  import * as Me from './me.js'
8
10
 
9
11
  /** Origin pinned for SIWE domain binding; Hono test requests use this host. */
@@ -18,6 +20,40 @@ function createApp() {
18
20
  })
19
21
  }
20
22
 
23
+ /** App with the early-access gate enabled; allowlist entries seed through `EarlyAccess.add`. */
24
+ function createGatedApp(db = TestApp.database()) {
25
+ return TestApp.create({
26
+ auth: { keys: [TestApp.key] },
27
+ db,
28
+ session: { wallet: { origin } },
29
+ tmp_earlyAccess: true,
30
+ })
31
+ }
32
+
33
+ /** RequestInit for a JSON mutation carrying the session cookie. */
34
+ function json(method: 'POST', cookie: string, body: unknown) {
35
+ return {
36
+ body: JSON.stringify(body),
37
+ headers: { 'content-type': 'application/json', cookie },
38
+ method,
39
+ }
40
+ }
41
+
42
+ /** Signs in a fresh scripted account; returns its session cookie and user id. */
43
+ async function session(app: TestApp.signIn.App) {
44
+ const account = privateKeyToAccount(generatePrivateKey())
45
+ const { cookie } = await TestApp.signIn(app, account)
46
+ const me = await app.request('/v1/me', { headers: { cookie: cookie! } })
47
+ const { id } = (await me.json()) as { id: string }
48
+ return { cookie: cookie!, userId: id }
49
+ }
50
+
51
+ /** Reads the session's `tmp_earlyAccess` flag from `/v1/me`. TODO: remove after GA. */
52
+ async function tmp_earlyAccess(app: TestApp.signIn.App, cookie: string) {
53
+ const response = await app.request('/v1/me', { headers: { cookie } })
54
+ return (await TestApp.json(response, Me.schema.User)).tmp_earlyAccess
55
+ }
56
+
21
57
  describe('GET /me', () => {
22
58
  test('returns the signed-in user', async () => {
23
59
  const app = createApp()
@@ -182,13 +218,14 @@ describe('sign-in email requirement', () => {
182
218
  })
183
219
  })
184
220
 
185
- describe('sign-up email domains', () => {
186
- test('allows new users on an allowlisted domain', async () => {
187
- const oidc = await createIssuer({ email: 'dev@TEMPO.xyz' })
221
+ describe('open sign-up', () => {
222
+ test('allows new users on any domain with the gate on', async () => {
223
+ const oidc = await createIssuer({ email: 'dev@example.com' })
188
224
  try {
189
225
  const app = TestApp.create({
190
226
  auth: { keys: [TestApp.key] },
191
- session: { emailAllowlist: ['Tempo.XYZ'], wallet: { issuer: oidc.url, origin } },
227
+ session: { wallet: { issuer: oidc.url, origin } },
228
+ tmp_earlyAccess: true,
192
229
  })
193
230
  const account = privateKeyToAccount(generatePrivateKey())
194
231
  const { cookie, verifyResponse } = await TestApp.signIn(app, account, {
@@ -199,109 +236,124 @@ describe('sign-up email domains', () => {
199
236
  const response = await app.request('/v1/me', { headers: { cookie: cookie! } })
200
237
  expect(response.status).toBe(200)
201
238
  const body = await TestApp.json(response, Me.schema.User)
202
- expect(body.email).toBe('dev@TEMPO.xyz')
239
+ expect(body.email).toBe('dev@example.com')
203
240
  } finally {
204
241
  await oidc.close()
205
242
  }
206
243
  })
207
244
 
208
- test('rejects new users on other domains', async () => {
209
- const oidc = await createIssuer({ email: 'dev@example.com' })
210
- try {
211
- const app = TestApp.create({
212
- auth: { keys: [TestApp.key] },
213
- session: { emailAllowlist: ['tempo.xyz'], wallet: { issuer: oidc.url, origin } },
214
- })
215
- const account = privateKeyToAccount(generatePrivateKey())
216
- const { cookie, verifyResponse } = await TestApp.signIn(app, account, {
217
- idToken: ({ nonce }) => oidc.mint({ address: account.address, nonce }),
218
- })
245
+ test('allows new users without an identity token with the gate on', async () => {
246
+ const app = createGatedApp()
247
+ const account = privateKeyToAccount(generatePrivateKey())
248
+ const { cookie, verifyResponse } = await TestApp.signIn(app, account)
219
249
 
220
- expect(verifyResponse.status).toBe(403)
221
- expect(cookie).toBeUndefined()
222
- expect(await verifyResponse.json()).toMatchInlineSnapshot(`
223
- {
224
- "error": "sign-ups are restricted to approved email domains or invited emails",
225
- }
226
- `)
227
- } finally {
228
- await oidc.close()
229
- }
250
+ expect(verifyResponse.status).toBe(200)
251
+ expect(cookie).toBeDefined()
230
252
  })
253
+ })
231
254
 
232
- test('rejects new users without an identity token', async () => {
233
- const app = TestApp.create({
234
- auth: { keys: [TestApp.key] },
235
- session: { emailAllowlist: ['tempo.xyz'], wallet: { origin } },
236
- })
237
- const account = privateKeyToAccount(generatePrivateKey())
238
- const { cookie, verifyResponse } = await TestApp.signIn(app, account)
255
+ describe('tmp_earlyAccess', () => {
256
+ test('defaults to true while the gate is dormant', async () => {
257
+ const app = createApp()
258
+ const { cookie } = await session(app)
259
+ expect(await tmp_earlyAccess(app, cookie)).toBe(true)
260
+ })
239
261
 
240
- expect(verifyResponse.status).toBe(403)
241
- expect(cookie).toBeUndefined()
262
+ test('matches domain entries case-insensitively', async () => {
263
+ const db = TestApp.database()
264
+ const app = createGatedApp(db)
265
+ await EarlyAccess.add(db, { createdBy: 'test', entry: 'Example.ORG' })
266
+ const user = await session(app)
267
+ await Users.setEmail(db, user.userId, 'Dev@EXAMPLE.org')
268
+
269
+ expect(await tmp_earlyAccess(app, user.cookie)).toBe(true)
242
270
  })
243
271
 
244
- test('allows invited emails regardless of domain', async () => {
245
- const oidc = await createIssuer({ email: 'Invited@Example.com' })
246
- try {
247
- const db = TestApp.database()
272
+ test('exact-email entries grant only that address', async () => {
273
+ const db = TestApp.database()
274
+ const app = createGatedApp(db)
275
+ await EarlyAccess.add(db, { createdBy: 'test', entry: 'dev@example.org' })
276
+ const granted = await session(app)
277
+ await Users.setEmail(db, granted.userId, 'dev@example.org')
278
+ const denied = await session(app)
279
+ await Users.setEmail(db, denied.userId, 'other@example.org')
280
+
281
+ expect(await tmp_earlyAccess(app, granted.cookie)).toBe(true)
282
+ expect(await tmp_earlyAccess(app, denied.cookie)).toBe(false)
283
+ })
248
284
 
249
- // Owner signs up while sign-up is open and issues the invitation.
250
- const open = TestApp.create({
251
- auth: { keys: [TestApp.key] },
252
- db,
253
- session: { wallet: { origin } },
254
- })
255
- const owner = privateKeyToAccount(generatePrivateKey())
256
- const { cookie } = await TestApp.signIn(open, owner)
257
- const created = await open.request('/v1/orgs', {
258
- body: JSON.stringify({ name: 'Acme' }),
259
- headers: { 'content-type': 'application/json', cookie: cookie! },
260
- method: 'POST',
261
- })
262
- const org = (await created.json()) as { id: string }
263
- const invited = await open.request(`/v1/orgs/${org.id}/invitations`, {
264
- body: JSON.stringify({ email: 'invited@example.com', role: 'member' }),
265
- headers: { 'content-type': 'application/json', cookie: cookie! },
266
- method: 'POST',
267
- })
268
- expect(invited.status).toBe(200)
285
+ test('stays false without a matching entry', async () => {
286
+ const db = TestApp.database()
287
+ const app = createGatedApp(db)
288
+ const user = await session(app)
289
+ await Users.setEmail(db, user.userId, 'dev@example.net')
269
290
 
270
- const restricted = TestApp.create({
271
- auth: { keys: [TestApp.key] },
272
- db,
273
- session: { emailAllowlist: ['tempo.xyz'], wallet: { issuer: oidc.url, origin } },
274
- })
275
- const account = privateKeyToAccount(generatePrivateKey())
276
- const { verifyResponse } = await TestApp.signIn(restricted, account, {
277
- idToken: ({ nonce }) => oidc.mint({ address: account.address, nonce }),
278
- })
279
- expect(verifyResponse.status).toBe(200)
280
- } finally {
281
- await oidc.close()
282
- }
291
+ expect(await tmp_earlyAccess(app, user.cookie)).toBe(false)
292
+ })
293
+
294
+ test('stays false without a verified email', async () => {
295
+ const app = createGatedApp()
296
+ const { cookie } = await session(app)
297
+
298
+ expect(await tmp_earlyAccess(app, cookie)).toBe(false)
283
299
  })
284
300
 
285
- test('allows existing users regardless of domain', async () => {
301
+ test('flips false once the entry is removed', async () => {
286
302
  const db = TestApp.database()
287
- const account = privateKeyToAccount(generatePrivateKey())
303
+ const app = createGatedApp(db)
304
+ await EarlyAccess.add(db, { createdBy: 'test', entry: 'example.org' })
305
+ const user = await session(app)
306
+ await Users.setEmail(db, user.userId, 'dev@example.org')
307
+ expect(await tmp_earlyAccess(app, user.cookie)).toBe(true)
308
+
309
+ await EarlyAccess.remove(db, 'example.org')
310
+ expect(await tmp_earlyAccess(app, user.cookie)).toBe(false)
311
+ })
288
312
 
289
- // Row created before the restriction (open sign-up deployment).
290
- const open = TestApp.create({
291
- auth: { keys: [TestApp.key] },
292
- db,
293
- session: { wallet: { origin } },
294
- })
295
- const first = await TestApp.signIn(open, account)
296
- expect(first.verifyResponse.status).toBe(200)
313
+ test('invitations from early-access inviters grant access until revoked', async () => {
314
+ const db = TestApp.database()
315
+ const app = createGatedApp(db)
316
+ await EarlyAccess.add(db, { createdBy: 'test', entry: 'example.org' })
317
+ const inviter = await session(app)
318
+ await Users.setEmail(db, inviter.userId, 'owner@example.org')
319
+ const created = await app.request('/v1/orgs', json('POST', inviter.cookie, { name: 'Acme' }))
320
+ const org = (await created.json()) as { id: string }
321
+ const invited = await app.request(
322
+ `/v1/orgs/${org.id}/invitations`,
323
+ json('POST', inviter.cookie, { email: 'invitee@example.net', role: 'member' }),
324
+ )
325
+ expect(invited.status).toBe(200)
326
+ const invitation = (await invited.json()) as { id: string }
297
327
 
298
- const restricted = TestApp.create({
299
- auth: { keys: [TestApp.key] },
300
- db,
301
- session: { emailAllowlist: ['tempo.xyz'], wallet: { origin } },
328
+ const invitee = await session(app)
329
+ await Users.setEmail(db, invitee.userId, 'invitee@example.net')
330
+ expect(await tmp_earlyAccess(app, invitee.cookie)).toBe(true)
331
+
332
+ // Revoking the invitation withdraws the grant.
333
+ const revoked = await app.request(`/v1/orgs/${org.id}/invitations/${invitation.id}`, {
334
+ headers: { cookie: inviter.cookie },
335
+ method: 'DELETE',
302
336
  })
303
- const second = await TestApp.signIn(restricted, account)
304
- expect(second.verifyResponse.status).toBe(200)
337
+ expect(revoked.status).toBe(200)
338
+ expect(await tmp_earlyAccess(app, invitee.cookie)).toBe(false)
339
+ })
340
+
341
+ test('invitations from non-early-access inviters do not grant access', async () => {
342
+ const db = TestApp.database()
343
+ const app = createGatedApp(db)
344
+ const inviter = await session(app)
345
+ await Users.setEmail(db, inviter.userId, 'owner@example.net')
346
+ const created = await app.request('/v1/orgs', json('POST', inviter.cookie, { name: 'Acme' }))
347
+ const org = (await created.json()) as { id: string }
348
+ const invited = await app.request(
349
+ `/v1/orgs/${org.id}/invitations`,
350
+ json('POST', inviter.cookie, { email: 'friend@example.net', role: 'member' }),
351
+ )
352
+ expect(invited.status).toBe(200)
353
+
354
+ const invitee = await session(app)
355
+ await Users.setEmail(db, invitee.userId, 'friend@example.net')
356
+ expect(await tmp_earlyAccess(app, invitee.cookie)).toBe(false)
305
357
  })
306
358
  })
307
359
 
@@ -35,6 +35,11 @@ export namespace schema {
35
35
  z.describe('Opaque user id (`usr_…`).'),
36
36
  z.meta({ examples: ['usr_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
37
37
  ),
38
+ // TODO: remove after GA.
39
+ tmp_earlyAccess: z.boolean().check(
40
+ z.describe('Whether the user has early access; gated features require it while the platform is invite-only.'), // prettier-ignore
41
+ z.meta({ examples: [true] }),
42
+ ),
38
43
  updatedAt: z
39
44
  .string()
40
45
  .check(
@@ -94,6 +99,7 @@ export function me() {
94
99
  createdAt: user.createdAt,
95
100
  ...(user.email ? { email: user.email } : {}),
96
101
  id: user.id,
102
+ tmp_earlyAccess: await Auth.tmp_earlyAccess(c),
97
103
  updatedAt: user.updatedAt,
98
104
  }),
99
105
  200,
@@ -247,6 +247,7 @@ describe('GET /orgs/:orgId/usage/sponsorships', () => {
247
247
  "data": [
248
248
  {
249
249
  "count": 2,
250
+ "failed": 0,
250
251
  "feeTotal": {
251
252
  "amount": "0.35",
252
253
  "currency": "usd",
@@ -255,6 +256,7 @@ describe('GET /orgs/:orgId/usage/sponsorships', () => {
255
256
  },
256
257
  {
257
258
  "count": 1,
259
+ "failed": 1,
258
260
  "feeTotal": {
259
261
  "amount": "0",
260
262
  "currency": "usd",
@@ -275,6 +277,7 @@ describe('GET /orgs/:orgId/usage/sponsorships', () => {
275
277
  "data": [
276
278
  {
277
279
  "count": 3,
280
+ "failed": 1,
278
281
  "feeTotal": {
279
282
  "amount": "0.35",
280
283
  "currency": "usd",
@@ -302,6 +305,7 @@ describe('GET /orgs/:orgId/usage/sponsorships', () => {
302
305
  [
303
306
  {
304
307
  "count": 1,
308
+ "failed": 0,
305
309
  "feeTotal": {
306
310
  "amount": "0.5",
307
311
  "currency": "usd",
@@ -333,6 +337,7 @@ describe('GET /orgs/:orgId/usage/sponsorships', () => {
333
337
  "data": [
334
338
  {
335
339
  "count": 1,
340
+ "failed": 0,
336
341
  "feeTotal": {
337
342
  "amount": "0.1",
338
343
  "currency": "usd",
@@ -352,6 +357,7 @@ describe('GET /orgs/:orgId/usage/sponsorships', () => {
352
357
  "data": [
353
358
  {
354
359
  "count": 1,
360
+ "failed": 0,
355
361
  "feeTotal": {
356
362
  "amount": "0.3",
357
363
  "currency": "usd",
@@ -199,6 +199,12 @@ export namespace schema {
199
199
  z.describe('Sponsored transactions recorded in the bucket, any status.'),
200
200
  z.meta({ examples: [42] }),
201
201
  ),
202
+ failed: z
203
+ .number()
204
+ .check(
205
+ z.describe('Sponsored transactions in the bucket whose status is `failed`.'),
206
+ z.meta({ examples: [3] }),
207
+ ),
202
208
  feeTotal: z
203
209
  .object({
204
210
  amount: z.string().check(
@@ -462,6 +468,7 @@ export function usage() {
462
468
  Response.validated(schema.getSponsorshipUsage.Response, {
463
469
  data: buckets.map((bucket) => ({
464
470
  count: bucket.count,
471
+ failed: bucket.failed,
465
472
  feeTotal: { amount: core_Billing.fromBaseUnits(bucket.feeTotal), currency },
466
473
  timestamp: bucket.timestamp,
467
474
  })),