Tools for working with hash timelock contract swaps
npm install goldengateUtility functions for working with HTLC-based transfers.

Supported networks:
- btc: Bitcoin mainnet
- btctestnet: Bitcoin testnet3
An HTLC swap is carried out in two phases.
In the first phase, the swap partners setup the details of the swap. Once the
first phase is set, the funding party funds an HTLC with those details
on-chain.
In the second phase either party sweeps the funds using either the successful
claim path or the unsuccessful refund path.
In this phase, the following elements must be synchronized between the swappers:
- A claim public key
- A refund public key
- A timeout block height
- A claim hash
- A number of tokens
Aside from tokens, these values are the inputs to the swapScript orswapScriptV2 method that produces the witness script.
This script may be converted into an address with addressForScript, and phase
1 completes when the funder spends to this address with the given tokens.
In this phase each party must derive an address to sweep the funds out to, and
they must determine the UTXO or UTXOs that they will sweep.
The funder knows the UTXO details because they crafted the output, but either
party can use findDeposit to find a UTXO given the swap address.
Now either party can attempt to claim the funds out to their address, through
either claimTransaction or refundTransaction.
These transactions will also require a fee rate, which can be derived from a
backing estimator using getChainFeeRate or it can be derived using blind RBF
with confirmationFee
And they will require a chain height, which can be retrieved using getHeight.
Once a transaction is formed, it can be broadcast with broadcastTransaction.
Attempt a refund
{
[fee_tokens_per_vbyte]:
hash:
[lnd]:
[network]:
refund_private_key:
[request]:
service_public_key:
start_height:
sweep_address:
timeout_height:
tokens:
[version]:
}
@returns via cbk or Promise
{
refund_transaction:
transaction_id:
transaction_vout:
}
Attempt a sweep
{
current_height:
deadline_height:
[is_dry_run]:
[lnd]:
[min_fee_rate]:
max_fee_multiplier:
network:
private_key:
[request]:
secret:
[sends]: [{
address:
tokens:
}]
start_height:
sweep_address:
tokens:
transaction_id:
transaction_vout:
witness_script:
}
@returns via cbk or Promise
{
fee_rate:
min_fee_rate:
transaction:
}
Cancel a swap out
{
id:
[is_taproot]:
@returns via cbk or Promise
``node
const {cancelSwapOut} = require('goldengate');
const {parsePaymentRequest} = require('ln-service');
// use createSwapOut to get fund request
const {id, payment} = parsePaymentRequest({request: swap.swap_fund_request});
// Get metadata from lightningLabsSwapAuth
await cancelSwapOut({id, metadata, payment, service});
`
Create a swap in
{
fee:
[in_through]:
[macaroon]:
max_timeout_height:
[preimage]:
[private_key]:
[public_key]:
request:
service:
}
@returns via cbk or Promise
{
address:
id:
nested_address:
[private_key]:
script:
[service_message]:
service_public_key:
timeout:
tokens:
version:
}
Example:
`node
const {createSwapIn, getSwapInQuote} = require('goldengate');
const {lightningLabsSwapService} = require('goldengate');
const tokens = 1000000;
const {fee} = await getSwapInQuote({service, tokens});
const currentBlockHeight = 1500000;
const request = 'bolt11RequestForTokensMinusFee';
const {service} = lightningLabsSwapService({network: 'btctestnet'});
const {address} = await createSwapIn({
fee,
request,
service,
max_timeout_height: currentBlockHeight + 1000,
});
// address is the swap on-chain address to send to for the swap
`
Create a swap out request
Get the timeout value by getting swap out terms to determine a CLTV delta
{
[fund_at]:
[hash]:
[macaroon]:
network:
[preimage]:
[private_key]:
[public_key]:
[secret]:
service:
timeout:
tokens:
}
@returns via cbk or Promise
{
address:
[private_key]:
script:
[secret]:
[service_message]:
service_public_key:
swap_execute_request:
swap_fund_request:
timeout:
version:
}
`node
const {createSwapOut, lightningLabsSwapService} = require('goldengate');
const swap = await createSwapOut({
network: 'btctestnet',
service: lightningLabsSwapService({network: 'btctestnet'}).service,
tokens: 1000000,
});
// swap.address: Address to watch to sweep incoming funds
// swap.private_key: Private key to use to sweep incoming funds
// swap.secret: Preimage to use to sweep incoming funds
// swap.swap_execute_request: Payment request for pre-paying
// swap.swap_fund_request: Payment request for funding the swap
// swap.timeout: Server refund timeout height
// swap.version: HTLC script version number
`
Decode encoded swap recovery blob
{
recovery:
}
@returns via cbk or Promise
{
[claim_private_key]:
[claim_public_key]:
[execution_id]:
[id]:
[refund_private_key]:
[refund_public_key]:
script:
[secret]:
start_height:
[sweep_address]:
timeout:
tokens:
[version]:
}
`node
const {decodeSwapRecovery} = require('goldengate');
// Recovery blob is a hex encoded blob with swap refund details
const recoveryDetails = await decodeSwapRecovery({recovery});
// Details to use to attempt a refund from a swap
`
Encode recovery blob
Note: Make sure to encode the version given by swap creation
Either a private key or public key is required for claim/refund
Either the secret preimage or the preimage hash is required for claim/refund
{
[claim_private_key]:
[claim_public_key]:
[execution_id]:
[id]:
[refund_private_key]:
[refund_public_key]:
[secret]:
start_height:
[sweep_address]:
timeout:
tokens:
[version]:
}
@throws
@returns
{
recovery:
}
`node
const {encodeSwapRecovery} = require('goldengate');
const {recovery} = encodeSwapRecovery({
claim_public_key: serverSwapPublicKey,
execution_id: executionPaymentRequestPreimageHash,
id: fundingPaymentRequestPreimageHash,
refund_private_key: refundPrivateKey,
start_height: swapStartedAtBlockHeightNumber,
sweep_address: sendRefundFundsToChainAddress,
timeout: swapTimeoutNumber,
tokens: swapTokensAmount,
version: swapVersionNumber,
});
// Recovery is a blob with the inputs required for a refund attempt
`
Generic swap service
For fetch, pass a function like @alexbosworth/node-fetch that returns a URL
{
fetch:
socket:
}
@throws
@returns
{
service:
}
Get a PSBT from a raw transaction
{
network:
request:
transaction:
}
@returns via cbk or Promise
{
psbt:
}
Get swap in quote from swap service
{
service:
tokens:
}
@returns via cbk or Promise
{
cltv_delta:
fee:
}
`node
const {getSwapInQuote, lightningLabsSwapService} = require('goldengate');
const {fee} = await getSwapInQuote({
service: lightningLabsSwapService({network: 'btctestnet'}).service,
tokens: 1000000,
});
// Fee required to complete a swap in
`
Get swap terms from swap service
{
[macaroon]:
[preimage]:
service:
}
@returns via cbk or Promise
{
max_tokens:
min_tokens:
}
`node
const {getSwapInTerms, lightningLabsSwapService} = require('goldengate');
const {service} = lightningLabsSwapService({network: 'btctestnet'});
const maxTokens = (await getSwapInTerms({service})).max_tokens;
`
Get an unpaid swap macaroon that can be converted to a paid one by paying
{
service:
}
@returns via cbk or Promise
{
macaroon:
request:
}
`node
const {getSwapMacaroon} = require('goldengate');
const {lightningLabsSwapService} = require('goldengate');
const {service} = lightningLabsSwapService({network: 'btctestnet'});
const {macaroon, request} = await getSwapMacaroon({service});
`
Get swap quote from swap service
Obtain CLTV delta for timeout by getting swap terms
{
[delay]:
[macaroon]:
[preimage]:
service:
timeout:
tokens:
}
@returns via cbk or Promise
{
deposit:
destination:
fee:
}
`node
const {getSwapOutQuote, lightningLabsSwapService} = require('goldengate');
const {service} = lightningLabsSwapService({network: 'btctestnet'});
const {fee} = await getSwapOutQuote({service, tokens: 1000000});
// Fee is the service fee to perform a swap out
`
Get swap terms from swap service
{
[macaroon]:
[preimage]:
service:
}
@returns via cbk or Promise
{
max_cltv_delta:
max_tokens:
min_cltv_delta:
min_tokens:
}
`node
const {getSwapOutTerms, lightningLabsSwapService} = require('goldengate');
const {service} = lightningLabsSwapService({network: 'btc'});
const swapOutLimit = (await getSwapOutTerms({service})).max_tokens;
`
Determine if a transaction is an HTLC sweep
{
transaction:
}
@throws
@returns
{
[is_success_sweep]:
[is_timeout_sweep]:
}
Example:
`node
const {isSweep} = require('goldengate');
// Transaction is a hex encoded raw transaction
const isTimeoutSweep = isSweep({transaction}).is_timeout_sweep;
`
Lightning Labs swap service
{
[is_free]:
@throws
@returns
{
service:
}
Example:
`node
const {lightningLabsSwapService} = require('goldengate');
const {service} = lightningLabsSwapService({network: 'btctestnet'});
const {fee} = await getSwapOutQuote({service, tokens: 1000000});
`
Release the swap secret to the swap server to obtain inbound more quickly
{
secret:
service:
}
@returns via cbk or Promise
Example:
`node
const {lightningLabsSwapService, releaseSwapOutSecret} = require('goldengate');
const secret = '0000000000000000000000000000000000000000000000000000000000000000';
const {service} = lightningLabsSwapService({network: 'btctestnet'});
// Tell the server about the preimage to complete the off-chain part of the swap
await releaseSwapOutSecret({secret, service});
`
Subscribe to the server status of a swap in
{
id:
macaroon:
preimage:
service:
}
@throws
@returns
@event 'status_update'
{
at:
[is_broadcast]:
[is_claimed]:
[is_confirmed]:
[is_failed]:
[is_known]:
[is_refunded]:
}
Subscribe to the server status of a swap out
{
id:
macaroon:
preimage:
service:
}
@throws
@returns
@event 'status_update'
{
at:
[is_broadcast]:
[is_claimed]:
[is_confirmed]:
[is_failed]:
[is_known]:
[is_refunded]:
}
Get swap redeem script / witness program
A hash or secret is required
A private key or public key is required
{
[claim_private_key]:
[claim_public_key]:
[hash]:
[refund_private_key]:
[refund_public_key]:
[secret]:
timeout:
}
@throws
@returns
{
script:
}
Derive the swap user id from the swap macaroon
{
macaroon:
}
@throws
@returns
{
id:
}
Example:
`node
const {swapUserId} = require('goldengate');
// Derive the user id from the swap macaroon
const {id} = swapUserId({macaroon: 'base64Macaroon'});
``