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
@@ -5,8 +5,11 @@ import * as OpenApi from '../../internal/OpenApi.js'
5
5
  import * as Response from '../../internal/Response.js'
6
6
  import * as Schema from '../../internal/Schema.js'
7
7
  import * as Db from '../../db/Db.js'
8
+ import * as Organizations from '../../db/tables/organizations.js'
9
+ import * as VerifiedTokenRequests from '../../db/tables/verifiedTokenRequests.js'
8
10
  import * as VerifiedTokens from '../../internal/VerifiedTokens.js'
9
11
  import { schema as readSchema } from '../../apps/data/routes/verified-tokens.js'
12
+ import { schema as requestSchema } from '../../apps/management/routes/verified-token-requests.js'
10
13
  import type * as App from '../App.js'
11
14
 
12
15
  /** Required `chainId` query: admin mutations always address a chain explicitly. */
@@ -123,6 +126,74 @@ export namespace schema {
123
126
  .check(z.describe('Query parameters for a logo read.'))
124
127
  }
125
128
 
129
+ /**
130
+ * A verification request with reviewer context: the management wire shape
131
+ * plus the requesting org's name and the reviewing admin's email.
132
+ */
133
+ export const Request = Schema.describe(
134
+ z.extend(requestSchema.VerifiedTokenRequest, {
135
+ orgName: z
136
+ .nullable(z.string())
137
+ .check(z.describe("Requesting organization's current name, or null when deleted.")),
138
+ reviewedBy: z
139
+ .nullable(z.string())
140
+ .check(z.describe('Reviewing admin email, or null while pending.')),
141
+ }),
142
+ 'A verified-token request with requester organization context.',
143
+ )
144
+
145
+ /** Path parameters addressing one verification request. */
146
+ export const RequestParams = z
147
+ .object({
148
+ requestId: z
149
+ .string()
150
+ .check(z.regex(/^vtr_[A-Za-z0-9_-]+$/), z.describe('Request resource id (`vtr_…`).')),
151
+ })
152
+ .check(z.describe('Path parameters for one verification request.'))
153
+
154
+ /** Schemas for the listVerifiedTokenRequests operation. */
155
+ export namespace listVerifiedTokenRequests {
156
+ /** Query parameters for the request queue. */
157
+ export const Query = z
158
+ .strictObject({
159
+ status: z
160
+ ._default(z.enum(['approved', 'denied', 'pending']), 'pending')
161
+ .check(z.describe('Review status to filter by.')),
162
+ })
163
+ .check(z.describe('Query parameters for the verification-request queue.'))
164
+
165
+ /** Response: requests in the selected status, oldest first. */
166
+ export const Response = Schema.describe(
167
+ z.object({
168
+ data: z.array(Request).check(z.describe('Requests in the selected status, oldest first.')),
169
+ }),
170
+ 'Verified-token requests in review-queue order.',
171
+ )
172
+ }
173
+
174
+ /** Schemas for the denyVerifiedTokenRequest operation. */
175
+ export namespace denyVerifiedTokenRequest {
176
+ /** Request body for a denial. */
177
+ export const Body = z
178
+ .object({
179
+ reviewNote: z.optional(
180
+ z
181
+ .string()
182
+ .check(z.trim(), z.maxLength(500), z.describe('Reason shown to the requester.')),
183
+ ),
184
+ })
185
+ .check(z.describe('Request body for denying a verification request.'))
186
+ }
187
+
188
+ /** Response shared by the approve and deny operations. */
189
+ export namespace reviewVerifiedTokenRequest {
190
+ /** The reviewed request. */
191
+ export const Response = Schema.describe(
192
+ z.object({ data: Request }),
193
+ 'The reviewed verified-token request.',
194
+ )
195
+ }
196
+
126
197
  /** Schemas for the replaceVerifiedTokens operation. */
