11/**
2- * Boltz Exchange API client for BTC ↔ Lightning swaps
3- * Docs: https://docs.boltz.exchange/v/api
2+ * Boltz Exchange API v2 client for BTC ↔ Lightning swaps
3+ * Docs: https://docs.boltz.exchange/v/api/v2
44 * No API key needed — public, non-custodial submarine swaps.
55 */
66
7- const BOLTZ_API_URL = 'https://api.boltz.exchange ';
7+ import crypto from 'crypto ';
88
9- export interface BoltzPairInfo {
9+ const BOLTZ_API = 'https://api.boltz.exchange/v2' ;
10+
11+ // --- Types ---
12+
13+ export interface BoltzSubmarinePairInfo {
14+ hash : string ;
1015 rate : number ;
11- limits : {
12- minimal : number ;
13- maximal : number ;
14- } ;
15- fees : {
16- percentage : number ;
17- percentageSwapIn : number ;
18- minerFees : {
19- baseAsset : {
20- normal : number ;
21- reverse : { claim : number ; lockup : number } ;
22- } ;
23- quoteAsset : {
24- normal : number ;
25- reverse : { claim : number ; lockup : number } ;
26- } ;
27- } ;
28- } ;
16+ limits : { minimal : number ; maximal : number ; maximalZeroConf : number } ;
17+ fees : { percentage : number ; minerFees : number } ;
2918}
3019
3120export interface BoltzSwapResponse {
3221 id : string ;
33- bip21 ?: string ;
34- address ?: string ;
35- expectedAmount ?: number ;
36- acceptZeroConf ?: boolean ;
37- timeoutBlockHeight ?: number ;
22+ bip21 : string ;
23+ address : string ;
24+ expectedAmount : number ;
25+ acceptZeroConf : boolean ;
26+ timeoutBlockHeight : number ;
27+ claimAddress ?: string ;
3828 redeemScript ?: string ;
29+ swapTree ?: unknown ;
3930}
4031
4132export interface BoltzReverseSwapResponse {
4233 id : string ;
4334 invoice : string ;
44- redeemScript : string ;
4535 lockupAddress : string ;
4636 timeoutBlockHeight : number ;
4737 onchainAmount : number ;
38+ redeemScript ?: string ;
39+ swapTree ?: unknown ;
4840}
4941
5042export interface BoltzSwapStatus {
5143 status : string ;
52- transaction ?: {
53- id : string ;
54- hex ?: string ;
44+ transaction ?: { id : string ; hex ?: string } ;
45+ }
46+
47+ // --- Helpers ---
48+
49+ /** Generate an ephemeral keypair for refund/claim paths */
50+ function generateKeyPair ( ) {
51+ const keyPair = crypto . generateKeyPairSync ( 'ec' , {
52+ namedCurve : 'secp256k1' ,
53+ publicKeyEncoding : { type : 'spki' , format : 'der' } ,
54+ privateKeyEncoding : { type : 'pkcs8' , format : 'der' } ,
55+ } ) ;
56+ // Extract raw 33-byte compressed public key from DER
57+ const derPub = Buffer . from ( keyPair . publicKey ) ;
58+ // DER SPKI for secp256k1 has a fixed header; the last 65 bytes are the uncompressed key
59+ const uncompressed = derPub . subarray ( derPub . length - 65 ) ;
60+ const x = uncompressed . subarray ( 1 , 33 ) ;
61+ const prefix = uncompressed [ 64 ] % 2 === 0 ? 0x02 : 0x03 ;
62+ const compressed = Buffer . concat ( [ Buffer . from ( [ prefix ] ) , x ] ) ;
63+ return {
64+ publicKey : compressed . toString ( 'hex' ) ,
65+ privateKey : Buffer . from ( keyPair . privateKey ) . toString ( 'hex' ) ,
5566 } ;
5667}
5768
69+ // --- API ---
70+
5871/**
59- * Get BTC/ BTC pair info (on-chain ↔ Lightning limits, fees, rates )
72+ * Get BTC→ BTC submarine swap pair info (limits, fees)
6073 */
61- export async function getBoltzPairInfo ( ) : Promise < BoltzPairInfo > {
62- const res = await fetch ( `${ BOLTZ_API_URL } /getpairs` ) ;
63- if ( ! res . ok ) throw new Error ( `Boltz getpairs failed: ${ res . status } ` ) ;
74+ export async function getBoltzPairInfo ( ) : Promise < BoltzSubmarinePairInfo > {
75+ const res = await fetch ( `${ BOLTZ_API } /swap/submarine` ) ;
76+ if ( ! res . ok ) throw new Error ( `Boltz pairs failed: ${ res . status } ` ) ;
77+ const data = await res . json ( ) ;
78+ const pair = data ?. BTC ?. BTC ;
79+ if ( ! pair ) throw new Error ( 'BTC/BTC submarine pair not found' ) ;
80+ return pair ;
81+ }
82+
83+ export async function getBoltzReversePairInfo ( ) {
84+ const res = await fetch ( `${ BOLTZ_API } /swap/reverse` ) ;
85+ if ( ! res . ok ) throw new Error ( `Boltz reverse pairs failed: ${ res . status } ` ) ;
6486 const data = await res . json ( ) ;
65- const pair = data . pairs ?. [ ' BTC/BTC' ] ;
66- if ( ! pair ) throw new Error ( 'BTC/BTC pair not found on Boltz ' ) ;
87+ const pair = data ?. BTC ?. BTC ;
88+ if ( ! pair ) throw new Error ( 'BTC/BTC reverse pair not found' ) ;
6789 return pair ;
6890}
6991
7092/**
71- * Create a Normal Swap: On-chain BTC → Lightning
72- * User sends BTC to the returned address, Boltz pays the Lightning invoice.
73- *
74- * @param invoice - Lightning invoice (BOLT11) to be paid by Boltz
75- * @param refundAddress - On-chain BTC address for refunds if swap fails
93+ * Create submarine swap: On-chain BTC → Lightning
94+ * User sends BTC to returned address, Boltz pays the invoice.
7695 */
77- export async function createSwapIn ( invoice : string , refundAddress ?: string ) : Promise < BoltzSwapResponse > {
96+ export async function createSwapIn (
97+ invoice : string ,
98+ refundAddress ?: string ,
99+ ) : Promise < BoltzSwapResponse & { refundPrivateKey ?: string } > {
100+ const kp = generateKeyPair ( ) ;
101+
78102 const body : Record < string , unknown > = {
79- type : 'submarine' ,
80- pairId : 'BTC/BTC' ,
81- orderSide : 'sell' ,
103+ from : 'BTC' ,
104+ to : 'BTC' ,
82105 invoice,
106+ refundPublicKey : kp . publicKey ,
83107 } ;
84- if ( refundAddress ) body . refundAddress = refundAddress ;
85108
86- const res = await fetch ( `${ BOLTZ_API_URL } /createswap ` , {
109+ const res = await fetch ( `${ BOLTZ_API } /swap/submarine ` , {
87110 method : 'POST' ,
88111 headers : { 'Content-Type' : 'application/json' } ,
89112 body : JSON . stringify ( body ) ,
@@ -93,31 +116,29 @@ export async function createSwapIn(invoice: string, refundAddress?: string): Pro
93116 const err = await res . text ( ) ;
94117 throw new Error ( `Boltz createswap failed: ${ res . status } - ${ err } ` ) ;
95118 }
96- return res . json ( ) ;
119+ const swap = await res . json ( ) ;
120+ return { ...swap , refundPrivateKey : kp . privateKey } ;
97121}
98122
99123/**
100- * Create a Reverse Swap: Lightning → On-chain BTC
101- * User pays a Lightning invoice, Boltz sends BTC to the on-chain address.
102- *
103- * @param onchainAmount - Amount in sats to receive on-chain
104- * @param claimAddress - On-chain BTC address to receive funds
124+ * Create reverse swap: Lightning → On-chain BTC
125+ * User pays LN invoice, Boltz sends BTC on-chain.
105126 */
106127export async function createSwapOut (
107- onchainAmount : number ,
128+ invoiceAmount : number ,
108129 claimAddress : string ,
109- preimageHash ?: string ,
110- ) : Promise < BoltzReverseSwapResponse > {
130+ ) : Promise < BoltzReverseSwapResponse & { claimPrivateKey ?: string } > {
131+ const kp = generateKeyPair ( ) ;
132+
111133 const body : Record < string , unknown > = {
112- type : 'reversesubmarine' ,
113- pairId : 'BTC/BTC' ,
114- orderSide : 'buy' ,
115- onchainAmount,
134+ from : 'BTC' ,
135+ to : 'BTC' ,
136+ invoiceAmount,
116137 claimAddress,
138+ claimPublicKey : kp . publicKey ,
117139 } ;
118- if ( preimageHash ) body . preimageHash = preimageHash ;
119140
120- const res = await fetch ( `${ BOLTZ_API_URL } /createswap ` , {
141+ const res = await fetch ( `${ BOLTZ_API } /swap/reverse ` , {
121142 method : 'POST' ,
122143 headers : { 'Content-Type' : 'application/json' } ,
123144 body : JSON . stringify ( body ) ,
@@ -127,55 +148,40 @@ export async function createSwapOut(
127148 const err = await res . text ( ) ;
128149 throw new Error ( `Boltz reverse swap failed: ${ res . status } - ${ err } ` ) ;
129150 }
130- return res . json ( ) ;
151+ const swap = await res . json ( ) ;
152+ return { ...swap , claimPrivateKey : kp . privateKey } ;
131153}
132154
133155/**
134156 * Check swap status
135157 */
136158export async function getSwapStatus ( swapId : string ) : Promise < BoltzSwapStatus > {
137- const res = await fetch ( `${ BOLTZ_API_URL } /swapstatus` , {
138- method : 'POST' ,
139- headers : { 'Content-Type' : 'application/json' } ,
140- body : JSON . stringify ( { id : swapId } ) ,
141- } ) ;
159+ const res = await fetch ( `${ BOLTZ_API } /swap/${ swapId } ` ) ;
142160 if ( ! res . ok ) {
143161 const err = await res . text ( ) ;
144- throw new Error ( `Boltz swapstatus failed: ${ res . status } - ${ err } ` ) ;
162+ throw new Error ( `Boltz status failed: ${ res . status } - ${ err } ` ) ;
145163 }
146164 return res . json ( ) ;
147165}
148166
149167/**
150- * Get fee estimation for a swap
168+ * Estimate swap fees
151169 */
152170export async function estimateSwapFee (
153171 direction : 'in' | 'out' ,
154172 amountSats : number ,
155173) : Promise < { totalFee : number ; receiveSats : number ; minerFee : number ; serviceFee : number } > {
156- const pair = await getBoltzPairInfo ( ) ;
157-
158174 if ( direction === 'in' ) {
159- // On-chain → Lightning: user sends BTC, receives Lightning sats
160- const serviceFee = Math . ceil ( amountSats * ( pair . fees . percentageSwapIn / 100 ) ) ;
161- const minerFee = pair . fees . minerFees . baseAsset . normal ;
175+ const pair = await getBoltzPairInfo ( ) ;
176+ const serviceFee = Math . ceil ( amountSats * ( pair . fees . percentage / 100 ) ) ;
177+ const minerFee = pair . fees . minerFees ;
162178 const totalFee = serviceFee + minerFee ;
163- return {
164- totalFee,
165- receiveSats : amountSats - totalFee ,
166- minerFee,
167- serviceFee,
168- } ;
179+ return { totalFee, receiveSats : amountSats - totalFee , minerFee, serviceFee } ;
169180 } else {
170- // Lightning → On-chain: user pays LN invoice, receives on-chain BTC
181+ const pair = await getBoltzReversePairInfo ( ) ;
171182 const serviceFee = Math . ceil ( amountSats * ( pair . fees . percentage / 100 ) ) ;
172- const minerFee = pair . fees . minerFees . baseAsset . reverse . claim + pair . fees . minerFees . baseAsset . reverse . lockup ;
183+ const minerFee = pair . fees . minerFees ?. claim + pair . fees . minerFees ?. lockup || 0 ;
173184 const totalFee = serviceFee + minerFee ;
174- return {
175- totalFee,
176- receiveSats : amountSats - totalFee ,
177- minerFee,
178- serviceFee,
179- } ;
185+ return { totalFee, receiveSats : amountSats - totalFee , minerFee, serviceFee } ;
180186 }
181187}
0 commit comments