@arkade-os/sdk 0.3.0-alpha.8 → 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 +48 -14
- 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 +13 -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 +15 -170
- package/dist/cjs/providers/expoIndexer.js +22 -111
- package/dist/cjs/providers/expoUtils.js +124 -0
- 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 +105 -15
- package/dist/cjs/utils/transaction.js +28 -0
- package/dist/cjs/utils/unknownFields.js +7 -7
- 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 -0
- package/dist/cjs/wallet/serviceWorker/wallet.js +7 -8
- package/dist/cjs/wallet/serviceWorker/worker.js +46 -27
- package/dist/cjs/wallet/unroll.js +7 -9
- package/dist/cjs/wallet/utils.js +9 -0
- package/dist/cjs/wallet/vtxo-manager.js +323 -0
- package/dist/cjs/wallet/wallet.js +98 -125
- 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 +15 -137
- package/dist/esm/providers/expoIndexer.js +22 -78
- package/dist/esm/providers/expoUtils.js +87 -0
- 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 +97 -8
- package/dist/esm/utils/transaction.js +24 -0
- package/dist/esm/utils/unknownFields.js +3 -3
- 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 -0
- package/dist/esm/wallet/serviceWorker/wallet.js +8 -9
- package/dist/esm/wallet/serviceWorker/worker.js +48 -29
- package/dist/esm/wallet/unroll.js +5 -7
- package/dist/esm/wallet/utils.js +8 -0
- package/dist/esm/wallet/vtxo-manager.js +317 -0
- package/dist/esm/wallet/wallet.js +92 -119
- 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 +9 -7
- 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 +62 -26
- package/dist/types/providers/errors.d.ts +13 -0
- package/dist/types/providers/expoIndexer.d.ts +2 -10
- package/dist/types/providers/expoUtils.d.ts +18 -0
- package/dist/types/providers/indexer.d.ts +1 -9
- 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 +12 -4
- 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 +6 -4
- 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 -0
- 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 +2 -1
- package/dist/types/wallet/vtxo-manager.d.ts +179 -0
- package/dist/types/wallet/wallet.d.ts +8 -4
- package/package.json +1 -2
- 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
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 './utils.js';
|
|
27
|
+
import { extendCoin, 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
|
|
@@ -58,7 +59,7 @@ import { extendVirtualCoin } from './utils.js';
|
|
|
58
59
|
* ```
|
|
59
60
|
*/
|
|
60
61
|
export class Wallet {
|
|
61
|
-
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) {
|
|
62
63
|
this.identity = identity;
|
|
63
64
|
this.network = network;
|
|
64
65
|
this.networkName = networkName;
|
|
@@ -70,9 +71,15 @@ export class Wallet {
|
|
|
70
71
|
this.boardingTapscript = boardingTapscript;
|
|
71
72
|
this.serverUnrollScript = serverUnrollScript;
|
|
72
73
|
this.forfeitOutputScript = forfeitOutputScript;
|
|
74
|
+
this.forfeitPubkey = forfeitPubkey;
|
|
73
75
|
this.dustAmount = dustAmount;
|
|
74
76
|
this.walletRepository = walletRepository;
|
|
75
77
|
this.contractRepository = contractRepository;
|
|
78
|
+
this.renewalConfig = {
|
|
79
|
+
enabled: renewalConfig?.enabled ?? false,
|
|
80
|
+
...DEFAULT_RENEWAL_CONFIG,
|
|
81
|
+
...renewalConfig,
|
|
82
|
+
};
|
|
76
83
|
}
|
|
77
84
|
static async create(config) {
|
|
78
85
|
const pubkey = await config.identity.xOnlyPublicKey();
|
|
@@ -102,6 +109,7 @@ export class Wallet {
|
|
|
102
109
|
const esploraUrl = config.esploraUrl || ESPLORA_URL[info.network];
|
|
103
110
|
// Use provided onchainProvider instance or create a new one
|
|
104
111
|
const onchainProvider = config.onchainProvider || new EsploraProvider(esploraUrl);
|
|
112
|
+
// Generate timelocks
|
|
105
113
|
const exitTimelock = {
|
|
106
114
|
value: info.unilateralExitDelay,
|
|
107
115
|
type: info.unilateralExitDelay < 512n ? "blocks" : "seconds",
|
|
@@ -127,21 +135,22 @@ export class Wallet {
|
|
|
127
135
|
// the serverUnrollScript is the one used to create output scripts of the checkpoint transactions
|
|
128
136
|
let serverUnrollScript;
|
|
129
137
|
try {
|
|
130
|
-
const raw = hex.decode(info.
|
|
138
|
+
const raw = hex.decode(info.checkpointTapscript);
|
|
131
139
|
serverUnrollScript = CSVMultisigTapscript.decode(raw);
|
|
132
140
|
}
|
|
133
141
|
catch (e) {
|
|
134
|
-
throw new Error("Invalid
|
|
142
|
+
throw new Error("Invalid checkpointTapscript from server");
|
|
135
143
|
}
|
|
136
144
|
// parse the server forfeit address
|
|
137
145
|
// server is expecting funds to be sent to this address
|
|
146
|
+
const forfeitPubkey = hex.decode(info.forfeitPubkey).slice(1);
|
|
138
147
|
const forfeitAddress = Address(network).decode(info.forfeitAddress);
|
|
139
148
|
const forfeitOutputScript = OutScript.encode(forfeitAddress);
|
|
140
149
|
// Set up storage and repositories
|
|
141
150
|
const storage = config.storage || new InMemoryStorageAdapter();
|
|
142
151
|
const walletRepository = new WalletRepositoryImpl(storage);
|
|
143
152
|
const contractRepository = new ContractRepositoryImpl(storage);
|
|
144
|
-
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);
|
|
145
154
|
}
|
|
146
155
|
get arkAddress() {
|
|
147
156
|
return this.offchainTapscript.address(this.network.hrp, this.arkServerPublicKey);
|
|
@@ -324,15 +333,12 @@ export class Wallet {
|
|
|
324
333
|
async getBoardingUtxos() {
|
|
325
334
|
const boardingAddress = await this.getBoardingAddress();
|
|
326
335
|
const boardingUtxos = await this.onchainProvider.getCoins(boardingAddress);
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
intentTapLeafScript: exit,
|
|
334
|
-
tapTree: encodedBoardingTapscript,
|
|
335
|
-
}));
|
|
336
|
+
const utxos = boardingUtxos.map((utxo) => {
|
|
337
|
+
return extendCoin(this, utxo);
|
|
338
|
+
});
|
|
339
|
+
// Save boardingUtxos using unified repository
|
|
340
|
+
await this.walletRepository.saveUtxos(boardingAddress, utxos);
|
|
341
|
+
return utxos;
|
|
336
342
|
}
|
|
337
343
|
async sendBitcoin(params) {
|
|
338
344
|
if (params.amount <= 0) {
|
|
@@ -453,7 +459,7 @@ export class Wallet {
|
|
|
453
459
|
const signingPublicKeys = [];
|
|
454
460
|
if (hasOffchainOutputs) {
|
|
455
461
|
session = this.identity.signerSession();
|
|
456
|
-
signingPublicKeys.push(hex.encode(session.getPublicKey()));
|
|
462
|
+
signingPublicKeys.push(hex.encode(await session.getPublicKey()));
|
|
457
463
|
}
|
|
458
464
|
const [intent, deleteIntent] = await Promise.all([
|
|
459
465
|
this.makeRegisterIntentSignature(params.inputs, outputs, onchainOutputIndexes, signingPublicKeys),
|
|
@@ -469,8 +475,8 @@ export class Wallet {
|
|
|
469
475
|
...params.inputs.map((input) => `${input.txid}:${input.vout}`),
|
|
470
476
|
];
|
|
471
477
|
const settlementStream = this.arkProvider.getEventStream(abortController.signal, topics);
|
|
472
|
-
//
|
|
473
|
-
let
|
|
478
|
+
// batchId, sweepTapTreeRoot and forfeitOutputScript are set once the BatchStarted event is received
|
|
479
|
+
let batchId;
|
|
474
480
|
let sweepTapTreeRoot;
|
|
475
481
|
const vtxoChunks = [];
|
|
476
482
|
const connectorsChunks = [];
|
|
@@ -483,30 +489,26 @@ export class Wallet {
|
|
|
483
489
|
switch (event.type) {
|
|
484
490
|
// the settlement failed
|
|
485
491
|
case SettlementEventType.BatchFailed:
|
|
486
|
-
|
|
487
|
-
if (event.id === roundId) {
|
|
488
|
-
throw new Error(event.reason);
|
|
489
|
-
}
|
|
490
|
-
break;
|
|
492
|
+
throw new Error(event.reason);
|
|
491
493
|
case SettlementEventType.BatchStarted:
|
|
492
494
|
if (step !== undefined) {
|
|
493
495
|
continue;
|
|
494
496
|
}
|
|
495
|
-
const res = await this.handleBatchStartedEvent(event, intentId, this.
|
|
497
|
+
const res = await this.handleBatchStartedEvent(event, intentId, this.forfeitPubkey, this.forfeitOutputScript);
|
|
496
498
|
if (!res.skip) {
|
|
497
499
|
step = event.type;
|
|
498
500
|
sweepTapTreeRoot = res.sweepTapTreeRoot;
|
|
499
|
-
|
|
501
|
+
batchId = res.roundId;
|
|
500
502
|
if (!hasOffchainOutputs) {
|
|
501
503
|
// if there are no offchain outputs, we don't have to handle musig2 tree signatures
|
|
502
504
|
// we can directly advance to the finalization step
|
|
503
|
-
step = SettlementEventType.
|
|
505
|
+
step = SettlementEventType.TreeNonces;
|
|
504
506
|
}
|
|
505
507
|
}
|
|
506
508
|
break;
|
|
507
509
|
case SettlementEventType.TreeTx:
|
|
508
510
|
if (step !== SettlementEventType.BatchStarted &&
|
|
509
|
-
step !== SettlementEventType.
|
|
511
|
+
step !== SettlementEventType.TreeNonces) {
|
|
510
512
|
continue;
|
|
511
513
|
}
|
|
512
514
|
// index 0 = vtxo tree
|
|
@@ -522,7 +524,7 @@ export class Wallet {
|
|
|
522
524
|
}
|
|
523
525
|
break;
|
|
524
526
|
case SettlementEventType.TreeSignature:
|
|
525
|
-
if (step !== SettlementEventType.
|
|
527
|
+
if (step !== SettlementEventType.TreeNonces) {
|
|
526
528
|
continue;
|
|
527
529
|
}
|
|
528
530
|
if (!hasOffchainOutputs) {
|
|
@@ -564,7 +566,7 @@ export class Wallet {
|
|
|
564
566
|
break;
|
|
565
567
|
// the musig2 nonces of the vtxo tree transactions are generated
|
|
566
568
|
// the server expects now the partial musig2 signatures
|
|
567
|
-
case SettlementEventType.
|
|
569
|
+
case SettlementEventType.TreeNonces:
|
|
568
570
|
if (step !== SettlementEventType.TreeSigningStarted) {
|
|
569
571
|
continue;
|
|
570
572
|
}
|
|
@@ -572,14 +574,18 @@ export class Wallet {
|
|
|
572
574
|
if (!session) {
|
|
573
575
|
throw new Error("Signing session not set");
|
|
574
576
|
}
|
|
575
|
-
await this.
|
|
577
|
+
const signed = await this.handleSettlementTreeNoncesEvent(event, session);
|
|
578
|
+
if (signed) {
|
|
579
|
+
step = event.type;
|
|
580
|
+
}
|
|
581
|
+
break;
|
|
576
582
|
}
|
|
577
583
|
step = event.type;
|
|
578
584
|
break;
|
|
579
585
|
// the vtxo tree is signed, craft, sign and submit forfeit transactions
|
|
580
586
|
// if any boarding utxos are involved, the settlement tx is also signed
|
|
581
587
|
case SettlementEventType.BatchFinalization:
|
|
582
|
-
if (step !== SettlementEventType.
|
|
588
|
+
if (step !== SettlementEventType.TreeNonces) {
|
|
583
589
|
continue;
|
|
584
590
|
}
|
|
585
591
|
if (!this.forfeitOutputScript) {
|
|
@@ -597,8 +603,10 @@ export class Wallet {
|
|
|
597
603
|
if (step !== SettlementEventType.BatchFinalization) {
|
|
598
604
|
continue;
|
|
599
605
|
}
|
|
600
|
-
|
|
601
|
-
|
|
606
|
+
if (event.id === batchId) {
|
|
607
|
+
abortController.abort();
|
|
608
|
+
return event.commitmentTxid;
|
|
609
|
+
}
|
|
602
610
|
}
|
|
603
611
|
}
|
|
604
612
|
}
|
|
@@ -677,10 +685,10 @@ export class Wallet {
|
|
|
677
685
|
};
|
|
678
686
|
return stopFunc;
|
|
679
687
|
}
|
|
680
|
-
async handleBatchStartedEvent(event, intentId,
|
|
688
|
+
async handleBatchStartedEvent(event, intentId, forfeitPubKey, forfeitOutputScript) {
|
|
681
689
|
const utf8IntentId = new TextEncoder().encode(intentId);
|
|
682
690
|
const intentIdHash = sha256(utf8IntentId);
|
|
683
|
-
const intentIdHashStr = hex.encode(
|
|
691
|
+
const intentIdHashStr = hex.encode(intentIdHash);
|
|
684
692
|
let skip = true;
|
|
685
693
|
// check if our intent ID hash matches any in the event
|
|
686
694
|
for (const idHash of event.intentIdHashes) {
|
|
@@ -700,7 +708,7 @@ export class Wallet {
|
|
|
700
708
|
value: event.batchExpiry,
|
|
701
709
|
type: event.batchExpiry >= 512n ? "seconds" : "blocks",
|
|
702
710
|
},
|
|
703
|
-
pubkeys: [
|
|
711
|
+
pubkeys: [forfeitPubKey],
|
|
704
712
|
}).script;
|
|
705
713
|
const sweepTapTreeRoot = tapLeafHash(sweepTapscript);
|
|
706
714
|
return {
|
|
@@ -721,12 +729,19 @@ export class Wallet {
|
|
|
721
729
|
throw new Error("Shared output not found");
|
|
722
730
|
}
|
|
723
731
|
session.init(vtxoGraph, sweepTapTreeRoot, sharedOutput.amount);
|
|
724
|
-
|
|
732
|
+
const pubkey = hex.encode(await session.getPublicKey());
|
|
733
|
+
const nonces = await session.getNonces();
|
|
734
|
+
await this.arkProvider.submitTreeNonces(event.id, pubkey, nonces);
|
|
725
735
|
}
|
|
726
|
-
async
|
|
727
|
-
session.
|
|
728
|
-
|
|
729
|
-
|
|
736
|
+
async handleSettlementTreeNoncesEvent(event, session) {
|
|
737
|
+
const { hasAllNonces } = await session.aggregatedNonces(event.txid, event.nonces);
|
|
738
|
+
// wait to receive and aggregate all nonces before sending signatures
|
|
739
|
+
if (!hasAllNonces)
|
|
740
|
+
return false;
|
|
741
|
+
const signatures = await session.sign();
|
|
742
|
+
const pubkey = hex.encode(await session.getPublicKey());
|
|
743
|
+
await this.arkProvider.submitTreeSignatures(event.id, pubkey, signatures);
|
|
744
|
+
return true;
|
|
730
745
|
}
|
|
731
746
|
async handleSettlementFinalizationEvent(event, inputs, forfeitOutputScript, connectorsGraph) {
|
|
732
747
|
// the signed forfeits transactions to submit
|
|
@@ -741,8 +756,6 @@ export class Wallet {
|
|
|
741
756
|
const vtxo = vtxos.find((vtxo) => vtxo.txid === input.txid && vtxo.vout === input.vout);
|
|
742
757
|
// boarding utxo, we need to sign the settlement tx
|
|
743
758
|
if (!vtxo) {
|
|
744
|
-
hasBoardingUtxos = true;
|
|
745
|
-
const inputIndexes = [];
|
|
746
759
|
for (let i = 0; i < settlementPsbt.inputsLength; i++) {
|
|
747
760
|
const settlementInput = settlementPsbt.getInput(i);
|
|
748
761
|
if (!settlementInput.txid ||
|
|
@@ -758,9 +771,12 @@ export class Wallet {
|
|
|
758
771
|
settlementPsbt.updateInput(i, {
|
|
759
772
|
tapLeafScript: [input.forfeitTapLeafScript],
|
|
760
773
|
});
|
|
761
|
-
|
|
774
|
+
settlementPsbt = await this.identity.sign(settlementPsbt, [
|
|
775
|
+
i,
|
|
776
|
+
]);
|
|
777
|
+
hasBoardingUtxos = true;
|
|
778
|
+
break;
|
|
762
779
|
}
|
|
763
|
-
settlementPsbt = await this.identity.sign(settlementPsbt, inputIndexes);
|
|
764
780
|
continue;
|
|
765
781
|
}
|
|
766
782
|
if (isRecoverable(vtxo) || isSubdust(vtxo, this.dustAmount)) {
|
|
@@ -774,7 +790,7 @@ export class Wallet {
|
|
|
774
790
|
throw new Error("not enough connectors received");
|
|
775
791
|
}
|
|
776
792
|
const connectorLeaf = connectorsLeaves[connectorIndex];
|
|
777
|
-
const connectorTxId =
|
|
793
|
+
const connectorTxId = connectorLeaf.id;
|
|
778
794
|
const connectorOutput = connectorLeaf.getOutput(0);
|
|
779
795
|
if (!connectorOutput) {
|
|
780
796
|
throw new Error("connector output not found");
|
|
@@ -815,111 +831,68 @@ export class Wallet {
|
|
|
815
831
|
: undefined);
|
|
816
832
|
}
|
|
817
833
|
}
|
|
818
|
-
async makeRegisterIntentSignature(
|
|
834
|
+
async makeRegisterIntentSignature(coins, outputs, onchainOutputsIndexes, cosignerPubKeys) {
|
|
819
835
|
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
820
|
-
const
|
|
836
|
+
const inputs = this.prepareIntentProofInputs(coins);
|
|
821
837
|
const message = {
|
|
822
838
|
type: "register",
|
|
823
|
-
input_tap_trees: inputTapTrees,
|
|
824
839
|
onchain_output_indexes: onchainOutputsIndexes,
|
|
825
840
|
valid_at: nowSeconds,
|
|
826
841
|
expire_at: nowSeconds + 2 * 60, // valid for 2 minutes
|
|
827
842
|
cosigners_public_keys: cosignerPubKeys,
|
|
828
843
|
};
|
|
829
844
|
const encodedMessage = JSON.stringify(message, null, 0);
|
|
830
|
-
const
|
|
845
|
+
const proof = Intent.create(encodedMessage, inputs, outputs);
|
|
846
|
+
const signedProof = await this.identity.sign(proof);
|
|
831
847
|
return {
|
|
832
|
-
|
|
848
|
+
proof: base64.encode(signedProof.toPSBT()),
|
|
833
849
|
message: encodedMessage,
|
|
834
850
|
};
|
|
835
851
|
}
|
|
836
|
-
async makeDeleteIntentSignature(
|
|
852
|
+
async makeDeleteIntentSignature(coins) {
|
|
837
853
|
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
838
|
-
const
|
|
854
|
+
const inputs = this.prepareIntentProofInputs(coins);
|
|
839
855
|
const message = {
|
|
840
856
|
type: "delete",
|
|
841
857
|
expire_at: nowSeconds + 2 * 60, // valid for 2 minutes
|
|
842
858
|
};
|
|
843
859
|
const encodedMessage = JSON.stringify(message, null, 0);
|
|
844
|
-
const
|
|
860
|
+
const proof = Intent.create(encodedMessage, inputs, []);
|
|
861
|
+
const signedProof = await this.identity.sign(proof);
|
|
845
862
|
return {
|
|
846
|
-
|
|
863
|
+
proof: base64.encode(signedProof.toPSBT()),
|
|
847
864
|
message: encodedMessage,
|
|
848
865
|
};
|
|
849
866
|
}
|
|
850
|
-
|
|
867
|
+
prepareIntentProofInputs(coins) {
|
|
851
868
|
const inputs = [];
|
|
852
|
-
const
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
const
|
|
856
|
-
|
|
869
|
+
for (const input of coins) {
|
|
870
|
+
const vtxoScript = VtxoScript.decode(input.tapTree);
|
|
871
|
+
const sequence = getSequence(input);
|
|
872
|
+
const unknown = [VtxoTaprootTree.encode(input.tapTree)];
|
|
873
|
+
if (input.extraWitness) {
|
|
874
|
+
unknown.push(ConditionWitness.encode(input.extraWitness));
|
|
875
|
+
}
|
|
857
876
|
inputs.push({
|
|
858
|
-
txid: hex.decode(
|
|
859
|
-
index:
|
|
877
|
+
txid: hex.decode(input.txid),
|
|
878
|
+
index: input.vout,
|
|
860
879
|
witnessUtxo: {
|
|
861
|
-
amount: BigInt(
|
|
880
|
+
amount: BigInt(input.value),
|
|
862
881
|
script: vtxoScript.pkScript,
|
|
863
882
|
},
|
|
864
883
|
sequence,
|
|
865
|
-
tapLeafScript: [
|
|
884
|
+
tapLeafScript: [input.intentTapLeafScript],
|
|
885
|
+
unknown,
|
|
866
886
|
});
|
|
867
|
-
inputTapTrees.push(hex.encode(bip322Input.tapTree));
|
|
868
|
-
inputExtraWitnesses.push(bip322Input.extraWitness || []);
|
|
869
887
|
}
|
|
870
|
-
return
|
|
871
|
-
inputs,
|
|
872
|
-
inputTapTrees,
|
|
873
|
-
finalizer: finalizeWithExtraWitnesses(inputExtraWitnesses),
|
|
874
|
-
};
|
|
875
|
-
}
|
|
876
|
-
async makeBIP322Signature(message, inputs, finalizer, outputs) {
|
|
877
|
-
const proof = BIP322.create(message, inputs, outputs);
|
|
878
|
-
const signedProof = await this.identity.sign(proof);
|
|
879
|
-
return BIP322.signature(signedProof, finalizer);
|
|
888
|
+
return inputs;
|
|
880
889
|
}
|
|
881
890
|
}
|
|
882
891
|
Wallet.MIN_FEE_RATE = 1; // sats/vbyte
|
|
883
|
-
function
|
|
884
|
-
return function (tx) {
|
|
885
|
-
for (let i = 0; i < tx.inputsLength; i++) {
|
|
886
|
-
try {
|
|
887
|
-
tx.finalizeIdx(i);
|
|
888
|
-
}
|
|
889
|
-
catch (e) {
|
|
890
|
-
// handle empty witness error
|
|
891
|
-
if (e instanceof Error &&
|
|
892
|
-
e.message.includes("finalize/taproot: empty witness")) {
|
|
893
|
-
const tapLeaves = tx.getInput(i).tapLeafScript;
|
|
894
|
-
if (!tapLeaves || tapLeaves.length <= 0)
|
|
895
|
-
throw e;
|
|
896
|
-
const [cb, s] = tapLeaves[0];
|
|
897
|
-
const script = s.slice(0, -1);
|
|
898
|
-
tx.updateInput(i, {
|
|
899
|
-
finalScriptWitness: [
|
|
900
|
-
script,
|
|
901
|
-
TaprootControlBlock.encode(cb),
|
|
902
|
-
],
|
|
903
|
-
});
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
const finalScriptWitness = tx.getInput(i).finalScriptWitness;
|
|
907
|
-
if (!finalScriptWitness)
|
|
908
|
-
throw new Error("input not finalized");
|
|
909
|
-
// input 0 and 1 spend the same pkscript
|
|
910
|
-
const extra = inputExtraWitnesses[i === 0 ? 0 : i - 1];
|
|
911
|
-
if (extra && extra.length > 0) {
|
|
912
|
-
tx.updateInput(i, {
|
|
913
|
-
finalScriptWitness: [...extra, ...finalScriptWitness],
|
|
914
|
-
});
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
};
|
|
918
|
-
}
|
|
919
|
-
function getSequence(bip322Input) {
|
|
892
|
+
function getSequence(coin) {
|
|
920
893
|
let sequence = undefined;
|
|
921
894
|
try {
|
|
922
|
-
const scriptWithLeafVersion =
|
|
895
|
+
const scriptWithLeafVersion = coin.intentTapLeafScript[1];
|
|
923
896
|
const script = scriptWithLeafVersion.subarray(0, scriptWithLeafVersion.length - 1);
|
|
924
897
|
const params = CSVMultisigTapscript.decode(script).params;
|
|
925
898
|
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 "
|
|
2
|
-
import { TransactionInputUpdate } from "@scure/btc-signer/psbt";
|
|
1
|
+
import { Transaction } from "./utils/transaction";
|
|
2
|
+
import { TransactionInputUpdate } from "@scure/btc-signer/psbt.js";
|
|
3
3
|
export declare function buildForfeitTx(inputs: TransactionInputUpdate[], forfeitPkScript: Uint8Array, txLocktime?: number): Transaction;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Transaction } from "
|
|
1
|
+
import { Transaction } from "../utils/transaction";
|
|
2
2
|
import { SignerSession } from "../tree/signingSession";
|
|
3
3
|
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";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Transaction } from "@scure/btc-signer/transaction.js";
|
|
2
1
|
import { Identity } from ".";
|
|
2
|
+
import { Transaction } from "../utils/transaction";
|
|
3
3
|
import { SignerSession } from "../tree/signingSession";
|
|
4
4
|
/**
|
|
5
5
|
* In-memory single key implementation for Bitcoin transaction signing.
|
|
@@ -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
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { Transaction } from "
|
|
1
|
+
import { Transaction } from "./utils/transaction";
|
|
2
2
|
import { SingleKey } from "./identity/singleKey";
|
|
3
3
|
import { Identity } from "./identity";
|
|
4
4
|
import { ArkAddress } from "./script/address";
|
|
5
5
|
import { VHTLC } from "./script/vhtlc";
|
|
6
6
|
import { DefaultVtxo } from "./script/default";
|
|
7
|
-
import { VtxoScript, EncodedVtxoScript, TapLeafScript } from "./script/base";
|
|
7
|
+
import { VtxoScript, EncodedVtxoScript, TapLeafScript, TapTreeCoder } from "./script/base";
|
|
8
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,11 +18,11 @@ 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, TreeNoncesEvent, BatchStartedEvent, TreeTxEvent, TreeSignatureEvent, ScheduledSession } from "./providers/ark";
|
|
21
22
|
import { CLTVMultisigTapscript, ConditionCSVMultisigTapscript, ConditionMultisigTapscript, CSVMultisigTapscript, decodeTapscript, MultisigTapscript, TapscriptType, ArkTapscript, RelativeTimelock } from "./script/tapscript";
|
|
22
|
-
import { hasBoardingTxExpired, 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
28
|
import { RestIndexerProvider, IndexerProvider, IndexerTxType, ChainTxType, PageResponse, Batch, ChainTx, CommitmentTx, TxHistoryRecord, VtxoChain, Tx, Vtxo, PaginationOptions, SubscriptionResponse, SubscriptionHeartbeat, SubscriptionEvent } from "./providers/indexer";
|
|
@@ -31,5 +32,6 @@ 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
|
-
|
|
35
|
-
export
|
|
35
|
+
import { ArkError, maybeArkError } from "./providers/errors";
|
|
36
|
+
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, TapTreeCoder, ArkPsbtFieldKey, ArkPsbtFieldKeyType, setArkPsbtField, getArkPsbtFields, CosignerPublicKey, VtxoTreeExpiry, VtxoTaprootTree, ConditionWitness, buildOffchainTx, verifyTapscriptSignatures, waitForIncomingFunds, hasBoardingTxExpired, ArkNote, networks, WalletRepositoryImpl, ContractRepositoryImpl, Intent, TxTree, P2A, Unroll, Transaction, ArkError, maybeArkError, };
|
|
37
|
+
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, TreeNoncesEvent, 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 { TransactionInput, TransactionOutput } from "@scure/btc-signer/psbt.js";
|
|
2
|
+
import { Transaction } from "../utils/transaction";
|
|
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
|
+
}
|