/** @module-tag localnet */ import { nanoid } from 'nanoid' import { Hash, Hex, Secp256k1 } from 'ox' import { ZoneRpcAuthentication } from 'ox/tempo' import { createClient, http } from 'viem' import { prepareTransactionRequest, sendRawTransactionSync, signTransaction } from 'viem/actions' import { Account as TempoAccount, Actions, Transaction, withRelay } from 'viem/tempo' import * as TestApp from '../../../test/App.js' import * as Containers from '../../../test/containers.js' import * as Relay from '../../../test/Relay.js' import * as Runtime from '../../../test/runtime.js' import * as Tempo from '../../../test/Tempo.js' import * as TestStripe from '../../../test/Stripe.js' import * as BillingSettings from '../../db/tables/billingSettings.js' import type * as Db from '../../db/Db.js' import * as Fees from '../../internal/Fees.js' import type * as Log from '../../internal/Log.js' import * as Organizations from '../../db/tables/organizations.js' import * as Projects from '../../db/tables/projects.js' import * as SponsoredTransactions from '../../db/tables/sponsoredTransactions.js' import * as StripeCustomers from '../../db/tables/stripeCustomers.js' import * as core_Billing from '../management/Billing.js' import * as RequestListener from '../../handlers/internal/requestListener.js' import * as Viem from '../../internal/Viem.js' import * as Sponsorships from './Sponsorships.js' const runtime = Runtime.get() const feePayerAccount = Tempo.accounts[10]! const senderAccount = Tempo.accounts[9]! const zone = runtime.zone.chainId const zoneScope = `zone:${zone}:read` as const const zoneOptions = TestApp.zone({ chainId: zone, rpcUrl: runtime.zone.publicZoneUrl, }) /** Super admin secret configured on the end-to-end billing app. */ const superAdminSecret = 'tempo:sk:a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1' /** Keys covering the sponsorship policy matrix. */ const keys = [ { id: 'key_sponsor', orgId: 'org_1', projectId: 'prj_1', environment: 'sandbox', scopes: ['rpc-relay:sponsor'], token: 'secret_sponsor', }, { id: 'key_sponsor_prod', orgId: 'org_1', projectId: 'prj_1', environment: 'production', scopes: ['rpc-relay:sponsor'], token: 'secret_sponsor_prod', }, { id: 'key_wildcard', orgId: 'org_2', projectId: 'prj_2', environment: 'sandbox', scopes: ['*'], token: 'secret_wildcard', }, { id: 'key_wildcard_prod', orgId: 'org_2', projectId: 'prj_2', environment: 'production', scopes: ['*'], token: 'secret_wildcard_prod', }, { id: 'key_unscoped', orgId: 'org_1', projectId: 'prj_1', environment: 'sandbox', scopes: ['rpc-relay:read'], token: 'secret_unscoped', }, { id: 'key_zone_relay', orgId: 'org_1', projectId: 'prj_1', environment: 'sandbox', scopes: ['rpc-relay:read', zoneScope], token: 'secret_zone_relay', }, { id: 'key_zone_sponsor', orgId: 'org_1', projectId: 'prj_1', environment: 'sandbox', scopes: ['rpc-relay:sponsor', zoneScope], token: 'secret_zone_sponsor', }, { id: 'key_unattributed', orgId: 'org_1', environment: 'sandbox', scopes: ['rpc-relay:sponsor'], token: 'secret_unattributed', }, { id: 'key_unattributed_prod', orgId: 'org_1', environment: 'production', scopes: ['rpc-relay:sponsor'], token: 'secret_unattributed_prod', }, ] as const satisfies readonly TestApp.kvStore.Key[] describe('behavior: Zone chain routing', () => { const zoneSelectors = [ ['relay', 'path'], ['relay', 'body'], ['sponsor', 'path'], ['sponsor', 'body'], ] as const // Prool's default Zone dev key. const privateKey = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' function app() { return TestApp.create({ auth: { keys }, zones: [zoneOptions], }) } function token() { const issuedAt = Math.floor(Date.now() / 1000) const authentication = ZoneRpcAuthentication.from({ chainId: zone, expiresAt: issuedAt + 300, issuedAt, zoneId: 1, }) return ZoneRpcAuthentication.serialize(authentication, { signature: Secp256k1.sign({ payload: ZoneRpcAuthentication.getSignPayload(authentication), privateKey, }), }) } async function request( app: ReturnType, options: { apiKey?: string | undefined basePath?: string | undefined body: object | readonly object[] mount: 'relay' | 'sponsor' pathChainId?: number | undefined zoneToken?: string | undefined }, ) { const apiKey = options.apiKey ?? (options.mount === 'relay' ? 'secret_unscoped' : 'secret_sponsor') const path = `${options.basePath ?? ''}/rpc/${options.mount}${options.pathChainId ? `/${options.pathChainId}` : ''}` const response = await app.fetch( new Request(`http://tempo-api.test${path}`, { body: JSON.stringify(options.body), headers: { 'content-type': 'application/json', 'tempo-api-key': apiKey, ...(options.zoneToken ? { [ZoneRpcAuthentication.headerName]: options.zoneToken } : {}), }, method: 'POST', }), ) return await response.json() } test.each(zoneSelectors)('rejects an unscoped Zone on %s via %s', async (mount, selector) => { const body = await request(app(), { body: { id: 1, jsonrpc: '2.0', method: 'eth_chainId', params: selector === 'body' ? [{ chainId: zone }] : [], }, mount, ...(selector === 'path' ? { pathChainId: zone } : {}), }) expect(body).toMatchObject({ error: { code: -32602, data: { code: 'api_key_forbidden' }, message: expect.stringContaining(zoneScope), }, }) }) test.each(zoneSelectors)('allows a Zone token on %s via %s', async (mount, selector) => { const body = await request(app(), { body: { id: 1, jsonrpc: '2.0', method: 'eth_chainId', params: selector === 'body' ? [{ chainId: zone }] : [], }, mount, ...(selector === 'path' ? { pathChainId: zone } : {}), zoneToken: token(), }) expect(body).toMatchObject({ result: `0x${zone.toString(16)}` }) }) test('allows a Zone token on the default chain', async () => { const api = TestApp.create({ auth: { keys }, defaultChainId: zone, zones: [zoneOptions], }) const body = await request(api, { body: { id: 1, jsonrpc: '2.0', method: 'eth_chainId' }, mount: 'relay', zoneToken: token(), }) expect(body).toMatchObject({ result: `0x${zone.toString(16)}` }) }) test('allows a Zone token in a batch', async () => { const body = (await request(app(), { body: [ { id: 1, jsonrpc: '2.0', method: 'eth_chainId', params: [{ chainId: zone }] }, { id: 2, jsonrpc: '2.0', method: 'eth_chainId', params: [{ chainId: zone }] }, ], mount: 'relay', zoneToken: token(), })) as { result?: string }[] expect(body.map((entry) => entry.result)).toEqual([ `0x${zone.toString(16)}`, `0x${zone.toString(16)}`, ]) }) test.each([ ['relay', 'secret_zone_relay'], ['sponsor', 'secret_zone_sponsor'], ] as const)('allows a Zone-scoped key on %s', async (mount, apiKey) => { const body = await request(app(), { apiKey, body: { id: 1, jsonrpc: '2.0', method: 'eth_chainId', params: [{ chainId: zone }] }, mount, }) expect(body).toMatchObject({ result: `0x${zone.toString(16)}` }) }) test('forwards RPC basic auth to a Zone internal RPC', async () => { const authorization: (string | null)[] = [] const server = await Relay.createServer( RequestListener.fromFetchHandler(async (request) => { authorization.push(request.headers.get('authorization')) return Response.json({ id: 1, jsonrpc: '2.0', result: Hex.fromNumber(zone) }) }), ) try { const api = TestApp.create({ auth: { keys }, rpc: ({ chainId }) => (chainId === zone ? { auth: 'rpc:secret', url: server.url } : {}), zones: [zoneOptions], }) const body = await request(api, { apiKey: 'secret_zone_relay', body: { id: 1, jsonrpc: '2.0', method: 'eth_chainId', params: [{ chainId: zone }] }, mount: 'relay', }) expect(body).toMatchObject({ result: Hex.fromNumber(zone) }) expect(authorization).toEqual(['Basic cnBjOnNlY3JldA==']) } finally { await server.closeAsync() } }) test('rejects a blank Zone token', async () => { const body = await request(app(), { body: { id: 1, jsonrpc: '2.0', method: 'eth_chainId', params: [{ chainId: zone }] }, mount: 'relay', zoneToken: ' ', }) expect(body).toMatchObject({ error: { data: { code: 'api_key_forbidden' } } }) }) test('does not forward a Zone token to a non-Zone RPC', async () => { const tokens: (string | null)[] = [] const server = await Relay.createServer( RequestListener.fromFetchHandler(async (request) => { tokens.push(request.headers.get(ZoneRpcAuthentication.headerName)) return Response.json({ id: 1, jsonrpc: '2.0', result: Hex.fromNumber(runtime.chainId) }) }), ) try { const api = TestApp.create({ auth: { keys }, rpc: { url: () => server.url }, zones: [zoneOptions], }) const body = await request(api, { body: { id: 1, jsonrpc: '2.0', method: 'eth_chainId' }, mount: 'relay', pathChainId: runtime.chainId, zoneToken: token(), }) expect(body).toMatchObject({ result: Hex.fromNumber(runtime.chainId) }) expect(tokens).toEqual([null]) } finally { await server.closeAsync() } }) test('rejects conflicting path and body chain ids', async () => { const body = await request(app(), { body: { id: 1, jsonrpc: '2.0', method: 'eth_chainId', params: [{ chainId: zone }], }, mount: 'relay', pathChainId: runtime.chainId, }) expect(body).toMatchObject({ error: { code: -32602, message: 'Conflicting chain ids.' } }) }) test('rejects every Zone request in a batch', async () => { const body = (await request(app(), { body: [ { id: 1, jsonrpc: '2.0', method: 'eth_chainId', params: [{ chainId: zone }] }, { id: 2, jsonrpc: '2.0', method: 'eth_chainId', params: [{ chainId: zone }] }, ], mount: 'relay', })) as { error?: { data?: { code?: string } } }[] expect(body).toHaveLength(2) expect(body.every((entry) => entry.error?.data?.code === 'api_key_forbidden')).toBe(true) }) test('rejects a Zone configured as the default chain', async () => { const api = TestApp.create({ auth: { keys }, defaultChainId: zone, zones: [zoneOptions], }) const body = await request(api, { body: { id: 1, jsonrpc: '2.0', method: 'eth_chainId' }, mount: 'relay', }) expect(body).toMatchObject({ error: { data: { code: 'api_key_forbidden' } } }) }) test('rejects Zone selectors under a base path', async () => { const api = TestApp.create({ auth: { keys }, path: '/api', zones: [zoneOptions], }) const body = await request(api, { basePath: '/api', body: { id: 1, jsonrpc: '2.0', method: 'eth_chainId' }, mount: 'relay', pathChainId: zone, }) expect(body).toMatchObject({ error: { data: { code: 'api_key_forbidden' } } }) }) test('requires Zone authorization for an embedded signed transaction', async () => { const entries: Log.Entry[] = [] const api = TestApp.create({ auth: { keys }, logger: (entry) => void entries.push(entry), relay: { feePayer: { account: feePayerAccount } }, zones: [zoneOptions], }) const serialized = await senderAccount.signTransaction({ chainId: zone, feePayer: true, gas: 100_000n, maxFeePerGas: 1_000_000n, maxPriorityFeePerGas: 0n, nonce: 0, to: Tempo.accounts[8]!.address, value: 0n, }) const rpc = { body: { id: 1, jsonrpc: '2.0', method: 'eth_signRawTransaction', params: [serialized], }, mount: 'sponsor', } as const const refused = await request(api, rpc) const authorized = await request(api, { ...rpc, zoneToken: token() }) expect(refused).toMatchObject({ error: { data: { code: 'api_key_forbidden' } } }) expectSponsorshipCode({ code: 'api_key_forbidden', entry: entries[0] }) expect(authorized).toMatchObject({ result: expect.stringMatching(/^0x/) }) }) }) describe.skipIf(runtime.mode !== 'localnet')('behavior: sponsorship', () => { test('behavior: scoped keys sponsor, recorded with attribution', async () => { const db = TestApp.database() const app = TestApp.create({ auth: { keys }, db, relay: { feePayer: { account: feePayerAccount } }, }) const body = await relayRequest(app, { method: 'eth_signRawTransaction', params: [await signSponsorable()], token: 'secret_sponsor', }) expect(body.error).toBeUndefined() // The relay returned the envelope counter-signed by the fee payer. const signed = Transaction.deserialize(body.result as `0x76${string}`) expect(signed.feePayerSignature).toBeDefined() const [row] = await SponsoredTransactions.listPending(db) expect(row?.transactionHash).toBe(Hash.keccak256(body.result!)) expect(row?.signPayload).toMatch(/^0x/) expect(row?.chainId).toBe(runtime.chainId) // The stored envelope is exactly the counter-signed transaction returned. expect(row?.transaction).toBe(body.result) expect({ ...row, chainId: '', createdAt: '', id: '', signPayload: '', transaction: '', transactionHash: '' }) // prettier-ignore .toMatchInlineSnapshot(` { "apiKeyId": "key_sponsor", "billable": false, "chainId": "", "createdAt": "", "currency": null, "environment": "sandbox", "feeAmount": null, "feeMax": "1", "feeToken": "0x20c0000000000000000000000000000000000000", "finalizedAt": null, "id": "", "meterReportedAt": null, "orgId": "org_1", "projectId": "prj_1", "signPayload": "", "status": "pending", "transaction": "", "transactionHash": "", } `) }) test('behavior: non-mainnet production keys record non-billable rows', async () => { const db = TestApp.database() const app = TestApp.create({ auth: { keys }, db, relay: { feePayer: { account: feePayerAccount } }, }) const body = await relayRequest(app, { method: 'eth_signRawTransaction', params: [await signSponsorable()], token: 'secret_sponsor_prod', }) expect(body.error).toBeUndefined() const [row] = await SponsoredTransactions.listPending(db) expect(row?.billable).toBe(false) expect(row?.environment).toMatchInlineSnapshot(`"production"`) }) test('behavior: the wildcard scope grants sponsorship', async () => { const db = TestApp.database() const app = TestApp.create({ auth: { keys }, db, relay: { feePayer: { account: feePayerAccount } }, }) const body = await relayRequest(app, { method: 'eth_signRawTransaction', params: [await signSponsorable()], token: 'secret_wildcard', }) expect(body.error).toBeUndefined() const [row] = await SponsoredTransactions.listPending(db) expect(row?.orgId).toMatchInlineSnapshot(`"org_2"`) }) test('behavior: read-only keys cannot access the sponsor route', async () => { const db = TestApp.database() const app = TestApp.create({ auth: { keys }, db, relay: { feePayer: { account: feePayerAccount } }, }) const response = await app.fetch( new Request('http://tempo-api.test/rpc/sponsor', { body: JSON.stringify({ id: 1, jsonrpc: '2.0', method: 'eth_chainId' }), headers: { 'content-type': 'application/json', 'tempo-api-key': 'secret_unscoped' }, method: 'POST', }), ) expect(response.status).toBe(403) expect(await response.json()).toMatchObject({ error: { code: 'api_key_forbidden' } }) expect(await SponsoredTransactions.listPending(db)).toHaveLength(0) }) test('behavior: organization-attributed keys sponsor without a project', async () => { const db = TestApp.database() const app = TestApp.create({ auth: { keys }, db, relay: { feePayer: { account: feePayerAccount } }, }) const body = await relayRequest(app, { method: 'eth_signRawTransaction', params: [await signSponsorable()], token: 'secret_unattributed_prod', }) expect(body.error).toBeUndefined() expect(Transaction.deserialize(body.result as `0x76${string}`).feePayerSignature).toBeDefined() const [row] = await SponsoredTransactions.listPending(db) expect(row?.orgId).toMatchInlineSnapshot(`"org_1"`) expect(row?.projectId).toBeNull() }) test('behavior: organization-attributed keys use a validated request project', async () => { const db = TestApp.database() await Organizations.create(db, { id: 'org_1', name: 'One' }) const project = await Projects.create(db, { name: 'Privy app', orgId: 'org_1' }) const app = TestApp.create({ auth: { keys }, db, relay: { feePayer: { account: feePayerAccount } }, }) const body = await relayRequest(app, { method: 'eth_signRawTransaction', params: [await signSponsorable()], projectId: project.id, token: 'secret_unattributed_prod', }) expect(body.error).toBeUndefined() const [row] = await SponsoredTransactions.listPending(db) expect(row?.projectId).toBe(project.id) }) test('behavior: batches aggregate sponsorship rejections without retaining payloads', async () => { const db = TestApp.database() const entries: Log.Entry[] = [] const app = TestApp.create({ auth: { keys }, db, logger: (entry) => void entries.push(entry), relay: { feePayer: { account: feePayerAccount } }, }) const transactions = await Promise.all([ signSponsorable({ chainId: 999_999, nonce: 0 }), signSponsorable({ chainId: 999_999, nonce: 1 }), ]) const response = await app.fetch( new Request('http://tempo-api.test/rpc/sponsor', { body: JSON.stringify( transactions.map((transaction, index) => ({ id: index + 1, jsonrpc: '2.0', method: 'eth_signRawTransaction', params: [transaction], })), ), headers: { 'content-type': 'application/json', 'tempo-api-key': 'secret_sponsor', }, method: 'POST', }), ) const body = await response.json() expect(body).toMatchObject([ { error: { data: { code: 'chain_id_unsupported' } } }, { error: { data: { code: 'chain_id_unsupported' } } }, ]) expect(entries).toHaveLength(1) expect(entries[0]?.rpc).toStrictEqual({ code: -32602, dataCode: 'chain_id_unsupported', errors: 2, }) expect(entries[0]?.sponsorship).toStrictEqual({ chainId: 999_999, method: 'eth_signRawTransaction', outcome: 'rejected', reason: 'chain_id_unsupported', rejections: 2, }) expect(JSON.stringify(entries[0])).not.toContain(transactions[0]) expect(JSON.stringify(entries[0])).not.toContain(transactions[1]) expect(await SponsoredTransactions.listPending(db)).toHaveLength(0) }) test('behavior: invalid batch items never enable payload hashes', async () => { const db = TestApp.database() const entries: Log.Entry[] = [] const app = TestApp.create({ auth: { keys }, db, logger: (entry) => void entries.push(entry), relay: { feePayer: { account: feePayerAccount } }, }) const serialized = await signSponsorable({ chainId: 999_999 }) const response = await app.fetch( new Request('http://tempo-api.test/rpc/sponsor', { body: JSON.stringify([ null, { id: 2, jsonrpc: '2.0', method: 'eth_signRawTransaction', params: [serialized], }, ]), headers: { 'content-type': 'application/json', 'tempo-api-key': 'secret_sponsor', }, method: 'POST', }), ) const body = await response.json() expect(body).toMatchObject([ { error: { code: -32600 } }, { error: { data: { code: 'chain_id_unsupported' } } }, ]) expect(entries[0]?.sponsorship).toStrictEqual({ chainId: 999_999, method: 'eth_signRawTransaction', outcome: 'rejected', reason: 'chain_id_unsupported', rejections: 1, }) expect(JSON.stringify(entries[0])).not.toContain(serialized) expect(await SponsoredTransactions.listPending(db)).toHaveLength(0) }) test('behavior: batch diagnostics use only rejected request methods', async () => { const db = TestApp.database() const entries: Log.Entry[] = [] const app = TestApp.create({ auth: { keys }, db, logger: (entry) => void entries.push(entry), relay: { feePayer: { account: feePayerAccount } }, }) const serialized = await signSponsorable({ chainId: 999_999 }) const response = await app.fetch( new Request('http://tempo-api.test/rpc/sponsor', { body: JSON.stringify([ { id: 1, jsonrpc: '2.0', method: 'eth_fillTransaction', params: [], }, { id: 2, jsonrpc: '2.0', method: 'eth_signRawTransaction', params: [serialized], }, ]), headers: { 'content-type': 'application/json', 'tempo-api-key': 'secret_sponsor', }, method: 'POST', }), ) const body = await response.json() expect(body).toMatchObject([ { error: { data: { code: 'internal_error' } } }, { error: { data: { code: 'chain_id_unsupported' } } }, ]) expect(entries[0]?.sponsorship).toStrictEqual({ chainId: 999_999, method: 'eth_signRawTransaction', outcome: 'rejected', reason: 'chain_id_unsupported', rejections: 1, }) expect(JSON.stringify(entries[0])).not.toContain(serialized) expect(await SponsoredTransactions.listPending(db)).toHaveLength(0) }) test('behavior: duplicate batch ids suppress request attribution', async () => { const db = TestApp.database() const entries: Log.Entry[] = [] const app = TestApp.create({ auth: { keys }, db, logger: (entry) => void entries.push(entry), relay: { feePayer: { account: feePayerAccount } }, }) const serialized = await signSponsorable({ chainId: 999_999 }) const response = await app.fetch( new Request('http://tempo-api.test/rpc/sponsor/999999', { body: JSON.stringify([ { id: 1, jsonrpc: '2.0', method: 'eth_chainId', }, { id: 1, jsonrpc: '2.0', method: 'eth_signRawTransaction', params: [serialized], }, ]), headers: { 'content-type': 'application/json', 'tempo-api-key': 'secret_sponsor', }, method: 'POST', }), ) const body = await response.json() expect(body).toMatchObject([ { error: { data: { code: 'chain_id_unsupported' } } }, { error: { data: { code: 'chain_id_unsupported' } } }, ]) expect(entries[0]?.sponsorship).toStrictEqual({ chainId: 999_999, outcome: 'rejected', reason: 'chain_id_unsupported', rejections: 2, }) expect(JSON.stringify(entries[0])).not.toContain(serialized) expect(await SponsoredTransactions.listPending(db)).toHaveLength(0) }) test('behavior: super admin principals are refused', async () => { const db = TestApp.database() const entries: Log.Entry[] = [] const app = TestApp.create({ auth: { keys, superAdmin: { secret: 'super-admin-secret' } }, db, logger: (entry) => void entries.push(entry), relay: { feePayer: { account: feePayerAccount } }, }) const serialized = await signSponsorable() const body = await relayRequest(app, { method: 'eth_signRawTransaction', params: [serialized], token: 'super-admin-secret', }) expect(body.result).toBeUndefined() expect(body.error).toStrictEqual({ code: -32602, data: { code: 'api_key_required' }, message: 'Sponsorship rejected.', }) expectSponsorshipCode({ code: 'api_key_required', entry: entries[0] }) expect(JSON.stringify(entries[0])).not.toContain(serialized) expect(await SponsoredTransactions.listPending(db)).toHaveLength(0) }) test('behavior: sponsorship policy invariants return internal errors', async () => { const db = TestApp.database() const entries: Log.Entry[] = [] const app = TestApp.create({ auth: { keys, overrides: { 'ALL /rpc/sponsor': { apiKey: { scopes: [] } }, }, }, db, logger: (entry) => void entries.push(entry), relay: { feePayer: { account: feePayerAccount } }, }) const serialized = await signSponsorable() const body = await relayRequest(app, { method: 'eth_signRawTransaction', params: [serialized], token: 'secret_unscoped', }) expect(body.error).toStrictEqual({ code: -32603, data: { code: 'internal_error' }, message: 'Internal error', }) expect(entries[0]?.level).toBe('error') expectSponsorshipCode({ code: 'internal_error', entry: entries[0] }) expect(entries[0]?.sponsorship?.internalErrors).toBe(1) expect(JSON.stringify(entries[0])).not.toContain(serialized) expect(await SponsoredTransactions.listPending(db)).toHaveLength(0) }) test('behavior: sponsorship stays closed without a fee payer', async () => { const db = TestApp.database() const app = TestApp.create({ auth: { keys }, db }) const body = await relayRequest(app, { method: 'eth_signRawTransaction', params: [await signSponsorable()], token: 'secret_sponsor', }) expect(body.error?.code).toBe(-32601) expect(await SponsoredTransactions.listPending(db)).toHaveLength(0) }) test('behavior: mainnet sponsorship is refused until billing ships', async () => { const db = TestApp.database() const app = TestApp.create({ auth: { keys }, db, relay: { feePayer: { account: feePayerAccount } }, }) const body = await relayRequest(app, { method: 'eth_signRawTransaction', params: [ await senderAccount.signTransaction({ chainId: 4217, feePayer: true, gas: 100_000n, maxFeePerGas: 1_000_000n, maxPriorityFeePerGas: 0n, nonce: 0, to: Tempo.accounts[8]!.address, value: 0n, }), ], token: 'secret_sponsor', }) expect(body.result).toBeUndefined() expect(body.error?.code).toBe(-32602) expect(await SponsoredTransactions.listPending(db)).toHaveLength(0) }) test('behavior: an app-provided fee payer URL cannot trigger the managed fee payer', async () => { const db = TestApp.database() const app = TestApp.create({ auth: { keys }, db, relay: { feePayer: { account: feePayerAccount } }, }) const body = await relayRequest(app, { method: 'eth_fillTransaction', params: [ { calls: [{ to: Tempo.accounts[8]!.address, value: '0x0' }], chainId: `0x${runtime.chainId.toString(16)}`, feePayer: 'https://example.com', from: senderAccount.address, gas: '0x186a0', maxFeePerGas: '0xf4240', maxPriorityFeePerGas: '0x0', nonce: '0x0', }, ], token: 'secret_sponsor', }) const result = body.result as unknown as | { capabilities?: { balanceDiffs?: unknown; sponsored?: boolean } tx?: { feePayerSignature?: string | null } } | undefined expect(result?.tx?.feePayerSignature ?? null).toBeNull() expect(result?.capabilities?.balanceDiffs).toBeUndefined() expect(result?.capabilities?.sponsored).toBe(false) expect(await SponsoredTransactions.listPending(db)).toHaveLength(0) }) test('behavior: sponsorship is refused for chains this deployment does not serve', async () => { const db = TestApp.database() const entries: Log.Entry[] = [] const app = TestApp.create({ auth: { keys }, db, logger: (entry) => void entries.push(entry), relay: { feePayer: { account: feePayerAccount } }, }) const serialized = await senderAccount.signTransaction({ chainId: 999_999, feePayer: true, gas: 100_000n, maxFeePerGas: 1_000_000n, maxPriorityFeePerGas: 0n, nonce: 0, to: Tempo.accounts[8]!.address, value: 0n, }) const body = await relayRequest(app, { method: 'eth_signRawTransaction', params: [serialized], token: 'secret_sponsor', }) expect(body.result).toBeUndefined() expect(body.error).toStrictEqual({ code: -32602, data: { code: 'chain_id_unsupported' }, message: 'Unsupported chain id.', }) expect(entries[0]?.rpc).toStrictEqual({ code: -32602, dataCode: 'chain_id_unsupported', errors: 1, }) expect(entries[0]?.sponsorship).toStrictEqual({ chainId: 999_999, method: 'eth_signRawTransaction', outcome: 'rejected', payloadHash: Hash.keccak256(serialized), reason: 'chain_id_unsupported', rejections: 1, }) expect(JSON.stringify(entries[0])).not.toContain(serialized) expect(await SponsoredTransactions.listPending(db)).toHaveLength(0) }) }) // Chain-free (`eth_signRawTransaction` signs locally; the sponsor fee token // short-circuits resolution), so the gate matrix runs in every mode. describe('behavior: mainnet billing gate', () => { const fill = { calls: [{ to: Tempo.accounts[8]!.address, value: '0x0' }], capabilities: { balanceDiffs: false }, chainId: Hex.fromNumber(Viem.chainId.mainnet), feePayer: true, feeToken: '0x20c0000000000000000000000000000000000000', from: senderAccount.address, gas: Hex.fromNumber(100_000), maxFeePerGas: Hex.fromNumber(1_000_000), maxPriorityFeePerGas: '0x0', nonce: '0x0', } as const /** Activates billing for the org so the gate reaches the limit checks. */ async function activate(db: ReturnType, orgId = 'org_1') { await StripeCustomers.create(db, { orgId, stripeCustomerId: `cus_gate_${orgId}` }) await StripeCustomers.setStatus(db, orgId, 'active') // The fee-token gate reads the verified list; seed mainnet's curated tokens. await TestApp.verifiedSeed(db, Viem.chainId.mainnet) } /** Configures one attributed project for Tempo-funded sponsorship. */ async function configureSubsidy(db: ReturnType) { await Organizations.create(db, { id: 'org_1', name: 'Privy' }) await Organizations.setSponsorshipSubsidy(db, 'org_1', { durationDays: 90, projectSpendLimit: '100.00', }) const project = await Projects.create(db, { name: 'Privy app', orgId: 'org_1' }) await activate(db) return project } // Gas prices are attodollars (1e-18 USD) per gas; the signed cap in // fee-token units is ceil(gas × maxFeePerGas / 1e12). The helper's default // envelope (100k gas × 1e6 atto) caps at 1 microdollar — negligible. /** A $0.50 signed cap: 1M gas × 5e11 attodollars. */ const halfDollarFees = { gas: 1_000_000n, maxFeePerGas: 500_000_000_000n } as const /** A $2 signed cap, over the `'1.00'` platform default. */ const overCapFees = { gas: 1_000_000n, maxFeePerGas: 2_000_000_000_000n } as const test('behavior: active project promotions waive charges with active billing', async () => { const db = TestApp.database() const project = await configureSubsidy(db) const body = await relayRequest(createMainnetApp({ db }), { method: 'eth_signRawTransaction', params: [await signSponsorable({ chainId: Viem.chainId.mainnet })], projectId: project.id, token: 'secret_unattributed_prod', }) expect(body.error).toBeUndefined() const [row] = await SponsoredTransactions.listPending(db) expect(row).toMatchObject({ billable: false, orgId: 'org_1', projectId: project.id, }) const updated = await Projects.get(db, project.id) const startsAt = updated?.sponsorshipSubsidyStartsAt const endsAt = updated?.sponsorshipSubsidyEndsAt expect({ endsAt, startsAt }).toMatchObject({ endsAt: expect.any(String), startsAt: expect.any(String) }) // prettier-ignore if (!endsAt || !startsAt) throw new Error('Expected an activated promotion window.') expect(Date.parse(endsAt) - Date.parse(startsAt)).toBe(90 * 86_400_000) }) test('behavior: exhausted project promotions return to customer billing', async () => { const db = TestApp.database() await Organizations.create(db, { id: 'org_1', name: 'Privy' }) await Organizations.setSponsorshipSubsidy(db, 'org_1', { durationDays: 90, projectSpendLimit: '0.000001', }) const project = await Projects.create(db, { name: 'Privy app', orgId: 'org_1' }) await activate(db) const app = createMainnetApp({ db }) const subsidized = await relayRequest(app, { method: 'eth_signRawTransaction', params: [await signSponsorable({ chainId: Viem.chainId.mainnet })], projectId: project.id, token: 'secret_unattributed_prod', }) const billed = await relayRequest(app, { method: 'eth_signRawTransaction', params: [await signSponsorable({ chainId: Viem.chainId.mainnet, nonce: 1 })], projectId: project.id, token: 'secret_unattributed_prod', }) expect({ billed: billed.error, subsidized: subsidized.error }).toStrictEqual({ billed: undefined, subsidized: undefined, }) expect(await SponsoredTransactions.listPending(db)).toMatchObject([ { billable: false, projectId: project.id }, { billable: true, projectId: project.id }, ]) }) test('behavior: uncapped project promotions waive charges with active billing', async () => { const db = TestApp.database() await Organizations.create(db, { id: 'org_1', name: 'Privy' }) await Organizations.setSponsorshipSubsidy(db, 'org_1', { durationDays: 90, projectSpendLimit: null, }) const project = await Projects.create(db, { name: 'Privy app', orgId: 'org_1' }) await activate(db) const body = await relayRequest(createMainnetApp({ db }), { method: 'eth_signRawTransaction', params: [await signSponsorable({ chainId: Viem.chainId.mainnet })], projectId: project.id, token: 'secret_unattributed_prod', }) expect(body.error).toBeUndefined() expect(await SponsoredTransactions.listPending(db)).toMatchObject([ { billable: false, projectId: project.id }, ]) }) test('behavior: project promotions still require active billing', async () => { const db = TestApp.database() await Organizations.create(db, { id: 'org_1', name: 'Privy' }) await Organizations.setSponsorshipSubsidy(db, 'org_1', { durationDays: 90, projectSpendLimit: null, }) const project = await Projects.create(db, { name: 'Privy app', orgId: 'org_1' }) await TestApp.verifiedSeed(db, Viem.chainId.mainnet) const body = await relayRequest(createMainnetApp({ db }), { method: 'eth_signRawTransaction', params: [await signSponsorable({ chainId: Viem.chainId.mainnet })], projectId: project.id, token: 'secret_unattributed_prod', }) expect(body.error?.data).toStrictEqual({ code: 'billing_required' }) expect(await SponsoredTransactions.listPending(db)).toHaveLength(0) expect(await Projects.get(db, project.id)).toMatchObject({ sponsorshipSubsidyEndsAt: null, sponsorshipSubsidyStartsAt: null, }) }) test('behavior: subsidized organization keys without project attribution use customer billing', async () => { const db = TestApp.database() await configureSubsidy(db) const body = await relayRequest(createMainnetApp({ db }), { method: 'eth_signRawTransaction', params: [await signSponsorable({ chainId: Viem.chainId.mainnet })], token: 'secret_unattributed_prod', }) expect(body.error).toBeUndefined() expect(await SponsoredTransactions.listPending(db)).toMatchObject([ { billable: true, orgId: 'org_1', projectId: null }, ]) }) test('behavior: expired project promotions return to customer billing', async () => { const db = TestApp.database() const project = await configureSubsidy(db) await Projects.activateSponsorship(db, { at: new Date(Date.now() - 91 * 86_400_000).toISOString(), endsAt: new Date(Date.now() - 86_400_000).toISOString(), id: project.id, orgId: 'org_1', spendLimit: '100000000', }) const body = await relayRequest(createMainnetApp({ db }), { method: 'eth_signRawTransaction', params: [await signSponsorable({ chainId: Viem.chainId.mainnet })], projectId: project.id, token: 'secret_unattributed_prod', }) expect(body.error).toBeUndefined() expect(await SponsoredTransactions.listPending(db)).toMatchObject([ { billable: true, projectId: project.id }, ]) }) test('behavior: rejects request attribution from project-scoped keys', async () => { const body = await relayRequest(createMainnetApp(), { method: 'eth_signRawTransaction', params: [await signSponsorable({ chainId: Viem.chainId.mainnet })], projectId: 'prj_1', token: 'secret_sponsor_prod', }) expect(body.error).toStrictEqual({ code: -32602, data: { code: 'project_id_invalid' }, message: 'Project attribution rejected.', }) }) test('behavior: rejects request attribution outside the key organization', async () => { const db = TestApp.database() await Organizations.create(db, { id: 'org_2', name: 'Two' }) const project = await Projects.create(db, { name: 'Other app', orgId: 'org_2' }) const body = await relayRequest(createMainnetApp({ db }), { method: 'eth_signRawTransaction', params: [await signSponsorable({ chainId: Viem.chainId.mainnet })], projectId: project.id, token: 'secret_unattributed_prod', }) expect(body.error?.data).toStrictEqual({ code: 'project_id_invalid' }) }) test('behavior: refuses mainnet sponsorship without a billing source', async () => { const entries: Log.Entry[] = [] const body = await relayRequest( createMainnetApp({ logger: (entry) => void entries.push(entry) }), { method: 'eth_signRawTransaction', params: [await signSponsorable({ chainId: Viem.chainId.mainnet })], token: 'secret_sponsor_prod', }, ) expect(body.error?.code).toBe(-32602) expect(body.error?.message).toBe('Billing required.') expect(body.error?.data).toStrictEqual({ code: 'billing_required' }) expectSponsorshipCode({ code: 'billing_required', entry: entries[0] }) }) test('behavior: returns the billing reason for sponsor fills', async () => { const body = await relayRequest(createMainnetApp(), { method: 'eth_fillTransaction', params: [fill], token: 'secret_sponsor_prod', }) expect(body.error).toStrictEqual({ code: -32602, data: { code: 'billing_required' }, message: 'Billing required.', }) }) test('behavior: error capabilities retain sponsor fill attribution', async () => { const entries: Log.Entry[] = [] const body = await relayRequest( createMainnetApp({ logger: (entry) => void entries.push(entry) }), { method: 'eth_fillTransaction', params: [ { ...fill, capabilities: { balanceDiffs: false, errors: true }, }, ], token: 'secret_sponsor_prod', }, ) type Result = { capabilities?: { error?: { errorName?: string | undefined; message?: string | undefined } | undefined sponsored?: boolean | undefined } } const result = body.result as unknown as Result | undefined expect(body.error).toBeUndefined() expect(result?.capabilities).toMatchObject({ error: { errorName: 'unknown', message: 'Billing required.' }, sponsored: false, }) expect(entries[0]?.level).toBe('warn') expect(entries[0]?.rpc).toBeUndefined() expect(entries[0]?.sponsorship).toStrictEqual({ chainId: Viem.chainId.mainnet, method: 'eth_fillTransaction', outcome: 'rejected', reason: 'billing_required', rejections: 1, }) }) test('behavior: error capabilities hide sponsorship policy invariants', async () => { const entries: Log.Entry[] = [] const app = TestApp.create({ auth: { keys, overrides: { 'ALL /rpc/sponsor': { apiKey: { scopes: [] } }, }, }, db: TestApp.database(), logger: (entry) => void entries.push(entry), relay: { feePayer: { account: feePayerAccount } }, supportedChainIds: [Viem.chainId.mainnet], }) const body = await relayRequest(app, { method: 'eth_fillTransaction', params: [ { ...fill, capabilities: { balanceDiffs: false, errors: true }, }, ], token: 'secret_unscoped', }) expect(body.error).toBeUndefined() expect(body.result).toMatchObject({ capabilities: { error: { errorName: 'unknown', message: 'Internal error' }, sponsored: false, }, }) expect(JSON.stringify(body)).not.toContain('ineligible API key') expect(entries[0]?.level).toBe('error') expect(entries[0]?.rpc).toBeUndefined() expect(entries[0]?.sponsorship).toStrictEqual({ chainId: Viem.chainId.mainnet, internalErrors: 1, method: 'eth_fillTransaction', outcome: 'rejected', reason: 'internal_error', rejections: 1, }) }) test('behavior: relay fills still fall back to self-payment', async () => { const server = await Relay.createServer( RequestListener.fromFetchHandler(async (request) => { type Rpc = { id: number method: string params: readonly [Record] } const rpc = (await request.json()) as Rpc if (rpc.method !== 'eth_fillTransaction') return Response.json({ id: rpc.id, jsonrpc: '2.0', result: '0x' }) return Response.json({ id: rpc.id, jsonrpc: '2.0', result: { capabilities: {}, tx: rpc.params[0] }, }) }), ) try { const body = await relayRequest( TestApp.create({ auth: { keys }, db: TestApp.database(), relay: { feePayer: { account: feePayerAccount } }, rpc: { url: () => server.url }, supportedChainIds: [Viem.chainId.mainnet], }), { method: 'eth_fillTransaction', mount: 'relay', params: [fill], token: 'secret_wildcard_prod', }, ) type Result = { capabilities?: { sponsored?: boolean | undefined } | undefined } const result = body.result as unknown as Result expect(body.error).toBeUndefined() expect(result.capabilities?.sponsored).toBe(false) } finally { await server.closeAsync() } }) test('behavior: refuses mainnet sponsorship while billing is inactive', async () => { const db = TestApp.database() await StripeCustomers.create(db, { orgId: 'org_1', stripeCustomerId: 'cus_gate' }) const body = await relayRequest(createMainnetApp({ db }), { method: 'eth_signRawTransaction', params: [await signSponsorable({ chainId: Viem.chainId.mainnet })], token: 'secret_sponsor_prod', }) expect(body.error?.code).toBe(-32602) expect(body.error?.message).toBe('Billing required.') }) test('behavior: past-due billing refuses with its own reason', async () => { const db = TestApp.database() const entries: Log.Entry[] = [] await activate(db) // An unpaid invoice parked the org; the caller must settle it, not attach a card. await StripeCustomers.setStatus(db, 'org_1', 'past_due') const body = await relayRequest( createMainnetApp({ db, logger: (entry) => void entries.push(entry) }), { method: 'eth_signRawTransaction', params: [await signSponsorable({ chainId: Viem.chainId.mainnet, ...halfDollarFees })], token: 'secret_sponsor_prod', }, ) expect(body.error?.code).toBe(-32602) expect(body.error?.message).toBe('Billing past due.') expect(body.error?.data).toStrictEqual({ code: 'billing_past_due' }) expectSponsorshipCode({ code: 'billing_past_due', entry: entries[0] }) }) test('behavior: active billing opens mainnet sponsorship and records it', async () => { const db = TestApp.database() await activate(db) const body = await relayRequest(createMainnetApp({ db }), { method: 'eth_signRawTransaction', params: [await signSponsorable({ chainId: Viem.chainId.mainnet, ...halfDollarFees })], token: 'secret_sponsor_prod', }) expect(body.error).toBeUndefined() const signed = Transaction.deserialize(body.result as `0x76${string}`) expect(signed.feePayerSignature).toBeDefined() const [row] = await SponsoredTransactions.listPending(db) expect(row?.chainId).toBe(Viem.chainId.mainnet) expect(row?.billable).toBe(true) expect(row?.orgId).toBe('org_1') // The signed liability cap in fee-token units, for spend-limit accounting. expect(row?.feeMax).toBe('500000') }) test('behavior: a sandbox key never sponsors mainnet, even with active billing', async () => { const db = TestApp.database() const entries: Log.Entry[] = [] await activate(db) // org_1 is active… const app = TestApp.create({ auth: { keys }, db, logger: (entry) => void entries.push(entry), relay: { feePayer: { account: feePayerAccount } }, supportedChainIds: [Viem.chainId.mainnet], }) const serialized = await signSponsorable({ chainId: Viem.chainId.mainnet, ...halfDollarFees, }) const body = await relayRequest(app, { method: 'eth_signRawTransaction', params: [serialized], token: 'secret_sponsor', // …but this key is sandbox. }) // Refused for the environment, before the billing check reveals a reason. expect(body.error).toStrictEqual({ code: -32602, data: { code: 'production_api_key_required' }, message: 'Sponsorship rejected.', }) expect(entries[0]?.rpc).toStrictEqual({ code: -32602, dataCode: 'production_api_key_required', errors: 1, }) expect(entries[0]?.sponsorship).toStrictEqual({ chainId: Viem.chainId.mainnet, method: 'eth_signRawTransaction', outcome: 'rejected', payloadHash: Hash.keccak256(serialized), reason: 'production_api_key_required', rejections: 1, }) expect(JSON.stringify(entries[0])).not.toContain(serialized) expect(await SponsoredTransactions.listPending(db)).toHaveLength(0) }) test('behavior: other orgs stay gated by their own billing', async () => { const db = TestApp.database() // org_1 is active; the production wildcard key belongs to org_2, which has nothing. await activate(db) const body = await relayRequest(createMainnetApp({ db }), { method: 'eth_signRawTransaction', params: [await signSponsorable({ chainId: Viem.chainId.mainnet, ...halfDollarFees })], token: 'secret_wildcard_prod', }) expect(body.error?.code).toBe(-32602) expect(body.error?.message).toBe('Billing required.') }) test('behavior: refuses a transaction over the platform fee cap', async () => { const db = TestApp.database() const entries: Log.Entry[] = [] await activate(db) const body = await relayRequest( createMainnetApp({ db, logger: (entry) => void entries.push(entry) }), { method: 'eth_signRawTransaction', params: [await signSponsorable({ chainId: Viem.chainId.mainnet, ...overCapFees })], token: 'secret_sponsor_prod', }, ) expect(body.error?.code).toBe(-32602) expect(body.error?.message).toBe('Transaction fee limit exceeded.') expect(body.error?.data).toStrictEqual({ code: 'tx_fee_limit_exceeded' }) expectSponsorshipCode({ code: 'tx_fee_limit_exceeded', entry: entries[0] }) }) test('behavior: the org fee limit tightens the platform cap but never loosens it', async () => { const db = TestApp.database() await activate(db) // Tighter than the platform default: $0.50 envelopes now refuse. await BillingSettings.upsert(db, { orgId: 'org_1', txFeeLimit: '0.10' }) const app = createMainnetApp({ db }) const tightened = await relayRequest(app, { method: 'eth_signRawTransaction', params: [await signSponsorable({ chainId: Viem.chainId.mainnet, ...halfDollarFees })], token: 'secret_sponsor_prod', }) expect(tightened.error?.data).toStrictEqual({ code: 'tx_fee_limit_exceeded' }) // Looser than the platform default: the $2 envelope still refuses. await BillingSettings.upsert(db, { orgId: 'org_1', txFeeLimit: '999999' }) const loosened = await relayRequest(app, { method: 'eth_signRawTransaction', params: [await signSponsorable({ chainId: Viem.chainId.mainnet, ...overCapFees })], token: 'secret_sponsor_prod', }) expect(loosened.error?.data).toStrictEqual({ code: 'tx_fee_limit_exceeded' }) }) test('behavior: refuses fee tokens the verified list does not resolve to USD', async () => { const db = TestApp.database() const entries: Log.Entry[] = [] await activate(db) // EURC is verified yet non-USD; fee math reads micro-USD, so it refuses. const eurc = `0x${'e0'.repeat(20)}` as const await TestApp.verifiedSeed(db, Viem.chainId.mainnet, { tokens: [{ address: eurc, currency: 'EUR', decimals: 6, name: 'Euro Coin', symbol: 'EURC' }], }) const app = createMainnetApp({ db, logger: (entry) => void entries.push(entry) }) const request = async (feeToken: `0x${string}`) => await relayRequest(app, { method: 'eth_signRawTransaction', params: [await signSponsorable({ chainId: Viem.chainId.mainnet, feeToken })], token: 'secret_sponsor_prod', }) const nonUsd = await request(eurc) expect(nonUsd.error?.message).toBe('Fee token unsupported.') expect(nonUsd.error?.data).toStrictEqual({ code: 'fee_token_unsupported' }) expectSponsorshipCode({ code: 'fee_token_unsupported', entry: entries[0] }) // Tokens absent from the verified list refuse the same way. const unverified = await request(`0x${'ab'.repeat(20)}`) expect(unverified.error?.data).toStrictEqual({ code: 'fee_token_unsupported' }) expectSponsorshipCode({ code: 'fee_token_unsupported', entry: entries[1] }) expect(await SponsoredTransactions.listPending(db)).toHaveLength(0) }) test('behavior: sponsored rows snapshot the fee currency', async () => { const db = TestApp.database() await activate(db) const body = await relayRequest(createMainnetApp({ db }), { method: 'eth_signRawTransaction', params: [await signSponsorable({ chainId: Viem.chainId.mainnet, ...halfDollarFees })], token: 'secret_sponsor_prod', }) expect(body.error).toBeUndefined() const [row] = await SponsoredTransactions.listPending(db) expect(row?.currency).toBe('usd') }) test('behavior: refuses sponsorship over the period spend limit; raising restores', async () => { const db = TestApp.database() const entries: Log.Entry[] = [] await activate(db) await BillingSettings.upsert(db, { orgId: 'org_1', spendLimit: '1' }) // $0.90 already finalized this window; a $0.50 cap would overshoot $1. const seeded = await SponsoredTransactions.upsert(db, { apiKeyId: 'key_sponsor_prod', billable: true, chainId: Viem.chainId.mainnet, environment: 'production', orgId: 'org_1', projectId: 'prj_1', signPayload: `0x${'31'.repeat(32)}`, transaction: `0x76${'cc'.repeat(16)}`, transactionHash: `0x${'32'.repeat(32)}`, }) await SponsoredTransactions.finalize(db, seeded.id, { feeAmount: '900000', finalizedAt: new Date().toISOString(), }) const app = createMainnetApp({ db, logger: (entry) => void entries.push(entry) }) const request = async () => await relayRequest(app, { method: 'eth_signRawTransaction', params: [await signSponsorable({ chainId: Viem.chainId.mainnet, ...halfDollarFees })], token: 'secret_sponsor_prod', }) const refused = await request() expect(refused.error?.message).toBe('Spend limit exceeded.') expect(refused.error?.data).toStrictEqual({ code: 'spend_limit_exceeded' }) expectSponsorshipCode({ code: 'spend_limit_exceeded', entry: entries[0] }) await BillingSettings.upsert(db, { orgId: 'org_1', spendLimit: '10' }) const restored = await request() expect(restored.error).toBeUndefined() }) test('behavior: pending signed caps consume spend-limit budget', async () => { const db = TestApp.database() await activate(db) await BillingSettings.upsert(db, { orgId: 'org_1', spendLimit: '1' }) // A pending $0.90 cap, fee unknown until its receipt lands. await SponsoredTransactions.upsert(db, { apiKeyId: 'key_sponsor_prod', billable: true, chainId: Viem.chainId.mainnet, environment: 'production', feeMax: '900000', orgId: 'org_1', projectId: 'prj_1', signPayload: `0x${'33'.repeat(32)}`, transaction: `0x76${'cc'.repeat(16)}`, transactionHash: `0x${'34'.repeat(32)}`, }) const body = await relayRequest(createMainnetApp({ db }), { method: 'eth_signRawTransaction', params: [await signSponsorable({ chainId: Viem.chainId.mainnet, ...halfDollarFees })], token: 'secret_sponsor_prod', }) expect(body.error?.data).toStrictEqual({ code: 'spend_limit_exceeded' }) }) test('behavior: concurrent bursts cannot overshoot the spend limit', async () => { // A per-request factory (like production) over a multi-connection pool, so // the reserve transactions run concurrently and the advisory lock — not the // pool — is what serializes them. const db = TestApp.databaseFactory() const seed = db() await activate(seed) // Limit fits exactly two $0.50 sponsorships. await BillingSettings.upsert(seed, { orgId: 'org_1', spendLimit: '1' }) const app = createMainnetApp({ db }) // Distinct nonces → distinct envelopes → distinct rows, fired together. const results = await Promise.all( Array.from({ length: 6 }, async (_, nonce) => relayRequest(app, { method: 'eth_signRawTransaction', params: [await signSponsorable({ chainId: Viem.chainId.mainnet, nonce, ...halfDollarFees })], // prettier-ignore token: 'secret_sponsor_prod', }), ), ) const sponsored = results.filter((r) => !r.error) const refused = results.filter((r) => r.error?.data?.code === 'spend_limit_exceeded') expect(sponsored).toHaveLength(2) expect(refused).toHaveLength(4) // Recorded spend never exceeds the limit ($1 = 1e6 base units). const spend = await SponsoredTransactions.spend(seed, { chainIds: [Viem.chainId.mainnet], orgId: 'org_1', since: '2000-01-01T00:00:00.000Z', }) expect(spend).toBe(1_000_000n) }) }) describe.skipIf(runtime.mode !== 'localnet')('behavior: sponsorship (viem)', () => { const alphaUsd = '0x20c0000000000000000000000000000000000001' const feePayer = TempoAccount.fromSecp256k1(Secp256k1.randomPrivateKey()) const sender = TempoAccount.fromSecp256k1(Secp256k1.randomPrivateKey()) const relayClient = Relay.getClient() let db: ReturnType let server: Relay.Server function getClient(policy?: 'sign-and-broadcast' | 'sign-only') { return createClient({ account: sender, chain: Tempo.chain, transport: withRelay( Relay.http(), http(`${server.url}/rpc/sponsor`, { fetchOptions: { headers: { 'tempo-api-key': 'secret_sponsor' } }, }), policy ? { policy } : {}, ), }) } beforeAll(async () => { await Promise.all( [feePayer, sender].map((account) => Actions.faucet.fundSync(relayClient, { account, timeout: 60_000 }), ), ) db = TestApp.database() const app = TestApp.create({ auth: { keys }, db, // Explicit fee token: exercises the consumer override of the pathUSD default. relay: { feePayer: { account: feePayer, feeToken: alphaUsd } }, }) server = await Relay.createServer(RequestListener.fromFetchHandler((req) => app.fetch(req))) }, 120_000) afterAll(async () => { await server.closeAsync() }) test('behavior: transferSync sponsors end-to-end, recording a fill intent', async () => { const { receipt } = await Actions.token.transferSync(getClient(), { amount: 1n, feePayer: true, to: Tempo.accounts[8]!.address, token: alphaUsd, }) // Sponsored on-chain: the fee payer paid, the sender's transfer landed. expect(receipt.status).toBe('success') expect(receipt.feePayer?.toLowerCase()).toBe(feePayer.address.toLowerCase()) const intent = (await SponsoredTransactions.listPending(db)).find( (row) => row.transactionHash === null, ) expect(intent?.apiKeyId).toBe('key_sponsor') expect(intent?.orgId).toBe('org_1') expect(intent?.billable).toBe(false) expect(intent?.feeToken?.toLowerCase()).toBe(alphaUsd) const finalizer = Sponsorships.createFinalizer({ db, defaultChainId: runtime.chainId, feePayer: feePayer.address, rpc: TestApp.rpc, tidx: { baseUrl: runtime.tidxUrl! }, }) let finalized: Awaited> for (let attempt = 0; attempt < 30; attempt++) { await finalizer.tick() finalized = await SponsoredTransactions.get(db, intent!.id) if (finalized?.status === 'finalized') break await new Promise((resolve) => setTimeout(resolve, 1_000)) } expect(finalized?.status).toBe('finalized') expect(finalized?.transactionHash).toBe(receipt.transactionHash) expect(finalized?.feeAmount).toBe( Fees.fromGas(receipt.gasUsed, receipt.effectiveGasPrice).toString(), ) }, 60_000) test('behavior: sign-only sends record and finalize from the real receipt', async () => { const request = await prepareTransactionRequest(relayClient, { account: sender, calls: [ Actions.token.transfer.call(relayClient, { amount: 1n, to: Tempo.accounts[8]!.address, token: alphaUsd, }), ], feePayer: true, }) const signed = await signTransaction(relayClient, request) const receipt = await sendRawTransactionSync(getClient(), { serializedTransaction: signed, }) const row = (await SponsoredTransactions.listPending(db)).find( (row) => row.transactionHash === receipt.transactionHash, ) expect(row?.apiKeyId).toBe('key_sponsor') expect(row?.orgId).toBe('org_1') expect(row?.transaction.startsWith('0x76')).toBe(true) // The finalizer resolves the row against the real localnet receipt. const result = await Sponsorships.finalize(db, { getClient: () => relayClient }) expect(result.failed).toBe(0) expect(result.finalized).toBeGreaterThanOrEqual(1) const finalized = await SponsoredTransactions.get(db, row!.id) expect(finalized?.status).toBe('finalized') expect(finalized?.feeAmount).toBe( Fees.fromGas(receipt.gasUsed, receipt.effectiveGasPrice).toString(), ) }, 30_000) test('behavior: sign-and-broadcast sends record through the relay', async () => { const request = await prepareTransactionRequest(relayClient, { account: sender, calls: [ Actions.token.transfer.call(relayClient, { amount: 2n, to: Tempo.accounts[8]!.address, token: alphaUsd, }), ], feePayer: true, }) const signed = await signTransaction(relayClient, request) const receipt = await sendRawTransactionSync(getClient('sign-and-broadcast'), { serializedTransaction: signed, }) const rows = await SponsoredTransactions.listPending(db) expect(rows.find((row) => row.transactionHash === receipt.transactionHash)).toBeDefined() }, 30_000) }) /** * Signs a sponsorship-requesting Tempo transaction (fee-payer slot present but * empty; the serializer strips `feeToken`, the sponsor picks it) entirely * offline: `eth_signRawTransaction` never touches the chain, so the suite * runs without a node. */ async function signSponsorable( overrides: { chainId?: number feeToken?: `0x${string}` gas?: bigint maxFeePerGas?: bigint nonce?: number } = {}, ) { return await senderAccount.signTransaction({ chainId: overrides.chainId ?? runtime.chainId, feePayer: true, gas: overrides.gas ?? 100_000n, maxFeePerGas: overrides.maxFeePerGas ?? 1_000_000n, maxPriorityFeePerGas: 0n, nonce: overrides.nonce ?? 0, to: Tempo.accounts[8]!.address, value: 0n, ...(overrides.feeToken ? { feeToken: overrides.feeToken } : {}), }) } /** Posts one JSON-RPC request to a composed relay mount. */ async function relayRequest(app: ReturnType, options: relayRequest.Options) { const response = await app.fetch( new Request(`http://tempo-api.test/rpc/${options.mount ?? 'sponsor'}`, { body: JSON.stringify({ id: 1, jsonrpc: '2.0', method: options.method, params: options.params }), // prettier-ignore headers: { 'content-type': 'application/json', ...(options.projectId ? { 'tempo-project-id': options.projectId } : {}), ...(options.token ? { 'tempo-api-key': options.token } : {}), }, method: 'POST', }), ) return (await response.json()) as { error?: { code: number; data?: { code: string } | undefined; message: string } | undefined result?: `0x${string}` | undefined } } declare namespace relayRequest { /** Options for one composed relay request. */ type Options = { /** JSON-RPC method. */ method: string /** Relay mount to call. */ mount?: 'relay' | 'sponsor' | undefined /** JSON-RPC parameters. */ params: readonly unknown[] /** Request-level project attribution. */ projectId?: string | undefined /** API-key credential. */ token?: string | undefined } } // The full billing-to-invoice flow with nothing synthetic: a disposable node // runs under mainnet's chain id (dev consensus, funded dev alloc), so a real // card attach opens the real gate, a real sponsored transaction mines with a // real receipt, the real finalizer prices it, and real meter events land on a // real Stripe invoice. describe.runIf(TestStripe.secretKey)( 'behavior: sponsored spend reaches the invoice (stripe sandbox)', () => { // prettier-ignore const webhookSecret = `whsec_${'c'.repeat(32)}` let node: Awaited> | undefined beforeAll(async () => { node = await Containers.tempo({ chainId: Viem.chainId.mainnet }) }, 240_000) afterAll(async () => { await TestStripe.sweep() await node?.stop() }) test( 'a sponsored transaction meters real usage onto the invoice', { retry: 0, timeout: 300_000 }, async () => { // prettier-ignore const endpoint = node!.endpoints.default const url = `http://${endpoint.host}:${endpoint.port}` const stripe = TestStripe.client() const db = TestApp.database() // The key attributes to org_1; the row must exist for the usage read. await Organizations.create(db, { id: 'org_1', name: 'Sponsored Spend Org' }) // Mainnet sponsorship refuses fee tokens the verified list cannot // resolve to USD; seed it like the gate tests do. await TestApp.verifiedSeed(db, Viem.chainId.mainnet) const app = TestApp.create({ auth: { keys, superAdmin: { secret: superAdminSecret } }, billing: { stripe: { client: stripe, webhookSecret } }, db, relay: { feePayer: { account: feePayerAccount } }, rpc: { url: () => url }, supportedChainIds: [Viem.chainId.mainnet], }) const chainClient = Viem.getClient({ chainId: Viem.chainId.mainnet, rpc: { url: () => url }, }) // Both signers pay real fee-token balances on the node. await Actions.faucet.fundSync(chainClient, { account: senderAccount.address, timeout: 60_000 }) // prettier-ignore await Actions.faucet.fundSync(chainClient, { account: feePayerAccount.address, timeout: 60_000 }) // prettier-ignore // Closed until a real card attach flips the derived status. const refused = await relayRequest(app, { method: 'eth_signRawTransaction', params: [await signSponsorable({ chainId: Viem.chainId.mainnet })], token: 'secret_sponsor_prod', }) expect(refused.error?.data).toStrictEqual({ code: 'billing_required' }) const customer = await stripe.customers.create({ metadata: { orgId: 'org_1' }, name: `Test Org ${nanoid(10)}`, }) TestStripe.track(customer.id) await StripeCustomers.create(db, { orgId: 'org_1', stripeCustomerId: customer.id }) await TestStripe.completeCheckout(app, { customer: customer.id, webhookSecret }) expect((await StripeCustomers.get(db, 'org_1'))?.status).toBe('active') // Broadcast a sponsored transaction through the relay; it mines locally. const sent = await relayRequest(app, { method: 'eth_sendRawTransactionSync', params: [ await signSponsorable({ chainId: Viem.chainId.mainnet, gas: 1_000_000n, maxFeePerGas: 500_000_000_000n, }), ], token: 'secret_sponsor_prod', }) expect(sent.error).toBeUndefined() // The real receipt finalizes the row with the actual charged fee. const finalizer = Sponsorships.createFinalizer({ db, rpc: { url: () => url } }) await finalizer.tick() const [row] = await SponsoredTransactions.listUnreported(db, { chainIds: [Viem.chainId.mainnet] }) // prettier-ignore expect(row?.status).toBe('finalized') expect(row?.feeMax).toBe('500000') const feeAmount = BigInt(row!.feeAmount!) expect(feeAmount).toBeGreaterThan(0n) expect(feeAmount).toBeLessThanOrEqual(500_000n) // Usage surfaces through the API exactly as enforced. const usage = await app.request('/v1/orgs/org_1/billing', { headers: { 'tempo-api-key': superAdminSecret }, }) expect(usage.status).toBe(200) const body = (await usage.json()) as { spend: { amount: string } } expect(body.spend.amount).toBe(core_Billing.fromBaseUnits(feeAmount)) // The reporter meters the spend; Stripe aggregates it onto the upcoming // invoice (asynchronously, so poll). expect(await core_Billing.createReporter({ db, stripe }).tick()).toStrictEqual({ failed: 0, reported: 1, skipped: 0, }) const { priceId } = await core_Billing.ensureFixtures(stripe) const [subscription] = (await stripe.subscriptions.list({ customer: customer.id, limit: 1, price: priceId })).data // prettier-ignore expect(subscription).toBeDefined() const deadline = Date.now() + 180_000 let quantity: number | null | undefined while (Date.now() < deadline) { const preview = await stripe.invoices.createPreview({ customer: customer.id, subscription: subscription!.id, }) quantity = preview.lines.data.find((l) => l.pricing?.price_details?.price === priceId)?.quantity // prettier-ignore if (quantity === Number(feeAmount)) break await new Promise((resolve) => setTimeout(resolve, 5_000)) } expect(quantity).toBe(Number(feeAmount)) }, ) }, ) function createMainnetApp(options: createMainnetApp.Options = {}) { return TestApp.create({ auth: { keys }, db: options.db ?? TestApp.database(), ...(options.logger ? { logger: options.logger } : {}), relay: { feePayer: { account: feePayerAccount } }, supportedChainIds: [Viem.chainId.mainnet], }) } declare namespace createMainnetApp { /** Mainnet sponsorship test app options. */ type Options = { /** Database source. */ db?: Db.Source | undefined /** Structured request log sink. */ logger?: Log.Emit | undefined } } function expectSponsorshipCode(options: expectSponsorshipCode.Options) { expect(options.entry?.rpc?.dataCode).toBe(options.code) expect(options.entry?.sponsorship?.reason).toBe(options.code) } declare namespace expectSponsorshipCode { /** Expected canonical sponsorship code. */ type Options = { /** Expected client and internal code. */ code: Log.SponsorshipReason /** Structured request log entry. */ entry: Log.Entry | undefined } }