@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
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { base64, hex } from "@scure/base";
|
|
2
2
|
import * as bip68 from "bip68";
|
|
3
|
-
import {
|
|
4
|
-
import { SigHash, Transaction } from "@scure/btc-signer
|
|
5
|
-
import {
|
|
3
|
+
import { tapLeafHash } from "@scure/btc-signer/payment.js";
|
|
4
|
+
import { SigHash, Transaction, Address, OutScript, } from "@scure/btc-signer";
|
|
5
|
+
import { sha256 } from "@scure/btc-signer/utils.js";
|
|
6
6
|
import { vtxosToTxs } from '../utils/transactionHistory.js';
|
|
7
7
|
import { ArkAddress } from '../script/address.js';
|
|
8
8
|
import { DefaultVtxo } from '../script/default.js';
|
|
@@ -12,18 +12,19 @@ import { SettlementEventType, RestArkProvider, } from '../providers/ark.js';
|
|
|
12
12
|
import { buildForfeitTx } from '../forfeit.js';
|
|
13
13
|
import { validateConnectorsTxGraph, validateVtxoTxGraph, } from '../tree/validation.js';
|
|
14
14
|
import { isRecoverable, isSpendable, isSubdust, TxType, } from './index.js';
|
|
15
|
-
import { sha256, sha256x2 } from "@scure/btc-signer/utils.js";
|
|
16
15
|
import { VtxoScript } from '../script/base.js';
|
|
17
16
|
import { CSVMultisigTapscript } from '../script/tapscript.js';
|
|
18
|
-
import { buildOffchainTx } from '../utils/arkTransaction.js';
|
|
17
|
+
import { buildOffchainTx, hasBoardingTxExpired } from '../utils/arkTransaction.js';
|
|
18
|
+
import { DEFAULT_RENEWAL_CONFIG } from './vtxo-manager.js';
|
|
19
19
|
import { ArkNote } from '../arknote/index.js';
|
|
20
|
-
import {
|
|
20
|
+
import { Intent } from '../intent/index.js';
|
|
21
21
|
import { RestIndexerProvider } from '../providers/indexer.js';
|
|
22
22
|
import { TxTree } from '../tree/txTree.js';
|
|
23
|
+
import { ConditionWitness, VtxoTaprootTree } from '../utils/unknownFields.js';
|
|
23
24
|
import { InMemoryStorageAdapter } from '../storage/inMemory.js';
|
|
24
25
|
import { WalletRepositoryImpl, } from '../repositories/walletRepository.js';
|
|
25
26
|
import { ContractRepositoryImpl, } from '../repositories/contractRepository.js';
|
|
26
|
-
import { extendVirtualCoin } from './
|
|
27
|
+
import { extendVirtualCoin } from './utils.js';
|
|
27
28
|
/**
|
|
28
29
|
* Main wallet implementation for Bitcoin transactions with Ark protocol support.
|
|
29
30
|
* The wallet does not store any data locally and relies on Ark and onchain
|
|
@@ -31,13 +32,21 @@ import { extendVirtualCoin } from './serviceWorker/utils.js';
|
|
|
31
32
|
*
|
|
32
33
|
* @example
|
|
33
34
|
* ```typescript
|
|
34
|
-
* // Create a wallet
|
|
35
|
+
* // Create a wallet with URL configuration
|
|
35
36
|
* const wallet = await Wallet.create({
|
|
36
37
|
* identity: SingleKey.fromHex('your_private_key'),
|
|
37
38
|
* arkServerUrl: 'https://ark.example.com',
|
|
38
39
|
* esploraUrl: 'https://mempool.space/api'
|
|
39
40
|
* });
|
|
40
41
|
*
|
|
42
|
+
* // Or with custom provider instances (e.g., for Expo/React Native)
|
|
43
|
+
* const wallet = await Wallet.create({
|
|
44
|
+
* identity: SingleKey.fromHex('your_private_key'),
|
|
45
|
+
* arkProvider: new ExpoArkProvider('https://ark.example.com'),
|
|
46
|
+
* indexerProvider: new ExpoIndexerProvider('https://ark.example.com'),
|
|
47
|
+
* esploraUrl: 'https://mempool.space/api'
|
|
48
|
+
* });
|
|
49
|
+
*
|
|
41
50
|
* // Get addresses
|
|
42
51
|
* const arkAddress = await wallet.getAddress();
|
|
43
52
|
* const boardingAddress = await wallet.getBoardingAddress();
|
|
@@ -50,7 +59,7 @@ import { extendVirtualCoin } from './serviceWorker/utils.js';
|
|
|
50
59
|
* ```
|
|
51
60
|
*/
|
|
52
61
|
export class Wallet {
|
|
53
|
-
constructor(identity, network, networkName, onchainProvider, arkProvider, indexerProvider, arkServerPublicKey, offchainTapscript, boardingTapscript, serverUnrollScript, forfeitOutputScript, dustAmount, walletRepository, contractRepository) {
|
|
62
|
+
constructor(identity, network, networkName, onchainProvider, arkProvider, indexerProvider, arkServerPublicKey, offchainTapscript, boardingTapscript, serverUnrollScript, forfeitOutputScript, forfeitPubkey, dustAmount, walletRepository, contractRepository, renewalConfig) {
|
|
54
63
|
this.identity = identity;
|
|
55
64
|
this.network = network;
|
|
56
65
|
this.networkName = networkName;
|
|
@@ -62,20 +71,45 @@ export class Wallet {
|
|
|
62
71
|
this.boardingTapscript = boardingTapscript;
|
|
63
72
|
this.serverUnrollScript = serverUnrollScript;
|
|
64
73
|
this.forfeitOutputScript = forfeitOutputScript;
|
|
74
|
+
this.forfeitPubkey = forfeitPubkey;
|
|
65
75
|
this.dustAmount = dustAmount;
|
|
66
76
|
this.walletRepository = walletRepository;
|
|
67
77
|
this.contractRepository = contractRepository;
|
|
78
|
+
this.renewalConfig = {
|
|
79
|
+
enabled: renewalConfig?.enabled ?? false,
|
|
80
|
+
...DEFAULT_RENEWAL_CONFIG,
|
|
81
|
+
...renewalConfig,
|
|
82
|
+
};
|
|
68
83
|
}
|
|
69
84
|
static async create(config) {
|
|
70
85
|
const pubkey = await config.identity.xOnlyPublicKey();
|
|
71
86
|
if (!pubkey) {
|
|
72
87
|
throw new Error("Invalid configured public key");
|
|
73
88
|
}
|
|
74
|
-
|
|
75
|
-
const
|
|
89
|
+
// Use provided arkProvider instance or create a new one from arkServerUrl
|
|
90
|
+
const arkProvider = config.arkProvider ||
|
|
91
|
+
(() => {
|
|
92
|
+
if (!config.arkServerUrl) {
|
|
93
|
+
throw new Error("Either arkProvider or arkServerUrl must be provided");
|
|
94
|
+
}
|
|
95
|
+
return new RestArkProvider(config.arkServerUrl);
|
|
96
|
+
})();
|
|
97
|
+
// Extract arkServerUrl from provider if not explicitly provided
|
|
98
|
+
const arkServerUrl = config.arkServerUrl || arkProvider.serverUrl;
|
|
99
|
+
if (!arkServerUrl) {
|
|
100
|
+
throw new Error("Could not determine arkServerUrl from provider");
|
|
101
|
+
}
|
|
102
|
+
// Use provided indexerProvider instance or create a new one
|
|
103
|
+
// indexerUrl defaults to arkServerUrl if not provided
|
|
104
|
+
const indexerUrl = config.indexerUrl || arkServerUrl;
|
|
105
|
+
const indexerProvider = config.indexerProvider || new RestIndexerProvider(indexerUrl);
|
|
76
106
|
const info = await arkProvider.getInfo();
|
|
77
107
|
const network = getNetwork(info.network);
|
|
78
|
-
|
|
108
|
+
// Extract esploraUrl from provider if not explicitly provided
|
|
109
|
+
const esploraUrl = config.esploraUrl || ESPLORA_URL[info.network];
|
|
110
|
+
// Use provided onchainProvider instance or create a new one
|
|
111
|
+
const onchainProvider = config.onchainProvider || new EsploraProvider(esploraUrl);
|
|
112
|
+
// Generate timelocks
|
|
79
113
|
const exitTimelock = {
|
|
80
114
|
value: info.unilateralExitDelay,
|
|
81
115
|
type: info.unilateralExitDelay < 512n ? "blocks" : "seconds",
|
|
@@ -99,17 +133,24 @@ export class Wallet {
|
|
|
99
133
|
// Save tapscripts
|
|
100
134
|
const offchainTapscript = bareVtxoTapscript;
|
|
101
135
|
// the serverUnrollScript is the one used to create output scripts of the checkpoint transactions
|
|
102
|
-
|
|
103
|
-
|
|
136
|
+
let serverUnrollScript;
|
|
137
|
+
try {
|
|
138
|
+
const raw = hex.decode(info.checkpointTapscript);
|
|
139
|
+
serverUnrollScript = CSVMultisigTapscript.decode(raw);
|
|
140
|
+
}
|
|
141
|
+
catch (e) {
|
|
142
|
+
throw new Error("Invalid checkpointTapscript from server");
|
|
143
|
+
}
|
|
104
144
|
// parse the server forfeit address
|
|
105
145
|
// server is expecting funds to be sent to this address
|
|
146
|
+
const forfeitPubkey = hex.decode(info.forfeitPubkey).slice(1);
|
|
106
147
|
const forfeitAddress = Address(network).decode(info.forfeitAddress);
|
|
107
148
|
const forfeitOutputScript = OutScript.encode(forfeitAddress);
|
|
108
149
|
// Set up storage and repositories
|
|
109
150
|
const storage = config.storage || new InMemoryStorageAdapter();
|
|
110
151
|
const walletRepository = new WalletRepositoryImpl(storage);
|
|
111
152
|
const contractRepository = new ContractRepositoryImpl(storage);
|
|
112
|
-
return new Wallet(config.identity, network, info.network, onchainProvider, arkProvider, indexerProvider, serverPubKey, offchainTapscript, boardingTapscript, serverUnrollScript, forfeitOutputScript, info.dust, walletRepository, contractRepository);
|
|
153
|
+
return new Wallet(config.identity, network, info.network, onchainProvider, arkProvider, indexerProvider, serverPubKey, offchainTapscript, boardingTapscript, serverUnrollScript, forfeitOutputScript, forfeitPubkey, info.dust, walletRepository, contractRepository, config.renewalConfig);
|
|
113
154
|
}
|
|
114
155
|
get arkAddress() {
|
|
115
156
|
return this.offchainTapscript.address(this.network.hrp, this.arkServerPublicKey);
|
|
@@ -171,40 +212,24 @@ export class Wallet {
|
|
|
171
212
|
// if (cachedVtxos.length) return cachedVtxos;
|
|
172
213
|
// For now, always fetch fresh data from provider and update cache
|
|
173
214
|
// In future, we can add cache invalidation logic based on timestamps
|
|
174
|
-
const
|
|
175
|
-
const
|
|
176
|
-
const forfeit = this.offchainTapscript.forfeit();
|
|
177
|
-
const exit = this.offchainTapscript.exit();
|
|
178
|
-
const extendedVtxos = spendableVtxos.map((vtxo) => ({
|
|
179
|
-
...vtxo,
|
|
180
|
-
forfeitTapLeafScript: forfeit,
|
|
181
|
-
intentTapLeafScript: exit,
|
|
182
|
-
tapTree: encodedOffchainTapscript,
|
|
183
|
-
}));
|
|
215
|
+
const vtxos = await this.getVirtualCoins(filter);
|
|
216
|
+
const extendedVtxos = vtxos.map((vtxo) => extendVirtualCoin(this, vtxo));
|
|
184
217
|
// Update cache with fresh data
|
|
185
218
|
await this.walletRepository.saveVtxos(address, extendedVtxos);
|
|
186
219
|
return extendedVtxos;
|
|
187
220
|
}
|
|
188
221
|
async getVirtualCoins(filter = { withRecoverable: true, withUnrolled: false }) {
|
|
189
222
|
const scripts = [hex.encode(this.offchainTapscript.pkScript)];
|
|
190
|
-
const response = await this.indexerProvider.getVtxos({
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const response = await this.indexerProvider.getVtxos({
|
|
197
|
-
scripts,
|
|
198
|
-
recoverableOnly: true,
|
|
199
|
-
});
|
|
200
|
-
vtxos.push(...response.vtxos);
|
|
223
|
+
const response = await this.indexerProvider.getVtxos({ scripts });
|
|
224
|
+
const allVtxos = response.vtxos;
|
|
225
|
+
let vtxos = allVtxos.filter(isSpendable);
|
|
226
|
+
// all recoverable vtxos are spendable by definition
|
|
227
|
+
if (!filter.withRecoverable) {
|
|
228
|
+
vtxos = vtxos.filter((vtxo) => !isRecoverable(vtxo));
|
|
201
229
|
}
|
|
202
230
|
if (filter.withUnrolled) {
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
spentOnly: true,
|
|
206
|
-
});
|
|
207
|
-
vtxos.push(...response.vtxos.filter((vtxo) => vtxo.isUnrolled));
|
|
231
|
+
const spentVtxos = allVtxos.filter((vtxo) => !isSpendable(vtxo));
|
|
232
|
+
vtxos.push(...spentVtxos.filter((vtxo) => vtxo.isUnrolled));
|
|
208
233
|
}
|
|
209
234
|
return vtxos;
|
|
210
235
|
}
|
|
@@ -242,10 +267,10 @@ export class Wallet {
|
|
|
242
267
|
return txs;
|
|
243
268
|
}
|
|
244
269
|
async getBoardingTxs() {
|
|
245
|
-
const boardingAddress = await this.getBoardingAddress();
|
|
246
|
-
const txs = await this.onchainProvider.getTransactions(boardingAddress);
|
|
247
270
|
const utxos = [];
|
|
248
271
|
const commitmentsToIgnore = new Set();
|
|
272
|
+
const boardingAddress = await this.getBoardingAddress();
|
|
273
|
+
const txs = await this.onchainProvider.getTransactions(boardingAddress);
|
|
249
274
|
for (const tx of txs) {
|
|
250
275
|
for (let i = 0; i < tx.vout.length; i++) {
|
|
251
276
|
const vout = tx.vout[i];
|
|
@@ -386,13 +411,15 @@ export class Wallet {
|
|
|
386
411
|
}
|
|
387
412
|
}
|
|
388
413
|
}
|
|
389
|
-
// if no params are provided, use all boarding and offchain
|
|
414
|
+
// if no params are provided, use all non expired boarding utxos and offchain vtxos as inputs
|
|
390
415
|
// and send all to the offchain address
|
|
391
416
|
if (!params) {
|
|
392
417
|
let amount = 0;
|
|
393
|
-
const
|
|
418
|
+
const exitScript = CSVMultisigTapscript.decode(hex.decode(this.boardingTapscript.exitScript));
|
|
419
|
+
const boardingTimelock = exitScript.params.timelock;
|
|
420
|
+
const boardingUtxos = (await this.getBoardingUtxos()).filter((utxo) => !hasBoardingTxExpired(utxo, boardingTimelock));
|
|
394
421
|
amount += boardingUtxos.reduce((sum, input) => sum + input.value, 0);
|
|
395
|
-
const vtxos = await this.getVtxos();
|
|
422
|
+
const vtxos = await this.getVtxos({ withRecoverable: true });
|
|
396
423
|
amount += vtxos.reduce((sum, input) => sum + input.value, 0);
|
|
397
424
|
const inputs = [...boardingUtxos, ...vtxos];
|
|
398
425
|
if (inputs.length === 0) {
|
|
@@ -435,7 +462,7 @@ export class Wallet {
|
|
|
435
462
|
const signingPublicKeys = [];
|
|
436
463
|
if (hasOffchainOutputs) {
|
|
437
464
|
session = this.identity.signerSession();
|
|
438
|
-
signingPublicKeys.push(hex.encode(session.getPublicKey()));
|
|
465
|
+
signingPublicKeys.push(hex.encode(await session.getPublicKey()));
|
|
439
466
|
}
|
|
440
467
|
const [intent, deleteIntent] = await Promise.all([
|
|
441
468
|
this.makeRegisterIntentSignature(params.inputs, outputs, onchainOutputIndexes, signingPublicKeys),
|
|
@@ -474,7 +501,7 @@ export class Wallet {
|
|
|
474
501
|
if (step !== undefined) {
|
|
475
502
|
continue;
|
|
476
503
|
}
|
|
477
|
-
const res = await this.handleBatchStartedEvent(event, intentId, this.
|
|
504
|
+
const res = await this.handleBatchStartedEvent(event, intentId, this.forfeitPubkey, this.forfeitOutputScript);
|
|
478
505
|
if (!res.skip) {
|
|
479
506
|
step = event.type;
|
|
480
507
|
sweepTapTreeRoot = res.sweepTapTreeRoot;
|
|
@@ -602,22 +629,22 @@ export class Wallet {
|
|
|
602
629
|
let onchainStopFunc;
|
|
603
630
|
let indexerStopFunc;
|
|
604
631
|
if (this.onchainProvider && boardingAddress) {
|
|
632
|
+
const findVoutOnTx = (tx) => {
|
|
633
|
+
return tx.vout.findIndex((v) => v.scriptpubkey_address === boardingAddress);
|
|
634
|
+
};
|
|
605
635
|
onchainStopFunc = await this.onchainProvider.watchAddresses([boardingAddress], (txs) => {
|
|
636
|
+
// find all utxos belonging to our boarding address
|
|
606
637
|
const coins = txs
|
|
638
|
+
// filter txs where address is in output
|
|
639
|
+
.filter((tx) => findVoutOnTx(tx) !== -1)
|
|
640
|
+
// return utxo as Coin
|
|
607
641
|
.map((tx) => {
|
|
608
|
-
const
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
txid: tx.txid,
|
|
615
|
-
vout,
|
|
616
|
-
value: Number(tx.vout[vout].value),
|
|
617
|
-
status: tx.status,
|
|
618
|
-
};
|
|
619
|
-
})
|
|
620
|
-
.filter((coin) => coin !== null);
|
|
642
|
+
const { txid, status } = tx;
|
|
643
|
+
const vout = findVoutOnTx(tx);
|
|
644
|
+
const value = Number(tx.vout[vout].value);
|
|
645
|
+
return { txid, vout, value, status };
|
|
646
|
+
});
|
|
647
|
+
// and notify via callback
|
|
621
648
|
eventCallback({
|
|
622
649
|
type: "utxo",
|
|
623
650
|
coins,
|
|
@@ -659,10 +686,10 @@ export class Wallet {
|
|
|
659
686
|
};
|
|
660
687
|
return stopFunc;
|
|
661
688
|
}
|
|
662
|
-
async handleBatchStartedEvent(event, intentId,
|
|
689
|
+
async handleBatchStartedEvent(event, intentId, forfeitPubKey, forfeitOutputScript) {
|
|
663
690
|
const utf8IntentId = new TextEncoder().encode(intentId);
|
|
664
691
|
const intentIdHash = sha256(utf8IntentId);
|
|
665
|
-
const intentIdHashStr = hex.encode(
|
|
692
|
+
const intentIdHashStr = hex.encode(intentIdHash);
|
|
666
693
|
let skip = true;
|
|
667
694
|
// check if our intent ID hash matches any in the event
|
|
668
695
|
for (const idHash of event.intentIdHashes) {
|
|
@@ -682,7 +709,7 @@ export class Wallet {
|
|
|
682
709
|
value: event.batchExpiry,
|
|
683
710
|
type: event.batchExpiry >= 512n ? "seconds" : "blocks",
|
|
684
711
|
},
|
|
685
|
-
pubkeys: [
|
|
712
|
+
pubkeys: [forfeitPubKey],
|
|
686
713
|
}).script;
|
|
687
714
|
const sweepTapTreeRoot = tapLeafHash(sweepTapscript);
|
|
688
715
|
return {
|
|
@@ -703,12 +730,15 @@ export class Wallet {
|
|
|
703
730
|
throw new Error("Shared output not found");
|
|
704
731
|
}
|
|
705
732
|
session.init(vtxoGraph, sweepTapTreeRoot, sharedOutput.amount);
|
|
706
|
-
|
|
733
|
+
const pubkey = hex.encode(await session.getPublicKey());
|
|
734
|
+
const nonces = await session.getNonces();
|
|
735
|
+
await this.arkProvider.submitTreeNonces(event.id, pubkey, nonces);
|
|
707
736
|
}
|
|
708
737
|
async handleSettlementSigningNoncesGeneratedEvent(event, session) {
|
|
709
738
|
session.setAggregatedNonces(event.treeNonces);
|
|
710
|
-
const signatures = session.sign();
|
|
711
|
-
|
|
739
|
+
const signatures = await session.sign();
|
|
740
|
+
const pubkey = hex.encode(await session.getPublicKey());
|
|
741
|
+
await this.arkProvider.submitTreeSignatures(event.id, pubkey, signatures);
|
|
712
742
|
}
|
|
713
743
|
async handleSettlementFinalizationEvent(event, inputs, forfeitOutputScript, connectorsGraph) {
|
|
714
744
|
// the signed forfeits transactions to submit
|
|
@@ -756,7 +786,7 @@ export class Wallet {
|
|
|
756
786
|
throw new Error("not enough connectors received");
|
|
757
787
|
}
|
|
758
788
|
const connectorLeaf = connectorsLeaves[connectorIndex];
|
|
759
|
-
const connectorTxId =
|
|
789
|
+
const connectorTxId = connectorLeaf.id;
|
|
760
790
|
const connectorOutput = connectorLeaf.getOutput(0);
|
|
761
791
|
if (!connectorOutput) {
|
|
762
792
|
throw new Error("connector output not found");
|
|
@@ -797,111 +827,68 @@ export class Wallet {
|
|
|
797
827
|
: undefined);
|
|
798
828
|
}
|
|
799
829
|
}
|
|
800
|
-
async makeRegisterIntentSignature(
|
|
830
|
+
async makeRegisterIntentSignature(coins, outputs, onchainOutputsIndexes, cosignerPubKeys) {
|
|
801
831
|
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
802
|
-
const
|
|
832
|
+
const inputs = this.prepareIntentProofInputs(coins);
|
|
803
833
|
const message = {
|
|
804
834
|
type: "register",
|
|
805
|
-
input_tap_trees: inputTapTrees,
|
|
806
835
|
onchain_output_indexes: onchainOutputsIndexes,
|
|
807
836
|
valid_at: nowSeconds,
|
|
808
837
|
expire_at: nowSeconds + 2 * 60, // valid for 2 minutes
|
|
809
838
|
cosigners_public_keys: cosignerPubKeys,
|
|
810
839
|
};
|
|
811
840
|
const encodedMessage = JSON.stringify(message, null, 0);
|
|
812
|
-
const
|
|
841
|
+
const proof = Intent.create(encodedMessage, inputs, outputs);
|
|
842
|
+
const signedProof = await this.identity.sign(proof);
|
|
813
843
|
return {
|
|
814
|
-
|
|
844
|
+
proof: base64.encode(signedProof.toPSBT()),
|
|
815
845
|
message: encodedMessage,
|
|
816
846
|
};
|
|
817
847
|
}
|
|
818
|
-
async makeDeleteIntentSignature(
|
|
848
|
+
async makeDeleteIntentSignature(coins) {
|
|
819
849
|
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
820
|
-
const
|
|
850
|
+
const inputs = this.prepareIntentProofInputs(coins);
|
|
821
851
|
const message = {
|
|
822
852
|
type: "delete",
|
|
823
853
|
expire_at: nowSeconds + 2 * 60, // valid for 2 minutes
|
|
824
854
|
};
|
|
825
855
|
const encodedMessage = JSON.stringify(message, null, 0);
|
|
826
|
-
const
|
|
856
|
+
const proof = Intent.create(encodedMessage, inputs, []);
|
|
857
|
+
const signedProof = await this.identity.sign(proof);
|
|
827
858
|
return {
|
|
828
|
-
|
|
859
|
+
proof: base64.encode(signedProof.toPSBT()),
|
|
829
860
|
message: encodedMessage,
|
|
830
861
|
};
|
|
831
862
|
}
|
|
832
|
-
|
|
863
|
+
prepareIntentProofInputs(coins) {
|
|
833
864
|
const inputs = [];
|
|
834
|
-
const
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
const
|
|
838
|
-
|
|
865
|
+
for (const input of coins) {
|
|
866
|
+
const vtxoScript = VtxoScript.decode(input.tapTree);
|
|
867
|
+
const sequence = getSequence(input);
|
|
868
|
+
const unknown = [VtxoTaprootTree.encode(input.tapTree)];
|
|
869
|
+
if (input.extraWitness) {
|
|
870
|
+
unknown.push(ConditionWitness.encode(input.extraWitness));
|
|
871
|
+
}
|
|
839
872
|
inputs.push({
|
|
840
|
-
txid: hex.decode(
|
|
841
|
-
index:
|
|
873
|
+
txid: hex.decode(input.txid),
|
|
874
|
+
index: input.vout,
|
|
842
875
|
witnessUtxo: {
|
|
843
|
-
amount: BigInt(
|
|
876
|
+
amount: BigInt(input.value),
|
|
844
877
|
script: vtxoScript.pkScript,
|
|
845
878
|
},
|
|
846
879
|
sequence,
|
|
847
|
-
tapLeafScript: [
|
|
880
|
+
tapLeafScript: [input.intentTapLeafScript],
|
|
881
|
+
unknown,
|
|
848
882
|
});
|
|
849
|
-
inputTapTrees.push(hex.encode(bip322Input.tapTree));
|
|
850
|
-
inputExtraWitnesses.push(bip322Input.extraWitness || []);
|
|
851
883
|
}
|
|
852
|
-
return
|
|
853
|
-
inputs,
|
|
854
|
-
inputTapTrees,
|
|
855
|
-
finalizer: finalizeWithExtraWitnesses(inputExtraWitnesses),
|
|
856
|
-
};
|
|
857
|
-
}
|
|
858
|
-
async makeBIP322Signature(message, inputs, finalizer, outputs) {
|
|
859
|
-
const proof = BIP322.create(message, inputs, outputs);
|
|
860
|
-
const signedProof = await this.identity.sign(proof);
|
|
861
|
-
return BIP322.signature(signedProof, finalizer);
|
|
884
|
+
return inputs;
|
|
862
885
|
}
|
|
863
886
|
}
|
|
864
887
|
Wallet.MIN_FEE_RATE = 1; // sats/vbyte
|
|
865
|
-
function
|
|
866
|
-
return function (tx) {
|
|
867
|
-
for (let i = 0; i < tx.inputsLength; i++) {
|
|
868
|
-
try {
|
|
869
|
-
tx.finalizeIdx(i);
|
|
870
|
-
}
|
|
871
|
-
catch (e) {
|
|
872
|
-
// handle empty witness error
|
|
873
|
-
if (e instanceof Error &&
|
|
874
|
-
e.message.includes("finalize/taproot: empty witness")) {
|
|
875
|
-
const tapLeaves = tx.getInput(i).tapLeafScript;
|
|
876
|
-
if (!tapLeaves || tapLeaves.length <= 0)
|
|
877
|
-
throw e;
|
|
878
|
-
const [cb, s] = tapLeaves[0];
|
|
879
|
-
const script = s.slice(0, -1);
|
|
880
|
-
tx.updateInput(i, {
|
|
881
|
-
finalScriptWitness: [
|
|
882
|
-
script,
|
|
883
|
-
TaprootControlBlock.encode(cb),
|
|
884
|
-
],
|
|
885
|
-
});
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
const finalScriptWitness = tx.getInput(i).finalScriptWitness;
|
|
889
|
-
if (!finalScriptWitness)
|
|
890
|
-
throw new Error("input not finalized");
|
|
891
|
-
// input 0 and 1 spend the same pkscript
|
|
892
|
-
const extra = inputExtraWitnesses[i === 0 ? 0 : i - 1];
|
|
893
|
-
if (extra && extra.length > 0) {
|
|
894
|
-
tx.updateInput(i, {
|
|
895
|
-
finalScriptWitness: [...extra, ...finalScriptWitness],
|
|
896
|
-
});
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
};
|
|
900
|
-
}
|
|
901
|
-
function getSequence(bip322Input) {
|
|
888
|
+
function getSequence(coin) {
|
|
902
889
|
let sequence = undefined;
|
|
903
890
|
try {
|
|
904
|
-
const scriptWithLeafVersion =
|
|
891
|
+
const scriptWithLeafVersion = coin.intentTapLeafScript[1];
|
|
905
892
|
const script = scriptWithLeafVersion.subarray(0, scriptWithLeafVersion.length - 1);
|
|
906
893
|
const params = CSVMultisigTapscript.decode(script).params;
|
|
907
894
|
sequence = bip68.encode(params.timelock.type === "blocks"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { TapLeafScript, VtxoScript } from "../script/base";
|
|
2
1
|
import { Bytes } from "@scure/btc-signer/utils.js";
|
|
2
|
+
import { TapLeafScript, VtxoScript } from "../script/base";
|
|
3
3
|
import { ExtendedCoin, Status } from "../wallet";
|
|
4
4
|
/**
|
|
5
5
|
* ArkNotes are special virtual coins in the Ark protocol that can be created
|
package/dist/types/forfeit.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { Transaction } from "@scure/btc-signer
|
|
2
|
-
import { TransactionInputUpdate } from "@scure/btc-signer/psbt";
|
|
1
|
+
import { Transaction } from "@scure/btc-signer";
|
|
2
|
+
import { TransactionInputUpdate } from "@scure/btc-signer/psbt.js";
|
|
3
3
|
export declare function buildForfeitTx(inputs: TransactionInputUpdate[], forfeitPkScript: Uint8Array, txLocktime?: number): Transaction;
|
|
@@ -4,7 +4,7 @@ export interface Identity {
|
|
|
4
4
|
signerSession(): SignerSession;
|
|
5
5
|
xOnlyPublicKey(): Promise<Uint8Array>;
|
|
6
6
|
compressedPublicKey(): Promise<Uint8Array>;
|
|
7
|
-
signMessage(message:
|
|
7
|
+
signMessage(message: Uint8Array, signatureType: "schnorr" | "ecdsa"): Promise<Uint8Array>;
|
|
8
8
|
sign(tx: Transaction, inputIndexes?: number[]): Promise<Transaction>;
|
|
9
9
|
}
|
|
10
10
|
export * from "./singleKey";
|
|
@@ -35,5 +35,5 @@ export declare class SingleKey implements Identity {
|
|
|
35
35
|
compressedPublicKey(): Promise<Uint8Array>;
|
|
36
36
|
xOnlyPublicKey(): Promise<Uint8Array>;
|
|
37
37
|
signerSession(): SignerSession;
|
|
38
|
-
signMessage(message:
|
|
38
|
+
signMessage(message: Uint8Array, signatureType?: "schnorr" | "ecdsa"): Promise<Uint8Array>;
|
|
39
39
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -5,11 +5,12 @@ import { ArkAddress } from "./script/address";
|
|
|
5
5
|
import { VHTLC } from "./script/vhtlc";
|
|
6
6
|
import { DefaultVtxo } from "./script/default";
|
|
7
7
|
import { VtxoScript, EncodedVtxoScript, TapLeafScript } from "./script/base";
|
|
8
|
-
import { TxType, IWallet, WalletConfig, ArkTransaction, Coin, ExtendedCoin, ExtendedVirtualCoin, WalletBalance, SendBitcoinParams, Recipient, SettleParams, Status, VirtualStatus, Outpoint, VirtualCoin, TxKey, GetVtxosFilter, TapLeaves } from "./wallet";
|
|
8
|
+
import { TxType, IWallet, WalletConfig, ProviderClass, ArkTransaction, Coin, ExtendedCoin, ExtendedVirtualCoin, WalletBalance, SendBitcoinParams, Recipient, SettleParams, Status, VirtualStatus, Outpoint, VirtualCoin, TxKey, GetVtxosFilter, TapLeaves } from "./wallet";
|
|
9
9
|
import { Wallet, waitForIncomingFunds, IncomingFunds } from "./wallet/wallet";
|
|
10
10
|
import { TxTree, TxTreeNode } from "./tree/txTree";
|
|
11
11
|
import { SignerSession, TreeNonces, TreePartialSigs } from "./tree/signingSession";
|
|
12
12
|
import { Ramps } from "./wallet/ramps";
|
|
13
|
+
import { VtxoManager } from "./wallet/vtxo-manager";
|
|
13
14
|
import { ServiceWorkerWallet } from "./wallet/serviceWorker/wallet";
|
|
14
15
|
import { OnchainWallet } from "./wallet/onchain";
|
|
15
16
|
import { setupServiceWorker } from "./wallet/serviceWorker/utils";
|
|
@@ -17,19 +18,19 @@ import { Worker } from "./wallet/serviceWorker/worker";
|
|
|
17
18
|
import { Request } from "./wallet/serviceWorker/request";
|
|
18
19
|
import { Response } from "./wallet/serviceWorker/response";
|
|
19
20
|
import { ESPLORA_URL, EsploraProvider, OnchainProvider, ExplorerTransaction } from "./providers/onchain";
|
|
20
|
-
import { RestArkProvider, ArkProvider, SettlementEvent, SettlementEventType, ArkInfo,
|
|
21
|
+
import { RestArkProvider, ArkProvider, SettlementEvent, SettlementEventType, ArkInfo, SignedIntent, Output, TxNotification, BatchFinalizationEvent, BatchFinalizedEvent, BatchFailedEvent, TreeSigningStartedEvent, TreeNoncesAggregatedEvent, BatchStartedEvent, TreeTxEvent, TreeSignatureEvent, ScheduledSession } from "./providers/ark";
|
|
21
22
|
import { CLTVMultisigTapscript, ConditionCSVMultisigTapscript, ConditionMultisigTapscript, CSVMultisigTapscript, decodeTapscript, MultisigTapscript, TapscriptType, ArkTapscript, RelativeTimelock } from "./script/tapscript";
|
|
22
|
-
import { buildOffchainTx, ArkTxInput, OffchainTx } from "./utils/arkTransaction";
|
|
23
|
+
import { hasBoardingTxExpired, buildOffchainTx, verifyTapscriptSignatures, ArkTxInput, OffchainTx } from "./utils/arkTransaction";
|
|
23
24
|
import { VtxoTaprootTree, ConditionWitness, getArkPsbtFields, setArkPsbtField, ArkPsbtFieldCoder, ArkPsbtFieldKey, ArkPsbtFieldKeyType, CosignerPublicKey, VtxoTreeExpiry } from "./utils/unknownFields";
|
|
24
|
-
import {
|
|
25
|
+
import { Intent } from "./intent";
|
|
25
26
|
import { ArkNote } from "./arknote";
|
|
26
27
|
import { networks, Network, NetworkName } from "./networks";
|
|
27
|
-
import { RestIndexerProvider, IndexerProvider, IndexerTxType, ChainTxType, PageResponse, Batch, ChainTx, CommitmentTx, TxHistoryRecord, VtxoChain, Tx, Vtxo, PaginationOptions, SubscriptionResponse } from "./providers/indexer";
|
|
28
|
+
import { RestIndexerProvider, IndexerProvider, IndexerTxType, ChainTxType, PageResponse, Batch, ChainTx, CommitmentTx, TxHistoryRecord, VtxoChain, Tx, Vtxo, PaginationOptions, SubscriptionResponse, SubscriptionHeartbeat, SubscriptionEvent } from "./providers/indexer";
|
|
28
29
|
import { Nonces } from "./musig2/nonces";
|
|
29
30
|
import { PartialSig } from "./musig2/sign";
|
|
30
31
|
import { AnchorBumper, P2A } from "./utils/anchor";
|
|
31
32
|
import { Unroll } from "./wallet/unroll";
|
|
32
33
|
import { WalletRepositoryImpl } from "./repositories/walletRepository";
|
|
33
34
|
import { ContractRepositoryImpl } from "./repositories/contractRepository";
|
|
34
|
-
export { Wallet, SingleKey, OnchainWallet, Ramps, ESPLORA_URL, EsploraProvider, RestArkProvider, RestIndexerProvider, ArkAddress, DefaultVtxo, VtxoScript, VHTLC, TxType, IndexerTxType, ChainTxType, SettlementEventType, setupServiceWorker, Worker, ServiceWorkerWallet, Request, Response, decodeTapscript, MultisigTapscript, CSVMultisigTapscript, ConditionCSVMultisigTapscript, ConditionMultisigTapscript, CLTVMultisigTapscript, ArkPsbtFieldKey, ArkPsbtFieldKeyType, setArkPsbtField, getArkPsbtFields, CosignerPublicKey, VtxoTreeExpiry, VtxoTaprootTree, ConditionWitness, buildOffchainTx, waitForIncomingFunds, ArkNote, networks, WalletRepositoryImpl, ContractRepositoryImpl,
|
|
35
|
-
export type { Identity, IWallet, WalletConfig, ArkTransaction, Coin, ExtendedCoin, ExtendedVirtualCoin, WalletBalance, SendBitcoinParams, Recipient, SettleParams, Status, VirtualStatus, Outpoint, VirtualCoin, TxKey, TapscriptType, ArkTxInput, OffchainTx, TapLeaves, IncomingFunds, IndexerProvider, PageResponse, Batch, ChainTx, CommitmentTx, TxHistoryRecord, Vtxo, VtxoChain, Tx, OnchainProvider, ArkProvider, SettlementEvent, ArkInfo,
|
|
35
|
+
export { Wallet, SingleKey, OnchainWallet, Ramps, VtxoManager, ESPLORA_URL, EsploraProvider, RestArkProvider, RestIndexerProvider, ArkAddress, DefaultVtxo, VtxoScript, VHTLC, TxType, IndexerTxType, ChainTxType, SettlementEventType, setupServiceWorker, Worker, ServiceWorkerWallet, Request, Response, decodeTapscript, MultisigTapscript, CSVMultisigTapscript, ConditionCSVMultisigTapscript, ConditionMultisigTapscript, CLTVMultisigTapscript, ArkPsbtFieldKey, ArkPsbtFieldKeyType, setArkPsbtField, getArkPsbtFields, CosignerPublicKey, VtxoTreeExpiry, VtxoTaprootTree, ConditionWitness, buildOffchainTx, verifyTapscriptSignatures, waitForIncomingFunds, hasBoardingTxExpired, ArkNote, networks, WalletRepositoryImpl, ContractRepositoryImpl, Intent, TxTree, P2A, Unroll, Transaction, };
|
|
36
|
+
export type { Identity, IWallet, WalletConfig, ProviderClass, ArkTransaction, Coin, ExtendedCoin, ExtendedVirtualCoin, WalletBalance, SendBitcoinParams, Recipient, SettleParams, Status, VirtualStatus, Outpoint, VirtualCoin, TxKey, TapscriptType, ArkTxInput, OffchainTx, TapLeaves, IncomingFunds, IndexerProvider, PageResponse, Batch, ChainTx, CommitmentTx, TxHistoryRecord, Vtxo, VtxoChain, Tx, OnchainProvider, ArkProvider, SettlementEvent, ArkInfo, SignedIntent, Output, TxNotification, ExplorerTransaction, BatchFinalizationEvent, BatchFinalizedEvent, BatchFailedEvent, TreeSigningStartedEvent, TreeNoncesAggregatedEvent, BatchStartedEvent, TreeTxEvent, TreeSignatureEvent, ScheduledSession, PaginationOptions, SubscriptionResponse, SubscriptionHeartbeat, SubscriptionEvent, Network, NetworkName, ArkTapscript, RelativeTimelock, EncodedVtxoScript, TapLeafScript, SignerSession, TreeNonces, TreePartialSigs, GetVtxosFilter, Nonces, PartialSig, ArkPsbtFieldCoder, TxTreeNode, AnchorBumper, };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Transaction } from "@scure/btc-signer";
|
|
2
|
+
import { TransactionInput, TransactionOutput } from "@scure/btc-signer/psbt.js";
|
|
3
|
+
/**
|
|
4
|
+
* Intent proof implementation for Bitcoin message signing.
|
|
5
|
+
*
|
|
6
|
+
* Intent proof defines a standard for signing Bitcoin messages as well as proving
|
|
7
|
+
* ownership of coins. This namespace provides utilities for creating and
|
|
8
|
+
* validating Intent proof.
|
|
9
|
+
*
|
|
10
|
+
* it is greatly inspired by BIP322.
|
|
11
|
+
* @see https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* // Create a Intent proof
|
|
16
|
+
* const proof = Intent.create(
|
|
17
|
+
* "Hello Bitcoin!",
|
|
18
|
+
* [input],
|
|
19
|
+
* [output]
|
|
20
|
+
* );
|
|
21
|
+
*
|
|
22
|
+
* // Sign the proof
|
|
23
|
+
* const signedProof = await identity.sign(proof);
|
|
24
|
+
*
|
|
25
|
+
*/
|
|
26
|
+
export declare namespace Intent {
|
|
27
|
+
type Proof = Transaction;
|
|
28
|
+
/**
|
|
29
|
+
* Creates a new Intent proof unsigned transaction.
|
|
30
|
+
*
|
|
31
|
+
* This function constructs a special transaction that can be signed to prove
|
|
32
|
+
* ownership of VTXOs and UTXOs. The proof includes the message to be
|
|
33
|
+
* signed and the inputs/outputs that demonstrate ownership.
|
|
34
|
+
*
|
|
35
|
+
* @param message - The Intent message to be signed
|
|
36
|
+
* @param inputs - Array of transaction inputs to prove ownership of
|
|
37
|
+
* @param outputs - Optional array of transaction outputs
|
|
38
|
+
* @returns An unsigned Intent proof transaction
|
|
39
|
+
*/
|
|
40
|
+
function create(message: string, inputs: TransactionInput[], outputs?: TransactionOutput[]): Proof;
|
|
41
|
+
}
|