Security review by GreyBound — reviewed & hardened (2026-08-10).
Package layout: dlc_builder/ (swap builder + Taproot helpers + adaptor math), lending_dlc_builder/ (collateral), Signer/ (offline PSBT). API: dlc_builder.build_dlc.
Taproot-native cross-chain atomic swaps & lending
Protocol v1 -> v2 — genuine BIP-340 adaptor signatures on P2TR
Bitcoin ↔ Fractal Bitcoin (primary swap pair in this specification)
|
Bitcoin BTC |
Fractal Bitcoin FB |
Litecoin LTC |
DigiByte DGB |
Groestlcoin GRS |
Bellscoin BEL |
Non-Custodial · Atomic · On-Chain Verified · Open Protocol
Trust model (v2): Funds sit in on-chain Taproot script paths only. The coordinator never holds adaptor secrets or claim keys for swaps — it matches orders, builds descriptors, and relays public pre-signatures. Atomicity is cryptographically enforced: completing a claim on-chain reveals the adaptor secret t to the counterparty via BIP-340 signature extraction. Lending uses the same v2 primitives; some loan paths are server-gated (collateral release after verified repayment) — see Lending v2.
| Package | Purpose |
|---|---|
dlc_builder/ |
Swap DLCs + BIP-340 adaptor math + shared Taproot helpers |
dlc_builder/ |
Deprecated v1 swap scripts + shared Taproot helpers |
lending_dlc_builder/ |
3-leaf collateral DLC for cross-chain lending |
Signer/ |
Offline recovery & PSBT signing (signer.py) |
PROTOCOL.md |
Full protocol specification (v2, API detail) |
pip install -r requirements.txt
export PYTHONPATH=.
python3 dlc_builder/test_roundtrip.py
python3 dlc_builder/example_swap.pyBitcoin, Fractal Bitcoin, and compatible forks share the same architecture, scripting language, and UTXO model — yet moving value between them today often requires trusting a centralized bridge. Wrapped tokens introduce counterparty risk. Centralized exchanges add KYC friction, withdrawal delays, and custodial exposure.
NexumBit exists because cross-chain swaps should not require trust.
NexumBit is a non-custodial, peer-to-peer atomic swap protocol purpose-built for Bitcoin and Fractal Bitcoin. It replaces traditional HTLC-based bridges with Discreet Log Contracts (DLCs) on Taproot, using real BIP-340 adaptor signatures to cryptographically bind two independent on-chain transactions into a single atomic operation.
- No wrapped tokens. You send real BTC; you receive real FB (and vice versa).
- No custodian. Funds are locked in on-chain Taproot contracts spendable only via published script paths.
- Script-enforced rules. Who can claim, when refunds unlock, and how paths behave are defined by Tapscript on each chain.
- Cryptographic atomicity. The adaptor secret
tis revealed on-chain when the first party claims; the counterparty extracts it from the completed Schnorr signature. - No coordinator claim key. Deprecated v1 used
<point> CHECKSIGVERIFY <receiver> CHECKSIGwith a coordinator-held key — removed for new swaps.
The backend is a matchmaker and relay — it never holds t or ephemeral claim keys for swaps.
- Two users create opposite orders — each submits a per-swap ephemeral claim key (browser-held; wallets cannot adaptor-sign).
- The backend matches them and builds v2 DLC legs (claim + refund scripts, unspendable NUMS internal key). Addresses do not depend on adaptor point
T. - Both users fund their DLC outputs — secret-holder funds first in production. The backend monitors confirmations on both chains.
- Secret-holder generates
t, publishesT = t·Gto the relay. Both parties submit claim pre-signatures (public, verified by the relay). - Once both are confirmed, claims become available. Either party completes their pre-signature with
t→ standard BIP-340 Schnorr on-chain → counterparty extractstand claims the other leg. - If anything goes wrong, timelocks ensure each funder can reclaim via the CLTV refund path after timeout + grace — no counterparty cooperation needed.
Funding uses PSBTs signed in the user's wallet. Claim signing uses the ephemeral key in the browser signer or offline Signer/. Sovereignty is never surrendered.
| Key | Who holds it | Purpose |
|---|---|---|
| Wallet key | Your hardware / browser wallet | Fund DLC, sign refund after timeout |
| Ephemeral claim key | Browser signer or Recovery Kit | Sign claim path (receiver_ephemeral_privkey) |
Adaptor secret t |
Secret-holder until claim | Complete adaptor presignature; revealed on-chain after first claim |
NexumBit never stores wallet private keys or ephemeral claim keys on the server.
- Lending — 3-leaf collateral DLC, attestation modes, witness stacks (
WITNESS.md) - Offline signer — recovery kit, v2 adaptor complete/extract, lending PSBT signing
- Full spec — API reference, lending v2, configuration
- Repository layout
- See also
- Overview
- Architecture
- Protocol Flow
- On-Chain Construction (v2)
- Adaptor Signatures & Atomicity (v2)
- Timelock Security Model
- Cross-Swap Data Linking
- Failure Scenarios & Recovery
- Worked Example
- Lending v2
- API Reference
- Configuration Parameters
- BIP Compliance
- Deprecated v1
- License
NexumBit is a fully non-custodial, peer-to-peer bridge supporting Bitcoin (BTC), Fractal Bitcoin (FB), Litecoin (LTC), DigiByte (DGB), Groestlcoin (GRS), and Bellscoin (BEL) — Taproot-capable chains with a shared script model.
The protocol uses Discreet Log Contracts (DLCs) built on Taproot (P2TR) outputs with genuine BIP-340 adaptor signatures to achieve atomic cross-chain swaps. At no point does any third party custody user funds. The NexumBit backend acts as a matchmaker and relay — all value transfer happens on-chain, verified by Bitcoin Script.
| Property | Mechanism |
|---|---|
| Non-custodial | Funds locked in on-chain Taproot script paths; coordinator never holds claim keys or t |
| Atomic | BIP-340 adaptor presign / complete / extract links both legs cryptographically |
| Trust-minimized | Script enforces spend rules; coordinator is replaceable (worst case: DoS / bad relay data) |
| On-chain revelation | First claim broadcasts completed Schnorr s; counterparty extracts t = s − s' (mod n) |
| Recoverable | Timelock refund paths + offline Signer/ recovery kit |
┌──────────────────┐ ┌──────────────────┐
│ User A │ │ User B │
│ wallet: fund │ │ wallet: fund │
│ browser: claim │ │ browser: claim │
│ (ephemeral key) │ │ (ephemeral key) │
└────────┬─────────┘ └────────┬─────────┘
│ │
│ HTTPS/JSON │ HTTPS/JSON
▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ NexumBit Backend (relay) │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌────────────────────┐ │
│ │ Matching │ │ v2 DLC │ │ PSBT Builder │ │
│ │ Service │ │ Builder │ │ │ │
│ │ ────────── │ │ ────────── │ │ ────────────── │ │
│ │ Pairs │ │ NUMS │ │ Funding + refund │ │
│ │ compatible │ │ internal │ │ PSBTs; claim │ │
│ │ orders │ │ key, claim │ │ sighash assembly │ │
│ │ │ │ + refund │ │ │ │
│ └─────────────┘ └──────────────┘ └────────────────────┘ │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌────────────────────┐ │
│ │ Swap │ │ Adaptor Sig │ │ Taproot │ │
│ │ Monitor │ │ Verifier │ │ Helpers │ │
│ │ ────────── │ │ ────────── │ │ ────────────── │ │
│ │ Confirms + │ │ Presign / │ │ Leaf hashes, │ │
│ │ extracts t │ │ verify / │ │ merkle trees, │ │
│ │ from chain │ │ relay only │ │ control blocks │ │
│ └─────────────┘ └──────────────┘ └────────────────────┘ │
│ │
│ DOES NOT hold: t, ephemeral claim keys, coordinator cosign │
└─────────────────────────────────────────────────────────────┘
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ Bitcoin Network │ │ Fractal Bitcoin │
│ (BTC) │ │ (FB) │
│ + conf required │ │ + conf required │
└──────────────────┘ └──────────────────┘
| Open-source component | Role |
|---|---|
dlc_builder/ |
descriptors, NUMS internal key, BIP-340 adaptor math |
Signer/ |
Offline presign / complete / extract, recovery kit |
lending_dlc_builder/ |
3-leaf collateral DLC |
Every swap progresses through a deterministic state machine. Invalid transitions are rejected by the SwapStateMachine validator.
stateDiagram-v2
[*] --> WAITING_FOR_MATCH: User creates order
WAITING_FOR_MATCH --> MATCHED: Auto or manual match found
WAITING_FOR_MATCH --> CANCELLED: User cancels
MATCHED --> FUND_A: User funds DLC A
MATCHED --> WAITING_FOR_MATCH: User unmatches
MATCHED --> CANCELLED: User cancels (before funding)
FUND_A --> WAIT_CONFS: Tx detected on-chain
FUND_A --> REFUND_AVAILABLE: Counterparty timeout
WAIT_CONFS --> READY_TO_CLAIM: Both sides fully confirmed
WAIT_CONFS --> REFUND_AVAILABLE: Counterparty timeout
READY_TO_CLAIM --> DONE: v2 claim broadcast
READY_TO_CLAIM --> REFUND_AVAILABLE: Emergency timeout
REFUND_AVAILABLE --> REFUNDED: Refund broadcast
REFUND_AVAILABLE --> READY_TO_CLAIM: Recovery (counterparty appeared)
DONE --> [*]
REFUNDED --> [*]
CANCELLED --> [*]
- User A posts an order: "I want to swap 0.00001010 BTC for ~1.535 FB" (includes ephemeral claim pubkey)
- User B posts an order: "I want to swap 1.535 FB for ~0.00001010 BTC" (includes ephemeral claim pubkey)
- Matching Service finds them compatible (amounts and rates within configured tolerance)
- Backend builds two v2 DLC legs (claim + refund, NUMS internal key). Adaptor point
Tis not required yet. - Secret-holder funds first (party with earlier refund deadline). Counterparty funds after holder's DLC A is visible on-chain.
- Swap Monitor watches both chains for confirmations (e.g. 3 BTC / 10 FB)
- Once both sides are confirmed, state transitions to
READY_TO_CLAIM - Secret-holder generates
t, publishesT = t·G. Both parties submit claim pre-signatures over the claim sighash. - Secret-holder claims first (or either party):
adaptorComplete(presig, t)→ broadcast standard Schnorr signature - Counterparty extracts
tfrom on-chain signature, completes their pre-signature, claims their leg - Both swaps marked
DONE
sequenceDiagram
participant A as User A (BTC → FB)
participant BE as NexumBit Backend (relay)
participant B as User B (FB → BTC)
participant BTC as Bitcoin Chain
participant FB as Fractal Chain
Note over A,B: 1. Order Creation
A->>BE: POST /swap/create (ephemeral claim pubkey)
B->>BE: POST /swap/create (ephemeral claim pubkey)
Note over BE: match → build v2 legs (no T yet) → MATCHED
BE-->>A: DLC A address (BTC) + funding PSBT
BE-->>B: DLC A address (FB) + funding PSBT
Note over A,B: 2. Funding (secret-holder first)
A->>BTC: Sign & broadcast DLC A funding tx
A->>BE: POST /confirm-dlc-a {txid}
B->>FB: Sign & broadcast DLC A funding tx
B->>BE: POST /confirm-dlc-a {txid}
Note over BE: Both → WAIT_CONFS → READY_TO_CLAIM
Note over A,B: 3. Adaptor setup
B->>BE: POST /v2/adaptor-point {T} (secret-holder)
A->>BE: POST /v2/presignature
B->>BE: POST /v2/presignature
Note over A,B: 4. Claiming (v2)
B->>BE: GET /v2/claim-sighash
B->>B: adaptorComplete(presig, t)
B->>BE: POST /v2/finalize-claim {sig}
Note over BE: on-chain sig reveals t
A->>BE: GET /v2/claim-package (revealed_secret)
A->>A: adaptorComplete with t
A->>BE: POST /v2/finalize-claim
Note over BE: Monitor → DONE
- Leg A = User A's funded output (claimed by B's ephemeral key).
- Leg B = User B's funded output (claimed by A's ephemeral key).
- First-claimed leg = whichever has the earlier refund unlock (after grace).
- Secret-holder = receiver of the first-claimed leg → generates and holds
tuntil claim.
Each DLC output is a Taproot (P2TR) address containing two spending paths in a script tree. The internal key is unspendable (NUMS + per-DLC tweak) — funds can only move via script paths.
flowchart TD
subgraph P2TR["DLC Output — P2TR"]
IK["Internal Key<br/>(NUMS + per-DLC tweak<br/>key-path unspendable)"]
end
P2TR --> CLAIM
P2TR --> REFUND
subgraph CLAIM["🟢 Claim Path — Success"]
C1["OP_DATA_32 <receiver_xonly>"]
C2["OP_CHECKSIG"]
C3["<b>Witness:</b> <completed_schnorr_sig> <claim_script> <control_block>"]
C4["Adaptor sig completed off-chain with t"]
C5["No timelock — always spendable"]
end
subgraph REFUND["🔴 Refund Path — Timeout"]
R1["OP_PUSH <timeout_height>"]
R2["OP_CHECKLOCKTIMEVERIFY"]
R3["OP_DROP"]
R4["OP_DATA_32 <sender_xonly>"]
R5["OP_CHECKSIG"]
R6["<b>Witness:</b> <sender_sig> <refund_script> <control_block>"]
R7["Only after nLockTime ≥ timeout + grace"]
end
The v2 claim script requires a single Schnorr signature under the ephemeral receiver key:
<receiver_xonly_pubkey> OP_CHECKSIG
Witness stack (bottom to top):
<completed_bip340_schnorr_signature> # adaptorComplete(presig, t)
<claim_script>
<control_block>
Atomicity is off-chain: the completed signature is a standard BIP-340 Schnorr sig valid under the receiver pubkey. The adaptor point T is not in the script.
The refund script allows the original sender (wallet key) to reclaim funds after a block height timeout:
<timeout_block_height> OP_CHECKLOCKTIMEVERIFY OP_DROP
<sender_xonly_pubkey> OP_CHECKSIG
Witness stack:
<sender_schnorr_signature>
<refund_script>
<control_block>
Transaction must set nLockTime >= timeout_block_height (+ grace period in production).
The DLC address is derived following BIP-341 Taproot output construction:
1. Build leaf scripts:
claim_script = CHECKSIG(receiver_ephemeral)
refund_script = CLTV(timeout) + CHECKSIG(sender_wallet)
2. Compute leaf hashes (BIP-341 TapLeaf):
leaf_hash = TaggedHash("TapLeaf", 0xC0 || compact_size(script) || script)
3. Build merkle tree:
merkle_root = TaggedHash("TapBranch", sort(claim_hash, refund_hash))
4. Derive internal key (v2 — unspendable):
r = TaggedHash("NexumDLCv2/internal", claim_hash || refund_hash)
internal_key = NUMS_point + r·G (no known discrete log)
5. Tweak to output key:
tweak = TaggedHash("TapTweak", internal_key || merkle_root)
output_key = internal_key + tweak·G
6. Encode as bech32m address:
address = bech32m_encode(hrp, 1, output_key)
Critical: Leaf version MUST be
0xC0(Tapscript). Using0x00creates unspendable outputs per BIP-342.
Note: Adaptor point
Tdoes not affect the address or scripts. It is published post-funding for presignature exchange only.
Build descriptors in Python: dlc_builder.
| Transaction | Built By | Signed By | Contains |
|---|---|---|---|
| Funding | Backend | User wallet (UniSat, etc.) | Sends exact amount to DLC P2TR address |
| Claim | Backend (sighash) + client | Ephemeral claim key (browser / Signer) | Spends DLC via claim path; completed adaptor Schnorr in witness |
| Refund | Backend | User wallet | Spends DLC via refund path after timeout; nLockTime set |
Claim transactions require the client to complete an adaptor pre-signature with t before broadcast. The coordinator relays public pre-signatures but never holds t.
Unlike HTLCs (which reveal a preimage on-chain via OP_HASH160), v2 uses real BIP-340 Schnorr adaptor signatures:
Setup: signer secret d, P = d·G (x-only, even-Y per BIP-340);
adaptor secret t, T = t·G (33-byte compressed point)
Presign(d, msg, T) → (R', s') R_adapted = R' + T must have even Y
Complete(presig, t) → (r, s) standard Schnorr valid for P over msg
Extract(presig, sig, T) → t t = (s − s') mod n
Implemented in dlc_builder/adaptor_sig.py, Signer/signer.py, and the NexumBit browser adaptor-signer.js (byte-compatible).
DLC A (BTC): claim = adaptorComplete(presig_A, t) under B's ephemeral key
DLC B (FB): claim = adaptorComplete(presig_B, t) under A's ephemeral key
Both legs share the same adaptor point T = t·G. When the secret-holder broadcasts their completed signature, t is extractable from (presig, on_chain_sig). The counterparty uses t to complete their leg. A pre-signature alone is useless without t.
| Property | v2 mechanism |
|---|---|
First claimer reveals t |
On-chain s + public s' → extract t |
| Counterparty can claim | Uses extracted t to complete their pre-signature |
| Coordinator cannot block | Does not hold t or claim keys |
| Pre-signature alone useless | Cannot complete without t |
| Field | Custodial? |
|---|---|
adaptor_point (T) |
Public — safe |
adaptor_presig_a/b |
Public pre-signatures — safe without t |
revealed_secret |
Public once on-chain |
adaptor_secret |
Not stored for v2 swaps |
receiver_ephemeral_privkey |
Not stored — client-side only |
Timeline:
Block 0 Block T_A (+ grace) Block T_B (+ grace)
│ │ │
▼ ▼ ▼
├─── DLC A valid ─┤ │
│ (claim ok) │ refund available │
│ │ │
├──────────── DLC B valid ───────────────────┤
│ (claim ok) │ refund available
- Secret-holder funds first — counterparty cannot fund until holder's DLC A is on-chain (production guard).
- DLC A timeout (shorter): party with earlier refund deadline; holds
tuntil claim. - DLC B timeout (longer): gives the second funder adequate time to fund and claim.
- Grace period (
REFUND_GRACE_HOURS): after nominal timeout, claim path remains valid so counterparty can still extracttand claim after the first party's broadcast.
| Attack | Prevention |
|---|---|
| Double-spend (RBF) | Claims only allowed after full confirmations |
| Counterparty never funds | Secret-holder refunds after timeout; holder-funds-first guard |
| One-sided claim | Extract t from first claim; complete counterparty presig |
| Reorg attack | Confirmation gates prevent premature claiming |
| Backend compromise | No claim keys or t; worst case = DoS / bad relay data |
| Lost ephemeral key | Cannot claim; refund still works with wallet key after timeout |
Both sides must reach their required confirmation targets before either side can claim:
┌───────────────────────────┐
│ BOTH chains confirmed? │
│ BTC ≥ target AND │
│ FB ≥ target │
└────────────┬──────────────┘
│ YES
▼
┌───────────────────────────┐
│ READY_TO_CLAIM │
│ Adaptor setup + claim │
└───────────────────────────┘
When two swaps are matched, their DLC contracts are cross-referenced:
flowchart LR
subgraph SwapA ["Swap A — User A sends BTC"]
A_dlcA["<b>DLC A</b><br/>BTC chain<br/>User A funds here"]
A_dlcB["<b>DLC B</b><br/>FB chain<br/>User A claims here"]
end
subgraph SwapB ["Swap B — User B sends FB"]
B_dlcA["<b>DLC A</b><br/>FB chain<br/>User B funds here"]
B_dlcB["<b>DLC B</b><br/>BTC chain<br/>User B claims here"]
end
A_dlcB ---|"Same address"| B_dlcA
B_dlcB ---|"Same address"| A_dlcA
A_dlcA ---|"dlc_b_confs synced"| B_dlcA
B_dlcA ---|"dlc_b_confs synced"| A_dlcA
- Swap A's DLC B = Swap B's DLC A (same on-chain address on FB)
- Swap B's DLC B = Swap A's DLC A (same on-chain address on BTC)
- Both DLCs share the same adaptor point
T(from secret-holder'st) - Confirmation counts are synced bidirectionally
flowchart TD
Start["Both users matched"] --> BothFund{Both fund<br/>their DLC A?}
BothFund -->|Yes| BothConfirm{Both reach<br/>confirmation targets?}
BothFund -->|"Only one funds"| TimeoutCheck{"Timeout<br/>block reached?"}
BothFund -->|"Neither funds"| NothingHappens["No action needed<br/>Cancel anytime"]
TimeoutCheck -->|Yes| RefundAvailable["REFUND_AVAILABLE<br/>Funder signs refund PSBT"]
TimeoutCheck -->|No| WaitMore["Keep waiting<br/>in WAIT_CONFS"]
RefundAvailable --> Refunded["REFUNDED ✓"]
BothConfirm -->|Yes| ReadyToClaim["READY_TO_CLAIM"]
BothConfirm -->|No| WaitConfs["WAIT_CONFS<br/>Monitor polls both chains"]
WaitConfs --> BothConfirm
ReadyToClaim --> UserClaims{User claims?}
UserClaims -->|Yes| Done["DONE ✓"]
UserClaims -->|"Never claims"| StillClaimable["Stays claimable<br/>(no expiry on claim)"]
StillClaimable -->|"After DLC timeout"| BothPaths["Claim or refund<br/>First to broadcast wins"]
For eligible swap states, users can download a Recovery Kit containing descriptors, outpoints, timeouts, T, and (for the secret-holder) adaptor_secret. Critically, save receiver_ephemeral_privkey — without it you cannot claim (refund still works with your wallet key).
Offline recovery: Signer/README.md — mode [C] uses build_v2_claim_psbt with adaptor complete/extract.
GET /v1/swap/{swap_id}/recovery-kit?address={your_wallet_address}
A simplified walkthrough of a completed BTC ↔ FB swap (v2):
| User A | User B | |
|---|---|---|
| Direction | BTC → FB | FB → BTC |
| Sends | X sats on BTC | Y sats on FB |
| Receives | Y sats on FB | X sats on BTC |
| Ephemeral claim key | d_a (claims FB leg) |
d_b (claims BTC leg) |
| Secret-holder | — | User B (earlier refund deadline) |
Adaptor point: T = t·G (generated by User B after both funded; not in scripts)
DLC A (BTC chain) — User A locks X sats:
Address: bc1p<taproot_address_A>
Timeout: Block H_a
Claim script: <userB_ephemeral_xonly> OP_CHECKSIG
Refund script: H_a CLTV DROP <userA_wallet_xonly> OP_CHECKSIG
Internal key: NUMS + TaggedHash("NexumDLCv2/internal", leaves)
DLC B (FB chain) — User B locks Y sats:
Address: bc1p<taproot_address_B>
Timeout: Block H_b (H_b > H_a)
Claim script: <userA_ephemeral_xonly> OP_CHECKSIG
Refund script: H_b CLTV DROP <userB_wallet_xonly> OP_CHECKSIG
1. User B (secret-holder) funds DLC A on BTC chain first
2. User A funds DLC A on FB chain after B's tx is visible
3. Monitor confirms both chains reach required confirmations ✓
4. User B publishes T; both submit presignatures over claim sighashes
5. User B claims DLC A on BTC:
adaptorComplete(presig, t) → Witness: <schnorr_sig> <claim_script> <control_block>
On-chain sig reveals t
6. User A extracts t, claims DLC B on FB:
adaptorComplete(presig, t) → Witness: <schnorr_sig> <claim_script> <control_block>
7. Both swaps → DONE ✓
Cross-chain lending uses two DLCs:
- Loan delivery DLC (2-leaf v2) — lender funds; borrower claims loan proceeds after collateral is confirmed.
- Collateral DLC (3-leaf) — borrower funds; repay / lender-claim / safety paths.
Loan delivery uses the same claim/refund structure as swaps (ephemeral borrower claim key). Collateral repay leaf migrates to <borrower_xonly> OP_CHECKSIG with server-gated t release after verified repayment.
See lending_dlc_builder/README.md and lending_dlc_builder/WITNESS.md.
| Method | Path | Description |
|---|---|---|
GET |
/v1/swap/protocol-config |
adaptor_v2_enabled, protocol_version |
POST |
/v1/swap/{id}/v2/adaptor-point |
Secret-holder publishes T |
POST |
/v1/swap/{id}/v2/presignature |
Submit verified claim pre-signature |
GET |
/v1/swap/{id}/v2/claim-sighash |
Claim tx sighash for client signing |
POST |
/v1/swap/{id}/v2/finalize-claim |
Verify completed sig, broadcast |
GET |
/v1/swap/{id}/v2/claim-package |
Descriptor + t + counterparty presig |
POST |
/v1/swap/{id}/confirm-dlc-a |
Confirm funding |
POST |
/v1/swap/{id}/refund-dlc-a |
Refund PSBT (wallet path) |
GET |
/v1/swap/{id}/recovery-kit |
Recovery JSON |
Removed for v2: POST /claim-dlc-b (v1 coordinator co-sign PSBT) returns 409.
| Method | Path | Description |
|---|---|---|
POST |
/v1/swap/create |
Create order (user_pubkey_to = ephemeral claim key when v2) |
GET |
/v1/swap/{id} |
Swap details |
POST |
/v1/swap/{id}/cancel |
Cancel unfunded |
| Parameter | Description |
|---|---|
ADAPTOR_SIG_V2_ENABLED |
true (default) — new swaps use v2 |
CONF_BTC |
Required Bitcoin confirmations before claim is allowed |
CONF_FB |
Required Fractal Bitcoin confirmations before claim is allowed |
REFUND_GRACE_HOURS |
Extra blocks before refund path unlocks (claim still valid) |
TIMEOUT_A |
DLC A refund timeout — shorter, protects the first funder |
TIMEOUT_B |
DLC B refund timeout — longer, gives second funder more time |
INTENT_TTL |
How long an unmatched order stays active before expiring |
SLIPPAGE_BPS |
Configurable per-order slippage tolerance for auto-matching |
Exact values are configurable at deployment and not disclosed here.
| BIP | Usage |
|---|---|
| BIP-340 | Schnorr signatures + adaptor presign / complete / extract |
| BIP-341 | Taproot output construction, merkle trees, script-path sighash |
| BIP-342 | Tapscript execution (leaf version 0xC0) |
| BIP-174 | PSBT v0 format for funding and refund |
| BIP-370 | PSBT v2 extensions |
| BIP-322 | Message signing for wallet ownership verification |
| BIP-371 | Taproot PSBT fields (tap_leaf_script, etc.) |
Protocol v1 (dlc_builder.build_dlc) used:
<adaptor_xonly_pubkey> OP_CHECKSIGVERIFY
<receiver_xonly_pubkey> OP_CHECKSIG
The coordinator pre-signed using the adaptor secret as a normal private key. That is not a BIP-340 adaptor signature; atomicity was coordinator-enforced, not cryptographic. Secrets were described as "never on-chain" but the construction did not provide real cross-chain extraction.
v1 is disabled for new swaps on NexumBit. Depracted Code will be deleted soon. Legacy reference: dlc_builder/README.md.
This protocol specification and the open-source DLC builder are released under the MIT License:
Copyright (c) 2025–2026 NexumBit contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
The protocol is based on well-established Bitcoin primitives (Taproot, Schnorr signatures, CLTV timelocks) and does not rely on any proprietary or patented technology. Chain logos in this document are used for identification; see each project's terms for logo usage (Bitcoin, Litecoin, Fractal Bitcoin, Bellscoin/Nintondo).
Protocol v2 · Real BIP-340 adaptor signatures · NUMS-unspendable Taproot · Built in Solitude