Plain English
wallet.dispose() is supposed to fully shut down before it returns, so callers can safely tear down their own resources (like a database connection) right after. It doesn't: it kicks off the real cleanup but never waits for it, so the promise resolves early. If an event arrives in that gap, the SDK tries to use resources (like the caller's database) that may already be gone.
Root cause
Wallet.dispose() in chunk-DSC3G6QZ.js (~line 479247):
async dispose() {
const manager = this._contractManager ?? (this._contractManagerInitializing ? await this._contractManagerInitializing.catch(() => void 0) : void 0);
manager?.dispose(); // <-- missing `await`
this._contractManager = void 0;
this._contractManagerInitializing = void 0;
}
ContractManager.dispose() itself is correct — it awaits contractEventsSubscriptionReady and then unsubscribes:
async dispose() {
this.disposePromise ??= (async () => {
this.disposed = true;
...
const subscription = await this.contractEventsSubscriptionReady;
subscription?.();
})();
return this.disposePromise;
}
But because Wallet.dispose() never awaits it, await wallet.dispose() resolves before the SSE subscription is actually torn down.
Repro
From demos/intents/swap-to-lightning/mainnet/typescript/index.ts:
console.log("Waiting for the swap to settle or refund...");
const outcome = await manager.waitForSwapCompletion(rfqId);
console.log("Swap finished:", outcome);
await manager.stop();
await transport.close();
await wallet.dispose();
closeDB(); // <-- app-owned SQLite connection, passed in via SQLExecutor
Observed error
The swap itself completes successfully (state: 'settled'), but shutdown throws:
Funded! https://arkade.space/tx/ff3848bac52ab964e334afafaab093b067775316f853cf145fac68af05e0b4eb
Waiting for the swap to settle or refund...
Swap finished: { state: 'settled', txid: undefined }
Error handling contract event: TypeError: The database connection is not open
at Database.prepare (.../better-sqlite3/lib/methods/wrappers.js:5:21)
at Object.all (.../index.ts:97:10)
at SQLiteContractRepository.getContracts (@arkade-os/sdk/dist/repositories/sqlite/index.js:546:32)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async _ContractManager.annotateVtxos (@arkade-os/sdk/dist/chunk-DSC3G6QZ.js:9740:23)
at async _ContractManager.fetchContractVtxosBulk (@arkade-os/sdk/dist/chunk-DSC3G6QZ.js:10323:23)
at async _ContractManager.fetchContractVxosFromIndexer (@arkade-os/sdk/dist/chunk-DSC3G6QZ.js:10261:41)
at async _ContractManager.syncContracts (@arkade-os/sdk/dist/chunk-DSC3G6QZ.js:10168:20)
at async _ContractManager.handleContractEvent (@arkade-os/sdk/dist/chunk-DSC3G6QZ.js:10115:11)
@arkade-os/sdk@0.4.66.
Suggested fix
Add the missing await in Wallet.dispose():
async dispose() {
const manager = this._contractManager ?? ...;
await manager?.dispose(); // was: manager?.dispose();
this._contractManager = void 0;
this._contractManagerInitializing = void 0;
}
Separately, ContractManager.handleContractEvent has no disposed guard at its top, so even a fully-awaited dispose() doesn't cover a handler that was already mid-flight (e.g. awaiting a network call) the instant dispose() was called. Worth an early-return check there too for full safety.
Workaround
The demo now awaits contractManager.dispose() directly instead of wallet.dispose(), which sidesteps the missing await:
const contractManager = await wallet.getContractManager();
...
await contractManager.dispose();
closeDB();
Plain English
wallet.dispose()is supposed to fully shut down before it returns, so callers can safely tear down their own resources (like a database connection) right after. It doesn't: it kicks off the real cleanup but never waits for it, so the promise resolves early. If an event arrives in that gap, the SDK tries to use resources (like the caller's database) that may already be gone.Root cause
Wallet.dispose()inchunk-DSC3G6QZ.js(~line 479247):ContractManager.dispose()itself is correct — it awaitscontractEventsSubscriptionReadyand then unsubscribes:But because
Wallet.dispose()never awaits it,await wallet.dispose()resolves before the SSE subscription is actually torn down.Repro
From
demos/intents/swap-to-lightning/mainnet/typescript/index.ts:Observed error
The swap itself completes successfully (
state: 'settled'), but shutdown throws:@arkade-os/sdk@0.4.66.Suggested fix
Add the missing
awaitinWallet.dispose():Separately,
ContractManager.handleContractEventhas nodisposedguard at its top, so even a fully-awaiteddispose()doesn't cover a handler that was already mid-flight (e.g. awaiting a network call) the instantdispose()was called. Worth an early-return check there too for full safety.Workaround
The demo now awaits
contractManager.dispose()directly instead ofwallet.dispose(), which sidesteps the missing await: