@atomiqlabs/sdk 4.0.0-beta.4 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +699 -170
- package/dist/SmartChainAssets.d.ts +10 -0
- package/dist/SmartChainAssets.js +10 -0
- package/package.json +3 -3
- package/src/SmartChainAssets.ts +10 -0
- package/src/example/BrowserExample.js +0 -199
- package/src/example/BrowserExample.ts +0 -141
- package/src/example/NodeJSExample.js +0 -206
- package/src/example/NodeJSExample.ts +0 -150
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
A typescript multichain client for atomiqlabs trustlesss cross-chain swaps. Enables trustless swaps between smart chains (Solana, EVM, Starknet, etc.) and bitcoin (on-chain - L1 and lightning network - L2).
|
|
4
4
|
|
|
5
|
+
Example SDK integration in NodeJS available [here](https://github.com/atomiqlabs/atomiq-sdk-demo/blob/main/src/index.ts)
|
|
6
|
+
|
|
5
7
|
## Installation
|
|
6
8
|
```
|
|
7
9
|
npm install @atomiqlabs/sdk
|
|
@@ -18,6 +20,28 @@ npm install @atomiqlabs/chain-starknet
|
|
|
18
20
|
|
|
19
21
|
## How to use?
|
|
20
22
|
|
|
23
|
+
- [Preparations](#preparations)
|
|
24
|
+
- [Setting up signers](#signer)
|
|
25
|
+
- [Initialization](#initialization)
|
|
26
|
+
- Swaps:
|
|
27
|
+
- [Smart Chain -> BTC L1](#swap-smart-chain---bitcoin-on-chain)
|
|
28
|
+
- [BTC L1 -> Solana (Old swap protocol)](#swap-bitcoin-on-chain---solana)
|
|
29
|
+
- [BTC L1 -> Starknet (New swap protocol)](#swap-bitcoin-on-chain---starknet)
|
|
30
|
+
- [Smart Chain -> BTC Lightning network L2](#swap-smart-chain---bitcoin-lightning-network)
|
|
31
|
+
- [Smart Chain -> BTC Lightning network L2 (LNURL-pay)](#swap-smart-chain---bitcoin-lightning-network-1)
|
|
32
|
+
- [BTC Lightning network L2 -> Smart Chain](#swap-bitcoin-lightning-network---smart-chain)
|
|
33
|
+
- [BTC Lightning network L2 (LNURL-withdraw) -> Smart Chain](#swap-bitcoin-lightning-network---smart-chain-1)
|
|
34
|
+
- [Swap states](#getting-state-of-the-swap)
|
|
35
|
+
- [Swap size limits](#swap-size-limits)
|
|
36
|
+
- [Stored swaps](#stored-swaps)
|
|
37
|
+
- [Get existing swaps](#get-swap-by-id)
|
|
38
|
+
- [Refundable swaps](#get-refundable-swaps)
|
|
39
|
+
- [Claimable swaps](#get-claimable-swaps)
|
|
40
|
+
- [Helpers](#helpers)
|
|
41
|
+
- [Wallet spendable balance](#getting-wallet-balances)
|
|
42
|
+
- [Unified address parsers](#unified-address-parser)
|
|
43
|
+
- [Customize swapper instance](#additional-swapper-options)
|
|
44
|
+
|
|
21
45
|
### Preparations
|
|
22
46
|
|
|
23
47
|
Set Solana & Starknet RPC URL to use
|
|
@@ -30,6 +54,10 @@ const starknetRpc = "https://starknet-mainnet.public.blastapi.io/rpc/v0_7";
|
|
|
30
54
|
Create swapper factory, here we can pick and choose which chains we want to have supported in the SDK, ensure the "as const" keyword is used such that the typescript compiler can properly infer the types.
|
|
31
55
|
|
|
32
56
|
```typescript
|
|
57
|
+
import {SolanaInitializer, SolanaInitializerType} from "@atomiqlabs/chain-solana";
|
|
58
|
+
import {StarknetInitializer, StarknetInitializerType} from "@atomiqlabs/chain-starknet";
|
|
59
|
+
import {SwapperFactory} from "@atomiqlabs/sdk";
|
|
60
|
+
|
|
33
61
|
const Factory = new SwapperFactory<[SolanaInitializerType, StarknetInitializerType]>([SolanaInitializer, StarknetInitializer] as const);
|
|
34
62
|
const Tokens = Factory.Tokens; //Get the supported tokens for all the specified chains.
|
|
35
63
|
```
|
|
@@ -39,6 +67,8 @@ const Tokens = Factory.Tokens; //Get the supported tokens for all the specified
|
|
|
39
67
|
This uses browser's Indexed DB by default
|
|
40
68
|
|
|
41
69
|
```typescript
|
|
70
|
+
import {BitcoinNetwork} from "@atomiqlabs/sdk";
|
|
71
|
+
|
|
42
72
|
const swapper = Factory.newSwapper({
|
|
43
73
|
chains: {
|
|
44
74
|
SOLANA: {
|
|
@@ -52,11 +82,22 @@ const swapper = Factory.newSwapper({
|
|
|
52
82
|
});
|
|
53
83
|
```
|
|
54
84
|
|
|
85
|
+
if you want to use custom pricing api, mempool.space RPC url, or tune HTTP request timeouts check out [additional options](#additional-swapper-options)
|
|
86
|
+
|
|
55
87
|
#### NodeJS
|
|
56
88
|
|
|
57
|
-
For NodeJS we need to
|
|
89
|
+
For NodeJS we need to use sqlite storage, for that we first need to install the sqlite storage adaptor
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
npm install @atomiqlabs/storage-sqlite
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Then use pass it in the newSwapper function
|
|
58
96
|
|
|
59
97
|
```typescript
|
|
98
|
+
import {SqliteStorageManager, SqliteUnifiedStorage} from "@atomiqlabs/storage-sqlite";
|
|
99
|
+
import {BitcoinNetwork} from "@atomiqlabs/sdk";
|
|
100
|
+
|
|
60
101
|
const swapper = Factory.newSwapper({
|
|
61
102
|
chains: {
|
|
62
103
|
SOLANA: {
|
|
@@ -67,21 +108,27 @@ const swapper = Factory.newSwapper({
|
|
|
67
108
|
}
|
|
68
109
|
},
|
|
69
110
|
bitcoinNetwork: BitcoinNetwork.TESTNET, //or BitcoinNetwork.MAINNET - this also sets the network to use for Solana (solana devnet for bitcoin testnet) & Starknet (sepolia for bitcoin testnet)
|
|
70
|
-
//The following
|
|
111
|
+
//The following lines are important for running on backend node.js,
|
|
71
112
|
// because the SDK by default uses browser's Indexed DB
|
|
72
|
-
|
|
113
|
+
swapStorage: chainId => new SqliteUnifiedStorage("CHAIN_"+chainId+".sqlite3"),
|
|
114
|
+
chainStorageCtor: name => new SqliteStorageManager("STORE_"+name+".sqlite3"),
|
|
73
115
|
});
|
|
74
116
|
```
|
|
75
117
|
|
|
118
|
+
if you want to use custom pricing api, mempool.space RPC url, or tune HTTP request timeouts check out [additional options](#additional-swapper-options)
|
|
119
|
+
|
|
76
120
|
### Signer
|
|
77
121
|
|
|
78
122
|
```typescript
|
|
79
|
-
|
|
123
|
+
import {SolanaSigner} from "@atomiqlabs/chain-solana";
|
|
124
|
+
//Browser - react, using solana wallet adapter
|
|
80
125
|
const anchorWallet = useAnchorWallet();
|
|
81
126
|
const wallet = new SolanaSigner(anchorWallet);
|
|
82
127
|
```
|
|
83
128
|
|
|
84
129
|
```typescript
|
|
130
|
+
import {WalletAccount} from "starknet";
|
|
131
|
+
import {StarknetSigner} from "@atomiqlabs/chain-starknet";
|
|
85
132
|
//Browser, using get-starknet
|
|
86
133
|
const swo = await connect();
|
|
87
134
|
const wallet = new StarknetSigner(new WalletAccount(starknetRpc, swo.wallet));
|
|
@@ -90,8 +137,16 @@ const wallet = new StarknetSigner(new WalletAccount(starknetRpc, swo.wallet));
|
|
|
90
137
|
or
|
|
91
138
|
|
|
92
139
|
```typescript
|
|
93
|
-
|
|
94
|
-
|
|
140
|
+
import {Keypair} from "@solana/web3.js";
|
|
141
|
+
import {SolanaKeypairWallet, SolanaSigner} from "@atomiqlabs/chain-solana";
|
|
142
|
+
//Creating Solana signer from private key
|
|
143
|
+
const solanaSigner = new SolanaSigner(new SolanaKeypairWallet(Keypair.fromSecretKey(solanaKey)), Keypair.fromSecretKey(solanaKey));
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import {SolanaKeypairWallet, SolanaSigner} from "@atomiqlabs/chain-solana";
|
|
148
|
+
//Creating Starknet signer from private key
|
|
149
|
+
const starknetSigner = new StarknetSigner(new StarknetKeypairWallet(starknetRpc, starknetKey));
|
|
95
150
|
```
|
|
96
151
|
|
|
97
152
|
### Initialization
|
|
@@ -106,42 +161,67 @@ Now we have the multichain swapper initialized
|
|
|
106
161
|
|
|
107
162
|
### Extract chain-specific swapper with signer
|
|
108
163
|
|
|
109
|
-
To make it easier to do swaps between bitcoin and a specific chain we can extract a chain-specific swapper, and also set a signer.
|
|
164
|
+
To make it easier to do swaps between bitcoin and a specific chain we can extract a chain-specific swapper, and also set a signer. e.g.:
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
const solanaSwapper = swapper.withChain<"SOLANA">("SOLANA");
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
or also with signer
|
|
110
171
|
|
|
111
172
|
```typescript
|
|
112
|
-
const
|
|
173
|
+
const starknetSwapperWithSigner = swapper.withChain<"STARKNET">("STARKNET").withSigner(signer);
|
|
113
174
|
```
|
|
114
175
|
|
|
115
176
|
### Bitcoin on-chain swaps
|
|
116
177
|
|
|
117
178
|
#### Swap Smart chain -> Bitcoin on-chain
|
|
118
179
|
|
|
119
|
-
|
|
180
|
+
Getting swap quote
|
|
120
181
|
|
|
121
182
|
```typescript
|
|
122
183
|
const _exactIn = false; //exactIn = false, so we specify the output amount
|
|
123
|
-
const _amount = 10000n; //Amount in BTC base units - sats
|
|
184
|
+
const _amount = 10000n; //Amount in BTC base units - sats (10000 sats = 0.0001 BTC)
|
|
124
185
|
const _address = "bc1qtw67hj77rt8zrkkg3jgngutu0yfgt9czjwusxt"; //BTC address of the recipient
|
|
125
186
|
|
|
126
187
|
//Create the swap: swapping SOL to Bitcoin on-chain, receiving _amount of satoshis (smallest unit of bitcoin) to _address
|
|
127
|
-
const swap = await
|
|
128
|
-
Tokens.SOLANA.SOL,
|
|
129
|
-
Tokens.BITCOIN.BTC,
|
|
188
|
+
const swap = await swapper.swap(
|
|
189
|
+
Tokens.SOLANA.SOL, //From specified source token
|
|
190
|
+
Tokens.BITCOIN.BTC, //Swap to BTC
|
|
130
191
|
_amount,
|
|
131
192
|
_exactIn,
|
|
132
|
-
|
|
193
|
+
solanaSigner.getAddress(), //Source address and smart chain signer
|
|
194
|
+
_address //Destination of the swap
|
|
133
195
|
);
|
|
134
196
|
|
|
135
197
|
//Get the amount required to pay and fee
|
|
136
|
-
const
|
|
137
|
-
const fee: string = swap.getFee().amountInSrcToken.
|
|
198
|
+
const input: string = swap.getInputWithoutFee().toString(); //Input amount excluding fees
|
|
199
|
+
const fee: string = swap.getFee().amountInSrcToken.toString(); //Fees paid on the output
|
|
200
|
+
const inputWithFees: string = swap.getInput().toString(); //Total amount paid including fees
|
|
201
|
+
|
|
202
|
+
const output: string = swap.getOutput().toString(); //Total output amount
|
|
138
203
|
|
|
139
204
|
//Get swap expiration time
|
|
140
|
-
const expiry: number = swap.
|
|
205
|
+
const expiry: number = swap.getQuoteExpiry(); //Expiration time of the swap quote in UNIX milliseconds, swap needs to be initiated before this time
|
|
206
|
+
|
|
207
|
+
//Get pricing info
|
|
208
|
+
const swapPrice = swap.getPriceInfo().swapPrice; //Price of the current swap (excluding fees)
|
|
209
|
+
const marketPrice = swap.getPriceInfo().marketPrice; //Current market price
|
|
210
|
+
const difference = swap.getPriceInfo().difference; //Difference between the swap price & current market price
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Initiating the swap
|
|
141
214
|
|
|
215
|
+
```typescript
|
|
142
216
|
//Initiate and pay for the swap
|
|
143
|
-
await swap.commit();
|
|
217
|
+
await swap.commit(solanaSigner);
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
or [sign and send transaction manually](#manually-signing-smart-chain-transactions)
|
|
221
|
+
|
|
222
|
+
Wait for the swap to execute, refund in case of failure
|
|
144
223
|
|
|
224
|
+
```typescript
|
|
145
225
|
//Wait for the swap to conclude
|
|
146
226
|
const result: boolean = await swap.waitForPayment();
|
|
147
227
|
if(!result) {
|
|
@@ -172,48 +252,83 @@ if(!result) {
|
|
|
172
252
|
- ToBTCSwapState.REFUNDABLE = 4
|
|
173
253
|
- Swap was initiated but counterparty failed to process it, the user can now refund his funds
|
|
174
254
|
|
|
175
|
-
#### Swap Bitcoin on-chain ->
|
|
255
|
+
#### Swap Bitcoin on-chain -> Solana
|
|
256
|
+
|
|
257
|
+
NOTE: Solana uses an old swap protocol for Bitcoin on-chain -> Solana swaps, the flow here is different from the one for Starknet and other chains.
|
|
176
258
|
|
|
177
|
-
|
|
259
|
+
Getting swap quote
|
|
178
260
|
|
|
179
261
|
```typescript
|
|
180
262
|
const _exactIn = true; //exactIn = true, so we specify the input amount
|
|
181
263
|
const _amount = fromHumanReadableString("0.0001", Tokens.BITCOIN.BTC); //Amount in BTC base units - sats, we can also use a utility function here
|
|
182
264
|
|
|
183
265
|
//Create the swap: swapping _amount of satoshis of Bitcoin on-chain to SOL
|
|
184
|
-
const swap = await
|
|
185
|
-
Tokens.BITCOIN.BTC,
|
|
186
|
-
Tokens.SOLANA.SOL,
|
|
266
|
+
const swap = await swapper.swap(
|
|
267
|
+
Tokens.BITCOIN.BTC, //Swap from BTC
|
|
268
|
+
Tokens.SOLANA.SOL, //Into specified destination token
|
|
187
269
|
_amount,
|
|
188
|
-
_exactIn
|
|
270
|
+
_exactIn, //Whether we define an input or output amount
|
|
271
|
+
undefined, //Source address for the swap, not used for swaps from BTC
|
|
272
|
+
solanaSigner.getAddress() //Destination address
|
|
189
273
|
);
|
|
190
274
|
|
|
191
|
-
//Get the amount required to pay
|
|
192
|
-
const
|
|
193
|
-
const
|
|
194
|
-
const
|
|
275
|
+
//Get the amount required to pay and fee
|
|
276
|
+
const input: string = swap.getInputWithoutFee().toString(); //Input amount excluding fees
|
|
277
|
+
const fee: string = swap.getFee().amountInSrcToken.toString(); //Fees paid on the output
|
|
278
|
+
const inputWithFees: string = swap.getInput().toString(); //Total amount paid including fees
|
|
195
279
|
|
|
196
|
-
|
|
197
|
-
|
|
280
|
+
const output: string = swap.getOutput().toString(); //Total output amount
|
|
281
|
+
|
|
282
|
+
//Get swap expiration time
|
|
283
|
+
const expiry: number = swap.getQuoteExpiry(); //Expiration time of the swap quote in UNIX milliseconds, swap needs to be initiated before this time
|
|
198
284
|
|
|
199
285
|
//Get security deposit amount (Human readable amount of SOL that needs to be put down to rent the liquidity from swap intermediary), you will get this deposit back if the swap succeeds
|
|
200
|
-
const securityDeposit: string = swap.getSecurityDeposit().
|
|
286
|
+
const securityDeposit: string = swap.getSecurityDeposit().toString();
|
|
201
287
|
//Get claimer bounty (Human readable amount of SOL reserved as a reward for watchtowers to claim the swap on your behalf)
|
|
202
|
-
const claimerBounty: string = swap.getClaimerBounty().
|
|
288
|
+
const claimerBounty: string = swap.getClaimerBounty().toString();
|
|
203
289
|
|
|
204
|
-
//
|
|
205
|
-
|
|
290
|
+
//Get pricing info
|
|
291
|
+
const swapPrice = swap.getPriceInfo().swapPrice; //Price of the current swap (excluding fees)
|
|
292
|
+
const marketPrice = swap.getPriceInfo().marketPrice; //Current market price
|
|
293
|
+
const difference = swap.getPriceInfo().difference; //Difference between the swap price & current market price
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Initiating the swap
|
|
206
297
|
|
|
298
|
+
```typescript
|
|
299
|
+
//Initiate the swap on the destination chain (Solana) by opening up the bitcoin swap address
|
|
300
|
+
await swap.commit(solanaSigner);
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
or [sign and send transaction manually](#manually-signing-smart-chain-transactions)
|
|
304
|
+
|
|
305
|
+
Sending bitcoin
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
207
308
|
//Get the bitcoin address
|
|
208
309
|
const receivingAddressOnBitcoin = swap.getAddress();
|
|
209
310
|
//Get the QR code data (contains the address and amount)
|
|
210
|
-
const qrCodeData = swap.
|
|
211
|
-
//
|
|
212
|
-
|
|
311
|
+
const qrCodeData = swap.getHyperlink(); //Data that can be displayed in the form of QR code
|
|
312
|
+
//Send the exact amount of BTC to the provided address
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
or get a psbt and sign it
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
//Or obtain the funded PSBT (input already added) - ready for signing
|
|
319
|
+
const {psbt, signInputs} = await swap.getFundedPsbt({address: "", publicKey: ""});
|
|
320
|
+
for(let signIdx of signInputs) {
|
|
321
|
+
psbt.signIdx(..., signIdx); //Or pass it to external signer
|
|
322
|
+
}
|
|
323
|
+
const bitcoinTxId = await swap.submitPsbt(psbt);
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Waiting for swap execution
|
|
213
327
|
|
|
328
|
+
```typescript
|
|
214
329
|
try {
|
|
215
330
|
//Wait for the payment to arrive
|
|
216
|
-
await swap.
|
|
331
|
+
await swap.waitForBitcoinTransaction(
|
|
217
332
|
null, null,
|
|
218
333
|
(
|
|
219
334
|
txId: string, //Transaction ID of the received bitcoin transaction
|
|
@@ -231,10 +346,10 @@ try {
|
|
|
231
346
|
|
|
232
347
|
//Swap should get automatically claimed by the watchtowers, if not we can call swap.claim() ourselves
|
|
233
348
|
try {
|
|
234
|
-
|
|
349
|
+
await swap.waitTillClaimed(timeoutSignal(30*1000));
|
|
235
350
|
} catch (e) {
|
|
236
|
-
|
|
237
|
-
|
|
351
|
+
//Claim ourselves when automatic claim doesn't happen in 30 seconds
|
|
352
|
+
await swap.claim(solanaSigner);
|
|
238
353
|
}
|
|
239
354
|
```
|
|
240
355
|
|
|
@@ -255,41 +370,184 @@ try {
|
|
|
255
370
|
- FromBTCSwapState.CLAIM_CLAIMED = 3
|
|
256
371
|
- Swap funds are claimed to the user's wallet
|
|
257
372
|
|
|
373
|
+
|
|
374
|
+
#### Swap Bitcoin on-chain -> Starknet
|
|
375
|
+
|
|
376
|
+
NOTE: Starknet uses a new swap protocol for Bitcoin on-chain -> Solana swaps, the flow here is different from the one for Solana!
|
|
377
|
+
|
|
378
|
+
Getting swap quote
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
const _exactIn = true; //exactIn = true, so we specify the input amount
|
|
382
|
+
const _amount = fromHumanReadableString("0.0001", Tokens.BITCOIN.BTC); //Amount in BTC base units - sats, we can also use a utility function here
|
|
383
|
+
|
|
384
|
+
//Create the swap: swapping _amount of satoshis of Bitcoin on-chain to SOL
|
|
385
|
+
const swap = await swapper.swap(
|
|
386
|
+
Tokens.BITCOIN.BTC, //Swap from BTC
|
|
387
|
+
Tokens.STARKNET.STRK, //Into specified destination token
|
|
388
|
+
_amount,
|
|
389
|
+
_exactIn, //Whether we define an input or output amount
|
|
390
|
+
undefined, //Source address for the swap, not used for swaps from BTC
|
|
391
|
+
starknetSigner.getAddress(), //Destination address
|
|
392
|
+
{
|
|
393
|
+
gasAmount: 1_000_000_000_000_000_000n //We can also request a gas drop on the destination chain (here requesting 1 STRK)
|
|
394
|
+
}
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
//Get the amount required to pay and fee
|
|
398
|
+
const input: string = swap.getInputWithoutFee().toString(); //Input amount excluding fees
|
|
399
|
+
const fee: string = swap.getFee().amountInSrcToken.toString(); //Fees paid on the output
|
|
400
|
+
const inputWithFees: string = swap.getInput().toString(); //Total amount paid including fees
|
|
401
|
+
|
|
402
|
+
const output: string = swap.getOutput().toString(); //Total output amount
|
|
403
|
+
|
|
404
|
+
//Get swap expiration time
|
|
405
|
+
const expiry: number = swap.getQuoteExpiry(); //Expiration time of the swap quote in UNIX milliseconds, swap needs to be initiated before this time
|
|
406
|
+
|
|
407
|
+
//Get pricing info
|
|
408
|
+
const swapPrice = swap.getPriceInfo().swapPrice; //Price of the current swap (excluding fees)
|
|
409
|
+
const marketPrice = swap.getPriceInfo().marketPrice; //Current market price
|
|
410
|
+
const difference = swap.getPriceInfo().difference; //Difference between the swap price & current market price
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Initiating the swap
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
//Obtain the funded PSBT (input already added) - ready for signing
|
|
417
|
+
const {psbt, signInputs} = await swap.getFundedPsbt({address: "", publicKey: ""});
|
|
418
|
+
for(let signIdx of signInputs) {
|
|
419
|
+
psbt.signIdx(..., signIdx); //Or pass it to external signer
|
|
420
|
+
}
|
|
421
|
+
const bitcoinTxId = await swap.submitPsbt(psbt);
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
or get raw PSBT and add inputs manually
|
|
425
|
+
|
|
426
|
+
```typescript
|
|
427
|
+
//Or obtain raw PSBT to which inputs still need to be added
|
|
428
|
+
const {psbt, in1sequence} = await swap.getPsbt();
|
|
429
|
+
psbt.addInput(...);
|
|
430
|
+
//Make sure the second input's sequence (index 1) is as specified in the in1sequence variable
|
|
431
|
+
psbt.updateInput(1, {sequence: in1sequence});
|
|
432
|
+
//Sign the PSBT, sign every input except the first one
|
|
433
|
+
for(let i=1;i<psbt.inputsLength; i++) psbt.signIdx(..., i); //Or pass it to external signer
|
|
434
|
+
//Submit the signed PSBT
|
|
435
|
+
const bitcoinTxId = await swap.submitPsbt(psbt);
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
Waiting for swap execution
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
try {
|
|
442
|
+
//Wait for the payment to arrive
|
|
443
|
+
await swap.waitForBitcoinTransaction(
|
|
444
|
+
null, null,
|
|
445
|
+
(
|
|
446
|
+
txId: string, //Transaction ID of the received bitcoin transaction
|
|
447
|
+
confirmations: number, //Current confirmations of the transaction
|
|
448
|
+
targetConfirmations: number, //Required confirmations
|
|
449
|
+
transactionETAms: number //Estimated in time (in milliseconds) until when the transaction will receive required amount of confirmations
|
|
450
|
+
) => {
|
|
451
|
+
//Callback for transaction updates
|
|
452
|
+
}
|
|
453
|
+
);
|
|
454
|
+
} catch(e) {
|
|
455
|
+
//Error occurred while waiting for payment, this is most likely due to network errors
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
//Swap should get automatically claimed by the watchtowers, if not we can call swap.claim() ourselves
|
|
460
|
+
try {
|
|
461
|
+
await swap.waitTillClaimedOrFronted(timeoutSignal(30*1000));
|
|
462
|
+
} catch (e) {
|
|
463
|
+
//Claim ourselves when automatic claim doesn't happen in 30 seconds
|
|
464
|
+
await swap.claim(starknetSigner);
|
|
465
|
+
}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
##### Swap states
|
|
469
|
+
|
|
470
|
+
- SpvFromBTCSwapState.CLOSED = -5
|
|
471
|
+
- Catastrophic failure during swap, shall never happen
|
|
472
|
+
- SpvFromBTCSwapState.FAILED = -4
|
|
473
|
+
- Bitcoin transaction was sent, but was double-spent later, therefore the swap was failed (no BTC was sent)
|
|
474
|
+
- SpvFromBTCSwapState.DECLINED = -3
|
|
475
|
+
- LP declined to process the swap transaction, no BTC was sent
|
|
476
|
+
- SpvFromBTCSwapState.QUOTE_EXPIRED = -2
|
|
477
|
+
- Swap quote expired and cannot be executed anymore
|
|
478
|
+
- SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED = -1
|
|
479
|
+
- Swap quote soft-expired (i.e. the quote probably expired, but if there is a bitcoin transaction being submitted it might still succeed)
|
|
480
|
+
- SpvFromBTCSwapState.CREATED = 0
|
|
481
|
+
- Swap quote is created, waiting on user to sign the bitcoin swap transaction
|
|
482
|
+
- SpvFromBTCSwapState.SIGNED = 1
|
|
483
|
+
- Bitcoin swap transaction was signed by the client
|
|
484
|
+
- SpvFromBTCSwapState.POSTED = 2
|
|
485
|
+
- Bitcoin swap transaction was posted to the LP
|
|
486
|
+
- SpvFromBTCSwapState.BROADCASTED = 3
|
|
487
|
+
- LP broadcasted the bitcoin swap transaction
|
|
488
|
+
- SpvFromBTCSwapState.FRONTED = 4
|
|
489
|
+
- Swap funds have been deposited to the user's wallet in front of the time
|
|
490
|
+
- SpvFromBTCSwapState.BTC_TX_CONFIRMED = 5
|
|
491
|
+
- Bitcoin swap transaction is confirmed
|
|
492
|
+
- SpvFromBTCSwapState.CLAIM_CLAIMED = 6
|
|
493
|
+
- Swap funds are claimed to the user's wallet
|
|
494
|
+
|
|
258
495
|
### Bitcoin lightning network swaps
|
|
259
496
|
|
|
260
497
|
#### Swap Smart chain -> Bitcoin lightning network
|
|
261
498
|
|
|
499
|
+
Getting swap quote
|
|
500
|
+
|
|
262
501
|
```typescript
|
|
263
502
|
//Destination lightning network invoice, amount needs to be part of the invoice!
|
|
264
503
|
const _lightningInvoice = "lnbc10u1pj2q0g9pp5ejs6m677m39cznpzum7muruvh50ys93ln82p4j9ks2luqm56xxlshp52r2anlhddfa9ex9vpw9gstxujff8a0p8s3pzvua930js0kwfea6scqzzsxqyz5vqsp5073zskc5qfgp7lre0t6s8uexxxey80ax564hsjklfwfjq2ew0ewq9qyyssqvzmgs6f8mvuwgfa9uqxhtza07qem4yfhn9wwlpskccmuwplsqmh8pdy6c42kqdu8p73kky9lsnl40qha5396d8lpgn90y27ltfc5rfqqq59cya";
|
|
265
504
|
|
|
266
505
|
//Create the swap: swapping SOL to Bitcoin lightning
|
|
267
|
-
const swap = await
|
|
268
|
-
Tokens.SOLANA.SOL,
|
|
269
|
-
Tokens.BITCOIN.BTCLN,
|
|
270
|
-
|
|
271
|
-
false,
|
|
272
|
-
|
|
506
|
+
const swap = await swapper.swap(
|
|
507
|
+
Tokens.SOLANA.SOL, //From specified source token
|
|
508
|
+
Tokens.BITCOIN.BTCLN, //Swap to BTC-LN
|
|
509
|
+
undefined, //Amount is specified in the lightning network invoice!
|
|
510
|
+
false, //Make sure we use exactIn=false for swaps to BTC-LN, if you want to use exactIn=true and set an amount, use LNURL-pay!
|
|
511
|
+
solanaSigner.getAddress(), //Source address and smart chain signer
|
|
512
|
+
_lightningInvoice //Destination of the swap
|
|
273
513
|
);
|
|
274
514
|
|
|
275
515
|
//Get the amount required to pay and fee
|
|
276
|
-
const
|
|
277
|
-
const fee: string = swap.getFee().amountInSrcToken.
|
|
516
|
+
const input: string = swap.getInputWithoutFee().toString(); //Input amount excluding fees
|
|
517
|
+
const fee: string = swap.getFee().amountInSrcToken.toString(); //Fees paid on the output
|
|
518
|
+
const inputWithFees: string = swap.getInput().toString(); //Total amount paid including fees
|
|
519
|
+
|
|
520
|
+
const output: string = swap.getOutput().toString(); //Total output amount
|
|
278
521
|
|
|
279
522
|
//Get swap expiration time
|
|
280
|
-
const expiry: number = swap.
|
|
523
|
+
const expiry: number = swap.getQuoteExpiry(); //Expiration time of the swap quote in UNIX milliseconds, swap needs to be initiated before this time
|
|
524
|
+
|
|
525
|
+
//Get pricing info
|
|
526
|
+
const swapPrice = swap.getPriceInfo().swapPrice; //Price of the current swap (excluding fees)
|
|
527
|
+
const marketPrice = swap.getPriceInfo().marketPrice; //Current market price
|
|
528
|
+
const difference = swap.getPriceInfo().difference; //Difference between the swap price & current market price
|
|
529
|
+
```
|
|
281
530
|
|
|
531
|
+
Initiating the swap
|
|
532
|
+
|
|
533
|
+
```typescript
|
|
282
534
|
//Initiate and pay for the swap
|
|
283
|
-
await swap.commit();
|
|
535
|
+
await swap.commit(solanaSigner);
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
or [sign and send transaction manually](#manually-signing-smart-chain-transactions)
|
|
284
539
|
|
|
540
|
+
Wait for the swap to execute, refund in case of failure
|
|
541
|
+
|
|
542
|
+
```typescript
|
|
285
543
|
//Wait for the swap to conclude
|
|
286
544
|
const result: boolean = await swap.waitForPayment();
|
|
287
545
|
if(!result) {
|
|
288
|
-
|
|
289
|
-
|
|
546
|
+
//Swap failed, money can be refunded
|
|
547
|
+
await swap.refund(solanaSigner);
|
|
290
548
|
} else {
|
|
291
|
-
|
|
292
|
-
|
|
549
|
+
//Swap successful, we can get the lightning payment secret pre-image, which acts as a proof of payment
|
|
550
|
+
const lightningSecret = swap.getSecret();
|
|
293
551
|
}
|
|
294
552
|
```
|
|
295
553
|
|
|
@@ -313,41 +571,79 @@ if(!result) {
|
|
|
313
571
|
- Swap was initiated but counterparty failed to process it, the user can now refund his funds
|
|
314
572
|
|
|
315
573
|
#### Swap Bitcoin lightning network -> Smart chain
|
|
574
|
+
|
|
575
|
+
Getting swap quote
|
|
576
|
+
|
|
316
577
|
```typescript
|
|
317
578
|
const _exactIn = true; //exactIn = true, so we specify the input amount
|
|
318
579
|
const _amount = 10000n; //Amount in BTC base units - sats
|
|
319
580
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
Tokens.
|
|
323
|
-
Tokens.SOLANA.SOL,
|
|
581
|
+
const swap = await swapper.swap(
|
|
582
|
+
Tokens.BITCOIN.BTCLN, //Swap from BTC-LN
|
|
583
|
+
Tokens.STARKNET.STRK, //Into specified destination token
|
|
324
584
|
_amount,
|
|
325
|
-
_exactIn
|
|
585
|
+
_exactIn, //Whether we define an input or output amount
|
|
586
|
+
undefined, //Source address for the swap, not used for swaps from BTC-LN
|
|
587
|
+
signer.getAddress() //Destination address
|
|
326
588
|
);
|
|
327
589
|
|
|
328
590
|
//Get the bitcoin lightning network invoice (the invoice contains pre-entered amount)
|
|
329
|
-
const receivingLightningInvoice: string = swap.
|
|
330
|
-
//Get the
|
|
331
|
-
const qrCodeData: string = swap.
|
|
591
|
+
const receivingLightningInvoice: string = swap.getAddress();
|
|
592
|
+
//Get the URI hyperlink (contains the lightning network invoice) which can be displayed also as QR code
|
|
593
|
+
const qrCodeData: string = swap.getHyperlink();
|
|
594
|
+
|
|
595
|
+
//Get the amount required to pay and fee
|
|
596
|
+
const input: string = swap.getInputWithoutFee().toString(); //Input amount excluding fees
|
|
597
|
+
const fee: string = swap.getFee().amountInSrcToken.toString(); //Fees paid on the output
|
|
598
|
+
const inputWithFees: string = swap.getInput().toString(); //Total amount paid including fees
|
|
332
599
|
|
|
333
|
-
|
|
334
|
-
const amountToBePaidOnBitcoin: string = swap.getInput().amount; //Human readable amount of BTC that needs to be send to the BTC swap address
|
|
335
|
-
const amountToBeReceivedOnSolana: string = swap.getOutput().amount; //Human readable amount SOL that will be received on Solana
|
|
336
|
-
const fee: string = swap.getFee().amountInSrcToken.amount; //Human readable fee in BTC
|
|
600
|
+
const output: string = swap.getOutput().toString(); //Total output amount
|
|
337
601
|
|
|
602
|
+
//Get swap expiration time
|
|
603
|
+
const expiry: number = swap.getQuoteExpiry(); //Expiration time of the swap quote in UNIX milliseconds, swap needs to be initiated before this time
|
|
604
|
+
|
|
605
|
+
//Get security deposit amount (Human readable amount of STRK that needs to be put down to rent the liquidity from swap intermediary), you will get this deposit back if the swap succeeds
|
|
606
|
+
const securityDeposit: string = swap.getSecurityDeposit().toString();
|
|
607
|
+
|
|
608
|
+
//Get pricing info
|
|
609
|
+
const swapPrice = swap.getPriceInfo().swapPrice; //Price of the current swap (excluding fees)
|
|
610
|
+
const marketPrice = swap.getPriceInfo().marketPrice; //Current market price
|
|
611
|
+
const difference = swap.getPriceInfo().difference; //Difference between the swap price & current market price
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
Pay the displayed lightning network invoice from an external wallet
|
|
615
|
+
|
|
616
|
+
Wait for the payment to be received
|
|
617
|
+
|
|
618
|
+
```typescript
|
|
619
|
+
//Start listening to incoming lightning network payment
|
|
620
|
+
const success = await swap.waitForPayment();
|
|
621
|
+
if(!success) {
|
|
622
|
+
//Lightning network payment not received in time and quote expired!
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
Claim the funds on the destination smart chains, this settles the swap and lightning network payment
|
|
628
|
+
|
|
629
|
+
```typescript
|
|
338
630
|
try {
|
|
339
|
-
//Wait for the lightning payment to arrive
|
|
340
|
-
await swap.waitForPayment();
|
|
341
631
|
//Claim the swap funds - this will initiate 2 transactions
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
632
|
+
if(swap.canCommitAndClaimInOneShot()) {
|
|
633
|
+
//Some chains (e.g. Solana) support signing multiple transactions in one flow
|
|
634
|
+
await swap.commitAndClaim(solanaSigner);
|
|
635
|
+
} else {
|
|
636
|
+
//Other chains (e.g. Starknet) don't support signing multiple transaction in one flow, therefore you need to sign one-by-one
|
|
637
|
+
await swap.commit(starknetSigner);
|
|
638
|
+
await swap.claim(starknetSigner);
|
|
639
|
+
}
|
|
346
640
|
} catch(e) {
|
|
347
641
|
//Error occurred while waiting for payment
|
|
348
642
|
}
|
|
349
643
|
```
|
|
350
644
|
|
|
645
|
+
or [sign and send transactions manually](#manually-signing-smart-chain-transactions)
|
|
646
|
+
|
|
351
647
|
##### Swap states
|
|
352
648
|
|
|
353
649
|
- FromBTCLNSwapState.FAILED = -4
|
|
@@ -367,24 +663,6 @@ try {
|
|
|
367
663
|
- FromBTCLNSwapState.CLAIM_CLAIMED = 3
|
|
368
664
|
- Funds were successfully claimed & lightning network secret pre-image revealed, so the lightning network payment will settle now
|
|
369
665
|
|
|
370
|
-
### Getting state of the swap
|
|
371
|
-
|
|
372
|
-
You can get the current state of the swap with:
|
|
373
|
-
|
|
374
|
-
```typescript
|
|
375
|
-
const state = swap.getState();
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
You can also set a listener to listen for swap state changes:
|
|
379
|
-
|
|
380
|
-
```typescript
|
|
381
|
-
swap.events.on("swapState", swap => {
|
|
382
|
-
const newState = swap.getState();
|
|
383
|
-
});
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
For the meaning of the states please refer to the "Swap state" section under each swap type.
|
|
387
|
-
|
|
388
666
|
### LNURLs & readable lightning identifiers
|
|
389
667
|
|
|
390
668
|
LNURLs extend the lightning network functionality by creating static lightning addreses (LNURL-pay & static internet identifiers) and QR codes which allow you to pull funds from them (LNURL-withdraw)
|
|
@@ -394,6 +672,8 @@ This SDK supports:
|
|
|
394
672
|
* LNURL-withdraw ([LUD-3](https://github.com/lnurl/luds/blob/luds/03.md))
|
|
395
673
|
* Static internet identifiers ([LUD-16](https://github.com/lnurl/luds/blob/luds/16.md))
|
|
396
674
|
|
|
675
|
+
You can parse LNURLs and lightning invoices automatically using the [Unified address parser](#unified-address-parser)
|
|
676
|
+
|
|
397
677
|
#### Differences
|
|
398
678
|
|
|
399
679
|
Lightning invoices:
|
|
@@ -410,136 +690,385 @@ LNURLs & lightning identifiers:
|
|
|
410
690
|
* Possibility to attach a message/comment to a payment
|
|
411
691
|
* Receive a message/url as a result of the payment
|
|
412
692
|
|
|
413
|
-
#### Helpers
|
|
414
|
-
|
|
415
|
-
It is good practice to automatically distinguish between lightning network invoices & LNURLs and adjust the UI accordingly.
|
|
416
|
-
Therefore there are a few helper functions to help with that:
|
|
417
|
-
```typescript
|
|
418
|
-
const isLNInvoice: boolean = swapper.isValidLightningInvoice(_input); //Checks if the input is lightning network invoice
|
|
419
|
-
const isLNURL: boolean = swapper.isValidLNURL(_input); //Checks if the input is LNURL or lightning identifier
|
|
420
|
-
if(isLNURL) {
|
|
421
|
-
//Get the type of the LNURL
|
|
422
|
-
const result: (LNURLPay | LNURLWithdraw | null) = await swapper.getLNURLTypeAndData(_input);
|
|
423
|
-
if(result.type==="pay") {
|
|
424
|
-
const lnurlPayData: LNURLPay = result;
|
|
425
|
-
const minPayable: bigint = lnurlPayData.min; //Minimum payment amount in satoshis
|
|
426
|
-
const maxPayable: bigint = lnurlPayData.max; //Maximum payment amount in satoshis
|
|
427
|
-
const icon: (string | null) = lnurlPayData.icon; //URL encoded icon that should be displayed on the UI
|
|
428
|
-
const shortDescription: (string | null) = lnurlPayData.shortDescription; //Short description of the payment
|
|
429
|
-
const longDescription: (string | null) = lnurlPayData.longDescription; //Long description of the payment
|
|
430
|
-
const maxCommentLength: (number | 0) = lnurlPayData.commentMaxLength; //Maximum allowed length of the payment message/comment (0 means no comment allowed)
|
|
431
|
-
//Should show a UI displaying the icon, short description, long description, allowing the user to choose an amount he wishes to pay and possibly also a comment
|
|
432
|
-
}
|
|
433
|
-
if(result.type==="withdraw") {
|
|
434
|
-
const lnurlWithdrawData: LNURLWithdraw = result;
|
|
435
|
-
const minWithdrawable: bigint = lnurlWithdrawData.min;
|
|
436
|
-
const maxWithdrawable: bigint = lnurlWithdrawData.max;
|
|
437
|
-
//Should show a UI allowing the user to choose an amount he wishes to withdraw
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
```
|
|
441
|
-
|
|
442
693
|
#### Swap Smart chain -> Bitcoin lightning network
|
|
694
|
+
|
|
695
|
+
Getting swap quote
|
|
696
|
+
|
|
443
697
|
```typescript
|
|
444
698
|
const _lnurlOrIdentifier: string = "lnurl1dp68gurn8ghj7ampd3kx2ar0veekzar0wd5xjtnrdakj7tnhv4kxctttdehhwm30d3h82unvwqhkx6rfvdjx2ctvxyesuk0a27"; //Destination LNURL-pay or readable identifier
|
|
445
699
|
const _exactIn = false; //exactIn = false, so we specify the output amount
|
|
446
700
|
const _amount: bigint = 10000n; //Amount of satoshis to send (1 BTC = 100 000 000 satoshis)
|
|
447
701
|
|
|
448
702
|
//Create the swap: swapping SOL to Bitcoin lightning
|
|
449
|
-
const swap = await
|
|
450
|
-
Tokens.SOLANA.SOL,
|
|
451
|
-
Tokens.BITCOIN.BTCLN,
|
|
452
|
-
_amount,
|
|
453
|
-
_exactIn,
|
|
454
|
-
|
|
703
|
+
const swap = await swapper.swap(
|
|
704
|
+
Tokens.SOLANA.SOL, //From specified source token
|
|
705
|
+
Tokens.BITCOIN.BTCLN, //Swap to BTC-LN
|
|
706
|
+
_amount, //Now we can specify an amount for a lightning network payment!
|
|
707
|
+
_exactIn, //We can also use exactIn=true here and set an amount in input token
|
|
708
|
+
solanaSigner.getAddress(), //Source address and smart chain signer
|
|
709
|
+
_lnurlOrIdentifier, //Destination of the swap
|
|
710
|
+
{
|
|
711
|
+
comment: "Hello world" //For LNURL-pay we can also pass a comment to the recipient
|
|
712
|
+
}
|
|
455
713
|
);
|
|
456
714
|
|
|
457
715
|
//Get the amount required to pay and fee
|
|
458
|
-
const
|
|
459
|
-
const fee: string = swap.getFee().amountInSrcToken.
|
|
716
|
+
const input: string = swap.getInputWithoutFee().toString(); //Input amount excluding fees
|
|
717
|
+
const fee: string = swap.getFee().amountInSrcToken.toString(); //Fees paid on the output
|
|
718
|
+
const inputWithFees: string = swap.getInput().toString(); //Total amount paid including fees
|
|
719
|
+
|
|
720
|
+
const output: string = swap.getOutput().toString(); //Total output amount
|
|
460
721
|
|
|
461
722
|
//Get swap expiration time
|
|
462
|
-
const expiry: number = swap.
|
|
723
|
+
const expiry: number = swap.getQuoteExpiry(); //Expiration time of the swap quote in UNIX milliseconds, swap needs to be initiated before this time
|
|
724
|
+
|
|
725
|
+
//Get pricing info
|
|
726
|
+
const swapPrice = swap.getPriceInfo().swapPrice; //Price of the current swap (excluding fees)
|
|
727
|
+
const marketPrice = swap.getPriceInfo().marketPrice; //Current market price
|
|
728
|
+
const difference = swap.getPriceInfo().difference; //Difference between the swap price & current market price
|
|
729
|
+
```
|
|
463
730
|
|
|
731
|
+
Initiating the swap
|
|
732
|
+
|
|
733
|
+
```typescript
|
|
464
734
|
//Initiate and pay for the swap
|
|
465
|
-
await swap.commit();
|
|
735
|
+
await swap.commit(solanaSigner);
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
or [sign and send transaction manually](#manually-signing-smart-chain-transactions)
|
|
466
739
|
|
|
740
|
+
Wait for the swap to execute, refund in case of failure
|
|
741
|
+
|
|
742
|
+
```typescript
|
|
467
743
|
//Wait for the swap to conclude
|
|
468
744
|
const result: boolean = await swap.waitForPayment();
|
|
469
745
|
if(!result) {
|
|
470
|
-
|
|
471
|
-
|
|
746
|
+
//Swap failed, money can be refunded
|
|
747
|
+
await swap.refund(solanaSigner);
|
|
472
748
|
} else {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
749
|
+
//Swap successful, we can get the lightning payment secret pre-image, which acts as a proof of payment
|
|
750
|
+
const lightningSecret = swap.getSecret();
|
|
751
|
+
//In case the LNURL contained a success action, we can read it now and display it to user
|
|
752
|
+
if(swap.hasSuccessAction()) {
|
|
753
|
+
//Contains a success action that should displayed to the user
|
|
754
|
+
const successMessage = swap.getSuccessAction();
|
|
755
|
+
const description: string = successMessage.description; //Description of the message
|
|
756
|
+
const text: (string | null) = successMessage.text; //Main text of the message
|
|
757
|
+
const url: (string | null) = successMessage.url; //URL link which should be displayed
|
|
758
|
+
}
|
|
483
759
|
}
|
|
484
760
|
```
|
|
485
761
|
|
|
486
762
|
#### Swap Bitcoin lightning network -> Smart chain
|
|
763
|
+
|
|
764
|
+
Getting swap quote
|
|
765
|
+
|
|
487
766
|
```typescript
|
|
488
767
|
const _lnurl: string = "lnurl1dp68gurn8ghj7ampd3kx2ar0veekzar0wd5xjtnrdakj7tnhv4kxctttdehhwm30d3h82unvwqhkx6rfvdjx2ctvxyesuk0a27"; //Destination LNURL-pay or readable identifier
|
|
489
768
|
const _exactIn = true; //exactIn = true, so we specify the input amount
|
|
490
769
|
const _amount = 10000n; //Amount in BTC base units - sats
|
|
491
770
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
Tokens.
|
|
495
|
-
Tokens.SOLANA.SOL,
|
|
771
|
+
const swap = await swapper.swap(
|
|
772
|
+
Tokens.BITCOIN.BTCLN, //Swap from BTC-LN
|
|
773
|
+
Tokens.STARKNET.STRK, //Into specified destination token
|
|
496
774
|
_amount,
|
|
497
|
-
_exactIn,
|
|
498
|
-
_lnurl
|
|
775
|
+
_exactIn, //Whether we define an input or output amount
|
|
776
|
+
_lnurl, //Source LNURL for the swap
|
|
777
|
+
signer.getAddress() //Destination address
|
|
499
778
|
);
|
|
500
779
|
|
|
501
|
-
//Get the amount
|
|
502
|
-
const
|
|
503
|
-
const
|
|
504
|
-
const
|
|
780
|
+
//Get the amount required to pay and fee
|
|
781
|
+
const input: string = swap.getInputWithoutFee().toString(); //Input amount excluding fees
|
|
782
|
+
const fee: string = swap.getFee().amountInSrcToken.toString(); //Fees paid on the output
|
|
783
|
+
const inputWithFees: string = swap.getInput().toString(); //Total amount paid including fees
|
|
784
|
+
|
|
785
|
+
const output: string = swap.getOutput().toString(); //Total output amount
|
|
786
|
+
|
|
787
|
+
//Get swap expiration time
|
|
788
|
+
const expiry: number = swap.getQuoteExpiry(); //Expiration time of the swap quote in UNIX milliseconds, swap needs to be initiated before this time
|
|
789
|
+
|
|
790
|
+
//Get security deposit amount (Human readable amount of STRK that needs to be put down to rent the liquidity from swap intermediary), you will get this deposit back if the swap succeeds
|
|
791
|
+
const securityDeposit: string = swap.getSecurityDeposit().toString();
|
|
792
|
+
|
|
793
|
+
//Get pricing info
|
|
794
|
+
const swapPrice = swap.getPriceInfo().swapPrice; //Price of the current swap (excluding fees)
|
|
795
|
+
const marketPrice = swap.getPriceInfo().marketPrice; //Current market price
|
|
796
|
+
const difference = swap.getPriceInfo().difference; //Difference between the swap price & current market price
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
Wait for the payment to be received
|
|
505
800
|
|
|
801
|
+
```typescript
|
|
802
|
+
//Start listening to incoming lightning network payment
|
|
803
|
+
const success = await swap.waitForPayment();
|
|
804
|
+
if(!success) {
|
|
805
|
+
//Lightning network payment not received in time and quote expired!
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
Claim the funds on the destination smart chains, this settles the swap and lightning network payment
|
|
811
|
+
|
|
812
|
+
```typescript
|
|
506
813
|
try {
|
|
507
|
-
//Submit the withdraw request & wait for the payment to arrive
|
|
508
|
-
await swap.waitForPayment();
|
|
509
814
|
//Claim the swap funds - this will initiate 2 transactions
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
815
|
+
if(swap.canCommitAndClaimInOneShot()) {
|
|
816
|
+
//Some chains (e.g. Solana) support signing multiple transactions in one flow
|
|
817
|
+
await swap.commitAndClaim(solanaSigner);
|
|
818
|
+
} else {
|
|
819
|
+
//Other chains (e.g. Starknet) don't support signing multiple transaction in one flow, therefore you need to sign one-by-one
|
|
820
|
+
await swap.commit(starknetSigner);
|
|
821
|
+
await swap.claim(starknetSigner);
|
|
822
|
+
}
|
|
514
823
|
} catch(e) {
|
|
515
824
|
//Error occurred while waiting for payment
|
|
516
825
|
}
|
|
517
826
|
```
|
|
518
827
|
|
|
519
|
-
|
|
828
|
+
or [sign and send transactions manually](#manually-signing-smart-chain-transactions)
|
|
829
|
+
|
|
830
|
+
### Getting state of the swap
|
|
831
|
+
|
|
832
|
+
You can get the current state of the swap with:
|
|
833
|
+
|
|
834
|
+
```typescript
|
|
835
|
+
const state = swap.getState();
|
|
836
|
+
```
|
|
837
|
+
|
|
838
|
+
You can also set a listener to listen for swap state changes:
|
|
839
|
+
|
|
840
|
+
```typescript
|
|
841
|
+
swap.events.on("swapState", swap => {
|
|
842
|
+
const newState = swap.getState();
|
|
843
|
+
});
|
|
844
|
+
```
|
|
845
|
+
|
|
846
|
+
For the meaning of the states please refer to the "Swap state" section under each swap type.
|
|
847
|
+
|
|
848
|
+
### Swap size limits
|
|
849
|
+
|
|
850
|
+
Swap sizes are limited by the LPs you are connected to, they are advertised in BTC terms by LPs during handshake
|
|
851
|
+
|
|
852
|
+
```typescript
|
|
853
|
+
const swapLimits = swapper.getSwapLimits(srcToken, dstToken);
|
|
854
|
+
const inputMin = swapLimits.input.min;
|
|
855
|
+
const inputMax = swapLimits.input.max;
|
|
856
|
+
const outputMin = swapLimits.output.min;
|
|
857
|
+
const outputMax = swapLimits.output.max;
|
|
858
|
+
```
|
|
859
|
+
|
|
860
|
+
NOTE: swap limits denominated in BTC are retrieved from the LPs during initial handshake, however limits in other tokens are only returned when getting a quote fails due to amount being too low or too high. For example if you want to get swap limits for the BTC -> SOL swap, the input limits will be immediately available, while the output limits will only get populated once a quote request fails due to amount being too low or high.
|
|
861
|
+
|
|
862
|
+
```typescript
|
|
863
|
+
let swapLimits = swapper.getSwapLimits(Tokens.BITCOIN.BTC, Tokens.SOLANA.SOL);
|
|
864
|
+
let inputMin = swapLimits.input.min; //Immediately available
|
|
865
|
+
let inputMax = swapLimits.input.max; //Immediately available
|
|
866
|
+
let outputMin = swapLimits.output.min; //Not available from the get-go
|
|
867
|
+
let outputMax = swapLimits.output.max; //Not available from the get-go
|
|
868
|
+
|
|
869
|
+
//You can also listen to swap limit changes (optional)
|
|
870
|
+
swapper.on("swapLimitsChanged", () => {
|
|
871
|
+
//New limits available with swapper.getSwapLimits(srcToken, dstToken)
|
|
872
|
+
//Useful in e.g. a react application where you want to dynamically set min/max swappable amount
|
|
873
|
+
})
|
|
874
|
+
|
|
875
|
+
//Try to swap really small amount of SOL with exactOut swap
|
|
876
|
+
try {
|
|
877
|
+
const swap = await swapper.swap(
|
|
878
|
+
Tokens.BITCOIN.BTC, //Swap from BTC
|
|
879
|
+
Tokens.SOLANA.SOL, //Into specified destination token
|
|
880
|
+
1n, //1 lamport = 0.000000001 SOL
|
|
881
|
+
false, //Whether we define an input or output amount
|
|
882
|
+
undefined, //Source address for the swap, not used for swaps from BTC
|
|
883
|
+
solanaSigner.getAddress() //Destination address
|
|
884
|
+
);
|
|
885
|
+
} catch (e) {
|
|
886
|
+
//Fails with OutOfBoundsError
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
swapLimits = swapper.getSwapLimits(Tokens.BITCOIN.BTC, Tokens.SOLANA.SOL);
|
|
890
|
+
inputMin = swapLimits.input.min; //Immediately available
|
|
891
|
+
inputMax = swapLimits.input.max; //Immediately available
|
|
892
|
+
outputMin = swapLimits.output.min; //Now available due to failed quote
|
|
893
|
+
outputMax = swapLimits.output.max; //Now available due to failed quote
|
|
894
|
+
```
|
|
895
|
+
|
|
896
|
+
### Stored swaps
|
|
897
|
+
|
|
898
|
+
#### Get swap by ID
|
|
899
|
+
|
|
900
|
+
You can retrieve a swap by it's id, you can get an ID of the swap with
|
|
901
|
+
|
|
902
|
+
```typescript
|
|
903
|
+
const swapId = swap.getId();
|
|
904
|
+
```
|
|
905
|
+
|
|
906
|
+
And then later retrieve it from the storage
|
|
907
|
+
|
|
908
|
+
```typescript
|
|
909
|
+
const swap = await swapper.getSwapById(id);
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
#### Get refundable swaps
|
|
520
913
|
You can refund the swaps in one of two cases:
|
|
521
914
|
* In case intermediary is non-cooperative and goes offline, you can claim the funds from the swap contract back after some time.
|
|
522
915
|
* In case intermediary tried to pay but was unsuccessful, so he sent you signed message with which you can refund now without waiting.
|
|
523
916
|
|
|
524
917
|
This call can be checked on every startup and periodically every few minutes.
|
|
525
918
|
```typescript
|
|
526
|
-
//Get
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
}
|
|
919
|
+
//Get refundable swaps and refund them
|
|
920
|
+
const refundableSolanaSwaps = await swapper.getRefundableSwaps("SOLANA", solanaSigner.getAddress());
|
|
921
|
+
for(let swap of refundableSolanaSwaps) await swap.refund(solanaSigner);
|
|
922
|
+
const refundableStarknetSwaps = await swapper.getRefundableSwaps("STARKNET", starknetSigner.getAddress());
|
|
923
|
+
for(let swap of refundableStarknetSwaps) await swap.refund(starknetSigner);
|
|
532
924
|
```
|
|
533
925
|
|
|
534
|
-
|
|
926
|
+
#### Get claimable swaps
|
|
535
927
|
Returns swaps that are ready to be claimed by the client, this can happen if client closes the application when a swap is in-progress and the swap is concluded while the client is offline.
|
|
536
928
|
|
|
537
929
|
```typescript
|
|
538
930
|
//Get the swaps
|
|
539
|
-
const
|
|
931
|
+
const claimableSolanaSwaps = await solanaSwapper.getClaimableSwaps("SOLANA", solanaSigner.getAddress());
|
|
932
|
+
//Claim all the claimable swaps
|
|
933
|
+
for(let swap of claimableSolanaSwaps) {
|
|
934
|
+
if(swap.canCommit()) await swap.commit(solanaSigner); //This is for Bitcoin (lightning) -> Smart chain swaps, where commit & claim procedure might be needed
|
|
935
|
+
await swap.claim(solanaSigner);
|
|
936
|
+
}
|
|
937
|
+
//Get the swaps
|
|
938
|
+
const claimableStarknetSwaps = await solanaSwapper.getClaimableSwaps("STARKNET", starknetSigner.getAddress());
|
|
540
939
|
//Claim all the claimable swaps
|
|
541
|
-
for(let swap of
|
|
542
|
-
|
|
543
|
-
|
|
940
|
+
for(let swap of claimableStarknetSwaps) {
|
|
941
|
+
if(swap.canCommit()) await swap.commit(starknetSigner); //This is for Bitcoin (lightning) -> Smart chain swaps, where commit & claim procedure might be needed
|
|
942
|
+
await swap.claim(starknetSigner);
|
|
943
|
+
}
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
### Helpers
|
|
947
|
+
|
|
948
|
+
#### Getting wallet balances
|
|
949
|
+
|
|
950
|
+
The SDK also contains helper functions for getting the maximum spendable balance of wallets
|
|
951
|
+
|
|
952
|
+
```typescript
|
|
953
|
+
//Spendable balance of the starknet wallet address (discounting transaction fees)
|
|
954
|
+
const strkBalance = await swapper.Utils.getSpendableBalance(starknetSigner, Tokens.STARKNET.STRK);
|
|
955
|
+
//Spendable balance of the solana wallet address (discounting transaction fees)
|
|
956
|
+
const solBalance = await swapper.Utils.getSpendableBalance(solanaSigner, Tokens.SOLANA.SOL);
|
|
957
|
+
//Spendable balance of the bitcoin wallet - here we also need to specify the destination chain (as there are different swap protocols available with different on-chain footprints)
|
|
958
|
+
const {balance: btcBalance, feeRate: btcFeeRate} = await swapper.Utils.getBitcoinSpendableBalance(bitcoinWalletAddress, "SOLANA");
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
#### Unified address parser
|
|
962
|
+
|
|
963
|
+
A common way for parsing all address formats supported by the SDK, automatically recognizes:
|
|
964
|
+
- Bitcoin on-chain L1 address formats (p2pkh, p2wpkh, p2wsh, p2wsh, p2tr)
|
|
965
|
+
- [BIP-21](https://en.bitcoin.it/wiki/BIP_0021) bitcoin payment URI
|
|
966
|
+
- BOLT11 lightning network invoices
|
|
967
|
+
- [LUD-6](https://github.com/lnurl/luds/blob/luds/06.md) LNURL-pay links
|
|
968
|
+
- [LUD-3](https://github.com/lnurl/luds/blob/luds/03.md) LNURL-withdraw links
|
|
969
|
+
- [LUD-16](https://github.com/lnurl/luds/blob/luds/16.md) Lightning static internet identifiers
|
|
970
|
+
- Smart chain addresses (Solana, Starknet, etc.)
|
|
971
|
+
|
|
972
|
+
```typescript
|
|
973
|
+
const res = await swapper.Utils.parseAddress(address);
|
|
974
|
+
switch(res.type) {
|
|
975
|
+
case "BITCOIN":
|
|
976
|
+
//Bitcoin on-chain L1 address or BIP-21 URI scheme with amount
|
|
977
|
+
const btcAmount = res.amount;
|
|
978
|
+
break;
|
|
979
|
+
case "LIGHTNING":
|
|
980
|
+
//BOLT11 lightning network invoice with pre-set amount
|
|
981
|
+
const lnAmount = res.amount;
|
|
982
|
+
break;
|
|
983
|
+
case "LNURL":
|
|
984
|
+
//LNURL payment or withdrawal link
|
|
985
|
+
if(isLNURLWithdraw(res.lnurl)) {
|
|
986
|
+
//LNURL-withdraw allowing withdrawals over the lightning network
|
|
987
|
+
const lnurlWithdrawData: LNURLWithdraw = res.lnurl;
|
|
988
|
+
const minWithdrawable = res.min; //Minimum payment amount
|
|
989
|
+
const maxWithdrawable = res.max; //Maximum payment amount
|
|
990
|
+
const fixedAmount = res.amount; //If res.min===res.max, an fixed amount is returned instead
|
|
991
|
+
//Should show a UI allowing the user to choose an amount he wishes to withdraw
|
|
992
|
+
}
|
|
993
|
+
if(isLNURLPay(res.lnurl)) {
|
|
994
|
+
//LNURL-pay or static lightning internet identifier allowing repeated payments over the lightning network
|
|
995
|
+
const lnurlPayData: LNURLPay = res.lnurl;
|
|
996
|
+
const minPayable = res.min; //Minimum payment amount
|
|
997
|
+
const maxPayable = res.max; //Maximum payment amount
|
|
998
|
+
const fixedAmount = res.amount; //If res.min===res.max, an fixed amount is returned instead
|
|
999
|
+
const icon: (string | null) = res.lnurl.icon; //URL encoded icon that should be displayed on the UI
|
|
1000
|
+
const shortDescription: (string | null) = res.lnurl.shortDescription; //Short description of the payment
|
|
1001
|
+
const longDescription: (string | null) = res.lnurl.longDescription; //Long description of the payment
|
|
1002
|
+
const maxCommentLength: (number | 0) = res.lnurl.commentMaxLength; //Maximum allowed length of the payment message/comment (0 means no comment allowed)
|
|
1003
|
+
//Should show a UI displaying the icon, short description, long description, allowing the user to choose an amount he wishes to pay and possibly also a comment
|
|
1004
|
+
}
|
|
1005
|
+
break;
|
|
1006
|
+
default:
|
|
1007
|
+
//Addresses for smart chains
|
|
1008
|
+
break;
|
|
544
1009
|
}
|
|
545
1010
|
```
|
|
1011
|
+
|
|
1012
|
+
### Manually signing smart chain transactions
|
|
1013
|
+
|
|
1014
|
+
You can also sign the transactions on smart chain side (Solana, Starknet, etc.) of the SDK externally by a separate wallet. Each function which executes any transaction has its txs(action) counterpart, e.g.:
|
|
1015
|
+
- commit() -> txsCommit()
|
|
1016
|
+
- claim() -> txsClaim()
|
|
1017
|
+
- commitAndClaim -> txsCommitAndClaim()
|
|
1018
|
+
- refund() -> txsRefund()
|
|
1019
|
+
|
|
1020
|
+
After sending the transactions, you also need to make sure the SDK has enough time to receive an event notification of the transaction being executed, for this you have the waitTill(action) functions, e.g.:
|
|
1021
|
+
|
|
1022
|
+
- commit() -> waitTillCommited()
|
|
1023
|
+
- claim() -> waitTillClaimed()
|
|
1024
|
+
- commitAndClaim -> waitTillClaimed()
|
|
1025
|
+
- refund() -> waitTillRefunded()
|
|
1026
|
+
|
|
1027
|
+
```typescript
|
|
1028
|
+
//Example for Solana
|
|
1029
|
+
const txns = await swap.txsCommit(); //Also works with txsClaim, txsRefund, txCommitAndClaim
|
|
1030
|
+
txns.forEach(val => val.tx.sign(...val.signers));
|
|
1031
|
+
const signedTransactions = await solanaSigner.wallet.signAllTransactions(txns.map(val => val.tx));
|
|
1032
|
+
for(let tx of signedTransactions) {
|
|
1033
|
+
const res = await solanaRpc.sendRawTransaction(tx.serialize());
|
|
1034
|
+
await solanaRpc.confirmTransaction(res);
|
|
1035
|
+
}
|
|
1036
|
+
await swap.waitTillCommited(); //Or other relevant waitTillClaimed, waitTillRefunded
|
|
1037
|
+
|
|
1038
|
+
//Example for Starknet
|
|
1039
|
+
const txns = await swap.txsCommit(); //Also works with txsClaim, txsRefund, txCommitAndClaim
|
|
1040
|
+
for(let tx of txns) {
|
|
1041
|
+
if(tx.type==="INVOKE") await starknetSigner.account.execute(tx.tx, tx.details);
|
|
1042
|
+
if(tx.type==="DEPLOY_ACCOUNT") await starknetSigner.account.deployAccount(tx.tx, tx.details);
|
|
1043
|
+
}
|
|
1044
|
+
await swap.waitTillCommited(); //Or other relevant waitTillClaimed, waitTillRefunded
|
|
1045
|
+
```
|
|
1046
|
+
|
|
1047
|
+
### Additional swapper options
|
|
1048
|
+
|
|
1049
|
+
You can further customize the swapper instance with these options, you can:
|
|
1050
|
+
- adjust the maximum accepted pricing difference from the LPs
|
|
1051
|
+
- use custom mempool.space instance
|
|
1052
|
+
- use custom pricing API
|
|
1053
|
+
- use own LP node for swaps
|
|
1054
|
+
- adjust HTTP request timeouts
|
|
1055
|
+
- add parameters to be sent with each LP request
|
|
1056
|
+
|
|
1057
|
+
```typescript
|
|
1058
|
+
const swapper = Factory.newSwapper({
|
|
1059
|
+
...
|
|
1060
|
+
//Additional optional options
|
|
1061
|
+
pricingFeeDifferencePPM: 20000n, //Maximum allowed pricing difference for quote (between swap & market price) in ppm (parts per million) (20000 == 2%)
|
|
1062
|
+
mempoolApi: new MempoolApi("<url to custom mempool.space instance>"), //Set the SDK to use a custom mempool.space instance instead of the public one
|
|
1063
|
+
getPriceFn: (tickers: string[], abortSignal?: AbortSignal) => customPricingApi.getUsdPriceForTickers(tickers) //Overrides the default pricing API engine with a custom price getter
|
|
1064
|
+
|
|
1065
|
+
intermediaryUrl: "<url to custom LP node>",
|
|
1066
|
+
registryUrl: "<url to custom LP node registry>",
|
|
1067
|
+
|
|
1068
|
+
getRequestTimeout: 10000, //Timeout in milliseconds for GET requests
|
|
1069
|
+
postRequestTimeout: 10000, //Timeout in milliseconds for POST requests
|
|
1070
|
+
defaultAdditionalParameters: {lpData: "Pls give gud price"}, //Additional request data sent to LPs
|
|
1071
|
+
|
|
1072
|
+
defaultTrustedIntermediaryUrl: "<url to custom LP node>" //LP node/intermediary to use for trusted gas swaps
|
|
1073
|
+
});
|
|
1074
|
+
```
|