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.
- package/dist/App.d.ts +13987 -5755
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js.map +1 -1
- package/dist/admin/App.d.ts +910 -412
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/App.js +4 -0
- package/dist/admin/App.js.map +1 -1
- package/dist/admin/apps/early-access.d.ts +148 -0
- package/dist/admin/apps/early-access.d.ts.map +1 -0
- package/dist/admin/apps/early-access.js +133 -0
- package/dist/admin/apps/early-access.js.map +1 -0
- package/dist/admin/apps/verified-tokens.d.ts +643 -118
- package/dist/admin/apps/verified-tokens.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.js +214 -1
- 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 +45 -33
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/apps/data/App.d.ts +3733 -2428
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +11 -6
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/FundingQuoteProviders.d.ts +34 -1
- package/dist/apps/data/FundingQuoteProviders.d.ts.map +1 -1
- package/dist/apps/data/FundingQuoteProviders.js +193 -2
- package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
- package/dist/apps/data/FxOracle.d.ts +71 -0
- package/dist/apps/data/FxOracle.d.ts.map +1 -0
- package/dist/apps/data/FxOracle.js +97 -0
- package/dist/apps/data/FxOracle.js.map +1 -0
- package/dist/apps/data/index.d.ts +1 -0
- package/dist/apps/data/index.d.ts.map +1 -1
- package/dist/apps/data/index.js +1 -0
- package/dist/apps/data/index.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +3575 -1358
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +230 -88
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/addresses.d.ts +963 -342
- package/dist/apps/data/routes/addresses.d.ts.map +1 -1
- package/dist/apps/data/routes/addresses.js +11 -9
- package/dist/apps/data/routes/addresses.js.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +108 -57
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +88 -45
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/coingecko.d.ts +169 -169
- package/dist/apps/data/routes/exchanges.d.ts +952 -201
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
- package/dist/apps/data/routes/exchanges.js +518 -76
- package/dist/apps/data/routes/exchanges.js.map +1 -1
- package/dist/apps/data/routes/fee-amm.d.ts +24 -24
- package/dist/apps/data/routes/funding.d.ts +8 -2
- package/dist/apps/data/routes/funding.d.ts.map +1 -1
- package/dist/apps/data/routes/funding.js +32 -6
- package/dist/apps/data/routes/funding.js.map +1 -1
- package/dist/apps/data/routes/indexer.d.ts +5 -5
- package/dist/apps/data/routes/receipts.d.ts +392 -315
- package/dist/apps/data/routes/receipts.d.ts.map +1 -1
- package/dist/apps/data/routes/receipts.js +152 -77
- package/dist/apps/data/routes/receipts.js.map +1 -1
- package/dist/apps/data/routes/tokens.d.ts +166 -173
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/tokens.js +14 -12
- package/dist/apps/data/routes/tokens.js.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +277 -536
- package/dist/apps/data/routes/transactions.d.ts.map +1 -1
- package/dist/apps/data/routes/transactions.js +96 -77
- package/dist/apps/data/routes/transactions.js.map +1 -1
- package/dist/apps/data/routes/transfers.d.ts +82 -17
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +70 -13
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/valuation.d.ts +307 -0
- package/dist/apps/data/routes/valuation.d.ts.map +1 -0
- package/dist/apps/data/routes/valuation.js +292 -0
- package/dist/apps/data/routes/valuation.js.map +1 -0
- package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/verified-tokens.js +7 -5
- package/dist/apps/data/routes/verified-tokens.js.map +1 -1
- package/dist/apps/data/routes/webhooks.d.ts +459 -479
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +124 -25
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/index.d.ts +1 -1
- package/dist/apps/index.d.ts.map +1 -1
- package/dist/apps/index.js +1 -1
- package/dist/apps/index.js.map +1 -1
- package/dist/apps/management/App.d.ts +8606 -1681
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/App.js +12 -1
- package/dist/apps/management/App.js.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +23 -0
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +5 -3
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +88 -48
- package/dist/apps/management/routes/billing.d.ts.map +1 -1
- package/dist/apps/management/routes/billing.js +10 -2
- package/dist/apps/management/routes/billing.js.map +1 -1
- package/dist/apps/management/routes/faucet.d.ts +218 -0
- package/dist/apps/management/routes/faucet.d.ts.map +1 -0
- package/dist/apps/management/routes/faucet.js +139 -0
- package/dist/apps/management/routes/faucet.js.map +1 -0
- package/dist/apps/management/routes/invitations.d.ts +121 -121
- package/dist/apps/management/routes/invitations.d.ts.map +1 -1
- package/dist/apps/management/routes/invitations.js +3 -0
- package/dist/apps/management/routes/invitations.js.map +1 -1
- package/dist/apps/management/routes/invite-links.d.ts +197 -168
- package/dist/apps/management/routes/invite-links.d.ts.map +1 -1
- package/dist/apps/management/routes/invite-links.js +26 -1
- package/dist/apps/management/routes/invite-links.js.map +1 -1
- package/dist/apps/management/routes/me.d.ts +2 -0
- package/dist/apps/management/routes/me.d.ts.map +1 -1
- package/dist/apps/management/routes/me.js +4 -0
- package/dist/apps/management/routes/me.js.map +1 -1
- package/dist/apps/management/routes/members.d.ts +49 -49
- package/dist/apps/management/routes/orgs.d.ts +91 -91
- package/dist/apps/management/routes/projects.d.ts +115 -115
- package/dist/apps/management/routes/usage.d.ts +35 -32
- package/dist/apps/management/routes/usage.d.ts.map +1 -1
- package/dist/apps/management/routes/usage.js +4 -0
- package/dist/apps/management/routes/usage.js.map +1 -1
- package/dist/apps/management/routes/verified-token-requests.d.ts +697 -0
- package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -0
- package/dist/apps/management/routes/verified-token-requests.js +271 -0
- package/dist/apps/management/routes/verified-token-requests.js.map +1 -0
- package/dist/apps/management/routes/webhooks.d.ts +6977 -0
- package/dist/apps/management/routes/webhooks.d.ts.map +1 -0
- package/dist/apps/management/routes/webhooks.js +573 -0
- package/dist/apps/management/routes/webhooks.js.map +1 -0
- package/dist/apps/mcp.d.ts +9 -2
- package/dist/apps/mcp.d.ts.map +1 -1
- package/dist/apps/mcp.js +9 -4
- package/dist/apps/mcp.js.map +1 -1
- package/dist/cli/Cli.d.ts.map +1 -1
- package/dist/cli/Cli.js +7 -2
- package/dist/cli/Cli.js.map +1 -1
- package/dist/db/Db.d.ts +4 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +83 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +41 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +45 -0
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/earlyAccess.d.ts +50 -0
- package/dist/db/tables/earlyAccess.d.ts.map +1 -0
- package/dist/db/tables/earlyAccess.js +63 -0
- package/dist/db/tables/earlyAccess.js.map +1 -0
- package/dist/db/tables/invitations.d.ts +12 -1
- package/dist/db/tables/invitations.d.ts.map +1 -1
- package/dist/db/tables/invitations.js +23 -0
- package/dist/db/tables/invitations.js.map +1 -1
- package/dist/db/tables/inviteLinks.d.ts +10 -2
- package/dist/db/tables/inviteLinks.d.ts.map +1 -1
- package/dist/db/tables/inviteLinks.js +10 -1
- package/dist/db/tables/inviteLinks.js.map +1 -1
- package/dist/db/tables/sponsoredTransactions.d.ts +2 -0
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
- package/dist/db/tables/sponsoredTransactions.js +2 -0
- package/dist/db/tables/sponsoredTransactions.js.map +1 -1
- package/dist/db/tables/verifiedTokenRequests.d.ts +94 -0
- package/dist/db/tables/verifiedTokenRequests.d.ts.map +1 -0
- package/dist/db/tables/verifiedTokenRequests.js +120 -0
- package/dist/db/tables/verifiedTokenRequests.js.map +1 -0
- package/dist/db/tables/webhookSubscriptions.d.ts +14 -0
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
- package/dist/db/tables/webhookSubscriptions.js +16 -0
- package/dist/db/tables/webhookSubscriptions.js.map +1 -1
- package/dist/internal/Auth.d.ts +43 -2
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +60 -45
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/FundingQuotes.d.ts +69 -15
- package/dist/internal/FundingQuotes.d.ts.map +1 -1
- package/dist/internal/FundingQuotes.js +74 -25
- package/dist/internal/FundingQuotes.js.map +1 -1
- package/dist/internal/Log.d.ts +19 -1
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +2 -0
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +9 -0
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/OpenApi.d.ts +2 -2
- package/dist/internal/OpenApi.d.ts.map +1 -1
- package/dist/internal/OpenApi.js +3 -0
- package/dist/internal/OpenApi.js.map +1 -1
- package/dist/internal/Schema.d.ts +14 -5
- package/dist/internal/Schema.d.ts.map +1 -1
- package/dist/internal/Schema.js +23 -5
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Value.d.ts +18 -0
- package/dist/internal/Value.d.ts.map +1 -1
- package/dist/internal/Value.js +11 -1
- package/dist/internal/Value.js.map +1 -1
- package/dist/internal/VerifiedTokens.d.ts.map +1 -1
- package/dist/internal/VerifiedTokens.js +10 -1
- package/dist/internal/VerifiedTokens.js.map +1 -1
- package/dist/internal/Viem.d.ts +1 -1
- package/dist/internal/Webhooks.d.ts +24 -3
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +34 -8
- package/dist/internal/Webhooks.js.map +1 -1
- package/package.json +2 -2
- package/src/App.ts +9 -1
- package/src/Client.test-d.ts +638 -6
- package/src/admin/App.ts +4 -0
- package/src/admin/apps/early-access.test.ts +125 -0
- package/src/admin/apps/early-access.ts +175 -0
- package/src/admin/apps/verified-tokens.test.ts +201 -0
- package/src/admin/apps/verified-tokens.ts +532 -286
- package/src/admin/ui/plugin.ts +2 -1
- package/src/admin/ui/src/lib/early-access.ts +63 -0
- package/src/admin/ui/src/lib/verified-tokens.ts +79 -0
- package/src/admin/ui/src/routeTree.gen.ts +33 -2
- package/src/admin/ui/src/routes/__root.tsx +2 -1
- package/src/admin/ui/src/routes/early-access.tsx +157 -0
- package/src/admin/ui/src/routes/verified-tokens.tsx +230 -19
- package/src/admin/ui.generated.ts +48 -33
- package/src/apps/data/App.ts +22 -14
- package/src/apps/data/FundingQuoteProviders.test.ts +298 -0
- package/src/apps/data/FundingQuoteProviders.ts +257 -4
- package/src/apps/data/FxOracle.test.ts +118 -0
- package/src/apps/data/FxOracle.ts +153 -0
- package/src/apps/data/index.ts +1 -0
- package/src/apps/data/routes/activities.test.ts +173 -3
- package/src/apps/data/routes/activities.ts +403 -132
- package/src/apps/data/routes/addresses.ts +19 -9
- package/src/apps/data/routes/balances.test.ts +144 -11
- package/src/apps/data/routes/balances.ts +140 -51
- package/src/apps/data/routes/exchanges.test.ts +540 -9
- package/src/apps/data/routes/exchanges.ts +690 -103
- package/src/apps/data/routes/funding.test.ts +84 -0
- package/src/apps/data/routes/funding.ts +38 -6
- package/src/apps/data/routes/receipts.test.ts +125 -23
- package/src/apps/data/routes/receipts.ts +205 -100
- package/src/apps/data/routes/tokens.test.ts +43 -0
- package/src/apps/data/routes/tokens.ts +27 -12
- package/src/apps/data/routes/transactions.test.ts +72 -19
- package/src/apps/data/routes/transactions.ts +162 -92
- package/src/apps/data/routes/transfers.test.ts +146 -4
- package/src/apps/data/routes/transfers.ts +91 -13
- package/src/apps/data/routes/valuation.test.ts +422 -0
- package/src/apps/data/routes/valuation.ts +423 -0
- package/src/apps/data/routes/verified-tokens.ts +14 -2
- package/src/apps/data/routes/webhooks.test.ts +56 -8
- package/src/apps/data/routes/webhooks.ts +144 -27
- package/src/apps/index.ts +1 -1
- package/src/apps/management/App.ts +13 -0
- package/src/apps/management/routes/api-keys.test.ts +87 -0
- package/src/apps/management/routes/api-keys.ts +4 -3
- package/src/apps/management/routes/billing.test.ts +112 -1
- package/src/apps/management/routes/billing.ts +7 -2
- package/src/apps/management/routes/faucet.test.ts +79 -0
- package/src/apps/management/routes/faucet.ts +185 -0
- package/src/apps/management/routes/invitations.test.ts +172 -0
- package/src/apps/management/routes/invitations.ts +3 -0
- package/src/apps/management/routes/invite-links.test.ts +117 -0
- package/src/apps/management/routes/invite-links.ts +43 -1
- package/src/apps/management/routes/me.test.ts +140 -88
- package/src/apps/management/routes/me.ts +6 -0
- package/src/apps/management/routes/usage.test.ts +6 -0
- package/src/apps/management/routes/usage.ts +7 -0
- package/src/apps/management/routes/verified-token-requests.test.ts +377 -0
- package/src/apps/management/routes/verified-token-requests.ts +397 -0
- package/src/apps/management/routes/webhooks.test.ts +426 -0
- package/src/apps/management/routes/webhooks.ts +672 -0
- package/src/apps/mcp.test.ts +13 -0
- package/src/apps/mcp.ts +18 -5
- package/src/cli/Cli.test.ts +36 -0
- package/src/cli/Cli.ts +7 -2
- package/src/db/Db.test.ts +19 -0
- package/src/db/Db.ts +95 -0
- package/src/db/Schema.ts +53 -0
- package/src/db/tables/earlyAccess.ts +86 -0
- package/src/db/tables/invitations.ts +26 -1
- package/src/db/tables/inviteLinks.ts +22 -3
- package/src/db/tables/sponsoredTransactions.ts +4 -0
- package/src/db/tables/verifiedTokenRequests.test.ts +200 -0
- package/src/db/tables/verifiedTokenRequests.ts +182 -0
- package/src/db/tables/webhookSubscriptions.ts +31 -0
- package/src/internal/Auth.ts +73 -45
- package/src/internal/FundingQuotes.test.ts +143 -1
- package/src/internal/FundingQuotes.ts +95 -28
- package/src/internal/Log.test-d.ts +21 -0
- package/src/internal/Log.test.ts +50 -0
- package/src/internal/Log.ts +24 -1
- package/src/internal/MetricSink.test.ts +160 -0
- package/src/internal/MetricSink.ts +10 -0
- package/src/internal/OpenApi.ts +3 -0
- package/src/internal/Schema.ts +50 -5
- package/src/internal/Value.test.ts +20 -0
- package/src/internal/Value.ts +24 -1
- package/src/internal/VerifiedTokens.ts +9 -1
- package/src/internal/Webhooks.test.ts +140 -0
- package/src/internal/Webhooks.ts +53 -9
package/src/admin/App.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type * as Store from '../internal/Store.js'
|
|
|
11
11
|
import * as Response from '../internal/Response.js'
|
|
12
12
|
|
|
13
13
|
import { apiKeys } from './apps/api-keys.js'
|
|
14
|
+
import { earlyAccess } from './apps/early-access.js'
|
|
14
15
|
import { organizations } from './apps/organizations.js'
|
|
15
16
|
import { scopes } from './apps/scopes.js'
|
|
16
17
|
import { verifiedTokens } from './apps/verified-tokens.js'
|
|
@@ -117,6 +118,8 @@ export function create(options: create.Options) {
|
|
|
117
118
|
'/config.json',
|
|
118
119
|
'/api-keys',
|
|
119
120
|
'/api-keys/*',
|
|
121
|
+
'/early-access',
|
|
122
|
+
'/early-access/*',
|
|
120
123
|
'/organizations',
|
|
121
124
|
'/organizations/*',
|
|
122
125
|
'/scopes',
|
|
@@ -152,6 +155,7 @@ export function create(options: create.Options) {
|
|
|
152
155
|
const routed = app
|
|
153
156
|
.get('/config.json', (c) => c.json(authConfig(c.get('identity')), 200))
|
|
154
157
|
.route('/api-keys', apiKeys())
|
|
158
|
+
.route('/early-access', earlyAccess())
|
|
155
159
|
.route('/organizations', organizations())
|
|
156
160
|
.route('/scopes', scopes())
|
|
157
161
|
.route('/verified-tokens', verifiedTokens())
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import * as TestAdmin from '../../../test/Admin.js'
|
|
2
|
+
import * as TestApp from '../../../test/App.js'
|
|
3
|
+
import * as EarlyAccessApp from './early-access.js'
|
|
4
|
+
|
|
5
|
+
/** Lists the table's entries. */
|
|
6
|
+
async function listAdded(client: ReturnType<typeof TestAdmin.setup>['client']) {
|
|
7
|
+
const response = await client['early-access'].$get()
|
|
8
|
+
const body = await TestApp.json(response, EarlyAccessApp.schema.listEarlyAccessEntries.Response)
|
|
9
|
+
return body.data
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
describe('authentication', () => {
|
|
13
|
+
test('denied identity → 401 on every route', async () => {
|
|
14
|
+
const { app } = TestAdmin.setup({ identify: () => null })
|
|
15
|
+
const routes = [
|
|
16
|
+
new Request('http://admin/early-access'),
|
|
17
|
+
new Request('http://admin/early-access', {
|
|
18
|
+
body: JSON.stringify({ entry: 'example.org' }),
|
|
19
|
+
headers: { 'content-type': 'application/json' },
|
|
20
|
+
method: 'POST',
|
|
21
|
+
}),
|
|
22
|
+
new Request('http://admin/early-access', {
|
|
23
|
+
body: JSON.stringify({ entry: 'example.org' }),
|
|
24
|
+
headers: { 'content-type': 'application/json' },
|
|
25
|
+
method: 'DELETE',
|
|
26
|
+
}),
|
|
27
|
+
]
|
|
28
|
+
for (const request of routes) {
|
|
29
|
+
const response = await app.request(request)
|
|
30
|
+
expect(response.status).toBe(401)
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
describe('GET /early-access', () => {
|
|
36
|
+
test('starts empty; entries are seeded through the dash, never migrations', async () => {
|
|
37
|
+
const { client } = TestAdmin.setup()
|
|
38
|
+
const response = await client['early-access'].$get()
|
|
39
|
+
const body = await TestApp.json(response, EarlyAccessApp.schema.listEarlyAccessEntries.Response)
|
|
40
|
+
|
|
41
|
+
expect(response.status).toBe(200)
|
|
42
|
+
expect(body.data).toEqual([])
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
describe('POST /early-access', () => {
|
|
47
|
+
test('add → list → remove round-trip', async () => {
|
|
48
|
+
const { client } = TestAdmin.setup()
|
|
49
|
+
|
|
50
|
+
const created = await client['early-access'].$post({ json: { entry: 'a/b@example.org' } })
|
|
51
|
+
expect(created.status).toBe(200)
|
|
52
|
+
const body = await TestApp.json(created, EarlyAccessApp.schema.createEarlyAccessEntry.Response)
|
|
53
|
+
expect(body.data.entry).toBe('a/b@example.org')
|
|
54
|
+
// createdBy is the verified admin email, never client-supplied.
|
|
55
|
+
expect(body.data.createdBy).toBe(TestAdmin.identity.email)
|
|
56
|
+
|
|
57
|
+
expect((await listAdded(client)).map((record) => record.entry)).toEqual(['a/b@example.org'])
|
|
58
|
+
|
|
59
|
+
// Reserved URL characters stay in the JSON body.
|
|
60
|
+
const removed = await client['early-access'].$delete({ json: { entry: 'a/b@example.org' } })
|
|
61
|
+
expect(removed.status).toBe(200)
|
|
62
|
+
expect(await listAdded(client)).toEqual([])
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('normalizes the entry lowercase and trimmed', async () => {
|
|
66
|
+
const { client } = TestAdmin.setup()
|
|
67
|
+
const response = await client['early-access'].$post({ json: { entry: ' User@Example.ORG ' } })
|
|
68
|
+
const body = await TestApp.json(response, EarlyAccessApp.schema.createEarlyAccessEntry.Response)
|
|
69
|
+
|
|
70
|
+
expect(response.status).toBe(200)
|
|
71
|
+
expect(body.data.entry).toBe('user@example.org')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('rejects an invalid entry with 400', async () => {
|
|
75
|
+
const { client } = TestAdmin.setup()
|
|
76
|
+
for (const entry of ['', ' ', 'no spaces.org x', 'not-a-domain', '@example.org', 'user@']) {
|
|
77
|
+
const response = await client['early-access'].$post({ json: { entry } })
|
|
78
|
+
const body = (await response.json()) as { error?: { code?: string } }
|
|
79
|
+
expect(response.status).toBe(400)
|
|
80
|
+
expect(body.error?.code).toBe('body_invalid')
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test('rejects a duplicate entry with 409', async () => {
|
|
85
|
+
const { client } = TestAdmin.setup()
|
|
86
|
+
await client['early-access'].$post({ json: { entry: 'example.org' } })
|
|
87
|
+
const duplicate = await client['early-access'].$post({ json: { entry: 'Example.org' } })
|
|
88
|
+
const body = (await duplicate.json()) as { error?: { code?: string } }
|
|
89
|
+
|
|
90
|
+
expect(duplicate.status).toBe(409)
|
|
91
|
+
expect(body.error?.code).toBe('entry_exists')
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
describe('DELETE /early-access', () => {
|
|
96
|
+
test('removing an unknown entry → 404', async () => {
|
|
97
|
+
const { client } = TestAdmin.setup()
|
|
98
|
+
const response = await client['early-access'].$delete({
|
|
99
|
+
json: { entry: 'missing.example.org' },
|
|
100
|
+
})
|
|
101
|
+
const body = (await response.json()) as { error?: { code?: string } }
|
|
102
|
+
|
|
103
|
+
expect(response.status).toBe(404)
|
|
104
|
+
expect(body.error?.code).toBe('entry_not_found')
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
describe('audit logging', () => {
|
|
109
|
+
test('writes an audit row for allowlist mutations', async () => {
|
|
110
|
+
const { client, db } = TestAdmin.setup()
|
|
111
|
+
await client['early-access'].$post({ json: { entry: 'example.org' } })
|
|
112
|
+
const records = await db.kysely
|
|
113
|
+
.selectFrom('admin_audit_logs')
|
|
114
|
+
.selectAll()
|
|
115
|
+
.orderBy('createdAt', 'desc')
|
|
116
|
+
.execute()
|
|
117
|
+
|
|
118
|
+
expect(records[0]).toMatchObject({
|
|
119
|
+
actor: TestAdmin.identity.email,
|
|
120
|
+
method: 'POST',
|
|
121
|
+
path: '/early-access',
|
|
122
|
+
status: 200,
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
})
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { Hono } from 'hono'
|
|
2
|
+
import * as z from 'zod/mini'
|
|
3
|
+
|
|
4
|
+
import * as Db from '../../db/Db.js'
|
|
5
|
+
import * as db_Schema from '../../db/Schema.js'
|
|
6
|
+
import * as EarlyAccess from '../../db/tables/earlyAccess.js'
|
|
7
|
+
import * as OpenApi from '../../internal/OpenApi.js'
|
|
8
|
+
import * as Response from '../../internal/Response.js'
|
|
9
|
+
import * as Schema from '../../internal/Schema.js'
|
|
10
|
+
import type * as App from '../App.js'
|
|
11
|
+
|
|
12
|
+
// A bare domain (`example.org`) or a full email (`user@example.org`), matched
|
|
13
|
+
// after the trim/lowercase transforms below.
|
|
14
|
+
const entryPattern =
|
|
15
|
+
/^(?:[^\s@]+@)?[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$/
|
|
16
|
+
|
|
17
|
+
/** Zod schemas owned by the admin early-access handler. */
|
|
18
|
+
export namespace schema {
|
|
19
|
+
/** A stored early-access allowlist entry (the `early_access` row shape). */
|
|
20
|
+
export const Entry = Schema.describe(
|
|
21
|
+
db_Schema.EarlyAccessEntry,
|
|
22
|
+
'A stored early-access allowlist entry.',
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
/** Schemas for the listEarlyAccessEntries operation. */
|
|
26
|
+
export namespace listEarlyAccessEntries {
|
|
27
|
+
/** Response body: all allowlist entries. */
|
|
28
|
+
export const Response = Schema.describe(
|
|
29
|
+
z.object({
|
|
30
|
+
data: z.array(Entry).check(z.describe('Allowlist entries, ordered by entry.')),
|
|
31
|
+
}),
|
|
32
|
+
'Early-access allowlist entries.',
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Schemas for the createEarlyAccessEntry operation. */
|
|
37
|
+
export namespace createEarlyAccessEntry {
|
|
38
|
+
/** Request body for adding an allowlist entry. */
|
|
39
|
+
export const Body = z
|
|
40
|
+
.object({
|
|
41
|
+
entry: z.string().check(
|
|
42
|
+
z.trim(),
|
|
43
|
+
z.toLowerCase(),
|
|
44
|
+
z.regex(entryPattern, 'Must be a bare domain (example.org) or a full email (user@example.org).'), // prettier-ignore
|
|
45
|
+
z.describe('Domain or exact email to grant early access; normalized lowercase.'),
|
|
46
|
+
),
|
|
47
|
+
})
|
|
48
|
+
.check(z.describe('Request body for adding an allowlist entry.'))
|
|
49
|
+
|
|
50
|
+
/** Response body: the stored entry. */
|
|
51
|
+
export const Response = Schema.describe(
|
|
52
|
+
z.object({ data: Entry }),
|
|
53
|
+
'The stored allowlist entry.',
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Schemas for the deleteEarlyAccessEntry operation. */
|
|
58
|
+
export namespace deleteEarlyAccessEntry {
|
|
59
|
+
/** Request body addressing one allowlist entry. */
|
|
60
|
+
export const Body = z
|
|
61
|
+
.object({
|
|
62
|
+
entry: z.string().check(
|
|
63
|
+
z.trim(),
|
|
64
|
+
z.toLowerCase(),
|
|
65
|
+
z.regex(entryPattern, 'Must be a bare domain (example.org) or a full email (user@example.org).'), // prettier-ignore
|
|
66
|
+
z.describe('The allowlist entry to remove; normalized lowercase.'),
|
|
67
|
+
),
|
|
68
|
+
})
|
|
69
|
+
.check(z.describe('Request body for removing an allowlist entry.'))
|
|
70
|
+
|
|
71
|
+
/** Response body: the removed entry. */
|
|
72
|
+
export const Response = Schema.describe(
|
|
73
|
+
z.object({ data: Entry }),
|
|
74
|
+
'The removed allowlist entry.',
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Management routes for the temporary early-access allowlist (`early_access`
|
|
81
|
+
* table): entries are lowercase domains or exact emails.
|
|
82
|
+
*
|
|
83
|
+
* - `GET /` lists entries.
|
|
84
|
+
* - `POST /` adds an entry (`409` on a duplicate).
|
|
85
|
+
* - `DELETE /` removes an entry (`404` if absent).
|
|
86
|
+
*/
|
|
87
|
+
export function earlyAccess() {
|
|
88
|
+
return new Hono<App.Environment>()
|
|
89
|
+
.get(
|
|
90
|
+
'/',
|
|
91
|
+
OpenApi.describeRoute({
|
|
92
|
+
operationId: 'listEarlyAccessEntries',
|
|
93
|
+
responses: OpenApi.responses({
|
|
94
|
+
success: {
|
|
95
|
+
description: 'Early-access allowlist entries.',
|
|
96
|
+
schema: schema.listEarlyAccessEntries.Response,
|
|
97
|
+
},
|
|
98
|
+
}),
|
|
99
|
+
summary: 'List early access',
|
|
100
|
+
tags: ['Early Access'],
|
|
101
|
+
}),
|
|
102
|
+
async (c) => {
|
|
103
|
+
const db = Db.get(c.get('db'))
|
|
104
|
+
return c.json(
|
|
105
|
+
Response.validated(schema.listEarlyAccessEntries.Response, {
|
|
106
|
+
data: await EarlyAccess.list(db),
|
|
107
|
+
}),
|
|
108
|
+
)
|
|
109
|
+
},
|
|
110
|
+
)
|
|
111
|
+
.post(
|
|
112
|
+
'/',
|
|
113
|
+
OpenApi.validate('json', schema.createEarlyAccessEntry.Body, {
|
|
114
|
+
code: 'body_invalid',
|
|
115
|
+
message: 'Invalid request body',
|
|
116
|
+
}),
|
|
117
|
+
OpenApi.describeRoute({
|
|
118
|
+
operationId: 'createEarlyAccessEntry',
|
|
119
|
+
responses: OpenApi.responses({
|
|
120
|
+
errors: { 409: 'Entry already exists.' },
|
|
121
|
+
success: {
|
|
122
|
+
description: 'The stored allowlist entry.',
|
|
123
|
+
schema: schema.createEarlyAccessEntry.Response,
|
|
124
|
+
},
|
|
125
|
+
}),
|
|
126
|
+
summary: 'Add early-access entry',
|
|
127
|
+
tags: ['Early Access'],
|
|
128
|
+
}),
|
|
129
|
+
async (c) => {
|
|
130
|
+
const db = Db.get(c.get('db'))
|
|
131
|
+
const { entry } = c.req.valid('json')
|
|
132
|
+
// `createdBy` is the verified admin email; `requireAuth` guarantees
|
|
133
|
+
// identity on this gated route.
|
|
134
|
+
const record = await EarlyAccess.add(db, { createdBy: c.get('identity')!.email, entry })
|
|
135
|
+
if (!record)
|
|
136
|
+
return Response.error(c, {
|
|
137
|
+
code: 'entry_exists',
|
|
138
|
+
message: 'Entry already exists.',
|
|
139
|
+
status: 409,
|
|
140
|
+
})
|
|
141
|
+
return c.json(Response.validated(schema.createEarlyAccessEntry.Response, { data: record }))
|
|
142
|
+
},
|
|
143
|
+
)
|
|
144
|
+
.delete(
|
|
145
|
+
'/',
|
|
146
|
+
OpenApi.validate('json', schema.deleteEarlyAccessEntry.Body, {
|
|
147
|
+
code: 'body_invalid',
|
|
148
|
+
message: 'Invalid request body',
|
|
149
|
+
}),
|
|
150
|
+
OpenApi.describeRoute({
|
|
151
|
+
operationId: 'deleteEarlyAccessEntry',
|
|
152
|
+
responses: OpenApi.responses({
|
|
153
|
+
errors: { 404: 'Entry not found.' },
|
|
154
|
+
success: {
|
|
155
|
+
description: 'The removed allowlist entry.',
|
|
156
|
+
schema: schema.deleteEarlyAccessEntry.Response,
|
|
157
|
+
},
|
|
158
|
+
}),
|
|
159
|
+
summary: 'Remove early-access entry',
|
|
160
|
+
tags: ['Early Access'],
|
|
161
|
+
}),
|
|
162
|
+
async (c) => {
|
|
163
|
+
const db = Db.get(c.get('db'))
|
|
164
|
+
const { entry } = c.req.valid('json')
|
|
165
|
+
const record = await EarlyAccess.remove(db, entry)
|
|
166
|
+
if (!record)
|
|
167
|
+
return Response.error(c, {
|
|
168
|
+
code: 'entry_not_found',
|
|
169
|
+
message: 'Entry not found.',
|
|
170
|
+
status: 404,
|
|
171
|
+
})
|
|
172
|
+
return c.json(Response.validated(schema.deleteEarlyAccessEntry.Response, { data: record }))
|
|
173
|
+
},
|
|
174
|
+
)
|
|
175
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as TestAdmin from '../../../test/Admin.js'
|
|
2
2
|
import * as TestApp from '../../../test/App.js'
|
|
3
|
+
import * as Organizations from '../../db/tables/organizations.js'
|
|
4
|
+
import * as VerifiedTokenRequests from '../../db/tables/verifiedTokenRequests.js'
|
|
3
5
|
import * as Verified from './verified-tokens.js'
|
|
4
6
|
|
|
5
7
|
/** A token used for write round-trips. */
|
|
@@ -338,6 +340,205 @@ describe('PUT /verified-tokens/:address/logo', () => {
|
|
|
338
340
|
})
|
|
339
341
|
})
|
|
340
342
|
|
|
343
|
+
/** A stored verification request seeded through the repo write boundary. */
|
|
344
|
+
const requestInput = {
|
|
345
|
+
address: '0x20c00000000000000000000000000000000000aa',
|
|
346
|
+
chainId: 42431,
|
|
347
|
+
currency: 'USD',
|
|
348
|
+
decimals: 6,
|
|
349
|
+
name: 'Requested USD',
|
|
350
|
+
orgId: 'org_1a2b3c4d5e6f7g8h9j0k1m2n',
|
|
351
|
+
requestedBy: 'usr_1a2b3c4d5e6f7g8h9j0k1m2n',
|
|
352
|
+
symbol: 'rUSD',
|
|
353
|
+
} as const
|
|
354
|
+
|
|
355
|
+
describe('GET /verified-tokens/requests', () => {
|
|
356
|
+
test('denied identity → 401', async () => {
|
|
357
|
+
const { client } = TestAdmin.setup({ identify: () => null })
|
|
358
|
+
const response = await client['verified-tokens'].requests.$get({ query: {} })
|
|
359
|
+
expect(response.status).toBe(401)
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
test('lists the queue by status with org names', async () => {
|
|
363
|
+
const { client, db } = TestAdmin.setup()
|
|
364
|
+
const org = await Organizations.create(db, { name: 'Acme' })
|
|
365
|
+
const pending = await VerifiedTokenRequests.create(db, {
|
|
366
|
+
...requestInput,
|
|
367
|
+
logo: '<svg xmlns="http://www.w3.org/2000/svg"/>',
|
|
368
|
+
orgId: org.id,
|
|
369
|
+
})
|
|
370
|
+
const denied = await VerifiedTokenRequests.create(db, {
|
|
371
|
+
...requestInput,
|
|
372
|
+
address: '0x20c00000000000000000000000000000000000bb',
|
|
373
|
+
symbol: 'rEUR',
|
|
374
|
+
})
|
|
375
|
+
await VerifiedTokenRequests.review(db, denied!.id, {
|
|
376
|
+
reviewedBy: 'admin@tempo.xyz',
|
|
377
|
+
status: 'denied',
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
const queue = await client['verified-tokens'].requests.$get({ query: {} })
|
|
381
|
+
expect(queue.status).toBe(200)
|
|
382
|
+
const queueBody = await TestApp.json(queue, Verified.schema.listVerifiedTokenRequests.Response)
|
|
383
|
+
expect(queueBody.data.map(({ id, orgName }) => ({ id, orgName }))).toEqual([
|
|
384
|
+
{ id: pending!.id, orgName: 'Acme' },
|
|
385
|
+
])
|
|
386
|
+
// The uploaded SVG surfaces to reviewers.
|
|
387
|
+
expect(queueBody.data[0]?.logo).toBe('<svg xmlns="http://www.w3.org/2000/svg"/>')
|
|
388
|
+
|
|
389
|
+
const reviewed = await client['verified-tokens'].requests.$get({ query: { status: 'denied' } })
|
|
390
|
+
const reviewedBody = await TestApp.json(reviewed, Verified.schema.listVerifiedTokenRequests.Response) // prettier-ignore
|
|
391
|
+
expect(reviewedBody.data.map(({ id }) => id)).toEqual([denied!.id])
|
|
392
|
+
})
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
describe('POST /verified-tokens/requests/:requestId/approve', () => {
|
|
396
|
+
test('publishes the token and stamps the review', async () => {
|
|
397
|
+
const { client, db } = TestAdmin.setup()
|
|
398
|
+
const org = await Organizations.create(db, { name: 'Acme' })
|
|
399
|
+
const request = await VerifiedTokenRequests.create(db, { ...requestInput, orgId: org.id })
|
|
400
|
+
|
|
401
|
+
const approved = await client['verified-tokens'].requests[':requestId'].approve.$post({
|
|
402
|
+
param: { requestId: request!.id },
|
|
403
|
+
})
|
|
404
|
+
expect(approved.status).toBe(200)
|
|
405
|
+
const body = await TestApp.json(approved, Verified.schema.reviewVerifiedTokenRequest.Response)
|
|
406
|
+
expect(body.data).toMatchObject({
|
|
407
|
+
orgName: 'Acme',
|
|
408
|
+
reviewedBy: TestAdmin.identity.email,
|
|
409
|
+
status: 'approved',
|
|
410
|
+
})
|
|
411
|
+
expect(Date.parse(body.data.reviewedAt!)).not.toBeNaN()
|
|
412
|
+
|
|
413
|
+
const listed = await client['verified-tokens'].$get({ query: { chainId } })
|
|
414
|
+
const listedBody = await TestApp.json(listed, Verified.schema.listVerifiedTokens.Response)
|
|
415
|
+
expect(listedBody.data.map(({ address }) => address)).toEqual([requestInput.address])
|
|
416
|
+
|
|
417
|
+
const repeat = await client['verified-tokens'].requests[':requestId'].approve.$post({
|
|
418
|
+
param: { requestId: request!.id },
|
|
419
|
+
})
|
|
420
|
+
expect(repeat.status).toBe(409)
|
|
421
|
+
const repeatBody = (await repeat.json()) as { error?: { code?: string } }
|
|
422
|
+
expect(repeatBody.error?.code).toBe('request_not_pending')
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
test('publishes an uploaded logo to the asset store', async () => {
|
|
426
|
+
const svg = '<svg xmlns="http://www.w3.org/2000/svg"/>'
|
|
427
|
+
const stored: { key: string; content: { body: ArrayBuffer; contentType: string } }[] = []
|
|
428
|
+
const { client, db } = TestAdmin.setup({
|
|
429
|
+
assets: {
|
|
430
|
+
get: async (key) => stored.find((entry) => entry.key === key)?.content,
|
|
431
|
+
put: async (key, content) => void stored.push({ content, key }),
|
|
432
|
+
},
|
|
433
|
+
})
|
|
434
|
+
const request = await VerifiedTokenRequests.create(db, { ...requestInput, logo: svg })
|
|
435
|
+
|
|
436
|
+
const approved = await client['verified-tokens'].requests[':requestId'].approve.$post({
|
|
437
|
+
param: { requestId: request!.id },
|
|
438
|
+
})
|
|
439
|
+
expect(approved.status).toBe(200)
|
|
440
|
+
expect(stored).toHaveLength(1)
|
|
441
|
+
expect(stored[0]?.key).toBe(`42431/icons/${requestInput.address}`)
|
|
442
|
+
expect(new TextDecoder().decode(stored[0]?.content.body)).toBe(svg)
|
|
443
|
+
|
|
444
|
+
const listed = await client['verified-tokens'].$get({ query: { chainId } })
|
|
445
|
+
const listedBody = await TestApp.json(listed, Verified.schema.listVerifiedTokens.Response)
|
|
446
|
+
expect(listedBody.data.map(({ address }) => address)).toEqual([requestInput.address])
|
|
447
|
+
})
|
|
448
|
+
|
|
449
|
+
test('rejects an uploaded logo without an asset writer, keeping the request pending', async () => {
|
|
450
|
+
const { client, db } = TestAdmin.setup()
|
|
451
|
+
const request = await VerifiedTokenRequests.create(db, {
|
|
452
|
+
...requestInput,
|
|
453
|
+
logo: '<svg xmlns="http://www.w3.org/2000/svg"/>',
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
const approved = await client['verified-tokens'].requests[':requestId'].approve.$post({
|
|
457
|
+
param: { requestId: request!.id },
|
|
458
|
+
})
|
|
459
|
+
expect(approved.status).toBe(404)
|
|
460
|
+
const body = (await approved.json()) as { error?: { code?: string } }
|
|
461
|
+
expect(body.error?.code).toBe('assets_not_enabled')
|
|
462
|
+
|
|
463
|
+
const record = await VerifiedTokenRequests.get(db, request!.id)
|
|
464
|
+
expect(record?.status).toBe('pending')
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
test('converges when the token is already verified', async () => {
|
|
468
|
+
const { client, db } = TestAdmin.setup()
|
|
469
|
+
const request = await VerifiedTokenRequests.create(db, requestInput)
|
|
470
|
+
await client['verified-tokens'].$post({
|
|
471
|
+
json: { ...novel, address: requestInput.address, chainId: 42431, symbol: requestInput.symbol }, // prettier-ignore
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
const approved = await client['verified-tokens'].requests[':requestId'].approve.$post({
|
|
475
|
+
param: { requestId: request!.id },
|
|
476
|
+
})
|
|
477
|
+
expect(approved.status).toBe(200)
|
|
478
|
+
const body = await TestApp.json(approved, Verified.schema.reviewVerifiedTokenRequest.Response)
|
|
479
|
+
expect(body.data.status).toBe('approved')
|
|
480
|
+
|
|
481
|
+
// The pre-existing entry survives untouched; nothing was re-created.
|
|
482
|
+
const listed = await client['verified-tokens'].$get({ query: { chainId } })
|
|
483
|
+
const listedBody = await TestApp.json(listed, Verified.schema.listVerifiedTokens.Response)
|
|
484
|
+
expect(listedBody.data).toHaveLength(1)
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
test('rejects a symbol conflict and keeps the request pending', async () => {
|
|
488
|
+
const { client, db } = TestAdmin.setup()
|
|
489
|
+
// Same symbol already verified under a different address.
|
|
490
|
+
await client['verified-tokens'].$post({
|
|
491
|
+
json: { ...novel, chainId: 42431, symbol: requestInput.symbol },
|
|
492
|
+
})
|
|
493
|
+
const request = await VerifiedTokenRequests.create(db, requestInput)
|
|
494
|
+
|
|
495
|
+
const approved = await client['verified-tokens'].requests[':requestId'].approve.$post({
|
|
496
|
+
param: { requestId: request!.id },
|
|
497
|
+
})
|
|
498
|
+
expect(approved.status).toBe(409)
|
|
499
|
+
const body = (await approved.json()) as { error?: { code?: string } }
|
|
500
|
+
expect(body.error?.code).toBe('duplicate_symbol')
|
|
501
|
+
|
|
502
|
+
const record = await VerifiedTokenRequests.get(db, request!.id)
|
|
503
|
+
expect(record?.status).toBe('pending')
|
|
504
|
+
})
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
describe('POST /verified-tokens/requests/:requestId/deny', () => {
|
|
508
|
+
test('records the review note', async () => {
|
|
509
|
+
const { client, db } = TestAdmin.setup()
|
|
510
|
+
const request = await VerifiedTokenRequests.create(db, requestInput)
|
|
511
|
+
|
|
512
|
+
const denied = await client['verified-tokens'].requests[':requestId'].deny.$post({
|
|
513
|
+
json: { reviewNote: 'Logo unreachable.' },
|
|
514
|
+
param: { requestId: request!.id },
|
|
515
|
+
})
|
|
516
|
+
expect(denied.status).toBe(200)
|
|
517
|
+
const body = await TestApp.json(denied, Verified.schema.reviewVerifiedTokenRequest.Response)
|
|
518
|
+
expect(body.data).toMatchObject({
|
|
519
|
+
reviewNote: 'Logo unreachable.',
|
|
520
|
+
reviewedBy: TestAdmin.identity.email,
|
|
521
|
+
status: 'denied',
|
|
522
|
+
})
|
|
523
|
+
|
|
524
|
+
// Nothing was published.
|
|
525
|
+
const listed = await client['verified-tokens'].$get({ query: { chainId } })
|
|
526
|
+
const listedBody = await TestApp.json(listed, Verified.schema.listVerifiedTokens.Response)
|
|
527
|
+
expect(listedBody.data).toEqual([])
|
|
528
|
+
})
|
|
529
|
+
|
|
530
|
+
test('unknown request id → 404', async () => {
|
|
531
|
+
const { client } = TestAdmin.setup()
|
|
532
|
+
const response = await client['verified-tokens'].requests[':requestId'].deny.$post({
|
|
533
|
+
json: {},
|
|
534
|
+
param: { requestId: 'vtr_1a2b3c4d5e6f7g8h9j0k1m2n' },
|
|
535
|
+
})
|
|
536
|
+
expect(response.status).toBe(404)
|
|
537
|
+
const body = (await response.json()) as { error?: { code?: string } }
|
|
538
|
+
expect(body.error?.code).toBe('verified_token_request_not_found')
|
|
539
|
+
})
|
|
540
|
+
})
|
|
541
|
+
|
|
341
542
|
describe('GET /verified-tokens/:address/logo', () => {
|
|
342
543
|
const svg = '<svg xmlns="http://www.w3.org/2000/svg"/>'
|
|
343
544
|
|