tapimo 0.5.3 → 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 (289) 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 +526 -1
  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 +872 -251
  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/rpc.d.ts +31 -31
  64. package/dist/apps/data/routes/tokens.d.ts +166 -173
  65. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  66. package/dist/apps/data/routes/tokens.js +14 -12
  67. package/dist/apps/data/routes/tokens.js.map +1 -1
  68. package/dist/apps/data/routes/transactions.d.ts +277 -536
  69. package/dist/apps/data/routes/transactions.d.ts.map +1 -1
  70. package/dist/apps/data/routes/transactions.js +96 -77
  71. package/dist/apps/data/routes/transactions.js.map +1 -1
  72. package/dist/apps/data/routes/transfers.d.ts +82 -17
  73. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  74. package/dist/apps/data/routes/transfers.js +70 -13
  75. package/dist/apps/data/routes/transfers.js.map +1 -1
  76. package/dist/apps/data/routes/valuation.d.ts +307 -0
  77. package/dist/apps/data/routes/valuation.d.ts.map +1 -0
  78. package/dist/apps/data/routes/valuation.js +292 -0
  79. package/dist/apps/data/routes/valuation.js.map +1 -0
  80. package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -1
  81. package/dist/apps/data/routes/verified-tokens.js +7 -5
  82. package/dist/apps/data/routes/verified-tokens.js.map +1 -1
  83. package/dist/apps/data/routes/webhooks.d.ts +459 -479
  84. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  85. package/dist/apps/data/routes/webhooks.js +124 -25
  86. package/dist/apps/data/routes/webhooks.js.map +1 -1
  87. package/dist/apps/index.d.ts +1 -1
  88. package/dist/apps/index.d.ts.map +1 -1
  89. package/dist/apps/index.js +1 -1
  90. package/dist/apps/index.js.map +1 -1
  91. package/dist/apps/management/App.d.ts +8606 -1681
  92. package/dist/apps/management/App.d.ts.map +1 -1
  93. package/dist/apps/management/App.js +12 -1
  94. package/dist/apps/management/App.js.map +1 -1
  95. package/dist/apps/management/routes/api-keys.d.ts +23 -0
  96. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  97. package/dist/apps/management/routes/api-keys.js +5 -3
  98. package/dist/apps/management/routes/api-keys.js.map +1 -1
  99. package/dist/apps/management/routes/billing.d.ts +88 -48
  100. package/dist/apps/management/routes/billing.d.ts.map +1 -1
  101. package/dist/apps/management/routes/billing.js +10 -2
  102. package/dist/apps/management/routes/billing.js.map +1 -1
  103. package/dist/apps/management/routes/faucet.d.ts +218 -0
  104. package/dist/apps/management/routes/faucet.d.ts.map +1 -0
  105. package/dist/apps/management/routes/faucet.js +139 -0
  106. package/dist/apps/management/routes/faucet.js.map +1 -0
  107. package/dist/apps/management/routes/invitations.d.ts +121 -121
  108. package/dist/apps/management/routes/invitations.d.ts.map +1 -1
  109. package/dist/apps/management/routes/invitations.js +3 -0
  110. package/dist/apps/management/routes/invitations.js.map +1 -1
  111. package/dist/apps/management/routes/invite-links.d.ts +49 -20
  112. package/dist/apps/management/routes/invite-links.d.ts.map +1 -1
  113. package/dist/apps/management/routes/invite-links.js +26 -1
  114. package/dist/apps/management/routes/invite-links.js.map +1 -1
  115. package/dist/apps/management/routes/me.d.ts +2 -0
  116. package/dist/apps/management/routes/me.d.ts.map +1 -1
  117. package/dist/apps/management/routes/me.js +4 -0
  118. package/dist/apps/management/routes/me.js.map +1 -1
  119. package/dist/apps/management/routes/members.d.ts +49 -49
  120. package/dist/apps/management/routes/orgs.d.ts +91 -91
  121. package/dist/apps/management/routes/projects.d.ts +44 -44
  122. package/dist/apps/management/routes/usage.d.ts +35 -32
  123. package/dist/apps/management/routes/usage.d.ts.map +1 -1
  124. package/dist/apps/management/routes/usage.js +4 -0
  125. package/dist/apps/management/routes/usage.js.map +1 -1
  126. package/dist/apps/management/routes/verified-token-requests.d.ts +697 -0
  127. package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -0
  128. package/dist/apps/management/routes/verified-token-requests.js +271 -0
  129. package/dist/apps/management/routes/verified-token-requests.js.map +1 -0
  130. package/dist/apps/management/routes/webhooks.d.ts +6977 -0
  131. package/dist/apps/management/routes/webhooks.d.ts.map +1 -0
  132. package/dist/apps/management/routes/webhooks.js +573 -0
  133. package/dist/apps/management/routes/webhooks.js.map +1 -0
  134. package/dist/db/Db.d.ts +4 -0
  135. package/dist/db/Db.d.ts.map +1 -1
  136. package/dist/db/Db.js +83 -0
  137. package/dist/db/Db.js.map +1 -1
  138. package/dist/db/Schema.d.ts +41 -0
  139. package/dist/db/Schema.d.ts.map +1 -1
  140. package/dist/db/Schema.js +45 -0
  141. package/dist/db/Schema.js.map +1 -1
  142. package/dist/db/tables/earlyAccess.d.ts +50 -0
  143. package/dist/db/tables/earlyAccess.d.ts.map +1 -0
  144. package/dist/db/tables/earlyAccess.js +63 -0
  145. package/dist/db/tables/earlyAccess.js.map +1 -0
  146. package/dist/db/tables/invitations.d.ts +12 -1
  147. package/dist/db/tables/invitations.d.ts.map +1 -1
  148. package/dist/db/tables/invitations.js +23 -0
  149. package/dist/db/tables/invitations.js.map +1 -1
  150. package/dist/db/tables/inviteLinks.d.ts +10 -2
  151. package/dist/db/tables/inviteLinks.d.ts.map +1 -1
  152. package/dist/db/tables/inviteLinks.js +10 -1
  153. package/dist/db/tables/inviteLinks.js.map +1 -1
  154. package/dist/db/tables/sponsoredTransactions.d.ts +2 -0
  155. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  156. package/dist/db/tables/sponsoredTransactions.js +2 -0
  157. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  158. package/dist/db/tables/verifiedTokenRequests.d.ts +94 -0
  159. package/dist/db/tables/verifiedTokenRequests.d.ts.map +1 -0
  160. package/dist/db/tables/verifiedTokenRequests.js +120 -0
  161. package/dist/db/tables/verifiedTokenRequests.js.map +1 -0
  162. package/dist/db/tables/webhookSubscriptions.d.ts +14 -0
  163. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  164. package/dist/db/tables/webhookSubscriptions.js +16 -0
  165. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  166. package/dist/internal/Auth.d.ts +43 -2
  167. package/dist/internal/Auth.d.ts.map +1 -1
  168. package/dist/internal/Auth.js +60 -45
  169. package/dist/internal/Auth.js.map +1 -1
  170. package/dist/internal/FundingQuotes.d.ts +69 -15
  171. package/dist/internal/FundingQuotes.d.ts.map +1 -1
  172. package/dist/internal/FundingQuotes.js +74 -25
  173. package/dist/internal/FundingQuotes.js.map +1 -1
  174. package/dist/internal/Log.d.ts +19 -1
  175. package/dist/internal/Log.d.ts.map +1 -1
  176. package/dist/internal/Log.js +2 -0
  177. package/dist/internal/Log.js.map +1 -1
  178. package/dist/internal/MetricSink.d.ts.map +1 -1
  179. package/dist/internal/MetricSink.js +9 -0
  180. package/dist/internal/MetricSink.js.map +1 -1
  181. package/dist/internal/OpenApi.d.ts +2 -2
  182. package/dist/internal/OpenApi.d.ts.map +1 -1
  183. package/dist/internal/OpenApi.js +3 -0
  184. package/dist/internal/OpenApi.js.map +1 -1
  185. package/dist/internal/Schema.d.ts +14 -5
  186. package/dist/internal/Schema.d.ts.map +1 -1
  187. package/dist/internal/Schema.js +23 -5
  188. package/dist/internal/Schema.js.map +1 -1
  189. package/dist/internal/Value.d.ts +18 -0
  190. package/dist/internal/Value.d.ts.map +1 -1
  191. package/dist/internal/Value.js +11 -1
  192. package/dist/internal/Value.js.map +1 -1
  193. package/dist/internal/VerifiedTokens.d.ts.map +1 -1
  194. package/dist/internal/VerifiedTokens.js +10 -1
  195. package/dist/internal/VerifiedTokens.js.map +1 -1
  196. package/dist/internal/Viem.d.ts +1 -1
  197. package/dist/internal/Webhooks.d.ts +24 -3
  198. package/dist/internal/Webhooks.d.ts.map +1 -1
  199. package/dist/internal/Webhooks.js +34 -8
  200. package/dist/internal/Webhooks.js.map +1 -1
  201. package/package.json +1 -1
  202. package/src/App.ts +9 -1
  203. package/src/Client.test-d.ts +638 -6
  204. package/src/admin/App.ts +4 -0
  205. package/src/admin/apps/early-access.test.ts +125 -0
  206. package/src/admin/apps/early-access.ts +175 -0
  207. package/src/admin/apps/verified-tokens.test.ts +201 -0
  208. package/src/admin/apps/verified-tokens.ts +532 -286
  209. package/src/admin/ui/plugin.ts +2 -1
  210. package/src/admin/ui/src/lib/early-access.ts +63 -0
  211. package/src/admin/ui/src/lib/verified-tokens.ts +79 -0
  212. package/src/admin/ui/src/routeTree.gen.ts +33 -2
  213. package/src/admin/ui/src/routes/__root.tsx +2 -1
  214. package/src/admin/ui/src/routes/early-access.tsx +157 -0
  215. package/src/admin/ui/src/routes/verified-tokens.tsx +230 -19
  216. package/src/admin/ui.generated.ts +48 -33
  217. package/src/apps/data/App.ts +22 -14
  218. package/src/apps/data/FundingQuoteProviders.test.ts +298 -0
  219. package/src/apps/data/FundingQuoteProviders.ts +257 -4
  220. package/src/apps/data/FxOracle.test.ts +118 -0
  221. package/src/apps/data/FxOracle.ts +153 -0
  222. package/src/apps/data/index.ts +1 -0
  223. package/src/apps/data/routes/activities.test.ts +173 -3
  224. package/src/apps/data/routes/activities.ts +403 -132
  225. package/src/apps/data/routes/addresses.ts +19 -9
  226. package/src/apps/data/routes/balances.test.ts +144 -11
  227. package/src/apps/data/routes/balances.ts +140 -51
  228. package/src/apps/data/routes/exchanges.test.ts +540 -9
  229. package/src/apps/data/routes/exchanges.ts +690 -103
  230. package/src/apps/data/routes/funding.test.ts +84 -0
  231. package/src/apps/data/routes/funding.ts +38 -6
  232. package/src/apps/data/routes/receipts.test.ts +125 -23
  233. package/src/apps/data/routes/receipts.ts +205 -100
  234. package/src/apps/data/routes/tokens.test.ts +43 -0
  235. package/src/apps/data/routes/tokens.ts +27 -12
  236. package/src/apps/data/routes/transactions.test.ts +72 -19
  237. package/src/apps/data/routes/transactions.ts +162 -92
  238. package/src/apps/data/routes/transfers.test.ts +146 -4
  239. package/src/apps/data/routes/transfers.ts +91 -13
  240. package/src/apps/data/routes/valuation.test.ts +422 -0
  241. package/src/apps/data/routes/valuation.ts +423 -0
  242. package/src/apps/data/routes/verified-tokens.ts +14 -2
  243. package/src/apps/data/routes/webhooks.test.ts +56 -8
  244. package/src/apps/data/routes/webhooks.ts +144 -27
  245. package/src/apps/index.ts +1 -1
  246. package/src/apps/management/App.ts +13 -0
  247. package/src/apps/management/routes/api-keys.test.ts +87 -0
  248. package/src/apps/management/routes/api-keys.ts +4 -3
  249. package/src/apps/management/routes/billing.test.ts +112 -1
  250. package/src/apps/management/routes/billing.ts +7 -2
  251. package/src/apps/management/routes/faucet.test.ts +79 -0
  252. package/src/apps/management/routes/faucet.ts +185 -0
  253. package/src/apps/management/routes/invitations.test.ts +172 -0
  254. package/src/apps/management/routes/invitations.ts +3 -0
  255. package/src/apps/management/routes/invite-links.test.ts +117 -0
  256. package/src/apps/management/routes/invite-links.ts +43 -1
  257. package/src/apps/management/routes/me.test.ts +140 -88
  258. package/src/apps/management/routes/me.ts +6 -0
  259. package/src/apps/management/routes/usage.test.ts +6 -0
  260. package/src/apps/management/routes/usage.ts +7 -0
  261. package/src/apps/management/routes/verified-token-requests.test.ts +377 -0
  262. package/src/apps/management/routes/verified-token-requests.ts +397 -0
  263. package/src/apps/management/routes/webhooks.test.ts +426 -0
  264. package/src/apps/management/routes/webhooks.ts +672 -0
  265. package/src/db/Db.test.ts +19 -0
  266. package/src/db/Db.ts +95 -0
  267. package/src/db/Schema.ts +53 -0
  268. package/src/db/tables/earlyAccess.ts +86 -0
  269. package/src/db/tables/invitations.ts +26 -1
  270. package/src/db/tables/inviteLinks.ts +22 -3
  271. package/src/db/tables/sponsoredTransactions.ts +4 -0
  272. package/src/db/tables/verifiedTokenRequests.test.ts +200 -0
  273. package/src/db/tables/verifiedTokenRequests.ts +182 -0
  274. package/src/db/tables/webhookSubscriptions.ts +31 -0
  275. package/src/internal/Auth.ts +73 -45
  276. package/src/internal/FundingQuotes.test.ts +143 -1
  277. package/src/internal/FundingQuotes.ts +95 -28
  278. package/src/internal/Log.test-d.ts +21 -0
  279. package/src/internal/Log.test.ts +50 -0
  280. package/src/internal/Log.ts +24 -1
  281. package/src/internal/MetricSink.test.ts +160 -0
  282. package/src/internal/MetricSink.ts +10 -0
  283. package/src/internal/OpenApi.ts +3 -0
  284. package/src/internal/Schema.ts +50 -5
  285. package/src/internal/Value.test.ts +20 -0
  286. package/src/internal/Value.ts +24 -1
  287. package/src/internal/VerifiedTokens.ts +9 -1
  288. package/src/internal/Webhooks.test.ts +140 -0
  289. package/src/internal/Webhooks.ts +53 -9
