tapimo 0.15.0 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/App.d.ts +26 -26
- package/dist/admin/App.d.ts +3 -0
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +41 -41
- package/dist/admin/apps/routes.d.ts +64 -64
- package/dist/admin/apps/verified-tokens.d.ts +161 -155
- package/dist/admin/apps/verified-tokens.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.js +24 -4
- package/dist/admin/apps/verified-tokens.js.map +1 -1
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.d.ts.map +1 -1
- package/dist/admin/ui.generated.js +201 -209
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +357 -357
- package/dist/apps/data/routes/blocks.d.ts +125 -125
- package/dist/apps/data/routes/fee-amm.d.ts +108 -108
- package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
- package/dist/apps/data/routes/fee-amm.js +10 -2
- package/dist/apps/data/routes/fee-amm.js.map +1 -1
- package/dist/apps/data/routes/indexer.d.ts +43 -43
- package/dist/apps/data/routes/receipts.d.ts +235 -235
- package/dist/apps/data/routes/rpc.js +45 -11
- package/dist/apps/data/routes/rpc.js.map +1 -1
- package/dist/apps/data/routes/transfers.d.ts +81 -81
- package/dist/apps/data/routes/valuation.d.ts +32 -32
- package/dist/apps/data/routes/verified-tokens.d.ts +86 -86
- package/dist/apps/data/routes/webhooks.d.ts +1 -1
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +17 -10
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/data/routes/zones.d.ts +31 -31
- package/dist/apps/management/App.d.ts +26 -26
- package/dist/apps/management/routes/api-keys.d.ts +246 -246
- package/dist/apps/management/routes/invitations.d.ts +138 -138
- package/dist/apps/management/routes/invite-links.d.ts +168 -168
- package/dist/apps/management/routes/orgs.d.ts +98 -98
- package/dist/apps/management/routes/projects.d.ts +125 -125
- package/dist/apps/management/routes/verified-token-requests.d.ts +95 -95
- package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -1
- package/dist/apps/management/routes/verified-token-requests.js +7 -9
- package/dist/apps/management/routes/verified-token-requests.js.map +1 -1
- package/dist/apps/management/routes/webhooks.d.ts +423 -423
- package/dist/apps/mcp.d.ts.map +1 -1
- package/dist/apps/mcp.js +16 -3
- package/dist/apps/mcp.js.map +1 -1
- package/dist/apps/mpp/App.d.ts +44 -44
- package/dist/apps/routes/routes/chains.d.ts +25 -25
- package/dist/apps/routes/routes/quotes.d.ts +87 -87
- package/dist/apps/routes/routes/transfers.d.ts +438 -438
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +5 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/tables/verifiedTokenRequests.d.ts +21 -4
- package/dist/db/tables/verifiedTokenRequests.d.ts.map +1 -1
- package/dist/db/tables/verifiedTokenRequests.js +65 -44
- package/dist/db/tables/verifiedTokenRequests.js.map +1 -1
- package/dist/internal/Auth.d.ts +16 -0
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +31 -8
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/routes/providers/relay.d.ts +1 -1
- package/dist/internal/routes/providers/relay.js +1 -1
- package/dist/internal/routes/providers/relay.js.map +1 -1
- package/package.json +1 -1
- package/src/admin/apps/verified-tokens.test.ts +32 -0
- package/src/admin/apps/verified-tokens.ts +30 -4
- package/src/admin/ui/src/lib/verified-tokens.ts +24 -9
- package/src/admin/ui/src/routes/__root.tsx +1 -1
- package/src/admin/ui/src/routes/organizations.tsx +34 -4
- package/src/admin/ui/src/routes/verified-tokens.tsx +64 -20
- package/src/admin/ui.generated.ts +202 -263
- package/src/apps/data/App.test.ts +26 -0
- package/src/apps/data/routes/fee-amm.test.ts +19 -0
- package/src/apps/data/routes/fee-amm.ts +10 -2
- package/src/apps/data/routes/rpc.test.ts +32 -0
- package/src/apps/data/routes/rpc.ts +47 -10
- package/src/apps/data/routes/webhooks.scan.test.ts +29 -0
- package/src/apps/data/routes/webhooks.test.ts +6 -0
- package/src/apps/data/routes/webhooks.ts +25 -12
- package/src/apps/management/routes/verified-token-requests.test.ts +16 -2
- package/src/apps/management/routes/verified-token-requests.ts +7 -10
- package/src/apps/mcp.test.ts +108 -25
- package/src/apps/mcp.ts +15 -3
- package/src/db/Db.ts +9 -0
- package/src/db/tables/routesDeposits.test.ts +72 -0
- package/src/db/tables/verifiedTokenRequests.test.ts +39 -20
- package/src/db/tables/verifiedTokenRequests.ts +80 -44
- package/src/internal/Auth.test.ts +92 -0
- package/src/internal/Auth.ts +51 -9
- package/src/internal/routes/providers/relay.test.ts +59 -0
- package/src/internal/routes/providers/relay.ts +1 -1
|
@@ -2,6 +2,7 @@ import { type Context, Hono } from 'hono'
|
|
|
2
2
|
import * as z from 'zod/mini'
|
|
3
3
|
|
|
4
4
|
import * as AssetResponse from '../../internal/AssetResponse.js'
|
|
5
|
+
import * as Cursor from '../../internal/Cursor.js'
|
|
5
6
|
import * as OpenApi from '../../internal/OpenApi.js'
|
|
6
7
|
import * as Response from '../../internal/Response.js'
|
|
7
8
|
import * as Schema from '../../internal/Schema.js'
|
|
@@ -15,6 +16,8 @@ import type * as App from '../App.js'
|
|
|
15
16
|
|
|
16
17
|
/** Required `chainId` query: admin mutations always address a chain explicitly. */
|
|
17
18
|
const ChainIdQuery = Schema.ChainId
|
|
19
|
+
/** Maximum rows per review-queue response, bounding inline SVG payloads to 1.25 MiB. */
|
|
20
|
+
const maxRequestPage = 5
|
|
18
21
|
|
|
19
22
|
/** Zod schemas owned by the admin verified-tokens handler. */
|
|
20
23
|
export namespace schema {
|
|
@@ -157,6 +160,16 @@ export namespace schema {
|
|
|
157
160
|
/** Query parameters for the request queue. */
|
|
158
161
|
export const Query = z
|
|
159
162
|
.strictObject({
|
|
163
|
+
cursor: Schema.Cursor,
|
|
164
|
+
limit: z
|
|
165
|
+
.pipe(
|
|
166
|
+
Schema.Limit,
|
|
167
|
+
z.transform((value) => Math.min(value, maxRequestPage)),
|
|
168
|
+
)
|
|
169
|
+
.check(
|
|
170
|
+
z.describe(`How many requests to return (5 maximum, default ${maxRequestPage}).`),
|
|
171
|
+
z.meta({ examples: [maxRequestPage] }),
|
|
172
|
+
),
|
|
160
173
|
status: z
|
|
161
174
|
._default(z.enum(['approved', 'denied', 'pending']), 'pending')
|
|
162
175
|
.check(z.describe('Review status to filter by.')),
|
|
@@ -167,8 +180,9 @@ export namespace schema {
|
|
|
167
180
|
export const Response = Schema.describe(
|
|
168
181
|
z.object({
|
|
169
182
|
data: z.array(Request).check(z.describe('Requests in the selected status, oldest first.')),
|
|
183
|
+
nextCursor: Schema.NextCursor,
|
|
170
184
|
}),
|
|
171
|
-
'
|
|
185
|
+
'A page of verified-token requests in review-queue order.',
|
|
172
186
|
)
|
|
173
187
|
}
|
|
174
188
|
|
|
@@ -558,14 +572,26 @@ export function verifiedTokens() {
|
|
|
558
572
|
}),
|
|
559
573
|
async (c) => {
|
|
560
574
|
const db = Db.get(c.get('db'))
|
|
561
|
-
const { status } = c.req.valid('query')
|
|
562
|
-
const
|
|
575
|
+
const { cursor, limit, status } = c.req.valid('query')
|
|
576
|
+
const decoded = cursor ? Cursor.decode(cursor, ['timestamp', 'id']) : undefined
|
|
577
|
+
const records = await VerifiedTokenRequests.listByStatus(db, status, {
|
|
578
|
+
...(decoded
|
|
579
|
+
? { cursor: { createdAt: String(decoded[0]), id: String(decoded[1]) } }
|
|
580
|
+
: {}),
|
|
581
|
+
limit: limit + 1,
|
|
582
|
+
})
|
|
583
|
+
const page = Cursor.paginate({
|
|
584
|
+
key: (record) => [record.createdAt, record.id],
|
|
585
|
+
limit,
|
|
586
|
+
rows: records,
|
|
587
|
+
})
|
|
563
588
|
return c.json(
|
|
564
589
|
Response.validated(schema.listVerifiedTokenRequests.Response, {
|
|
565
|
-
data:
|
|
590
|
+
data: page.rows.map((record) => ({
|
|
566
591
|
...VerifiedTokenRequests.toRequest(record),
|
|
567
592
|
orgName: record.orgName,
|
|
568
593
|
})),
|
|
594
|
+
nextCursor: page.nextCursor,
|
|
569
595
|
}),
|
|
570
596
|
)
|
|
571
597
|
},
|
|
@@ -76,26 +76,41 @@ export namespace create {
|
|
|
76
76
|
|
|
77
77
|
/** Review queue over organization verification requests. */
|
|
78
78
|
export namespace requests {
|
|
79
|
+
/** One page returned by the queue endpoint. */
|
|
80
|
+
export type Data = InferResponseType<(typeof client)['verified-tokens']['requests']['$get'], 200>
|
|
81
|
+
|
|
79
82
|
/** A verification request record from the queue endpoint. */
|
|
80
|
-
export type VerifiedTokenRequest =
|
|
81
|
-
(typeof client)['verified-tokens']['requests']['$get'],
|
|
82
|
-
200
|
|
83
|
-
>['data'][number]
|
|
83
|
+
export type VerifiedTokenRequest = Data['data'][number]
|
|
84
84
|
|
|
85
85
|
export namespace list {
|
|
86
86
|
export function key() {
|
|
87
87
|
return ['verified-token-requests'] as const
|
|
88
88
|
}
|
|
89
|
-
export async function fn() {
|
|
90
|
-
const response = await client['verified-tokens'].requests.$get({
|
|
89
|
+
export async function fn(options: fn.Options) {
|
|
90
|
+
const response = await client['verified-tokens'].requests.$get({
|
|
91
|
+
query: { ...options, limit: '5' },
|
|
92
|
+
})
|
|
91
93
|
if (!response.ok) throw new Error('Failed to load requests.')
|
|
92
|
-
return response.json()
|
|
94
|
+
return response.json() as Promise<Data>
|
|
95
|
+
}
|
|
96
|
+
export declare namespace fn {
|
|
97
|
+
/** Review-queue page selection. */
|
|
98
|
+
type Options = {
|
|
99
|
+
/** Cursor after the previous loaded page. */
|
|
100
|
+
cursor?: string | undefined
|
|
101
|
+
}
|
|
93
102
|
}
|
|
94
103
|
export function options() {
|
|
95
|
-
return ReactQuery.
|
|
104
|
+
return ReactQuery.infiniteQueryOptions({
|
|
105
|
+
getNextPageParam: (page: Data) => page.nextCursor ?? undefined,
|
|
106
|
+
initialPageParam: null as string | null,
|
|
107
|
+
queryFn: ({ pageParam }): Promise<Data> => fn({ cursor: pageParam ?? undefined }),
|
|
108
|
+
queryKey: key(),
|
|
109
|
+
staleTime: 30_000,
|
|
110
|
+
})
|
|
96
111
|
}
|
|
97
112
|
export function useQuery() {
|
|
98
|
-
return ReactQuery.
|
|
113
|
+
return ReactQuery.useInfiniteQuery(options())
|
|
99
114
|
}
|
|
100
115
|
}
|
|
101
116
|
|
|
@@ -111,7 +111,7 @@ function Admin() {
|
|
|
111
111
|
<div className="relative flex min-h-dvh min-w-[360px] bg-background max-md:h-dvh max-md:flex-col max-md:overflow-hidden">
|
|
112
112
|
<div className="flex-1 bg-background max-md:hidden" />
|
|
113
113
|
<Sidebar email={email} logoutUrl={logoutUrl} />
|
|
114
|
-
<main className="flex w-full max-w-[1024px] flex-col px-4 pt-5 pb-10 max-md:min-h-0 max-md:flex-1 max-md:overflow-y-auto sm:px-8">
|
|
114
|
+
<main className="flex min-w-0 w-full max-w-[1024px] flex-col px-4 pt-5 pb-10 max-md:min-h-0 max-md:flex-1 max-md:overflow-y-auto sm:px-8">
|
|
115
115
|
<Outlet />
|
|
116
116
|
</main>
|
|
117
117
|
<div className="flex-1 max-md:hidden" />
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
useNavigate,
|
|
7
7
|
useRouter,
|
|
8
8
|
} from '@tanstack/react-router'
|
|
9
|
-
import { useEffect, useState } from 'react'
|
|
9
|
+
import { useEffect, useRef, useState } from 'react'
|
|
10
10
|
import { Button, Input, Rows } from 'regen-ui'
|
|
11
11
|
import { ChevronRight, Search } from 'lucide-react'
|
|
12
12
|
|
|
@@ -27,20 +27,39 @@ function PageComponent() {
|
|
|
27
27
|
const { query } = Route.useLoaderDeps()
|
|
28
28
|
const navigate = useNavigate({ from: Route.fullPath })
|
|
29
29
|
const [value, setValue] = useState(query ?? '')
|
|
30
|
+
const debouncedValue = useDebouncedValue(value, 300)
|
|
31
|
+
const submittedValue = useRef(query ?? '')
|
|
30
32
|
const organizations = useSuspenseInfiniteQuery(Organizations.list.options({ query }))
|
|
31
33
|
const records = organizations.data.pages.flatMap((page) => page.data)
|
|
32
34
|
const invalid = value.trim().length > 0 && value.trim().length < 3
|
|
33
35
|
|
|
34
|
-
useEffect(() =>
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (value.trim() !== submittedValue.current) return
|
|
38
|
+
const next = query ?? ''
|
|
39
|
+
submittedValue.current = next
|
|
40
|
+
setValue(next)
|
|
41
|
+
}, [query, value])
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
const next = debouncedValue.trim()
|
|
44
|
+
if (value.trim() !== next || (next.length > 0 && next.length < 3) || next === (query ?? ''))
|
|
45
|
+
return
|
|
46
|
+
submittedValue.current = next
|
|
47
|
+
void navigate({
|
|
48
|
+
replace: true,
|
|
49
|
+
search: Organizations.searchParameters({ query: next }),
|
|
50
|
+
to: '/organizations',
|
|
51
|
+
})
|
|
52
|
+
}, [debouncedValue, navigate, query, value])
|
|
35
53
|
|
|
36
54
|
return (
|
|
37
55
|
<Page title="Organizations" description="Look up organizations by id, name, or member email">
|
|
38
56
|
<form
|
|
39
|
-
className="flex gap-2"
|
|
57
|
+
className="flex min-w-0 gap-2"
|
|
40
58
|
onSubmit={(event) => {
|
|
41
59
|
event.preventDefault()
|
|
42
60
|
const next = value.trim()
|
|
43
61
|
if (invalid) return
|
|
62
|
+
submittedValue.current = next
|
|
44
63
|
void navigate({
|
|
45
64
|
search: Organizations.searchParameters({ query: next }),
|
|
46
65
|
to: '/organizations',
|
|
@@ -56,7 +75,7 @@ function PageComponent() {
|
|
|
56
75
|
value={value}
|
|
57
76
|
/>
|
|
58
77
|
</div>
|
|
59
|
-
<Button disabled={invalid} type="submit">
|
|
78
|
+
<Button className="shrink-0" disabled={invalid} type="submit">
|
|
60
79
|
<Search className="size-4" />
|
|
61
80
|
Search
|
|
62
81
|
</Button>
|
|
@@ -127,6 +146,17 @@ function PageComponent() {
|
|
|
127
146
|
)
|
|
128
147
|
}
|
|
129
148
|
|
|
149
|
+
function useDebouncedValue(value: string, delay: number) {
|
|
150
|
+
const [debouncedValue, setDebouncedValue] = useState(value)
|
|
151
|
+
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
const timeout = setTimeout(() => setDebouncedValue(value), delay)
|
|
154
|
+
return () => clearTimeout(timeout)
|
|
155
|
+
}, [delay, value])
|
|
156
|
+
|
|
157
|
+
return debouncedValue
|
|
158
|
+
}
|
|
159
|
+
|
|
130
160
|
function LookupError(options: LookupError.Props) {
|
|
131
161
|
const router = useRouter()
|
|
132
162
|
return (
|
|
@@ -22,7 +22,7 @@ export const Route = createFileRoute('/verified-tokens')({
|
|
|
22
22
|
loader: ({ context }) =>
|
|
23
23
|
Promise.all([
|
|
24
24
|
context.queryClient.prefetchQuery(VerifiedTokens.get.options({ chainId: defaultChain })),
|
|
25
|
-
context.queryClient.
|
|
25
|
+
context.queryClient.prefetchInfiniteQuery(VerifiedTokens.requests.list.options()),
|
|
26
26
|
]),
|
|
27
27
|
component: RouteComponent,
|
|
28
28
|
})
|
|
@@ -47,7 +47,10 @@ function RouteComponent() {
|
|
|
47
47
|
const tokens = VerifiedTokens.get.useQuery({ chainId: chain })
|
|
48
48
|
const requests = VerifiedTokens.requests.list.useQuery()
|
|
49
49
|
const create = VerifiedTokens.create.useMutation()
|
|
50
|
-
const
|
|
50
|
+
const pending =
|
|
51
|
+
requests.data?.pages.flatMap((page: VerifiedTokens.requests.Data) => page.data) ?? []
|
|
52
|
+
const pendingCount = pending.length
|
|
53
|
+
const pendingLabel = requests.hasNextPage ? `${pendingCount}+` : String(pendingCount)
|
|
51
54
|
|
|
52
55
|
return (
|
|
53
56
|
<Dialog.Root onOpenChange={setOpen} open={open}>
|
|
@@ -75,7 +78,7 @@ function RouteComponent() {
|
|
|
75
78
|
onChange={(id) => void navigate({ search: id === 'verified' ? {} : { tab: id } })}
|
|
76
79
|
tabs={[
|
|
77
80
|
{ id: 'verified', label: 'Verified' },
|
|
78
|
-
{ id: 'pending', label: pendingCount > 0 ? `Pending (${
|
|
81
|
+
{ id: 'pending', label: pendingCount > 0 ? `Pending (${pendingLabel})` : 'Pending' },
|
|
79
82
|
]}
|
|
80
83
|
/>
|
|
81
84
|
{tab === 'verified' ? (
|
|
@@ -87,9 +90,13 @@ function RouteComponent() {
|
|
|
87
90
|
/>
|
|
88
91
|
) : (
|
|
89
92
|
<RequestQueue
|
|
90
|
-
error={requests.error}
|
|
93
|
+
error={requests.isLoadingError ? requests.error : null}
|
|
94
|
+
hasNextPage={requests.hasNextPage}
|
|
95
|
+
loadMoreError={requests.isFetchNextPageError ? requests.error : null}
|
|
91
96
|
loading={requests.isPending}
|
|
92
|
-
|
|
97
|
+
loadingMore={requests.isFetchingNextPage}
|
|
98
|
+
onLoadMore={() => requests.fetchNextPage()}
|
|
99
|
+
requests={pending}
|
|
93
100
|
/>
|
|
94
101
|
)}
|
|
95
102
|
<Dialog.Popup>
|
|
@@ -115,11 +122,7 @@ function chainLabel(chainId: number) {
|
|
|
115
122
|
}
|
|
116
123
|
|
|
117
124
|
/** 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
|
-
}) {
|
|
125
|
+
function RequestQueue(props: RequestQueue.Props) {
|
|
123
126
|
const approve = VerifiedTokens.requests.approve.useMutation()
|
|
124
127
|
const deny = VerifiedTokens.requests.deny.useMutation()
|
|
125
128
|
const [selected, setSelected] = useState<ReadonlySet<string>>(new Set())
|
|
@@ -197,21 +200,62 @@ function RequestQueue(props: {
|
|
|
197
200
|
) : props.requests.length === 0 ? (
|
|
198
201
|
<Notice>No pending requests.</Notice>
|
|
199
202
|
) : (
|
|
200
|
-
<
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
203
|
+
<div className="flex flex-col gap-4 pb-4">
|
|
204
|
+
<ul className="divide-y divide-border">
|
|
205
|
+
{props.requests.map((request) => (
|
|
206
|
+
<RequestRow
|
|
207
|
+
key={request.id}
|
|
208
|
+
onToggle={() => toggle(request.id)}
|
|
209
|
+
request={request}
|
|
210
|
+
selected={selected.has(request.id)}
|
|
211
|
+
/>
|
|
212
|
+
))}
|
|
213
|
+
</ul>
|
|
214
|
+
{props.loadMoreError ? (
|
|
215
|
+
<div
|
|
216
|
+
className="flex items-center justify-center gap-3 text-copy-14 text-negative"
|
|
217
|
+
role="alert"
|
|
218
|
+
>
|
|
219
|
+
<span>{props.loadMoreError.message}</span>
|
|
220
|
+
<Button loading={props.loadingMore} onClick={() => void props.onLoadMore()}>
|
|
221
|
+
Retry
|
|
222
|
+
</Button>
|
|
223
|
+
</div>
|
|
224
|
+
) : props.hasNextPage ? (
|
|
225
|
+
<Button
|
|
226
|
+
className="self-center"
|
|
227
|
+
loading={props.loadingMore}
|
|
228
|
+
onClick={() => void props.onLoadMore()}
|
|
229
|
+
>
|
|
230
|
+
Load more
|
|
231
|
+
</Button>
|
|
232
|
+
) : null}
|
|
233
|
+
</div>
|
|
210
234
|
)}
|
|
211
235
|
</Card>
|
|
212
236
|
)
|
|
213
237
|
}
|
|
214
238
|
|
|
239
|
+
declare namespace RequestQueue {
|
|
240
|
+
/** Pending queue display state and pagination controls. */
|
|
241
|
+
type Props = {
|
|
242
|
+
/** Queue load error. */
|
|
243
|
+
error: Error | null
|
|
244
|
+
/** Whether another queue page is available. */
|
|
245
|
+
hasNextPage: boolean
|
|
246
|
+
/** Whether the first page is loading. */
|
|
247
|
+
loading: boolean
|
|
248
|
+
/** Error returned while loading another page. */
|
|
249
|
+
loadMoreError: Error | null
|
|
250
|
+
/** Whether another page is loading. */
|
|
251
|
+
loadingMore: boolean
|
|
252
|
+
/** Loads the next queue page. */
|
|
253
|
+
onLoadMore: () => Promise<unknown>
|
|
254
|
+
/** Loaded requests in review order. */
|
|
255
|
+
requests: readonly VerifiedTokens.requests.VerifiedTokenRequest[]
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
215
259
|
/** Confirmation label: one token by name, several by count. */
|
|
216
260
|
function describeSelection(requests: readonly VerifiedTokens.requests.VerifiedTokenRequest[]) {
|
|
217
261
|
const [first] = requests
|