@arkade-os/sdk 0.3.0-alpha.7 → 0.3.1-alpha.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 +115 -14
- package/dist/cjs/adapters/expo.js +8 -0
- package/dist/cjs/arknote/index.js +3 -3
- package/dist/cjs/forfeit.js +5 -2
- package/dist/cjs/identity/singleKey.js +5 -4
- package/dist/cjs/index.js +7 -3
- package/dist/cjs/{bip322 → intent}/index.js +37 -55
- package/dist/cjs/providers/ark.js +62 -23
- package/dist/cjs/providers/expoArk.js +82 -0
- package/dist/cjs/providers/expoIndexer.js +105 -0
- package/dist/cjs/providers/indexer.js +3 -1
- package/dist/cjs/providers/utils.js +122 -0
- package/dist/cjs/script/base.js +1 -2
- package/dist/cjs/script/tapscript.js +20 -21
- package/dist/cjs/script/vhtlc.js +2 -2
- package/dist/cjs/tree/signingSession.js +7 -8
- package/dist/cjs/tree/txTree.js +3 -4
- package/dist/cjs/tree/validation.js +2 -3
- package/dist/cjs/utils/arkTransaction.js +117 -12
- package/dist/cjs/utils/unknownFields.js +5 -5
- package/dist/cjs/wallet/index.js +1 -1
- package/dist/cjs/wallet/onchain.js +4 -5
- package/dist/cjs/wallet/serviceWorker/utils.js +2 -9
- package/dist/cjs/wallet/serviceWorker/wallet.js +4 -8
- package/dist/cjs/wallet/serviceWorker/worker.js +25 -23
- package/dist/cjs/wallet/unroll.js +6 -7
- package/dist/cjs/wallet/utils.js +11 -0
- package/dist/cjs/wallet/vtxo-manager.js +381 -0
- package/dist/cjs/wallet/wallet.js +130 -143
- package/dist/esm/adapters/expo.js +3 -0
- package/dist/esm/arknote/index.js +2 -2
- package/dist/esm/forfeit.js +4 -1
- package/dist/esm/identity/singleKey.js +7 -6
- package/dist/esm/index.js +7 -6
- package/dist/esm/{bip322 → intent}/index.js +31 -48
- package/dist/esm/providers/ark.js +62 -23
- package/dist/esm/providers/expoArk.js +78 -0
- package/dist/esm/providers/expoIndexer.js +101 -0
- package/dist/esm/providers/indexer.js +3 -1
- package/dist/esm/providers/utils.js +87 -0
- package/dist/esm/script/base.js +1 -2
- package/dist/esm/script/tapscript.js +1 -2
- package/dist/esm/script/vhtlc.js +1 -1
- package/dist/esm/tree/signingSession.js +8 -9
- package/dist/esm/tree/txTree.js +3 -4
- package/dist/esm/tree/validation.js +2 -3
- package/dist/esm/utils/arkTransaction.js +108 -5
- package/dist/esm/utils/unknownFields.js +1 -1
- package/dist/esm/wallet/index.js +1 -1
- package/dist/esm/wallet/onchain.js +1 -2
- package/dist/esm/wallet/serviceWorker/utils.js +1 -8
- package/dist/esm/wallet/serviceWorker/wallet.js +5 -9
- package/dist/esm/wallet/serviceWorker/worker.js +26 -24
- package/dist/esm/wallet/unroll.js +2 -3
- package/dist/esm/wallet/utils.js +8 -0
- package/dist/esm/wallet/vtxo-manager.js +372 -0
- package/dist/esm/wallet/wallet.js +124 -137
- 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 +1 -1
- package/dist/types/identity/singleKey.d.ts +1 -1
- package/dist/types/index.d.ts +8 -7
- package/dist/types/intent/index.d.ts +41 -0
- package/dist/types/providers/ark.d.ts +190 -22
- package/dist/types/providers/expoArk.d.ts +22 -0
- package/dist/types/providers/expoIndexer.d.ts +18 -0
- package/dist/types/providers/indexer.d.ts +8 -8
- package/dist/types/providers/utils.d.ts +18 -0
- package/dist/types/script/base.d.ts +3 -2
- package/dist/types/tree/signingSession.d.ts +10 -10
- package/dist/types/utils/anchor.d.ts +2 -2
- package/dist/types/utils/arkTransaction.d.ts +16 -4
- package/dist/types/utils/unknownFields.d.ts +2 -2
- package/dist/types/wallet/index.d.ts +47 -7
- package/dist/types/wallet/onchain.d.ts +1 -1
- 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 +3 -1
- package/dist/types/wallet/unroll.d.ts +1 -1
- package/dist/types/wallet/utils.d.ts +2 -0
- package/dist/types/wallet/vtxo-manager.d.ts +207 -0
- package/dist/types/wallet/wallet.d.ts +16 -4
- 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];
|
|
@@ -402,7 +427,7 @@ class Wallet {
|
|
|
402
427
|
// TODO persist final virtual tx and checkpoints to repository
|
|
403
428
|
// sign the checkpoints
|
|
404
429
|
const finalCheckpoints = await Promise.all(signedCheckpointTxs.map(async (c) => {
|
|
405
|
-
const tx =
|
|
430
|
+
const tx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(c));
|
|
406
431
|
const signedCheckpoint = await this.identity.sign(tx);
|
|
407
432
|
return base_1.base64.encode(signedCheckpoint.toPSBT());
|
|
408
433
|
}));
|
|
@@ -423,13 +448,15 @@ class Wallet {
|
|
|
423
448
|
}
|
|
424
449
|
}
|
|
425
450
|
}
|
|
426
|
-
// if no params are provided, use all boarding and offchain
|
|
451
|
+
// if no params are provided, use all non expired boarding utxos and offchain vtxos as inputs
|
|
427
452
|
// and send all to the offchain address
|
|
428
453
|
if (!params) {
|
|
429
454
|
let amount = 0;
|
|
430
|
-
const
|
|
455
|
+
const exitScript = tapscript_1.CSVMultisigTapscript.decode(base_1.hex.decode(this.boardingTapscript.exitScript));
|
|
456
|
+
const boardingTimelock = exitScript.params.timelock;
|
|
457
|
+
const boardingUtxos = (await this.getBoardingUtxos()).filter((utxo) => !(0, arkTransaction_1.hasBoardingTxExpired)(utxo, boardingTimelock));
|
|
431
458
|
amount += boardingUtxos.reduce((sum, input) => sum + input.value, 0);
|
|
432
|
-
const vtxos = await this.getVtxos();
|
|
459
|
+
const vtxos = await this.getVtxos({ withRecoverable: true });
|
|
433
460
|
amount += vtxos.reduce((sum, input) => sum + input.value, 0);
|
|
434
461
|
const inputs = [...boardingUtxos, ...vtxos];
|
|
435
462
|
if (inputs.length === 0) {
|
|
@@ -458,8 +485,8 @@ class Wallet {
|
|
|
458
485
|
}
|
|
459
486
|
catch {
|
|
460
487
|
// onchain
|
|
461
|
-
const addr = (0,
|
|
462
|
-
script =
|
|
488
|
+
const addr = (0, btc_signer_1.Address)(this.network).decode(output.address);
|
|
489
|
+
script = btc_signer_1.OutScript.encode(addr);
|
|
463
490
|
onchainOutputIndexes.push(index);
|
|
464
491
|
}
|
|
465
492
|
outputs.push({
|
|
@@ -472,7 +499,7 @@ class Wallet {
|
|
|
472
499
|
const signingPublicKeys = [];
|
|
473
500
|
if (hasOffchainOutputs) {
|
|
474
501
|
session = this.identity.signerSession();
|
|
475
|
-
signingPublicKeys.push(base_1.hex.encode(session.getPublicKey()));
|
|
502
|
+
signingPublicKeys.push(base_1.hex.encode(await session.getPublicKey()));
|
|
476
503
|
}
|
|
477
504
|
const [intent, deleteIntent] = await Promise.all([
|
|
478
505
|
this.makeRegisterIntentSignature(params.inputs, outputs, onchainOutputIndexes, signingPublicKeys),
|
|
@@ -511,7 +538,7 @@ class Wallet {
|
|
|
511
538
|
if (step !== undefined) {
|
|
512
539
|
continue;
|
|
513
540
|
}
|
|
514
|
-
const res = await this.handleBatchStartedEvent(event, intentId, this.
|
|
541
|
+
const res = await this.handleBatchStartedEvent(event, intentId, this.forfeitPubkey, this.forfeitOutputScript);
|
|
515
542
|
if (!res.skip) {
|
|
516
543
|
step = event.type;
|
|
517
544
|
sweepTapTreeRoot = res.sweepTapTreeRoot;
|
|
@@ -639,22 +666,22 @@ class Wallet {
|
|
|
639
666
|
let onchainStopFunc;
|
|
640
667
|
let indexerStopFunc;
|
|
641
668
|
if (this.onchainProvider && boardingAddress) {
|
|
669
|
+
const findVoutOnTx = (tx) => {
|
|
670
|
+
return tx.vout.findIndex((v) => v.scriptpubkey_address === boardingAddress);
|
|
671
|
+
};
|
|
642
672
|
onchainStopFunc = await this.onchainProvider.watchAddresses([boardingAddress], (txs) => {
|
|
673
|
+
// find all utxos belonging to our boarding address
|
|
643
674
|
const coins = txs
|
|
675
|
+
// filter txs where address is in output
|
|
676
|
+
.filter((tx) => findVoutOnTx(tx) !== -1)
|
|
677
|
+
// return utxo as Coin
|
|
644
678
|
.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);
|
|
679
|
+
const { txid, status } = tx;
|
|
680
|
+
const vout = findVoutOnTx(tx);
|
|
681
|
+
const value = Number(tx.vout[vout].value);
|
|
682
|
+
return { txid, vout, value, status };
|
|
683
|
+
});
|
|
684
|
+
// and notify via callback
|
|
658
685
|
eventCallback({
|
|
659
686
|
type: "utxo",
|
|
660
687
|
coins,
|
|
@@ -696,10 +723,10 @@ class Wallet {
|
|
|
696
723
|
};
|
|
697
724
|
return stopFunc;
|
|
698
725
|
}
|
|
699
|
-
async handleBatchStartedEvent(event, intentId,
|
|
726
|
+
async handleBatchStartedEvent(event, intentId, forfeitPubKey, forfeitOutputScript) {
|
|
700
727
|
const utf8IntentId = new TextEncoder().encode(intentId);
|
|
701
728
|
const intentIdHash = (0, utils_js_1.sha256)(utf8IntentId);
|
|
702
|
-
const intentIdHashStr = base_1.hex.encode(
|
|
729
|
+
const intentIdHashStr = base_1.hex.encode(intentIdHash);
|
|
703
730
|
let skip = true;
|
|
704
731
|
// check if our intent ID hash matches any in the event
|
|
705
732
|
for (const idHash of event.intentIdHashes) {
|
|
@@ -719,7 +746,7 @@ class Wallet {
|
|
|
719
746
|
value: event.batchExpiry,
|
|
720
747
|
type: event.batchExpiry >= 512n ? "seconds" : "blocks",
|
|
721
748
|
},
|
|
722
|
-
pubkeys: [
|
|
749
|
+
pubkeys: [forfeitPubKey],
|
|
723
750
|
}).script;
|
|
724
751
|
const sweepTapTreeRoot = (0, payment_js_1.tapLeafHash)(sweepTapscript);
|
|
725
752
|
return {
|
|
@@ -732,7 +759,7 @@ class Wallet {
|
|
|
732
759
|
// validates the vtxo tree, creates a signing session and generates the musig2 nonces
|
|
733
760
|
async handleSettlementSigningEvent(event, sweepTapTreeRoot, session, vtxoGraph) {
|
|
734
761
|
// validate the unsigned vtxo tree
|
|
735
|
-
const commitmentTx =
|
|
762
|
+
const commitmentTx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(event.unsignedCommitmentTx));
|
|
736
763
|
(0, validation_1.validateVtxoTxGraph)(vtxoGraph, commitmentTx, sweepTapTreeRoot);
|
|
737
764
|
// TODO check if our registered outputs are in the vtxo tree
|
|
738
765
|
const sharedOutput = commitmentTx.getOutput(0);
|
|
@@ -740,18 +767,21 @@ class Wallet {
|
|
|
740
767
|
throw new Error("Shared output not found");
|
|
741
768
|
}
|
|
742
769
|
session.init(vtxoGraph, sweepTapTreeRoot, sharedOutput.amount);
|
|
743
|
-
|
|
770
|
+
const pubkey = base_1.hex.encode(await session.getPublicKey());
|
|
771
|
+
const nonces = await session.getNonces();
|
|
772
|
+
await this.arkProvider.submitTreeNonces(event.id, pubkey, nonces);
|
|
744
773
|
}
|
|
745
774
|
async handleSettlementSigningNoncesGeneratedEvent(event, session) {
|
|
746
775
|
session.setAggregatedNonces(event.treeNonces);
|
|
747
|
-
const signatures = session.sign();
|
|
748
|
-
|
|
776
|
+
const signatures = await session.sign();
|
|
777
|
+
const pubkey = base_1.hex.encode(await session.getPublicKey());
|
|
778
|
+
await this.arkProvider.submitTreeSignatures(event.id, pubkey, signatures);
|
|
749
779
|
}
|
|
750
780
|
async handleSettlementFinalizationEvent(event, inputs, forfeitOutputScript, connectorsGraph) {
|
|
751
781
|
// the signed forfeits transactions to submit
|
|
752
782
|
const signedForfeits = [];
|
|
753
783
|
const vtxos = await this.getVirtualCoins();
|
|
754
|
-
let settlementPsbt =
|
|
784
|
+
let settlementPsbt = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(event.commitmentTx));
|
|
755
785
|
let hasBoardingUtxos = false;
|
|
756
786
|
let connectorIndex = 0;
|
|
757
787
|
const connectorsLeaves = connectorsGraph?.leaves() || [];
|
|
@@ -793,7 +823,7 @@ class Wallet {
|
|
|
793
823
|
throw new Error("not enough connectors received");
|
|
794
824
|
}
|
|
795
825
|
const connectorLeaf = connectorsLeaves[connectorIndex];
|
|
796
|
-
const connectorTxId =
|
|
826
|
+
const connectorTxId = connectorLeaf.id;
|
|
797
827
|
const connectorOutput = connectorLeaf.getOutput(0);
|
|
798
828
|
if (!connectorOutput) {
|
|
799
829
|
throw new Error("connector output not found");
|
|
@@ -812,7 +842,7 @@ class Wallet {
|
|
|
812
842
|
amount: BigInt(vtxo.value),
|
|
813
843
|
script: base_2.VtxoScript.decode(input.tapTree).pkScript,
|
|
814
844
|
},
|
|
815
|
-
sighashType:
|
|
845
|
+
sighashType: btc_signer_1.SigHash.DEFAULT,
|
|
816
846
|
tapLeafScript: [input.forfeitTapLeafScript],
|
|
817
847
|
},
|
|
818
848
|
{
|
|
@@ -834,112 +864,69 @@ class Wallet {
|
|
|
834
864
|
: undefined);
|
|
835
865
|
}
|
|
836
866
|
}
|
|
837
|
-
async makeRegisterIntentSignature(
|
|
867
|
+
async makeRegisterIntentSignature(coins, outputs, onchainOutputsIndexes, cosignerPubKeys) {
|
|
838
868
|
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
839
|
-
const
|
|
869
|
+
const inputs = this.prepareIntentProofInputs(coins);
|
|
840
870
|
const message = {
|
|
841
871
|
type: "register",
|
|
842
|
-
input_tap_trees: inputTapTrees,
|
|
843
872
|
onchain_output_indexes: onchainOutputsIndexes,
|
|
844
873
|
valid_at: nowSeconds,
|
|
845
874
|
expire_at: nowSeconds + 2 * 60, // valid for 2 minutes
|
|
846
875
|
cosigners_public_keys: cosignerPubKeys,
|
|
847
876
|
};
|
|
848
877
|
const encodedMessage = JSON.stringify(message, null, 0);
|
|
849
|
-
const
|
|
878
|
+
const proof = intent_1.Intent.create(encodedMessage, inputs, outputs);
|
|
879
|
+
const signedProof = await this.identity.sign(proof);
|
|
850
880
|
return {
|
|
851
|
-
|
|
881
|
+
proof: base_1.base64.encode(signedProof.toPSBT()),
|
|
852
882
|
message: encodedMessage,
|
|
853
883
|
};
|
|
854
884
|
}
|
|
855
|
-
async makeDeleteIntentSignature(
|
|
885
|
+
async makeDeleteIntentSignature(coins) {
|
|
856
886
|
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
857
|
-
const
|
|
887
|
+
const inputs = this.prepareIntentProofInputs(coins);
|
|
858
888
|
const message = {
|
|
859
889
|
type: "delete",
|
|
860
890
|
expire_at: nowSeconds + 2 * 60, // valid for 2 minutes
|
|
861
891
|
};
|
|
862
892
|
const encodedMessage = JSON.stringify(message, null, 0);
|
|
863
|
-
const
|
|
893
|
+
const proof = intent_1.Intent.create(encodedMessage, inputs, []);
|
|
894
|
+
const signedProof = await this.identity.sign(proof);
|
|
864
895
|
return {
|
|
865
|
-
|
|
896
|
+
proof: base_1.base64.encode(signedProof.toPSBT()),
|
|
866
897
|
message: encodedMessage,
|
|
867
898
|
};
|
|
868
899
|
}
|
|
869
|
-
|
|
900
|
+
prepareIntentProofInputs(coins) {
|
|
870
901
|
const inputs = [];
|
|
871
|
-
const
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
const
|
|
875
|
-
|
|
902
|
+
for (const input of coins) {
|
|
903
|
+
const vtxoScript = base_2.VtxoScript.decode(input.tapTree);
|
|
904
|
+
const sequence = getSequence(input);
|
|
905
|
+
const unknown = [unknownFields_1.VtxoTaprootTree.encode(input.tapTree)];
|
|
906
|
+
if (input.extraWitness) {
|
|
907
|
+
unknown.push(unknownFields_1.ConditionWitness.encode(input.extraWitness));
|
|
908
|
+
}
|
|
876
909
|
inputs.push({
|
|
877
|
-
txid: base_1.hex.decode(
|
|
878
|
-
index:
|
|
910
|
+
txid: base_1.hex.decode(input.txid),
|
|
911
|
+
index: input.vout,
|
|
879
912
|
witnessUtxo: {
|
|
880
|
-
amount: BigInt(
|
|
913
|
+
amount: BigInt(input.value),
|
|
881
914
|
script: vtxoScript.pkScript,
|
|
882
915
|
},
|
|
883
916
|
sequence,
|
|
884
|
-
tapLeafScript: [
|
|
917
|
+
tapLeafScript: [input.intentTapLeafScript],
|
|
918
|
+
unknown,
|
|
885
919
|
});
|
|
886
|
-
inputTapTrees.push(base_1.hex.encode(bip322Input.tapTree));
|
|
887
|
-
inputExtraWitnesses.push(bip322Input.extraWitness || []);
|
|
888
920
|
}
|
|
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);
|
|
921
|
+
return inputs;
|
|
899
922
|
}
|
|
900
923
|
}
|
|
901
924
|
exports.Wallet = Wallet;
|
|
902
925
|
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) {
|
|
926
|
+
function getSequence(coin) {
|
|
940
927
|
let sequence = undefined;
|
|
941
928
|
try {
|
|
942
|
-
const scriptWithLeafVersion =
|
|
929
|
+
const scriptWithLeafVersion = coin.intentTapLeafScript[1];
|
|
943
930
|
const script = scriptWithLeafVersion.subarray(0, scriptWithLeafVersion.length - 1);
|
|
944
931
|
const params = tapscript_1.CSVMultisigTapscript.decode(script).params;
|
|
945
932
|
sequence = bip68.encode(params.timelock.type === "blocks"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { base58, hex } from "@scure/base";
|
|
2
|
-
import { VtxoScript } from '../script/base.js';
|
|
3
2
|
import { sha256 } from "@scure/btc-signer/utils.js";
|
|
4
|
-
import { Script } from "@scure/btc-signer
|
|
3
|
+
import { Script } from "@scure/btc-signer";
|
|
4
|
+
import { VtxoScript } from '../script/base.js';
|
|
5
5
|
/**
|
|
6
6
|
* ArkNotes are special virtual coins in the Ark protocol that can be created
|
|
7
7
|
* and spent without requiring any transactions. The server mints them, and they
|
package/dist/esm/forfeit.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { Transaction } from "@scure/btc-signer
|
|
1
|
+
import { Transaction } from "@scure/btc-signer";
|
|
2
2
|
import { P2A } from './utils/anchor.js';
|
|
3
3
|
export function buildForfeitTx(inputs, forfeitPkScript, txLocktime) {
|
|
4
4
|
const tx = new Transaction({
|
|
5
5
|
version: 3,
|
|
6
6
|
lockTime: txLocktime,
|
|
7
|
+
allowUnknownOutputs: true,
|
|
8
|
+
allowUnknown: true,
|
|
9
|
+
allowUnknownInputs: true,
|
|
7
10
|
});
|
|
8
11
|
let amount = 0n;
|
|
9
12
|
for (const input of inputs) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { pubECDSA, pubSchnorr, randomPrivateKeyBytes,
|
|
2
|
-
import { hex } from "@scure/base";
|
|
1
|
+
import { pubECDSA, pubSchnorr, randomPrivateKeyBytes, } from "@scure/btc-signer/utils.js";
|
|
3
2
|
import { SigHash } from "@scure/btc-signer/transaction.js";
|
|
3
|
+
import { hex } from "@scure/base";
|
|
4
4
|
import { TreeSignerSession } from '../tree/signingSession.js';
|
|
5
|
-
import { schnorr } from "@noble/secp256k1";
|
|
5
|
+
import { schnorr, sign } from "@noble/secp256k1";
|
|
6
6
|
const ZERO_32 = new Uint8Array(32).fill(0);
|
|
7
7
|
const ALL_SIGHASH = Object.values(SigHash).filter((x) => typeof x === "number");
|
|
8
8
|
/**
|
|
@@ -79,8 +79,9 @@ export class SingleKey {
|
|
|
79
79
|
signerSession() {
|
|
80
80
|
return TreeSignerSession.random();
|
|
81
81
|
}
|
|
82
|
-
async signMessage(message) {
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
async signMessage(message, signatureType = "schnorr") {
|
|
83
|
+
if (signatureType === "ecdsa")
|
|
84
|
+
return sign(message, this.key, { prehash: false });
|
|
85
|
+
return schnorr.sign(message, this.key);
|
|
85
86
|
}
|
|
86
87
|
}
|
package/dist/esm/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import { TxType, } from './wallet/index.js';
|
|
|
8
8
|
import { Wallet, waitForIncomingFunds } from './wallet/wallet.js';
|
|
9
9
|
import { TxTree } from './tree/txTree.js';
|
|
10
10
|
import { Ramps } from './wallet/ramps.js';
|
|
11
|
+
import { VtxoManager } from './wallet/vtxo-manager.js';
|
|
11
12
|
import { ServiceWorkerWallet } from './wallet/serviceWorker/wallet.js';
|
|
12
13
|
import { OnchainWallet } from './wallet/onchain.js';
|
|
13
14
|
import { setupServiceWorker } from './wallet/serviceWorker/utils.js';
|
|
@@ -17,9 +18,9 @@ import { Response } from './wallet/serviceWorker/response.js';
|
|
|
17
18
|
import { ESPLORA_URL, EsploraProvider, } from './providers/onchain.js';
|
|
18
19
|
import { RestArkProvider, SettlementEventType, } from './providers/ark.js';
|
|
19
20
|
import { CLTVMultisigTapscript, ConditionCSVMultisigTapscript, ConditionMultisigTapscript, CSVMultisigTapscript, decodeTapscript, MultisigTapscript, } from './script/tapscript.js';
|
|
20
|
-
import { buildOffchainTx, } from './utils/arkTransaction.js';
|
|
21
|
+
import { hasBoardingTxExpired, buildOffchainTx, verifyTapscriptSignatures, } from './utils/arkTransaction.js';
|
|
21
22
|
import { VtxoTaprootTree, ConditionWitness, getArkPsbtFields, setArkPsbtField, ArkPsbtFieldKey, ArkPsbtFieldKeyType, CosignerPublicKey, VtxoTreeExpiry, } from './utils/unknownFields.js';
|
|
22
|
-
import {
|
|
23
|
+
import { Intent } from './intent/index.js';
|
|
23
24
|
import { ArkNote } from './arknote/index.js';
|
|
24
25
|
import { networks } from './networks.js';
|
|
25
26
|
import { RestIndexerProvider, IndexerTxType, ChainTxType, } from './providers/indexer.js';
|
|
@@ -29,7 +30,7 @@ import { WalletRepositoryImpl } from './repositories/walletRepository.js';
|
|
|
29
30
|
import { ContractRepositoryImpl } from './repositories/contractRepository.js';
|
|
30
31
|
export {
|
|
31
32
|
// Wallets
|
|
32
|
-
Wallet, SingleKey, OnchainWallet, Ramps,
|
|
33
|
+
Wallet, SingleKey, OnchainWallet, Ramps, VtxoManager,
|
|
33
34
|
// Providers
|
|
34
35
|
ESPLORA_URL, EsploraProvider, RestArkProvider, RestIndexerProvider,
|
|
35
36
|
// Script-related
|
|
@@ -43,15 +44,15 @@ decodeTapscript, MultisigTapscript, CSVMultisigTapscript, ConditionCSVMultisigTa
|
|
|
43
44
|
// Ark PSBT fields
|
|
44
45
|
ArkPsbtFieldKey, ArkPsbtFieldKeyType, setArkPsbtField, getArkPsbtFields, CosignerPublicKey, VtxoTreeExpiry, VtxoTaprootTree, ConditionWitness,
|
|
45
46
|
// Utils
|
|
46
|
-
buildOffchainTx, waitForIncomingFunds,
|
|
47
|
+
buildOffchainTx, verifyTapscriptSignatures, waitForIncomingFunds, hasBoardingTxExpired,
|
|
47
48
|
// Arknote
|
|
48
49
|
ArkNote,
|
|
49
50
|
// Network
|
|
50
51
|
networks,
|
|
51
52
|
// Repositories
|
|
52
53
|
WalletRepositoryImpl, ContractRepositoryImpl,
|
|
53
|
-
//
|
|
54
|
-
|
|
54
|
+
// Intent proof
|
|
55
|
+
Intent,
|
|
55
56
|
// TxTree
|
|
56
57
|
TxTree,
|
|
57
58
|
// Anchor
|