import { Hono } from 'hono' import { tempoTestnet } from 'viem/tempo/chains' import * as TestAdmin from '../../../test/Admin.js' import * as TestApp from '../../../test/App.js' import * as TestEarn from '../../../test/Earn.js' import * as Runtime from '../../../test/runtime.js' import * as Earn from '../../apps/data/routes/earn.js' import * as core_EarnVaults from '../../db/tables/earnVaults.js' import * as App from './earn-vaults.js' const asset = TestEarn.assetTokenAddress const vaultAddress = TestEarn.vaultAddress const input = { chainId: tempoTestnet.id, description: 'Bridge test PATHUSD deposited into the canonical Earn v1 stack.', label: 'btPATHUSD Earn', privateInputTokens: [], privateOutputTokens: [], slug: 'btpathusd-earn', vaultAddress, zones: [], } as const const zoneInput = { ...input, label: 'Moderato Earn', slug: 'moderato-earn', vaultAddress: TestEarn.zoneVaultAddress, zones: [TestEarn.zoneRoute], } as const describe('authentication', () => { test('denied identity returns 401', async () => { const { app } = TestAdmin.setup({ identify: () => null }) const [detail, list, put, remove] = await Promise.all([ app.request(`/earn/verified-vaults/${vaultAddress}?chainId=${tempoTestnet.id}`), app.request(`/earn/verified-vaults?chainId=${tempoTestnet.id}`), app.request('/earn/verified-vaults', { body: JSON.stringify(input), headers: { 'content-type': 'application/json' }, method: 'PUT', }), app.request(`/earn/verified-vaults/${vaultAddress}?chainId=${tempoTestnet.id}`, { method: 'DELETE', }), ]) expect([detail.status, list.status, put.status, remove.status]).toEqual([401, 401, 401, 401]) }) }) describe('getVerifiedEarnVault', () => { test('rejects malformed vault addresses', async () => { const { app } = TestAdmin.setup() const response = await app.request(`/earn/verified-vaults/invalid?chainId=${tempoTestnet.id}`) expect(response.status).toBe(400) expect(await response.json()).toMatchObject({ error: { code: 'vault_id_invalid' } }) }) // This surface cannot measure rates or value assets, so it rejects both // fields instead of answering with a null that reads as unmeasurable. test('rejects rate and valuation includes', async () => { const { app } = TestAdmin.setup() const response = await app.request( `/earn/verified-vaults/${vaultAddress}?chainId=${tempoTestnet.id}&include=apy,tvl`, ) expect(response.status).toBe(400) expect(await response.json()).toMatchObject({ error: { code: 'query_invalid' } }) }) test.runIf(Runtime.get().mode === 'testnet')( 'resolves an unverified vault for registry entry', async () => { const db = TestApp.database() const { app } = TestAdmin.setup({ db, zones: [TestEarn.zone] }) const publicApp = TestApp.create({ auth: false, db, zones: [TestEarn.zone] }) const query = `?chainId=${tempoTestnet.id}&include=access,capabilities,zone` const [response, publicResponse] = await Promise.all([ app.request(`/earn/verified-vaults/${vaultAddress}${query}`), publicApp.request(`/v1/earn/vaults/${vaultAddress}${query}`), ]) expect([response.status, publicResponse.status]).toEqual([200, 200]) const [vault, publicVault] = await Promise.all([ TestApp.json(response, App.schema.getVerifiedEarnVault.Response), TestApp.json(publicResponse, Earn.schema.getEarnVault.Response), ]) // Every field the admin include set can select is resolved identically to // the public surface. expect(publicVault.meta).toBeUndefined() expect(vault).toStrictEqual(publicVault) expect({ description: vault.description, slug: vault.slug, vaultAddress: vault.vaultAddress, verified: vault.verified, }).toMatchInlineSnapshot(` { "description": null, "slug": null, "vaultAddress": "0xf4ae63687d6753a78e7f551d2eda1d0d31a5ea3a", "verified": false, } `) }, 90_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'returns token logo URLs under the app mount', async () => { const { app } = TestAdmin.setup({ assets: { get: async (key) => key === `${tempoTestnet.id}/icons/${asset}` ? { body: new ArrayBuffer(0), contentType: 'image/svg+xml' } : undefined, }, zones: [TestEarn.zone], }) const mounted = new Hono().route('/admin', app) const response = await mounted.request( `http://host/admin/earn/verified-vaults/${vaultAddress}?chainId=${tempoTestnet.id}&include=token.logoUri`, ) const vault = await TestApp.json(response, App.schema.getVerifiedEarnVault.Response) expect(vault.assetToken.logoUri).toBe( `http://host/admin/verified-tokens/${asset}/logo?chainId=${tempoTestnet.id}`, ) expect((await mounted.request(vault.assetToken.logoUri!)).status).toBe(200) }, 90_000, ) }) describe('listVerifiedEarnVaults', () => { test('rejects rate and valuation includes', async () => { const { app } = TestAdmin.setup() const response = await app.request( `/earn/verified-vaults?chainId=${tempoTestnet.id}&include=apy,tvl`, ) expect(response.status).toBe(400) expect(await response.json()).toMatchObject({ error: { code: 'query_invalid' } }) }) test.runIf(Runtime.get().mode === 'testnet')( 'matches the public verified collection', async () => { const db = TestApp.database() await core_EarnVaults.upsert(db, TestEarn.verifiedVault) const { app } = TestAdmin.setup({ db, zones: [TestEarn.zone] }) const publicApp = TestApp.create({ auth: false, db, zones: [TestEarn.zone] }) const query = `?chainId=${tempoTestnet.id}&include=access,capabilities,zone&limit=5` const [response, publicResponse] = await Promise.all([ app.request(`/earn/verified-vaults${query}`), publicApp.request(`/v1/earn/vaults/verified${query}`), ]) expect([response.status, publicResponse.status]).toEqual([200, 200]) const [body, publicBody] = await Promise.all([ TestApp.json(response, App.schema.listVerifiedEarnVaults.Response), TestApp.json(publicResponse, App.schema.listVerifiedEarnVaults.Response), ]) expect(body).toStrictEqual(publicBody) }, 90_000, ) }) describe('putVerifiedEarnVault', () => { test('rejects invalid slugs', async () => { const { app } = TestAdmin.setup() const response = await app.request('/earn/verified-vaults', { body: JSON.stringify({ ...input, slug: 'Not a slug' }), headers: { 'content-type': 'application/json' }, method: 'PUT', }) expect(response.status).toBe(400) expect(await response.json()).toMatchObject({ error: { code: 'body_invalid' } }) }) test('rejects derived fields', async () => { const { app } = TestAdmin.setup() const response = await app.request('/earn/verified-vaults', { body: JSON.stringify({ ...input, engine: { type: 'erc4626' } }), headers: { 'content-type': 'application/json' }, method: 'PUT', }) expect(response.status).toBe(400) expect(await response.json()).toMatchObject({ error: { code: 'body_invalid' } }) }) test.runIf(Runtime.get().mode === 'testnet')( 'rejects duplicate Zone routes', async () => { const { app } = TestAdmin.setup({ zones: [TestEarn.zone] }) const response = await app.request('/earn/verified-vaults', { body: JSON.stringify({ ...zoneInput, zones: [TestEarn.zoneRoute, TestEarn.zoneRoute], }), headers: { 'content-type': 'application/json' }, method: 'PUT', }) expect(response.status).toBe(400) expect(await response.json()).toMatchObject({ error: { code: 'body_invalid', details: [{ message: 'Zone 1424310003 is configured more than once.' }], }, }) }, 60_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'rejects a router bound to a different vault', async () => { const { app } = TestAdmin.setup({ zones: [TestEarn.zone] }) const response = await app.request('/earn/verified-vaults', { body: JSON.stringify({ ...zoneInput, vaultAddress }), headers: { 'content-type': 'application/json' }, method: 'PUT', }) expect(response.status).toBe(409) expect(await response.json()).toEqual( expect.objectContaining({ error: expect.objectContaining({ code: 'earn_vault_verification_failed', details: expect.arrayContaining([ { message: 'Earn router is configured for a different Earn vault.' }, ]), }), }), ) }, 60_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'verifies, stores, and preserves a Zone route', async () => { const { app, db } = TestAdmin.setup({ zones: [TestEarn.zone] }) const response = await app.request('/earn/verified-vaults', { body: JSON.stringify(zoneInput), headers: { 'content-type': 'application/json' }, method: 'PUT', }) expect(response.status).toBe(200) const vault = await TestApp.json(response, App.schema.putVerifiedEarnVault.Response) expect(vault.capabilities).toMatchObject({ privateRouting: true, routerSwaps: true }) expect(vault.zones).toHaveLength(1) expect(vault.zones?.[0]).toMatchObject(TestEarn.zoneRoute) expect(vault.zone).toStrictEqual( vault.zones?.[0] ? { chainId: vault.zones[0].chainId, inputTokens: vault.zones[0].inputTokens, name: vault.zones[0].name, outputTokens: vault.zones[0].outputTokens, } : null, ) expect(vault.zones?.[0]?.inputTokens).toHaveLength(1) expect(vault.zones?.[0]?.outputTokens).toStrictEqual(vault.zones?.[0]?.inputTokens) const updatedResponse = await app.request('/earn/verified-vaults', { body: JSON.stringify({ chainId: zoneInput.chainId, label: 'Updated Moderato Earn', vaultAddress: zoneInput.vaultAddress, }), headers: { 'content-type': 'application/json' }, method: 'PUT', }) expect(updatedResponse.status).toBe(200) expect( (await TestApp.json(updatedResponse, App.schema.putVerifiedEarnVault.Response)).zones, ).toStrictEqual(vault.zones) expect( await core_EarnVaults.get(db, { chainId: zoneInput.chainId, vaultAddress: zoneInput.vaultAddress, }), ).toMatchObject({ zones: [TestEarn.zoneRoute] }) }, 90_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'migrates preserved private tokens to a verified Zone route', async () => { const { app, db } = TestAdmin.setup({ zones: [TestEarn.zone] }) await core_EarnVaults.upsert(db, { ...TestEarn.zoneVerifiedVault, privateInputTokens: [asset], privateOutputTokens: [asset], zones: [], }) const response = await app.request('/earn/verified-vaults', { body: JSON.stringify({ chainId: zoneInput.chainId, vaultAddress: zoneInput.vaultAddress, zones: zoneInput.zones, }), headers: { 'content-type': 'application/json' }, method: 'PUT', }) expect(response.status).toBe(200) const vault = await TestApp.json(response, App.schema.putVerifiedEarnVault.Response) expect(vault.zones).toHaveLength(1) expect( await core_EarnVaults.get(db, { chainId: zoneInput.chainId, vaultAddress: zoneInput.vaultAddress, }), ).toMatchObject({ privateInputTokens: [asset], privateOutputTokens: [asset], zones: [TestEarn.zoneRoute], }) }, 90_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'rejects private tokens without a configured router', async () => { const { app } = TestAdmin.setup({ zones: [TestEarn.zone] }) const response = await app.request('/earn/verified-vaults', { body: JSON.stringify({ ...input, privateInputTokens: [asset], privateOutputTokens: [asset], }), headers: { 'content-type': 'application/json' }, method: 'PUT', }) expect(response.status).toBe(409) expect(await response.json()).toMatchObject({ error: { code: 'earn_vault_verification_failed' }, }) }, 90_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'verifies, stores, and updates curated fields', async () => { const { app, db } = TestAdmin.setup({ zones: [TestEarn.zone] }) const response = await app.request('/earn/verified-vaults', { body: JSON.stringify(input), headers: { 'content-type': 'application/json' }, method: 'PUT', }) expect(response.status).toBe(200) const body = await TestApp.json(response, App.schema.putVerifiedEarnVault.Response) expect({ description: body.description, engineType: body.engine.type, gatewayExposed: 'gateway' in body, id: body.id, label: body.label, slug: body.slug, vaultAddress: body.vaultAddress, verified: body.verified, zone: body.zone, }).toMatchInlineSnapshot(` { "description": "Bridge test PATHUSD deposited into the canonical Earn v1 stack.", "engineType": "erc4626", "gatewayExposed": false, "id": "0xf4ae63687d6753a78e7f551d2eda1d0d31a5ea3a", "label": "btPATHUSD Earn", "slug": "btpathusd-earn", "vaultAddress": "0xf4ae63687d6753a78e7f551d2eda1d0d31a5ea3a", "verified": true, "zone": null, } `) const created = await core_EarnVaults.get(db, { chainId: tempoTestnet.id, vaultAddress, }) const updatedResponse = await app.request('/earn/verified-vaults', { body: JSON.stringify({ chainId: tempoTestnet.id, description: null, label: 'Updated btPATHUSD Earn', slug: 'updated-btpathusd-earn', vaultAddress, }), headers: { 'content-type': 'application/json' }, method: 'PUT', }) expect(updatedResponse.status).toBe(200) const updated = await TestApp.json(updatedResponse, App.schema.putVerifiedEarnVault.Response) expect({ description: updated.description, label: updated.label, slug: updated.slug, zone: updated.zone, }).toMatchInlineSnapshot(` { "description": null, "label": "Updated btPATHUSD Earn", "slug": "updated-btpathusd-earn", "zone": null, } `) const stored = await core_EarnVaults.get(db, { chainId: tempoTestnet.id, vaultAddress, }) expect(stored).toMatchObject({ privateInputTokens: [], privateOutputTokens: [], slug: 'updated-btpathusd-earn', }) expect(stored?.createdAt).toBe(created?.createdAt) }, 90_000, ) }) describe('deleteVerifiedEarnVault', () => { test('physically removes the row without reading contracts', async () => { const { app, db } = TestAdmin.setup({ rpc: { url: () => 'http://127.0.0.1:1' } }) await core_EarnVaults.upsert(db, input) const response = await app.request( `/earn/verified-vaults/${vaultAddress}?chainId=${tempoTestnet.id}`, { method: 'DELETE' }, ) expect(response.status).toBe(204) expect( await core_EarnVaults.get(db, { chainId: tempoTestnet.id, vaultAddress }), ).toBeUndefined() const missing = await app.request( `/earn/verified-vaults/${vaultAddress}?chainId=${tempoTestnet.id}`, { method: 'DELETE' }, ) expect(missing.status).toBe(404) }) test.runIf(Runtime.get().mode === 'testnet')( 'leaves the public vault available as unverified', async () => { const db = TestApp.database() await core_EarnVaults.upsert(db, TestEarn.verifiedVault) const { app } = TestAdmin.setup({ db, zones: [TestEarn.zone] }) const publicApp = TestApp.create({ auth: false, db, zones: [TestEarn.zone] }) const response = await app.request( `/earn/verified-vaults/${vaultAddress}?chainId=${tempoTestnet.id}`, { method: 'DELETE' }, ) expect(response.status).toBe(204) const publicResponse = await publicApp.request( `/v1/earn/vaults/${vaultAddress}?chainId=${tempoTestnet.id}&include=zone`, ) expect(publicResponse.status).toBe(200) const vault = await TestApp.json(publicResponse, App.schema.getVerifiedEarnVault.Response) expect({ description: vault.description, slug: vault.slug, verified: vault.verified, zone: vault.zone, }).toStrictEqual({ description: null, slug: null, verified: false, zone: null, }) }, 90_000, ) })