@@ -1,12 +1,13 @@
1
- import { createFileRoute } from '@tanstack/react-router'
1
+ import { createFileRoute, useNavigate } from '@tanstack/react-router'
2
2
  import { useState } from 'react'
3
- import { Button, Fields, Input, Select, useCopy } from 'regen-ui'
3
+ import { Button, Fields, Input, Select, Tabs, useCopy } from 'regen-ui'
4
4
  import { Check, Copy as CopyIcon, ExternalLink, Plus, Trash2 } from 'lucide-react'
5
5
  import { isAddress } from 'viem'
6
6
  import { tempo as mainnet, tempoTestnet as testnet } from 'viem/tempo/chains'
7
7
  import { Hooks } from 'wagmi/tempo'
8
8
 
9
9
  import { Card, Notice, Page } from '../components/Page'
10
+ import { Checkbox } from '../components/Checkbox'
10
11
  import { Dialog } from '../components/Dialog'
11
12
  import * as VerifiedTokens from '../lib/verified-tokens'
12
13
 
@@ -17,8 +18,12 @@ type ChainId = typeof mainnet.id | typeof testnet.id
17
18
  const defaultChain: ChainId = mainnet.id
18
19
 
19
20
  export const Route = createFileRoute('/verified-tokens')({
21
+ validateSearch: VerifiedTokens.search,
20
22
  loader: ({ context }) =>
21
- context.queryClient.prefetchQuery(VerifiedTokens.get.options({ chainId: defaultChain })),
23
+ Promise.all([
24
+ context.queryClient.prefetchQuery(VerifiedTokens.get.options({ chainId: defaultChain })),
25
+ context.queryClient.prefetchQuery(VerifiedTokens.requests.list.options()),
26
+ ]),
22
27
  component: RouteComponent,
23
28
  })
