Handler for VHTLC.ScriptV2 — the VHTLC whose preimage leaves carry an
explicit OP_SIZE 32 OP_EQUALVERIFY before the hash check, and which the RFQ
swap corridor builds (@arkade-os/swap's lightningSendContract).
Why a separate type string rather than a flag on vhtlc. A handler's
type names the script class it derives, the way every other registered type
does (default → DefaultVtxo.Script, vhtlc → VHTLC.Script, boarding,
arkade). V1 and V2 produce different script bytes — and so different
pkScripts — for identical participant keys, and upsertContractRow derives
the script from params and refuses any row whose supplied script does not
match. One type cannot serve both. vhtlc-v2 also cannot COLLIDE with
vhtlc: a colliding row needs one pkScript claimed by two types, and the two
versions' preimage conditions differ in every tree, so no parameters make
them equal. A use-named type (swap-lockup) was the alternative and is
worse: contracts are keyed by script, so the moment a second use registered
the same ScriptV2 the two uses would fight over one row.
Which leaves this offers, and why the set is smaller than the ladder.
ScriptV2 has nine leaves; a wallet holding ONE of the two participant keys
can satisfy four of them, and offering a leaf whose signature the caller
cannot produce is worse than offering fewer — it turns a refusal into a
transaction that gets built and then rejected.
leaf
needs
offered
claim
receiver + server, preimage
yes, to the receiver
refund
sender + receiver + server
no — needs the counterparty live
refundWithoutReceiver
sender + server, CLTV
yes, to the sender
unilateralClaim
receiver, CSV
yes, to the receiver
unilateralRefund
sender + receiver, CSV
no — needs the counterparty live
unilateralRefundWithoutReceiver
sender, CSV
yes, to the sender
nonInteractiveClaim
server + emulator
no — the wallet holds neither key
nonInteractiveRefund
server + receiver + emulator
no — the wallet holds neither key
nonInteractiveRefundWithoutReceiver
server + emulator, CLTV
no — the wallet holds neither key
The two omitted interactive leaves are the same two the vhtlc handler
omits, for the same reason: refund and unilateralRefund both need the
OTHER party's signature, and this protocol has no message that asks for one
(see @arkade-os/swap's refund.ts module doc). The two covenant leaves are
pushed by the emulator on the counterparty's behalf and are not this wallet's
to build at all.
So refundWithoutReceiver is the sender's only collaborative way out, gated
on the CLTV the quote committed to — which is exactly the leaf
pushRefundWithoutReceiver builds. The CSV leaves are offered only in the
non-collaborative context, where the caller has already accepted that
spending them means a real unilateral exit first.
The selection discipline is deliberately identical to the vhtlc handler's,
and test/contracts/vhtlcV2-handler.test.ts pins the two against each other
rung-for-rung across every role and timelock context so they cannot drift.
Handler for VHTLC.ScriptV2 — the VHTLC whose preimage leaves carry an explicit
OP_SIZE 32 OP_EQUALVERIFYbefore the hash check, and which the RFQ swap corridor builds (@arkade-os/swap'slightningSendContract).Why a separate type string rather than a flag on
vhtlc. A handler'stypenames the script class it derives, the way every other registered type does (default→DefaultVtxo.Script,vhtlc→VHTLC.Script,boarding,arkade). V1 and V2 produce different script bytes — and so different pkScripts — for identical participant keys, andupsertContractRowderives the script fromparamsand refuses any row whose suppliedscriptdoes not match. One type cannot serve both.vhtlc-v2also cannot COLLIDE withvhtlc: a colliding row needs one pkScript claimed by two types, and the two versions' preimage conditions differ in every tree, so no parameters make them equal. A use-named type (swap-lockup) was the alternative and is worse: contracts are keyed by script, so the moment a second use registered the same ScriptV2 the two uses would fight over one row.Which leaves this offers, and why the set is smaller than the ladder. ScriptV2 has nine leaves; a wallet holding ONE of the two participant keys can satisfy four of them, and offering a leaf whose signature the caller cannot produce is worse than offering fewer — it turns a refusal into a transaction that gets built and then rejected.
claimrefundrefundWithoutReceiverunilateralClaimunilateralRefundunilateralRefundWithoutReceivernonInteractiveClaimnonInteractiveRefundnonInteractiveRefundWithoutReceiverThe two omitted interactive leaves are the same two the
vhtlchandler omits, for the same reason:refundandunilateralRefundboth need the OTHER party's signature, and this protocol has no message that asks for one (see@arkade-os/swap'srefund.tsmodule doc). The two covenant leaves are pushed by the emulator on the counterparty's behalf and are not this wallet's to build at all.So
refundWithoutReceiveris the sender's only collaborative way out, gated on the CLTV the quote committed to — which is exactly the leafpushRefundWithoutReceiverbuilds. The CSV leaves are offered only in the non-collaborative context, where the caller has already accepted that spending them means a real unilateral exit first.The selection discipline is deliberately identical to the
vhtlchandler's, andtest/contracts/vhtlcV2-handler.test.tspins the two against each other rung-for-rung across every role and timelock context so they cannot drift.