import * as TestAdmin from '../../../test/Admin.js'
import * as TestApp from '../../../test/App.js'
import * as Organizations from '../../db/tables/organizations.js'
import * as VerifiedTokenRequests from '../../db/tables/verifiedTokenRequests.js'
import * as Verified from './verified-tokens.js'
/** A token used for write round-trips. */
const novel = {
address: '0x20c00000000000000000000000000000000000ff',
currency: 'GBP',
decimals: 8,
name: 'Test GBP',
symbol: 'tGBP',
} as const
const chainId = '42431' as const
/** Write-enabled admin client backed by a fresh schema-isolated database. */
function writes() {
return TestAdmin.setup()
}
describe('authentication', () => {
test('denied identity → 401', async () => {
const { client } = TestAdmin.setup({ identify: () => null })
const response = await client['verified-tokens'].$post({
json: { ...novel, chainId: 42431 },
})
expect(response.status).toBe(401)
})
})
describe('list', () => {
test('empty list → data [] and null version', async () => {
const { client } = writes()
const response = await client['verified-tokens'].$get({ query: { chainId } })
expect(response.status).toBe(200)
const body = await TestApp.json(response, Verified.schema.listVerifiedTokens.Response)
expect(body.data).toEqual([])
expect(body.version).toBeNull()
})
test('reflects created tokens with a version', async () => {
const { client } = writes()
await client['verified-tokens'].$post({ json: { ...novel, chainId: 42431 } })
const response = await client['verified-tokens'].$get({ query: { chainId } })
const body = await TestApp.json(response, Verified.schema.listVerifiedTokens.Response)
expect(body.data).toHaveLength(1)
expect(body.data[0]?.address).toBe(novel.address)
expect(body.version).toBeTruthy()
})
})
describe('write lifecycle', () => {
test('create → patch → delete', async () => {
const { client } = writes()
const created = await client['verified-tokens'].$post({
json: { ...novel, chainId: 42431 },
})
expect(created.status).toBe(200)
const createdBody = await TestApp.json(created, Verified.schema.createVerifiedToken.Response)
expect(createdBody.data.address).toBe(novel.address)
expect(createdBody.version).toBeTruthy()
const patched = await client['verified-tokens'][':address'].$patch({
json: { name: 'Renamed GBP' },
param: { address: novel.address },
query: { chainId },
})
const patchedBody = await TestApp.json(patched, Verified.schema.patchVerifiedToken.Response)
expect(patchedBody.data.name).toBe('Renamed GBP')
const deleted = await client['verified-tokens'][':address'].$delete({
param: { address: novel.address },
query: { chainId },
})
expect(deleted.status).toBe(200)
// Prove the delete took effect: re-creating the same address now succeeds
// instead of conflicting.
const recreated = await client['verified-tokens'].$post({
json: { ...novel, chainId: 42431 },
})
expect(recreated.status).toBe(200)
})
test('rejects a duplicate address with 409', async () => {
const { client } = writes()
await client['verified-tokens'].$post({ json: { ...novel, chainId: 42431 } })
const duplicate = await client['verified-tokens'].$post({
json: { ...novel, chainId: 42431 },
})
expect(duplicate.status).toBe(409)
const body = (await duplicate.json()) as { error?: { code?: string } }
expect(body.error?.code).toBe('duplicate_address')
})
test('returns 404 patching an unknown address', async () => {
const { client } = writes()
const response = await client['verified-tokens'][':address'].$patch({
json: { name: 'x' },
param: { address: novel.address },
query: { chainId },
})
expect(response.status).toBe(404)
const body = (await response.json()) as { error?: { code?: string } }
expect(body.error?.code).toBe('verified_token_not_found')
})
test('rejects a stale If-Match with 412', async () => {
const { client } = writes()
await client['verified-tokens'].$post({ json: { ...novel, chainId: 42431 } })
const response = await client['verified-tokens'][':address'].$patch(
{ json: { name: 'x' }, param: { address: novel.address }, query: { chainId } },
{ headers: { 'if-match': '"stale"' } },
)
expect(response.status).toBe(412)
const body = (await response.json()) as { error?: { code?: string } }
expect(body.error?.code).toBe('version_mismatch')
})
test('replaces the whole list via PUT', async () => {
const { client } = writes()
const response = await client['verified-tokens'].$put({
json: { tokens: [novel] },
query: { chainId },
})
expect(response.status).toBe(200)
const body = await TestApp.json(response, Verified.schema.replaceVerifiedTokens.Response)
expect(body.data.map((token) => token.address)).toEqual([novel.address])
})
test('requires an explicit chainId on query-addressed mutations', async () => {
const { client } = writes()
const response = await client['verified-tokens'].$put({
json: { tokens: [novel] },
// Cast past the typed client to exercise runtime validation.
query: {} as never,
})
expect(response.status).toBe(400)
const body = (await response.json()) as { error?: { code?: string } }
expect(body.error?.code).toBe('query_invalid')
})
})
describe('create with inline logo', () => {
const svg = ''
/** Write-enabled setup with an in-memory asset store capturing every put. */
function setup() {
const stored: { key: string; content: { body: ArrayBuffer; contentType: string } }[] = []
const { client } = TestAdmin.setup({
assets: {
get: async (key) => stored.find((entry) => entry.key === key)?.content,
put: async (key, content) => void stored.push({ content, key }),
},
})
return { client, stored }
}
/** Count of tokens currently in the chain's verified list. */
async function count(client: ReturnType['client']) {
const response = await client['verified-tokens'].$get({ query: { chainId } })
const body = await TestApp.json(response, Verified.schema.listVerifiedTokens.Response)
return body.data.length
}
test('uploads the logo and creates the token', async () => {
const { client, stored } = setup()
const response = await client['verified-tokens'].$post({
json: { ...novel, chainId: 42431, logo: svg },
})
expect(response.status).toBe(200)
expect(stored).toHaveLength(1)
expect(stored[0]?.key).toBe(`42431/icons/${novel.address}`)
expect(new TextDecoder().decode(stored[0]?.content.body)).toBe(svg)
expect(await count(client)).toBe(1)
})
test('a duplicate is rejected before the logo is uploaded', async () => {
const { client, stored } = setup()
await client['verified-tokens'].$post({ json: { ...novel, chainId: 42431 } })
const response = await client['verified-tokens'].$post({
json: { ...novel, chainId: 42431, logo: svg },
})
expect(response.status).toBe(409)
// The upload must not run for a duplicate, so an existing icon is never overwritten.
expect(stored).toHaveLength(0)
})
test('a failed upload leaves no token', async () => {
const { client } = TestAdmin.setup({
assets: {
get: async () => undefined,
put: async () => {
throw new Error('upload failed')
},
},
})
const response = await client['verified-tokens'].$post({
json: { ...novel, chainId: 42431, logo: svg },
})
expect(response.status).toBe(502)
expect(await count(client)).toBe(0)
})
test('a logo without an asset writer is rejected, creating no token', async () => {
const { client } = writes()
const response = await client['verified-tokens'].$post({
json: { ...novel, chainId: 42431, logo: svg },
})
expect(response.status).toBe(404)
const body = (await response.json()) as { error?: { code?: string } }
expect(body.error?.code).toBe('assets_not_enabled')
expect(await count(client)).toBe(0)
})
test('a non-SVG logo is rejected with 415, creating no token', async () => {
const { client, stored } = setup()
const response = await client['verified-tokens'].$post({
json: { ...novel, chainId: 42431, logo: 'not svg' },
})
expect(response.status).toBe(415)
expect(stored).toHaveLength(0)
expect(await count(client)).toBe(0)
})
})
describe('logoUri', () => {
test('round-trips through create and patch', async () => {
const { client } = writes()
const logoUri = 'https://example.com/logo.svg'
const created = await client['verified-tokens'].$post({
json: { ...novel, chainId: 42431, logoUri },
})
expect(created.status).toBe(200)
const createdBody = await TestApp.json(created, Verified.schema.createVerifiedToken.Response)
expect(createdBody.data.logoUri).toBe(logoUri)
const patched = await client['verified-tokens'][':address'].$patch({
json: { logoUri: 'https://example.com/logo-v2.svg' },
param: { address: novel.address },
query: { chainId },
})
const patchedBody = await TestApp.json(patched, Verified.schema.patchVerifiedToken.Response)
expect(patchedBody.data.logoUri).toBe('https://example.com/logo-v2.svg')
// Untouched fields survive the patch.
expect(patchedBody.data.symbol).toBe(novel.symbol)
})
test('rejects a non-HTTPS logoUri with 400', async () => {
const { client } = writes()
const response = await client['verified-tokens'].$post({
json: { ...novel, chainId: 42431, logoUri: 'javascript:alert(1)' },
})
expect(response.status).toBe(400)
const body = (await response.json()) as { error?: { code?: string } }
expect(body.error?.code).toBe('body_invalid')
})
})
describe('PUT /verified-tokens/:address/logo', () => {
const svg = ''
/** Setup with an in-memory asset store capturing every put. */
function uploads() {
const stored: { key: string; content: { body: ArrayBuffer; contentType: string } }[] = []
const { client } = TestAdmin.setup({
assets: {
get: async (key) => stored.find((entry) => entry.key === key)?.content,
put: async (key, content) => {
stored.push({ content, key })
},
},
})
return { client, stored }
}
test('stores the SVG under the main API icon key', async () => {
const { client, stored } = uploads()
const response = await client['verified-tokens'][':address'].logo.$put(
{ param: { address: novel.address }, query: { chainId } },
{ headers: { 'content-type': 'image/svg+xml' }, init: { body: svg } },
)
expect(response.status).toBe(200)
const body = await TestApp.json(response, Verified.schema.uploadVerifiedTokenLogo.Response)
const key = `42431/icons/${novel.address}`
expect(body).toEqual({ key, path: `/assets/${key}` })
expect(stored).toHaveLength(1)
expect(stored[0]?.key).toBe(key)
expect(stored[0]?.content.contentType).toBe('image/svg+xml')
expect(new TextDecoder().decode(stored[0]?.content.body)).toBe(svg)
})
test('404 when no asset writer is configured', async () => {
const { client } = writes()
const response = await client['verified-tokens'][':address'].logo.$put(
{ param: { address: novel.address }, query: { chainId } },
{ headers: { 'content-type': 'image/svg+xml' }, init: { body: svg } },
)
expect(response.status).toBe(404)
const body = (await response.json()) as { error?: { code?: string } }
expect(body.error?.code).toBe('assets_not_enabled')
})
test('rejects a non-SVG content type with 415', async () => {
const { client, stored } = uploads()
const response = await client['verified-tokens'][':address'].logo.$put(
{ param: { address: novel.address }, query: { chainId } },
{ headers: { 'content-type': 'image/png' }, init: { body: svg } },
)
expect(response.status).toBe(415)
expect(stored).toHaveLength(0)
})
test('rejects an empty body with 400', async () => {
const { client, stored } = uploads()
const response = await client['verified-tokens'][':address'].logo.$put(
{ param: { address: novel.address }, query: { chainId } },
{ headers: { 'content-type': 'image/svg+xml' } },
)
expect(response.status).toBe(400)
expect(stored).toHaveLength(0)
})
test('rejects an oversized body with 413', async () => {
const { client, stored } = uploads()
const response = await client['verified-tokens'][':address'].logo.$put(
{ param: { address: novel.address }, query: { chainId } },
{
headers: { 'content-type': 'image/svg+xml' },
init: { body: 'x'.repeat(256 * 1024 + 1) },
},
)
expect(response.status).toBe(413)
expect(stored).toHaveLength(0)
})
})
/** A stored verification request seeded through the repo write boundary. */
const requestInput = {
address: '0x20c00000000000000000000000000000000000aa',
chainId: 42431,
currency: 'USD',
decimals: 6,
name: 'Requested USD',
orgId: 'org_1a2b3c4d5e6f7g8h9j0k1m2n',
requestedBy: 'usr_1a2b3c4d5e6f7g8h9j0k1m2n',
symbol: 'rUSD',
} as const
describe('GET /verified-tokens/requests', () => {
test('denied identity → 401', async () => {
const { client } = TestAdmin.setup({ identify: () => null })
const response = await client['verified-tokens'].requests.$get({ query: {} })
expect(response.status).toBe(401)
})
test('lists the queue by status with org names', async () => {
const { client, db } = TestAdmin.setup()
const org = await Organizations.create(db, { name: 'Acme' })
const pending = await VerifiedTokenRequests.create(db, {
...requestInput,
logo: '',
orgId: org.id,
})
const denied = await VerifiedTokenRequests.create(db, {
...requestInput,
address: '0x20c00000000000000000000000000000000000bb',
symbol: 'rEUR',
})
await VerifiedTokenRequests.review(db, denied!.id, {
reviewedBy: 'admin@tempo.xyz',
status: 'denied',
})
const queue = await client['verified-tokens'].requests.$get({ query: {} })
expect(queue.status).toBe(200)
const queueBody = await TestApp.json(queue, Verified.schema.listVerifiedTokenRequests.Response)
expect(queueBody.data.map(({ id, orgName }) => ({ id, orgName }))).toEqual([
{ id: pending!.id, orgName: 'Acme' },
])
// The uploaded SVG surfaces to reviewers.
expect(queueBody.data[0]?.logo).toBe('')
const reviewed = await client['verified-tokens'].requests.$get({ query: { status: 'denied' } })
const reviewedBody = await TestApp.json(reviewed, Verified.schema.listVerifiedTokenRequests.Response) // prettier-ignore
expect(reviewedBody.data.map(({ id }) => id)).toEqual([denied!.id])
})
})
describe('POST /verified-tokens/requests/:requestId/approve', () => {
test('publishes the token and stamps the review', async () => {
const { client, db } = TestAdmin.setup()
const org = await Organizations.create(db, { name: 'Acme' })
const request = await VerifiedTokenRequests.create(db, { ...requestInput, orgId: org.id })
const approved = await client['verified-tokens'].requests[':requestId'].approve.$post({
param: { requestId: request!.id },
})
expect(approved.status).toBe(200)
const body = await TestApp.json(approved, Verified.schema.reviewVerifiedTokenRequest.Response)
expect(body.data).toMatchObject({
orgName: 'Acme',
reviewedBy: TestAdmin.identity.email,
status: 'approved',
})
expect(Date.parse(body.data.reviewedAt!)).not.toBeNaN()
const listed = await client['verified-tokens'].$get({ query: { chainId } })
const listedBody = await TestApp.json(listed, Verified.schema.listVerifiedTokens.Response)
expect(listedBody.data.map(({ address }) => address)).toEqual([requestInput.address])
const repeat = await client['verified-tokens'].requests[':requestId'].approve.$post({
param: { requestId: request!.id },
})
expect(repeat.status).toBe(409)
const repeatBody = (await repeat.json()) as { error?: { code?: string } }
expect(repeatBody.error?.code).toBe('request_not_pending')
})
test('publishes an uploaded logo to the asset store', async () => {
const svg = ''
const stored: { key: string; content: { body: ArrayBuffer; contentType: string } }[] = []
const { client, db } = TestAdmin.setup({
assets: {
get: async (key) => stored.find((entry) => entry.key === key)?.content,
put: async (key, content) => void stored.push({ content, key }),
},
})
const request = await VerifiedTokenRequests.create(db, { ...requestInput, logo: svg })
const approved = await client['verified-tokens'].requests[':requestId'].approve.$post({
param: { requestId: request!.id },
})
expect(approved.status).toBe(200)
expect(stored).toHaveLength(1)
expect(stored[0]?.key).toBe(`42431/icons/${requestInput.address}`)
expect(new TextDecoder().decode(stored[0]?.content.body)).toBe(svg)
const listed = await client['verified-tokens'].$get({ query: { chainId } })
const listedBody = await TestApp.json(listed, Verified.schema.listVerifiedTokens.Response)
expect(listedBody.data.map(({ address }) => address)).toEqual([requestInput.address])
})
test('rejects an uploaded logo without an asset writer, keeping the request pending', async () => {
const { client, db } = TestAdmin.setup()
const request = await VerifiedTokenRequests.create(db, {
...requestInput,
logo: '',
})
const approved = await client['verified-tokens'].requests[':requestId'].approve.$post({
param: { requestId: request!.id },
})
expect(approved.status).toBe(404)
const body = (await approved.json()) as { error?: { code?: string } }
expect(body.error?.code).toBe('assets_not_enabled')
const record = await VerifiedTokenRequests.get(db, request!.id)
expect(record?.status).toBe('pending')
})
test('converges when the token is already verified', async () => {
const { client, db } = TestAdmin.setup()
const request = await VerifiedTokenRequests.create(db, requestInput)
await client['verified-tokens'].$post({
json: { ...novel, address: requestInput.address, chainId: 42431, symbol: requestInput.symbol }, // prettier-ignore
})
const approved = await client['verified-tokens'].requests[':requestId'].approve.$post({
param: { requestId: request!.id },
})
expect(approved.status).toBe(200)
const body = await TestApp.json(approved, Verified.schema.reviewVerifiedTokenRequest.Response)
expect(body.data.status).toBe('approved')
// The pre-existing entry survives untouched; nothing was re-created.
const listed = await client['verified-tokens'].$get({ query: { chainId } })
const listedBody = await TestApp.json(listed, Verified.schema.listVerifiedTokens.Response)
expect(listedBody.data).toHaveLength(1)
})
test('rejects a symbol conflict and keeps the request pending', async () => {
const { client, db } = TestAdmin.setup()
// Same symbol already verified under a different address.
await client['verified-tokens'].$post({
json: { ...novel, chainId: 42431, symbol: requestInput.symbol },
})
const request = await VerifiedTokenRequests.create(db, requestInput)
const approved = await client['verified-tokens'].requests[':requestId'].approve.$post({
param: { requestId: request!.id },
})
expect(approved.status).toBe(409)
const body = (await approved.json()) as { error?: { code?: string } }
expect(body.error?.code).toBe('duplicate_symbol')
const record = await VerifiedTokenRequests.get(db, request!.id)
expect(record?.status).toBe('pending')
})
})
describe('POST /verified-tokens/requests/:requestId/deny', () => {
test('records the review note', async () => {
const { client, db } = TestAdmin.setup()
const request = await VerifiedTokenRequests.create(db, requestInput)
const denied = await client['verified-tokens'].requests[':requestId'].deny.$post({
json: { reviewNote: 'Logo unreachable.' },
param: { requestId: request!.id },
})
expect(denied.status).toBe(200)
const body = await TestApp.json(denied, Verified.schema.reviewVerifiedTokenRequest.Response)
expect(body.data).toMatchObject({
reviewNote: 'Logo unreachable.',
reviewedBy: TestAdmin.identity.email,
status: 'denied',
})
// Nothing was published.
const listed = await client['verified-tokens'].$get({ query: { chainId } })
const listedBody = await TestApp.json(listed, Verified.schema.listVerifiedTokens.Response)
expect(listedBody.data).toEqual([])
})
test('unknown request id → 404', async () => {
const { client } = TestAdmin.setup()
const response = await client['verified-tokens'].requests[':requestId'].deny.$post({
json: {},
param: { requestId: 'vtr_1a2b3c4d5e6f7g8h9j0k1m2n' },
})
expect(response.status).toBe(404)
const body = (await response.json()) as { error?: { code?: string } }
expect(body.error?.code).toBe('verified_token_request_not_found')
})
})
describe('GET /verified-tokens/:address/logo', () => {
const svg = ''
/** Setup with an in-memory asset store backing both put and get. */
function store() {
const objects = new Map()
const { client } = TestAdmin.setup({
assets: {
get: async (key) => objects.get(key),
put: async (key, content) => void objects.set(key, content),
},
})
return { client, objects }
}
test('serves a previously uploaded SVG', async () => {
const { client } = store()
await client['verified-tokens'][':address'].logo.$put(
{ param: { address: novel.address }, query: { chainId } },
{ headers: { 'content-type': 'image/svg+xml' }, init: { body: svg } },
)
const response = await client['verified-tokens'][':address'].logo.$get({
param: { address: novel.address },
query: { chainId },
})
expect(response.status).toBe(200)
expect(response.headers.get('content-type')).toBe('image/svg+xml')
expect(await response.text()).toBe(svg)
})
test('404 when the logo is absent', async () => {
const { client } = store()
const response = await client['verified-tokens'][':address'].logo.$get({
param: { address: novel.address },
query: { chainId },
})
expect(response.status).toBe(404)
})
test('404 when no asset reader is configured', async () => {
const { client } = writes()
const response = await client['verified-tokens'][':address'].logo.$get({
param: { address: novel.address },
query: { chainId },
})
expect(response.status).toBe(404)
})
})