import { Schema } from 'tapimo' import type * as z from 'zod/mini' import * as core_EarnVaults from '../../../db/tables/earnVaults.js' import * as TestApp from '../../../../test/App.js' import * as TestEarn from '../../../../test/Earn.js' import * as Runtime from '../../../../test/runtime.js' import * as Cursor from '../../../internal/Cursor.js' import * as Store from '../../../internal/Store.js' import * as Tidx from '../../../internal/Tidx.js' import * as Value from '../../../internal/Value.js' import * as Earn from './earn.js' /** * One read cache for the whole file, so the chain-scoped rate anchor (boundary * blocks and the migration scan) is fetched once per run instead of once per * test, matching the colo-shared cache production runs on. The edge response * cache stays off so no test is served another test's body. */ const cache = { edge: false, store: Store.memory() } as const /** Fields every vault read enriches with live state, value, and rate data. */ type EnrichedVault = Pick< z.output, 'apy' | 'instantLiquidity' | 'sharePrice' | 'state' | 'tvl' > type OpenApiSchema = { $ref?: string } type OpenApiOperation = { description?: string parameters?: readonly { description?: string name?: string schema?: { enum?: readonly unknown[]; examples?: readonly unknown[]; pattern?: string } }[] responses?: Record< string, { content?: { 'application/json'?: { schema?: OpenApiSchema } }; description?: string } > } type OpenApiDocument = { components?: { schemas?: Record } paths: Record< string, { get?: OpenApiOperation } > } function errorSchemas(document: OpenApiDocument, operation: OpenApiOperation | undefined) { return JSON.stringify( Object.entries(operation?.responses ?? {}) .filter(([status]) => !status.startsWith('2')) .map(([, response]) => { const schema = response.content?.['application/json']?.schema const name = schema?.$ref?.split('/').at(-1) return name ? document.components?.schemas?.[name] : schema }), ) } describe('schema.getEarnVaults.Query', () => { test('parses collection filters', () => { const query = Earn.schema.getEarnVaults.Query.parse({ capability: 'deposit,exactWithdraw', 'engine.type': 'erc4626', include: 'access,apy,capabilities,zone,zones,token.logoUri,tvl', limit: '10', }) expect(query.capability).toStrictEqual(['deposit', 'exactWithdraw']) expect(query['engine.type']).toBe('erc4626') expect(query.include).toStrictEqual([ 'access', 'apy', 'capabilities', 'zone', 'zones', 'token.logoUri', 'tvl', ]) expect(query.limit).toBe(10) }) test('defaults the page size', () => { expect(Earn.schema.getEarnVaults.Query.parse({}).limit).toBe(10) }) test('rejects obsolete and detail-only parameters', () => { expect(() => Earn.schema.getEarnVaults.Query.parse({ 'engine.kind': 'erc4626' })).toThrow() expect(() => Earn.schema.getEarnVaults.Query.parse({ status: 'live-demo' })).toThrow() expect(() => Earn.schema.getEarnVault.Query.parse({ gateway: '0x0000000000000000000000000000000000000001', }), ).toThrow() }) test('accepts every selectable rate window across every vault read', () => { expect([ // An omitted window stays absent, so no rate is measured. Earn.schema.getEarnVaults.Query.parse({})['apy.window'], Earn.schema.getEarnVaults.Query.parse({ 'apy.window': '1h' })['apy.window'], Earn.schema.getEarnVaults.Query.parse({ 'apy.window': '1d' })['apy.window'], Earn.schema.getVerifiedEarnVaults.Query.parse({ 'apy.window': '7d' })['apy.window'], Earn.schema.getEarnVault.Query.parse({ 'apy.window': '30d' })['apy.window'], ]).toStrictEqual([undefined, '1h', '1d', '7d', '30d']) }) }) describe('parseIndexedBlockAtOrBefore', () => { test('rejects an empty upstream result before classifying the boundary', () => { expect(() => Earn.parseIndexedBlockAtOrBefore({ asOf: '2026-07-21T09:00:00.000Z', rows: [], }), ).toThrowErrorMatchingInlineSnapshot( `[Error: TIDX returned no indexed head for a historical Earn position.]`, ) }) test('classifies a missing boundary only after validating the indexed head', () => { expect(() => Earn.parseIndexedBlockAtOrBefore({ asOf: '2026-07-21T09:00:00.000Z', rows: [{ kind: 'head', num: 10, timestamp: '2026-07-21T10:00:00.000Z' }], }), ).toThrowErrorMatchingInlineSnapshot( `[Earn.IndexedBlockNotFoundError: No indexed block exists at or before 2026-07-21T09:00:00.000Z.]`, ) }) }) describe('schema.getEarnVaultPosition.Query', () => { test('accepts offset-bearing ISO 8601 timestamps', () => { expect( Earn.schema.getEarnVaultPosition.Query.parse({ asOf: '2026-07-21T10:00:00+01:00', }).asOf, ).toBe('2026-07-21T10:00:00+01:00') }) }) describe('schema.getEarnAddressPositions.Query', () => { test('parses collection filters', () => { const query = Earn.schema.getEarnAddressPositions.Query.parse({ limit: '5', 'valuation.currency': 'usd', verified: 'true', }) expect(query.limit).toBe(5) expect(query['valuation.currency']).toBe('USD') expect(query.verified).toBe(true) }) test('defaults the page size', () => { expect(Earn.schema.getEarnAddressPositions.Query.parse({}).limit).toBe(10) }) test('rejects vault-collection filters', () => { expect(() => Earn.schema.getEarnAddressPositions.Query.parse({ capability: 'deposit' }), ).toThrow() expect(() => Earn.schema.getEarnAddressPositions.Query.parse({ asset: `0x${'aa'.repeat(20)}` }), ).toThrow() }) }) describe('schema.getEarnVaultEarnings.Response', () => { test('enforces status-specific earnings fields', () => { const response = { account: TestEarn.earningsAddress, assetToken: TestEarn.assetTokenAddress, currentValue: '1000000', id: TestEarn.earningsAddress, } expect({ active: Earn.schema.getEarnVaultEarnings.Response.safeParse({ ...response, activeEarnings: '0', period: 'active', status: 'complete', }).success, activeWithLifetimeEarnings: Earn.schema.getEarnVaultEarnings.Response.safeParse({ ...response, lifetimeEarnings: '0', period: 'active', status: 'complete', }).success, completeWithoutEarnings: Earn.schema.getEarnVaultEarnings.Response.safeParse({ ...response, period: 'lifetime', status: 'complete', }).success, incomplete: Earn.schema.getEarnVaultEarnings.Response.safeParse({ ...response, period: 'active', status: 'incomplete_cost_basis', }).success, incompleteWindow: Earn.schema.getEarnVaultEarnings.Response.safeParse({ ...response, period: '30d', status: 'incomplete_cost_basis', }).success, incompleteWithEarnings: Earn.schema.getEarnVaultEarnings.Response.safeParse({ ...response, lifetimeEarnings: '0', period: 'lifetime', status: 'incomplete_cost_basis', }).success, lifetime: Earn.schema.getEarnVaultEarnings.Response.safeParse({ ...response, lifetimeEarnings: '0', period: 'lifetime', status: 'complete', totalDeposited: '1000000', totalWithdrawn: '500000', }).success, pending: Earn.schema.getEarnVaultEarnings.Response.safeParse({ ...response, period: 'lifetime', status: 'pending_redemption', }).success, pendingActive: Earn.schema.getEarnVaultEarnings.Response.safeParse({ ...response, period: 'active', status: 'pending_redemption', }).success, pendingWindow: Earn.schema.getEarnVaultEarnings.Response.safeParse({ ...response, period: '30d', status: 'pending_redemption', }).success, pendingWithEarnings: Earn.schema.getEarnVaultEarnings.Response.safeParse({ ...response, period: 'lifetime', status: 'pending_redemption', totalDeposited: '1000000', totalWithdrawn: '500000', }).success, window: Earn.schema.getEarnVaultEarnings.Response.safeParse({ ...response, period: '30d', status: 'complete', windowEarnings: '0', }).success, windowWithActiveEarnings: Earn.schema.getEarnVaultEarnings.Response.safeParse({ ...response, activeEarnings: '0', period: '30d', status: 'complete', }).success, windowWithoutEarnings: Earn.schema.getEarnVaultEarnings.Response.safeParse({ ...response, period: '30d', status: 'complete', }).success, }).toMatchInlineSnapshot(` { "active": true, "activeWithLifetimeEarnings": false, "completeWithoutEarnings": false, "incomplete": true, "incompleteWindow": true, "incompleteWithEarnings": false, "lifetime": true, "pending": false, "pendingActive": false, "pendingWindow": true, "pendingWithEarnings": true, "window": true, "windowWithActiveEarnings": false, "windowWithoutEarnings": false, } `) }) }) describe('serializeVault', () => { test('includes a non-null instant-liquidity valuation with TVL', () => { const assetToken = { address: TestEarn.assetTokenAddress, currency: 'USD', decimals: 6, id: TestEarn.assetTokenAddress, name: 'Bridge Test PATHUSD', symbol: 'btPATHUSD', verified: true, } as const const shareTokenAddress = `0x20c0${'11'.repeat(18)}` as const const vault = Earn.serializeVault({ apy: null, assetToken, discovery: { assetToken, chainId: TestEarn.chain.id, engine: { address: TestEarn.vaultAddress, type: 'erc4626', venue: null }, instantLiquidity: '500000000', sharePrice: '1000000', shareToken: { address: shareTokenAddress, currency: 'USD', decimals: 6, name: 'Bridge Test PATHUSD Earn', symbol: 'btPATHUSDE', }, state: { depositsPaused: false, engineShares: '1000000000', feesActive: true, isAccountingAligned: true, openRedeemRequestCount: 0, totalAssets: '1000000000', totalEarnShares: '1000000000', }, vaultAddress: TestEarn.vaultAddress, }, include: ['tvl'], instantLiquidityValue: { amount: '500000000', currency: 'USD', decimals: 6, formatted: '500', }, shareToken: { address: shareTokenAddress, currency: 'USD', decimals: 6, id: shareTokenAddress, name: 'Bridge Test PATHUSD Earn', symbol: 'btPATHUSDE', verified: false, }, tvl: { amount: '1000000000', currency: 'USD', decimals: 6, formatted: '1000' }, zoneRoutes: [], zones: [], }) expect(vault.instantLiquidityValue).toStrictEqual({ amount: '500000000', currency: 'USD', decimals: 6, formatted: '500', }) }) }) describe('schema.getEarnVaultEarnings.Query', () => { test('defaults to lifetime and accepts active and trailing earnings', () => { expect([ Earn.schema.getEarnVaultEarnings.Query.parse({}).period, Earn.schema.getEarnVaultEarnings.Query.parse({ period: '30d' }).period, Earn.schema.getEarnVaultEarnings.Query.parse({ period: 'active' }).period, ]).toStrictEqual(['lifetime', '30d', 'active']) }) }) describe('calculateWindowEarnings', () => { test('adjusts snapshot growth for deposits and realized assets', () => { expect( Earn.calculateWindowEarnings({ deposited: 50n, endingValue: 130n, finalized: 10n, openingValue: 100n, redeemed: 15n, withdrewExact: 5n, }), ).toBe(10n) }) }) describe('calculateActiveCostBasis', () => { test('allocates weighted-average basis across partial exits', () => { expect( Earn.calculateActiveCostBasis({ actions: [ { assets: 100n, kind: 'deposit', shares: 100n }, { assets: 300n, kind: 'deposit', shares: 100n }, { kind: 'redeem', shares: 100n }, ], shareBalance: 100n, }), ).toBe(200n) }) test('calculates remaining basis while an async redemption is pending', () => { expect( Earn.calculateActiveCostBasis({ actions: [ { assets: 100n, kind: 'deposit', shares: 100n }, { kind: 'request', requestId: 'request-1', shares: 40n }, ], shareBalance: 60n, }), ).toBe(60n) }) test('restores removed basis when an async redemption is cancelled', () => { expect( Earn.calculateActiveCostBasis({ actions: [ { assets: 100n, kind: 'deposit', shares: 100n }, { kind: 'request', requestId: 'request-1', shares: 40n }, { kind: 'cancel', requestId: 'request-1', shares: 40n }, ], shareBalance: 100n, }), ).toBe(100n) }) test('recovers after unknown-basis shares fully exit', () => { expect( Earn.calculateActiveCostBasis({ actions: [ { kind: 'unknown', shares: 100n }, { kind: 'redeem', shares: 100n }, { assets: 200n, kind: 'deposit', shares: 100n }, ], shareBalance: 100n, }), ).toBe(200n) }) test('rejects remaining shares with unknown cost basis', () => { expect( Earn.calculateActiveCostBasis({ actions: [ { assets: 100n, kind: 'deposit', shares: 100n }, { kind: 'unknown', shares: 100n }, { kind: 'redeem', shares: 100n }, ], shareBalance: 100n, }), ).toBeUndefined() }) test('rejects history that does not reconcile to the indexed share balance', () => { expect( Earn.calculateActiveCostBasis({ actions: [{ assets: 100n, kind: 'deposit', shares: 100n }], shareBalance: 99n, }), ).toBeUndefined() }) }) describe('discoveryBatchLimit', () => { test('fetches one extra candidate without live filters', () => { expect( Earn.discoveryBatchLimit({ filtered: false, limit: 10, matches: 0, remaining: 250, }), ).toBe(11) }) test('overfetches filtered candidates within the scan limit', () => { expect([ Earn.discoveryBatchLimit({ filtered: true, limit: 10, matches: 0, remaining: 250, }), Earn.discoveryBatchLimit({ filtered: true, limit: 200, matches: 0, remaining: 250, }), ]).toStrictEqual([22, 50]) }) }) describe('GET /v1/earn/vaults/verified', () => { test('registers the static verified path before the vault path', async () => { const app = TestApp.create({ auth: false, cache }) const response = await app.request('/v1/earn/vaults/verified') expect(response.status).toBe(200) expect(await TestApp.json(response, Earn.schema.getVerifiedEarnVaults.Response)).toStrictEqual({ data: [], nextCursor: null, }) }) test.runIf(Runtime.get().mode === 'testnet')( 'folds scoped Zone routes into vault capabilities', async () => { const db = TestApp.database() await core_EarnVaults.upsert(db, TestEarn.zoneVerifiedVault) const otherZoneId = TestEarn.zone.id + 1 const scopedKey = { ...TestApp.key, id: 'key_earn_zones', scopes: ['data:read', `zone:${otherZoneId}:read`, `zone:${TestEarn.zone.id}:read`], token: 'secret_earn_zones', } satisfies TestApp.kvStore.Key const unrelatedKey = { ...TestApp.key, id: 'key_earn_other_zone', scopes: ['data:read', `zone:${otherZoneId}:read`], token: 'secret_earn_other_zone', } satisfies TestApp.kvStore.Key const writeOnlyKey = { ...TestApp.key, id: 'key_earn_write_zone', scopes: ['data:read', `zone:${TestEarn.zone.id}:write`], token: 'secret_earn_write_zone', } satisfies TestApp.kvStore.Key const wildcardKey = { ...TestApp.key, id: 'key_earn_all_zones', scopes: ['*'], token: 'secret_earn_all_zones', } satisfies TestApp.kvStore.Key const app = TestApp.create({ auth: { keys: [scopedKey, unrelatedKey, wildcardKey, writeOnlyKey] }, cache, db, zones: [TestEarn.zone], }) const base = `/v1/earn/vaults/verified?chainId=${TestEarn.chain.id}&limit=5` const included = `${base}&include=capabilities,zone,zones` const filtered = `${included}&capability=privateRouting` // Fill the query-credential cache entry before checking callers without its Zone scope. const scopedResponse = await app.request(`${filtered}&key=${scopedKey.token}`) const [ defaultResponse, unrelatedResponse, publicResponse, wildcardResponse, writeOnlyResponse, ] = await Promise.all([ app.request(base, { headers: { 'tempo-api-key': scopedKey.token } }), app.request(included, { headers: { 'tempo-api-key': unrelatedKey.token } }), app.request(filtered), app.request(filtered, { headers: { 'tempo-api-key': wildcardKey.token } }), app.request(filtered, { headers: { 'tempo-api-key': writeOnlyKey.token } }), ]) expect([ defaultResponse.status, scopedResponse.status, unrelatedResponse.status, publicResponse.status, wildcardResponse.status, writeOnlyResponse.status, ]).toStrictEqual([200, 200, 200, 200, 200, 200]) const [defaultBody, scopedBody, unrelatedBody, publicBody, wildcardBody, writeOnlyBody] = await Promise.all([ TestApp.json(defaultResponse, Earn.schema.getVerifiedEarnVaults.Response), TestApp.json(scopedResponse, Earn.schema.getVerifiedEarnVaults.Response), TestApp.json(unrelatedResponse, Earn.schema.getVerifiedEarnVaults.Response), TestApp.json(publicResponse, Earn.schema.getVerifiedEarnVaults.Response), TestApp.json(wildcardResponse, Earn.schema.getVerifiedEarnVaults.Response), TestApp.json(writeOnlyResponse, Earn.schema.getVerifiedEarnVaults.Response), ]) expect(defaultBody.data[0]).not.toHaveProperty('zone') expect(defaultBody.data[0]).not.toHaveProperty('zones') expect(unrelatedBody.data[0]).toMatchObject({ capabilities: { privateRouting: true, routerSwaps: true }, zone: null, zones: [], }) expect(publicBody.data[0]).toMatchObject({ capabilities: { privateRouting: true, routerSwaps: true }, zone: null, zones: [], }) expect(writeOnlyBody.data[0]).toMatchObject({ zone: null, zones: [] }) const vault = scopedBody.data[0] 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(wildcardBody.data[0]).toStrictEqual(vault) }, 90_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'returns null for an inaccessible legacy Zone route', async () => { const db = TestApp.database() await core_EarnVaults.upsert(db, { ...TestEarn.zoneVerifiedVault, privateInputTokens: [TestEarn.assetTokenAddress], privateOutputTokens: [TestEarn.assetTokenAddress], zones: [], }) const zone = { ...TestEarn.zone, contracts: { ...TestEarn.zone.contracts, earnRouter: { address: TestEarn.zoneRoute.earnRouter }, }, } const app = TestApp.create({ auth: false, cache, db, zones: [zone] }) const query = `chainId=${TestEarn.chain.id}&include=zone` const [detail, list] = await Promise.all([ app.request(`/v1/earn/vaults/${TestEarn.zoneVaultAddress}?${query}`), app.request(`/v1/earn/vaults/verified?${query}`), ]) expect([detail.status, list.status]).toStrictEqual([200, 200]) expect((await TestApp.json(detail, Earn.schema.getEarnVault.Response)).zone).toBeNull() expect( (await TestApp.json(list, Earn.schema.getVerifiedEarnVaults.Response)).data[0]?.zone, ).toBeNull() }, 90_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'returns curated fields for a public-only vault', async () => { const db = TestApp.database() const record = await core_EarnVaults.upsert(db, TestEarn.verifiedVault) const app = TestApp.create({ auth: false, cache, db, zones: [TestEarn.zone] }) const response = await app.request( `/v1/earn/vaults/verified?chainId=${TestEarn.verifiedVault.chainId}&include=access,capabilities,zone&limit=5`, ) expect(response.status).toBe(200) const body = await TestApp.json(response, Earn.schema.getVerifiedEarnVaults.Response) expect(body.nextCursor).toBeNull() expect(body.data).toHaveLength(1) const [vault] = body.data expect(vault).toBeDefined() if (!vault) return expect(vault.slug).toBe(record.slug) expect(vault).not.toHaveProperty('gateway') // The rate and the valuation are include-gated, and neither was requested. expect(vault).not.toHaveProperty('apy') expect(vault).not.toHaveProperty('tvl') const capabilities = vault.capabilities expect(capabilities).toBeDefined() if (!capabilities) return expect({ access: vault.access, assetTokenVerified: vault.assetToken.verified, description: vault.description, engine: vault.engine, id: vault.id, label: vault.label, privateRouting: capabilities.privateRouting, routerSwaps: capabilities.routerSwaps, shareTokenVerified: vault.shareToken.verified, vaultAddress: vault.vaultAddress, verified: vault.verified, zone: vault.zone, }).toMatchInlineSnapshot(` { "access": { "status": "allowlisted", }, "assetTokenVerified": false, "description": "Bridge test PATHUSD deposited into the canonical Earn v1 stack.", "engine": { "address": "0xd85fc943333a6cd7c0e3391acd54fc4e572f0baf", "type": "erc4626", "venue": "0xa8b4f0e69cd4b0e56b676343f02acd8c3b355322", }, "id": "0xf4ae63687d6753a78e7f551d2eda1d0d31a5ea3a", "label": "btPATHUSD Earn", "privateRouting": false, "routerSwaps": false, "shareTokenVerified": false, "vaultAddress": "0xf4ae63687d6753a78e7f551d2eda1d0d31a5ea3a", "verified": true, "zone": null, } `) }, 60_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'hides stale private fields without a configured router', async () => { const db = TestApp.database() await core_EarnVaults.upsert(db, { ...TestEarn.verifiedVault, privateInputTokens: [TestEarn.assetTokenAddress], privateOutputTokens: [TestEarn.assetTokenAddress], }) const app = TestApp.create({ auth: false, cache, db, zones: [TestEarn.zone] }) const response = await app.request( `/v1/earn/vaults/verified?chainId=${TestEarn.verifiedVault.chainId}&include=access,capabilities,zone&limit=5`, ) expect(response.status).toBe(200) const body = await TestApp.json(response, Earn.schema.getVerifiedEarnVaults.Response) expect(body.data[0]).toMatchObject({ capabilities: { privateRouting: false, routerSwaps: false }, verified: true, zone: null, }) expect(body.data[0]).not.toHaveProperty('gateway') }, 60_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'reuses live discovery across public endpoints', async () => { const db = TestApp.database() await core_EarnVaults.upsert(db, TestEarn.verifiedVault) // Its own cache: the assertion needs a cold discovery followed by a warm one. const app = TestApp.create({ auth: false, cache: { edge: false, store: Store.memory() }, db, zones: [TestEarn.zone], }) const detail = await app.request( `/v1/earn/vaults/${TestEarn.vaultAddress}?chainId=${TestEarn.chain.id}&include=access,capabilities,zone`, ) const verified = await app.request( `/v1/earn/vaults/verified?chainId=${TestEarn.chain.id}&include=access,capabilities,zone&limit=5`, ) expect([detail.status, verified.status]).toStrictEqual([200, 200]) expect(detail.headers.get('server-timing')).toContain('earn_vault_discovery') expect(verified.headers.get('server-timing')).not.toContain('earn_vault_discovery') }, 60_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'shares live enrichment across all three vault reads', async () => { const db = TestApp.database() await core_EarnVaults.upsert(db, TestEarn.verifiedVault) const app = TestApp.create({ auth: false, cache, db, zones: [TestEarn.zone] }) const cursor = Cursor.encode([ TestEarn.indexedDeployment.blockNumber, TestEarn.indexedDeployment.logIndex, ]) const query = `chainId=${TestEarn.chain.id}&apy.window=1h&include=tvl` const [all, verified, detail] = await Promise.all([ app.request(`/v1/earn/vaults?${query}&cursor=${encodeURIComponent(cursor)}&limit=5`), app.request(`/v1/earn/vaults/verified?${query}&limit=5`), app.request(`/v1/earn/vaults/${TestEarn.vaultAddress}?${query}`), ]) expect([all.status, verified.status, detail.status]).toStrictEqual([200, 200, 200]) for (const response of [all, verified, detail]) expect(response.headers.get('cache-control')).toBe( 'private, max-age=30, stale-while-revalidate=120', ) const [allBody, verifiedBody, vault] = await Promise.all([ TestApp.json(all, Earn.schema.getEarnVaults.Response), TestApp.json(verified, Earn.schema.getVerifiedEarnVaults.Response), TestApp.json(detail, Earn.schema.getEarnVault.Response), ]) const enriched = ({ apy, instantLiquidity, sharePrice, state, tvl }: EnrichedVault) => ({ apy, instantLiquidity, sharePrice, state, tvl, }) const rows = [allBody, verifiedBody].map((body) => body.data.find((candidate) => candidate.id === vault.id), ) expect(rows.every((row) => row !== undefined)).toBe(true) // The window promotes the rate and `include` selects the valuation, so all // three reads carry both fields. expect(['apy' in vault, 'tvl' in vault]).toStrictEqual([true, true]) for (const row of rows) if (row) expect(enriched(row)).toStrictEqual(enriched(vault)) // Every asset on these pages is USD-denominated, so no rate was consulted. expect([allBody.meta, verifiedBody.meta, vault.meta]).toStrictEqual([ undefined, undefined, undefined, ]) }, 90_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'applies the verified-vault address cursor', async () => { const db = TestApp.database() const vaultAddresses = [ TestEarn.vaultAddress, TestEarn.indexedDeployment.vaultAddress, ] as const for (const vaultAddress of vaultAddresses) await core_EarnVaults.upsert(db, { chainId: TestEarn.chain.id, description: null, label: 'Pagination vault', privateInputTokens: [], privateOutputTokens: [], vaultAddress, zones: [], }) const app = TestApp.create({ auth: false, cache, db, zones: [TestEarn.zone] }) const cursor = Cursor.encode([TestEarn.indexedDeployment.vaultAddress]) const response = await app.request( `/v1/earn/vaults/verified?chainId=${TestEarn.chain.id}&limit=5&cursor=${encodeURIComponent(cursor)}`, ) expect(response.status).toBe(200) const body = await TestApp.json(response, Earn.schema.getVerifiedEarnVaults.Response) expect(body.data.map((vault) => vault.id)).toStrictEqual([TestEarn.vaultAddress]) expect(body.nextCursor).toBeNull() }, 90_000, ) }) describe('GET /v1/earn/addresses/:address/positions', () => { test.runIf(Runtime.get().mode === 'testnet')( 'batches lifetime cash flows for multiple positions into one query', async () => { const tidx = Tidx.getClient({ chainId: TestEarn.chain.id, tidx: { auth: process.env.TIDX_AUTH, baseUrl: Runtime.get().tidxUrl }, }) let queryCount = 0 const client = new Proxy(tidx, { get(target, property, receiver) { if (property !== 'fetch') return Reflect.get(target, property, receiver) return (...options: Parameters) => { queryCount += 1 return target.fetch(...options) } }, }) const emptyVault = Schema.Address.parse(`0x${'11'.repeat(20)}`) const cashFlows = await Earn.getVaultLifetimeCashFlows(client, { account: TestEarn.earningsAddress, blockNumber: 1_000_000_000, vaults: [TestEarn.vaultAddress, emptyVault], }) expect(queryCount).toBe(1) expect(cashFlows.get(TestEarn.vaultAddress)).toMatchObject({ status: 'complete', totalDeposited: expect.stringMatching(/^[1-9]\d*$/), totalWithdrawn: expect.stringMatching(/^\d+$/), }) expect(cashFlows.get(emptyVault)).toStrictEqual({ status: 'complete', totalDeposited: '0', totalWithdrawn: '0', }) }, 60_000, ) test('returns chain selection errors', async () => { const app = TestApp.create({ auth: false, cache }) const invalid = await app.request( `/v1/earn/addresses/${TestEarn.positionAddress}/positions?chainId=abc`, ) const unsupported = await app.request( `/v1/earn/addresses/${TestEarn.positionAddress}/positions?chainId=999999`, ) expect([ (await TestApp.json(invalid, Schema.ErrorResponse)).error.code, (await TestApp.json(unsupported, Schema.ErrorResponse)).error.code, ]).toStrictEqual(['chain_id_invalid', 'chain_id_unsupported']) }) test('rejects a malformed account address', async () => { const response = await TestApp.create({ auth: false, cache }).request( '/v1/earn/addresses/not-an-address/positions', ) expect(response.status).toBe(400) expect(await TestApp.json(response, Schema.ErrorResponse)).toMatchObject({ error: { code: 'positions_invalid' }, }) }) test('returns an empty verified page without curated vaults', async () => { const app = TestApp.create({ auth: false, cache }) const response = await app.request( `/v1/earn/addresses/${TestEarn.positionAddress}/positions?include=earnings&verified=true`, ) expect(response.status).toBe(200) expect( await TestApp.json(response, Earn.schema.getEarnAddressPositions.Response), ).toStrictEqual({ data: [], nextCursor: null }) }) test.runIf(Runtime.get().mode === 'testnet')( 'lists non-zero verified positions for an account', async () => { const db = TestApp.database() // Valuation resolves through the curated verified list, so seed it. await TestApp.verifiedSeed(db, TestEarn.chain.id) await core_EarnVaults.upsert(db, TestEarn.verifiedVault) await core_EarnVaults.upsert(db, { ...TestEarn.verifiedVault, description: 'Active-earnings Earn deployment.', label: 'Active btPATHUSD Earn', vaultAddress: TestEarn.activeEarningsVaultAddress, }) const app = TestApp.create({ auth: false, cache, db, zones: [TestEarn.zone] }) const response = await app.request( `/v1/earn/addresses/${TestEarn.activeEarningsAddress}/positions?chainId=${TestEarn.chain.id}&include=earnings&verified=true&valuation.currency=USD`, ) expect(response.status).toBe(200) const body = await TestApp.json(response, Earn.schema.getEarnAddressPositions.Response) expect(body.data.every((position) => BigInt(position.shareAmount.amount) > 0n)).toBe(true) const row = body.data.find( (position) => position.vaultAddress === TestEarn.activeEarningsVaultAddress, ) expect(row).toBeDefined() if (!row) return // Amounts render in their own token's display currency and precision. expect(row.shareAmount.currency).toBe(row.shareToken.currency) expect(row.shareAmount.decimals).toBe(row.shareToken.decimals) expect(row.assetAmount.currency).toBe(row.assetToken.currency) expect(row.assetAmount.decimals).toBe(row.assetToken.decimals) expect(row.lifetimeCashFlows?.status).toMatch(/^(complete|incomplete_history)$/) // A USD-denominated asset values identically without consulting rates. expect(row.valuation).toStrictEqual({ amount: row.assetAmount.formatted, currency: 'USD' }) expect(body.meta).toBeUndefined() expect({ assetToken: row.assetToken.address, formattedShape: /^\d+(\.\d+)?$/.test(row.assetAmount.formatted), id: row.id, sharesHeld: BigInt(row.shareAmount.amount) > 0n, valued: BigInt(row.assetAmount.amount) > 0n, vaultAddress: row.vaultAddress, verified: row.verified, }).toMatchInlineSnapshot(` { "assetToken": "0x20c0000000000000000000000000000000000001", "formattedShape": true, "id": "0x3e4405801a1bee1b58394228ea0b44b667e222fc", "sharesHeld": true, "valued": true, "vaultAddress": "0x3e4405801a1bee1b58394228ea0b44b667e222fc", "verified": true, } `) }, 60_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'discovers held vaults from indexed share balances', async () => { const app = TestApp.create({ auth: false, cache, zones: [TestEarn.zone] }) const response = await app.request( `/v1/earn/addresses/${TestEarn.activeEarningsAddress}/positions?chainId=${TestEarn.chain.id}`, ) expect(response.status).toBe(200) const body = await TestApp.json(response, Earn.schema.getEarnAddressPositions.Response) expect(body.data.map((position) => position.vaultAddress)).toContain( TestEarn.activeEarningsVaultAddress, ) expect( body.data.every( (position) => position.id === position.vaultAddress && !position.verified && /^\d+$/.test(position.assetAmount.amount) && BigInt(position.shareAmount.amount) > 0n && position.assetToken.address.startsWith('0x20c0') && position.shareToken.address.startsWith('0x20c0'), ), ).toBe(true) // An address-keyset cursor excludes every vault at or before it. const cursor = Cursor.encode([TestEarn.activeEarningsVaultAddress]) const paged = await app.request( `/v1/earn/addresses/${TestEarn.activeEarningsAddress}/positions?chainId=${TestEarn.chain.id}&cursor=${encodeURIComponent(cursor)}`, ) expect(paged.status).toBe(200) const pagedBody = await TestApp.json(paged, Earn.schema.getEarnAddressPositions.Response) expect( pagedBody.data.every( (position) => position.vaultAddress > TestEarn.activeEarningsVaultAddress, ), ).toBe(true) }, 90_000, ) }) describe('GET /v1/earn/vaults/:vaultId', () => { test('rejects a malformed vault address', async () => { const app = TestApp.create({ auth: false, cache }) const response = await app.request('/v1/earn/vaults/not-an-address') expect(response.status).toBe(400) expect((await TestApp.json(response, Schema.ErrorResponse)).error.code).toBe('vault_id_invalid') }) test.runIf(Runtime.get().mode === 'testnet')( 'resolves an unregistered vault directly from chain state', async () => { const app = TestApp.create({ auth: false, cache, zones: [TestEarn.zone] }) const path = `/v1/earn/vaults/${TestEarn.vaultAddress}?chainId=${TestEarn.chain.id}` const [response, includedResponse] = await Promise.all([ app.request(path), app.request(`${path}&include=access,capabilities,zone`), ]) expect([response.status, includedResponse.status]).toStrictEqual([200, 200]) const vault = await TestApp.json(response, Earn.schema.getEarnVault.Response) expect(vault).not.toHaveProperty('gateway') expect(vault).not.toHaveProperty('access') expect(vault).not.toHaveProperty('capabilities') expect(vault).not.toHaveProperty('zone') const included = await TestApp.json(includedResponse, Earn.schema.getEarnVault.Response) expect({ access: included.access, description: included.description, engineType: included.engine.type, id: included.id, label: included.label, slug: included.slug, vaultAddress: included.vaultAddress, verified: included.verified, zone: included.zone, }).toMatchInlineSnapshot(` { "access": { "status": "allowlisted", }, "description": null, "engineType": "erc4626", "id": "0xf4ae63687d6753a78e7f551d2eda1d0d31a5ea3a", "label": "Tempo Earn Live Vault (Earn)", "slug": null, "vaultAddress": "0xf4ae63687d6753a78e7f551d2eda1d0d31a5ea3a", "verified": false, "zone": null, } `) }, 60_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'includes token logos only when requested', async () => { const db = TestApp.database() const logoUri = 'https://assets.tempo.xyz/tokens/btpathusd.svg' await TestApp.verifiedSeed(db, TestEarn.chain.id, { tokens: [ { address: TestEarn.assetTokenAddress, currency: 'USD', decimals: 6, logoUri, name: 'Bridge Test PATHUSD', symbol: 'btPATHUSD', }, ], }) const app = TestApp.create({ auth: false, cache, db, verifiedTokens: {}, zones: [TestEarn.zone], }) const path = `/v1/earn/vaults/${TestEarn.vaultAddress}?chainId=${TestEarn.chain.id}` const [defaultResponse, includedResponse] = await Promise.all([ app.request(path), app.request(`${path}&include=token.logoUri`), ]) expect([defaultResponse.status, includedResponse.status]).toEqual([200, 200]) const [vault, included] = await Promise.all([ TestApp.json(defaultResponse, Earn.schema.getEarnVault.Response), TestApp.json(includedResponse, Earn.schema.getEarnVault.Response), ]) expect(vault.assetToken).not.toHaveProperty('logoUri') expect(included.assetToken.logoUri).toBe(logoUri) const timing = includedResponse.headers.get('server-timing') ?? '' expect(timing).not.toContain('token_metadata') expect(timing).toContain('token_logo') expect(timing).toContain('token_logo_uri') }, 90_000, ) test('rejects windows outside the selectable set', async () => { const app = TestApp.create({ auth: false, cache }) const statuses: number[] = [] const codes: string[] = [] for (const window of ['0h', '24h', '31d', '7x', '90d', '168h']) { const response = await app.request( `/v1/earn/vaults/0x${'aa'.repeat(20)}?apy.window=${window}`, ) statuses.push(response.status) codes.push((await TestApp.json(response, Schema.ErrorResponse)).error.code) } expect(statuses).toStrictEqual([400, 400, 400, 400, 400, 400]) expect(codes).toStrictEqual([ 'query_invalid', 'query_invalid', 'query_invalid', 'query_invalid', 'query_invalid', 'query_invalid', ]) }) test.runIf(Runtime.get().mode === 'testnet')( 'enriches the vault with live state, share price, and liquidity', async () => { // No verified tokens, so the asset has no priced display currency. const app = TestApp.create({ auth: false, cache, verifiedTokens: false, zones: [TestEarn.zone], }) const response = await app.request( `/v1/earn/vaults/${TestEarn.vaultAddress}?chainId=${TestEarn.chain.id}&include=tvl`, ) expect(response.status).toBe(200) expect(response.headers.get('cache-control')).toBe( 'private, max-age=30, stale-while-revalidate=120', ) const vault = await TestApp.json(response, Earn.schema.getEarnVault.Response) // An unverified asset has no display currency, so no rate set is usable. expect(vault.meta).toBeUndefined() expect({ depositsPaused: vault.state.depositsPaused, engineType: vault.engine.type, feesActive: vault.state.feesActive, // The pinned venue exposes no withdrawal-limit view, so liquidity is // absent rather than a restatement of the backing. instantLiquidity: vault.instantLiquidity, instantLiquidityValue: vault.instantLiquidityValue, isAccountingAligned: vault.state.isAccountingAligned, openRedeemRequestCount: vault.state.openRedeemRequestCount, pricedPerShare: vault.sharePrice !== null && BigInt(vault.sharePrice.amount) > 0n, sharePrice: { currency: vault.sharePrice?.currency, decimals: vault.sharePrice?.decimals }, tvl: vault.tvl, }).toMatchInlineSnapshot(` { "depositsPaused": false, "engineType": "erc4626", "feesActive": true, "instantLiquidity": null, "instantLiquidityValue": null, "isAccountingAligned": true, "openRedeemRequestCount": 0, "pricedPerShare": true, "sharePrice": { "currency": "USD", "decimals": 6, }, "tvl": null, } `) }, 60_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'reports no instant liquidity for a Veda engine', async () => { const app = TestApp.create({ auth: false, cache, zones: [TestEarn.zone] }) const response = await app.request( `/v1/earn/vaults/${TestEarn.vedaVaultAddress}?chainId=${TestEarn.chain.id}`, ) expect(response.status).toBe(200) const vault = await TestApp.json(response, Earn.schema.getEarnVault.Response) expect({ engineType: vault.engine.type, instantLiquidity: vault.instantLiquidity, }).toMatchInlineSnapshot(` { "engineType": "veda", "instantLiquidity": null, } `) }, 60_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'measures the rate over the selected window', async () => { const app = TestApp.create({ auth: false, cache, zones: [TestEarn.zone] }) const response = await app.request( `/v1/earn/vaults/${TestEarn.vaultAddress}?chainId=${TestEarn.chain.id}&apy.window=1d`, ) expect(response.status).toBe(200) // Selecting a window requests the rate, so no `include` is needed. const apy = (await TestApp.json(response, Earn.schema.getEarnVault.Response)).apy expect(apy).toBeDefined() expect(apy).not.toBeNull() if (!apy) return expect({ bucketed: apy.asOf === new Date(apy.asOf).toISOString() && apy.asOf.endsWith(':00.000Z'), measured: /^-?\d+\.\d{6}$/.test(apy.net), methodology: apy.methodology, // The selected window is echoed back so a caller can pin what it read. window: apy.window, }).toMatchInlineSnapshot(` { "bucketed": true, "measured": true, "methodology": "share-price-growth-annualized:v1", "window": "1d", } `) }, 90_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'leaves the rate unmeasured when the window starts before deployment', async () => { // This vault was deployed on 2026-07-23, so the pinned window begins before deployment. vi.useFakeTimers({ now: new Date('2026-07-24T10:00:00Z'), toFake: ['Date'] }) try { const app = TestApp.create({ auth: false, cache, zones: [TestEarn.zone] }) const response = await app.request( `/v1/earn/vaults/${TestEarn.vaultAddress}?chainId=${TestEarn.chain.id}&apy.window=30d`, ) expect(response.status).toBe(200) const vault = await TestApp.json(response, Earn.schema.getEarnVault.Response) expect(vault.apy).toBeNull() } finally { vi.useRealTimers() } }, 60_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'defaults to a seven-day window', async () => { const app = TestApp.create({ auth: false, cache, zones: [TestEarn.zone] }) const path = `/v1/earn/vaults/${TestEarn.vaultAddress}?chainId=${TestEarn.chain.id}` const request = { headers: { 'cache-control': 'no-store' } } const [defaulted, explicit] = await Promise.all([ app.request(`${path}&include=apy`, request), app.request(`${path}&apy.window=7d`, request), ]) expect([defaulted.status, explicit.status]).toStrictEqual([200, 200]) const [a, b] = await Promise.all([defaulted.json(), explicit.json()]) expect(a).toStrictEqual(b) }, 60_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'measures rates and values assets only when requested', async () => { const app = TestApp.create({ auth: false, cache, zones: [TestEarn.zone] }) const path = `/v1/earn/vaults/${TestEarn.vaultAddress}?chainId=${TestEarn.chain.id}` const request = { headers: { 'cache-control': 'no-store' } } const [bare, requested] = await Promise.all([ app.request(path, request), app.request(`${path}&include=apy,tvl`, request), ]) expect([bare.status, requested.status]).toStrictEqual([200, 200]) const [bareVault, requestedVault] = await Promise.all([ TestApp.json(bare, Earn.schema.getEarnVault.Response), TestApp.json(requested, Earn.schema.getEarnVault.Response), ]) // `earn_apy` covers the indexer boundary query and the archive quotes, // `valuation_rates` the FX rate set. A name appears only when its resolver // ran, so an absent name proves the upstream reads never happened. const shape = ( response: globalThis.Response, vault: z.output, ) => { const timing = response.headers.get('server-timing') ?? '' return { apyField: 'apy' in vault, apyTiming: timing.includes('earn_apy'), valuation: vault.meta !== undefined, ratesTiming: timing.includes('valuation_rates'), tvlField: 'tvl' in vault, } } expect({ bare: shape(bare, bareVault), requested: shape(requested, requestedVault), }).toStrictEqual({ bare: { apyField: false, apyTiming: false, valuation: false, ratesTiming: false, tvlField: false, }, requested: { apyField: true, apyTiming: true, // The asset is unverified here, so it has no display currency to // convert and the loaded rate set goes unused. valuation: false, ratesTiming: true, tvlField: true, }, }) }, 90_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'values total assets in USD for a verified asset', async () => { const db = TestApp.database() await TestApp.verifiedSeed(db, TestEarn.chain.id, { tokens: [ { address: TestEarn.assetTokenAddress, currency: 'USD', decimals: 6, name: 'Bridge Test PATHUSD', symbol: 'btPATHUSD', }, ], }) const app = TestApp.create({ auth: false, cache, db, // Read the curated list on every request; the neighbouring provenance // test seeds the same chain with a different currency. verifiedTokens: { refreshMs: 0 }, zones: [TestEarn.zone], }) const response = await app.request( `/v1/earn/vaults/${TestEarn.vaultAddress}?chainId=${TestEarn.chain.id}&include=tvl`, ) expect(response.status).toBe(200) const vault = await TestApp.json(response, Earn.schema.getEarnVault.Response) const tvl = vault.tvl expect(tvl).toBeDefined() expect(tvl).not.toBeNull() if (!tvl) return expect({ currency: tvl.currency, decimals: tvl.decimals, // A USD asset at six decimals values one-for-one against its backing. tracksTotalAssets: BigInt(tvl.amount) === BigInt(vault.state.totalAssets), }).toMatchInlineSnapshot(` { "currency": "USD", "decimals": 6, "tracksTotalAssets": true, } `) }, 90_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'reports rate provenance when the asset needs conversion', async () => { const db = TestApp.database() // An AUD-denominated asset forces an FX conversion into the USD TVL // denomination, which is the only case that consults a rate set. await TestApp.verifiedSeed(db, TestEarn.chain.id, { tokens: [ { address: TestEarn.assetTokenAddress, currency: 'AUD', decimals: 6, name: 'Bridge Test PATHUSD', symbol: 'btPATHUSD', }, ], }) const app = TestApp.create({ auth: false, cache, db, // Read the curated list on every request; the neighbouring TVL test seeds // the same chain with a different currency. verifiedTokens: { refreshMs: 0 }, zones: [TestEarn.zone], }) const response = await app.request( `/v1/earn/vaults/${TestEarn.vaultAddress}?chainId=${TestEarn.chain.id}&include=tvl`, ) expect(response.status).toBe(200) const vault = await TestApp.json(response, Earn.schema.getEarnVault.Response) expect({ // The fixture prices AUD at 1.6 and USD at 1.0 against the EUR base. convertedFromAud: BigInt(vault.tvl?.amount ?? '0') === (BigInt(vault.state.totalAssets) * 625n) / 1000n, valuation: vault.meta?.valuation, tvlCurrency: vault.tvl?.currency, }).toMatchInlineSnapshot(` { "convertedFromAud": true, "tvlCurrency": "USD", "valuation": { "asOf": "2026-01-01T00:00:00.000Z", "basis": "nominal", "source": "ecb", }, } `) }, 90_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'returns 404 for an incompatible contract', async () => { const app = TestApp.create({ auth: false, cache, zones: [TestEarn.zone] }) const response = await app.request( `/v1/earn/vaults/${TestEarn.assetTokenAddress}?chainId=${TestEarn.chain.id}`, ) expect(response.status).toBe(404) expect((await TestApp.json(response, Schema.ErrorResponse)).error.code).toBe( 'earn_vault_not_found', ) }, 60_000, ) }) describe('GET /v1/earn/vaults/:vaultId/positions/:address', () => { test('rejects invalid as-of timestamps', async () => { const app = TestApp.create({ auth: false, cache }) const [malformed, unix] = await Promise.all([ app.request( `/v1/earn/vaults/${TestEarn.vaultAddress}/positions/${TestEarn.positionAddress}?asOf=not-a-time`, ), app.request( `/v1/earn/vaults/${TestEarn.vaultAddress}/positions/${TestEarn.positionAddress}?asOf=1787312400`, ), ]) for (const response of [malformed, unix]) { expect(response.status).toBe(400) expect(await TestApp.json(response, Schema.ErrorResponse)).toMatchObject({ error: { code: 'query_invalid' }, }) } }) test('returns chain selection errors', async () => { const app = TestApp.create({ auth: false, cache }) const invalid = await app.request( `/v1/earn/vaults/${TestEarn.vaultAddress}/positions/${TestEarn.positionAddress}?chainId=abc`, ) const unsupported = await app.request( `/v1/earn/vaults/${TestEarn.vaultAddress}/positions/${TestEarn.positionAddress}?chainId=999999`, ) expect([ (await TestApp.json(invalid, Schema.ErrorResponse)).error.code, (await TestApp.json(unsupported, Schema.ErrorResponse)).error.code, ]).toStrictEqual(['chain_id_invalid', 'chain_id_unsupported']) }) test('reaches validation on an RPC-only chain', async () => { const response = await TestApp.create({ auth: false, cache, rpc: { url: { 31_337: 'http://127.0.0.1:1' } }, tidx: {}, }).request('/v1/earn/vaults/not-an-address/positions/not-an-address?chainId=31337') expect(response.status).toBe(400) expect(await TestApp.json(response, Schema.ErrorResponse)).toMatchObject({ error: { code: 'position_invalid' }, }) }) test('requires TIDX for historical reads on an RPC-only chain', async () => { const response = await TestApp.create({ auth: false, cache, rpc: { url: { 31_337: 'http://127.0.0.1:1' } }, tidx: {}, }).request( `/v1/earn/vaults/${TestEarn.vaultAddress}/positions/${TestEarn.positionAddress}?chainId=31337&asOf=2026-07-21T09%3A00Z`, ) expect(response.status).toBe(400) expect(await TestApp.json(response, Schema.ErrorResponse)).toMatchObject({ error: { code: 'chain_id_unsupported' }, }) }) test('validates malformed historical timestamps on an RPC-only chain', async () => { const response = await TestApp.create({ auth: false, cache, rpc: { url: { 31_337: 'http://127.0.0.1:1' } }, tidx: {}, }).request( `/v1/earn/vaults/${TestEarn.vaultAddress}/positions/${TestEarn.positionAddress}?chainId=31337&asOf=not-a-time`, ) expect(response.status).toBe(400) expect(await TestApp.json(response, Schema.ErrorResponse)).toMatchObject({ error: { code: 'query_invalid' }, }) }) test('validates the complete historical query on an RPC-only chain', async () => { const app = TestApp.create({ auth: false, cache, rpc: { url: { 31_337: 'http://127.0.0.1:1' } }, tidx: {}, }) const path = `/v1/earn/vaults/${TestEarn.vaultAddress}/positions/${TestEarn.positionAddress}?chainId=31337&asOf=2026-07-21T09%3A00Z` const [duplicate, unknown] = await Promise.all([ app.request(`${path}&asOf=2026-07-21T10%3A00Z`), app.request(`${path}&unknown=1`), ]) for (const response of [duplicate, unknown]) { expect(response.status).toBe(400) expect(await TestApp.json(response, Schema.ErrorResponse)).toMatchObject({ error: { code: 'query_invalid' }, }) } }) test('rejects malformed vault position parameters', async () => { const response = await TestApp.create({ auth: false, cache }).request( '/v1/earn/vaults/not-an-address/positions/not-an-address', ) expect(response.status).toBe(400) expect(await TestApp.json(response, Schema.ErrorResponse)).toMatchObject({ error: { code: 'position_invalid' }, }) }) test.runIf(Runtime.get().mode === 'testnet')( 'returns the current position through viem', async () => { const app = TestApp.create({ auth: false, cache }) const response = await app.request( `/v1/earn/vaults/${TestEarn.vaultAddress}/positions/${TestEarn.positionAddress}?chainId=${TestEarn.chain.id}`, ) expect(response.status).toBe(200) expect(response.headers.get('cache-control')).toBe('no-store') const position = await TestApp.json(response, Earn.schema.getEarnVaultPosition.Response) expect({ account: position.account, amounts: [ position.assetAllowance, position.assetBalance, position.shareAllowance, position.shareBalance, position.value, ].every((amount) => /^\d+$/.test(amount)), assetToken: position.assetToken, id: position.id, shareToken: position.shareToken.startsWith('0x20c0'), }).toMatchInlineSnapshot(` { "account": "0xbe058e1c4df8a4366a387bf595b284246a93039e", "amounts": true, "assetToken": "0x20c000000000000000000000ff04042ee92fd449", "id": "0xbe058e1c4df8a4366a387bf595b284246a93039e", "shareToken": true, } `) }, 60_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'returns not found for an address without an Earn vault', async () => { const response = await TestApp.create({ auth: false, cache }).request( `/v1/earn/vaults/0x1111111111111111111111111111111111111111/positions/${TestEarn.positionAddress}?chainId=${TestEarn.chain.id}`, ) expect(response.status).toBe(404) expect(await TestApp.json(response, Schema.ErrorResponse)).toMatchObject({ error: { code: 'earn_vault_not_found' }, }) }, 60_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'returns a position as of an indexed ISO 8601 timestamp', async () => { const runtime = Runtime.get() const tidx = Tidx.getClient({ chainId: runtime.chainId, tidx: { auth: process.env.TIDX_AUTH, baseUrl: runtime.tidxUrl }, }) const indexed = await tidx.fetch({ engine: 'clickhouse', query: 'SELECT num, timestamp FROM blocks ORDER BY num DESC LIMIT 1 OFFSET 100', }) const blockNumber = Value.toNumber(indexed.rows[0]?.['num']) const asOf = Value.toIsoDateTime(indexed.rows[0]?.['timestamp']) if (blockNumber === undefined || asOf === undefined) throw new Error('expected an indexed block') const response = await TestApp.create({ auth: false, cache }).request( `/v1/earn/vaults/${TestEarn.vaultAddress}/positions/${TestEarn.positionAddress}?chainId=${TestEarn.chain.id}&asOf=${encodeURIComponent(asOf)}`, ) expect(response.status).toBe(200) const position = await TestApp.json(response, Earn.schema.getEarnVaultPosition.Response) expect(position.asOf).toBe(asOf) expect(position.block?.number).toBeGreaterThanOrEqual(blockNumber) expect(position.block?.timestamp).toMatch(/^\d{4}-\d{2}-\d{2}T/) expect(Date.parse(position.block?.timestamp ?? '')).toBeLessThanOrEqual(Date.parse(asOf)) }, 60_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'returns not found before the vault was deployed', async () => { const runtime = Runtime.get() const tidx = Tidx.getClient({ chainId: runtime.chainId, tidx: { auth: process.env.TIDX_AUTH, baseUrl: runtime.tidxUrl }, }) const indexed = await tidx.fetch({ engine: 'clickhouse', query: 'SELECT timestamp FROM blocks ORDER BY num ASC LIMIT 1', }) const asOf = Value.toIsoDateTime(indexed.rows[0]?.['timestamp']) if (asOf === undefined) throw new Error('expected the pre-deployment block to be indexed') const response = await TestApp.create({ auth: false, cache }).request( `/v1/earn/vaults/${TestEarn.indexedDeployment.vaultAddress}/positions/${TestEarn.positionAddress}?chainId=${TestEarn.chain.id}&asOf=${encodeURIComponent(asOf)}`, ) expect(response.status).toBe(404) expect(await TestApp.json(response, Schema.ErrorResponse)).toMatchObject({ error: { code: 'earn_vault_not_found' }, }) }, 60_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'returns not found for a deployed incompatible contract at a historical block', async () => { const runtime = Runtime.get() const tidx = Tidx.getClient({ chainId: runtime.chainId, tidx: { auth: process.env.TIDX_AUTH, baseUrl: runtime.tidxUrl }, }) const indexed = await tidx.fetch({ engine: 'clickhouse', query: 'SELECT timestamp FROM blocks ORDER BY num DESC LIMIT 1', }) const asOf = Value.toIsoDateTime(indexed.rows[0]?.['timestamp']) if (asOf === undefined) throw new Error('expected an indexed block') const response = await TestApp.create({ auth: false, cache }).request( `/v1/earn/vaults/${TestEarn.assetTokenAddress}/positions/${TestEarn.positionAddress}?chainId=${TestEarn.chain.id}&asOf=${encodeURIComponent(asOf)}`, ) expect(response.status).toBe(404) expect(await TestApp.json(response, Schema.ErrorResponse)).toMatchObject({ error: { code: 'earn_vault_not_found' }, }) }, 60_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'rejects timestamps beyond indexed coverage', async () => { const asOf = new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString() const response = await TestApp.create({ auth: false, cache }).request( `/v1/earn/vaults/${TestEarn.vaultAddress}/positions/${TestEarn.positionAddress}?chainId=${TestEarn.chain.id}&asOf=${encodeURIComponent(asOf)}`, ) expect(response.status).toBe(400) expect(await TestApp.json(response, Schema.ErrorResponse)).toMatchObject({ error: { code: 'query_invalid', message: `Indexed history does not yet cover ${asOf}.`, }, }) }, 60_000, ) }) describe('GET /v1/earn/vaults/:vaultId/earnings/:address', () => { test('returns chain selection errors', async () => { const app = TestApp.create({ auth: false, cache }) const invalid = await app.request( `/v1/earn/vaults/${TestEarn.vaultAddress}/earnings/${TestEarn.earningsAddress}?chainId=abc`, ) const unsupported = await app.request( `/v1/earn/vaults/${TestEarn.vaultAddress}/earnings/${TestEarn.earningsAddress}?chainId=999999`, ) expect([ (await TestApp.json(invalid, Schema.ErrorResponse)).error.code, (await TestApp.json(unsupported, Schema.ErrorResponse)).error.code, ]).toStrictEqual(['chain_id_invalid', 'chain_id_unsupported']) }) test('rejects malformed vault earnings parameters', async () => { const response = await TestApp.create({ auth: false, cache }).request( '/v1/earn/vaults/not-an-address/earnings/not-an-address', ) expect(response.status).toBe(400) expect(await TestApp.json(response, Schema.ErrorResponse)).toMatchObject({ error: { code: 'earnings_invalid' }, }) }) test.runIf(Runtime.get().mode === 'testnet')( 'returns lifetime return through indexed vault events', async () => { const app = TestApp.create({ auth: false, cache }) const response = await app.request( `/v1/earn/vaults/${TestEarn.vaultAddress}/earnings/${TestEarn.earningsAddress}?chainId=${TestEarn.chain.id}`, ) expect(response.status).toBe(200) const earnings = await TestApp.json(response, Earn.schema.getEarnVaultEarnings.Response) if (earnings.status !== 'complete') throw new Error('Expected complete earnings history.') expect(earnings).toMatchObject({ account: TestEarn.earningsAddress, assetToken: TestEarn.assetTokenAddress, currentValue: expect.stringMatching(/^\d+$/), id: TestEarn.earningsAddress, lifetimeEarnings: expect.stringMatching(/^-?\d+$/), period: 'lifetime', status: 'complete', totalDeposited: expect.stringMatching(/^\d+$/), totalWithdrawn: expect.stringMatching(/^\d+$/), }) }, 60_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'returns earnings on shares still held', async () => { const app = TestApp.create({ auth: false, cache }) const response = await app.request( `/v1/earn/vaults/${TestEarn.activeEarningsVaultAddress}/earnings/${TestEarn.activeEarningsAddress}?chainId=${TestEarn.chain.id}&period=active`, ) expect(response.status).toBe(200) const earnings = await TestApp.json(response, Earn.schema.getEarnVaultEarnings.Response) if (earnings.status !== 'complete' || earnings.period !== 'active') throw new Error('Expected complete active earnings history.') expect(earnings).toMatchObject({ account: TestEarn.activeEarningsAddress, activeEarnings: expect.stringMatching(/^-?\d+$/), period: 'active', status: 'complete', }) expect(earnings).not.toHaveProperty('lifetimeEarnings') }, 60_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'returns cash-flow-adjusted trailing earnings', async () => { const app = TestApp.create({ auth: false, cache }) const response = await app.request( `/v1/earn/vaults/${TestEarn.activeEarningsVaultAddress}/earnings/${TestEarn.activeEarningsAddress}?chainId=${TestEarn.chain.id}&period=30d`, ) expect(response.status).toBe(200) const earnings = await TestApp.json(response, Earn.schema.getEarnVaultEarnings.Response) if (earnings.status !== 'complete' || earnings.period !== '30d') throw new Error('Expected complete trailing earnings history.') expect(earnings).toMatchObject({ account: TestEarn.activeEarningsAddress, period: '30d', status: 'complete', windowEarnings: expect.stringMatching(/^-?\d+$/), }) expect(earnings).not.toHaveProperty('activeEarnings') expect(earnings).not.toHaveProperty('lifetimeEarnings') }, 60_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'omits lifetime earnings when venue shares make the cost basis incomplete', async () => { const app = TestApp.create({ auth: false, cache }) const response = await app.request( `/v1/earn/vaults/${TestEarn.vaultAddress}/earnings/${TestEarn.incompleteEarningsAddress}?chainId=${TestEarn.chain.id}`, ) expect(response.status).toBe(200) const earnings = await TestApp.json(response, Earn.schema.getEarnVaultEarnings.Response) expect(earnings.status).toBe('incomplete_cost_basis') expect(earnings).not.toHaveProperty('lifetimeEarnings') }, 60_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'returns not found for an address without an Earn vault', async () => { const response = await TestApp.create({ auth: false, cache }).request( `/v1/earn/vaults/0x1111111111111111111111111111111111111111/earnings/${TestEarn.earningsAddress}?chainId=${TestEarn.chain.id}`, ) expect(response.status).toBe(404) expect(await TestApp.json(response, Schema.ErrorResponse)).toMatchObject({ error: { code: 'earn_vault_not_found' }, }) }, 60_000, ) }) describe('GET /v1/earn/vaults', () => { test.runIf(Runtime.get().mode === 'testnet')( 'extracts an indexed deployment and applies onchain filters', async () => { const db = TestApp.database() const record = await core_EarnVaults.upsert(db, TestEarn.indexedVerifiedVault) const app = TestApp.create({ auth: false, cache, db, zones: [TestEarn.zone] }) const query = new URLSearchParams({ asset: TestEarn.assetTokenAddress, capability: 'deposit,exactWithdraw', chainId: String(TestEarn.chain.id), cursor: Cursor.encode([ TestEarn.indexedDeployment.blockNumber, TestEarn.indexedDeployment.logIndex + 1, ]), 'engine.type': 'erc4626', limit: '5', }) const response = await app.request(`/v1/earn/vaults?${query}`) expect(response.status).toBe(200) const timing = response.headers.get('server-timing') ?? '' expect(timing).not.toContain('token_metadata') expect(timing).not.toContain('token_logo') // Neither the rate measurement nor the FX rate set is reached on a page // that asked for neither. expect(timing).not.toContain('earn_apy') expect(timing).not.toContain('valuation_rates') const body = await TestApp.json(response, Earn.schema.getEarnVaults.Response) const vault = body.data.find( (candidate) => candidate.vaultAddress === TestEarn.indexedDeployment.vaultAddress, ) expect(vault).toBeDefined() if (!vault) return expect(vault.slug).toBe(record.slug) expect(vault.assetToken).not.toHaveProperty('logoUri') expect(vault.shareToken).not.toHaveProperty('logoUri') expect(vault).not.toHaveProperty('gateway') expect(vault).not.toHaveProperty('access') expect(vault).not.toHaveProperty('apy') expect(vault).not.toHaveProperty('capabilities') expect(vault).not.toHaveProperty('tvl') expect(vault).not.toHaveProperty('zone') query.set('include', 'access,apy,capabilities,token.logoUri,tvl,zone') const includedResponse = await app.request(`/v1/earn/vaults?${query}`) expect(includedResponse.status).toBe(200) const includedTiming = includedResponse.headers.get('server-timing') ?? '' expect(includedTiming).toContain('token_logo') expect(includedTiming).toContain('earn_apy') expect(includedTiming).toContain('valuation_rates') const includedBody = await TestApp.json(includedResponse, Earn.schema.getEarnVaults.Response) const included = includedBody.data.find( (candidate) => candidate.vaultAddress === TestEarn.indexedDeployment.vaultAddress, ) expect(included).toBeDefined() if (!included) return expect(['apy' in included, 'tvl' in included]).toStrictEqual([true, true]) expect({ access: included.access, assetToken: { address: included.assetToken.address, decimals: included.assetToken.decimals, name: included.assetToken.name, symbol: included.assetToken.symbol, }, capabilities: included.capabilities, description: included.description, engine: included.engine, id: included.id, label: included.label, shareToken: { address: included.shareToken.address, decimals: included.shareToken.decimals, name: included.shareToken.name, symbol: included.shareToken.symbol, }, vaultAddress: included.vaultAddress, verified: included.verified, }).toMatchInlineSnapshot(` { "access": { "status": "allowlisted", }, "assetToken": { "address": "0x20c000000000000000000000ff04042ee92fd449", "decimals": 6, "name": "Bridge Test PATHUSD", "symbol": "btPATHUSD", }, "capabilities": { "asyncRedeem": false, "boundedRedeem": true, "deposit": true, "exactWithdraw": true, "inKindDeposit": true, "privateRouting": false, "redeem": true, "routerSwaps": false, }, "description": "Indexed Bridge Test PATHUSD Earn deployment.", "engine": { "address": "0x3bba4aa05eee3c6e568e0e60185a4eb630927928", "type": "erc4626", "venue": "0xa8b4f0e69cd4b0e56b676343f02acd8c3b355322", }, "id": "0xd4a673d20aeac2773d340ed595c497d460663ca2", "label": "Indexed btPATHUSD Earn", "shareToken": { "address": "0x20c0000000000000000000006ee48bc223de1c5b", "decimals": 6, "name": "Tempo Earn Live Vault (Earn)", "symbol": "teLIVEE", }, "vaultAddress": "0xd4a673d20aeac2773d340ed595c497d460663ca2", "verified": true, } `) }, 60_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'traverses live indexed deployments from an opaque cursor', async () => { const app = TestApp.create({ auth: false, cache, zones: [TestEarn.zone] }) const cursor = Cursor.encode([ TestEarn.indexedDeployment.blockNumber, TestEarn.indexedDeployment.logIndex, ]) const response = await app.request( `/v1/earn/vaults?chainId=${TestEarn.chain.id}&cursor=${encodeURIComponent(cursor)}&limit=5`, ) expect(response.status).toBe(200) const body = await TestApp.json(response, Earn.schema.getEarnVaults.Response) expect({ ids: body.data.map((vault) => vault.id), nextCursor: body.nextCursor, valid: body.data.every( (vault) => vault.vaultAddress === vault.id && vault.assetToken.address.startsWith('0x20c0') && vault.engine.address.startsWith('0x') && vault.shareToken.address.startsWith('0x20c0') && vault.slug === null && !vault.verified, ), }).toMatchInlineSnapshot(` { "ids": [ "0xf4ae63687d6753a78e7f551d2eda1d0d31a5ea3a", ], "nextCursor": null, "valid": true, } `) }, 90_000, ) }) describe('OpenAPI', () => { test('publishes the flattened vault shape', async () => { const app = TestApp.create({ auth: false, cache }) const spec = (await (await app.request('/openapi.json')).json()) as OpenApiDocument const operation = spec.paths['/v1/earn/vaults']?.get const parameters = operation?.parameters?.map((parameter) => parameter.name) const response = JSON.stringify(operation?.responses?.['200']) expect(Object.keys(spec.paths)).toEqual( expect.arrayContaining([ '/v1/earn/addresses/{address}/positions', '/v1/earn/vaults', '/v1/earn/vaults/verified', '/v1/earn/vaults/{vaultId}', '/v1/earn/vaults/{vaultId}/earnings/{address}', '/v1/earn/vaults/{vaultId}/positions/{address}', ]), ) expect(parameters).toContain('apy.window') expect(parameters).toContain('engine.type') expect(parameters).toContain('include') expect(parameters).not.toContain('engine.kind') expect(parameters).not.toContain('status') expect(response).toContain('"assetToken"') expect(response).toContain('"engine"') expect(response).toContain('"type"') expect(response).toContain('"vaultAddress"') expect(response).toContain('"zone"') expect(response).toContain('"zones"') expect(response).toContain('"deploymentBlock"') expect(response).toContain('"earnRouter"') expect(response).toContain('"apy"') expect(response).toContain('"instantLiquidity"') expect(response).toContain('"sharePrice"') expect(response).toContain('"state"') expect(response).toContain('"tvl"') expect(response).toContain('"valuation"') expect(response).not.toContain('"adapter"') expect(response).not.toContain('"gateway"') expect(response).not.toContain('"product"') }) test('documents the rate window parameter', async () => { const app = TestApp.create({ auth: false, cache }) const spec = (await (await app.request('/openapi.json')).json()) as OpenApiDocument const window = spec.paths['/v1/earn/vaults']?.get?.parameters?.find( (parameter) => parameter.name === 'apy.window', ) expect(window?.schema?.enum).toStrictEqual(['1h', '1d', '7d', '30d']) expect(window?.description).toContain('includes `apy`') expect(window?.schema?.examples).toStrictEqual(['7d']) }) test('documents the include-gated vault fields', async () => { const app = TestApp.create({ auth: false, cache }) const spec = (await (await app.request('/openapi.json')).json()) as OpenApiDocument const parameters = spec.paths['/v1/earn/vaults']?.get?.parameters ?? [] const include = parameters.find((parameter) => parameter.name === 'include') expect(include?.description).toContain('`apy,tvl`') expect(JSON.stringify(include?.schema)).toContain('"apy"') expect(JSON.stringify(include?.schema)).toContain('"tvl"') expect(JSON.stringify(include?.schema)).toContain('"zones"') expect(JSON.stringify(spec.paths['/v1/earn/vaults/{vaultId}']?.get?.responses)).toContain( 'Instant liquidity valued in USD', ) }) test('documents vault-position inputs and responses', async () => { const app = TestApp.create({ auth: false, cache }) const spec = (await (await app.request('/openapi.json')).json()) as OpenApiDocument const operation = spec.paths['/v1/earn/vaults/{vaultId}/positions/{address}']?.get const address = operation?.parameters?.find((parameter) => parameter.name === 'address') const asOf = operation?.parameters?.find((parameter) => parameter.name === 'asOf') const errors = errorSchemas(spec, operation) const response = JSON.stringify(operation?.responses) const vaultId = operation?.parameters?.find((parameter) => parameter.name === 'vaultId') expect(errors).toContain('"chain_id_invalid"') expect(errors).toContain('"chain_id_unsupported"') expect(errors).toContain('"position_invalid"') expect(errors).toContain('"query_invalid"') expect(errors).toContain('"earn_vault_not_found"') expect(response).toContain('Stable resource ID for this vault position') expect(response).toContain('0x20c000000000000000000000b9537d11c60e8b50') expect(response).toContain('0x20c000000000000000000000300e14ab91a10769') expect(address?.schema?.examples).toStrictEqual([TestEarn.positionAddress]) expect(asOf?.description).toContain('latest indexed block at or before') expect(asOf?.schema?.examples).toStrictEqual(['2026-07-21T09:00:00.000Z']) expect(response).toContain('Indexed block used for a historical Earn position observation') expect(response).toContain('Assets held by this account at the observation block') expect(response).toContain('Earn shares held by this account at the observation block') expect(response).toContain('Asset value of this account’s earn shares at the observation block') expect(response).not.toContain('Assets this account currently holds') expect(response).not.toContain('Earn shares this account currently holds') expect(operation?.responses?.['200']?.description).toBe( 'Current or historical earn position for one account in one vault.', ) expect(vaultId?.schema?.examples).toStrictEqual([TestEarn.mainnetVaultAddress]) }) test('documents address-position inputs and responses', async () => { const app = TestApp.create({ auth: false, cache }) const spec = (await (await app.request('/openapi.json')).json()) as OpenApiDocument const operation = spec.paths['/v1/earn/addresses/{address}/positions']?.get const address = operation?.parameters?.find((parameter) => parameter.name === 'address') const denomination = operation?.parameters?.find( (parameter) => parameter.name === 'valuation.currency', ) const include = operation?.parameters?.find((parameter) => parameter.name === 'include') const verified = operation?.parameters?.find((parameter) => parameter.name === 'verified') const errors = errorSchemas(spec, operation) const response = JSON.stringify(operation?.responses) const tokenReference = JSON.stringify(spec.components?.schemas?.['TokenReference']) expect(operation?.description).toContain('currently holds shares') expect(address?.schema?.examples).toStrictEqual([TestEarn.positionAddress]) expect(denomination?.description).toContain('nominal value') expect(include?.description).toContain('`earnings`') expect(verified?.description).toContain('registry-curated') expect(verified?.schema?.examples).toStrictEqual([true]) expect(errors).toContain('"chain_id_invalid"') expect(errors).toContain('"chain_id_unsupported"') expect(errors).toContain('"positions_invalid"') expect(errors).toContain('"query_invalid"') expect(response).toContain('Stable resource ID for this position') expect(response).toContain('Current asset value of the held shares') expect(response).toContain('"assetAmount"') expect(response).toContain('"shareAmount"') expect(response).toContain('"lifetimeCashFlows"') expect(response).toContain('"valuation"') expect(response).toContain('"vaultAddress"') expect(response).toContain('"nextCursor"') expect(response).toContain('#/components/schemas/TokenReference') // Token references retain display metadata through the shared component. expect(tokenReference).toContain('"symbol"') }) test('documents vault-earnings variants', async () => { const app = TestApp.create({ auth: false, cache }) const spec = (await (await app.request('/openapi.json')).json()) as OpenApiDocument const operation = spec.paths['/v1/earn/vaults/{vaultId}/earnings/{address}']?.get const errors = errorSchemas(spec, operation) const period = operation?.parameters?.find((parameter) => parameter.name === 'period') const response = JSON.stringify(operation?.responses) expect(operation?.description).toContain('the trailing 30 days') expect(period?.description).toContain('trailing 30 days') expect(period?.schema?.enum).toStrictEqual(['30d', 'active', 'lifetime']) expect(period?.schema?.examples).toStrictEqual(['lifetime']) expect(errors).toContain('"earn_vault_not_found"') expect(response).toContain('Stable resource ID for these vault earnings') expect(response).toContain('Current share value plus completed redemptions') expect(response).toContain('Assets deposited with this account as receiver') expect(response).toContain('Assets returned by completed redemptions') expect(response).toContain('weighted-average asset cost basis') expect(response).toContain('Ending share value plus realized assets') expect(response).toContain('cannot reconstruct the asset cost basis for the requested period') expect(response).toContain('"active"') expect(response).toContain('"30d"') expect(response).toContain('"incomplete_cost_basis"') expect(response).toContain('"pending_redemption"') }) })