diff --git a/src/tempo/server/Session.test.ts b/src/tempo/server/Session.test.ts index 171514ac..46a52803 100644 --- a/src/tempo/server/Session.test.ts +++ b/src/tempo/server/Session.test.ts @@ -1268,6 +1268,32 @@ describe.runIf(isLocalnet)('session', () => { ).rejects.toThrow('close voucher amount must be >') }) + test('allows zero close for an untouched channel', async () => { + const { channelId, serializedTransaction } = await createSignedOpenTransaction(10000000n) + const server = createServer() + + await openServerChannel(server, channelId, serializedTransaction) + + const receipt = await server.verify({ + credential: { + challenge: makeChallenge({ id: 'challenge-zero-close', channelId }), + payload: { + action: 'close' as const, + channelId, + cumulativeAmount: '0', + signature: await signTestVoucher(channelId, 0n), + }, + }, + request: makeRequest(), + }) + + expect(receipt.status).toBe('success') + + const ch = await store.getChannel(channelId) + expect(ch).not.toBeNull() + expect(ch!.finalized).toBe(true) + }) + test('rejects close exceeding on-chain deposit', async () => { const { channelId, serializedTransaction } = await createSignedOpenTransaction(10000000n) const server = createServer() diff --git a/src/tempo/server/Session.ts b/src/tempo/server/Session.ts index 94aa8131..c1eb9f52 100644 --- a/src/tempo/server/Session.ts +++ b/src/tempo/server/Session.ts @@ -840,7 +840,9 @@ async function handleClose( reason: `close voucher amount must be >= ${channel.spent} (spent)`, }) } - if (voucher.cumulativeAmount <= onChain.settled) { + const isUntouchedZeroClose = + voucher.cumulativeAmount === 0n && channel.spent === 0n && onChain.settled === 0n + if (!isUntouchedZeroClose && voucher.cumulativeAmount <= onChain.settled) { throw new VerificationFailedError({ reason: `close voucher amount must be > ${onChain.settled} (on-chain settled)`, })