24
29
 
@@ -35,10 +40,14 @@ const explorers: Record<number, string> = {
35
40
  }
36
41
 
37
42
  function RouteComponent() {
43
+ const { tab = 'verified' } = Route.useSearch()
44
+ const navigate = useNavigate({ from: Route.fullPath })
38
45
  const [chain, setChain] = useState<ChainId>(defaultChain)
39
46
  const [open, setOpen] = useState(false)
40
47
  const tokens = VerifiedTokens.get.useQuery({ chainId: chain })
48
+ const requests = VerifiedTokens.requests.list.useQuery()
41
49
  const create = VerifiedTokens.create.useMutation()
50
+ const pendingCount = requests.data?.data.length ?? 0
42
51
 
43
52
  return (
44
53
  <Dialog.Root onOpenChange={setOpen} open={open}>
@@ -46,25 +55,43 @@ function RouteComponent() {
46
55
  title="Verified Tokens"
47
56
  description="Manage the curated verified token list"
48
57
  action={
49
- <div className="flex items-center gap-2">
50
- <ChainSelect onChange={setChain} value={chain} />
51
- <Dialog.Trigger
52
- render={
53
- <Button variant="primary">
54
- <Plus className="size-4" />
55
- Add token
56
- </Button>
57
- }
58
- />
59
- </div>
58
+ tab === 'verified' ? (
59
+ <div className="flex items-center gap-2">
60
+ <ChainSelect onChange={setChain} value={chain} />
61
+ <Dialog.Trigger
62
+ render={
63
+ <Button variant="primary">
64
+ <Plus className="size-4" />
65
+ Add token
66
+ </Button>
67
+ }
68
+ />
69
+ </div>
70
+ ) : undefined
60
71
  }
61
72
  >
62
- <TokenList
63
- chain={chain}
64
- error={tokens.error}
65
- loading={tokens.isPending}
66
- tokens={tokens.data?.data ?? []}
73
+ <Tabs
74
+ active={tab}
75
+ onChange={(id) => void navigate({ search: id === 'verified' ? {} : { tab: id } })}
76
+ tabs={[
77
+ { id: 'verified', label: 'Verified' },
78
+ { id: 'pending', label: pendingCount > 0 ? `Pending (${pendingCount})` : 'Pending' },
79
+ ]}
67
80
  />
81
+ {tab === 'verified' ? (
82
+ <TokenList
83
+ chain={chain}
84
+ error={tokens.error}
85
+ loading={tokens.isPending}
86
+ tokens={tokens.data?.data ?? []}
87
+ />
88
+ ) : (
89
+ <RequestQueue
90
+ error={requests.error}
91
+ loading={requests.isPending}
92
+ requests={requests.data?.data ?? []}
93
+ />
94
+ )}
68
95
  <Dialog.Popup>
69
96
  <AddTokenForm
70
97
  defaultChain={chain}
@@ -82,6 +109,190 @@ function RouteComponent() {
82
109
  )
83
110
  }
84
111
 
112
+ /** Display label for a chain, falling back to the raw id. */
113
+ function chainLabel(chainId: number) {
114
+ return chains.find((chain) => chain.id === chainId)?.label ?? String(chainId)
115
+ }
116
+
117
+ /** Pending verification requests, oldest first, reviewed in bulk via selection. */
118
+ function RequestQueue(props: {
119
+ error: Error | null
120
+ loading: boolean
121
+ requests: readonly VerifiedTokens.requests.VerifiedTokenRequest[]
122
+ }) {
123
+ const approve = VerifiedTokens.requests.approve.useMutation()
124
+ const deny = VerifiedTokens.requests.deny.useMutation()
125
+ const [selected, setSelected] = useState<ReadonlySet<string>>(new Set())
126
+ const [busy, setBusy] = useState<'approve' | 'deny' | null>(null)
127
+ // Filtering against the live rows self-heals selections whose requests were
128
+ // reviewed elsewhere or removed by a refetch.
129
+ const chosen = props.requests.filter((request) => selected.has(request.id))
130
+
131
+ function toggle(id: string) {
132
+ setSelected((previous) => {
133
+ const next = new Set(previous)
134
+ if (next.has(id)) next.delete(id)
135
+ else next.add(id)
136
+ return next
137
+ })
138
+ }
139
+
140
+ async function review(action: 'approve' | 'deny') {
141
+ if (action === 'approve' && !confirm(`Verify ${describeSelection(chosen)}?`)) return
142
+ let reviewNote: string | undefined
143
+ if (action === 'deny') {
144
+ const input = prompt(`Reason for denying ${describeSelection(chosen)} (optional):`)
145
+ if (input === null) return
146
+ reviewNote = input.trim() || undefined
147
+ }
148
+ setBusy(action)
149
+ try {
150
+ for (const request of chosen) {
151
+ if (action === 'approve')
152
+ await approve.mutateAsync({ chainId: request.chainId, requestId: request.id })
153
+ else
154
+ await deny.mutateAsync({
155
+ requestId: request.id,
156
+ ...(reviewNote ? { reviewNote } : {}),
157
+ })
158
+ }
159
+ setSelected(new Set())
160
+ } catch (error) {
161
+ alert(error instanceof Error ? error.message : String(error))
162
+ } finally {
163
+ setBusy(null)
164
+ }
165
+ }
166
+
167
+ return (
168
+ <Card>
169
+ {props.requests.length > 0 ? (
170
+ <div className="flex items-center justify-between gap-4 border-b border-border px-5 py-3">
171
+ <span className="text-label-12 text-foreground-secondary">{chosen.length} selected</span>
172
+ <div className="flex items-center gap-2">
173
+ <Button
174
+ disabled={chosen.length === 0 || busy !== null}
175
+ loading={busy === 'deny'}
176
+ onClick={() => void review('deny')}
177
+ size="small"
178
+ >
179
+ Deny
180
+ </Button>
181
+ <Button
182
+ disabled={chosen.length === 0 || busy !== null}
183
+ loading={busy === 'approve'}
184
+ onClick={() => void review('approve')}
185
+ size="small"
186
+ variant="primary"
187
+ >
188
+ Approve
189
+ </Button>
190
+ </div>
191
+ </div>
192
+ ) : null}
193
+ {props.loading ? (
194
+ <Notice>Loading requests…</Notice>
195
+ ) : props.error ? (
196
+ <Notice>{props.error.message}</Notice>
197
+ ) : props.requests.length === 0 ? (
198
+ <Notice>No pending requests.</Notice>
199
+ ) : (
200
+ <ul className="divide-y divide-border">
201
+ {props.requests.map((request) => (
202
+ <RequestRow
203
+ key={request.id}
204
+ onToggle={() => toggle(request.id)}
205
+ request={request}
206
+ selected={selected.has(request.id)}
207
+ />
208
+ ))}
209
+ </ul>
210
+ )}
211
+ </Card>
212
+ )
213
+ }
214
+
215
+ /** Confirmation label: one token by name, several by count. */
216
+ function describeSelection(requests: readonly VerifiedTokens.requests.VerifiedTokenRequest[]) {
217
+ const [first] = requests
218
+ if (requests.length === 1 && first) return `${first.name} (${first.symbol})`
219
+ return `${requests.length} tokens`
220
+ }
221
+
222
+ function RequestRow(props: {
223
+ onToggle: () => void
224
+ request: VerifiedTokens.requests.VerifiedTokenRequest
225
+ selected: boolean
226
+ }) {
227
+ const { request } = props
228
+ return (
229
+ // The row is a pointer affordance for the checkbox; clicks on interactive
230
+ // children (copy button, the checkbox itself) keep their own behavior.
231
+ <li
232
+ className="flex cursor-pointer items-center justify-between gap-4 px-5 py-3 transition-colors hover:bg-surface-hover data-[selected]:bg-surface-hover"
233
+ data-selected={props.selected || undefined}
234
+ onClick={(event) => {
235
+ if ((event.target as HTMLElement).closest('a, button, input')) return
236
+ props.onToggle()
237
+ }}
238
+ >
239
+ <div className="flex min-w-0 items-center gap-4">
240
+ <RequestLogo request={request} />
241
+ <div className="flex min-w-0 flex-col gap-1">
242
+ <span className="text-label-14 text-foreground">
243
+ {request.name} <span className="text-foreground-secondary">{request.symbol}</span>{' '}
244
+ <span className="text-foreground-secondary">· {chainLabel(request.chainId)}</span>
245
+ </span>
246
+ <div className="flex items-center gap-3">
247
+ <Address value={request.address} />
248
+ <span className="text-label-12 text-foreground-secondary">
249
+ {request.orgName ?? request.orgId} ·{' '}
250
+ {new Intl.DateTimeFormat(undefined, { dateStyle: 'medium' }).format(
251
+ new Date(request.createdAt),
252
+ )}
253
+ </span>
254
+ </div>
255
+ {request.note ? (
256
+ <p className="truncate text-label-12 text-foreground-secondary" title={request.note}>
257
+ {request.note}
258
+ </p>
259
+ ) : null}
260
+ </div>
261
+ </div>
262
+ <Checkbox
263
+ aria-label={`Select ${request.name}`}
264
+ checked={props.selected}
265
+ onCheckedChange={props.onToggle}
266
+ />
267
+ </li>
268
+ )
269
+ }
270
+
271
+ /** Requested logo (uploaded SVG or URL), falling back to symbol initials. */
272
+ function RequestLogo(props: { request: VerifiedTokens.requests.VerifiedTokenRequest }) {
273
+ const { request } = props
274
+ const [failed, setFailed] = useState(false)
275
+ // Data-URI `img` rendering keeps requester-supplied SVG inert (no scripts).
276
+ const src = request.logo
277
+ ? `data:image/svg+xml;utf8,${encodeURIComponent(request.logo)}`
278
+ : request.logoUri
279
+ if (src && !failed)
280
+ return (
281
+ <img
282
+ alt=""
283
+ className="size-8 shrink-0 rounded-full bg-secondary"
284
+ onError={() => setFailed(true)}
285
+ src={src}
286
+ title={request.logo ? 'Uploaded SVG' : request.logoUri}
287
+ />
288
+ )
289
+ return (
290
+ <div className="flex size-8 shrink-0 items-center justify-center rounded-full bg-secondary text-label-12 text-foreground-secondary">
291
+ {request.symbol.slice(0, 2).toUpperCase()}
292
+ </div>
293
+ )
294
+ }
295
+
85
296
  function ChainSelect(props: {
86
297
  className?: string
87
298
  onChange: (chain: ChainId) => void