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
@@ -0,0 +1,377 @@
1
+ import { testClient } from 'hono/testing'
2
+ import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
3
+ import type * as z from 'zod/mini'
4
+
5
+ import * as TestApp from '../../../../test/App.js'
6
+ import type * as Db from '../../../db/Db.js'
7
+ import * as Memberships from '../../../db/tables/memberships.js'
8
+ import * as VerifiedTokenRequests from '../../../db/tables/verifiedTokenRequests.js'
9
+ import * as VerifiedTokens from '../../../internal/VerifiedTokens.js'
10
+ import type { schema } from './verified-token-requests.js'
11
+
12
+ /** Origin pinned for SIWE domain binding; Hono test requests use this host. */
13
+ const origin = 'http://localhost'
14
+
15
+ /** RPC request options carrying a session cookie. */
16
+ function as(cookie: string) {
17
+ return { headers: { cookie } } as const
18
+ }
19
+
20
+ function createApp(db: Db.Source = TestApp.database()) {
21
+ const app = TestApp.create({ db, session: { wallet: { origin } } })
22
+ return { app, client: testClient(app) }
23
+ }
24
+
25
+ /** Signs in a fresh scripted account and resolves its user id. */
26
+ async function session({ app, client }: ReturnType<typeof createApp>) {
27
+ const { cookie } = await TestApp.signIn(app, privateKeyToAccount(generatePrivateKey()))
28
+ if (!cookie) throw new Error('Sign-in did not establish a session')
29
+ const me = await client.v1.me.$get(undefined, as(cookie))
30
+ expect(me.status).toBe(200)
31
+ if (me.status !== 200) throw new Error(`Failed to resolve session user: ${me.status}`)
32
+ const { id: userId } = await me.json()
33
+ return { cookie, userId }
34
+ }
35
+
36
+ /** Creates an owner and organization through their public write boundaries. */
37
+ async function setup(context: ReturnType<typeof createApp>) {
38
+ const owner = await session(context)
39
+ const response = await context.client.v1.orgs.$post({ json: { name: 'Acme' } }, as(owner.cookie))
40
+ expect(response.status).toBe(200)
41
+ if (response.status !== 200) throw new Error(`Failed to create organization: ${response.status}`)
42
+ const org = await response.json()
43
+ return { org, owner }
44
+ }
45
+
46
+ const token = {
47
+ address: '0x20c00000000000000000000000000000000000aa',
48
+ chainId: 42431,
49
+ currency: 'USD',
50
+ decimals: 6,
51
+ name: 'Test USD',
52
+ symbol: 'tUSD',
53
+ } satisfies Partial<z.input<typeof schema.CreateBody>>
54
+
55
+ describe('POST /v1/orgs/:orgId/verified-token-requests', () => {
56
+ test('members submit requests', async () => {
57
+ const db = TestApp.database()
58
+ const context = createApp(db)
59
+ const requests = context.client.v1.orgs[':orgId{org_[A-Za-z0-9_-]+}']['verified-token-requests']
60
+ const { org } = await setup(context)
61
+ const member = await session(context)
62
+ await Memberships.create(db, { orgId: org.id, role: 'member', userId: member.userId })
63
+
64
+ // Checksummed input normalizes to the stored lowercase address.
65
+ const created = await requests.$post(
66
+ {
67
+ json: { ...token, address: '0x20C00000000000000000000000000000000000AA', note: 'We are the issuer.' }, // prettier-ignore
68
+ param: { orgId: org.id },
69
+ },
70
+ as(member.cookie),
71
+ )
72
+ expect(created.status).toBe(200)
73
+ if (created.status !== 200) throw new Error(`Failed to create request: ${created.status}`)
74
+ const request = await created.json()
75
+ expect(request.id).toMatch(/^vtr_/)
76
+ expect(request).toMatchObject({
77
+ address: token.address,
78
+ note: 'We are the issuer.',
79
+ orgId: org.id,
80
+ requestedBy: member.userId,
81
+ reviewNote: null,
82
+ reviewedAt: null,
83
+ status: 'pending',
84
+ })
85
+ })
86
+
87
+ test('rejects tokens already on the verified list', async () => {
88
+ const db = TestApp.database()
89
+ const context = createApp(db)
90
+ const requests = context.client.v1.orgs[':orgId{org_[A-Za-z0-9_-]+}']['verified-token-requests']
91
+ const { org, owner } = await setup(context)
92
+ await VerifiedTokens.create(db, { ...token, name: 'Listed USD' })
93
+
94
+ const byAddress = await requests.$post(
95
+ { json: token, param: { orgId: org.id } },
96
+ as(owner.cookie),
97
+ )
98
+ expect(byAddress.status).toBe(409)
99
+ const addressBody = (await byAddress.json()) as { error?: { code?: string } }
100
+ expect(addressBody.error?.code).toBe('verified_token_exists')
101
+
102
+ // Same symbol (case-insensitive), different address.
103
+ const bySymbol = await requests.$post(
104
+ {
105
+ json: { ...token, address: '0x20c00000000000000000000000000000000000bb', symbol: 'TUSD' },
106
+ param: { orgId: org.id },
107
+ },
108
+ as(owner.cookie),
109
+ )
110
+ expect(bySymbol.status).toBe(409)
111
+ const symbolBody = (await bySymbol.json()) as { error?: { code?: string } }
112
+ expect(symbolBody.error?.code).toBe('verified_symbol_exists')
113
+ })
114
+
115
+ test('rejects a token with a pending request from any org', async () => {
116
+ const context = createApp()
117
+ const requests = context.client.v1.orgs[':orgId{org_[A-Za-z0-9_-]+}']['verified-token-requests']
118
+ const first = await setup(context)
119
+ const second = await setup(context)
120
+
121
+ const created = await requests.$post(
122
+ { json: token, param: { orgId: first.org.id } },
123
+ as(first.owner.cookie),
124
+ )
125
+ expect(created.status).toBe(200)
126
+
127
+ const duplicate = await requests.$post(
128
+ { json: token, param: { orgId: second.org.id } },
129
+ as(second.owner.cookie),
130
+ )
131
+ expect(duplicate.status).toBe(409)
132
+ const body = (await duplicate.json()) as { error?: { code?: string } }
133
+ expect(body.error?.code).toBe('request_pending_exists')
134
+ })
135
+
136
+ test('caps pending requests per organization', async () => {
137
+ const context = createApp()
138
+ const requests = context.client.v1.orgs[':orgId{org_[A-Za-z0-9_-]+}']['verified-token-requests']
139
+ const { org, owner } = await setup(context)
140
+
141
+ for (let index = 0; index < 10; index++) {
142
+ const created = await requests.$post(
143
+ {
144
+ json: {
145
+ ...token,
146
+ address: `0x20c${(index + 1).toString(16).padStart(37, '0')}` as `0x${string}`,
147
+ symbol: `tUSD${index}`,
148
+ },
149
+ param: { orgId: org.id },
150
+ },
151
+ as(owner.cookie),
152
+ )
153
+ expect(created.status).toBe(200)
154
+ }
155
+
156
+ const overflow = await requests.$post(
157
+ {
158
+ json: { ...token, address: `0x20c${'f'.padStart(37, '0')}` as `0x${string}`, symbol: 'tOVER' }, // prettier-ignore
159
+ param: { orgId: org.id },
160
+ },
161
+ as(owner.cookie),
162
+ )
163
+ expect(overflow.status).toBe(409)
164
+ const body = (await overflow.json()) as { error?: { code?: string } }
165
+ expect(body.error?.code).toBe('request_limit_reached')
166
+ })
167
+
168
+ test('accepts an SVG logo and echoes it back', async () => {
169
+ const context = createApp()
170
+ const requests = context.client.v1.orgs[':orgId{org_[A-Za-z0-9_-]+}']['verified-token-requests']
171
+ const { org, owner } = await setup(context)
172
+
173
+ const created = await requests.$post(
174
+ {
175
+ json: { ...token, logo: '<svg xmlns="http://www.w3.org/2000/svg"/>' },
176
+ param: { orgId: org.id },
177
+ },
178
+ as(owner.cookie),
179
+ )
180
+ expect(created.status).toBe(200)
181
+ if (created.status !== 200) throw new Error(`Failed to create request: ${created.status}`)
182
+ // The org sees its own uploaded logo (the console list previews it).
183
+ expect((await created.json()).logo).toBe('<svg xmlns="http://www.w3.org/2000/svg"/>')
184
+
185
+ const invalid = await requests.$post(
186
+ {
187
+ json: { ...token, address: '0x20c00000000000000000000000000000000000bb', logo: 'not svg', symbol: 'tEUR' }, // prettier-ignore
188
+ param: { orgId: org.id },
189
+ },
190
+ as(owner.cookie),
191
+ )
192
+ expect(invalid.status).toBe(400)
193
+
194
+ const oversized = await requests.$post(
195
+ {
196
+ json: {
197
+ ...token,
198
+ address: '0x20c00000000000000000000000000000000000cc',
199
+ logo: `<svg xmlns="http://www.w3.org/2000/svg">${'a'.repeat(256 * 1024)}</svg>`,
200
+ symbol: 'tGBP',
201
+ },
202
+ param: { orgId: org.id },
203
+ },
204
+ as(owner.cookie),
205
+ )
206
+ expect(oversized.status).toBe(400)
207
+ })
208
+
209
+ test('caps free-text field lengths', async () => {
210
+ const context = createApp()
211
+ const requests = context.client.v1.orgs[':orgId{org_[A-Za-z0-9_-]+}']['verified-token-requests']
212
+ const { org, owner } = await setup(context)
213
+
214
+ const oversized = [
215
+ { name: 'x'.repeat(101) },
216
+ { note: 'x'.repeat(501) },
217
+ { symbol: 'x'.repeat(21) },
218
+ ]
219
+ for (const fields of oversized) {
220
+ const response = await requests.$post(
221
+ { json: { ...token, ...fields }, param: { orgId: org.id } },
222
+ as(owner.cookie),
223
+ )
224
+ expect(response.status).toBe(400)
225
+ const body = (await response.json()) as { error?: { code?: string } }
226
+ expect(body.error?.code).toBe('body_invalid')
227
+ }
228
+ })
229
+ })
230
+
231
+ describe('GET /v1/orgs/:orgId/verified-token-requests', () => {
232
+ test('lists only the organization requests', async () => {
233
+ const context = createApp()
234
+ const requests = context.client.v1.orgs[':orgId{org_[A-Za-z0-9_-]+}']['verified-token-requests']
235
+ const { org, owner } = await setup(context)
236
+ const foreign = await setup(context)
237
+
238
+ const created = await requests.$post(
239
+ { json: token, param: { orgId: org.id } },
240
+ as(owner.cookie),
241
+ )
242
+ expect(created.status).toBe(200)
243
+ if (created.status !== 200) throw new Error(`Failed to create request: ${created.status}`)
244
+ const request = await created.json()
245
+
246
+ const listed = await requests.$get({ param: { orgId: org.id } }, as(owner.cookie))
247
+ expect(listed.status).toBe(200)
248
+ if (listed.status !== 200) throw new Error(`Failed to list requests: ${listed.status}`)
249
+ expect((await listed.json()).data.map(({ id }) => id)).toEqual([request.id])
250
+
251
+ // A non-member resolves neither the org nor its requests.
252
+ const denied = await requests.$get({ param: { orgId: org.id } }, as(foreign.owner.cookie))
253
+ expect(denied.status).toBe(404)
254
+
255
+ const empty = await requests.$get({ param: { orgId: foreign.org.id } }, as(foreign.owner.cookie)) // prettier-ignore
256
+ expect(empty.status).toBe(200)
257
+ if (empty.status !== 200) throw new Error(`Failed to list requests: ${empty.status}`)
258
+ expect((await empty.json()).data).toEqual([])
259
+ })
260
+
261
+ test('review fields surface without reviewer identity', async () => {
262
+ const db = TestApp.database()
263
+ const context = createApp(db)
264
+ const requests = context.client.v1.orgs[':orgId{org_[A-Za-z0-9_-]+}']['verified-token-requests']
265
+ const { org, owner } = await setup(context)
266
+
267
+ const created = await requests.$post(
268
+ { json: token, param: { orgId: org.id } },
269
+ as(owner.cookie),
270
+ )
271
+ expect(created.status).toBe(200)
272
+ if (created.status !== 200) throw new Error(`Failed to create request: ${created.status}`)
273
+ const request = await created.json()
274
+ await VerifiedTokenRequests.review(db, request.id, {
275
+ reviewNote: 'Logo unreachable.',
276
+ reviewedBy: 'admin@tempo.xyz',
277
+ status: 'denied',
278
+ })
279
+
280
+ const listed = await requests.$get({ param: { orgId: org.id } }, as(owner.cookie))
281
+ expect(listed.status).toBe(200)
282
+ if (listed.status !== 200) throw new Error(`Failed to list requests: ${listed.status}`)
283
+ const [row] = (await listed.json()).data
284
+ expect(row).toMatchObject({ reviewNote: 'Logo unreachable.', status: 'denied' })
285
+ expect(row?.reviewedAt).toBeTruthy()
286
+ // Reviewer identity stays on the admin surface.
287
+ expect(row && 'reviewedBy' in row).toBe(false)
288
+ })
289
+
290
+ test('requires a session', async () => {
291
+ const context = createApp()
292
+ const requests = context.client.v1.orgs[':orgId{org_[A-Za-z0-9_-]+}']['verified-token-requests']
293
+ const response = await requests.$get({ param: { orgId: 'org_1a2b3c4d5e6f7g8h9j0k1m2n' } })
294
+ expect(response.status).toBe(401)
295
+ })
296
+ })
297
+
298
+ describe('DELETE /v1/orgs/:orgId/verified-token-requests/:requestId', () => {
299
+ test('members cancel pending requests', async () => {
300
+ const db = TestApp.database()
301
+ const context = createApp(db)
302
+ const requests = context.client.v1.orgs[':orgId{org_[A-Za-z0-9_-]+}']['verified-token-requests']
303
+ const { org, owner } = await setup(context)
304
+ const member = await session(context)
305
+ await Memberships.create(db, { orgId: org.id, role: 'member', userId: member.userId })
306
+
307
+ const created = await requests.$post(
308
+ { json: token, param: { orgId: org.id } },
309
+ as(owner.cookie),
310
+ )
311
+ expect(created.status).toBe(200)
312
+ if (created.status !== 200) throw new Error(`Failed to create request: ${created.status}`)
313
+ const request = await created.json()
314
+
315
+ const canceled = await requests[':requestId{vtr_[A-Za-z0-9_-]+}'].$delete(
316
+ { param: { orgId: org.id, requestId: request.id } },
317
+ as(member.cookie),
318
+ )
319
+ expect(canceled.status).toBe(200)
320
+ if (canceled.status !== 200) throw new Error(`Failed to cancel request: ${canceled.status}`)
321
+ expect(await canceled.json()).toEqual({ id: request.id })
322
+
323
+ const repeat = await requests[':requestId{vtr_[A-Za-z0-9_-]+}'].$delete(
324
+ { param: { orgId: org.id, requestId: request.id } },
325
+ as(member.cookie),
326
+ )
327
+ expect(repeat.status).toBe(404)
328
+ })
329
+
330
+ test('is org-scoped', async () => {
331
+ const context = createApp()
332
+ const requests = context.client.v1.orgs[':orgId{org_[A-Za-z0-9_-]+}']['verified-token-requests']
333
+ const { org, owner } = await setup(context)
334
+ const foreign = await setup(context)
335
+
336
+ const created = await requests.$post(
337
+ { json: token, param: { orgId: org.id } },
338
+ as(owner.cookie),
339
+ )
340
+ expect(created.status).toBe(200)
341
+ if (created.status !== 200) throw new Error(`Failed to create request: ${created.status}`)
342
+ const request = await created.json()
343
+
344
+ const crossCancel = await requests[':requestId{vtr_[A-Za-z0-9_-]+}'].$delete(
345
+ { param: { orgId: foreign.org.id, requestId: request.id } },
346
+ as(foreign.owner.cookie),
347
+ )
348
+ expect(crossCancel.status).toBe(404)
349
+ })
350
+
351
+ test('rejects reviewed requests with 409', async () => {
352
+ const db = TestApp.database()
353
+ const context = createApp(db)
354
+ const requests = context.client.v1.orgs[':orgId{org_[A-Za-z0-9_-]+}']['verified-token-requests']
355
+ const { org, owner } = await setup(context)
356
+
357
+ const created = await requests.$post(
358
+ { json: token, param: { orgId: org.id } },
359
+ as(owner.cookie),
360
+ )
361
+ expect(created.status).toBe(200)
362
+ if (created.status !== 200) throw new Error(`Failed to create request: ${created.status}`)
363
+ const request = await created.json()
364
+ await VerifiedTokenRequests.review(db, request.id, {
365
+ reviewedBy: 'admin@tempo.xyz',
366
+ status: 'denied',
367
+ })
368
+
369
+ const canceled = await requests[':requestId{vtr_[A-Za-z0-9_-]+}'].$delete(
370
+ { param: { orgId: org.id, requestId: request.id } },
371
+ as(owner.cookie),
372
+ )
373
+ expect(canceled.status).toBe(409)
374
+ const body = (await canceled.json()) as { error?: { code?: string } }
375
+ expect(body.error?.code).toBe('request_not_pending')
376
+ })
377
+ })