127
198
  export namespace replaceVerifiedTokens {
128
199
  /** Query parameters for a bulk replace. */
@@ -158,311 +229,486 @@ export namespace schema {
158
229
  * - `PATCH /:address` partially updates a token (`404` if absent).
159
230
  * - `DELETE /:address` removes a token (`404` if absent).
160
231
  * - `PUT /:address/logo` uploads a curated logo SVG (gated on `assets`).
232
+ * - `GET /requests` lists organization verification requests.
233
+ * - `POST /requests/:requestId/approve|deny` reviews a request.
161
234
  */
162
235
  export function verifiedTokens() {
163
- return new Hono<App.Environment>()
164
- .get(
165
- '/',
166
- OpenApi.validate('query', schema.listVerifiedTokens.Query, {
167
- code: 'query_invalid',
168
- message: 'Invalid query parameters',
169
- }),
170
- OpenApi.describeRoute({
171
- operationId: 'listVerifiedTokens',
172
- responses: OpenApi.responses({
173
- success: {
174
- description: 'Curated verified tokens.',
175
- schema: schema.listVerifiedTokens.Response,
176
- },
236
+ return (
237
+ new Hono<App.Environment>()
238
+ .get(
239
+ '/',
240
+ OpenApi.validate('query', schema.listVerifiedTokens.Query, {
241
+ code: 'query_invalid',
242
+ message: 'Invalid query parameters',
177
243
  }),
178
- summary: 'List verified tokens',
179
- tags: ['Verified Tokens'],
180
- }),
181
- async (c) => {
182
- const db = Db.get(c.get('db'))
183
- const { chainId } = c.req.valid('query')
184
- const snapshot = await VerifiedTokens.read(db, chainId)
185
- return c.json(
186
- Response.validated(schema.listVerifiedTokens.Response, {
187
- data: snapshot?.list ?? [],
188
- version: snapshot?.version ?? null,
244
+ OpenApi.describeRoute({
245
+ operationId: 'listVerifiedTokens',
246
+ responses: OpenApi.responses({
247
+ success: {
248
+ description: 'Curated verified tokens.',
249
+ schema: schema.listVerifiedTokens.Response,
250
+ },
189
251
  }),
190
- )
191
- },
192
- )
193
- .post(
194
- '/',
195
- OpenApi.validate('json', schema.createVerifiedToken.Body, {
196
- code: 'body_invalid',
197
- message: 'Invalid request body',
198
- }),
199
- OpenApi.describeRoute({
200
- operationId: 'createVerifiedToken',
201
- responses: OpenApi.responses({
202
- errors: {
203
- 409: 'Duplicate address or symbol.',
204
- 413: 'Logo exceeds the size limit.',
205
- 415: 'Logo is not valid SVG.',
206
- },
207
- success: {
208
- description: 'Created verified token.',
209
- schema: schema.createVerifiedToken.Response,
210
- },
252
+ summary: 'List verified tokens',
253
+ tags: ['Verified Tokens'],
211
254
  }),
212
- summary: 'Create verified token',
213
- tags: ['Verified Tokens'],
214
- }),
215
- async (c) => {
216
- const db = Db.get(c.get('db'))
217
- const { logo, ...body } = c.req.valid('json')
218
- try {
219
- if (logo !== undefined) {
220
- const assets = c.get('assets')
221
- if (!assets?.put) return assetsNotEnabled(c)
222
- const svg = encodeLogo(logo)
223
- const duplicate = await findDuplicate(db, body)
224
- if (duplicate) throw duplicate
225
- await assets.put(logoKey(body.chainId, body.address), {
226
- body: svg,
227
- contentType: 'image/svg+xml',
228
- })
229
- }
230
- const { snapshot, token } = await VerifiedTokens.create(db, body)
255
+ async (c) => {
256
+ const db = Db.get(c.get('db'))
257
+ const { chainId } = c.req.valid('query')
258
+ const snapshot = await VerifiedTokens.read(db, chainId)
231
259
  return c.json(
232
- Response.validated(schema.createVerifiedToken.Response, {
233
- data: token,
234
- version: snapshot.version,
260
+ Response.validated(schema.listVerifiedTokens.Response, {
261
+ data: snapshot?.list ?? [],
262
+ version: snapshot?.version ?? null,
235
263
  }),
236
264
  )
237
- } catch (cause) {
238
- return mutationError(c, cause)
239
- }
240
- },
241
- )
242
- .put(
243
- '/',
244
- OpenApi.validate('query', schema.replaceVerifiedTokens.Query, {
245
- code: 'query_invalid',
246
- message: 'Invalid query parameters',
247
- }),
248
- OpenApi.validate('json', schema.replaceVerifiedTokens.Body, {
249
- code: 'body_invalid',
250
- message: 'Invalid request body',
251
- }),
252
- OpenApi.describeRoute({
253
- operationId: 'replaceVerifiedTokens',
254
- responses: OpenApi.responses({
255
- errors: {
256
- 409: 'Duplicate address or symbol.',
257
- 412: 'Version precondition failed.',
258
- },
259
- success: {
260
- description: 'Published verified list.',
261
- schema: schema.replaceVerifiedTokens.Response,
262
- },
265
+ },
266
+ )
267
+ .post(
268
+ '/',
269
+ OpenApi.validate('json', schema.createVerifiedToken.Body, {
270
+ code: 'body_invalid',
271
+ message: 'Invalid request body',
263
272
  }),
264
- summary: 'Replace verified tokens',
265
- tags: ['Verified Tokens'],
266
- }),
267
- async (c) => {
268
- const db = Db.get(c.get('db'))
269
- const { chainId } = c.req.valid('query')
270
- const { tokens } = c.req.valid('json')
271
- try {
272
- const { snapshot } = await VerifiedTokens.replace(db, chainId, tokens, {
273
- ...ifMatch(c),
274
- })
275
- return c.json(
276
- Response.validated(schema.replaceVerifiedTokens.Response, {
277
- data: snapshot.list,
278
- version: snapshot.version,
279
- }),
280
- )
281
- } catch (cause) {
282
- return mutationError(c, cause)
283
- }
284
- },
285
- )
286
- .patch(
287
- '/:address',
288
- OpenApi.validate('param', schema.Params, {
289
- code: 'address_invalid',
290
- message: 'Invalid token address',
291
- }),
292
- OpenApi.validate('query', schema.patchVerifiedToken.Query, {
293
- code: 'query_invalid',
294
- message: 'Invalid query parameters',
295
- }),
296
- OpenApi.validate('json', schema.patchVerifiedToken.Body, {
297
- code: 'body_invalid',
298
- message: 'Invalid request body',
299
- }),
300
- OpenApi.describeRoute({
301
- operationId: 'patchVerifiedToken',
302
- responses: OpenApi.responses({
303
- errors: {
304
- 404: 'Verified token not found (or feature disabled).',
305
- 409: 'Duplicate symbol.',
306
- 412: 'Version precondition failed.',
307
- },
308
- success: {
309
- description: 'Updated verified token.',
310
- schema: schema.patchVerifiedToken.Response,
311
- },
273
+ OpenApi.describeRoute({
274
+ operationId: 'createVerifiedToken',
275
+ responses: OpenApi.responses({
276
+ errors: {
277
+ 409: 'Duplicate address or symbol.',
278
+ 413: 'Logo exceeds the size limit.',
279
+ 415: 'Logo is not valid SVG.',
280
+ },
281
+ success: {
282
+ description: 'Created verified token.',
283
+ schema: schema.createVerifiedToken.Response,
284
+ },
285
+ }),
286
+ summary: 'Create verified token',
287
+ tags: ['Verified Tokens'],
312
288
  }),
313
- summary: 'Update verified token',
314
- tags: ['Verified Tokens'],
315
- }),
316
- async (c) => {
317
- const db = Db.get(c.get('db'))
318
- const { address } = c.req.valid('param')
319
- const { chainId } = c.req.valid('query')
320
- const body = c.req.valid('json')
321
- try {
322
- const { snapshot, token } = await VerifiedTokens.patch(db, chainId, address, body, {
323
- ...ifMatch(c),
324
- })
289
+ async (c) => {
290
+ const db = Db.get(c.get('db'))
291
+ const { logo, ...body } = c.req.valid('json')
292
+ try {
293
+ if (logo !== undefined) {
294
+ const assets = c.get('assets')
295
+ if (!assets?.put) return assetsNotEnabled(c)
296
+ const svg = encodeLogo(logo)
297
+ const duplicate = await findDuplicate(db, body)
298
+ if (duplicate) throw duplicate
299
+ await assets.put(logoKey(body.chainId, body.address), {
300
+ body: svg,
301
+ contentType: 'image/svg+xml',
302
+ })
303
+ }
304
+ const { snapshot, token } = await VerifiedTokens.create(db, body)
305
+ return c.json(
306
+ Response.validated(schema.createVerifiedToken.Response, {
307
+ data: token,
308
+ version: snapshot.version,
309
+ }),
310
+ )
311
+ } catch (cause) {
312
+ return mutationError(c, cause)
313
+ }
314
+ },
315
+ )
316
+ .put(
317
+ '/',
318
+ OpenApi.validate('query', schema.replaceVerifiedTokens.Query, {
319
+ code: 'query_invalid',
320
+ message: 'Invalid query parameters',
321
+ }),
322
+ OpenApi.validate('json', schema.replaceVerifiedTokens.Body, {
323
+ code: 'body_invalid',
324
+ message: 'Invalid request body',
325
+ }),
326
+ OpenApi.describeRoute({
327
+ operationId: 'replaceVerifiedTokens',
328
+ responses: OpenApi.responses({
329
+ errors: {
330
+ 409: 'Duplicate address or symbol.',
331
+ 412: 'Version precondition failed.',
332
+ },
333
+ success: {
334
+ description: 'Published verified list.',
335
+ schema: schema.replaceVerifiedTokens.Response,
336
+ },
337
+ }),
338
+ summary: 'Replace verified tokens',
339
+ tags: ['Verified Tokens'],
340
+ }),
341
+ async (c) => {
342
+ const db = Db.get(c.get('db'))
343
+ const { chainId } = c.req.valid('query')
344
+ const { tokens } = c.req.valid('json')
345
+ try {
346
+ const { snapshot } = await VerifiedTokens.replace(db, chainId, tokens, {
347
+ ...ifMatch(c),
348
+ })
349
+ return c.json(
350
+ Response.validated(schema.replaceVerifiedTokens.Response, {
351
+ data: snapshot.list,
352
+ version: snapshot.version,
353
+ }),
354
+ )
355
+ } catch (cause) {
356
+ return mutationError(c, cause)
357
+ }
358
+ },
359
+ )
360
+ .patch(
361
+ '/:address',
362
+ OpenApi.validate('param', schema.Params, {
363
+ code: 'address_invalid',
364
+ message: 'Invalid token address',
365
+ }),
366
+ OpenApi.validate('query', schema.patchVerifiedToken.Query, {
367
+ code: 'query_invalid',
368
+ message: 'Invalid query parameters',
369
+ }),
370
+ OpenApi.validate('json', schema.patchVerifiedToken.Body, {
371
+ code: 'body_invalid',
372
+ message: 'Invalid request body',
373
+ }),
374
+ OpenApi.describeRoute({
375
+ operationId: 'patchVerifiedToken',
376
+ responses: OpenApi.responses({
377
+ errors: {
378
+ 404: 'Verified token not found (or feature disabled).',
379
+ 409: 'Duplicate symbol.',
380
+ 412: 'Version precondition failed.',
381
+ },
382
+ success: {
383
+ description: 'Updated verified token.',
384
+ schema: schema.patchVerifiedToken.Response,
385
+ },
386
+ }),
387
+ summary: 'Update verified token',
388
+ tags: ['Verified Tokens'],
389
+ }),
390
+ async (c) => {
391
+ const db = Db.get(c.get('db'))
392
+ const { address } = c.req.valid('param')
393
+ const { chainId } = c.req.valid('query')
394
+ const body = c.req.valid('json')
395
+ try {
396
+ const { snapshot, token } = await VerifiedTokens.patch(db, chainId, address, body, {
397
+ ...ifMatch(c),
398
+ })
399
+ return c.json(
400
+ Response.validated(schema.patchVerifiedToken.Response, {
401
+ data: token,
402
+ version: snapshot.version,
403
+ }),
404
+ )
405
+ } catch (cause) {
406
+ return mutationError(c, cause)
407
+ }
408
+ },
409
+ )
410
+ .delete(
411
+ '/:address',
412
+ OpenApi.validate('param', schema.Params, {
413
+ code: 'address_invalid',
414
+ message: 'Invalid token address',
415
+ }),
416
+ OpenApi.validate('query', schema.deleteVerifiedToken.Query, {
417
+ code: 'query_invalid',
418
+ message: 'Invalid query parameters',
419
+ }),
420
+ OpenApi.describeRoute({
421
+ operationId: 'deleteVerifiedToken',
422
+ responses: OpenApi.responses({
423
+ errors: {
424
+ 404: 'Verified token not found (or feature disabled).',
425
+ 412: 'Version precondition failed.',
426
+ },
427
+ success: {
428
+ description: 'Removed; returns the new snapshot version.',
429
+ schema: schema.deleteVerifiedToken.Response,
430
+ },
431
+ }),
432
+ summary: 'Delete verified token',
433
+ tags: ['Verified Tokens'],
434
+ }),
435
+ async (c) => {
436
+ const db = Db.get(c.get('db'))
437
+ const { address } = c.req.valid('param')
438
+ const { chainId } = c.req.valid('query')
439
+ try {
440
+ const { snapshot } = await VerifiedTokens.remove(db, chainId, address, {
441
+ ...ifMatch(c),
442
+ })
443
+ return c.json(
444
+ Response.validated(schema.deleteVerifiedToken.Response, {
445
+ version: snapshot.version,
446
+ }),
447
+ )
448
+ } catch (cause) {
449
+ return mutationError(c, cause)
450
+ }
451
+ },
452
+ )
453
+ .get(
454
+ '/:address/logo',
455
+ OpenApi.validate('param', schema.Params, {
456
+ code: 'address_invalid',
457
+ message: 'Invalid token address',
458
+ }),
459
+ OpenApi.validate('query', schema.getVerifiedTokenLogo.Query, {
460
+ code: 'query_invalid',
461
+ message: 'Invalid query parameters',
462
+ }),
463
+ async (c) => {
464
+ const assets = c.get('assets')
465
+ const { address } = c.req.valid('param')
466
+ const { chainId } = c.req.valid('query')
467
+ const logo = await assets?.get(logoKey(chainId, address))
468
+ if (!logo)
469
+ return Response.error(c, {
470
+ code: 'logo_not_found',
471
+ message: 'Logo not found.',
472
+ status: 404,
473
+ })
474
+ c.header('Content-Type', logo.contentType)
475
+ return c.body(logo.body)
476
+ },
477
+ )
478
+ .put(
479
+ '/:address/logo',
480
+ OpenApi.validate('param', schema.Params, {
481
+ code: 'address_invalid',
482
+ message: 'Invalid token address',
483
+ }),
484
+ OpenApi.validate('query', schema.uploadVerifiedTokenLogo.Query, {
485
+ code: 'query_invalid',
486
+ message: 'Invalid query parameters',
487
+ }),
488
+ OpenApi.describeRoute({
489
+ operationId: 'uploadVerifiedTokenLogo',
490
+ responses: OpenApi.responses({
491
+ errors: {
492
+ 404: 'Asset uploads are not enabled.',
493
+ 413: 'Logo exceeds the size limit.',
494
+ 415: 'Body is not `image/svg+xml`.',
495
+ },
496
+ success: {
497
+ description: 'Stored logo location.',
498
+ schema: schema.uploadVerifiedTokenLogo.Response,
499
+ },
500
+ }),
501
+ summary: 'Upload verified-token logo',
502
+ tags: ['Verified Tokens'],
503
+ }),
504
+ async (c) => {
505
+ const assets = c.get('assets')
506
+ if (!assets?.put) return assetsNotEnabled(c)
507
+ const { address } = c.req.valid('param')
508
+ const { chainId } = c.req.valid('query')
509
+ const contentType = c.req.header('content-type')?.split(';')[0]?.trim().toLowerCase()
510
+ if (contentType !== 'image/svg+xml')
511
+ return Response.error(c, {
512
+ code: 'unsupported_media_type',
513
+ message: 'Logo uploads must be `image/svg+xml`.',
514
+ status: 415,
515
+ })
516
+ const body = await c.req.arrayBuffer()
517
+ if (body.byteLength === 0)
518
+ return Response.error(c, {
519
+ code: 'body_invalid',
520
+ message: 'Logo body is empty.',
521
+ status: 400,
522
+ })
523
+ if (body.byteLength > maxLogoBytes)
524
+ return Response.error(c, {
525
+ code: 'payload_too_large',
526
+ message: `Logo exceeds the ${maxLogoBytes / 1024} KiB limit.`,
527
+ status: 413,
528
+ })
529
+ // Keyed as the main API's loader reads it (curated icon takes precedence).
530
+ const key = logoKey(chainId, address)
531
+ await assets.put(key, { body, contentType: 'image/svg+xml' })
325
532
  return c.json(
326
- Response.validated(schema.patchVerifiedToken.Response, {
327
- data: token,
328
- version: snapshot.version,
533
+ Response.validated(schema.uploadVerifiedTokenLogo.Response, {
534
+ key,
535
+ path: `/assets/${key}`,
329
536
  }),
330
537
  )
331
- } catch (cause) {
332
- return mutationError(c, cause)
333
- }
334
- },
335
- )
336
- .delete(
337
- '/:address',
338
- OpenApi.validate('param', schema.Params, {
339
- code: 'address_invalid',
340
- message: 'Invalid token address',
341
- }),
342
- OpenApi.validate('query', schema.deleteVerifiedToken.Query, {
343
- code: 'query_invalid',
344
- message: 'Invalid query parameters',
345
- }),
346
- OpenApi.describeRoute({
347
- operationId: 'deleteVerifiedToken',
348
- responses: OpenApi.responses({
349
- errors: {
350
- 404: 'Verified token not found (or feature disabled).',
351
- 412: 'Version precondition failed.',
352
- },
353
- success: {
354
- description: 'Removed; returns the new snapshot version.',
355
- schema: schema.deleteVerifiedToken.Response,
356
- },
538
+ },
539
+ )
540
+ // Request-review routes live under the literal `/requests` segment; a
541
+ // future single-segment `GET`/`POST /:address` route would shadow it.
542
+ .get(
543
+ '/requests',
544
+ OpenApi.validate('query', schema.listVerifiedTokenRequests.Query, {
545
+ code: 'query_invalid',
546
+ message: 'Invalid query parameters',
357
547
  }),
358
- summary: 'Delete verified token',
359
- tags: ['Verified Tokens'],
360
- }),
361
- async (c) => {
362
- const db = Db.get(c.get('db'))
363
- const { address } = c.req.valid('param')
364
- const { chainId } = c.req.valid('query')
365
- try {
366
- const { snapshot } = await VerifiedTokens.remove(db, chainId, address, {
367
- ...ifMatch(c),
368
- })
548
+ OpenApi.describeRoute({
549
+ operationId: 'listVerifiedTokenRequests',
550
+ responses: OpenApi.responses({
551
+ success: {
552
+ description: 'Verification requests.',
553
+ schema: schema.listVerifiedTokenRequests.Response,
554
+ },
555
+ }),
556
+ summary: 'List token requests',
557
+ tags: ['Verified Tokens'],
558
+ }),
559
+ async (c) => {
560
+ const db = Db.get(c.get('db'))
561
+ const { status } = c.req.valid('query')
562
+ const records = await VerifiedTokenRequests.listByStatus(db, status)
369
563
  return c.json(
370
- Response.validated(schema.deleteVerifiedToken.Response, {
371
- version: snapshot.version,
564
+ Response.validated(schema.listVerifiedTokenRequests.Response, {
565
+ data: records.map((record) => ({
566
+ ...VerifiedTokenRequests.toRequest(record),
567
+ orgName: record.orgName,
568
+ })),
372
569
  }),
373
570
  )
374
- } catch (cause) {
375
- return mutationError(c, cause)
376
- }
377
- },
378
- )
379
- .get(
380
- '/:address/logo',
381
- OpenApi.validate('param', schema.Params, {
382
- code: 'address_invalid',
383
- message: 'Invalid token address',
384
- }),
385
- OpenApi.validate('query', schema.getVerifiedTokenLogo.Query, {
386
- code: 'query_invalid',
387
- message: 'Invalid query parameters',
388
- }),
389
- async (c) => {
390
- const assets = c.get('assets')
391
- const { address } = c.req.valid('param')
392
- const { chainId } = c.req.valid('query')
393
- const logo = await assets?.get(logoKey(chainId, address))
394
- if (!logo)
395
- return Response.error(c, {
396
- code: 'logo_not_found',
397
- message: 'Logo not found.',
398
- status: 404,
399
- })
400
- c.header('Content-Type', logo.contentType)
401
- return c.body(logo.body)
402
- },
403
- )
404
- .put(
405
- '/:address/logo',
406
- OpenApi.validate('param', schema.Params, {
407
- code: 'address_invalid',
408
- message: 'Invalid token address',
409
- }),
410
- OpenApi.validate('query', schema.uploadVerifiedTokenLogo.Query, {
411
- code: 'query_invalid',
412
- message: 'Invalid query parameters',
413
- }),
414
- OpenApi.describeRoute({
415
- operationId: 'uploadVerifiedTokenLogo',
416
- responses: OpenApi.responses({
417
- errors: {
418
- 404: 'Asset uploads are not enabled.',
419
- 413: 'Logo exceeds the size limit.',
420
- 415: 'Body is not `image/svg+xml`.',
421
- },
422
- success: {
423
- description: 'Stored logo location.',
424
- schema: schema.uploadVerifiedTokenLogo.Response,
425
- },
571
+ },
572
+ )
573
+ .post(
574
+ '/requests/:requestId/approve',
575
+ OpenApi.validate('param', schema.RequestParams, {
576
+ code: 'param_invalid',
577
+ message: 'Invalid request id',
426
578
  }),
427
- summary: 'Upload verified-token logo',
428
- tags: ['Verified Tokens'],
429
- }),
430
- async (c) => {
431
- const assets = c.get('assets')
432
- if (!assets?.put) return assetsNotEnabled(c)
433
- const { address } = c.req.valid('param')
434
- const { chainId } = c.req.valid('query')
435
- const contentType = c.req.header('content-type')?.split(';')[0]?.trim().toLowerCase()
436
- if (contentType !== 'image/svg+xml')
437
- return Response.error(c, {
438
- code: 'unsupported_media_type',
439
- message: 'Logo uploads must be `image/svg+xml`.',
440
- status: 415,
441
- })
442
- const body = await c.req.arrayBuffer()
443
- if (body.byteLength === 0)
444
- return Response.error(c, {
445
- code: 'body_invalid',
446
- message: 'Logo body is empty.',
447
- status: 400,
448
- })
449
- if (body.byteLength > maxLogoBytes)
450
- return Response.error(c, {
451
- code: 'payload_too_large',
452
- message: `Logo exceeds the ${maxLogoBytes / 1024} KiB limit.`,
453
- status: 413,
454
- })
455
- // Keyed as the main API's loader reads it (curated icon takes precedence).
456
- const key = logoKey(chainId, address)
457
- await assets.put(key, { body, contentType: 'image/svg+xml' })
458
- return c.json(
459
- Response.validated(schema.uploadVerifiedTokenLogo.Response, {
460
- key,
461
- path: `/assets/${key}`,
579
+ OpenApi.describeRoute({
580
+ operationId: 'approveVerifiedTokenRequest',
581
+ responses: OpenApi.responses({
582
+ errors: {
583
+ 404: 'Verification request not found.',
584
+ 409: 'Request already reviewed, or the token conflicts with the verified list.',
585
+ },
586
+ success: {
587
+ description: 'Approved request; the token is now verified.',
588
+ schema: schema.reviewVerifiedTokenRequest.Response,
589
+ },
462
590
  }),
463
- )
464
- },
465
- )
591
+ summary: 'Approve token request',
592
+ tags: ['Verified Tokens'],
593
+ }),
594
+ async (c) => {
595
+ const db = Db.get(c.get('db'))
596
+ const { requestId } = c.req.valid('param')
597
+ try {
598
+ const record = await VerifiedTokenRequests.get(db, requestId)
599
+ if (!record) return requestNotFound(c)
600
+ if (record.status !== 'pending') return requestNotPending(c)
601
+ // Skip creation when the token is already listed: the token create
602
+ // and the request review commit separately, so a crash between them
603
+ // leaves the request pending; re-approving converges here.
604
+ const snapshot = await VerifiedTokens.read(db, record.chainId)
605
+ if (!snapshot?.byAddress.has(record.address)) {
606
+ // A requester-uploaded logo publishes to the asset store first,
607
+ // so an upload failure leaves no token behind.
608
+ if (record.logo !== null) {
609
+ const assets = c.get('assets')
610
+ if (!assets?.put) return assetsNotEnabled(c)
611
+ await assets.put(logoKey(record.chainId, record.address), {
612
+ body: encodeLogo(record.logo),
613
+ contentType: 'image/svg+xml',
614
+ })
615
+ }
616
+ await VerifiedTokens.create(db, {
617
+ address: record.address,
618
+ chainId: record.chainId,
619
+ currency: record.currency,
620
+ decimals: record.decimals,
621
+ ...(record.logoUri === null ? {} : { logoUri: record.logoUri }),
622
+ name: record.name,
623
+ symbol: record.symbol,
624
+ })
625
+ }
626
+ const updated = await VerifiedTokenRequests.review(db, requestId, {
627
+ reviewedBy: c.get('identity')!.email,
628
+ status: 'approved',
629
+ })
630
+ if (!updated) return requestNotPending(c)
631
+ return c.json(
632
+ Response.validated(schema.reviewVerifiedTokenRequest.Response, {
633
+ data: await withOrgName(db, updated),
634
+ }),
635
+ )
636
+ } catch (cause) {
637
+ return mutationError(c, cause)
638
+ }
639
+ },
640
+ )
641
+ .post(
642
+ '/requests/:requestId/deny',
643
+ OpenApi.validate('param', schema.RequestParams, {
644
+ code: 'param_invalid',
645
+ message: 'Invalid request id',
646
+ }),
647
+ OpenApi.validate('json', schema.denyVerifiedTokenRequest.Body, {
648
+ code: 'body_invalid',
649
+ message: 'Invalid request body',
650
+ }),
651
+ OpenApi.describeRoute({
652
+ operationId: 'denyVerifiedTokenRequest',
653
+ responses: OpenApi.responses({
654
+ errors: {
655
+ 404: 'Verification request not found.',
656
+ 409: 'Request already reviewed.',
657
+ },
658
+ success: {
659
+ description: 'Denied request.',
660
+ schema: schema.reviewVerifiedTokenRequest.Response,
661
+ },
662
+ }),
663
+ summary: 'Deny token request',
664
+ tags: ['Verified Tokens'],
665
+ }),
666
+ async (c) => {
667
+ const db = Db.get(c.get('db'))
668
+ const { requestId } = c.req.valid('param')
669
+ const body = c.req.valid('json')
670
+ try {
671
+ const record = await VerifiedTokenRequests.get(db, requestId)
672
+ if (!record) return requestNotFound(c)
673
+ const updated = await VerifiedTokenRequests.review(db, requestId, {
674
+ ...(body.reviewNote ? { reviewNote: body.reviewNote } : {}),
675
+ reviewedBy: c.get('identity')!.email,
676
+ status: 'denied',
677
+ })
678
+ if (!updated) return requestNotPending(c)
679
+ return c.json(
680
+ Response.validated(schema.reviewVerifiedTokenRequest.Response, {
681
+ data: await withOrgName(db, updated),
682
+ }),
683
+ )
684
+ } catch (cause) {
685
+ return mutationError(c, cause)
686
+ }
687
+ },
688
+ )
689
+ )
690
+ }
691
+
692
+ /** Attaches the requesting org's current name to a request's wire shape. */
693
+ async function withOrgName(db: Db.Db, record: VerifiedTokenRequests.Record) {
694
+ const org = await Organizations.get(db, record.orgId)
695
+ return { ...VerifiedTokenRequests.toRequest(record), orgName: org?.name ?? null }
696
+ }
697
+
698
+ function requestNotFound(c: Context<App.Environment>) {
699
+ return Response.error(c, {
700
+ code: 'verified_token_request_not_found',
701
+ message: 'Verification request not found',
702
+ status: 404,
703
+ })
704
+ }
705
+
706
+ function requestNotPending(c: Context<App.Environment>) {
707
+ return Response.error(c, {
708
+ code: 'request_not_pending',
709
+ message: 'Request has already been reviewed',
710
+ status: 409,
711
+ })
466
712
  }
467
713
 
468
714
  /** Largest accepted logo upload (SVG icons are tiny; this is generous). */