@arkade-os/sdk 0.3.0-alpha.7 → 0.3.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/README.md +99 -14
- package/dist/cjs/adapters/expo.js +8 -0
- package/dist/cjs/arknote/index.js +3 -3
- package/dist/cjs/forfeit.js +2 -2
- package/dist/cjs/identity/singleKey.js +8 -8
- package/dist/cjs/index.js +14 -5
- package/dist/cjs/{bip322 → intent}/index.js +38 -61
- package/dist/cjs/musig2/index.js +2 -1
- package/dist/cjs/musig2/nonces.js +4 -0
- package/dist/cjs/providers/ark.js +76 -45
- package/dist/cjs/providers/errors.js +59 -0
- package/dist/cjs/providers/expoArk.js +82 -0
- package/dist/cjs/providers/expoIndexer.js +105 -0
- package/dist/cjs/providers/expoUtils.js +124 -0
- package/dist/cjs/providers/indexer.js +3 -1
- package/dist/cjs/providers/onchain.js +19 -20
- package/dist/cjs/repositories/walletRepository.js +64 -28
- package/dist/cjs/script/base.js +15 -7
- package/dist/cjs/script/tapscript.js +20 -21
- package/dist/cjs/script/vhtlc.js +2 -2
- package/dist/cjs/tree/signingSession.js +44 -11
- package/dist/cjs/tree/txTree.js +3 -4
- package/dist/cjs/tree/validation.js +2 -3
- package/dist/cjs/utils/arkTransaction.js +118 -15
- package/dist/cjs/utils/transaction.js +28 -0
- package/dist/cjs/utils/unknownFields.js +7 -7
- package/dist/cjs/wallet/index.js +1 -1
- package/dist/cjs/wallet/onchain.js +6 -7
- package/dist/cjs/wallet/serviceWorker/response.js +32 -0
- package/dist/cjs/wallet/serviceWorker/utils.js +2 -9
- package/dist/cjs/wallet/serviceWorker/wallet.js +7 -8
- package/dist/cjs/wallet/serviceWorker/worker.js +48 -32
- package/dist/cjs/wallet/unroll.js +7 -9
- package/dist/cjs/wallet/utils.js +20 -0
- package/dist/cjs/wallet/vtxo-manager.js +323 -0
- package/dist/cjs/wallet/wallet.js +165 -174
- package/dist/esm/adapters/expo.js +3 -0
- package/dist/esm/arknote/index.js +2 -2
- package/dist/esm/forfeit.js +1 -1
- package/dist/esm/identity/singleKey.js +9 -9
- package/dist/esm/index.js +14 -10
- package/dist/esm/{bip322 → intent}/index.js +32 -54
- package/dist/esm/musig2/index.js +1 -1
- package/dist/esm/musig2/nonces.js +3 -0
- package/dist/esm/providers/ark.js +76 -45
- package/dist/esm/providers/errors.js +54 -0
- package/dist/esm/providers/expoArk.js +78 -0
- package/dist/esm/providers/expoIndexer.js +101 -0
- package/dist/esm/providers/expoUtils.js +87 -0
- package/dist/esm/providers/indexer.js +3 -1
- package/dist/esm/providers/onchain.js +19 -20
- package/dist/esm/repositories/walletRepository.js +64 -28
- package/dist/esm/script/base.js +12 -4
- package/dist/esm/script/tapscript.js +1 -2
- package/dist/esm/script/vhtlc.js +1 -1
- package/dist/esm/tree/signingSession.js +45 -12
- package/dist/esm/tree/txTree.js +3 -4
- package/dist/esm/tree/validation.js +2 -3
- package/dist/esm/utils/arkTransaction.js +110 -9
- package/dist/esm/utils/transaction.js +24 -0
- package/dist/esm/utils/unknownFields.js +3 -3
- package/dist/esm/wallet/index.js +1 -1
- package/dist/esm/wallet/onchain.js +3 -4
- package/dist/esm/wallet/serviceWorker/response.js +32 -0
- package/dist/esm/wallet/serviceWorker/utils.js +1 -8
- package/dist/esm/wallet/serviceWorker/wallet.js +8 -9
- package/dist/esm/wallet/serviceWorker/worker.js +49 -33
- package/dist/esm/wallet/unroll.js +5 -7
- package/dist/esm/wallet/utils.js +16 -0
- package/dist/esm/wallet/vtxo-manager.js +317 -0
- package/dist/esm/wallet/wallet.js +159 -168
- package/dist/types/adapters/expo.d.ts +4 -0
- package/dist/types/arknote/index.d.ts +1 -1
- package/dist/types/forfeit.d.ts +2 -2
- package/dist/types/identity/index.d.ts +2 -2
- package/dist/types/identity/singleKey.d.ts +2 -2
- package/dist/types/index.d.ts +11 -9
- package/dist/types/intent/index.d.ts +41 -0
- package/dist/types/musig2/index.d.ts +1 -1
- package/dist/types/musig2/nonces.d.ts +1 -0
- package/dist/types/providers/ark.d.ts +197 -27
- package/dist/types/providers/errors.d.ts +13 -0
- package/dist/types/providers/expoArk.d.ts +22 -0
- package/dist/types/providers/expoIndexer.d.ts +18 -0
- package/dist/types/providers/expoUtils.d.ts +18 -0
- package/dist/types/providers/indexer.d.ts +8 -8
- package/dist/types/providers/onchain.d.ts +6 -2
- package/dist/types/repositories/walletRepository.d.ts +9 -5
- package/dist/types/script/base.d.ts +5 -2
- package/dist/types/tree/signingSession.d.ts +16 -11
- package/dist/types/utils/anchor.d.ts +2 -2
- package/dist/types/utils/arkTransaction.d.ts +15 -5
- package/dist/types/utils/transaction.d.ts +13 -0
- package/dist/types/utils/unknownFields.d.ts +4 -4
- package/dist/types/wallet/index.d.ts +47 -7
- package/dist/types/wallet/onchain.d.ts +1 -1
- package/dist/types/wallet/serviceWorker/response.d.ts +16 -2
- package/dist/types/wallet/serviceWorker/utils.d.ts +1 -2
- package/dist/types/wallet/serviceWorker/wallet.d.ts +2 -2
- package/dist/types/wallet/serviceWorker/worker.d.ts +7 -1
- package/dist/types/wallet/unroll.d.ts +1 -1
- package/dist/types/wallet/utils.d.ts +3 -0
- package/dist/types/wallet/vtxo-manager.d.ts +179 -0
- package/dist/types/wallet/wallet.d.ts +17 -5
- package/package.json +11 -3
- package/dist/cjs/bip322/errors.js +0 -13
- package/dist/esm/bip322/errors.js +0 -9
- package/dist/types/bip322/errors.d.ts +0 -6
- package/dist/types/bip322/index.d.ts +0 -57
|
@@ -38,8 +38,8 @@ exports.waitForIncomingFunds = waitForIncomingFunds;
|
|
|
38
38
|
const base_1 = require("@scure/base");
|
|
39
39
|
const bip68 = __importStar(require("bip68"));
|
|
40
40
|
const payment_js_1 = require("@scure/btc-signer/payment.js");
|
|
41
|
-
const
|
|
42
|
-
const
|
|
41
|
+
const btc_signer_1 = require("@scure/btc-signer");
|
|
42
|
+
const utils_js_1 = require("@scure/btc-signer/utils.js");
|
|
43
43
|
const transactionHistory_1 = require("../utils/transactionHistory");
|
|
44
44
|
const address_1 = require("../script/address");
|
|
45
45
|
const default_1 = require("../script/default");
|
|
@@ -49,18 +49,19 @@ const ark_1 = require("../providers/ark");
|
|
|
49
49
|
const forfeit_1 = require("../forfeit");
|
|
50
50
|
const validation_1 = require("../tree/validation");
|
|
51
51
|
const _1 = require(".");
|
|
52
|
-
const utils_js_1 = require("@scure/btc-signer/utils.js");
|
|
53
52
|
const base_2 = require("../script/base");
|
|
54
53
|
const tapscript_1 = require("../script/tapscript");
|
|
55
54
|
const arkTransaction_1 = require("../utils/arkTransaction");
|
|
55
|
+
const vtxo_manager_1 = require("./vtxo-manager");
|
|
56
56
|
const arknote_1 = require("../arknote");
|
|
57
|
-
const
|
|
57
|
+
const intent_1 = require("../intent");
|
|
58
58
|
const indexer_1 = require("../providers/indexer");
|
|
59
59
|
const txTree_1 = require("../tree/txTree");
|
|
60
|
+
const unknownFields_1 = require("../utils/unknownFields");
|
|
60
61
|
const inMemory_1 = require("../storage/inMemory");
|
|
61
62
|
const walletRepository_1 = require("../repositories/walletRepository");
|
|
62
63
|
const contractRepository_1 = require("../repositories/contractRepository");
|
|
63
|
-
const utils_1 = require("./
|
|
64
|
+
const utils_1 = require("./utils");
|
|
64
65
|
/**
|
|
65
66
|
* Main wallet implementation for Bitcoin transactions with Ark protocol support.
|
|
66
67
|
* The wallet does not store any data locally and relies on Ark and onchain
|
|
@@ -68,13 +69,21 @@ const utils_1 = require("./serviceWorker/utils");
|
|
|
68
69
|
*
|
|
69
70
|
* @example
|
|
70
71
|
* ```typescript
|
|
71
|
-
* // Create a wallet
|
|
72
|
+
* // Create a wallet with URL configuration
|
|
72
73
|
* const wallet = await Wallet.create({
|
|
73
74
|
* identity: SingleKey.fromHex('your_private_key'),
|
|
74
75
|
* arkServerUrl: 'https://ark.example.com',
|
|
75
76
|
* esploraUrl: 'https://mempool.space/api'
|
|
76
77
|
* });
|
|
77
78
|
*
|
|
79
|
+
* // Or with custom provider instances (e.g., for Expo/React Native)
|
|
80
|
+
* const wallet = await Wallet.create({
|
|
81
|
+
* identity: SingleKey.fromHex('your_private_key'),
|
|
82
|
+
* arkProvider: new ExpoArkProvider('https://ark.example.com'),
|
|
83
|
+
* indexerProvider: new ExpoIndexerProvider('https://ark.example.com'),
|
|
84
|
+
* esploraUrl: 'https://mempool.space/api'
|
|
85
|
+
* });
|
|
86
|
+
*
|
|
78
87
|
* // Get addresses
|
|
79
88
|
* const arkAddress = await wallet.getAddress();
|
|
80
89
|
* const boardingAddress = await wallet.getBoardingAddress();
|
|
@@ -87,7 +96,7 @@ const utils_1 = require("./serviceWorker/utils");
|
|
|
87
96
|
* ```
|
|
88
97
|
*/
|
|
89
98
|
class Wallet {
|
|
90
|
-
constructor(identity, network, networkName, onchainProvider, arkProvider, indexerProvider, arkServerPublicKey, offchainTapscript, boardingTapscript, serverUnrollScript, forfeitOutputScript, dustAmount, walletRepository, contractRepository) {
|
|
99
|
+
constructor(identity, network, networkName, onchainProvider, arkProvider, indexerProvider, arkServerPublicKey, offchainTapscript, boardingTapscript, serverUnrollScript, forfeitOutputScript, forfeitPubkey, dustAmount, walletRepository, contractRepository, renewalConfig) {
|
|
91
100
|
this.identity = identity;
|
|
92
101
|
this.network = network;
|
|
93
102
|
this.networkName = networkName;
|
|
@@ -99,20 +108,45 @@ class Wallet {
|
|
|
99
108
|
this.boardingTapscript = boardingTapscript;
|
|
100
109
|
this.serverUnrollScript = serverUnrollScript;
|
|
101
110
|
this.forfeitOutputScript = forfeitOutputScript;
|
|
111
|
+
this.forfeitPubkey = forfeitPubkey;
|
|
102
112
|
this.dustAmount = dustAmount;
|
|
103
113
|
this.walletRepository = walletRepository;
|
|
104
114
|
this.contractRepository = contractRepository;
|
|
115
|
+
this.renewalConfig = {
|
|
116
|
+
enabled: renewalConfig?.enabled ?? false,
|
|
117
|
+
...vtxo_manager_1.DEFAULT_RENEWAL_CONFIG,
|
|
118
|
+
...renewalConfig,
|
|
119
|
+
};
|
|
105
120
|
}
|
|
106
121
|
static async create(config) {
|
|
107
122
|
const pubkey = await config.identity.xOnlyPublicKey();
|
|
108
123
|
if (!pubkey) {
|
|
109
124
|
throw new Error("Invalid configured public key");
|
|
110
125
|
}
|
|
111
|
-
|
|
112
|
-
const
|
|
126
|
+
// Use provided arkProvider instance or create a new one from arkServerUrl
|
|
127
|
+
const arkProvider = config.arkProvider ||
|
|
128
|
+
(() => {
|
|
129
|
+
if (!config.arkServerUrl) {
|
|
130
|
+
throw new Error("Either arkProvider or arkServerUrl must be provided");
|
|
131
|
+
}
|
|
132
|
+
return new ark_1.RestArkProvider(config.arkServerUrl);
|
|
133
|
+
})();
|
|
134
|
+
// Extract arkServerUrl from provider if not explicitly provided
|
|
135
|
+
const arkServerUrl = config.arkServerUrl || arkProvider.serverUrl;
|
|
136
|
+
if (!arkServerUrl) {
|
|
137
|
+
throw new Error("Could not determine arkServerUrl from provider");
|
|
138
|
+
}
|
|
139
|
+
// Use provided indexerProvider instance or create a new one
|
|
140
|
+
// indexerUrl defaults to arkServerUrl if not provided
|
|
141
|
+
const indexerUrl = config.indexerUrl || arkServerUrl;
|
|
142
|
+
const indexerProvider = config.indexerProvider || new indexer_1.RestIndexerProvider(indexerUrl);
|
|
113
143
|
const info = await arkProvider.getInfo();
|
|
114
144
|
const network = (0, networks_1.getNetwork)(info.network);
|
|
115
|
-
|
|
145
|
+
// Extract esploraUrl from provider if not explicitly provided
|
|
146
|
+
const esploraUrl = config.esploraUrl || onchain_1.ESPLORA_URL[info.network];
|
|
147
|
+
// Use provided onchainProvider instance or create a new one
|
|
148
|
+
const onchainProvider = config.onchainProvider || new onchain_1.EsploraProvider(esploraUrl);
|
|
149
|
+
// Generate timelocks
|
|
116
150
|
const exitTimelock = {
|
|
117
151
|
value: info.unilateralExitDelay,
|
|
118
152
|
type: info.unilateralExitDelay < 512n ? "blocks" : "seconds",
|
|
@@ -136,17 +170,24 @@ class Wallet {
|
|
|
136
170
|
// Save tapscripts
|
|
137
171
|
const offchainTapscript = bareVtxoTapscript;
|
|
138
172
|
// the serverUnrollScript is the one used to create output scripts of the checkpoint transactions
|
|
139
|
-
|
|
140
|
-
|
|
173
|
+
let serverUnrollScript;
|
|
174
|
+
try {
|
|
175
|
+
const raw = base_1.hex.decode(info.checkpointTapscript);
|
|
176
|
+
serverUnrollScript = tapscript_1.CSVMultisigTapscript.decode(raw);
|
|
177
|
+
}
|
|
178
|
+
catch (e) {
|
|
179
|
+
throw new Error("Invalid checkpointTapscript from server");
|
|
180
|
+
}
|
|
141
181
|
// parse the server forfeit address
|
|
142
182
|
// server is expecting funds to be sent to this address
|
|
143
|
-
const
|
|
144
|
-
const
|
|
183
|
+
const forfeitPubkey = base_1.hex.decode(info.forfeitPubkey).slice(1);
|
|
184
|
+
const forfeitAddress = (0, btc_signer_1.Address)(network).decode(info.forfeitAddress);
|
|
185
|
+
const forfeitOutputScript = btc_signer_1.OutScript.encode(forfeitAddress);
|
|
145
186
|
// Set up storage and repositories
|
|
146
187
|
const storage = config.storage || new inMemory_1.InMemoryStorageAdapter();
|
|
147
188
|
const walletRepository = new walletRepository_1.WalletRepositoryImpl(storage);
|
|
148
189
|
const contractRepository = new contractRepository_1.ContractRepositoryImpl(storage);
|
|
149
|
-
return new Wallet(config.identity, network, info.network, onchainProvider, arkProvider, indexerProvider, serverPubKey, offchainTapscript, boardingTapscript, serverUnrollScript, forfeitOutputScript, info.dust, walletRepository, contractRepository);
|
|
190
|
+
return new Wallet(config.identity, network, info.network, onchainProvider, arkProvider, indexerProvider, serverPubKey, offchainTapscript, boardingTapscript, serverUnrollScript, forfeitOutputScript, forfeitPubkey, info.dust, walletRepository, contractRepository, config.renewalConfig);
|
|
150
191
|
}
|
|
151
192
|
get arkAddress() {
|
|
152
193
|
return this.offchainTapscript.address(this.network.hrp, this.arkServerPublicKey);
|
|
@@ -208,40 +249,24 @@ class Wallet {
|
|
|
208
249
|
// if (cachedVtxos.length) return cachedVtxos;
|
|
209
250
|
// For now, always fetch fresh data from provider and update cache
|
|
210
251
|
// In future, we can add cache invalidation logic based on timestamps
|
|
211
|
-
const
|
|
212
|
-
const
|
|
213
|
-
const forfeit = this.offchainTapscript.forfeit();
|
|
214
|
-
const exit = this.offchainTapscript.exit();
|
|
215
|
-
const extendedVtxos = spendableVtxos.map((vtxo) => ({
|
|
216
|
-
...vtxo,
|
|
217
|
-
forfeitTapLeafScript: forfeit,
|
|
218
|
-
intentTapLeafScript: exit,
|
|
219
|
-
tapTree: encodedOffchainTapscript,
|
|
220
|
-
}));
|
|
252
|
+
const vtxos = await this.getVirtualCoins(filter);
|
|
253
|
+
const extendedVtxos = vtxos.map((vtxo) => (0, utils_1.extendVirtualCoin)(this, vtxo));
|
|
221
254
|
// Update cache with fresh data
|
|
222
255
|
await this.walletRepository.saveVtxos(address, extendedVtxos);
|
|
223
256
|
return extendedVtxos;
|
|
224
257
|
}
|
|
225
258
|
async getVirtualCoins(filter = { withRecoverable: true, withUnrolled: false }) {
|
|
226
259
|
const scripts = [base_1.hex.encode(this.offchainTapscript.pkScript)];
|
|
227
|
-
const response = await this.indexerProvider.getVtxos({
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const response = await this.indexerProvider.getVtxos({
|
|
234
|
-
scripts,
|
|
235
|
-
recoverableOnly: true,
|
|
236
|
-
});
|
|
237
|
-
vtxos.push(...response.vtxos);
|
|
260
|
+
const response = await this.indexerProvider.getVtxos({ scripts });
|
|
261
|
+
const allVtxos = response.vtxos;
|
|
262
|
+
let vtxos = allVtxos.filter(_1.isSpendable);
|
|
263
|
+
// all recoverable vtxos are spendable by definition
|
|
264
|
+
if (!filter.withRecoverable) {
|
|
265
|
+
vtxos = vtxos.filter((vtxo) => !(0, _1.isRecoverable)(vtxo));
|
|
238
266
|
}
|
|
239
267
|
if (filter.withUnrolled) {
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
spentOnly: true,
|
|
243
|
-
});
|
|
244
|
-
vtxos.push(...response.vtxos.filter((vtxo) => vtxo.isUnrolled));
|
|
268
|
+
const spentVtxos = allVtxos.filter((vtxo) => !(0, _1.isSpendable)(vtxo));
|
|
269
|
+
vtxos.push(...spentVtxos.filter((vtxo) => vtxo.isUnrolled));
|
|
245
270
|
}
|
|
246
271
|
return vtxos;
|
|
247
272
|
}
|
|
@@ -279,10 +304,10 @@ class Wallet {
|
|
|
279
304
|
return txs;
|
|
280
305
|
}
|
|
281
306
|
async getBoardingTxs() {
|
|
282
|
-
const boardingAddress = await this.getBoardingAddress();
|
|
283
|
-
const txs = await this.onchainProvider.getTransactions(boardingAddress);
|
|
284
307
|
const utxos = [];
|
|
285
308
|
const commitmentsToIgnore = new Set();
|
|
309
|
+
const boardingAddress = await this.getBoardingAddress();
|
|
310
|
+
const txs = await this.onchainProvider.getTransactions(boardingAddress);
|
|
286
311
|
for (const tx of txs) {
|
|
287
312
|
for (let i = 0; i < tx.vout.length; i++) {
|
|
288
313
|
const vout = tx.vout[i];
|
|
@@ -345,15 +370,12 @@ class Wallet {
|
|
|
345
370
|
async getBoardingUtxos() {
|
|
346
371
|
const boardingAddress = await this.getBoardingAddress();
|
|
347
372
|
const boardingUtxos = await this.onchainProvider.getCoins(boardingAddress);
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
intentTapLeafScript: exit,
|
|
355
|
-
tapTree: encodedBoardingTapscript,
|
|
356
|
-
}));
|
|
373
|
+
const utxos = boardingUtxos.map((utxo) => {
|
|
374
|
+
return (0, utils_1.extendCoin)(this, utxo);
|
|
375
|
+
});
|
|
376
|
+
// Save boardingUtxos using unified repository
|
|
377
|
+
await this.walletRepository.saveUtxos(boardingAddress, utxos);
|
|
378
|
+
return utxos;
|
|
357
379
|
}
|
|
358
380
|
async sendBitcoin(params) {
|
|
359
381
|
if (params.amount <= 0) {
|
|
@@ -402,7 +424,7 @@ class Wallet {
|
|
|
402
424
|
// TODO persist final virtual tx and checkpoints to repository
|
|
403
425
|
// sign the checkpoints
|
|
404
426
|
const finalCheckpoints = await Promise.all(signedCheckpointTxs.map(async (c) => {
|
|
405
|
-
const tx =
|
|
427
|
+
const tx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(c));
|
|
406
428
|
const signedCheckpoint = await this.identity.sign(tx);
|
|
407
429
|
return base_1.base64.encode(signedCheckpoint.toPSBT());
|
|
408
430
|
}));
|
|
@@ -423,13 +445,15 @@ class Wallet {
|
|
|
423
445
|
}
|
|
424
446
|
}
|
|
425
447
|
}
|
|
426
|
-
// if no params are provided, use all boarding and offchain
|
|
448
|
+
// if no params are provided, use all non expired boarding utxos and offchain vtxos as inputs
|
|
427
449
|
// and send all to the offchain address
|
|
428
450
|
if (!params) {
|
|
429
451
|
let amount = 0;
|
|
430
|
-
const
|
|
452
|
+
const exitScript = tapscript_1.CSVMultisigTapscript.decode(base_1.hex.decode(this.boardingTapscript.exitScript));
|
|
453
|
+
const boardingTimelock = exitScript.params.timelock;
|
|
454
|
+
const boardingUtxos = (await this.getBoardingUtxos()).filter((utxo) => !(0, arkTransaction_1.hasBoardingTxExpired)(utxo, boardingTimelock));
|
|
431
455
|
amount += boardingUtxos.reduce((sum, input) => sum + input.value, 0);
|
|
432
|
-
const vtxos = await this.getVtxos();
|
|
456
|
+
const vtxos = await this.getVtxos({ withRecoverable: true });
|
|
433
457
|
amount += vtxos.reduce((sum, input) => sum + input.value, 0);
|
|
434
458
|
const inputs = [...boardingUtxos, ...vtxos];
|
|
435
459
|
if (inputs.length === 0) {
|
|
@@ -458,8 +482,8 @@ class Wallet {
|
|
|
458
482
|
}
|
|
459
483
|
catch {
|
|
460
484
|
// onchain
|
|
461
|
-
const addr = (0,
|
|
462
|
-
script =
|
|
485
|
+
const addr = (0, btc_signer_1.Address)(this.network).decode(output.address);
|
|
486
|
+
script = btc_signer_1.OutScript.encode(addr);
|
|
463
487
|
onchainOutputIndexes.push(index);
|
|
464
488
|
}
|
|
465
489
|
outputs.push({
|
|
@@ -472,7 +496,7 @@ class Wallet {
|
|
|
472
496
|
const signingPublicKeys = [];
|
|
473
497
|
if (hasOffchainOutputs) {
|
|
474
498
|
session = this.identity.signerSession();
|
|
475
|
-
signingPublicKeys.push(base_1.hex.encode(session.getPublicKey()));
|
|
499
|
+
signingPublicKeys.push(base_1.hex.encode(await session.getPublicKey()));
|
|
476
500
|
}
|
|
477
501
|
const [intent, deleteIntent] = await Promise.all([
|
|
478
502
|
this.makeRegisterIntentSignature(params.inputs, outputs, onchainOutputIndexes, signingPublicKeys),
|
|
@@ -488,8 +512,8 @@ class Wallet {
|
|
|
488
512
|
...params.inputs.map((input) => `${input.txid}:${input.vout}`),
|
|
489
513
|
];
|
|
490
514
|
const settlementStream = this.arkProvider.getEventStream(abortController.signal, topics);
|
|
491
|
-
//
|
|
492
|
-
let
|
|
515
|
+
// batchId, sweepTapTreeRoot and forfeitOutputScript are set once the BatchStarted event is received
|
|
516
|
+
let batchId;
|
|
493
517
|
let sweepTapTreeRoot;
|
|
494
518
|
const vtxoChunks = [];
|
|
495
519
|
const connectorsChunks = [];
|
|
@@ -502,30 +526,26 @@ class Wallet {
|
|
|
502
526
|
switch (event.type) {
|
|
503
527
|
// the settlement failed
|
|
504
528
|
case ark_1.SettlementEventType.BatchFailed:
|
|
505
|
-
|
|
506
|
-
if (event.id === roundId) {
|
|
507
|
-
throw new Error(event.reason);
|
|
508
|
-
}
|
|
509
|
-
break;
|
|
529
|
+
throw new Error(event.reason);
|
|
510
530
|
case ark_1.SettlementEventType.BatchStarted:
|
|
511
531
|
if (step !== undefined) {
|
|
512
532
|
continue;
|
|
513
533
|
}
|
|
514
|
-
const res = await this.handleBatchStartedEvent(event, intentId, this.
|
|
534
|
+
const res = await this.handleBatchStartedEvent(event, intentId, this.forfeitPubkey, this.forfeitOutputScript);
|
|
515
535
|
if (!res.skip) {
|
|
516
536
|
step = event.type;
|
|
517
537
|
sweepTapTreeRoot = res.sweepTapTreeRoot;
|
|
518
|
-
|
|
538
|
+
batchId = res.roundId;
|
|
519
539
|
if (!hasOffchainOutputs) {
|
|
520
540
|
// if there are no offchain outputs, we don't have to handle musig2 tree signatures
|
|
521
541
|
// we can directly advance to the finalization step
|
|
522
|
-
step = ark_1.SettlementEventType.
|
|
542
|
+
step = ark_1.SettlementEventType.TreeNonces;
|
|
523
543
|
}
|
|
524
544
|
}
|
|
525
545
|
break;
|
|
526
546
|
case ark_1.SettlementEventType.TreeTx:
|
|
527
547
|
if (step !== ark_1.SettlementEventType.BatchStarted &&
|
|
528
|
-
step !== ark_1.SettlementEventType.
|
|
548
|
+
step !== ark_1.SettlementEventType.TreeNonces) {
|
|
529
549
|
continue;
|
|
530
550
|
}
|
|
531
551
|
// index 0 = vtxo tree
|
|
@@ -541,7 +561,7 @@ class Wallet {
|
|
|
541
561
|
}
|
|
542
562
|
break;
|
|
543
563
|
case ark_1.SettlementEventType.TreeSignature:
|
|
544
|
-
if (step !== ark_1.SettlementEventType.
|
|
564
|
+
if (step !== ark_1.SettlementEventType.TreeNonces) {
|
|
545
565
|
continue;
|
|
546
566
|
}
|
|
547
567
|
if (!hasOffchainOutputs) {
|
|
@@ -583,7 +603,7 @@ class Wallet {
|
|
|
583
603
|
break;
|
|
584
604
|
// the musig2 nonces of the vtxo tree transactions are generated
|
|
585
605
|
// the server expects now the partial musig2 signatures
|
|
586
|
-
case ark_1.SettlementEventType.
|
|
606
|
+
case ark_1.SettlementEventType.TreeNonces:
|
|
587
607
|
if (step !== ark_1.SettlementEventType.TreeSigningStarted) {
|
|
588
608
|
continue;
|
|
589
609
|
}
|
|
@@ -591,14 +611,18 @@ class Wallet {
|
|
|
591
611
|
if (!session) {
|
|
592
612
|
throw new Error("Signing session not set");
|
|
593
613
|
}
|
|
594
|
-
await this.
|
|
614
|
+
const signed = await this.handleSettlementTreeNoncesEvent(event, session);
|
|
615
|
+
if (signed) {
|
|
616
|
+
step = event.type;
|
|
617
|
+
}
|
|
618
|
+
break;
|
|
595
619
|
}
|
|
596
620
|
step = event.type;
|
|
597
621
|
break;
|
|
598
622
|
// the vtxo tree is signed, craft, sign and submit forfeit transactions
|
|
599
623
|
// if any boarding utxos are involved, the settlement tx is also signed
|
|
600
624
|
case ark_1.SettlementEventType.BatchFinalization:
|
|
601
|
-
if (step !== ark_1.SettlementEventType.
|
|
625
|
+
if (step !== ark_1.SettlementEventType.TreeNonces) {
|
|
602
626
|
continue;
|
|
603
627
|
}
|
|
604
628
|
if (!this.forfeitOutputScript) {
|
|
@@ -616,8 +640,10 @@ class Wallet {
|
|
|
616
640
|
if (step !== ark_1.SettlementEventType.BatchFinalization) {
|
|
617
641
|
continue;
|
|
618
642
|
}
|
|
619
|
-
|
|
620
|
-
|
|
643
|
+
if (event.id === batchId) {
|
|
644
|
+
abortController.abort();
|
|
645
|
+
return event.commitmentTxid;
|
|
646
|
+
}
|
|
621
647
|
}
|
|
622
648
|
}
|
|
623
649
|
}
|
|
@@ -639,22 +665,22 @@ class Wallet {
|
|
|
639
665
|
let onchainStopFunc;
|
|
640
666
|
let indexerStopFunc;
|
|
641
667
|
if (this.onchainProvider && boardingAddress) {
|
|
668
|
+
const findVoutOnTx = (tx) => {
|
|
669
|
+
return tx.vout.findIndex((v) => v.scriptpubkey_address === boardingAddress);
|
|
670
|
+
};
|
|
642
671
|
onchainStopFunc = await this.onchainProvider.watchAddresses([boardingAddress], (txs) => {
|
|
672
|
+
// find all utxos belonging to our boarding address
|
|
643
673
|
const coins = txs
|
|
674
|
+
// filter txs where address is in output
|
|
675
|
+
.filter((tx) => findVoutOnTx(tx) !== -1)
|
|
676
|
+
// return utxo as Coin
|
|
644
677
|
.map((tx) => {
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
txid: tx.txid,
|
|
652
|
-
vout,
|
|
653
|
-
value: Number(tx.vout[vout].value),
|
|
654
|
-
status: tx.status,
|
|
655
|
-
};
|
|
656
|
-
})
|
|
657
|
-
.filter((coin) => coin !== null);
|
|
678
|
+
const { txid, status } = tx;
|
|
679
|
+
const vout = findVoutOnTx(tx);
|
|
680
|
+
const value = Number(tx.vout[vout].value);
|
|
681
|
+
return { txid, vout, value, status };
|
|
682
|
+
});
|
|
683
|
+
// and notify via callback
|
|
658
684
|
eventCallback({
|
|
659
685
|
type: "utxo",
|
|
660
686
|
coins,
|
|
@@ -696,10 +722,10 @@ class Wallet {
|
|
|
696
722
|
};
|
|
697
723
|
return stopFunc;
|
|
698
724
|
}
|
|
699
|
-
async handleBatchStartedEvent(event, intentId,
|
|
725
|
+
async handleBatchStartedEvent(event, intentId, forfeitPubKey, forfeitOutputScript) {
|
|
700
726
|
const utf8IntentId = new TextEncoder().encode(intentId);
|
|
701
727
|
const intentIdHash = (0, utils_js_1.sha256)(utf8IntentId);
|
|
702
|
-
const intentIdHashStr = base_1.hex.encode(
|
|
728
|
+
const intentIdHashStr = base_1.hex.encode(intentIdHash);
|
|
703
729
|
let skip = true;
|
|
704
730
|
// check if our intent ID hash matches any in the event
|
|
705
731
|
for (const idHash of event.intentIdHashes) {
|
|
@@ -719,7 +745,7 @@ class Wallet {
|
|
|
719
745
|
value: event.batchExpiry,
|
|
720
746
|
type: event.batchExpiry >= 512n ? "seconds" : "blocks",
|
|
721
747
|
},
|
|
722
|
-
pubkeys: [
|
|
748
|
+
pubkeys: [forfeitPubKey],
|
|
723
749
|
}).script;
|
|
724
750
|
const sweepTapTreeRoot = (0, payment_js_1.tapLeafHash)(sweepTapscript);
|
|
725
751
|
return {
|
|
@@ -732,7 +758,7 @@ class Wallet {
|
|
|
732
758
|
// validates the vtxo tree, creates a signing session and generates the musig2 nonces
|
|
733
759
|
async handleSettlementSigningEvent(event, sweepTapTreeRoot, session, vtxoGraph) {
|
|
734
760
|
// validate the unsigned vtxo tree
|
|
735
|
-
const commitmentTx =
|
|
761
|
+
const commitmentTx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(event.unsignedCommitmentTx));
|
|
736
762
|
(0, validation_1.validateVtxoTxGraph)(vtxoGraph, commitmentTx, sweepTapTreeRoot);
|
|
737
763
|
// TODO check if our registered outputs are in the vtxo tree
|
|
738
764
|
const sharedOutput = commitmentTx.getOutput(0);
|
|
@@ -740,18 +766,25 @@ class Wallet {
|
|
|
740
766
|
throw new Error("Shared output not found");
|
|
741
767
|
}
|
|
742
768
|
session.init(vtxoGraph, sweepTapTreeRoot, sharedOutput.amount);
|
|
743
|
-
|
|
769
|
+
const pubkey = base_1.hex.encode(await session.getPublicKey());
|
|
770
|
+
const nonces = await session.getNonces();
|
|
771
|
+
await this.arkProvider.submitTreeNonces(event.id, pubkey, nonces);
|
|
744
772
|
}
|
|
745
|
-
async
|
|
746
|
-
session.
|
|
747
|
-
|
|
748
|
-
|
|
773
|
+
async handleSettlementTreeNoncesEvent(event, session) {
|
|
774
|
+
const { hasAllNonces } = await session.aggregatedNonces(event.txid, event.nonces);
|
|
775
|
+
// wait to receive and aggregate all nonces before sending signatures
|
|
776
|
+
if (!hasAllNonces)
|
|
777
|
+
return false;
|
|
778
|
+
const signatures = await session.sign();
|
|
779
|
+
const pubkey = base_1.hex.encode(await session.getPublicKey());
|
|
780
|
+
await this.arkProvider.submitTreeSignatures(event.id, pubkey, signatures);
|
|
781
|
+
return true;
|
|
749
782
|
}
|
|
750
783
|
async handleSettlementFinalizationEvent(event, inputs, forfeitOutputScript, connectorsGraph) {
|
|
751
784
|
// the signed forfeits transactions to submit
|
|
752
785
|
const signedForfeits = [];
|
|
753
786
|
const vtxos = await this.getVirtualCoins();
|
|
754
|
-
let settlementPsbt =
|
|
787
|
+
let settlementPsbt = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(event.commitmentTx));
|
|
755
788
|
let hasBoardingUtxos = false;
|
|
756
789
|
let connectorIndex = 0;
|
|
757
790
|
const connectorsLeaves = connectorsGraph?.leaves() || [];
|
|
@@ -760,8 +793,6 @@ class Wallet {
|
|
|
760
793
|
const vtxo = vtxos.find((vtxo) => vtxo.txid === input.txid && vtxo.vout === input.vout);
|
|
761
794
|
// boarding utxo, we need to sign the settlement tx
|
|
762
795
|
if (!vtxo) {
|
|
763
|
-
hasBoardingUtxos = true;
|
|
764
|
-
const inputIndexes = [];
|
|
765
796
|
for (let i = 0; i < settlementPsbt.inputsLength; i++) {
|
|
766
797
|
const settlementInput = settlementPsbt.getInput(i);
|
|
767
798
|
if (!settlementInput.txid ||
|
|
@@ -777,9 +808,12 @@ class Wallet {
|
|
|
777
808
|
settlementPsbt.updateInput(i, {
|
|
778
809
|
tapLeafScript: [input.forfeitTapLeafScript],
|
|
779
810
|
});
|
|
780
|
-
|
|
811
|
+
settlementPsbt = await this.identity.sign(settlementPsbt, [
|
|
812
|
+
i,
|
|
813
|
+
]);
|
|
814
|
+
hasBoardingUtxos = true;
|
|
815
|
+
break;
|
|
781
816
|
}
|
|
782
|
-
settlementPsbt = await this.identity.sign(settlementPsbt, inputIndexes);
|
|
783
817
|
continue;
|
|
784
818
|
}
|
|
785
819
|
if ((0, _1.isRecoverable)(vtxo) || (0, _1.isSubdust)(vtxo, this.dustAmount)) {
|
|
@@ -793,7 +827,7 @@ class Wallet {
|
|
|
793
827
|
throw new Error("not enough connectors received");
|
|
794
828
|
}
|
|
795
829
|
const connectorLeaf = connectorsLeaves[connectorIndex];
|
|
796
|
-
const connectorTxId =
|
|
830
|
+
const connectorTxId = connectorLeaf.id;
|
|
797
831
|
const connectorOutput = connectorLeaf.getOutput(0);
|
|
798
832
|
if (!connectorOutput) {
|
|
799
833
|
throw new Error("connector output not found");
|
|
@@ -812,7 +846,7 @@ class Wallet {
|
|
|
812
846
|
amount: BigInt(vtxo.value),
|
|
813
847
|
script: base_2.VtxoScript.decode(input.tapTree).pkScript,
|
|
814
848
|
},
|
|
815
|
-
sighashType:
|
|
849
|
+
sighashType: btc_signer_1.SigHash.DEFAULT,
|
|
816
850
|
tapLeafScript: [input.forfeitTapLeafScript],
|
|
817
851
|
},
|
|
818
852
|
{
|
|
@@ -834,112 +868,69 @@ class Wallet {
|
|
|
834
868
|
: undefined);
|
|
835
869
|
}
|
|
836
870
|
}
|
|
837
|
-
async makeRegisterIntentSignature(
|
|
871
|
+
async makeRegisterIntentSignature(coins, outputs, onchainOutputsIndexes, cosignerPubKeys) {
|
|
838
872
|
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
839
|
-
const
|
|
873
|
+
const inputs = this.prepareIntentProofInputs(coins);
|
|
840
874
|
const message = {
|
|
841
875
|
type: "register",
|
|
842
|
-
input_tap_trees: inputTapTrees,
|
|
843
876
|
onchain_output_indexes: onchainOutputsIndexes,
|
|
844
877
|
valid_at: nowSeconds,
|
|
845
878
|
expire_at: nowSeconds + 2 * 60, // valid for 2 minutes
|
|
846
879
|
cosigners_public_keys: cosignerPubKeys,
|
|
847
880
|
};
|
|
848
881
|
const encodedMessage = JSON.stringify(message, null, 0);
|
|
849
|
-
const
|
|
882
|
+
const proof = intent_1.Intent.create(encodedMessage, inputs, outputs);
|
|
883
|
+
const signedProof = await this.identity.sign(proof);
|
|
850
884
|
return {
|
|
851
|
-
|
|
885
|
+
proof: base_1.base64.encode(signedProof.toPSBT()),
|
|
852
886
|
message: encodedMessage,
|
|
853
887
|
};
|
|
854
888
|
}
|
|
855
|
-
async makeDeleteIntentSignature(
|
|
889
|
+
async makeDeleteIntentSignature(coins) {
|
|
856
890
|
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
857
|
-
const
|
|
891
|
+
const inputs = this.prepareIntentProofInputs(coins);
|
|
858
892
|
const message = {
|
|
859
893
|
type: "delete",
|
|
860
894
|
expire_at: nowSeconds + 2 * 60, // valid for 2 minutes
|
|
861
895
|
};
|
|
862
896
|
const encodedMessage = JSON.stringify(message, null, 0);
|
|
863
|
-
const
|
|
897
|
+
const proof = intent_1.Intent.create(encodedMessage, inputs, []);
|
|
898
|
+
const signedProof = await this.identity.sign(proof);
|
|
864
899
|
return {
|
|
865
|
-
|
|
900
|
+
proof: base_1.base64.encode(signedProof.toPSBT()),
|
|
866
901
|
message: encodedMessage,
|
|
867
902
|
};
|
|
868
903
|
}
|
|
869
|
-
|
|
904
|
+
prepareIntentProofInputs(coins) {
|
|
870
905
|
const inputs = [];
|
|
871
|
-
const
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
const
|
|
875
|
-
|
|
906
|
+
for (const input of coins) {
|
|
907
|
+
const vtxoScript = base_2.VtxoScript.decode(input.tapTree);
|
|
908
|
+
const sequence = getSequence(input);
|
|
909
|
+
const unknown = [unknownFields_1.VtxoTaprootTree.encode(input.tapTree)];
|
|
910
|
+
if (input.extraWitness) {
|
|
911
|
+
unknown.push(unknownFields_1.ConditionWitness.encode(input.extraWitness));
|
|
912
|
+
}
|
|
876
913
|
inputs.push({
|
|
877
|
-
txid: base_1.hex.decode(
|
|
878
|
-
index:
|
|
914
|
+
txid: base_1.hex.decode(input.txid),
|
|
915
|
+
index: input.vout,
|
|
879
916
|
witnessUtxo: {
|
|
880
|
-
amount: BigInt(
|
|
917
|
+
amount: BigInt(input.value),
|
|
881
918
|
script: vtxoScript.pkScript,
|
|
882
919
|
},
|
|
883
920
|
sequence,
|
|
884
|
-
tapLeafScript: [
|
|
921
|
+
tapLeafScript: [input.intentTapLeafScript],
|
|
922
|
+
unknown,
|
|
885
923
|
});
|
|
886
|
-
inputTapTrees.push(base_1.hex.encode(bip322Input.tapTree));
|
|
887
|
-
inputExtraWitnesses.push(bip322Input.extraWitness || []);
|
|
888
924
|
}
|
|
889
|
-
return
|
|
890
|
-
inputs,
|
|
891
|
-
inputTapTrees,
|
|
892
|
-
finalizer: finalizeWithExtraWitnesses(inputExtraWitnesses),
|
|
893
|
-
};
|
|
894
|
-
}
|
|
895
|
-
async makeBIP322Signature(message, inputs, finalizer, outputs) {
|
|
896
|
-
const proof = bip322_1.BIP322.create(message, inputs, outputs);
|
|
897
|
-
const signedProof = await this.identity.sign(proof);
|
|
898
|
-
return bip322_1.BIP322.signature(signedProof, finalizer);
|
|
925
|
+
return inputs;
|
|
899
926
|
}
|
|
900
927
|
}
|
|
901
928
|
exports.Wallet = Wallet;
|
|
902
929
|
Wallet.MIN_FEE_RATE = 1; // sats/vbyte
|
|
903
|
-
function
|
|
904
|
-
return function (tx) {
|
|
905
|
-
for (let i = 0; i < tx.inputsLength; i++) {
|
|
906
|
-
try {
|
|
907
|
-
tx.finalizeIdx(i);
|
|
908
|
-
}
|
|
909
|
-
catch (e) {
|
|
910
|
-
// handle empty witness error
|
|
911
|
-
if (e instanceof Error &&
|
|
912
|
-
e.message.includes("finalize/taproot: empty witness")) {
|
|
913
|
-
const tapLeaves = tx.getInput(i).tapLeafScript;
|
|
914
|
-
if (!tapLeaves || tapLeaves.length <= 0)
|
|
915
|
-
throw e;
|
|
916
|
-
const [cb, s] = tapLeaves[0];
|
|
917
|
-
const script = s.slice(0, -1);
|
|
918
|
-
tx.updateInput(i, {
|
|
919
|
-
finalScriptWitness: [
|
|
920
|
-
script,
|
|
921
|
-
psbt_js_1.TaprootControlBlock.encode(cb),
|
|
922
|
-
],
|
|
923
|
-
});
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
const finalScriptWitness = tx.getInput(i).finalScriptWitness;
|
|
927
|
-
if (!finalScriptWitness)
|
|
928
|
-
throw new Error("input not finalized");
|
|
929
|
-
// input 0 and 1 spend the same pkscript
|
|
930
|
-
const extra = inputExtraWitnesses[i === 0 ? 0 : i - 1];
|
|
931
|
-
if (extra && extra.length > 0) {
|
|
932
|
-
tx.updateInput(i, {
|
|
933
|
-
finalScriptWitness: [...extra, ...finalScriptWitness],
|
|
934
|
-
});
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
};
|
|
938
|
-
}
|
|
939
|
-
function getSequence(bip322Input) {
|
|
930
|
+
function getSequence(coin) {
|
|
940
931
|
let sequence = undefined;
|
|
941
932
|
try {
|
|
942
|
-
const scriptWithLeafVersion =
|
|
933
|
+
const scriptWithLeafVersion = coin.intentTapLeafScript[1];
|
|
943
934
|
const script = scriptWithLeafVersion.subarray(0, scriptWithLeafVersion.length - 1);
|
|
944
935
|
const params = tapscript_1.CSVMultisigTapscript.decode(script).params;
|
|
945
936
|
sequence = bip68.encode(params.timelock.type === "blocks"
|