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
|
@@ -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
|
|
164
|
-
.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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.
|
|
233
|
-
data:
|
|
234
|
-
version: snapshot
|
|
260
|
+
Response.validated(schema.listVerifiedTokens.Response, {
|
|
261
|
+
data: snapshot?.list ?? [],
|
|
262
|
+
version: snapshot?.version ?? null,
|
|
235
263
|
}),
|
|
236
264
|
)
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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.
|
|
327
|
-
|
|
328
|
-
|
|
533
|
+
Response.validated(schema.uploadVerifiedTokenLogo.Response, {
|
|
534
|
+
key,
|
|
535
|
+
path: `/assets/${key}`,
|
|
329
536
|
}),
|
|
330
537
|
)
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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.
|
|
371
|
-
|
|
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
|
-
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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). */
|