@atomiqlabs/lp-lib 17.3.1 → 17.4.0
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/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/swaps/spv_vault_swap/SpvVaultSwap.d.ts +2 -0
- package/dist/swaps/spv_vault_swap/SpvVaultSwap.js +5 -1
- package/dist/swaps/spv_vault_swap/SpvVaultSwapHandler.d.ts +5 -1
- package/dist/swaps/spv_vault_swap/SpvVaultSwapHandler.js +74 -10
- package/dist/swaps/spv_vault_swap/StickyAddress.d.ts +6 -0
- package/dist/swaps/spv_vault_swap/StickyAddress.js +19 -0
- package/dist/wallets/IBitcoinWallet.d.ts +5 -0
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/swaps/spv_vault_swap/SpvVaultSwap.ts +8 -1
- package/src/swaps/spv_vault_swap/SpvVaultSwapHandler.ts +87 -10
- package/src/swaps/spv_vault_swap/StickyAddress.ts +21 -0
- package/src/wallets/IBitcoinWallet.ts +5 -0
package/dist/index.d.ts
CHANGED
|
@@ -38,5 +38,6 @@ export * from "./wallets/ILightningWallet";
|
|
|
38
38
|
export * from "./wallets/ISpvVaultSigner";
|
|
39
39
|
export * from "./swaps/spv_vault_swap/SpvVaults";
|
|
40
40
|
export * from "./swaps/spv_vault_swap/SpvVault";
|
|
41
|
+
export * from "./swaps/spv_vault_swap/StickyAddress";
|
|
41
42
|
export * from "./swaps/spv_vault_swap/SpvVaultSwap";
|
|
42
43
|
export * from "./swaps/spv_vault_swap/SpvVaultSwapHandler";
|
package/dist/index.js
CHANGED
|
@@ -54,5 +54,6 @@ __exportStar(require("./wallets/ILightningWallet"), exports);
|
|
|
54
54
|
__exportStar(require("./wallets/ISpvVaultSigner"), exports);
|
|
55
55
|
__exportStar(require("./swaps/spv_vault_swap/SpvVaults"), exports);
|
|
56
56
|
__exportStar(require("./swaps/spv_vault_swap/SpvVault"), exports);
|
|
57
|
+
__exportStar(require("./swaps/spv_vault_swap/StickyAddress"), exports);
|
|
57
58
|
__exportStar(require("./swaps/spv_vault_swap/SpvVaultSwap"), exports);
|
|
58
59
|
__exportStar(require("./swaps/spv_vault_swap/SpvVaultSwapHandler"), exports);
|
|
@@ -38,6 +38,8 @@ export declare class SpvVaultSwap extends SwapHandlerSwap<SpvVaultSwapState> {
|
|
|
38
38
|
readonly token: string;
|
|
39
39
|
readonly gasToken: string;
|
|
40
40
|
btcTxId: string;
|
|
41
|
+
saveStickyAddress?: boolean;
|
|
42
|
+
hasStickyAddress?: boolean;
|
|
41
43
|
constructor(chainIdentifier: string, quoteId: string, expiry: number, vault: SpvVault, vaultUtxo: string, btcAddress: string, btcFeeRate: number, recipient: string, amountBtc: bigint, amountToken: bigint, amountGasToken: bigint, swapFee: bigint, swapFeeInToken: bigint, gasSwapFee: bigint, gasSwapFeeInToken: bigint, callerFeeShare: bigint, frontingFeeShare: bigint, executionFeeShare: bigint, token: string, gasToken: string);
|
|
42
44
|
constructor(data: any);
|
|
43
45
|
serialize(): any;
|
|
@@ -75,6 +75,8 @@ class SpvVaultSwap extends SwapHandlerSwap_1.SwapHandlerSwap {
|
|
|
75
75
|
this.tokenMultiplier = (0, Utils_1.deserializeBN)(chainIdentifierOrObj.tokenMultiplier);
|
|
76
76
|
this.gasTokenMultiplier = (0, Utils_1.deserializeBN)(chainIdentifierOrObj.gasTokenMultiplier);
|
|
77
77
|
this.btcTxId = chainIdentifierOrObj.btcTxId;
|
|
78
|
+
this.hasStickyAddress = chainIdentifierOrObj.hasStickyAddress;
|
|
79
|
+
this.saveStickyAddress = chainIdentifierOrObj.saveStickyAddress;
|
|
78
80
|
}
|
|
79
81
|
this.type = SwapHandler_1.SwapHandlerType.FROM_BTC_SPV;
|
|
80
82
|
}
|
|
@@ -106,7 +108,9 @@ class SpvVaultSwap extends SwapHandlerSwap_1.SwapHandlerSwap {
|
|
|
106
108
|
gasToken: this.gasToken,
|
|
107
109
|
tokenMultiplier: (0, Utils_1.serializeBN)(this.tokenMultiplier),
|
|
108
110
|
gasTokenMultiplier: (0, Utils_1.serializeBN)(this.gasTokenMultiplier),
|
|
109
|
-
btcTxId: this.btcTxId
|
|
111
|
+
btcTxId: this.btcTxId,
|
|
112
|
+
hasStickyAddress: this.hasStickyAddress,
|
|
113
|
+
saveStickyAddress: this.saveStickyAddress
|
|
110
114
|
};
|
|
111
115
|
}
|
|
112
116
|
getIdentifierHash() {
|
|
@@ -9,6 +9,7 @@ import { ISpvVaultSigner } from "../../wallets/ISpvVaultSigner";
|
|
|
9
9
|
import { SpvVault } from "./SpvVault";
|
|
10
10
|
import { FromBtcAmountAssertions } from "../assertions/FromBtcAmountAssertions";
|
|
11
11
|
import { SpvVaults } from "./SpvVaults";
|
|
12
|
+
import { StickyAddress } from "./StickyAddress";
|
|
12
13
|
export type SpvVaultSwapHandlerConfig = SwapBaseConfig & {
|
|
13
14
|
vaultsCheckInterval: number;
|
|
14
15
|
gasTokenMax: {
|
|
@@ -40,7 +41,10 @@ export declare class SpvVaultSwapHandler extends SwapHandler<SpvVaultSwap, SpvVa
|
|
|
40
41
|
readonly AmountAssertions: FromBtcAmountAssertions;
|
|
41
42
|
readonly Vaults: SpvVaults;
|
|
42
43
|
config: SpvVaultSwapHandlerConfig;
|
|
43
|
-
|
|
44
|
+
readonly stickyAddresses?: IStorageManager<StickyAddress>;
|
|
45
|
+
constructor(storageDirectory: IIntermediaryStorage<SpvVaultSwap>, vaultStorage: IStorageManager<SpvVault>, path: string, chainsData: MultichainData, swapPricing: ISwapPrice, bitcoin: IBitcoinWallet, bitcoinRpc: BitcoinRpc<BtcBlock>, spvVaultSigner: ISpvVaultSigner, config: SpvVaultSwapHandlerConfig, stickyAddresses?: IStorageManager<StickyAddress>);
|
|
46
|
+
private getStickyAddress;
|
|
47
|
+
addStickyAddress(chainId: string, address: string, btcAddress: string): Promise<void>;
|
|
44
48
|
protected processClaimEvent(swap: SpvVaultSwap | null, event: SpvVaultClaimEvent): Promise<void>;
|
|
45
49
|
/**
|
|
46
50
|
* Chain event processor
|
|
@@ -15,9 +15,10 @@ const SpvVaults_1 = require("./SpvVaults");
|
|
|
15
15
|
const BitcoinUtils_1 = require("../../utils/BitcoinUtils");
|
|
16
16
|
const AmountAssertions_1 = require("../assertions/AmountAssertions");
|
|
17
17
|
const IPlugin_1 = require("../../plugins/IPlugin");
|
|
18
|
+
const StickyAddress_1 = require("./StickyAddress");
|
|
18
19
|
const TX_MAX_VSIZE = 16 * 1024;
|
|
19
20
|
class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
|
|
20
|
-
constructor(storageDirectory, vaultStorage, path, chainsData, swapPricing, bitcoin, bitcoinRpc, spvVaultSigner, config) {
|
|
21
|
+
constructor(storageDirectory, vaultStorage, path, chainsData, swapPricing, bitcoin, bitcoinRpc, spvVaultSigner, config, stickyAddresses) {
|
|
21
22
|
super(storageDirectory, path, chainsData, swapPricing);
|
|
22
23
|
this.type = SwapHandler_1.SwapHandlerType.FROM_BTC_SPV;
|
|
23
24
|
this.inflightSwapStates = new Set([SpvVaultSwap_1.SpvVaultSwapState.SIGNED, SpvVaultSwap_1.SpvVaultSwapState.SENT, SpvVaultSwap_1.SpvVaultSwapState.BTC_CONFIRMED]);
|
|
@@ -28,6 +29,43 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
|
|
|
28
29
|
this.config = config;
|
|
29
30
|
this.AmountAssertions = new FromBtcAmountAssertions_1.FromBtcAmountAssertions(config, swapPricing);
|
|
30
31
|
this.Vaults = new SpvVaults_1.SpvVaults(vaultStorage, bitcoin, spvVaultSigner, bitcoinRpc, this.chains, config);
|
|
32
|
+
this.stickyAddresses = stickyAddresses;
|
|
33
|
+
}
|
|
34
|
+
async getStickyAddress(chainId, address) {
|
|
35
|
+
if (this.stickyAddresses == null)
|
|
36
|
+
throw new Error("Sticky addresses are not supported!");
|
|
37
|
+
const { chainInterface } = this.getChain(chainId);
|
|
38
|
+
const normalizedAddress = chainInterface.normalizeAddress(address);
|
|
39
|
+
const addressIdentifier = chainId + "-" + normalizedAddress;
|
|
40
|
+
const result = this.stickyAddresses.data[addressIdentifier];
|
|
41
|
+
if (result == null)
|
|
42
|
+
return;
|
|
43
|
+
const btcAddress = result.address;
|
|
44
|
+
if (this.bitcoin.isOwnedAddress != null) {
|
|
45
|
+
if (!(await this.bitcoin.isOwnedAddress(btcAddress))) {
|
|
46
|
+
this.logger.warn(`getStickyAddress(): Failed to get sticky address, address ${btcAddress} is not controlled by our bitcoin wallet!`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return btcAddress;
|
|
51
|
+
}
|
|
52
|
+
async addStickyAddress(chainId, address, btcAddress) {
|
|
53
|
+
if (this.stickyAddresses == null)
|
|
54
|
+
throw new Error("Sticky addresses are not supported!");
|
|
55
|
+
const { chainInterface } = this.getChain(chainId);
|
|
56
|
+
const normalizedAddress = chainInterface.normalizeAddress(address);
|
|
57
|
+
if (this.bitcoin.isOwnedAddress != null) {
|
|
58
|
+
if (!(await this.bitcoin.isOwnedAddress(btcAddress))) {
|
|
59
|
+
this.logger.warn(`addStickyAddress(): Failed to create sticky address, address ${btcAddress} is not controlled by our bitcoin wallet!`);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const addressIdentifier = chainId + "-" + normalizedAddress;
|
|
64
|
+
if (this.stickyAddresses.data[addressIdentifier] != null) {
|
|
65
|
+
this.logger.warn(`addStickyAddress(): Failed to create sticky address, address sticky address already exists for ${addressIdentifier}!`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
await this.stickyAddresses.saveData(addressIdentifier, new StickyAddress_1.StickyAddress(btcAddress));
|
|
31
69
|
}
|
|
32
70
|
async processClaimEvent(swap, event) {
|
|
33
71
|
if (swap == null)
|
|
@@ -35,6 +73,13 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
|
|
|
35
73
|
//Update swap
|
|
36
74
|
swap.txIds.claim = event.meta?.txId;
|
|
37
75
|
await this.removeSwapData(swap, SpvVaultSwap_1.SpvVaultSwapState.CLAIMED);
|
|
76
|
+
if (swap.saveStickyAddress)
|
|
77
|
+
try {
|
|
78
|
+
await this.addStickyAddress(swap.chainIdentifier, swap.recipient, swap.btcAddress);
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
this.logger.error(`processClaimEvent(): Failed to create the sticky address for swap ${swap.getIdentifier()}`);
|
|
82
|
+
}
|
|
38
83
|
}
|
|
39
84
|
/**
|
|
40
85
|
* Chain event processor
|
|
@@ -91,6 +136,10 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
|
|
|
91
136
|
this.btcTxIdIndex.set(swap.btcTxId, swap);
|
|
92
137
|
}
|
|
93
138
|
await this.Vaults.init();
|
|
139
|
+
if (this.stickyAddresses != null) {
|
|
140
|
+
await this.stickyAddresses.init();
|
|
141
|
+
await this.stickyAddresses.loadData(StickyAddress_1.StickyAddress);
|
|
142
|
+
}
|
|
94
143
|
this.subscribeToEvents();
|
|
95
144
|
await PluginManager_1.PluginManager.serviceInitialize(this);
|
|
96
145
|
}
|
|
@@ -98,7 +147,8 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
|
|
|
98
147
|
if (swap.state === SpvVaultSwap_1.SpvVaultSwapState.CREATED) {
|
|
99
148
|
if (swap.expiry < Date.now() / 1000) {
|
|
100
149
|
await this.removeSwapData(swap, SpvVaultSwap_1.SpvVaultSwapState.EXPIRED);
|
|
101
|
-
|
|
150
|
+
if (!swap.hasStickyAddress)
|
|
151
|
+
await this.bitcoin.addUnusedAddress(swap.btcAddress);
|
|
102
152
|
}
|
|
103
153
|
}
|
|
104
154
|
if (swap.state === SpvVaultSwap_1.SpvVaultSwapState.SIGNED) {
|
|
@@ -182,10 +232,14 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
|
|
|
182
232
|
const { signer, chainInterface, spvVaultContract } = this.getChain(chainIdentifier);
|
|
183
233
|
metadata.times.requestReceived = Date.now();
|
|
184
234
|
/**
|
|
235
|
+
* address: string smart chain address of the recipient
|
|
185
236
|
* token: string Desired token to use
|
|
186
237
|
* gasToken: string
|
|
187
238
|
*/
|
|
188
239
|
const preFetchParsedBody = await req.paramReader.getParams({
|
|
240
|
+
address: (val) => val != null &&
|
|
241
|
+
typeof (val) === "string" &&
|
|
242
|
+
chainInterface.isValidAddress(val, true) ? val : null,
|
|
189
243
|
token: (val) => val != null &&
|
|
190
244
|
typeof (val) === "string" &&
|
|
191
245
|
this.isTokenSupported(chainIdentifier, val) ? val : null,
|
|
@@ -198,6 +252,10 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
|
|
|
198
252
|
code: 20100,
|
|
199
253
|
msg: "Invalid request body"
|
|
200
254
|
};
|
|
255
|
+
const stickyAddressObject = req.paramReader.getExistingParamsOrNull({
|
|
256
|
+
stickyAddress: SchemaVerifier_1.FieldTypeEnum.BooleanOptional
|
|
257
|
+
});
|
|
258
|
+
const useStickyAddress = stickyAddressObject?.stickyAddress;
|
|
201
259
|
//Create abortController for parallel prefetches
|
|
202
260
|
const responseStream = res.responseStream;
|
|
203
261
|
const abortController = (0, Utils_1.getAbortController)(responseStream);
|
|
@@ -210,7 +268,13 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
|
|
|
210
268
|
});
|
|
211
269
|
//Listener that re-adds the returned bitcoin address to the unused address list if request fails or closes
|
|
212
270
|
let abortAddUnusedAddressListener;
|
|
213
|
-
const bitcoinAddressPrefetch =
|
|
271
|
+
const bitcoinAddressPrefetch = (async () => {
|
|
272
|
+
if (useStickyAddress) {
|
|
273
|
+
const result = await this.getStickyAddress(chainIdentifier, preFetchParsedBody.address);
|
|
274
|
+
if (result != null)
|
|
275
|
+
return { address: result, isStickyAddress: true };
|
|
276
|
+
}
|
|
277
|
+
const value = await this.bitcoin.getAddress();
|
|
214
278
|
//Already aborted
|
|
215
279
|
if (abortController.signal.aborted) {
|
|
216
280
|
this.bitcoin.addUnusedAddress(value);
|
|
@@ -220,13 +284,12 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
|
|
|
220
284
|
abortController.signal.addEventListener("abort", abortAddUnusedAddressListener = () => {
|
|
221
285
|
this.bitcoin.addUnusedAddress(value);
|
|
222
286
|
});
|
|
223
|
-
return value;
|
|
224
|
-
}).catch(e => {
|
|
287
|
+
return { address: value, isStickyAddress: false };
|
|
288
|
+
})().catch(e => {
|
|
225
289
|
abortController.abort(e);
|
|
226
290
|
return null;
|
|
227
291
|
});
|
|
228
292
|
/**
|
|
229
|
-
* address: string smart chain address of the recipient
|
|
230
293
|
* amount: string amount (in sats)
|
|
231
294
|
* gasAmount: string Desired amount in gas token to also get
|
|
232
295
|
* exactOut: boolean Whether the swap should be an exact out instead of exact in swap
|
|
@@ -234,9 +297,6 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
|
|
|
234
297
|
* frontingFeeRate: string Fronting fee (in output token) to assign to the swap
|
|
235
298
|
*/
|
|
236
299
|
const actualParsedBody = await req.paramReader.getParams({
|
|
237
|
-
address: (val) => val != null &&
|
|
238
|
-
typeof (val) === "string" &&
|
|
239
|
-
chainInterface.isValidAddress(val, true) ? val : null,
|
|
240
300
|
amount: SchemaVerifier_1.FieldTypeEnum.BigInt,
|
|
241
301
|
gasAmount: SchemaVerifier_1.FieldTypeEnum.BigInt,
|
|
242
302
|
exactOut: SchemaVerifier_1.FieldTypeEnum.BooleanOptional,
|
|
@@ -304,9 +364,11 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
|
|
|
304
364
|
metadata.times.vaultPicked = Date.now();
|
|
305
365
|
//Create swap receive bitcoin address
|
|
306
366
|
const btcFeeRate = await btcFeeRatePrefetch;
|
|
307
|
-
const
|
|
367
|
+
const btcAddressObject = await bitcoinAddressPrefetch;
|
|
308
368
|
abortController.signal.throwIfAborted();
|
|
309
369
|
metadata.times.addressCreated = Date.now();
|
|
370
|
+
const receiveAddress = btcAddressObject.address;
|
|
371
|
+
const hasStickyAddress = btcAddressObject.isStickyAddress;
|
|
310
372
|
//Adjust the amounts based on passed fees
|
|
311
373
|
if (parsedBody.exactOut) {
|
|
312
374
|
totalInToken = parsedBody.amount;
|
|
@@ -327,6 +389,8 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
|
|
|
327
389
|
const quoteId = (0, crypto_1.randomBytes)(32).toString("hex");
|
|
328
390
|
const swap = new SpvVaultSwap_1.SpvVaultSwap(chainIdentifier, quoteId, expiry, vault, utxo, receiveAddress, btcFeeRate, parsedBody.address, totalBtcOutput, totalInToken, totalInGasToken, swapFee, swapFeeInToken, gasSwapFee, gasSwapFeeInToken, callerFeeShare, frontingFeeShare, executionFeeShare, useToken, gasToken);
|
|
329
391
|
swap.metadata = metadata;
|
|
392
|
+
swap.saveStickyAddress = useStickyAddress && !hasStickyAddress;
|
|
393
|
+
swap.hasStickyAddress = hasStickyAddress;
|
|
330
394
|
//We can remove the listener to add unused address now, as we are about to save the swap
|
|
331
395
|
abortController.signal.removeEventListener("abort", abortAddUnusedAddressListener);
|
|
332
396
|
await PluginManager_1.PluginManager.swapCreate(swap);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StickyAddress = void 0;
|
|
4
|
+
class StickyAddress {
|
|
5
|
+
constructor(addressOrSerialized) {
|
|
6
|
+
if (typeof (addressOrSerialized) === "string") {
|
|
7
|
+
this.address = addressOrSerialized;
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
this.address = addressOrSerialized.address;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
serialize() {
|
|
14
|
+
return {
|
|
15
|
+
address: this.address
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.StickyAddress = StickyAddress;
|
|
@@ -63,6 +63,11 @@ export declare abstract class IBitcoinWallet {
|
|
|
63
63
|
* Returns an unused address suitable for receiving
|
|
64
64
|
*/
|
|
65
65
|
abstract getAddress(): Promise<string>;
|
|
66
|
+
/**
|
|
67
|
+
* Whether a provided address is controlled by this wallet
|
|
68
|
+
* @param address
|
|
69
|
+
*/
|
|
70
|
+
isOwnedAddress?(address: string): Promise<boolean>;
|
|
66
71
|
/**
|
|
67
72
|
* Adds previously returned address (with getAddress call), to the pool of unused addresses
|
|
68
73
|
* @param address
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -49,5 +49,6 @@ export * from "./wallets/ISpvVaultSigner";
|
|
|
49
49
|
|
|
50
50
|
export * from "./swaps/spv_vault_swap/SpvVaults";
|
|
51
51
|
export * from "./swaps/spv_vault_swap/SpvVault";
|
|
52
|
+
export * from "./swaps/spv_vault_swap/StickyAddress";
|
|
52
53
|
export * from "./swaps/spv_vault_swap/SpvVaultSwap";
|
|
53
54
|
export * from "./swaps/spv_vault_swap/SpvVaultSwapHandler";
|
|
@@ -53,6 +53,9 @@ export class SpvVaultSwap extends SwapHandlerSwap<SpvVaultSwapState> {
|
|
|
53
53
|
|
|
54
54
|
btcTxId: string;
|
|
55
55
|
|
|
56
|
+
saveStickyAddress?: boolean;
|
|
57
|
+
hasStickyAddress?: boolean;
|
|
58
|
+
|
|
56
59
|
constructor(
|
|
57
60
|
chainIdentifier: string, quoteId: string, expiry: number,
|
|
58
61
|
vault: SpvVault, vaultUtxo: string,
|
|
@@ -129,6 +132,8 @@ export class SpvVaultSwap extends SwapHandlerSwap<SpvVaultSwapState> {
|
|
|
129
132
|
this.tokenMultiplier = deserializeBN(chainIdentifierOrObj.tokenMultiplier);
|
|
130
133
|
this.gasTokenMultiplier = deserializeBN(chainIdentifierOrObj.gasTokenMultiplier);
|
|
131
134
|
this.btcTxId = chainIdentifierOrObj.btcTxId;
|
|
135
|
+
this.hasStickyAddress = chainIdentifierOrObj.hasStickyAddress;
|
|
136
|
+
this.saveStickyAddress = chainIdentifierOrObj.saveStickyAddress;
|
|
132
137
|
}
|
|
133
138
|
this.type = SwapHandlerType.FROM_BTC_SPV;
|
|
134
139
|
}
|
|
@@ -161,7 +166,9 @@ export class SpvVaultSwap extends SwapHandlerSwap<SpvVaultSwapState> {
|
|
|
161
166
|
gasToken: this.gasToken,
|
|
162
167
|
tokenMultiplier: serializeBN(this.tokenMultiplier),
|
|
163
168
|
gasTokenMultiplier: serializeBN(this.gasTokenMultiplier),
|
|
164
|
-
btcTxId: this.btcTxId
|
|
169
|
+
btcTxId: this.btcTxId,
|
|
170
|
+
hasStickyAddress: this.hasStickyAddress,
|
|
171
|
+
saveStickyAddress: this.saveStickyAddress
|
|
165
172
|
};
|
|
166
173
|
}
|
|
167
174
|
|
|
@@ -38,6 +38,7 @@ import {SpvVaults, VAULT_DUST_AMOUNT} from "./SpvVaults";
|
|
|
38
38
|
import {isLegacyInput} from "../../utils/BitcoinUtils";
|
|
39
39
|
import {AmountAssertions} from "../assertions/AmountAssertions";
|
|
40
40
|
import {isQuoteThrow} from "../../plugins/IPlugin";
|
|
41
|
+
import {StickyAddress} from "./StickyAddress";
|
|
41
42
|
|
|
42
43
|
export type SpvVaultSwapHandlerConfig = SwapBaseConfig & {
|
|
43
44
|
vaultsCheckInterval: number,
|
|
@@ -78,6 +79,8 @@ export class SpvVaultSwapHandler extends SwapHandler<SpvVaultSwap, SpvVaultSwapS
|
|
|
78
79
|
|
|
79
80
|
config: SpvVaultSwapHandlerConfig;
|
|
80
81
|
|
|
82
|
+
readonly stickyAddresses?: IStorageManager<StickyAddress>;
|
|
83
|
+
|
|
81
84
|
constructor(
|
|
82
85
|
storageDirectory: IIntermediaryStorage<SpvVaultSwap>,
|
|
83
86
|
vaultStorage: IStorageManager<SpvVault>,
|
|
@@ -87,7 +90,8 @@ export class SpvVaultSwapHandler extends SwapHandler<SpvVaultSwap, SpvVaultSwapS
|
|
|
87
90
|
bitcoin: IBitcoinWallet,
|
|
88
91
|
bitcoinRpc: BitcoinRpc<BtcBlock>,
|
|
89
92
|
spvVaultSigner: ISpvVaultSigner,
|
|
90
|
-
config: SpvVaultSwapHandlerConfig
|
|
93
|
+
config: SpvVaultSwapHandlerConfig,
|
|
94
|
+
stickyAddresses?: IStorageManager<StickyAddress>
|
|
91
95
|
) {
|
|
92
96
|
super(storageDirectory, path, chainsData, swapPricing);
|
|
93
97
|
this.bitcoinRpc = bitcoinRpc;
|
|
@@ -96,6 +100,52 @@ export class SpvVaultSwapHandler extends SwapHandler<SpvVaultSwap, SpvVaultSwapS
|
|
|
96
100
|
this.config = config;
|
|
97
101
|
this.AmountAssertions = new FromBtcAmountAssertions(config, swapPricing);
|
|
98
102
|
this.Vaults = new SpvVaults(vaultStorage, bitcoin, spvVaultSigner, bitcoinRpc, this.chains, config);
|
|
103
|
+
this.stickyAddresses = stickyAddresses;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private async getStickyAddress(chainId: string, address: string): Promise<string | undefined> {
|
|
107
|
+
if(this.stickyAddresses==null) throw new Error("Sticky addresses are not supported!");
|
|
108
|
+
|
|
109
|
+
const {chainInterface} = this.getChain(chainId);
|
|
110
|
+
const normalizedAddress = chainInterface.normalizeAddress(address);
|
|
111
|
+
|
|
112
|
+
const addressIdentifier = chainId+"-"+normalizedAddress;
|
|
113
|
+
|
|
114
|
+
const result = this.stickyAddresses.data[addressIdentifier];
|
|
115
|
+
if(result==null) return;
|
|
116
|
+
|
|
117
|
+
const btcAddress = result.address;
|
|
118
|
+
|
|
119
|
+
if(this.bitcoin.isOwnedAddress!=null) {
|
|
120
|
+
if(!(await this.bitcoin.isOwnedAddress(btcAddress))) {
|
|
121
|
+
this.logger.warn(`getStickyAddress(): Failed to get sticky address, address ${btcAddress} is not controlled by our bitcoin wallet!`);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return btcAddress;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async addStickyAddress(chainId: string, address: string, btcAddress: string) {
|
|
130
|
+
if(this.stickyAddresses==null) throw new Error("Sticky addresses are not supported!");
|
|
131
|
+
|
|
132
|
+
const {chainInterface} = this.getChain(chainId);
|
|
133
|
+
const normalizedAddress = chainInterface.normalizeAddress(address);
|
|
134
|
+
|
|
135
|
+
if(this.bitcoin.isOwnedAddress!=null) {
|
|
136
|
+
if(!(await this.bitcoin.isOwnedAddress(btcAddress))) {
|
|
137
|
+
this.logger.warn(`addStickyAddress(): Failed to create sticky address, address ${btcAddress} is not controlled by our bitcoin wallet!`);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const addressIdentifier = chainId+"-"+normalizedAddress;
|
|
143
|
+
|
|
144
|
+
if(this.stickyAddresses.data[addressIdentifier]!=null) {
|
|
145
|
+
this.logger.warn(`addStickyAddress(): Failed to create sticky address, address sticky address already exists for ${addressIdentifier}!`);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
await this.stickyAddresses.saveData(addressIdentifier, new StickyAddress(btcAddress));
|
|
99
149
|
}
|
|
100
150
|
|
|
101
151
|
protected async processClaimEvent(swap: SpvVaultSwap | null, event: SpvVaultClaimEvent): Promise<void> {
|
|
@@ -103,6 +153,11 @@ export class SpvVaultSwapHandler extends SwapHandler<SpvVaultSwap, SpvVaultSwapS
|
|
|
103
153
|
//Update swap
|
|
104
154
|
swap.txIds.claim = event.meta?.txId;
|
|
105
155
|
await this.removeSwapData(swap, SpvVaultSwapState.CLAIMED);
|
|
156
|
+
if(swap.saveStickyAddress) try {
|
|
157
|
+
await this.addStickyAddress(swap.chainIdentifier, swap.recipient, swap.btcAddress);
|
|
158
|
+
} catch (e) {
|
|
159
|
+
this.logger.error(`processClaimEvent(): Failed to create the sticky address for swap ${swap.getIdentifier()}`);
|
|
160
|
+
}
|
|
106
161
|
}
|
|
107
162
|
|
|
108
163
|
/**
|
|
@@ -161,6 +216,10 @@ export class SpvVaultSwapHandler extends SwapHandler<SpvVaultSwap, SpvVaultSwapS
|
|
|
161
216
|
if(swap.btcTxId!=null) this.btcTxIdIndex.set(swap.btcTxId, swap);
|
|
162
217
|
}
|
|
163
218
|
await this.Vaults.init();
|
|
219
|
+
if(this.stickyAddresses!=null) {
|
|
220
|
+
await this.stickyAddresses.init();
|
|
221
|
+
await this.stickyAddresses.loadData(StickyAddress);
|
|
222
|
+
}
|
|
164
223
|
this.subscribeToEvents();
|
|
165
224
|
await PluginManager.serviceInitialize(this);
|
|
166
225
|
}
|
|
@@ -169,7 +228,7 @@ export class SpvVaultSwapHandler extends SwapHandler<SpvVaultSwap, SpvVaultSwapS
|
|
|
169
228
|
if(swap.state===SpvVaultSwapState.CREATED) {
|
|
170
229
|
if(swap.expiry < Date.now()/1000) {
|
|
171
230
|
await this.removeSwapData(swap, SpvVaultSwapState.EXPIRED);
|
|
172
|
-
await this.bitcoin.addUnusedAddress(swap.btcAddress);
|
|
231
|
+
if(!swap.hasStickyAddress) await this.bitcoin.addUnusedAddress(swap.btcAddress);
|
|
173
232
|
}
|
|
174
233
|
}
|
|
175
234
|
|
|
@@ -260,10 +319,14 @@ export class SpvVaultSwapHandler extends SwapHandler<SpvVaultSwap, SpvVaultSwapS
|
|
|
260
319
|
metadata.times.requestReceived = Date.now();
|
|
261
320
|
|
|
262
321
|
/**
|
|
322
|
+
* address: string smart chain address of the recipient
|
|
263
323
|
* token: string Desired token to use
|
|
264
324
|
* gasToken: string
|
|
265
325
|
*/
|
|
266
326
|
const preFetchParsedBody = await req.paramReader.getParams({
|
|
327
|
+
address: (val: string) => val!=null &&
|
|
328
|
+
typeof(val)==="string" &&
|
|
329
|
+
chainInterface.isValidAddress(val, true) ? val : null,
|
|
267
330
|
token: (val: string) => val!=null &&
|
|
268
331
|
typeof(val)==="string" &&
|
|
269
332
|
this.isTokenSupported(chainIdentifier, val) ? val : null,
|
|
@@ -276,6 +339,11 @@ export class SpvVaultSwapHandler extends SwapHandler<SpvVaultSwap, SpvVaultSwapS
|
|
|
276
339
|
msg: "Invalid request body"
|
|
277
340
|
};
|
|
278
341
|
|
|
342
|
+
const stickyAddressObject = req.paramReader.getExistingParamsOrNull({
|
|
343
|
+
stickyAddress: FieldTypeEnum.BooleanOptional
|
|
344
|
+
});
|
|
345
|
+
const useStickyAddress = stickyAddressObject?.stickyAddress;
|
|
346
|
+
|
|
279
347
|
//Create abortController for parallel prefetches
|
|
280
348
|
const responseStream = res.responseStream;
|
|
281
349
|
const abortController = getAbortController(responseStream);
|
|
@@ -293,7 +361,15 @@ export class SpvVaultSwapHandler extends SwapHandler<SpvVaultSwap, SpvVaultSwapS
|
|
|
293
361
|
|
|
294
362
|
//Listener that re-adds the returned bitcoin address to the unused address list if request fails or closes
|
|
295
363
|
let abortAddUnusedAddressListener: () => void;
|
|
296
|
-
|
|
364
|
+
|
|
365
|
+
const bitcoinAddressPrefetch: Promise<{address: string, isStickyAddress: boolean} | null> = (async () => {
|
|
366
|
+
if(useStickyAddress) {
|
|
367
|
+
const result = await this.getStickyAddress(chainIdentifier, preFetchParsedBody.address);
|
|
368
|
+
if(result!=null) return {address: result, isStickyAddress: true};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const value = await this.bitcoin.getAddress();
|
|
372
|
+
|
|
297
373
|
//Already aborted
|
|
298
374
|
if(abortController.signal.aborted) {
|
|
299
375
|
this.bitcoin.addUnusedAddress(value);
|
|
@@ -303,14 +379,13 @@ export class SpvVaultSwapHandler extends SwapHandler<SpvVaultSwap, SpvVaultSwapS
|
|
|
303
379
|
abortController.signal.addEventListener("abort", abortAddUnusedAddressListener = () => {
|
|
304
380
|
this.bitcoin.addUnusedAddress(value);
|
|
305
381
|
});
|
|
306
|
-
return value;
|
|
307
|
-
}).catch(e => {
|
|
382
|
+
return {address: value, isStickyAddress: false};
|
|
383
|
+
})().catch(e => {
|
|
308
384
|
abortController.abort(e);
|
|
309
385
|
return null;
|
|
310
386
|
});
|
|
311
387
|
|
|
312
388
|
/**
|
|
313
|
-
* address: string smart chain address of the recipient
|
|
314
389
|
* amount: string amount (in sats)
|
|
315
390
|
* gasAmount: string Desired amount in gas token to also get
|
|
316
391
|
* exactOut: boolean Whether the swap should be an exact out instead of exact in swap
|
|
@@ -318,9 +393,6 @@ export class SpvVaultSwapHandler extends SwapHandler<SpvVaultSwap, SpvVaultSwapS
|
|
|
318
393
|
* frontingFeeRate: string Fronting fee (in output token) to assign to the swap
|
|
319
394
|
*/
|
|
320
395
|
const actualParsedBody = await req.paramReader.getParams({
|
|
321
|
-
address: (val: string) => val!=null &&
|
|
322
|
-
typeof(val)==="string" &&
|
|
323
|
-
chainInterface.isValidAddress(val, true) ? val : null,
|
|
324
396
|
amount: FieldTypeEnum.BigInt,
|
|
325
397
|
gasAmount: FieldTypeEnum.BigInt,
|
|
326
398
|
exactOut: FieldTypeEnum.BooleanOptional,
|
|
@@ -411,10 +483,13 @@ export class SpvVaultSwapHandler extends SwapHandler<SpvVaultSwap, SpvVaultSwapS
|
|
|
411
483
|
|
|
412
484
|
//Create swap receive bitcoin address
|
|
413
485
|
const btcFeeRate = await btcFeeRatePrefetch;
|
|
414
|
-
const
|
|
486
|
+
const btcAddressObject = await bitcoinAddressPrefetch;
|
|
415
487
|
abortController.signal.throwIfAborted();
|
|
416
488
|
metadata.times.addressCreated = Date.now();
|
|
417
489
|
|
|
490
|
+
const receiveAddress = btcAddressObject.address;
|
|
491
|
+
const hasStickyAddress = btcAddressObject.isStickyAddress;
|
|
492
|
+
|
|
418
493
|
//Adjust the amounts based on passed fees
|
|
419
494
|
if(parsedBody.exactOut) {
|
|
420
495
|
totalInToken = parsedBody.amount;
|
|
@@ -445,6 +520,8 @@ export class SpvVaultSwapHandler extends SwapHandler<SpvVaultSwap, SpvVaultSwapS
|
|
|
445
520
|
useToken, gasToken
|
|
446
521
|
);
|
|
447
522
|
swap.metadata = metadata;
|
|
523
|
+
swap.saveStickyAddress = useStickyAddress && !hasStickyAddress
|
|
524
|
+
swap.hasStickyAddress = hasStickyAddress;
|
|
448
525
|
|
|
449
526
|
//We can remove the listener to add unused address now, as we are about to save the swap
|
|
450
527
|
abortController.signal.removeEventListener("abort", abortAddUnusedAddressListener);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {StorageObject} from "@atomiqlabs/base";
|
|
2
|
+
|
|
3
|
+
export class StickyAddress implements StorageObject {
|
|
4
|
+
|
|
5
|
+
address: string;
|
|
6
|
+
|
|
7
|
+
constructor(addressOrSerialized: string | any) {
|
|
8
|
+
if(typeof(addressOrSerialized) === "string") {
|
|
9
|
+
this.address = addressOrSerialized;
|
|
10
|
+
} else {
|
|
11
|
+
this.address = addressOrSerialized.address;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
serialize(): any {
|
|
16
|
+
return {
|
|
17
|
+
address: this.address
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
}
|
|
@@ -154,6 +154,11 @@ export abstract class IBitcoinWallet {
|
|
|
154
154
|
* Returns an unused address suitable for receiving
|
|
155
155
|
*/
|
|
156
156
|
abstract getAddress(): Promise<string>;
|
|
157
|
+
/**
|
|
158
|
+
* Whether a provided address is controlled by this wallet
|
|
159
|
+
* @param address
|
|
160
|
+
*/
|
|
161
|
+
isOwnedAddress?(address: string): Promise<boolean>;
|
|
157
162
|
/**
|
|
158
163
|
* Adds previously returned address (with getAddress call), to the pool of unused addresses
|
|
159
164
|
* @param address
|