@lendasat/lendaswap-sdk-pure 0.2.21-1 → 0.2.21
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/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +4 -1
- package/dist/api/client.js.map +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js.map +1 -1
- package/dist/version.d.ts +4 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +6 -0
- package/dist/version.js.map +1 -0
- package/package.json +4 -1
- package/dist/price-feed.d.ts +0 -124
- package/dist/price-feed.d.ts.map +0 -1
- package/dist/price-feed.js +0 -178
- package/dist/price-feed.js.map +0 -1
- package/dist/src/api/client.d.ts +0 -31
- package/dist/src/api/client.d.ts.map +0 -1
- package/dist/src/api/client.js +0 -12
- package/dist/src/api/client.js.map +0 -1
- package/dist/src/arkade.d.ts +0 -36
- package/dist/src/arkade.d.ts.map +0 -1
- package/dist/src/arkade.js +0 -68
- package/dist/src/arkade.js.map +0 -1
- package/dist/src/client.d.ts +0 -760
- package/dist/src/client.d.ts.map +0 -1
- package/dist/src/client.js +0 -2169
- package/dist/src/client.js.map +0 -1
- package/dist/src/create/arkade.d.ts +0 -34
- package/dist/src/create/arkade.d.ts.map +0 -1
- package/dist/src/create/arkade.js +0 -76
- package/dist/src/create/arkade.js.map +0 -1
- package/dist/src/create/bitcoin-to-arkade.d.ts +0 -36
- package/dist/src/create/bitcoin-to-arkade.d.ts.map +0 -1
- package/dist/src/create/bitcoin-to-arkade.js +0 -69
- package/dist/src/create/bitcoin-to-arkade.js.map +0 -1
- package/dist/src/create/bitcoin.d.ts +0 -31
- package/dist/src/create/bitcoin.d.ts.map +0 -1
- package/dist/src/create/bitcoin.js +0 -67
- package/dist/src/create/bitcoin.js.map +0 -1
- package/dist/src/create/evm-to-arkade.d.ts +0 -34
- package/dist/src/create/evm-to-arkade.d.ts.map +0 -1
- package/dist/src/create/evm-to-arkade.js +0 -69
- package/dist/src/create/evm-to-arkade.js.map +0 -1
- package/dist/src/create/evm-to-bitcoin.d.ts +0 -35
- package/dist/src/create/evm-to-bitcoin.d.ts.map +0 -1
- package/dist/src/create/evm-to-bitcoin.js +0 -71
- package/dist/src/create/evm-to-bitcoin.js.map +0 -1
- package/dist/src/create/evm-to-lightning.d.ts +0 -34
- package/dist/src/create/evm-to-lightning.d.ts.map +0 -1
- package/dist/src/create/evm-to-lightning.js +0 -66
- package/dist/src/create/evm-to-lightning.js.map +0 -1
- package/dist/src/create/index.d.ts +0 -19
- package/dist/src/create/index.d.ts.map +0 -1
- package/dist/src/create/index.js +0 -18
- package/dist/src/create/index.js.map +0 -1
- package/dist/src/create/lightning.d.ts +0 -31
- package/dist/src/create/lightning.d.ts.map +0 -1
- package/dist/src/create/lightning.js +0 -72
- package/dist/src/create/lightning.js.map +0 -1
- package/dist/src/create/types.d.ts +0 -247
- package/dist/src/create/types.d.ts.map +0 -1
- package/dist/src/create/types.js +0 -5
- package/dist/src/create/types.js.map +0 -1
- package/dist/src/delegate.d.ts +0 -62
- package/dist/src/delegate.d.ts.map +0 -1
- package/dist/src/delegate.js +0 -284
- package/dist/src/delegate.js.map +0 -1
- package/dist/src/esplora.d.ts +0 -41
- package/dist/src/esplora.d.ts.map +0 -1
- package/dist/src/esplora.js +0 -47
- package/dist/src/esplora.js.map +0 -1
- package/dist/src/evm/coordinator.d.ts +0 -247
- package/dist/src/evm/coordinator.d.ts.map +0 -1
- package/dist/src/evm/coordinator.js +0 -414
- package/dist/src/evm/coordinator.js.map +0 -1
- package/dist/src/evm/htlc.d.ts +0 -238
- package/dist/src/evm/htlc.d.ts.map +0 -1
- package/dist/src/evm/htlc.js +0 -278
- package/dist/src/evm/htlc.js.map +0 -1
- package/dist/src/evm/index.d.ts +0 -9
- package/dist/src/evm/index.d.ts.map +0 -1
- package/dist/src/evm/index.js +0 -9
- package/dist/src/evm/index.js.map +0 -1
- package/dist/src/evm/signing.d.ts +0 -30
- package/dist/src/evm/signing.d.ts.map +0 -1
- package/dist/src/evm/signing.js +0 -91
- package/dist/src/evm/signing.js.map +0 -1
- package/dist/src/generated/api.d.ts +0 -2736
- package/dist/src/generated/api.d.ts.map +0 -1
- package/dist/src/generated/api.js +0 -6
- package/dist/src/generated/api.js.map +0 -1
- package/dist/src/index.d.ts +0 -18
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/index.js +0 -24
- package/dist/src/index.js.map +0 -1
- package/dist/src/node.d.ts +0 -19
- package/dist/src/node.d.ts.map +0 -1
- package/dist/src/node.js +0 -19
- package/dist/src/node.js.map +0 -1
- package/dist/src/price-calculations.d.ts +0 -109
- package/dist/src/price-calculations.d.ts.map +0 -1
- package/dist/src/price-calculations.js +0 -135
- package/dist/src/price-calculations.js.map +0 -1
- package/dist/src/redeem/arkade.d.ts +0 -65
- package/dist/src/redeem/arkade.d.ts.map +0 -1
- package/dist/src/redeem/arkade.js +0 -217
- package/dist/src/redeem/arkade.js.map +0 -1
- package/dist/src/redeem/ethereum.d.ts +0 -52
- package/dist/src/redeem/ethereum.d.ts.map +0 -1
- package/dist/src/redeem/ethereum.js +0 -206
- package/dist/src/redeem/ethereum.js.map +0 -1
- package/dist/src/redeem/gasless.d.ts +0 -41
- package/dist/src/redeem/gasless.d.ts.map +0 -1
- package/dist/src/redeem/gasless.js +0 -71
- package/dist/src/redeem/gasless.js.map +0 -1
- package/dist/src/redeem/index.d.ts +0 -49
- package/dist/src/redeem/index.d.ts.map +0 -1
- package/dist/src/redeem/index.js +0 -189
- package/dist/src/redeem/index.js.map +0 -1
- package/dist/src/redeem/types.d.ts +0 -126
- package/dist/src/redeem/types.d.ts.map +0 -1
- package/dist/src/redeem/types.js +0 -36
- package/dist/src/redeem/types.js.map +0 -1
- package/dist/src/refund/arkade.d.ts +0 -62
- package/dist/src/refund/arkade.d.ts.map +0 -1
- package/dist/src/refund/arkade.js +0 -212
- package/dist/src/refund/arkade.js.map +0 -1
- package/dist/src/refund/index.d.ts +0 -10
- package/dist/src/refund/index.d.ts.map +0 -1
- package/dist/src/refund/index.js +0 -10
- package/dist/src/refund/index.js.map +0 -1
- package/dist/src/refund/onchain.d.ts +0 -137
- package/dist/src/refund/onchain.d.ts.map +0 -1
- package/dist/src/refund/onchain.js +0 -366
- package/dist/src/refund/onchain.js.map +0 -1
- package/dist/src/signer/index.d.ts +0 -106
- package/dist/src/signer/index.d.ts.map +0 -1
- package/dist/src/signer/index.js +0 -179
- package/dist/src/signer/index.js.map +0 -1
- package/dist/src/storage/idb.d.ts +0 -70
- package/dist/src/storage/idb.d.ts.map +0 -1
- package/dist/src/storage/idb.js +0 -236
- package/dist/src/storage/idb.js.map +0 -1
- package/dist/src/storage/index.d.ts +0 -152
- package/dist/src/storage/index.d.ts.map +0 -1
- package/dist/src/storage/index.js +0 -98
- package/dist/src/storage/index.js.map +0 -1
- package/dist/src/storage/sqlite.d.ts +0 -95
- package/dist/src/storage/sqlite.d.ts.map +0 -1
- package/dist/src/storage/sqlite.js +0 -206
- package/dist/src/storage/sqlite.js.map +0 -1
- package/dist/src/storage/types.d.ts +0 -57
- package/dist/src/storage/types.d.ts.map +0 -1
- package/dist/src/storage/types.js +0 -9
- package/dist/src/storage/types.js.map +0 -1
- package/dist/src/tokens.d.ts +0 -29
- package/dist/src/tokens.d.ts.map +0 -1
- package/dist/src/tokens.js +0 -89
- package/dist/src/tokens.js.map +0 -1
- package/dist/src/usd-price.d.ts +0 -34
- package/dist/src/usd-price.d.ts.map +0 -1
- package/dist/src/usd-price.js +0 -83
- package/dist/src/usd-price.js.map +0 -1
- package/dist/tests/api-client.test.d.ts +0 -2
- package/dist/tests/api-client.test.d.ts.map +0 -1
- package/dist/tests/api-client.test.js +0 -86
- package/dist/tests/api-client.test.js.map +0 -1
- package/dist/tests/client.test.d.ts +0 -2
- package/dist/tests/client.test.d.ts.map +0 -1
- package/dist/tests/client.test.js +0 -150
- package/dist/tests/client.test.js.map +0 -1
- package/dist/tests/index.test.d.ts +0 -2
- package/dist/tests/index.test.d.ts.map +0 -1
- package/dist/tests/index.test.js +0 -8
- package/dist/tests/index.test.js.map +0 -1
- package/dist/tests/onchain-refund.test.d.ts +0 -2
- package/dist/tests/onchain-refund.test.d.ts.map +0 -1
- package/dist/tests/onchain-refund.test.js +0 -279
- package/dist/tests/onchain-refund.test.js.map +0 -1
- package/dist/tests/signer.test.d.ts +0 -2
- package/dist/tests/signer.test.d.ts.map +0 -1
- package/dist/tests/signer.test.js +0 -92
- package/dist/tests/signer.test.js.map +0 -1
- package/dist/tests/sqlite-storage.test.d.ts +0 -2
- package/dist/tests/sqlite-storage.test.d.ts.map +0 -1
- package/dist/tests/sqlite-storage.test.js +0 -160
- package/dist/tests/sqlite-storage.test.js.map +0 -1
- package/dist/tests/storage.test.d.ts +0 -2
- package/dist/tests/storage.test.d.ts.map +0 -1
- package/dist/tests/storage.test.js +0 -184
- package/dist/tests/storage.test.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/dist/usdt0-bridge/bridge.d.ts +0 -82
- package/dist/usdt0-bridge/bridge.d.ts.map +0 -1
- package/dist/usdt0-bridge/bridge.js +0 -56
- package/dist/usdt0-bridge/bridge.js.map +0 -1
- package/dist/usdt0-bridge/chains.d.ts +0 -41
- package/dist/usdt0-bridge/chains.d.ts.map +0 -1
- package/dist/usdt0-bridge/chains.js +0 -117
- package/dist/usdt0-bridge/chains.js.map +0 -1
- package/dist/usdt0-bridge/layerzero-tracker.d.ts +0 -34
- package/dist/usdt0-bridge/layerzero-tracker.d.ts.map +0 -1
- package/dist/usdt0-bridge/layerzero-tracker.js +0 -86
- package/dist/usdt0-bridge/layerzero-tracker.js.map +0 -1
- package/dist/usdt0-bridge/oft-abi.d.ts +0 -145
- package/dist/usdt0-bridge/oft-abi.d.ts.map +0 -1
- package/dist/usdt0-bridge/oft-abi.js +0 -117
- package/dist/usdt0-bridge/oft-abi.js.map +0 -1
package/dist/src/client.js
DELETED
|
@@ -1,2169 +0,0 @@
|
|
|
1
|
-
import { createApiClient, } from "./api/client.js";
|
|
2
|
-
import { getVhtlcAmounts } from "./arkade.js";
|
|
3
|
-
import { createArkadeToEvmSwapGeneric, createBitcoinToArkadeSwap, createBitcoinToEvmSwap, createEvmToArkadeSwapGeneric, createEvmToBitcoinSwap, createEvmToLightningSwapGeneric, createLightningToEvmSwapGeneric, } from "./create";
|
|
4
|
-
import { delegateClaim, delegateRefund } from "./delegate.js";
|
|
5
|
-
import { broadcastTransaction, findOutputByAddress } from "./esplora.js";
|
|
6
|
-
import { encodeApproveCallData, encodeHtlcErc20RefundCallData } from "./evm";
|
|
7
|
-
import { buildArkadeClaim, claimViaGasless as gaslessClaim, claim as redeemClaim, } from "./redeem/index.js";
|
|
8
|
-
import { buildArkadeRefund, buildOnchainClaimTransaction, buildOnchainRefundTransaction, verifyHtlcAddress, } from "./refund";
|
|
9
|
-
import { bytesToHex, hexToBytes, Signer, } from "./signer/index.js";
|
|
10
|
-
import { SWAP_STORAGE_VERSION, } from "./storage";
|
|
11
|
-
import { isArkade, isBtcOnchain, isEvmToken, isLightning } from "./tokens.js";
|
|
12
|
-
// Re-export coordinator utilities for Arkade-to-EVM redeemAndExecute flow
|
|
13
|
-
export { buildExecuteAndCreateCalls, buildRedeemCalls, buildRedeemDigest, encodeExecuteAndCreate, encodeRedeemAndExecute, encodeRefundAndExecute, encodeRefundTo, } from "./evm/index.js";
|
|
14
|
-
const DEFAULT_BASE_URL = "https://apilendaswap.lendasat.com/";
|
|
15
|
-
/** Default Esplora URLs by network */
|
|
16
|
-
const DEFAULT_ESPLORA_URLS = {
|
|
17
|
-
mainnet: "https://mempool.space/api",
|
|
18
|
-
signet: "https://mutinynet.com/api",
|
|
19
|
-
regtest: "http://localhost:3000",
|
|
20
|
-
};
|
|
21
|
-
/**
|
|
22
|
-
* Builder for creating a Lendaswap client with a fluent API.
|
|
23
|
-
*
|
|
24
|
-
* The `build()` method is async and returns a fully initialized client.
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
* ```ts
|
|
28
|
-
* // Create client with new wallet (generates mnemonic)
|
|
29
|
-
* const client = await Client.builder()
|
|
30
|
-
* .withSignerStorage(new IdbWalletStorage())
|
|
31
|
-
* .build();
|
|
32
|
-
*
|
|
33
|
-
* // Create client with existing mnemonic
|
|
34
|
-
* const client = await Client.builder()
|
|
35
|
-
* .withSignerStorage(new IdbWalletStorage())
|
|
36
|
-
* .withMnemonic("abandon abandon abandon ...")
|
|
37
|
-
* .build();
|
|
38
|
-
*
|
|
39
|
-
* // Create client without storage (stateless, generates new mnemonic)
|
|
40
|
-
* const client = await Client.builder().build();
|
|
41
|
-
* ```
|
|
42
|
-
*/
|
|
43
|
-
export class ClientBuilder {
|
|
44
|
-
#baseUrl = DEFAULT_BASE_URL;
|
|
45
|
-
#apiKey;
|
|
46
|
-
#esploraUrl;
|
|
47
|
-
#arkadeServerUrl;
|
|
48
|
-
#signerStorage;
|
|
49
|
-
#swapStorage;
|
|
50
|
-
#mnemonic;
|
|
51
|
-
/**
|
|
52
|
-
* Sets the base URL for the API.
|
|
53
|
-
* @param baseUrl - The base URL of the Lendaswap API.
|
|
54
|
-
* @returns The builder instance for chaining.
|
|
55
|
-
*/
|
|
56
|
-
withBaseUrl(baseUrl) {
|
|
57
|
-
this.#baseUrl = baseUrl;
|
|
58
|
-
return this;
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Sets the API key for authenticated requests.
|
|
62
|
-
* @param apiKey - The API key to use for authentication.
|
|
63
|
-
* @returns The builder instance for chaining.
|
|
64
|
-
*/
|
|
65
|
-
withApiKey(apiKey) {
|
|
66
|
-
this.#apiKey = apiKey;
|
|
67
|
-
return this;
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Sets the Esplora API URL for broadcasting Bitcoin transactions.
|
|
71
|
-
*
|
|
72
|
-
* If not set, defaults will be used based on the network:
|
|
73
|
-
* - mainnet: https://mempool.space/api
|
|
74
|
-
* - testnet: https://mempool.space/testnet/api
|
|
75
|
-
* - signet: https://mempool.space/signet/api
|
|
76
|
-
*
|
|
77
|
-
* @param esploraUrl - The Esplora API base URL.
|
|
78
|
-
* @returns The builder instance for chaining.
|
|
79
|
-
*/
|
|
80
|
-
withEsploraUrl(esploraUrl) {
|
|
81
|
-
this.#esploraUrl = esploraUrl;
|
|
82
|
-
return this;
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Sets the Arkade server URL for VHTLC operations (claim, refund, amounts).
|
|
86
|
-
*
|
|
87
|
-
* If not set, defaults are used based on the network:
|
|
88
|
-
* - bitcoin: https://arkade.computer
|
|
89
|
-
* - signet: wa
|
|
90
|
-
*
|
|
91
|
-
* @param arkadeServerUrl - The Arkade server base URL.
|
|
92
|
-
* @returns The builder instance for chaining.
|
|
93
|
-
*/
|
|
94
|
-
withArkadeServerUrl(arkadeServerUrl) {
|
|
95
|
-
this.#arkadeServerUrl = arkadeServerUrl;
|
|
96
|
-
return this;
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Sets the storage backend for signer data (mnemonic and key index).
|
|
100
|
-
* @param storage - The storage implementation to use.
|
|
101
|
-
* @returns The builder instance for chaining.
|
|
102
|
-
*/
|
|
103
|
-
withSignerStorage(storage) {
|
|
104
|
-
this.#signerStorage = storage;
|
|
105
|
-
return this;
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Sets the storage backend for swap data.
|
|
109
|
-
*
|
|
110
|
-
* When configured, swaps will be automatically persisted after creation.
|
|
111
|
-
*
|
|
112
|
-
* @param storage - The swap storage implementation to use.
|
|
113
|
-
* @returns The builder instance for chaining.
|
|
114
|
-
*/
|
|
115
|
-
withSwapStorage(storage) {
|
|
116
|
-
this.#swapStorage = storage;
|
|
117
|
-
return this;
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Sets the mnemonic phrase to use for the signer.
|
|
121
|
-
*
|
|
122
|
-
* If provided, this mnemonic will be used instead of loading from storage
|
|
123
|
-
* or generating a new one. The mnemonic will be persisted to storage if
|
|
124
|
-
* storage is configured.
|
|
125
|
-
*
|
|
126
|
-
* @param mnemonic - The BIP39 mnemonic phrase (12, 15, 18, 21, or 24 words).
|
|
127
|
-
* @returns The builder instance for chaining.
|
|
128
|
-
*/
|
|
129
|
-
withMnemonic(mnemonic) {
|
|
130
|
-
this.#mnemonic = mnemonic;
|
|
131
|
-
return this;
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Builds and returns a fully initialized Client instance.
|
|
135
|
-
*
|
|
136
|
-
* Initialization order:
|
|
137
|
-
* 1. If `withMnemonic()` was called, use that mnemonic
|
|
138
|
-
* 2. Else if storage is configured and contains a mnemonic, load it
|
|
139
|
-
* 3. Else generate a new mnemonic
|
|
140
|
-
*
|
|
141
|
-
* The mnemonic is persisted to storage if storage is configured.
|
|
142
|
-
*
|
|
143
|
-
* @returns A promise that resolves to a fully initialized Client.
|
|
144
|
-
* @throws Error if the provided mnemonic is invalid.
|
|
145
|
-
*/
|
|
146
|
-
async build() {
|
|
147
|
-
let signer;
|
|
148
|
-
if (this.#mnemonic) {
|
|
149
|
-
// Use provided mnemonic
|
|
150
|
-
signer = Signer.fromMnemonic(this.#mnemonic);
|
|
151
|
-
if (this.#signerStorage) {
|
|
152
|
-
await this.#signerStorage.setMnemonic(signer.mnemonic);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
else if (this.#signerStorage) {
|
|
156
|
-
// Try to load from storage
|
|
157
|
-
const storedMnemonic = await this.#signerStorage.getMnemonic();
|
|
158
|
-
if (storedMnemonic) {
|
|
159
|
-
signer = Signer.fromMnemonic(storedMnemonic);
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
// Generate new and persist
|
|
163
|
-
signer = Signer.generate();
|
|
164
|
-
await this.#signerStorage.setMnemonic(signer.mnemonic);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
// No storage, generate new (stateless mode)
|
|
169
|
-
signer = Signer.generate();
|
|
170
|
-
}
|
|
171
|
-
return new Client({
|
|
172
|
-
baseUrl: this.#baseUrl,
|
|
173
|
-
apiKey: this.#apiKey,
|
|
174
|
-
esploraUrl: this.#esploraUrl,
|
|
175
|
-
arkadeServerUrl: this.#arkadeServerUrl,
|
|
176
|
-
}, signer, this.#signerStorage, this.#swapStorage);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Main client for interacting with the Lendaswap API.
|
|
181
|
-
*
|
|
182
|
-
* The client manages:
|
|
183
|
-
* - API communication
|
|
184
|
-
* - Signer (HD wallet) for key derivation
|
|
185
|
-
* - Storage for persisting mnemonic and key index
|
|
186
|
-
*
|
|
187
|
-
* Use `Client.builder()` to create a new instance.
|
|
188
|
-
*
|
|
189
|
-
* @example
|
|
190
|
-
* ```ts
|
|
191
|
-
* const client = await Client.builder()
|
|
192
|
-
* .withSignerStorage(new IdbWalletStorage())
|
|
193
|
-
* .withApiKey("your-api-key")
|
|
194
|
-
* .build();
|
|
195
|
-
*
|
|
196
|
-
* // Get mnemonic (for backup)
|
|
197
|
-
* const mnemonic = client.getMnemonic();
|
|
198
|
-
*
|
|
199
|
-
* // Derive swap parameters
|
|
200
|
-
* const params = await client.deriveSwapParams();
|
|
201
|
-
* ```
|
|
202
|
-
*/
|
|
203
|
-
export class Client {
|
|
204
|
-
#apiClient;
|
|
205
|
-
#config;
|
|
206
|
-
#signer;
|
|
207
|
-
#signerStorage;
|
|
208
|
-
#swapStorage;
|
|
209
|
-
/**
|
|
210
|
-
* Creates a new Client instance.
|
|
211
|
-
*
|
|
212
|
-
* Use `Client.builder()` instead of calling this constructor directly.
|
|
213
|
-
*
|
|
214
|
-
* @internal
|
|
215
|
-
*/
|
|
216
|
-
constructor(config, signer, signerStorage, swapStorage) {
|
|
217
|
-
this.#config = config;
|
|
218
|
-
this.#apiClient = createApiClient({
|
|
219
|
-
baseUrl: config.baseUrl,
|
|
220
|
-
apiKey: config.apiKey,
|
|
221
|
-
});
|
|
222
|
-
this.#signer = signer;
|
|
223
|
-
this.#signerStorage = signerStorage;
|
|
224
|
-
this.#swapStorage = swapStorage;
|
|
225
|
-
}
|
|
226
|
-
/**
|
|
227
|
-
* Creates a new ClientBuilder for fluent configuration.
|
|
228
|
-
* @returns A new ClientBuilder instance.
|
|
229
|
-
*/
|
|
230
|
-
static builder() {
|
|
231
|
-
return new ClientBuilder();
|
|
232
|
-
}
|
|
233
|
-
/** The underlying typed API client for direct API access. */
|
|
234
|
-
get api() {
|
|
235
|
-
return this.#apiClient;
|
|
236
|
-
}
|
|
237
|
-
/** The base URL of the API. */
|
|
238
|
-
get baseUrl() {
|
|
239
|
-
return this.#config.baseUrl;
|
|
240
|
-
}
|
|
241
|
-
/** The swap storage, if configured. */
|
|
242
|
-
get swapStorage() {
|
|
243
|
-
return this.#swapStorage;
|
|
244
|
-
}
|
|
245
|
-
// =========================================================================
|
|
246
|
-
// Signer Methods
|
|
247
|
-
// =========================================================================
|
|
248
|
-
/**
|
|
249
|
-
* Gets the mnemonic phrase.
|
|
250
|
-
*
|
|
251
|
-
* Store this securely - it's the only way to recover the wallet.
|
|
252
|
-
*
|
|
253
|
-
* @returns The BIP39 mnemonic phrase.
|
|
254
|
-
*/
|
|
255
|
-
getMnemonic() {
|
|
256
|
-
return this.#signer.mnemonic;
|
|
257
|
-
}
|
|
258
|
-
/**
|
|
259
|
-
* Loads a mnemonic phrase, replacing the current signer.
|
|
260
|
-
*
|
|
261
|
-
* The new mnemonic is persisted to storage if storage is configured.
|
|
262
|
-
*
|
|
263
|
-
* @param mnemonic - The BIP39 mnemonic phrase to load.
|
|
264
|
-
* @throws Error if the mnemonic is invalid.
|
|
265
|
-
*/
|
|
266
|
-
async loadMnemonic(mnemonic) {
|
|
267
|
-
this.#signer = Signer.fromMnemonic(mnemonic);
|
|
268
|
-
if (this.#signerStorage) {
|
|
269
|
-
await this.#signerStorage.setMnemonic(mnemonic);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Gets the user ID extended public key for wallet recovery.
|
|
274
|
-
*
|
|
275
|
-
* This can be shared with the server for recovering swap history.
|
|
276
|
-
*
|
|
277
|
-
* @returns The hex-encoded user ID xpub.
|
|
278
|
-
*/
|
|
279
|
-
getUserIdXpub() {
|
|
280
|
-
return this.#signer.getUserIdXpubString();
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* Derives swap parameters at the next available index.
|
|
284
|
-
*
|
|
285
|
-
* Automatically increments the key index in storage (if configured).
|
|
286
|
-
*
|
|
287
|
-
* @returns The derived swap parameters.
|
|
288
|
-
*/
|
|
289
|
-
async deriveSwapParams() {
|
|
290
|
-
let index = 0;
|
|
291
|
-
if (this.#signerStorage) {
|
|
292
|
-
index = await this.#signerStorage.incrementKeyIndex();
|
|
293
|
-
}
|
|
294
|
-
return this.#signer.deriveSwapParams(index);
|
|
295
|
-
}
|
|
296
|
-
/**
|
|
297
|
-
* Derives swap parameters at a specific index.
|
|
298
|
-
*
|
|
299
|
-
* Does not modify the stored key index. Useful for recovery scenarios.
|
|
300
|
-
*
|
|
301
|
-
* @param index - The key index to derive.
|
|
302
|
-
* @returns The derived swap parameters.
|
|
303
|
-
*/
|
|
304
|
-
deriveSwapParamsAtIndex(index) {
|
|
305
|
-
return this.#signer.deriveSwapParams(index);
|
|
306
|
-
}
|
|
307
|
-
/**
|
|
308
|
-
* Gets the current key index from storage.
|
|
309
|
-
* @returns The current key index, or 0 if no storage is configured.
|
|
310
|
-
*/
|
|
311
|
-
async getKeyIndex() {
|
|
312
|
-
if (this.#signerStorage) {
|
|
313
|
-
return this.#signerStorage.getKeyIndex();
|
|
314
|
-
}
|
|
315
|
-
return 0;
|
|
316
|
-
}
|
|
317
|
-
/**
|
|
318
|
-
* Sets the key index in storage.
|
|
319
|
-
*
|
|
320
|
-
* Useful for recovery scenarios where you need to set the index
|
|
321
|
-
* to a specific value.
|
|
322
|
-
*
|
|
323
|
-
* @param index - The new key index.
|
|
324
|
-
* @throws Error if no storage is configured.
|
|
325
|
-
*/
|
|
326
|
-
async setKeyIndex(index) {
|
|
327
|
-
if (!this.#signerStorage) {
|
|
328
|
-
throw new Error("No signer storage configured");
|
|
329
|
-
}
|
|
330
|
-
await this.#signerStorage.setKeyIndex(index);
|
|
331
|
-
}
|
|
332
|
-
// =========================================================================
|
|
333
|
-
// Health & Info
|
|
334
|
-
// =========================================================================
|
|
335
|
-
/**
|
|
336
|
-
* Checks the health status of the API.
|
|
337
|
-
* @returns A promise that resolves to "ok" if the API is healthy.
|
|
338
|
-
* @throws Error if the health check fails.
|
|
339
|
-
*/
|
|
340
|
-
async healthCheck() {
|
|
341
|
-
const { data, error } = await this.#apiClient.GET("/health");
|
|
342
|
-
if (error) {
|
|
343
|
-
throw new Error(`Health check failed: ${JSON.stringify(error)}`);
|
|
344
|
-
}
|
|
345
|
-
return data ?? "ok";
|
|
346
|
-
}
|
|
347
|
-
/**
|
|
348
|
-
* Gets the version information of the API.
|
|
349
|
-
* @returns A promise that resolves to the version info containing tag and commit hash.
|
|
350
|
-
* @throws Error if the request fails.
|
|
351
|
-
*/
|
|
352
|
-
async getVersion() {
|
|
353
|
-
const { data, error } = await this.#apiClient.GET("/version");
|
|
354
|
-
if (error) {
|
|
355
|
-
throw new Error(`Failed to get version: ${JSON.stringify(error)}`);
|
|
356
|
-
}
|
|
357
|
-
if (!data) {
|
|
358
|
-
throw new Error("No version data returned");
|
|
359
|
-
}
|
|
360
|
-
return data;
|
|
361
|
-
}
|
|
362
|
-
/**
|
|
363
|
-
* Gets the current Median Time Past (MTP) and tip block height.
|
|
364
|
-
* @returns A promise that resolves to the MTP timestamp and tip height.
|
|
365
|
-
* @throws Error if the request fails or MTP is not yet available.
|
|
366
|
-
*/
|
|
367
|
-
async getMtp() {
|
|
368
|
-
const { data, error } = await this.#apiClient.GET("/mtp");
|
|
369
|
-
if (error) {
|
|
370
|
-
throw new Error(`Failed to get MTP: ${JSON.stringify(error)}`);
|
|
371
|
-
}
|
|
372
|
-
if (!data) {
|
|
373
|
-
throw new Error("MTP not available yet");
|
|
374
|
-
}
|
|
375
|
-
return data;
|
|
376
|
-
}
|
|
377
|
-
// =========================================================================
|
|
378
|
-
// Tokens & Asset Pairs
|
|
379
|
-
// =========================================================================
|
|
380
|
-
/**
|
|
381
|
-
* Gets the list of supported tokens.
|
|
382
|
-
* @returns A promise that resolves to an array of token information.
|
|
383
|
-
* @throws Error if the request fails.
|
|
384
|
-
*/
|
|
385
|
-
async getTokens() {
|
|
386
|
-
const { data, error } = await this.#apiClient.GET("/tokens");
|
|
387
|
-
if (error || !data) {
|
|
388
|
-
throw new Error(`Failed to get tokens: ${JSON.stringify(error)}`);
|
|
389
|
-
}
|
|
390
|
-
return data;
|
|
391
|
-
}
|
|
392
|
-
// =========================================================================
|
|
393
|
-
// Quotes
|
|
394
|
-
// =========================================================================
|
|
395
|
-
/**
|
|
396
|
-
* Gets a quote for swapping between two tokens.
|
|
397
|
-
* @param params - Quote parameters.
|
|
398
|
-
* @param params.sourceChain - Source blockchain (e.g., "Arkade", "Polygon").
|
|
399
|
-
* @param params.sourceToken - Source token: contract address for EVM tokens, or "btc" for BTC.
|
|
400
|
-
* @param params.targetChain - Target blockchain (e.g., "Polygon", "Lightning").
|
|
401
|
-
* @param params.targetToken - Target token: contract address for EVM tokens, or "btc" for BTC.
|
|
402
|
-
* @param params.sourceAmount - Amount in smallest unit of source token (mutually exclusive with targetAmount).
|
|
403
|
-
* @param params.targetAmount - Amount in smallest unit of target token (mutually exclusive with sourceAmount).
|
|
404
|
-
* @returns A promise that resolves to the quote response with pricing details.
|
|
405
|
-
* @throws Error if the request fails.
|
|
406
|
-
*/
|
|
407
|
-
async getQuote(params) {
|
|
408
|
-
const { data, error } = await this.#apiClient.GET("/quote", {
|
|
409
|
-
params: {
|
|
410
|
-
query: {
|
|
411
|
-
source_chain: params.sourceChain,
|
|
412
|
-
source_token: params.sourceToken,
|
|
413
|
-
target_chain: params.targetChain,
|
|
414
|
-
target_token: params.targetToken,
|
|
415
|
-
source_amount: params.sourceAmount,
|
|
416
|
-
target_amount: params.targetAmount,
|
|
417
|
-
},
|
|
418
|
-
},
|
|
419
|
-
});
|
|
420
|
-
if (error) {
|
|
421
|
-
throw new Error(`Failed to get quote: ${JSON.stringify(error)}`);
|
|
422
|
-
}
|
|
423
|
-
if (!data) {
|
|
424
|
-
throw new Error("No quote data returned");
|
|
425
|
-
}
|
|
426
|
-
return data;
|
|
427
|
-
}
|
|
428
|
-
// =========================================================================
|
|
429
|
-
// Swap Status
|
|
430
|
-
// =========================================================================
|
|
431
|
-
/**
|
|
432
|
-
* Gets the status and details of a swap by its ID.
|
|
433
|
-
* @param id - The UUID of the swap.
|
|
434
|
-
* @param options - Optional settings.
|
|
435
|
-
* @param options.updateStorage - If true, updates the swap in storage after fetching.
|
|
436
|
-
* @returns A promise that resolves to the swap details.
|
|
437
|
-
* @throws Error if the request fails or swap is not found.
|
|
438
|
-
*/
|
|
439
|
-
async getSwap(id, options) {
|
|
440
|
-
const { data, error } = await this.#apiClient.GET("/swap/{id}", {
|
|
441
|
-
params: { path: { id } },
|
|
442
|
-
});
|
|
443
|
-
if (error) {
|
|
444
|
-
throw new Error(`Failed to get swap: ${JSON.stringify(error)}`);
|
|
445
|
-
}
|
|
446
|
-
if (!data) {
|
|
447
|
-
throw new Error("No swap data returned");
|
|
448
|
-
}
|
|
449
|
-
if (options?.updateStorage && this.#swapStorage) {
|
|
450
|
-
await this.#swapStorage.update(id, data);
|
|
451
|
-
}
|
|
452
|
-
return data;
|
|
453
|
-
}
|
|
454
|
-
/**
|
|
455
|
-
* Gets a swap from local storage without making a server request.
|
|
456
|
-
*
|
|
457
|
-
* Use this when you need swap data but don't need the latest status
|
|
458
|
-
* from the server. The stored swap includes the preimage, keys, and
|
|
459
|
-
* the last known swap response.
|
|
460
|
-
*
|
|
461
|
-
* @param id - The UUID of the swap.
|
|
462
|
-
* @returns The stored swap data, or null if not found.
|
|
463
|
-
*
|
|
464
|
-
* @example
|
|
465
|
-
* ```ts
|
|
466
|
-
* const stored = await client.getStoredSwap(swapId);
|
|
467
|
-
* if (stored) {
|
|
468
|
-
* console.log("Target:", stored.response.target_token);
|
|
469
|
-
* console.log("Status:", stored.response.status);
|
|
470
|
-
* }
|
|
471
|
-
* ```
|
|
472
|
-
*/
|
|
473
|
-
async getStoredSwap(id) {
|
|
474
|
-
if (!this.#swapStorage) {
|
|
475
|
-
return null;
|
|
476
|
-
}
|
|
477
|
-
return this.#swapStorage.get(id);
|
|
478
|
-
}
|
|
479
|
-
/**
|
|
480
|
-
* Gets all stored swaps from local storage.
|
|
481
|
-
*
|
|
482
|
-
* @returns Array of all stored swap data, or empty array if no storage is configured.
|
|
483
|
-
*/
|
|
484
|
-
async listAllSwaps() {
|
|
485
|
-
if (!this.#swapStorage) {
|
|
486
|
-
return [];
|
|
487
|
-
}
|
|
488
|
-
return this.#swapStorage.getAll();
|
|
489
|
-
}
|
|
490
|
-
async deleteSwap(id) {
|
|
491
|
-
if (!this.#swapStorage) {
|
|
492
|
-
return;
|
|
493
|
-
}
|
|
494
|
-
await this.#swapStorage.delete(id);
|
|
495
|
-
}
|
|
496
|
-
async clearSwapStorage() {
|
|
497
|
-
if (!this.#swapStorage) {
|
|
498
|
-
return;
|
|
499
|
-
}
|
|
500
|
-
await this.#swapStorage.clear();
|
|
501
|
-
}
|
|
502
|
-
/**
|
|
503
|
-
* Recovers all swaps associated with the current wallet from the server.
|
|
504
|
-
*
|
|
505
|
-
* Sends the user's xpub to the server, which returns all swaps belonging
|
|
506
|
-
* to that wallet. For each recovered swap, re-derives the keys using the
|
|
507
|
-
* swap's derivation index and stores it locally.
|
|
508
|
-
*
|
|
509
|
-
* After recovery, the key index is set to `highest_index + 1` so that
|
|
510
|
-
* new swaps don't reuse derivation indices.
|
|
511
|
-
*
|
|
512
|
-
* @returns The recovered swaps stored locally.
|
|
513
|
-
*/
|
|
514
|
-
async recoverSwaps() {
|
|
515
|
-
console.log(`Recovering ...`);
|
|
516
|
-
const xpub = this.getUserIdXpub();
|
|
517
|
-
console.log(`Recovering ${xpub}`);
|
|
518
|
-
const { data, error } = await this.#apiClient.POST("/swap/recover", {
|
|
519
|
-
body: { xpub },
|
|
520
|
-
});
|
|
521
|
-
if (error) {
|
|
522
|
-
throw new Error(`Failed to recover swaps: ${JSON.stringify(error)}`);
|
|
523
|
-
}
|
|
524
|
-
if (!data) {
|
|
525
|
-
throw new Error("No recovery data returned");
|
|
526
|
-
}
|
|
527
|
-
const storedSwaps = [];
|
|
528
|
-
console.log(`Recovered data ${JSON.stringify(data)}`);
|
|
529
|
-
for (const recoveredSwap of data.swaps) {
|
|
530
|
-
const { index, ...response } = recoveredSwap;
|
|
531
|
-
const swapParams = this.deriveSwapParamsAtIndex(index);
|
|
532
|
-
await this.#storeSwap(response.id, swapParams, response);
|
|
533
|
-
const stored = await this.getStoredSwap(response.id);
|
|
534
|
-
if (stored) {
|
|
535
|
-
storedSwaps.push(stored);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
// Update key index so new swaps don't reuse indices
|
|
539
|
-
if (data.highest_index >= 0) {
|
|
540
|
-
await this.setKeyIndex(data.highest_index + 1);
|
|
541
|
-
}
|
|
542
|
-
return storedSwaps;
|
|
543
|
-
}
|
|
544
|
-
/**
|
|
545
|
-
* Gets VHTLC amounts for an Arkade swap.
|
|
546
|
-
*
|
|
547
|
-
* Queries the Arkade indexer for spendable, spent, and recoverable balances
|
|
548
|
-
* at the VHTLC address associated with a swap. Works for:
|
|
549
|
-
* - BTC → EVM swaps where the source asset is Arkade
|
|
550
|
-
* - EVM → BTC swaps where the target asset is Arkade
|
|
551
|
-
*
|
|
552
|
-
* Reads swap data from local storage (does not contact the server).
|
|
553
|
-
*
|
|
554
|
-
* @param id - The UUID of the swap.
|
|
555
|
-
* @returns The VHTLC amounts in satoshis.
|
|
556
|
-
*/
|
|
557
|
-
async amountsForSwap(id) {
|
|
558
|
-
const stored = await this.getStoredSwap(id);
|
|
559
|
-
if (!stored) {
|
|
560
|
-
throw new Error(`Swap not found in local storage: ${id}`);
|
|
561
|
-
}
|
|
562
|
-
const swap = stored.response;
|
|
563
|
-
if (swap.direction !== "btc_to_arkade" &&
|
|
564
|
-
swap.direction !== "arkade_to_evm" &&
|
|
565
|
-
swap.direction !== "evm_to_arkade") {
|
|
566
|
-
throw new Error(`amountsForSwap only applies to VHTLC-based swaps, got ${swap.direction}`);
|
|
567
|
-
}
|
|
568
|
-
// Get VHTLC address based on swap direction
|
|
569
|
-
let vhtlcAddress;
|
|
570
|
-
if (swap.direction === "btc_to_arkade") {
|
|
571
|
-
vhtlcAddress = swap.arkade_vhtlc_address;
|
|
572
|
-
}
|
|
573
|
-
else if (swap.direction === "arkade_to_evm" ||
|
|
574
|
-
swap.direction === "evm_to_arkade") {
|
|
575
|
-
vhtlcAddress = swap.btc_vhtlc_address;
|
|
576
|
-
}
|
|
577
|
-
if (!vhtlcAddress) {
|
|
578
|
-
throw new Error("Swap does not have an Arkade VHTLC address");
|
|
579
|
-
}
|
|
580
|
-
return getVhtlcAmounts({
|
|
581
|
-
vhtlcAddress,
|
|
582
|
-
network: swap.network,
|
|
583
|
-
arkadeServerUrl: this.#config.arkadeServerUrl,
|
|
584
|
-
});
|
|
585
|
-
}
|
|
586
|
-
// =========================================================================
|
|
587
|
-
// Redeem
|
|
588
|
-
// =========================================================================
|
|
589
|
-
/**
|
|
590
|
-
* Claims a swap by revealing the preimage.
|
|
591
|
-
*
|
|
592
|
-
* Reads swap data and preimage from local storage. The claim method
|
|
593
|
-
* depends on the swap direction and target chain:
|
|
594
|
-
* - **Arkade/Lightning-to-EVM**: Gasless claim via server
|
|
595
|
-
* - **Other EVM swaps**: Returns call data for manual claiming
|
|
596
|
-
* - **Arkade**: Claims via Arkade protocol
|
|
597
|
-
*
|
|
598
|
-
* @param id - The UUID of the swap.
|
|
599
|
-
* @param _options - Deprecated. For Arkade/Lightning-to-EVM, destination is set at swap creation.
|
|
600
|
-
* @returns A ClaimResult with the outcome.
|
|
601
|
-
*
|
|
602
|
-
* @example
|
|
603
|
-
* ```ts
|
|
604
|
-
* // Arkade-to-EVM (gasless via server, uses stored target address)
|
|
605
|
-
* const result = await client.claim(swapId);
|
|
606
|
-
*
|
|
607
|
-
* // Other swap types
|
|
608
|
-
* const result = await client.claim(swapId);
|
|
609
|
-
* if (result.success) {
|
|
610
|
-
* console.log("Claim TX:", result.txHash);
|
|
611
|
-
* }
|
|
612
|
-
* ```
|
|
613
|
-
*/
|
|
614
|
-
async claim(id, _options) {
|
|
615
|
-
// Check swap storage is configured
|
|
616
|
-
if (!this.#swapStorage) {
|
|
617
|
-
return {
|
|
618
|
-
success: false,
|
|
619
|
-
message: "Swap storage is not configured. Cannot retrieve swap data needed for claim.",
|
|
620
|
-
};
|
|
621
|
-
}
|
|
622
|
-
// Get stored swap data (contains preimage, keys, and swap response)
|
|
623
|
-
const storedSwap = await this.#swapStorage.get(id);
|
|
624
|
-
if (!storedSwap) {
|
|
625
|
-
return {
|
|
626
|
-
success: false,
|
|
627
|
-
message: `Swap ${id} not found in local storage. Cannot claim without stored data.`,
|
|
628
|
-
};
|
|
629
|
-
}
|
|
630
|
-
const swap = storedSwap.response;
|
|
631
|
-
const secret = storedSwap.preimage;
|
|
632
|
-
// EVM-targeted swaps: use gasless claim via server (SDK signs internally)
|
|
633
|
-
// The destination is always the stored target_evm_address (set at swap creation time)
|
|
634
|
-
if (swap.direction === "arkade_to_evm" ||
|
|
635
|
-
swap.direction === "lightning_to_evm" ||
|
|
636
|
-
swap.direction === "bitcoin_to_evm") {
|
|
637
|
-
const evmSwap = swap;
|
|
638
|
-
// Use the stored target address - this was set when the swap was created
|
|
639
|
-
const destination = evmSwap.target_evm_address ?? evmSwap.client_evm_address;
|
|
640
|
-
if (!destination) {
|
|
641
|
-
return {
|
|
642
|
-
success: false,
|
|
643
|
-
message: "Gasless claim failed: no target address found. " +
|
|
644
|
-
"This swap may have been created before target address storage was implemented.",
|
|
645
|
-
};
|
|
646
|
-
}
|
|
647
|
-
const gaslessResult = await this.claimViaGasless(id, destination);
|
|
648
|
-
return {
|
|
649
|
-
success: true,
|
|
650
|
-
message: gaslessResult.message,
|
|
651
|
-
txHash: gaslessResult.txHash,
|
|
652
|
-
};
|
|
653
|
-
}
|
|
654
|
-
// EVM-to-Bitcoin: user claims BTC from on-chain Taproot HTLC with preimage
|
|
655
|
-
if (swap.direction === "evm_to_bitcoin") {
|
|
656
|
-
return this.#claimOnchainBtc(id, _options);
|
|
657
|
-
}
|
|
658
|
-
// Check if target is Arkade (handle both string "btc_arkade" and TokenInfo object)
|
|
659
|
-
const isArkadeTarget = swap.target_token.chain === "Arkade";
|
|
660
|
-
if (isArkadeTarget) {
|
|
661
|
-
// Determine destination address based on swap direction
|
|
662
|
-
let destinationAddress;
|
|
663
|
-
if (swap.direction === "btc_to_arkade") {
|
|
664
|
-
const btcToArkadeSwap = swap;
|
|
665
|
-
destinationAddress = btcToArkadeSwap.target_arkade_address;
|
|
666
|
-
}
|
|
667
|
-
else if (swap.direction === "evm_to_arkade") {
|
|
668
|
-
// For evm_to_arkade swaps, check if we have target_arkade_address in stored response
|
|
669
|
-
// Check if we have target_arkade_address in the stored response.
|
|
670
|
-
const storedResponse = swap;
|
|
671
|
-
if (storedResponse.target_arkade_address) {
|
|
672
|
-
destinationAddress = storedResponse.target_arkade_address;
|
|
673
|
-
}
|
|
674
|
-
else {
|
|
675
|
-
// Fetch from API to get the full response with target_arkade_address
|
|
676
|
-
const freshSwap = await this.getSwap(id);
|
|
677
|
-
const evmToArkadeSwap = freshSwap;
|
|
678
|
-
destinationAddress = evmToArkadeSwap.target_arkade_address;
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
if (!destinationAddress) {
|
|
682
|
-
return {
|
|
683
|
-
success: false,
|
|
684
|
-
message: "No Arkade destination address found in swap. Use claimArkade() with explicit destinationAddress.",
|
|
685
|
-
};
|
|
686
|
-
}
|
|
687
|
-
const arkadeResult = await this.claimArkade(id, { destinationAddress });
|
|
688
|
-
// Convert to ClaimResult format
|
|
689
|
-
return {
|
|
690
|
-
success: arkadeResult.success,
|
|
691
|
-
message: arkadeResult.message,
|
|
692
|
-
chain: "arkade",
|
|
693
|
-
txHash: arkadeResult.txId,
|
|
694
|
-
};
|
|
695
|
-
}
|
|
696
|
-
// For EVM chains, use the existing claim logic
|
|
697
|
-
return redeemClaim(id, secret, {
|
|
698
|
-
apiClient: this.#apiClient,
|
|
699
|
-
getSwap: () => Promise.resolve(swap),
|
|
700
|
-
});
|
|
701
|
-
}
|
|
702
|
-
/**
|
|
703
|
-
* Claims an Arkade-to-EVM swap gaslessly via the server.
|
|
704
|
-
*
|
|
705
|
-
* The SDK builds the EIP-712 digest, signs it with the swap's internally
|
|
706
|
-
* derived EVM key, and sends the signature + secret to the server. The
|
|
707
|
-
* server submits the `coordinator.redeemAndExecute` transaction.
|
|
708
|
-
*
|
|
709
|
-
* @param id - The UUID of the swap.
|
|
710
|
-
* @param destination - The EVM address where tokens should be sent.
|
|
711
|
-
* @returns The gasless claim result with transaction hash.
|
|
712
|
-
*
|
|
713
|
-
* @example
|
|
714
|
-
* ```ts
|
|
715
|
-
* const result = await client.claimViaGasless(swapId, "0xYourAddress");
|
|
716
|
-
* console.log("Claimed! TX:", result.txHash);
|
|
717
|
-
* ```
|
|
718
|
-
*/
|
|
719
|
-
async claimViaGasless(id, destination, options) {
|
|
720
|
-
if (!this.#swapStorage) {
|
|
721
|
-
throw new Error("Swap storage is not configured. Cannot retrieve preimage needed for gasless claim.");
|
|
722
|
-
}
|
|
723
|
-
// Fetch all data upfront
|
|
724
|
-
const stored = await this.#swapStorage.get(id);
|
|
725
|
-
if (!stored) {
|
|
726
|
-
throw new Error(`Swap ${id} not found in local storage.`);
|
|
727
|
-
}
|
|
728
|
-
const swap = (await this.getSwap(id, {
|
|
729
|
-
updateStorage: true,
|
|
730
|
-
}));
|
|
731
|
-
if (swap.direction !== "arkade_to_evm" &&
|
|
732
|
-
swap.direction !== "lightning_to_evm" &&
|
|
733
|
-
swap.direction !== "bitcoin_to_evm") {
|
|
734
|
-
throw new Error(`Expected arkade_to_evm or lightning_to_evm swap, got ${swap.direction}. claimViaGasless is for EVM-targeted swaps.`);
|
|
735
|
-
}
|
|
736
|
-
// Fetch DEX calldata if the target token differs from WBTC
|
|
737
|
-
const targetTokenAddress = String(swap.target_token.token_id);
|
|
738
|
-
const needsDexSwap = targetTokenAddress.toLowerCase() !== swap.wbtc_address.toLowerCase();
|
|
739
|
-
let dexCalldata;
|
|
740
|
-
if (needsDexSwap) {
|
|
741
|
-
const slippage = options?.slippage ?? 1.0;
|
|
742
|
-
const calldataResponse = await this.#apiClient.GET("/swap/{id}/redeem-and-swap-calldata", {
|
|
743
|
-
params: {
|
|
744
|
-
path: { id },
|
|
745
|
-
query: { destination, slippage },
|
|
746
|
-
},
|
|
747
|
-
});
|
|
748
|
-
if (calldataResponse.error) {
|
|
749
|
-
throw new Error(`Failed to fetch DEX calldata: ${calldataResponse.error.error}`);
|
|
750
|
-
}
|
|
751
|
-
if (calldataResponse.data) {
|
|
752
|
-
dexCalldata = {
|
|
753
|
-
to: calldataResponse.data.dex_calldata.to,
|
|
754
|
-
data: calldataResponse.data.dex_calldata.data,
|
|
755
|
-
value: calldataResponse.data.dex_calldata.value,
|
|
756
|
-
};
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
return gaslessClaim({
|
|
760
|
-
baseUrl: this.#config.baseUrl,
|
|
761
|
-
preimage: stored.preimage,
|
|
762
|
-
secretKey: hexToBytes(stored.secretKey),
|
|
763
|
-
swap,
|
|
764
|
-
destination,
|
|
765
|
-
dexCalldata,
|
|
766
|
-
});
|
|
767
|
-
}
|
|
768
|
-
/**
|
|
769
|
-
* Claims an Arkade (off-chain) VHTLC swap by revealing the preimage.
|
|
770
|
-
*
|
|
771
|
-
* Automatically selects the best claim method based on VTXO status:
|
|
772
|
-
* - **spendable** VTXOs → offchain spend (submitTx/finalizeTx)
|
|
773
|
-
* - **recoverable** or **mixed** VTXOs → delegated settlement via backend
|
|
774
|
-
*
|
|
775
|
-
* This is used for EVM-to-Arkade and BTC-to-Arkade swaps where the user
|
|
776
|
-
* claims BTC on Arkade after the server has funded the VHTLC.
|
|
777
|
-
*
|
|
778
|
-
* @param id - The UUID of the swap.
|
|
779
|
-
* @param options - Claim options including destination address.
|
|
780
|
-
* @returns The claim result with transaction ID and amount.
|
|
781
|
-
*
|
|
782
|
-
* @example
|
|
783
|
-
* ```ts
|
|
784
|
-
* const result = await client.claimArkade(swapId, {
|
|
785
|
-
* destinationAddress: "ark1q...", // Where to receive BTC
|
|
786
|
-
* });
|
|
787
|
-
* if (result.success) {
|
|
788
|
-
* console.log("Claim TX:", result.txId);
|
|
789
|
-
* console.log("Amount:", result.claimAmount);
|
|
790
|
-
* }
|
|
791
|
-
* ```
|
|
792
|
-
*/
|
|
793
|
-
async claimArkade(id, options) {
|
|
794
|
-
// Validate options
|
|
795
|
-
if (!options?.destinationAddress) {
|
|
796
|
-
return {
|
|
797
|
-
success: false,
|
|
798
|
-
message: "Destination address is required for Arkade claims. " +
|
|
799
|
-
'Provide it via the options parameter: { destinationAddress: "ark1..." }',
|
|
800
|
-
};
|
|
801
|
-
}
|
|
802
|
-
// Check swap storage is configured
|
|
803
|
-
if (!this.#swapStorage) {
|
|
804
|
-
return {
|
|
805
|
-
success: false,
|
|
806
|
-
message: "Swap storage is not configured. Cannot retrieve the preimage needed for claim.",
|
|
807
|
-
};
|
|
808
|
-
}
|
|
809
|
-
// Get stored swap data (contains preimage and secret key)
|
|
810
|
-
const storedSwap = await this.#swapStorage.get(id);
|
|
811
|
-
if (!storedSwap) {
|
|
812
|
-
return {
|
|
813
|
-
success: false,
|
|
814
|
-
message: `Swap ${id} not found in local storage. The preimage is required to claim.`,
|
|
815
|
-
};
|
|
816
|
-
}
|
|
817
|
-
const swap = storedSwap.response;
|
|
818
|
-
// Ensure we have an Arkade-target swap
|
|
819
|
-
if (swap.direction !== "btc_to_arkade" &&
|
|
820
|
-
swap.direction !== "evm_to_arkade") {
|
|
821
|
-
return {
|
|
822
|
-
success: false,
|
|
823
|
-
message: `Expected btc_to_arkade or evm_to_arkade swap, got ${swap.direction}. claimArkade is for swaps targeting Arkade.`,
|
|
824
|
-
};
|
|
825
|
-
}
|
|
826
|
-
// Extract common VHTLC parameters
|
|
827
|
-
const claimParams = this.#extractArkadeClaimParams(id, storedSwap);
|
|
828
|
-
// Query VTXO status to determine claim method
|
|
829
|
-
const amounts = await this.amountsForSwap(id);
|
|
830
|
-
const vtxoStatus = amounts.vtxoStatus;
|
|
831
|
-
if (vtxoStatus === "not_funded" || vtxoStatus === "spent") {
|
|
832
|
-
return {
|
|
833
|
-
success: false,
|
|
834
|
-
message: vtxoStatus === "not_funded"
|
|
835
|
-
? "No VTXOs found at the VHTLC address. The swap may not have been funded yet."
|
|
836
|
-
: "All VTXOs have already been spent.",
|
|
837
|
-
};
|
|
838
|
-
}
|
|
839
|
-
// Route based on VTXO status:
|
|
840
|
-
// - spendable: offchain spend (faster, no backend dependency)
|
|
841
|
-
// - recoverable/mixed: delegated settlement (handles expired batches)
|
|
842
|
-
if (vtxoStatus === "spendable") {
|
|
843
|
-
return this.#claimArkadeOffchain(claimParams, options);
|
|
844
|
-
}
|
|
845
|
-
// recoverable or mixed → delegate
|
|
846
|
-
return this.#claimArkadeDelegate(id, claimParams, options);
|
|
847
|
-
}
|
|
848
|
-
/**
|
|
849
|
-
* Extracts VHTLC claim parameters from a stored swap.
|
|
850
|
-
* @internal
|
|
851
|
-
*/
|
|
852
|
-
#extractArkadeClaimParams(_id, storedSwap) {
|
|
853
|
-
const swap = storedSwap.response;
|
|
854
|
-
const fullPubKey = storedSwap.publicKey;
|
|
855
|
-
const userPubKey = fullPubKey.length === 66 ? fullPubKey.slice(2) : fullPubKey;
|
|
856
|
-
let lendaswapPubKey;
|
|
857
|
-
let arkadeServerPubKey;
|
|
858
|
-
let vhtlcAddress;
|
|
859
|
-
let refundLocktime;
|
|
860
|
-
let unilateralClaimDelay;
|
|
861
|
-
let unilateralRefundDelay;
|
|
862
|
-
let unilateralRefundWithoutReceiverDelay;
|
|
863
|
-
let network;
|
|
864
|
-
if (swap.direction === "btc_to_arkade") {
|
|
865
|
-
const s = swap;
|
|
866
|
-
lendaswapPubKey = s.server_vhtlc_pk;
|
|
867
|
-
arkadeServerPubKey = s.arkade_server_pk;
|
|
868
|
-
vhtlcAddress = s.arkade_vhtlc_address;
|
|
869
|
-
refundLocktime = s.vhtlc_refund_locktime;
|
|
870
|
-
unilateralClaimDelay = s.unilateral_claim_delay;
|
|
871
|
-
unilateralRefundDelay = s.unilateral_refund_delay;
|
|
872
|
-
unilateralRefundWithoutReceiverDelay =
|
|
873
|
-
s.unilateral_refund_without_receiver_delay;
|
|
874
|
-
network = s.network;
|
|
875
|
-
}
|
|
876
|
-
else if (swap.direction === "evm_to_arkade") {
|
|
877
|
-
const s = swap;
|
|
878
|
-
lendaswapPubKey = s.sender_pk;
|
|
879
|
-
arkadeServerPubKey = s.arkade_server_pk;
|
|
880
|
-
vhtlcAddress = s.btc_vhtlc_address;
|
|
881
|
-
refundLocktime = s.vhtlc_refund_locktime;
|
|
882
|
-
unilateralClaimDelay = s.unilateral_claim_delay;
|
|
883
|
-
unilateralRefundDelay = s.unilateral_refund_delay;
|
|
884
|
-
unilateralRefundWithoutReceiverDelay =
|
|
885
|
-
s.unilateral_refund_without_receiver_delay;
|
|
886
|
-
network = s.network;
|
|
887
|
-
}
|
|
888
|
-
else {
|
|
889
|
-
throw Error(`Unsupported direction for Arkade claim: ${swap.direction}`);
|
|
890
|
-
}
|
|
891
|
-
return {
|
|
892
|
-
userSecretKey: storedSwap.secretKey,
|
|
893
|
-
userPubKey,
|
|
894
|
-
lendaswapPubKey,
|
|
895
|
-
arkadeServerPubKey,
|
|
896
|
-
vhtlcAddress,
|
|
897
|
-
refundLocktime,
|
|
898
|
-
unilateralClaimDelay,
|
|
899
|
-
unilateralRefundDelay,
|
|
900
|
-
unilateralRefundWithoutReceiverDelay,
|
|
901
|
-
network,
|
|
902
|
-
preimage: storedSwap.preimage,
|
|
903
|
-
preimageHash: storedSwap.preimageHash,
|
|
904
|
-
};
|
|
905
|
-
}
|
|
906
|
-
/**
|
|
907
|
-
* Claims via the offchain submitTx/finalizeTx path (spendable VTXOs only).
|
|
908
|
-
* @internal
|
|
909
|
-
*/
|
|
910
|
-
async #claimArkadeOffchain(params, options) {
|
|
911
|
-
try {
|
|
912
|
-
const result = await buildArkadeClaim({
|
|
913
|
-
...params,
|
|
914
|
-
destinationAddress: options.destinationAddress,
|
|
915
|
-
arkadeServerUrl: options.arkadeServerUrl ?? this.#config.arkadeServerUrl,
|
|
916
|
-
});
|
|
917
|
-
return {
|
|
918
|
-
success: true,
|
|
919
|
-
message: "Arkade claim executed successfully via offchain spend!",
|
|
920
|
-
txId: result.txId,
|
|
921
|
-
claimAmount: result.claimAmount,
|
|
922
|
-
};
|
|
923
|
-
}
|
|
924
|
-
catch (error) {
|
|
925
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
926
|
-
return {
|
|
927
|
-
success: false,
|
|
928
|
-
message: `Failed to execute offchain Arkade claim: ${message}`,
|
|
929
|
-
};
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
/**
|
|
933
|
-
* Claims via the delegated settlement path (works for all VTXO states).
|
|
934
|
-
* @internal
|
|
935
|
-
*/
|
|
936
|
-
async #claimArkadeDelegate(swapId, params, options) {
|
|
937
|
-
try {
|
|
938
|
-
const result = await delegateClaim({
|
|
939
|
-
...params,
|
|
940
|
-
destinationAddress: options.destinationAddress,
|
|
941
|
-
lendaswapApiUrl: this.#config.baseUrl,
|
|
942
|
-
arkadeServerUrl: options.arkadeServerUrl ?? this.#config.arkadeServerUrl,
|
|
943
|
-
swapId,
|
|
944
|
-
});
|
|
945
|
-
return {
|
|
946
|
-
success: true,
|
|
947
|
-
message: "Arkade claim executed successfully via delegated settlement!",
|
|
948
|
-
txId: result.commitmentTxid,
|
|
949
|
-
};
|
|
950
|
-
}
|
|
951
|
-
catch (error) {
|
|
952
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
953
|
-
return {
|
|
954
|
-
success: false,
|
|
955
|
-
message: `Failed to execute delegated Arkade claim: ${message}`,
|
|
956
|
-
};
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
// =========================================================================
|
|
960
|
-
// Refund
|
|
961
|
-
// =========================================================================
|
|
962
|
-
/**
|
|
963
|
-
* Attempts to refund a swap.
|
|
964
|
-
*
|
|
965
|
-
* Refund behavior depends on the swap type:
|
|
966
|
-
* - **Lightning to EVM**: Cannot refund - Lightning swaps auto-expire if not completed.
|
|
967
|
-
* The invoice will simply expire and no funds are locked.
|
|
968
|
-
* - **Arkade to EVM**: Off-chain refund via Arkade server
|
|
969
|
-
* - **Bitcoin (on-chain) to EVM**: Builds a signed refund transaction that the user
|
|
970
|
-
* must broadcast to reclaim their funds after the locktime.
|
|
971
|
-
*
|
|
972
|
-
* @param id - The UUID of the swap to refund.
|
|
973
|
-
* @param options - Options for on-chain refunds (required for btc_onchain swaps).
|
|
974
|
-
* @returns A RefundResult with the transaction details (for on-chain) or status message.
|
|
975
|
-
* @throws Error if the swap cannot be found, storage is not configured, or params are invalid.
|
|
976
|
-
*
|
|
977
|
-
* @example
|
|
978
|
-
* ```ts
|
|
979
|
-
* // For on-chain swaps
|
|
980
|
-
* const result = await client.refundSwap(swapId, {
|
|
981
|
-
* destinationAddress: "bc1q...",
|
|
982
|
-
* feeRateSatPerVb: 5,
|
|
983
|
-
* });
|
|
984
|
-
* if (result.success) {
|
|
985
|
-
* console.log("Broadcast this transaction:", result.txHex);
|
|
986
|
-
* console.log("Transaction ID:", result.txId);
|
|
987
|
-
* }
|
|
988
|
-
* ```
|
|
989
|
-
*/
|
|
990
|
-
async refundSwap(id, options) {
|
|
991
|
-
// Get the swap to determine its type
|
|
992
|
-
const storedSwap = await this.getStoredSwap(id);
|
|
993
|
-
if (!storedSwap) {
|
|
994
|
-
throw Error("Swap not found");
|
|
995
|
-
}
|
|
996
|
-
const swap = storedSwap.response;
|
|
997
|
-
// Use direction to determine refund method (source_token may be a TokenSummary object)
|
|
998
|
-
const direction = swap.direction;
|
|
999
|
-
// Arkade swaps require off-chain refund
|
|
1000
|
-
if (direction === "arkade_to_evm") {
|
|
1001
|
-
return this.#buildArkadeRefund(id, swap, options);
|
|
1002
|
-
}
|
|
1003
|
-
// Bitcoin on-chain swaps require on-chain refund transaction
|
|
1004
|
-
if (direction === "bitcoin_to_evm" || direction === "btc_to_arkade") {
|
|
1005
|
-
return this.#buildOnchainRefund(id, swap, options);
|
|
1006
|
-
}
|
|
1007
|
-
// EVM-sourced swaps return calldata for manual execution
|
|
1008
|
-
if (direction === "evm_to_arkade") {
|
|
1009
|
-
const evmOptions = options;
|
|
1010
|
-
return this.#buildEvmToArkadeRefund(id, swap, evmOptions?.mode);
|
|
1011
|
-
}
|
|
1012
|
-
// EVM-to-Bitcoin uses coordinator refund (same pattern as EVM-to-Arkade)
|
|
1013
|
-
if (direction === "evm_to_bitcoin") {
|
|
1014
|
-
const evmOptions = options;
|
|
1015
|
-
return this.#buildEvmToBitcoinRefund(id, swap, evmOptions?.mode);
|
|
1016
|
-
}
|
|
1017
|
-
// EVM-to-Lightning uses coordinator refund (same pattern as EVM-to-Arkade)
|
|
1018
|
-
if (direction === "evm_to_lightning") {
|
|
1019
|
-
const evmOptions = options;
|
|
1020
|
-
return this.#buildEvmToLightningRefund(id, swap, evmOptions?.mode);
|
|
1021
|
-
}
|
|
1022
|
-
return {
|
|
1023
|
-
success: false,
|
|
1024
|
-
message: `Refund not supported for direction: ${direction}.`,
|
|
1025
|
-
};
|
|
1026
|
-
}
|
|
1027
|
-
/**
|
|
1028
|
-
* Claims BTC from an on-chain Taproot HTLC for an EVM-to-Bitcoin swap.
|
|
1029
|
-
*
|
|
1030
|
-
* The user reveals the preimage to spend from the hashlock script path.
|
|
1031
|
-
* @internal
|
|
1032
|
-
*/
|
|
1033
|
-
async #claimOnchainBtc(id, options) {
|
|
1034
|
-
if (!this.#swapStorage) {
|
|
1035
|
-
return {
|
|
1036
|
-
success: false,
|
|
1037
|
-
message: "Swap storage is not configured. Cannot retrieve preimage and keys needed for claim.",
|
|
1038
|
-
};
|
|
1039
|
-
}
|
|
1040
|
-
const storedSwap = await this.#swapStorage.get(id);
|
|
1041
|
-
if (!storedSwap) {
|
|
1042
|
-
return {
|
|
1043
|
-
success: false,
|
|
1044
|
-
message: `Swap ${id} not found in local storage.`,
|
|
1045
|
-
};
|
|
1046
|
-
}
|
|
1047
|
-
// Fetch the latest swap state from API
|
|
1048
|
-
const swap = (await this.getSwap(id, {
|
|
1049
|
-
updateStorage: true,
|
|
1050
|
-
}));
|
|
1051
|
-
if (swap.direction !== "evm_to_bitcoin") {
|
|
1052
|
-
return {
|
|
1053
|
-
success: false,
|
|
1054
|
-
message: `Expected evm_to_bitcoin swap, got ${swap.direction}`,
|
|
1055
|
-
};
|
|
1056
|
-
}
|
|
1057
|
-
// Extract BTC HTLC parameters
|
|
1058
|
-
const btcHtlcAddress = swap.btc_htlc_address;
|
|
1059
|
-
const btcHashLock = swap.btc_hash_lock;
|
|
1060
|
-
const btcRefundLocktime = swap.btc_refund_locktime;
|
|
1061
|
-
const networkStr = swap.network;
|
|
1062
|
-
// Get server refund pk (needed to reconstruct the Taproot tree)
|
|
1063
|
-
const serverRefundPkRaw = swap
|
|
1064
|
-
.btc_server_refund_pk;
|
|
1065
|
-
if (!serverRefundPkRaw) {
|
|
1066
|
-
return {
|
|
1067
|
-
success: false,
|
|
1068
|
-
message: "Server refund public key not available. The API response may need to be updated.",
|
|
1069
|
-
};
|
|
1070
|
-
}
|
|
1071
|
-
// Map network string
|
|
1072
|
-
const networkMap = {
|
|
1073
|
-
mainnet: "mainnet",
|
|
1074
|
-
testnet: "testnet",
|
|
1075
|
-
signet: "signet",
|
|
1076
|
-
regtest: "regtest",
|
|
1077
|
-
};
|
|
1078
|
-
const network = networkMap[networkStr];
|
|
1079
|
-
if (!network) {
|
|
1080
|
-
return {
|
|
1081
|
-
success: false,
|
|
1082
|
-
message: `Unknown Bitcoin network: ${networkStr}`,
|
|
1083
|
-
};
|
|
1084
|
-
}
|
|
1085
|
-
// Get user's x-only public key (32 bytes from 33-byte compressed)
|
|
1086
|
-
const fullPubKey = storedSwap.publicKey;
|
|
1087
|
-
const userClaimPk = fullPubKey.length === 66 ? fullPubKey.slice(2) : fullPubKey;
|
|
1088
|
-
// Strip compressed key prefix if present
|
|
1089
|
-
const serverRefundPk = serverRefundPkRaw.length === 66
|
|
1090
|
-
? serverRefundPkRaw.slice(2)
|
|
1091
|
-
: serverRefundPkRaw;
|
|
1092
|
-
// Verify HTLC address matches our reconstruction
|
|
1093
|
-
const addressMatches = verifyHtlcAddress(btcHtlcAddress, btcHashLock, userClaimPk, // claimer = user (goes in hashlock position)
|
|
1094
|
-
serverRefundPk, // refunder = server (goes in timelock position)
|
|
1095
|
-
btcRefundLocktime, network);
|
|
1096
|
-
if (!addressMatches) {
|
|
1097
|
-
return {
|
|
1098
|
-
success: false,
|
|
1099
|
-
message: `HTLC address mismatch. Computed address does not match server's (${btcHtlcAddress}). ` +
|
|
1100
|
-
`Parameters: hashLock='${btcHashLock}', userPk='${userClaimPk}', ` +
|
|
1101
|
-
`serverPk='${serverRefundPk}', locktime='${btcRefundLocktime}', network='${network}'`,
|
|
1102
|
-
};
|
|
1103
|
-
}
|
|
1104
|
-
// Get the HTLC output info - prefer API data over Esplora lookup
|
|
1105
|
-
const esploraUrl = this.#config.esploraUrl ?? DEFAULT_ESPLORA_URLS[network];
|
|
1106
|
-
if (!esploraUrl) {
|
|
1107
|
-
return {
|
|
1108
|
-
success: false,
|
|
1109
|
-
message: `No Esplora URL configured for network ${network}.`,
|
|
1110
|
-
};
|
|
1111
|
-
}
|
|
1112
|
-
// Try to use funding info from the API response (faster, works before confirmation)
|
|
1113
|
-
const btcFundTxid = swap.btc_fund_txid;
|
|
1114
|
-
const btcFundVout = swap.btc_fund_vout;
|
|
1115
|
-
let htlcOutput = null;
|
|
1116
|
-
if (btcFundTxid && btcFundVout !== undefined) {
|
|
1117
|
-
// We have the funding info from the API, but we need to get the amount
|
|
1118
|
-
// Query the transaction to get the output amount
|
|
1119
|
-
try {
|
|
1120
|
-
const txResponse = await fetch(`${esploraUrl}/tx/${btcFundTxid}`);
|
|
1121
|
-
if (txResponse.ok) {
|
|
1122
|
-
const txData = (await txResponse.json());
|
|
1123
|
-
if (txData.vout?.[btcFundVout]) {
|
|
1124
|
-
htlcOutput = {
|
|
1125
|
-
txid: btcFundTxid,
|
|
1126
|
-
vout: btcFundVout,
|
|
1127
|
-
amount: BigInt(txData.vout[btcFundVout].value),
|
|
1128
|
-
};
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
catch {
|
|
1133
|
-
// Fall through to Esplora lookup
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
// Fallback: query Esplora for UTXOs at the address (requires confirmation)
|
|
1137
|
-
if (!htlcOutput) {
|
|
1138
|
-
htlcOutput = await findOutputByAddress(esploraUrl, btcHtlcAddress);
|
|
1139
|
-
}
|
|
1140
|
-
if (!htlcOutput) {
|
|
1141
|
-
return {
|
|
1142
|
-
success: false,
|
|
1143
|
-
message: `Could not find UTXO at HTLC address ${btcHtlcAddress}. The server may not have funded the HTLC yet.`,
|
|
1144
|
-
};
|
|
1145
|
-
}
|
|
1146
|
-
// Determine destination address: prefer explicit option, fall back to stored response
|
|
1147
|
-
const destinationAddress = options?.destinationAddress ??
|
|
1148
|
-
swap.target_btc_address;
|
|
1149
|
-
if (!destinationAddress) {
|
|
1150
|
-
return {
|
|
1151
|
-
success: false,
|
|
1152
|
-
message: "Destination address is required to claim BTC. " +
|
|
1153
|
-
'Provide it via options: { destinationAddress: "bc1p..." }',
|
|
1154
|
-
};
|
|
1155
|
-
}
|
|
1156
|
-
try {
|
|
1157
|
-
const result = buildOnchainClaimTransaction({
|
|
1158
|
-
fundingTxId: htlcOutput.txid,
|
|
1159
|
-
fundingVout: htlcOutput.vout,
|
|
1160
|
-
htlcAmount: htlcOutput.amount,
|
|
1161
|
-
hashLock: btcHashLock,
|
|
1162
|
-
userClaimPubKey: userClaimPk,
|
|
1163
|
-
serverRefundPubKey: serverRefundPk,
|
|
1164
|
-
userSecretKey: storedSwap.secretKey,
|
|
1165
|
-
preimage: storedSwap.preimage,
|
|
1166
|
-
refundLocktime: btcRefundLocktime,
|
|
1167
|
-
destinationAddress,
|
|
1168
|
-
feeRateSatPerVb: options?.feeRateSatPerVb ?? 2,
|
|
1169
|
-
network,
|
|
1170
|
-
});
|
|
1171
|
-
// Broadcast
|
|
1172
|
-
try {
|
|
1173
|
-
await broadcastTransaction(esploraUrl, result.txHex);
|
|
1174
|
-
return {
|
|
1175
|
-
success: true,
|
|
1176
|
-
message: "BTC claim transaction broadcast successfully!",
|
|
1177
|
-
txHash: result.txId,
|
|
1178
|
-
// chain: "bitcoin" — not in ClaimChain type
|
|
1179
|
-
};
|
|
1180
|
-
}
|
|
1181
|
-
catch (broadcastError) {
|
|
1182
|
-
const msg = broadcastError instanceof Error
|
|
1183
|
-
? broadcastError.message
|
|
1184
|
-
: String(broadcastError);
|
|
1185
|
-
return {
|
|
1186
|
-
success: true,
|
|
1187
|
-
message: `Claim transaction built but broadcast failed: ${msg}. TxHex: ${result.txHex}`,
|
|
1188
|
-
txHash: result.txId,
|
|
1189
|
-
// chain: "bitcoin" — not in ClaimChain type
|
|
1190
|
-
};
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
catch (error) {
|
|
1194
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
1195
|
-
return {
|
|
1196
|
-
success: false,
|
|
1197
|
-
message: `Failed to build claim transaction: ${msg}`,
|
|
1198
|
-
};
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
/**
|
|
1202
|
-
* Builds an on-chain Bitcoin refund transaction.
|
|
1203
|
-
* @internal
|
|
1204
|
-
*/
|
|
1205
|
-
async #buildOnchainRefund(id, swap, options) {
|
|
1206
|
-
// Validate options
|
|
1207
|
-
if (!options?.destinationAddress) {
|
|
1208
|
-
return {
|
|
1209
|
-
success: false,
|
|
1210
|
-
message: "Destination address is required for on-chain refunds. " +
|
|
1211
|
-
'Provide it via the options parameter: { destinationAddress: "bc1q..." }',
|
|
1212
|
-
};
|
|
1213
|
-
}
|
|
1214
|
-
// Check swap storage is configured
|
|
1215
|
-
if (!this.#swapStorage) {
|
|
1216
|
-
return {
|
|
1217
|
-
success: false,
|
|
1218
|
-
message: "Swap storage is not configured. Cannot retrieve the secret key needed for refund.",
|
|
1219
|
-
};
|
|
1220
|
-
}
|
|
1221
|
-
// Get stored swap data (contains secret key)
|
|
1222
|
-
const storedSwap = await this.#swapStorage.get(id);
|
|
1223
|
-
if (!storedSwap) {
|
|
1224
|
-
return {
|
|
1225
|
-
success: false,
|
|
1226
|
-
message: `Swap ${id} not found in local storage. The secret key is required to sign the refund transaction.`,
|
|
1227
|
-
};
|
|
1228
|
-
}
|
|
1229
|
-
// Ensure we have an on-chain funded swap
|
|
1230
|
-
if (swap.direction !== "bitcoin_to_evm" &&
|
|
1231
|
-
swap.direction !== "btc_to_arkade") {
|
|
1232
|
-
return {
|
|
1233
|
-
success: false,
|
|
1234
|
-
message: `Expected bitcoin_to_evm or btc_to_arkade swap, got ${swap.direction}`,
|
|
1235
|
-
};
|
|
1236
|
-
}
|
|
1237
|
-
// Extract on-chain HTLC fields based on direction
|
|
1238
|
-
// Both directions have the same on-chain HTLC but fields are named differently
|
|
1239
|
-
let btcHtlcAddress;
|
|
1240
|
-
let btcRefundLocktime;
|
|
1241
|
-
let hashLock;
|
|
1242
|
-
let serverPubKeyFull;
|
|
1243
|
-
let networkStr;
|
|
1244
|
-
if (swap.direction === "btc_to_arkade") {
|
|
1245
|
-
const arkadeSwap = swap;
|
|
1246
|
-
btcHtlcAddress = arkadeSwap.btc_htlc_address;
|
|
1247
|
-
btcRefundLocktime = arkadeSwap.btc_refund_locktime;
|
|
1248
|
-
hashLock = arkadeSwap.hash_lock;
|
|
1249
|
-
serverPubKeyFull = arkadeSwap.server_vhtlc_pk;
|
|
1250
|
-
networkStr = arkadeSwap.network;
|
|
1251
|
-
}
|
|
1252
|
-
else {
|
|
1253
|
-
// OnchainToEvmSwapResponse (on-chain Bitcoin to EVM)
|
|
1254
|
-
const onchainSwap = swap;
|
|
1255
|
-
btcHtlcAddress = onchainSwap.btc_htlc_address;
|
|
1256
|
-
btcRefundLocktime = onchainSwap.btc_refund_locktime;
|
|
1257
|
-
hashLock = onchainSwap.btc_hash_lock;
|
|
1258
|
-
serverPubKeyFull = onchainSwap.btc_server_pk;
|
|
1259
|
-
networkStr = onchainSwap.network;
|
|
1260
|
-
}
|
|
1261
|
-
// Check refund locktime
|
|
1262
|
-
const now = Math.floor(Date.now() / 1000);
|
|
1263
|
-
if (now < btcRefundLocktime) {
|
|
1264
|
-
const remainingSeconds = btcRefundLocktime - now;
|
|
1265
|
-
const remainingMinutes = Math.ceil(remainingSeconds / 60);
|
|
1266
|
-
return {
|
|
1267
|
-
success: false,
|
|
1268
|
-
message: `Refund is not yet available. The locktime expires in ${remainingMinutes} minutes ` +
|
|
1269
|
-
`(at ${new Date(btcRefundLocktime * 1000).toISOString()}).`,
|
|
1270
|
-
};
|
|
1271
|
-
}
|
|
1272
|
-
// Map network string to BitcoinNetwork type
|
|
1273
|
-
const networkMap = {
|
|
1274
|
-
mainnet: "mainnet",
|
|
1275
|
-
testnet: "testnet",
|
|
1276
|
-
signet: "signet",
|
|
1277
|
-
regtest: "regtest",
|
|
1278
|
-
};
|
|
1279
|
-
const network = networkMap[networkStr];
|
|
1280
|
-
if (!network) {
|
|
1281
|
-
return {
|
|
1282
|
-
success: false,
|
|
1283
|
-
message: `Unknown Bitcoin network: ${networkStr}`,
|
|
1284
|
-
};
|
|
1285
|
-
}
|
|
1286
|
-
// Get user's x-only public key (32 bytes) from stored swap
|
|
1287
|
-
// The stored publicKey is the full compressed pubkey (33 bytes)
|
|
1288
|
-
// We need to extract the x-only portion (drop the first byte prefix)
|
|
1289
|
-
const fullPubKey = storedSwap.publicKey;
|
|
1290
|
-
const userPubKey = fullPubKey.length === 66 ? fullPubKey.slice(2) : fullPubKey;
|
|
1291
|
-
// Strip compressed key prefix if present (33-byte → 32-byte x-only)
|
|
1292
|
-
const serverXOnlyPubKey = serverPubKeyFull.length === 66
|
|
1293
|
-
? serverPubKeyFull.slice(2)
|
|
1294
|
-
: serverPubKeyFull;
|
|
1295
|
-
// Verify that our computed HTLC address matches the server's address
|
|
1296
|
-
const addressMatches = verifyHtlcAddress(btcHtlcAddress, hashLock, serverXOnlyPubKey, userPubKey, btcRefundLocktime, network);
|
|
1297
|
-
if (!addressMatches) {
|
|
1298
|
-
return {
|
|
1299
|
-
success: false,
|
|
1300
|
-
message: `HTLC address mismatch. The computed address does not match the server's address (${btcHtlcAddress}). ` +
|
|
1301
|
-
`This could indicate different script construction. ` +
|
|
1302
|
-
`Parameters: \nhashLock='${hashLock}', \nserverPk='${serverPubKeyFull}', ` +
|
|
1303
|
-
`\nuserPk='${userPubKey}', \nlocktime='${btcRefundLocktime}',` +
|
|
1304
|
-
`\nnetwork='${network}'`,
|
|
1305
|
-
};
|
|
1306
|
-
}
|
|
1307
|
-
// Get the HTLC output info - prefer API data over Esplora lookup
|
|
1308
|
-
const esploraUrl = this.#config.esploraUrl ?? DEFAULT_ESPLORA_URLS[network];
|
|
1309
|
-
if (!esploraUrl) {
|
|
1310
|
-
return {
|
|
1311
|
-
success: false,
|
|
1312
|
-
message: `No Esplora URL configured for network ${network}. Cannot look up funding transaction.`,
|
|
1313
|
-
};
|
|
1314
|
-
}
|
|
1315
|
-
// Try to use funding info from the API response (faster, works before confirmation)
|
|
1316
|
-
const btcFundTxid = swap.btc_fund_txid;
|
|
1317
|
-
const btcFundVout = swap.btc_fund_vout;
|
|
1318
|
-
let htlcOutput = null;
|
|
1319
|
-
if (btcFundTxid && btcFundVout !== undefined) {
|
|
1320
|
-
// We have the funding info from the API, get the amount from the transaction
|
|
1321
|
-
try {
|
|
1322
|
-
const txResponse = await fetch(`${esploraUrl}/tx/${btcFundTxid}`);
|
|
1323
|
-
if (txResponse.ok) {
|
|
1324
|
-
const txData = (await txResponse.json());
|
|
1325
|
-
if (txData.vout?.[btcFundVout]) {
|
|
1326
|
-
htlcOutput = {
|
|
1327
|
-
txid: btcFundTxid,
|
|
1328
|
-
vout: btcFundVout,
|
|
1329
|
-
amount: BigInt(txData.vout[btcFundVout].value),
|
|
1330
|
-
};
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
|
-
catch {
|
|
1335
|
-
// Fall through to Esplora lookup
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
|
-
// Fallback: query Esplora for UTXOs at the address (requires confirmation)
|
|
1339
|
-
if (!htlcOutput) {
|
|
1340
|
-
htlcOutput = await findOutputByAddress(esploraUrl, btcHtlcAddress);
|
|
1341
|
-
}
|
|
1342
|
-
if (!htlcOutput) {
|
|
1343
|
-
return {
|
|
1344
|
-
success: false,
|
|
1345
|
-
message: `Could not find UTXO at HTLC address ${btcHtlcAddress}. ` +
|
|
1346
|
-
`The address may not have been funded yet.`,
|
|
1347
|
-
};
|
|
1348
|
-
}
|
|
1349
|
-
try {
|
|
1350
|
-
// Build the refund transaction
|
|
1351
|
-
const result = buildOnchainRefundTransaction({
|
|
1352
|
-
fundingTxId: htlcOutput.txid,
|
|
1353
|
-
fundingVout: htlcOutput.vout,
|
|
1354
|
-
htlcAmount: htlcOutput.amount,
|
|
1355
|
-
hashLock,
|
|
1356
|
-
serverPubKey: serverXOnlyPubKey,
|
|
1357
|
-
userPubKey,
|
|
1358
|
-
userSecretKey: storedSwap.secretKey,
|
|
1359
|
-
refundLocktime: btcRefundLocktime,
|
|
1360
|
-
destinationAddress: options.destinationAddress,
|
|
1361
|
-
feeRateSatPerVb: options.feeRateSatPerVb ?? 2,
|
|
1362
|
-
network,
|
|
1363
|
-
});
|
|
1364
|
-
// If dry run, just return the transaction without broadcasting
|
|
1365
|
-
if (options.dryRun) {
|
|
1366
|
-
return {
|
|
1367
|
-
success: true,
|
|
1368
|
-
message: "Refund transaction built successfully (dry run - not broadcast).",
|
|
1369
|
-
txHex: result.txHex,
|
|
1370
|
-
txId: result.txId,
|
|
1371
|
-
refundAmount: result.refundAmount,
|
|
1372
|
-
fee: result.fee,
|
|
1373
|
-
broadcast: false,
|
|
1374
|
-
htlcAddress: result.htlcAddress,
|
|
1375
|
-
serverHtlcAddress: btcHtlcAddress,
|
|
1376
|
-
};
|
|
1377
|
-
}
|
|
1378
|
-
// Broadcast the transaction
|
|
1379
|
-
const broadcastEsploraUrl = this.#config.esploraUrl ?? DEFAULT_ESPLORA_URLS[network];
|
|
1380
|
-
if (!broadcastEsploraUrl) {
|
|
1381
|
-
return {
|
|
1382
|
-
success: true,
|
|
1383
|
-
message: "Refund transaction built successfully. No Esplora URL configured for broadcast. " +
|
|
1384
|
-
"Broadcast the txHex manually to the Bitcoin network.",
|
|
1385
|
-
txHex: result.txHex,
|
|
1386
|
-
txId: result.txId,
|
|
1387
|
-
refundAmount: result.refundAmount,
|
|
1388
|
-
fee: result.fee,
|
|
1389
|
-
broadcast: false,
|
|
1390
|
-
htlcAddress: result.htlcAddress,
|
|
1391
|
-
serverHtlcAddress: btcHtlcAddress,
|
|
1392
|
-
};
|
|
1393
|
-
}
|
|
1394
|
-
try {
|
|
1395
|
-
await broadcastTransaction(broadcastEsploraUrl, result.txHex);
|
|
1396
|
-
return {
|
|
1397
|
-
success: true,
|
|
1398
|
-
message: "Refund transaction broadcast successfully!",
|
|
1399
|
-
txHex: result.txHex,
|
|
1400
|
-
txId: result.txId,
|
|
1401
|
-
refundAmount: result.refundAmount,
|
|
1402
|
-
fee: result.fee,
|
|
1403
|
-
broadcast: true,
|
|
1404
|
-
htlcAddress: result.htlcAddress,
|
|
1405
|
-
serverHtlcAddress: btcHtlcAddress,
|
|
1406
|
-
};
|
|
1407
|
-
}
|
|
1408
|
-
catch (broadcastError) {
|
|
1409
|
-
const broadcastMessage = broadcastError instanceof Error
|
|
1410
|
-
? broadcastError.message
|
|
1411
|
-
: String(broadcastError);
|
|
1412
|
-
return {
|
|
1413
|
-
success: true,
|
|
1414
|
-
message: `Transaction built but broadcast failed: ${broadcastMessage}. ` +
|
|
1415
|
-
"You can broadcast the txHex manually.",
|
|
1416
|
-
txHex: result.txHex,
|
|
1417
|
-
txId: result.txId,
|
|
1418
|
-
refundAmount: result.refundAmount,
|
|
1419
|
-
fee: result.fee,
|
|
1420
|
-
broadcast: false,
|
|
1421
|
-
htlcAddress: result.htlcAddress,
|
|
1422
|
-
serverHtlcAddress: btcHtlcAddress,
|
|
1423
|
-
};
|
|
1424
|
-
}
|
|
1425
|
-
}
|
|
1426
|
-
catch (error) {
|
|
1427
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1428
|
-
return {
|
|
1429
|
-
success: false,
|
|
1430
|
-
message: `Failed to build refund transaction: ${message}`,
|
|
1431
|
-
};
|
|
1432
|
-
}
|
|
1433
|
-
}
|
|
1434
|
-
/**
|
|
1435
|
-
* Builds and executes an Arkade (off-chain) VHTLC refund.
|
|
1436
|
-
*
|
|
1437
|
-
* Automatically selects the best refund method based on VTXO status:
|
|
1438
|
-
* - **spendable** VTXOs → offchain spend (submitTx/finalizeTx)
|
|
1439
|
-
* - **recoverable** or **mixed** VTXOs → delegated settlement via backend
|
|
1440
|
-
*
|
|
1441
|
-
* @internal
|
|
1442
|
-
*/
|
|
1443
|
-
async #buildArkadeRefund(id, swap, options) {
|
|
1444
|
-
// Validate options
|
|
1445
|
-
if (!options?.destinationAddress) {
|
|
1446
|
-
return {
|
|
1447
|
-
success: false,
|
|
1448
|
-
message: "Destination address is required for Arkade refunds. " +
|
|
1449
|
-
'Provide it via the options parameter: { destinationAddress: "ark1..." }',
|
|
1450
|
-
};
|
|
1451
|
-
}
|
|
1452
|
-
// Check swap storage is configured
|
|
1453
|
-
if (!this.#swapStorage) {
|
|
1454
|
-
return {
|
|
1455
|
-
success: false,
|
|
1456
|
-
message: "Swap storage is not configured. Cannot retrieve the secret key needed for refund.",
|
|
1457
|
-
};
|
|
1458
|
-
}
|
|
1459
|
-
// Get stored swap data (contains secret key)
|
|
1460
|
-
const storedSwap = await this.#swapStorage.get(id);
|
|
1461
|
-
if (!storedSwap) {
|
|
1462
|
-
return {
|
|
1463
|
-
success: false,
|
|
1464
|
-
message: `Swap ${id} not found in local storage. The secret key is required to sign the refund transaction.`,
|
|
1465
|
-
};
|
|
1466
|
-
}
|
|
1467
|
-
// Ensure we have an arkade_to_evm swap
|
|
1468
|
-
if (swap.direction !== "arkade_to_evm") {
|
|
1469
|
-
return {
|
|
1470
|
-
success: false,
|
|
1471
|
-
message: `Expected arkade_to_evm swap, got ${swap.direction}`,
|
|
1472
|
-
};
|
|
1473
|
-
}
|
|
1474
|
-
const s = swap;
|
|
1475
|
-
// Check refund locktime
|
|
1476
|
-
const now = Math.floor(Date.now() / 1000);
|
|
1477
|
-
if (now < s.vhtlc_refund_locktime) {
|
|
1478
|
-
const remainingSeconds = s.vhtlc_refund_locktime - now;
|
|
1479
|
-
const remainingMinutes = Math.ceil(remainingSeconds / 60);
|
|
1480
|
-
return {
|
|
1481
|
-
success: false,
|
|
1482
|
-
message: `Refund is not yet available. The VHTLC locktime expires in ${remainingMinutes} minutes ` +
|
|
1483
|
-
`(at ${new Date(s.vhtlc_refund_locktime * 1000).toISOString()}).`,
|
|
1484
|
-
};
|
|
1485
|
-
}
|
|
1486
|
-
const fullPubKey = storedSwap.publicKey;
|
|
1487
|
-
const userPubKey = fullPubKey.length === 66 ? fullPubKey.slice(2) : fullPubKey;
|
|
1488
|
-
const hashLock = s.hash_lock.startsWith("0x")
|
|
1489
|
-
? s.hash_lock.slice(2)
|
|
1490
|
-
: s.hash_lock;
|
|
1491
|
-
// Query VTXO status to determine refund method
|
|
1492
|
-
const amounts = await this.amountsForSwap(id);
|
|
1493
|
-
const vtxoStatus = amounts.vtxoStatus;
|
|
1494
|
-
if (vtxoStatus === "not_funded" || vtxoStatus === "spent") {
|
|
1495
|
-
return {
|
|
1496
|
-
success: false,
|
|
1497
|
-
message: vtxoStatus === "not_funded"
|
|
1498
|
-
? "No VTXOs found at the VHTLC address."
|
|
1499
|
-
: "All VTXOs have already been spent.",
|
|
1500
|
-
};
|
|
1501
|
-
}
|
|
1502
|
-
const refundParams = {
|
|
1503
|
-
userSecretKey: storedSwap.secretKey,
|
|
1504
|
-
userPubKey,
|
|
1505
|
-
lendaswapPubKey: s.receiver_pk,
|
|
1506
|
-
arkadeServerPubKey: s.arkade_server_pk,
|
|
1507
|
-
hashLock,
|
|
1508
|
-
vhtlcAddress: s.btc_vhtlc_address,
|
|
1509
|
-
refundLocktime: s.vhtlc_refund_locktime,
|
|
1510
|
-
unilateralClaimDelay: s.unilateral_claim_delay,
|
|
1511
|
-
unilateralRefundDelay: s.unilateral_refund_delay,
|
|
1512
|
-
unilateralRefundWithoutReceiverDelay: s.unilateral_refund_without_receiver_delay,
|
|
1513
|
-
destinationAddress: options.destinationAddress,
|
|
1514
|
-
network: s.network,
|
|
1515
|
-
};
|
|
1516
|
-
if (vtxoStatus === "spendable") {
|
|
1517
|
-
return this.#refundArkadeOffchain(refundParams, options);
|
|
1518
|
-
}
|
|
1519
|
-
// recoverable or mixed → delegate
|
|
1520
|
-
return this.#refundArkadeDelegate(refundParams, options);
|
|
1521
|
-
}
|
|
1522
|
-
/**
|
|
1523
|
-
* Refunds via the offchain submitTx/finalizeTx path (spendable VTXOs only).
|
|
1524
|
-
* @internal
|
|
1525
|
-
*/
|
|
1526
|
-
async #refundArkadeOffchain(params, options) {
|
|
1527
|
-
try {
|
|
1528
|
-
const result = await buildArkadeRefund({
|
|
1529
|
-
...params,
|
|
1530
|
-
arkadeServerUrl: options.arkadeServerUrl ?? this.#config.arkadeServerUrl,
|
|
1531
|
-
});
|
|
1532
|
-
return {
|
|
1533
|
-
success: true,
|
|
1534
|
-
message: "Arkade refund executed successfully via offchain spend!",
|
|
1535
|
-
txId: result.txId,
|
|
1536
|
-
refundAmount: result.refundAmount,
|
|
1537
|
-
broadcast: true,
|
|
1538
|
-
};
|
|
1539
|
-
}
|
|
1540
|
-
catch (error) {
|
|
1541
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1542
|
-
return {
|
|
1543
|
-
success: false,
|
|
1544
|
-
message: `Failed to execute offchain Arkade refund: ${message}`,
|
|
1545
|
-
};
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
/**
|
|
1549
|
-
* Refunds via the delegated settlement path (works for all VTXO states).
|
|
1550
|
-
* @internal
|
|
1551
|
-
*/
|
|
1552
|
-
async #refundArkadeDelegate(params, options) {
|
|
1553
|
-
try {
|
|
1554
|
-
const result = await delegateRefund({
|
|
1555
|
-
...params,
|
|
1556
|
-
lendaswapApiUrl: this.#config.baseUrl,
|
|
1557
|
-
arkadeServerUrl: options.arkadeServerUrl ?? this.#config.arkadeServerUrl,
|
|
1558
|
-
});
|
|
1559
|
-
return {
|
|
1560
|
-
success: true,
|
|
1561
|
-
message: "Arkade refund executed successfully via delegated settlement!",
|
|
1562
|
-
txId: result.commitmentTxid,
|
|
1563
|
-
broadcast: true,
|
|
1564
|
-
};
|
|
1565
|
-
}
|
|
1566
|
-
catch (error) {
|
|
1567
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1568
|
-
return {
|
|
1569
|
-
success: false,
|
|
1570
|
-
message: `Failed to execute delegated Arkade refund: ${message}`,
|
|
1571
|
-
};
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
|
-
/**
|
|
1575
|
-
* Builds refund data for an EVM-to-Arkade swap via the coordinator.
|
|
1576
|
-
*
|
|
1577
|
-
* Calls the server's refund-calldata endpoint which builds coordinator
|
|
1578
|
-
* calldata for `refundAndExecute` (swap WBTC back to source token) or
|
|
1579
|
-
* `refundTo` (return WBTC directly).
|
|
1580
|
-
*
|
|
1581
|
-
* @internal
|
|
1582
|
-
*/
|
|
1583
|
-
async #buildEvmToArkadeRefund(id, swap, mode = "swap-back") {
|
|
1584
|
-
const evmSwap = swap;
|
|
1585
|
-
const timelock = evmSwap.evm_refund_locktime;
|
|
1586
|
-
const now = Math.floor(Date.now() / 1000);
|
|
1587
|
-
const timelockExpired = now >= timelock;
|
|
1588
|
-
// Check if source token is WBTC - if so, use direct HTLCErc20 refund
|
|
1589
|
-
const sourceSymbol = evmSwap.source_token?.symbol?.toLowerCase();
|
|
1590
|
-
const isWbtcSource = sourceSymbol === "wbtc";
|
|
1591
|
-
if (isWbtcSource) {
|
|
1592
|
-
// Direct HTLCErc20 refund - no DEX swap needed
|
|
1593
|
-
const htlcAddress = evmSwap.evm_htlc_address;
|
|
1594
|
-
const hashLock = evmSwap.hash_lock;
|
|
1595
|
-
const refundData = encodeHtlcErc20RefundCallData(htlcAddress, {
|
|
1596
|
-
preimageHash: hashLock,
|
|
1597
|
-
amount: BigInt(evmSwap.source_amount),
|
|
1598
|
-
token: evmSwap.source_token.token_id,
|
|
1599
|
-
claimAddress: evmSwap.server_evm_address, // The server would have been the claimer
|
|
1600
|
-
timelock: timelock,
|
|
1601
|
-
});
|
|
1602
|
-
return {
|
|
1603
|
-
success: true,
|
|
1604
|
-
message: timelockExpired
|
|
1605
|
-
? "EVM refund calldata ready. Submit this transaction with your EVM wallet."
|
|
1606
|
-
: `Timelock has not expired yet. Refund will be available at ${new Date(timelock * 1000).toISOString()}.`,
|
|
1607
|
-
evmRefundData: {
|
|
1608
|
-
to: refundData.to,
|
|
1609
|
-
data: refundData.data,
|
|
1610
|
-
timelockExpired,
|
|
1611
|
-
timelockExpiry: timelock,
|
|
1612
|
-
},
|
|
1613
|
-
};
|
|
1614
|
-
}
|
|
1615
|
-
// Non-WBTC source: fetch coordinator refund calldata from server
|
|
1616
|
-
// - "swap-back": swap WBTC back to original token via DEX (default)
|
|
1617
|
-
// - "direct": return WBTC directly (useful when DEX calldata is stale)
|
|
1618
|
-
const response = await this.#apiClient.GET("/swap/{id}/refund-and-swap-calldata", {
|
|
1619
|
-
params: {
|
|
1620
|
-
path: { id },
|
|
1621
|
-
query: { mode },
|
|
1622
|
-
},
|
|
1623
|
-
});
|
|
1624
|
-
if (response.error) {
|
|
1625
|
-
return {
|
|
1626
|
-
success: false,
|
|
1627
|
-
message: `Failed to fetch refund calldata: ${response.error.error || "Unknown error"}`,
|
|
1628
|
-
};
|
|
1629
|
-
}
|
|
1630
|
-
const { coordinator_address, calldata } = response.data;
|
|
1631
|
-
return {
|
|
1632
|
-
success: true,
|
|
1633
|
-
message: timelockExpired
|
|
1634
|
-
? "EVM refund calldata ready. Submit this transaction with your EVM wallet."
|
|
1635
|
-
: `Timelock has not expired yet. Refund will be available at ${new Date(timelock * 1000).toISOString()}.`,
|
|
1636
|
-
evmRefundData: {
|
|
1637
|
-
to: coordinator_address,
|
|
1638
|
-
data: calldata,
|
|
1639
|
-
timelockExpired,
|
|
1640
|
-
timelockExpiry: timelock,
|
|
1641
|
-
},
|
|
1642
|
-
};
|
|
1643
|
-
}
|
|
1644
|
-
/**
|
|
1645
|
-
* Builds refund data for an EVM-to-Bitcoin swap via the coordinator.
|
|
1646
|
-
* Same pattern as EVM-to-Arkade: uses the coordinator refund-and-swap-calldata endpoint.
|
|
1647
|
-
* @internal
|
|
1648
|
-
*/
|
|
1649
|
-
async #buildEvmToBitcoinRefund(id, swap, mode = "swap-back") {
|
|
1650
|
-
const evmSwap = swap;
|
|
1651
|
-
const timelock = evmSwap.evm_refund_locktime;
|
|
1652
|
-
const now = Math.floor(Date.now() / 1000);
|
|
1653
|
-
const timelockExpired = now >= timelock;
|
|
1654
|
-
// Check if source token is WBTC - if so, use direct HTLCErc20 refund
|
|
1655
|
-
const sourceSymbol = evmSwap.source_token?.symbol?.toLowerCase();
|
|
1656
|
-
const isWbtcSource = sourceSymbol === "wbtc";
|
|
1657
|
-
if (isWbtcSource) {
|
|
1658
|
-
// Direct HTLCErc20 refund - no DEX swap needed
|
|
1659
|
-
const htlcAddress = evmSwap.evm_htlc_address;
|
|
1660
|
-
const hashLock = evmSwap.evm_hash_lock;
|
|
1661
|
-
const refundData = encodeHtlcErc20RefundCallData(htlcAddress, {
|
|
1662
|
-
preimageHash: hashLock,
|
|
1663
|
-
amount: BigInt(evmSwap.source_amount),
|
|
1664
|
-
token: evmSwap.source_token.token_id,
|
|
1665
|
-
claimAddress: evmSwap.server_evm_address, // The server would have been the claimer
|
|
1666
|
-
timelock: timelock,
|
|
1667
|
-
});
|
|
1668
|
-
return {
|
|
1669
|
-
success: true,
|
|
1670
|
-
message: timelockExpired
|
|
1671
|
-
? "EVM refund calldata ready. Submit this transaction with your EVM wallet."
|
|
1672
|
-
: `Timelock has not expired yet. Refund will be available at ${new Date(timelock * 1000).toISOString()}.`,
|
|
1673
|
-
evmRefundData: {
|
|
1674
|
-
to: refundData.to,
|
|
1675
|
-
data: refundData.data,
|
|
1676
|
-
timelockExpired,
|
|
1677
|
-
timelockExpiry: timelock,
|
|
1678
|
-
},
|
|
1679
|
-
};
|
|
1680
|
-
}
|
|
1681
|
-
// Non-WBTC source: use coordinator refund
|
|
1682
|
-
// - "swap-back": swap WBTC back to original token via DEX (default)
|
|
1683
|
-
// - "direct": return WBTC directly (useful when DEX calldata is stale)
|
|
1684
|
-
const response = await this.#apiClient.GET("/swap/{id}/refund-and-swap-calldata", {
|
|
1685
|
-
params: {
|
|
1686
|
-
path: { id },
|
|
1687
|
-
query: { mode },
|
|
1688
|
-
},
|
|
1689
|
-
});
|
|
1690
|
-
if (response.error) {
|
|
1691
|
-
return {
|
|
1692
|
-
success: false,
|
|
1693
|
-
message: `Failed to fetch refund calldata: ${response.error.error || "Unknown error"}`,
|
|
1694
|
-
};
|
|
1695
|
-
}
|
|
1696
|
-
const { coordinator_address, calldata } = response.data;
|
|
1697
|
-
return {
|
|
1698
|
-
success: true,
|
|
1699
|
-
message: timelockExpired
|
|
1700
|
-
? "EVM refund calldata ready. Submit this transaction with your EVM wallet."
|
|
1701
|
-
: `Timelock has not expired yet. Refund will be available at ${new Date(timelock * 1000).toISOString()}.`,
|
|
1702
|
-
evmRefundData: {
|
|
1703
|
-
to: coordinator_address,
|
|
1704
|
-
data: calldata,
|
|
1705
|
-
timelockExpired,
|
|
1706
|
-
timelockExpiry: timelock,
|
|
1707
|
-
},
|
|
1708
|
-
};
|
|
1709
|
-
}
|
|
1710
|
-
/**
|
|
1711
|
-
* Builds refund data for an EVM-to-Lightning swap via the coordinator.
|
|
1712
|
-
*
|
|
1713
|
-
* Like EVM-to-Arkade, the coordinator atomically swapped the source token to WBTC
|
|
1714
|
-
* before locking in the HTLC. For refunds:
|
|
1715
|
-
* - If source was WBTC: direct HTLCErc20 refund
|
|
1716
|
-
* - Otherwise: use coordinator refund endpoint (swap-back or direct mode)
|
|
1717
|
-
*
|
|
1718
|
-
* @internal
|
|
1719
|
-
*/
|
|
1720
|
-
async #buildEvmToLightningRefund(id, swap, mode = "swap-back") {
|
|
1721
|
-
const evmSwap = swap;
|
|
1722
|
-
const timelock = evmSwap.evm_refund_locktime;
|
|
1723
|
-
const now = Math.floor(Date.now() / 1000);
|
|
1724
|
-
const timelockExpired = now >= timelock;
|
|
1725
|
-
// Check if source token is WBTC - if so, use direct HTLCErc20 refund
|
|
1726
|
-
const sourceSymbol = evmSwap.source_token?.symbol?.toLowerCase();
|
|
1727
|
-
const isWbtcSource = sourceSymbol === "wbtc";
|
|
1728
|
-
if (isWbtcSource) {
|
|
1729
|
-
// Direct HTLCErc20 refund - no DEX swap needed
|
|
1730
|
-
const htlcAddress = evmSwap.evm_htlc_address;
|
|
1731
|
-
const hashLock = evmSwap.hash_lock;
|
|
1732
|
-
const refundData = encodeHtlcErc20RefundCallData(htlcAddress, {
|
|
1733
|
-
preimageHash: hashLock,
|
|
1734
|
-
amount: BigInt(evmSwap.source_amount),
|
|
1735
|
-
token: evmSwap.source_token.token_id,
|
|
1736
|
-
claimAddress: evmSwap.server_evm_address,
|
|
1737
|
-
timelock: timelock,
|
|
1738
|
-
});
|
|
1739
|
-
return {
|
|
1740
|
-
success: true,
|
|
1741
|
-
message: timelockExpired
|
|
1742
|
-
? "EVM refund calldata ready. Submit this transaction with your EVM wallet."
|
|
1743
|
-
: `Timelock has not expired yet. Refund will be available at ${new Date(timelock * 1000).toISOString()}.`,
|
|
1744
|
-
evmRefundData: {
|
|
1745
|
-
to: refundData.to,
|
|
1746
|
-
data: refundData.data,
|
|
1747
|
-
timelockExpired,
|
|
1748
|
-
timelockExpiry: timelock,
|
|
1749
|
-
},
|
|
1750
|
-
};
|
|
1751
|
-
}
|
|
1752
|
-
// Non-WBTC source: fetch coordinator refund calldata from server
|
|
1753
|
-
// - "swap-back": swap WBTC back to original token via DEX (default)
|
|
1754
|
-
// - "direct": return WBTC directly (useful when DEX calldata is stale)
|
|
1755
|
-
const response = await this.#apiClient.GET("/swap/{id}/refund-and-swap-calldata", {
|
|
1756
|
-
params: {
|
|
1757
|
-
path: { id },
|
|
1758
|
-
query: { mode },
|
|
1759
|
-
},
|
|
1760
|
-
});
|
|
1761
|
-
if (response.error) {
|
|
1762
|
-
return {
|
|
1763
|
-
success: false,
|
|
1764
|
-
message: `Failed to fetch refund calldata: ${response.error.error || "Unknown error"}`,
|
|
1765
|
-
};
|
|
1766
|
-
}
|
|
1767
|
-
const { coordinator_address, calldata } = response.data;
|
|
1768
|
-
return {
|
|
1769
|
-
success: true,
|
|
1770
|
-
message: timelockExpired
|
|
1771
|
-
? "EVM refund calldata ready. Submit this transaction with your EVM wallet."
|
|
1772
|
-
: `Timelock has not expired yet. Refund will be available at ${new Date(timelock * 1000).toISOString()}.`,
|
|
1773
|
-
evmRefundData: {
|
|
1774
|
-
to: coordinator_address,
|
|
1775
|
-
data: calldata,
|
|
1776
|
-
timelockExpired,
|
|
1777
|
-
timelockExpiry: timelock,
|
|
1778
|
-
},
|
|
1779
|
-
};
|
|
1780
|
-
}
|
|
1781
|
-
// =========================================================================
|
|
1782
|
-
// Swap Creation - BTC to EVM
|
|
1783
|
-
// =========================================================================
|
|
1784
|
-
/**
|
|
1785
|
-
* Gets the context object for swap creation functions.
|
|
1786
|
-
* @internal
|
|
1787
|
-
*/
|
|
1788
|
-
#getCreateContext() {
|
|
1789
|
-
return {
|
|
1790
|
-
apiClient: this.#apiClient,
|
|
1791
|
-
baseUrl: this.#config.baseUrl,
|
|
1792
|
-
deriveSwapParams: () => this.deriveSwapParams(),
|
|
1793
|
-
storeSwap: (swapId, swapParams, response) => this.#storeSwap(swapId, swapParams, response),
|
|
1794
|
-
};
|
|
1795
|
-
}
|
|
1796
|
-
/**
|
|
1797
|
-
* Stores a swap in the configured swap storage.
|
|
1798
|
-
* @internal
|
|
1799
|
-
*/
|
|
1800
|
-
async #storeSwap(swapId, swapParams, response, targetAddress) {
|
|
1801
|
-
if (!this.#swapStorage)
|
|
1802
|
-
return;
|
|
1803
|
-
const storedSwap = {
|
|
1804
|
-
version: SWAP_STORAGE_VERSION,
|
|
1805
|
-
swapId,
|
|
1806
|
-
keyIndex: swapParams.keyIndex,
|
|
1807
|
-
response: response,
|
|
1808
|
-
publicKey: bytesToHex(swapParams.publicKey),
|
|
1809
|
-
preimage: bytesToHex(swapParams.preimage),
|
|
1810
|
-
preimageHash: bytesToHex(swapParams.preimageHash),
|
|
1811
|
-
secretKey: bytesToHex(swapParams.secretKey),
|
|
1812
|
-
storedAt: Date.now(),
|
|
1813
|
-
updatedAt: Date.now(),
|
|
1814
|
-
targetAddress,
|
|
1815
|
-
};
|
|
1816
|
-
await this.#swapStorage.store(storedSwap);
|
|
1817
|
-
}
|
|
1818
|
-
/**
|
|
1819
|
-
* Creates a swap by routing to the correct direction-specific method
|
|
1820
|
-
* based on `sourceAsset.chain` and `targetAsset.chain`.
|
|
1821
|
-
*
|
|
1822
|
-
* Supported directions:
|
|
1823
|
-
* - Arkade → EVM
|
|
1824
|
-
* - Lightning → EVM
|
|
1825
|
-
* - Bitcoin (on-chain) → EVM
|
|
1826
|
-
* - Bitcoin (on-chain) → Arkade
|
|
1827
|
-
* - EVM → Arkade
|
|
1828
|
-
* - EVM → Bitcoin (on-chain)
|
|
1829
|
-
* - EVM → Lightning
|
|
1830
|
-
*
|
|
1831
|
-
* @param options - The swap options including source/target assets, amounts, and addresses.
|
|
1832
|
-
* @returns The swap result (response + swapParams).
|
|
1833
|
-
* @throws Error if the swap direction is unsupported or required fields are missing.
|
|
1834
|
-
*/
|
|
1835
|
-
async createSwap(options) {
|
|
1836
|
-
const { sourceAsset, targetAsset } = options;
|
|
1837
|
-
const sourceChain = sourceAsset.chain;
|
|
1838
|
-
const targetChain = targetAsset.chain;
|
|
1839
|
-
// Arkade → EVM
|
|
1840
|
-
if (isArkade(sourceAsset) && isEvmToken(targetChain)) {
|
|
1841
|
-
return this.createArkadeToEvmSwapGeneric({
|
|
1842
|
-
targetAddress: options.targetAddress,
|
|
1843
|
-
tokenAddress: targetAsset.token_id,
|
|
1844
|
-
evmChainId: Number(targetChain),
|
|
1845
|
-
sourceAmount: options.sourceAmount
|
|
1846
|
-
? BigInt(options.sourceAmount)
|
|
1847
|
-
: undefined,
|
|
1848
|
-
targetAmount: options.targetAmount
|
|
1849
|
-
? BigInt(options.targetAmount)
|
|
1850
|
-
: undefined,
|
|
1851
|
-
referralCode: options.referralCode,
|
|
1852
|
-
});
|
|
1853
|
-
}
|
|
1854
|
-
// Lightning → EVM
|
|
1855
|
-
if (isLightning(sourceAsset) && isEvmToken(targetChain)) {
|
|
1856
|
-
return this.createLightningToEvmSwapGeneric({
|
|
1857
|
-
targetAddress: options.targetAddress,
|
|
1858
|
-
tokenAddress: targetAsset.token_id,
|
|
1859
|
-
evmChainId: Number(targetChain),
|
|
1860
|
-
amountIn: options.sourceAmount,
|
|
1861
|
-
amountOut: options.targetAmount,
|
|
1862
|
-
referralCode: options.referralCode,
|
|
1863
|
-
});
|
|
1864
|
-
}
|
|
1865
|
-
// Bitcoin (on-chain) → EVM
|
|
1866
|
-
if (isBtcOnchain(sourceAsset) && isEvmToken(targetChain)) {
|
|
1867
|
-
return this.createBitcoinToEvmSwap({
|
|
1868
|
-
targetAddress: options.targetAddress,
|
|
1869
|
-
tokenAddress: targetAsset.token_id,
|
|
1870
|
-
evmChainId: Number(targetChain),
|
|
1871
|
-
sourceAmount: options.sourceAmount,
|
|
1872
|
-
targetAmount: options.targetAmount,
|
|
1873
|
-
referralCode: options.referralCode,
|
|
1874
|
-
});
|
|
1875
|
-
}
|
|
1876
|
-
// Bitcoin (on-chain) → Arkade
|
|
1877
|
-
if (isBtcOnchain(sourceAsset) && isArkade(targetAsset)) {
|
|
1878
|
-
if (options.targetAmount == null) {
|
|
1879
|
-
throw new Error("targetAmount (sats to receive on Arkade) is required for Bitcoin → Arkade swaps");
|
|
1880
|
-
}
|
|
1881
|
-
return this.createBitcoinToArkadeSwap({
|
|
1882
|
-
satsReceive: options.targetAmount,
|
|
1883
|
-
targetAddress: options.targetAddress,
|
|
1884
|
-
referralCode: options.referralCode,
|
|
1885
|
-
});
|
|
1886
|
-
}
|
|
1887
|
-
// EVM → Arkade
|
|
1888
|
-
if (isEvmToken(sourceChain) && isArkade(targetAsset)) {
|
|
1889
|
-
if (!options.userAddress) {
|
|
1890
|
-
throw new Error("userAddress is required for EVM → Arkade swaps");
|
|
1891
|
-
}
|
|
1892
|
-
return this.createEvmToArkadeSwapGeneric({
|
|
1893
|
-
targetAddress: options.targetAddress,
|
|
1894
|
-
tokenAddress: sourceAsset.token_id,
|
|
1895
|
-
evmChainId: Number(sourceChain),
|
|
1896
|
-
userAddress: options.userAddress,
|
|
1897
|
-
sourceAmount: options.sourceAmount
|
|
1898
|
-
? BigInt(options.sourceAmount)
|
|
1899
|
-
: undefined,
|
|
1900
|
-
targetAmount: options.targetAmount,
|
|
1901
|
-
referralCode: options.referralCode,
|
|
1902
|
-
});
|
|
1903
|
-
}
|
|
1904
|
-
// EVM → Bitcoin (on-chain)
|
|
1905
|
-
if (isEvmToken(sourceChain) && isBtcOnchain(targetAsset)) {
|
|
1906
|
-
if (!options.userAddress) {
|
|
1907
|
-
throw new Error("userAddress is required for EVM → Bitcoin swaps");
|
|
1908
|
-
}
|
|
1909
|
-
return this.createEvmToBitcoinSwap({
|
|
1910
|
-
tokenAddress: sourceAsset.token_id,
|
|
1911
|
-
evmChainId: Number(sourceChain),
|
|
1912
|
-
userAddress: options.userAddress,
|
|
1913
|
-
targetAddress: options.targetAddress,
|
|
1914
|
-
sourceAmount: options.sourceAmount
|
|
1915
|
-
? BigInt(options.sourceAmount)
|
|
1916
|
-
: undefined,
|
|
1917
|
-
targetAmount: options.targetAmount,
|
|
1918
|
-
referralCode: options.referralCode,
|
|
1919
|
-
});
|
|
1920
|
-
}
|
|
1921
|
-
// EVM → Lightning
|
|
1922
|
-
if (isEvmToken(sourceChain) && isLightning(targetAsset)) {
|
|
1923
|
-
if (!options.userAddress) {
|
|
1924
|
-
throw new Error("userAddress is required for EVM → Lightning swaps");
|
|
1925
|
-
}
|
|
1926
|
-
return this.createEvmToLightningSwapGeneric({
|
|
1927
|
-
lightningInvoice: options.targetAddress,
|
|
1928
|
-
evmChainId: Number(sourceChain),
|
|
1929
|
-
tokenAddress: sourceAsset.token_id,
|
|
1930
|
-
userAddress: options.userAddress,
|
|
1931
|
-
referralCode: options.referralCode,
|
|
1932
|
-
});
|
|
1933
|
-
}
|
|
1934
|
-
throw new Error(`Unsupported swap direction: ${sourceChain} → ${targetChain}`);
|
|
1935
|
-
}
|
|
1936
|
-
/**
|
|
1937
|
-
* Creates a new Arkade-to-EVM swap via the generic chain-agnostic endpoint.
|
|
1938
|
-
*
|
|
1939
|
-
* Uses the `/swap/arkade/evm` endpoint which supports any ERC-20 token
|
|
1940
|
-
* reachable through 1inch aggregation. Returns coordinator address and
|
|
1941
|
-
* optional 1inch calldata for the redeem-and-swap flow.
|
|
1942
|
-
*
|
|
1943
|
-
* @param options - The swap options.
|
|
1944
|
-
* @returns The swap response and parameters for storage.
|
|
1945
|
-
* @throws Error if the swap creation fails.
|
|
1946
|
-
*
|
|
1947
|
-
* @example
|
|
1948
|
-
* ```ts
|
|
1949
|
-
* const result = await client.createArkadeToEvmSwapGeneric({
|
|
1950
|
-
* targetAddress: "0x1234...",
|
|
1951
|
-
* tokenAddress: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", // USDC on Polygon
|
|
1952
|
-
* evmChainId: 137,
|
|
1953
|
-
* sourceAmount: 100000, // 100k sats
|
|
1954
|
-
* });
|
|
1955
|
-
* console.log("Fund:", result.response.btc_vhtlc_address);
|
|
1956
|
-
* console.log("Coordinator:", result.response.evm_coordinator_address);
|
|
1957
|
-
* ```
|
|
1958
|
-
*/
|
|
1959
|
-
async createArkadeToEvmSwapGeneric(options) {
|
|
1960
|
-
return createArkadeToEvmSwapGeneric(options, this.#getCreateContext());
|
|
1961
|
-
}
|
|
1962
|
-
/**
|
|
1963
|
-
* Creates a new Lightning to EVM swap using the generic chain-agnostic endpoint.
|
|
1964
|
-
*
|
|
1965
|
-
* @param options - The swap options including evmChainId and tokenAddress.
|
|
1966
|
-
* @returns The swap response and parameters for storage.
|
|
1967
|
-
*/
|
|
1968
|
-
async createLightningToEvmSwapGeneric(options) {
|
|
1969
|
-
return createLightningToEvmSwapGeneric(options, this.#getCreateContext());
|
|
1970
|
-
}
|
|
1971
|
-
/**
|
|
1972
|
-
* Creates a new Bitcoin (on-chain) to EVM swap.
|
|
1973
|
-
*
|
|
1974
|
-
* Automatically derives swap parameters and increments the key index.
|
|
1975
|
-
*
|
|
1976
|
-
* @param options - The swap options.
|
|
1977
|
-
* @returns The swap response and parameters for storage.
|
|
1978
|
-
* @throws Error if the swap creation fails.
|
|
1979
|
-
*
|
|
1980
|
-
* @example
|
|
1981
|
-
* ```ts
|
|
1982
|
-
* const result = await client.createBitcoinToEvmSwap({
|
|
1983
|
-
* targetAddress: "0x1234...",
|
|
1984
|
-
* tokenAddress: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", // USDC on Polygon
|
|
1985
|
-
* evmChainId: 137,
|
|
1986
|
-
* sourceAmount: 100000, // 100k sats
|
|
1987
|
-
* });
|
|
1988
|
-
* console.log("Send BTC to:", result.response.btc_htlc_address);
|
|
1989
|
-
* ```
|
|
1990
|
-
*/
|
|
1991
|
-
async createBitcoinToEvmSwap(options) {
|
|
1992
|
-
return createBitcoinToEvmSwap(options, this.#getCreateContext());
|
|
1993
|
-
}
|
|
1994
|
-
// =========================================================================
|
|
1995
|
-
// Swap Creation - Bitcoin (on-chain) to Arkade
|
|
1996
|
-
// =========================================================================
|
|
1997
|
-
/**
|
|
1998
|
-
* Creates a new Bitcoin (on-chain) to Arkade swap.
|
|
1999
|
-
*
|
|
2000
|
-
* The user sends on-chain BTC to a Taproot HTLC address and receives
|
|
2001
|
-
* Arkade VTXOs after the server funds the Arkade VHTLC.
|
|
2002
|
-
*
|
|
2003
|
-
* Automatically derives swap parameters and increments the key index.
|
|
2004
|
-
*
|
|
2005
|
-
* @param options - The swap options.
|
|
2006
|
-
* @returns The swap response and parameters for storage.
|
|
2007
|
-
* @throws Error if the swap creation fails.
|
|
2008
|
-
*
|
|
2009
|
-
* @example
|
|
2010
|
-
* ```ts
|
|
2011
|
-
* const result = await client.createBitcoinToArkadeSwap({
|
|
2012
|
-
* satsReceive: 100000, // 100k sats to receive on Arkade
|
|
2013
|
-
* targetAddress: "ark1q...", // Arkade address
|
|
2014
|
-
* });
|
|
2015
|
-
* console.log("Send BTC to:", result.response.btc_htlc_address);
|
|
2016
|
-
* console.log("Amount to send:", result.response.source_amount, "sats");
|
|
2017
|
-
* ```
|
|
2018
|
-
*/
|
|
2019
|
-
async createBitcoinToArkadeSwap(options) {
|
|
2020
|
-
return createBitcoinToArkadeSwap(options, this.#getCreateContext());
|
|
2021
|
-
}
|
|
2022
|
-
// =========================================================================
|
|
2023
|
-
// Swap Creation - EVM to Arkade
|
|
2024
|
-
// =========================================================================
|
|
2025
|
-
/**
|
|
2026
|
-
* Creates a new EVM-to-Arkade swap via the generic endpoint.
|
|
2027
|
-
*
|
|
2028
|
-
* Uses the chain-agnostic `/swap/evm/arkade` endpoint which supports any
|
|
2029
|
-
* ERC-20 token reachable through 1inch aggregation.
|
|
2030
|
-
*
|
|
2031
|
-
* @param options - The swap options.
|
|
2032
|
-
* @returns The swap response and parameters for storage.
|
|
2033
|
-
* @throws Error if the swap creation fails.
|
|
2034
|
-
*
|
|
2035
|
-
* @example
|
|
2036
|
-
* ```ts
|
|
2037
|
-
* const result = await client.createEvmToArkadeSwapGeneric({
|
|
2038
|
-
* targetAddress: "ark1q...",
|
|
2039
|
-
* tokenAddress: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", // USDC on Polygon
|
|
2040
|
-
* evmChainId: 137,
|
|
2041
|
-
* userAddress: "0x1234...",
|
|
2042
|
-
* sourceAmount: 100000000, // 100 USDC (6 decimals)
|
|
2043
|
-
* });
|
|
2044
|
-
* console.log("HTLC:", result.response.evm_htlc_address);
|
|
2045
|
-
* ```
|
|
2046
|
-
*/
|
|
2047
|
-
async createEvmToArkadeSwapGeneric(options) {
|
|
2048
|
-
return createEvmToArkadeSwapGeneric(options, this.#getCreateContext());
|
|
2049
|
-
}
|
|
2050
|
-
/**
|
|
2051
|
-
* Creates a new EVM-to-Bitcoin (on-chain) swap.
|
|
2052
|
-
*
|
|
2053
|
-
* Uses the chain-agnostic `/swap/evm/bitcoin` endpoint which supports any
|
|
2054
|
-
* ERC-20 token reachable through 1inch aggregation. The user locks tokens
|
|
2055
|
-
* in an EVM HTLC and receives BTC to an on-chain Taproot HTLC.
|
|
2056
|
-
*
|
|
2057
|
-
* @param options - The swap options.
|
|
2058
|
-
* @returns The swap response and parameters for storage.
|
|
2059
|
-
* @throws Error if the swap creation fails.
|
|
2060
|
-
*
|
|
2061
|
-
* @example
|
|
2062
|
-
* ```ts
|
|
2063
|
-
* const result = await client.createEvmToBitcoinSwap({
|
|
2064
|
-
* tokenAddress: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", // USDC on Polygon
|
|
2065
|
-
* evmChainId: 137,
|
|
2066
|
-
* userAddress: "0x1234...",
|
|
2067
|
-
* sourceAmount: 100000000n, // 100 USDC (6 decimals)
|
|
2068
|
-
* });
|
|
2069
|
-
* console.log("EVM HTLC:", result.response.evm_htlc_address);
|
|
2070
|
-
* console.log("BTC HTLC:", result.response.btc_htlc_address);
|
|
2071
|
-
* ```
|
|
2072
|
-
*/
|
|
2073
|
-
async createEvmToBitcoinSwap(options) {
|
|
2074
|
-
return createEvmToBitcoinSwap(options, this.#getCreateContext());
|
|
2075
|
-
}
|
|
2076
|
-
/**
|
|
2077
|
-
* Creates a new EVM to Lightning swap using the chain-agnostic generic endpoint.
|
|
2078
|
-
*
|
|
2079
|
-
* This allows users to swap any ERC-20 token from any supported EVM chain
|
|
2080
|
-
* to pay a Lightning invoice.
|
|
2081
|
-
*
|
|
2082
|
-
* @param options - The swap options including Lightning invoice, chain ID, and token address.
|
|
2083
|
-
* @returns The swap response and parameters for storage.
|
|
2084
|
-
* @throws Error if the swap creation fails.
|
|
2085
|
-
*
|
|
2086
|
-
* @example
|
|
2087
|
-
* ```ts
|
|
2088
|
-
* const result = await client.createEvmToLightningSwapGeneric({
|
|
2089
|
-
* lightningInvoice: "lnbc...",
|
|
2090
|
-
* evmChainId: 137, // Polygon
|
|
2091
|
-
* tokenAddress: "0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6", // WBTC
|
|
2092
|
-
* userAddress: "0x1234...",
|
|
2093
|
-
* });
|
|
2094
|
-
* console.log("HTLC contract:", result.response.evm_htlc_address);
|
|
2095
|
-
* console.log("Swap ID:", result.response.id);
|
|
2096
|
-
* ```
|
|
2097
|
-
*/
|
|
2098
|
-
async createEvmToLightningSwapGeneric(options) {
|
|
2099
|
-
return createEvmToLightningSwapGeneric(options, this.#getCreateContext());
|
|
2100
|
-
}
|
|
2101
|
-
// =========================================================================
|
|
2102
|
-
// Coordinator Funding (EVM-to-BTC via DEX + HTLC)
|
|
2103
|
-
// =========================================================================
|
|
2104
|
-
/**
|
|
2105
|
-
* Gets call data to fund an EVM-to-BTC swap via the HTLCCoordinator.
|
|
2106
|
-
*
|
|
2107
|
-
* The coordinator atomically swaps source tokens (e.g. USDC) to WBTC via DEX
|
|
2108
|
-
* and locks the WBTC into an HTLC in a single transaction.
|
|
2109
|
-
*
|
|
2110
|
-
* Fetches the coordinator calldata from the server, which builds the 1inch
|
|
2111
|
-
* swap calldata and computes the refundCallsHash.
|
|
2112
|
-
*
|
|
2113
|
-
* @param swapId - The UUID of the swap.
|
|
2114
|
-
* @param approveMax - If true, approves max uint256. If false, approves exact amount. Default: true.
|
|
2115
|
-
* @returns The approve and executeAndCreate call data.
|
|
2116
|
-
*
|
|
2117
|
-
* @example
|
|
2118
|
-
* ```ts
|
|
2119
|
-
* const swap = await client.createEvmToArkadeSwap({...});
|
|
2120
|
-
* const funding = await client.getCoordinatorFundingCallData(swap.response.id);
|
|
2121
|
-
*
|
|
2122
|
-
* // Step 1: Approve source token to coordinator
|
|
2123
|
-
* await wallet.sendTransaction({ to: funding.approve.to, data: funding.approve.data });
|
|
2124
|
-
*
|
|
2125
|
-
* // Step 2: Execute swap + create HTLC
|
|
2126
|
-
* await wallet.sendTransaction({ to: funding.executeAndCreate.to, data: funding.executeAndCreate.data });
|
|
2127
|
-
* ```
|
|
2128
|
-
*/
|
|
2129
|
-
async getCoordinatorFundingCallData(swapId, approveMax = true) {
|
|
2130
|
-
const swap = await this.getSwap(swapId);
|
|
2131
|
-
if (swap.direction !== "evm_to_arkade" &&
|
|
2132
|
-
swap.direction !== "evm_to_bitcoin" &&
|
|
2133
|
-
swap.direction !== "evm_to_lightning") {
|
|
2134
|
-
throw new Error(`Expected evm_to_arkade/evm_to_bitcoin/evm_to_lightning swap, got ${swap.direction}. Coordinator fund call data method is for EVM-sourced swaps via coordinator.`);
|
|
2135
|
-
}
|
|
2136
|
-
// Get source amount based on swap direction
|
|
2137
|
-
// All EVM-sourced swaps: source_amount is already in smallest units (integer)
|
|
2138
|
-
const evmSwap = swap;
|
|
2139
|
-
const exactAmount = BigInt(evmSwap.source_amount);
|
|
2140
|
-
// Fetch coordinator funding calldata from server
|
|
2141
|
-
const baseUrl = this.#config.baseUrl.replace(/\/$/, "");
|
|
2142
|
-
const url = `${baseUrl}/swap/${swapId}/swap-and-lock-calldata`;
|
|
2143
|
-
const headers = {};
|
|
2144
|
-
if (this.#config.apiKey) {
|
|
2145
|
-
headers["X-API-Key"] = this.#config.apiKey;
|
|
2146
|
-
}
|
|
2147
|
-
const resp = await fetch(url, { headers });
|
|
2148
|
-
if (!resp.ok) {
|
|
2149
|
-
const body = await resp.text();
|
|
2150
|
-
throw new Error(`Failed to get coordinator funding calldata: ${resp.status} ${body}`);
|
|
2151
|
-
}
|
|
2152
|
-
const serverData = (await resp.json());
|
|
2153
|
-
// Build approve call data: approve source token to coordinator
|
|
2154
|
-
const maxUint256 = BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
|
|
2155
|
-
const approveAmount = approveMax ? maxUint256 : exactAmount;
|
|
2156
|
-
const approve = encodeApproveCallData(serverData.source_token_address, serverData.coordinator_address, approveAmount);
|
|
2157
|
-
return {
|
|
2158
|
-
approve: {
|
|
2159
|
-
to: approve.to,
|
|
2160
|
-
data: approve.data,
|
|
2161
|
-
},
|
|
2162
|
-
executeAndCreate: {
|
|
2163
|
-
to: serverData.coordinator_address,
|
|
2164
|
-
data: serverData.execute_and_create_calldata,
|
|
2165
|
-
},
|
|
2166
|
-
};
|
|
2167
|
-
}
|
|
2168
|
-
}
|
|
2169
|
-
//# sourceMappingURL=client.js.map
|