@arkade-os/sdk 0.3.1-alpha.3 → 0.3.1-alpha.5
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 +9 -26
- package/dist/cjs/forfeit.js +2 -5
- package/dist/cjs/identity/singleKey.js +4 -5
- package/dist/cjs/index.js +5 -4
- package/dist/cjs/intent/index.js +3 -8
- package/dist/cjs/providers/onchain.js +19 -20
- package/dist/cjs/repositories/walletRepository.js +64 -2
- package/dist/cjs/script/base.js +14 -5
- package/dist/cjs/utils/arkTransaction.js +3 -5
- package/dist/cjs/utils/transaction.js +28 -0
- package/dist/cjs/wallet/onchain.js +4 -4
- package/dist/cjs/wallet/serviceWorker/worker.js +19 -2
- package/dist/cjs/wallet/unroll.js +3 -4
- package/dist/cjs/wallet/utils.js +9 -0
- package/dist/cjs/wallet/wallet.js +11 -13
- package/dist/esm/forfeit.js +1 -4
- package/dist/esm/identity/singleKey.js +3 -4
- package/dist/esm/index.js +3 -3
- package/dist/esm/intent/index.js +2 -7
- package/dist/esm/providers/onchain.js +19 -20
- package/dist/esm/repositories/walletRepository.js +64 -2
- package/dist/esm/script/base.js +11 -2
- package/dist/esm/utils/arkTransaction.js +3 -5
- package/dist/esm/utils/transaction.js +24 -0
- package/dist/esm/wallet/onchain.js +3 -3
- package/dist/esm/wallet/serviceWorker/worker.js +21 -4
- package/dist/esm/wallet/unroll.js +4 -5
- package/dist/esm/wallet/utils.js +8 -0
- package/dist/esm/wallet/wallet.js +12 -14
- package/dist/types/forfeit.d.ts +1 -1
- package/dist/types/identity/index.d.ts +1 -1
- package/dist/types/identity/singleKey.d.ts +1 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/intent/index.d.ts +1 -1
- package/dist/types/providers/onchain.d.ts +6 -2
- package/dist/types/repositories/walletRepository.d.ts +9 -1
- package/dist/types/script/base.d.ts +2 -0
- package/dist/types/utils/arkTransaction.d.ts +1 -3
- package/dist/types/utils/transaction.d.ts +13 -0
- package/dist/types/wallet/onchain.d.ts +1 -1
- package/dist/types/wallet/serviceWorker/worker.d.ts +4 -0
- package/dist/types/wallet/unroll.d.ts +1 -1
- package/dist/types/wallet/utils.d.ts +2 -1
- package/package.json +1 -1
package/dist/esm/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { Transaction } from
|
|
1
|
+
import { Transaction } from './utils/transaction.js';
|
|
2
2
|
import { SingleKey } from './identity/singleKey.js';
|
|
3
3
|
import { ArkAddress } from './script/address.js';
|
|
4
4
|
import { VHTLC } from './script/vhtlc.js';
|
|
5
5
|
import { DefaultVtxo } from './script/default.js';
|
|
6
|
-
import { VtxoScript } from './script/base.js';
|
|
6
|
+
import { VtxoScript, TapTreeCoder, } from './script/base.js';
|
|
7
7
|
import { TxType, } from './wallet/index.js';
|
|
8
8
|
import { Wallet, waitForIncomingFunds } from './wallet/wallet.js';
|
|
9
9
|
import { TxTree } from './tree/txTree.js';
|
|
@@ -41,7 +41,7 @@ TxType, IndexerTxType, ChainTxType, SettlementEventType,
|
|
|
41
41
|
// Service Worker
|
|
42
42
|
setupServiceWorker, Worker, ServiceWorkerWallet, Request, Response,
|
|
43
43
|
// Tapscript
|
|
44
|
-
decodeTapscript, MultisigTapscript, CSVMultisigTapscript, ConditionCSVMultisigTapscript, ConditionMultisigTapscript, CLTVMultisigTapscript,
|
|
44
|
+
decodeTapscript, MultisigTapscript, CSVMultisigTapscript, ConditionCSVMultisigTapscript, ConditionMultisigTapscript, CLTVMultisigTapscript, TapTreeCoder,
|
|
45
45
|
// Ark PSBT fields
|
|
46
46
|
ArkPsbtFieldKey, ArkPsbtFieldKeyType, setArkPsbtField, getArkPsbtFields, CosignerPublicKey, VtxoTreeExpiry, VtxoTaprootTree, ConditionWitness,
|
|
47
47
|
// Utils
|
package/dist/esm/intent/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { OP,
|
|
1
|
+
import { OP, Script, SigHash } from "@scure/btc-signer";
|
|
2
2
|
import { schnorr } from "@noble/curves/secp256k1.js";
|
|
3
|
+
import { Transaction } from '../utils/transaction.js';
|
|
3
4
|
/**
|
|
4
5
|
* Intent proof implementation for Bitcoin message signing.
|
|
5
6
|
*
|
|
@@ -84,9 +85,6 @@ function craftToSpendTx(message, pkScript) {
|
|
|
84
85
|
const messageHash = hashMessage(message);
|
|
85
86
|
const tx = new Transaction({
|
|
86
87
|
version: 0,
|
|
87
|
-
allowUnknownOutputs: true,
|
|
88
|
-
allowUnknown: true,
|
|
89
|
-
allowUnknownInputs: true,
|
|
90
88
|
});
|
|
91
89
|
// add input with zero hash and max index
|
|
92
90
|
tx.addInput({
|
|
@@ -109,9 +107,6 @@ function craftToSignTx(toSpend, inputs, outputs) {
|
|
|
109
107
|
const firstInput = inputs[0];
|
|
110
108
|
const tx = new Transaction({
|
|
111
109
|
version: 2,
|
|
112
|
-
allowUnknownOutputs: outputs.length === 0,
|
|
113
|
-
allowUnknown: true,
|
|
114
|
-
allowUnknownInputs: true,
|
|
115
110
|
lockTime: 0,
|
|
116
111
|
});
|
|
117
112
|
// add the first "toSpend" input
|
|
@@ -18,9 +18,10 @@ export const ESPLORA_URL = {
|
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
20
|
export class EsploraProvider {
|
|
21
|
-
constructor(baseUrl) {
|
|
21
|
+
constructor(baseUrl, opts) {
|
|
22
22
|
this.baseUrl = baseUrl;
|
|
23
|
-
this.
|
|
23
|
+
this.pollingInterval = opts?.pollingInterval ?? 15000;
|
|
24
|
+
this.forcePolling = opts?.forcePolling ?? false;
|
|
24
25
|
}
|
|
25
26
|
async getCoins(address) {
|
|
26
27
|
const response = await fetch(`${this.baseUrl}/address/${address}/utxo`);
|
|
@@ -91,13 +92,9 @@ export class EsploraProvider {
|
|
|
91
92
|
let intervalId = null;
|
|
92
93
|
const wsUrl = this.baseUrl.replace(/^http(s)?:/, "ws$1:") + "/v1/ws";
|
|
93
94
|
const poll = async () => {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
// websocket is not reliable, so we will fallback to polling
|
|
98
|
-
const pollingInterval = 5000; // 5 seconds
|
|
99
|
-
const getAllTxs = () => {
|
|
100
|
-
return Promise.all(addresses.map((address) => this.getTransactions(address))).then((txArrays) => txArrays.flat());
|
|
95
|
+
const getAllTxs = async () => {
|
|
96
|
+
const txArrays = await Promise.all(addresses.map((address) => this.getTransactions(address)));
|
|
97
|
+
return txArrays.flat();
|
|
101
98
|
};
|
|
102
99
|
// initial fetch to get existing transactions
|
|
103
100
|
const initialTxs = await getAllTxs();
|
|
@@ -122,9 +119,19 @@ export class EsploraProvider {
|
|
|
122
119
|
catch (error) {
|
|
123
120
|
console.error("Error in polling mechanism:", error);
|
|
124
121
|
}
|
|
125
|
-
}, pollingInterval);
|
|
122
|
+
}, this.pollingInterval);
|
|
126
123
|
};
|
|
127
124
|
let ws = null;
|
|
125
|
+
const stopFunc = () => {
|
|
126
|
+
if (ws)
|
|
127
|
+
ws.close();
|
|
128
|
+
if (intervalId)
|
|
129
|
+
clearInterval(intervalId);
|
|
130
|
+
};
|
|
131
|
+
if (this.forcePolling) {
|
|
132
|
+
await poll();
|
|
133
|
+
return stopFunc;
|
|
134
|
+
}
|
|
128
135
|
try {
|
|
129
136
|
ws = new WebSocket(wsUrl);
|
|
130
137
|
ws.addEventListener("open", () => {
|
|
@@ -171,13 +178,6 @@ export class EsploraProvider {
|
|
|
171
178
|
// if websocket is not available, fallback to polling
|
|
172
179
|
await poll();
|
|
173
180
|
}
|
|
174
|
-
const stopFunc = () => {
|
|
175
|
-
if (ws && ws.readyState === WebSocket.OPEN)
|
|
176
|
-
ws.close();
|
|
177
|
-
if (intervalId)
|
|
178
|
-
clearInterval(intervalId);
|
|
179
|
-
this.polling = false;
|
|
180
|
-
};
|
|
181
181
|
return stopFunc;
|
|
182
182
|
}
|
|
183
183
|
async getChainTip() {
|
|
@@ -245,8 +245,7 @@ const isExplorerTransaction = (tx) => {
|
|
|
245
245
|
return (typeof tx.txid === "string" &&
|
|
246
246
|
Array.isArray(tx.vout) &&
|
|
247
247
|
tx.vout.every((vout) => typeof vout.scriptpubkey_address === "string" &&
|
|
248
|
-
typeof vout.value === "
|
|
248
|
+
typeof vout.value === "number") &&
|
|
249
249
|
typeof tx.status === "object" &&
|
|
250
|
-
typeof tx.status.confirmed === "boolean"
|
|
251
|
-
typeof tx.status.block_time === "number");
|
|
250
|
+
typeof tx.status.confirmed === "boolean");
|
|
252
251
|
};
|
|
@@ -12,7 +12,14 @@ const serializeVtxo = (v) => ({
|
|
|
12
12
|
tapTree: toHex(v.tapTree),
|
|
13
13
|
forfeitTapLeafScript: serializeTapLeaf(v.forfeitTapLeafScript),
|
|
14
14
|
intentTapLeafScript: serializeTapLeaf(v.intentTapLeafScript),
|
|
15
|
-
extraWitness: v.extraWitness?.map(
|
|
15
|
+
extraWitness: v.extraWitness?.map(toHex),
|
|
16
|
+
});
|
|
17
|
+
const serializeUtxo = (u) => ({
|
|
18
|
+
...u,
|
|
19
|
+
tapTree: toHex(u.tapTree),
|
|
20
|
+
forfeitTapLeafScript: serializeTapLeaf(u.forfeitTapLeafScript),
|
|
21
|
+
intentTapLeafScript: serializeTapLeaf(u.intentTapLeafScript),
|
|
22
|
+
extraWitness: u.extraWitness?.map(toHex),
|
|
16
23
|
});
|
|
17
24
|
const deserializeTapLeaf = (t) => {
|
|
18
25
|
const cb = TaprootControlBlock.decode(fromHex(t.cb));
|
|
@@ -24,13 +31,21 @@ const deserializeVtxo = (o) => ({
|
|
|
24
31
|
tapTree: fromHex(o.tapTree),
|
|
25
32
|
forfeitTapLeafScript: deserializeTapLeaf(o.forfeitTapLeafScript),
|
|
26
33
|
intentTapLeafScript: deserializeTapLeaf(o.intentTapLeafScript),
|
|
27
|
-
extraWitness: o.extraWitness?.map(
|
|
34
|
+
extraWitness: o.extraWitness?.map(fromHex),
|
|
35
|
+
});
|
|
36
|
+
const deserializeUtxo = (o) => ({
|
|
37
|
+
...o,
|
|
38
|
+
tapTree: fromHex(o.tapTree),
|
|
39
|
+
forfeitTapLeafScript: deserializeTapLeaf(o.forfeitTapLeafScript),
|
|
40
|
+
intentTapLeafScript: deserializeTapLeaf(o.intentTapLeafScript),
|
|
41
|
+
extraWitness: o.extraWitness?.map(fromHex),
|
|
28
42
|
});
|
|
29
43
|
export class WalletRepositoryImpl {
|
|
30
44
|
constructor(storage) {
|
|
31
45
|
this.storage = storage;
|
|
32
46
|
this.cache = {
|
|
33
47
|
vtxos: new Map(),
|
|
48
|
+
utxos: new Map(),
|
|
34
49
|
transactions: new Map(),
|
|
35
50
|
walletState: null,
|
|
36
51
|
initialized: new Set(),
|
|
@@ -83,6 +98,53 @@ export class WalletRepositoryImpl {
|
|
|
83
98
|
this.cache.vtxos.set(address, []);
|
|
84
99
|
await this.storage.removeItem(`vtxos:${address}`);
|
|
85
100
|
}
|
|
101
|
+
async getUtxos(address) {
|
|
102
|
+
const cacheKey = `utxos:${address}`;
|
|
103
|
+
if (this.cache.utxos.has(address)) {
|
|
104
|
+
return this.cache.utxos.get(address);
|
|
105
|
+
}
|
|
106
|
+
const stored = await this.storage.getItem(cacheKey);
|
|
107
|
+
if (!stored) {
|
|
108
|
+
this.cache.utxos.set(address, []);
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const parsed = JSON.parse(stored);
|
|
113
|
+
const utxos = parsed.map(deserializeUtxo);
|
|
114
|
+
this.cache.utxos.set(address, utxos.slice());
|
|
115
|
+
return utxos.slice();
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
console.error(`Failed to parse UTXOs for address ${address}:`, error);
|
|
119
|
+
this.cache.utxos.set(address, []);
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async saveUtxos(address, utxos) {
|
|
124
|
+
const storedUtxos = await this.getUtxos(address);
|
|
125
|
+
utxos.forEach((utxo) => {
|
|
126
|
+
const existing = storedUtxos.findIndex((u) => u.txid === utxo.txid && u.vout === utxo.vout);
|
|
127
|
+
if (existing !== -1) {
|
|
128
|
+
storedUtxos[existing] = utxo;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
storedUtxos.push(utxo);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
this.cache.utxos.set(address, storedUtxos.slice());
|
|
135
|
+
await this.storage.setItem(`utxos:${address}`, JSON.stringify(storedUtxos.map(serializeUtxo)));
|
|
136
|
+
}
|
|
137
|
+
async removeUtxo(address, utxoId) {
|
|
138
|
+
const utxos = await this.getUtxos(address);
|
|
139
|
+
const [txid, vout] = utxoId.split(":");
|
|
140
|
+
const filtered = utxos.filter((v) => !(v.txid === txid && v.vout === parseInt(vout, 10)));
|
|
141
|
+
this.cache.utxos.set(address, filtered.slice());
|
|
142
|
+
await this.storage.setItem(`utxos:${address}`, JSON.stringify(filtered.map(serializeUtxo)));
|
|
143
|
+
}
|
|
144
|
+
async clearUtxos(address) {
|
|
145
|
+
this.cache.utxos.set(address, []);
|
|
146
|
+
await this.storage.removeItem(`utxos:${address}`);
|
|
147
|
+
}
|
|
86
148
|
async getTransactionHistory(address) {
|
|
87
149
|
const cacheKey = `tx:${address}`;
|
|
88
150
|
if (this.cache.transactions.has(address)) {
|
package/dist/esm/script/base.js
CHANGED
|
@@ -4,7 +4,7 @@ import { PSBTOutput } from "@scure/btc-signer/psbt.js";
|
|
|
4
4
|
import { hex } from "@scure/base";
|
|
5
5
|
import { ArkAddress } from './address.js';
|
|
6
6
|
import { ConditionCSVMultisigTapscript, CSVMultisigTapscript, } from './tapscript.js';
|
|
7
|
-
const TapTreeCoder = PSBTOutput.tapTree[2];
|
|
7
|
+
export const TapTreeCoder = PSBTOutput.tapTree[2];
|
|
8
8
|
export function scriptFromTapLeafScript(leaf) {
|
|
9
9
|
return leaf[1].subarray(0, leaf[1].length - 1); // remove the version byte
|
|
10
10
|
}
|
|
@@ -24,7 +24,16 @@ export class VtxoScript {
|
|
|
24
24
|
}
|
|
25
25
|
constructor(scripts) {
|
|
26
26
|
this.scripts = scripts;
|
|
27
|
-
|
|
27
|
+
// reverse the scripts if the number of scripts is odd
|
|
28
|
+
// this is to be compatible with arkd algorithm computing taproot tree from list of tapscripts
|
|
29
|
+
// the scripts must be reversed only HERE while we compute the tweaked public key
|
|
30
|
+
// but the original order should be preserved while encoding as taptree
|
|
31
|
+
// note: .slice().reverse() is used instead of .reverse() to avoid mutating the original array
|
|
32
|
+
const list = scripts.length % 2 !== 0 ? scripts.slice().reverse() : scripts;
|
|
33
|
+
const tapTree = taprootListToTree(list.map((script) => ({
|
|
34
|
+
script,
|
|
35
|
+
leafVersion: TAP_LEAF_VERSION,
|
|
36
|
+
})));
|
|
28
37
|
const payment = p2tr(TAPROOT_UNSPENDABLE_KEY, tapTree, undefined, true);
|
|
29
38
|
if (!payment.tapLeafScript ||
|
|
30
39
|
payment.tapLeafScript.length !== scripts.length) {
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { schnorr } from "@noble/curves/secp256k1.js";
|
|
2
2
|
import { hex } from "@scure/base";
|
|
3
|
-
import { DEFAULT_SEQUENCE,
|
|
3
|
+
import { DEFAULT_SEQUENCE, SigHash } from "@scure/btc-signer";
|
|
4
4
|
import { tapLeafHash } from "@scure/btc-signer/payment.js";
|
|
5
5
|
import { CLTVMultisigTapscript, decodeTapscript, } from '../script/tapscript.js';
|
|
6
6
|
import { scriptFromTapLeafScript, VtxoScript, } from '../script/base.js';
|
|
7
7
|
import { P2A } from './anchor.js';
|
|
8
8
|
import { setArkPsbtField, VtxoTaprootTree } from './unknownFields.js';
|
|
9
|
+
import { Transaction } from './transaction.js';
|
|
9
10
|
/**
|
|
10
11
|
* Builds an offchain transaction with checkpoint transactions.
|
|
11
12
|
*
|
|
@@ -45,8 +46,6 @@ function buildVirtualTx(inputs, outputs) {
|
|
|
45
46
|
}
|
|
46
47
|
const tx = new Transaction({
|
|
47
48
|
version: 3,
|
|
48
|
-
allowUnknown: true,
|
|
49
|
-
allowUnknownOutputs: true,
|
|
50
49
|
lockTime: Number(lockTime),
|
|
51
50
|
});
|
|
52
51
|
for (const [i, input] of inputs.entries()) {
|
|
@@ -71,8 +70,7 @@ function buildVirtualTx(inputs, outputs) {
|
|
|
71
70
|
}
|
|
72
71
|
function buildCheckpointTx(vtxo, serverUnrollScript) {
|
|
73
72
|
// create the checkpoint vtxo script from collaborative closure
|
|
74
|
-
const collaborativeClosure = decodeTapscript(vtxo.
|
|
75
|
-
scriptFromTapLeafScript(vtxo.tapLeafScript));
|
|
73
|
+
const collaborativeClosure = decodeTapscript(scriptFromTapLeafScript(vtxo.tapLeafScript));
|
|
76
74
|
// create the checkpoint vtxo script combining collaborative closure and server unroll script
|
|
77
75
|
const checkpointVtxoScript = new VtxoScript([
|
|
78
76
|
serverUnrollScript.script,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Transaction as BtcSignerTransaction } from "@scure/btc-signer";
|
|
2
|
+
/**
|
|
3
|
+
* Transaction is a wrapper around the @scure/btc-signer Transaction class.
|
|
4
|
+
* It adds the Ark protocol specific options to the transaction.
|
|
5
|
+
*/
|
|
6
|
+
export class Transaction extends BtcSignerTransaction {
|
|
7
|
+
constructor(opts) {
|
|
8
|
+
super(withArkOpts(opts));
|
|
9
|
+
}
|
|
10
|
+
static fromPSBT(psbt_, opts) {
|
|
11
|
+
return BtcSignerTransaction.fromPSBT(psbt_, withArkOpts(opts));
|
|
12
|
+
}
|
|
13
|
+
static fromRaw(raw, opts) {
|
|
14
|
+
return BtcSignerTransaction.fromRaw(raw, withArkOpts(opts));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
Transaction.ARK_TX_OPTS = {
|
|
18
|
+
allowUnknown: true,
|
|
19
|
+
allowUnknownOutputs: true,
|
|
20
|
+
allowUnknownInputs: true,
|
|
21
|
+
};
|
|
22
|
+
function withArkOpts(opts) {
|
|
23
|
+
return { ...Transaction.ARK_TX_OPTS, ...opts };
|
|
24
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { p2tr } from "@scure/btc-signer";
|
|
2
2
|
import { getNetwork } from '../networks.js';
|
|
3
3
|
import { ESPLORA_URL, EsploraProvider, } from '../providers/onchain.js';
|
|
4
4
|
import { findP2AOutput, P2A } from '../utils/anchor.js';
|
|
5
5
|
import { TxWeightEstimator } from '../utils/txSizeEstimator.js';
|
|
6
|
+
import { Transaction } from '../utils/transaction.js';
|
|
6
7
|
/**
|
|
7
8
|
* Onchain Bitcoin wallet implementation for traditional Bitcoin transactions.
|
|
8
9
|
*
|
|
@@ -104,9 +105,8 @@ export class OnchainWallet {
|
|
|
104
105
|
async bumpP2A(parent) {
|
|
105
106
|
const parentVsize = parent.vsize;
|
|
106
107
|
let child = new Transaction({
|
|
107
|
-
allowUnknownInputs: true,
|
|
108
|
-
allowLegacyWitnessUtxo: true,
|
|
109
108
|
version: 3,
|
|
109
|
+
allowLegacyWitnessUtxo: true,
|
|
110
110
|
});
|
|
111
111
|
child.addInput(findP2AOutput(parent)); // throws if not found
|
|
112
112
|
const childVsize = TxWeightEstimator.create()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference lib="webworker" />
|
|
2
2
|
import { SingleKey } from '../../identity/singleKey.js';
|
|
3
|
-
import { isRecoverable, isSpendable, isSubdust } from '../index.js';
|
|
3
|
+
import { isRecoverable, isSpendable, isSubdust, } from '../index.js';
|
|
4
4
|
import { Wallet } from '../wallet.js';
|
|
5
5
|
import { Request } from './request.js';
|
|
6
6
|
import { Response } from './response.js';
|
|
@@ -10,7 +10,7 @@ import { RestIndexerProvider } from '../../providers/indexer.js';
|
|
|
10
10
|
import { hex } from "@scure/base";
|
|
11
11
|
import { IndexedDBStorageAdapter } from '../../storage/indexedDB.js';
|
|
12
12
|
import { WalletRepositoryImpl, } from '../../repositories/walletRepository.js';
|
|
13
|
-
import { extendVirtualCoin } from '../utils.js';
|
|
13
|
+
import { extendCoin, extendVirtualCoin } from '../utils.js';
|
|
14
14
|
import { DEFAULT_DB_NAME } from './utils.js';
|
|
15
15
|
/**
|
|
16
16
|
* Worker is a class letting to interact with ServiceWorkerWallet from the client
|
|
@@ -57,6 +57,15 @@ export class Worker {
|
|
|
57
57
|
spent: allVtxos.filter((vtxo) => !isSpendable(vtxo)),
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Get all boarding utxos from wallet repository
|
|
62
|
+
*/
|
|
63
|
+
async getAllBoardingUtxos() {
|
|
64
|
+
if (!this.wallet)
|
|
65
|
+
return [];
|
|
66
|
+
const address = await this.wallet.getBoardingAddress();
|
|
67
|
+
return await this.walletRepository.getUtxos(address);
|
|
68
|
+
}
|
|
60
69
|
async start(withServiceWorkerUpdate = true) {
|
|
61
70
|
self.addEventListener("message", async (event) => {
|
|
62
71
|
await this.handleMessage(event);
|
|
@@ -130,6 +139,14 @@ export class Worker {
|
|
|
130
139
|
this.sendMessageToAllClients(Response.vtxoUpdate(newVtxos, spentVtxos));
|
|
131
140
|
}
|
|
132
141
|
if (funds.type === "utxo") {
|
|
142
|
+
const newUtxos = funds.coins.map((utxo) => extendCoin(this.wallet, utxo));
|
|
143
|
+
if (newUtxos.length === 0) {
|
|
144
|
+
this.sendMessageToAllClients(Response.utxoUpdate([]));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const boardingAddress = await this.wallet?.getBoardingAddress();
|
|
148
|
+
// save utxos using unified repository
|
|
149
|
+
await this.walletRepository.saveUtxos(boardingAddress, newUtxos);
|
|
133
150
|
// notify all clients about the utxo update
|
|
134
151
|
this.sendMessageToAllClients(Response.utxoUpdate(funds.coins));
|
|
135
152
|
}
|
|
@@ -288,7 +305,7 @@ export class Worker {
|
|
|
288
305
|
}
|
|
289
306
|
try {
|
|
290
307
|
const [boardingUtxos, spendableVtxos, sweptVtxos] = await Promise.all([
|
|
291
|
-
this.
|
|
308
|
+
this.getAllBoardingUtxos(),
|
|
292
309
|
this.getSpendableVtxos(),
|
|
293
310
|
this.getSweptVtxos(),
|
|
294
311
|
]);
|
|
@@ -393,7 +410,7 @@ export class Worker {
|
|
|
393
410
|
return;
|
|
394
411
|
}
|
|
395
412
|
try {
|
|
396
|
-
const boardingUtxos = await this.
|
|
413
|
+
const boardingUtxos = await this.getAllBoardingUtxos();
|
|
397
414
|
event.source?.postMessage(Response.boardingUtxos(message.id, boardingUtxos));
|
|
398
415
|
}
|
|
399
416
|
catch (error) {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { base64, hex } from "@scure/base";
|
|
2
|
-
import { SigHash,
|
|
2
|
+
import { SigHash, TaprootControlBlock } from "@scure/btc-signer";
|
|
3
3
|
import { ChainTxType } from '../providers/indexer.js';
|
|
4
4
|
import { VtxoScript } from '../script/base.js';
|
|
5
5
|
import { TxWeightEstimator } from '../utils/txSizeEstimator.js';
|
|
6
6
|
import { Wallet } from './wallet.js';
|
|
7
|
+
import { Transaction } from '../utils/transaction.js';
|
|
7
8
|
export var Unroll;
|
|
8
9
|
(function (Unroll) {
|
|
9
10
|
let StepType;
|
|
@@ -102,9 +103,7 @@ export var Unroll;
|
|
|
102
103
|
if (virtualTxs.txs.length === 0) {
|
|
103
104
|
throw new Error(`Tx ${nextTxToBroadcast.txid} not found`);
|
|
104
105
|
}
|
|
105
|
-
const tx = Transaction.fromPSBT(base64.decode(virtualTxs.txs[0])
|
|
106
|
-
allowUnknownInputs: true,
|
|
107
|
-
});
|
|
106
|
+
const tx = Transaction.fromPSBT(base64.decode(virtualTxs.txs[0]));
|
|
108
107
|
// finalize the tree transaction
|
|
109
108
|
if (nextTxToBroadcast.type === ChainTxType.TREE) {
|
|
110
109
|
const input = tx.getInput(0);
|
|
@@ -197,7 +196,7 @@ export var Unroll;
|
|
|
197
196
|
});
|
|
198
197
|
txWeightEstimator.addTapscriptInput(64, spendingLeaf[1].length, TaprootControlBlock.encode(spendingLeaf[0]).length);
|
|
199
198
|
}
|
|
200
|
-
const tx = new Transaction({
|
|
199
|
+
const tx = new Transaction({ version: 2 });
|
|
201
200
|
for (const input of inputs) {
|
|
202
201
|
tx.addInput(input);
|
|
203
202
|
}
|
package/dist/esm/wallet/utils.js
CHANGED
|
@@ -6,3 +6,11 @@ export function extendVirtualCoin(wallet, vtxo) {
|
|
|
6
6
|
tapTree: wallet.offchainTapscript.encode(),
|
|
7
7
|
};
|
|
8
8
|
}
|
|
9
|
+
export function extendCoin(wallet, utxo) {
|
|
10
|
+
return {
|
|
11
|
+
...utxo,
|
|
12
|
+
forfeitTapLeafScript: wallet.boardingTapscript.forfeit(),
|
|
13
|
+
intentTapLeafScript: wallet.boardingTapscript.exit(),
|
|
14
|
+
tapTree: wallet.boardingTapscript.encode(),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -24,7 +24,7 @@ import { ConditionWitness, VtxoTaprootTree } from '../utils/unknownFields.js';
|
|
|
24
24
|
import { InMemoryStorageAdapter } from '../storage/inMemory.js';
|
|
25
25
|
import { WalletRepositoryImpl, } from '../repositories/walletRepository.js';
|
|
26
26
|
import { ContractRepositoryImpl, } from '../repositories/contractRepository.js';
|
|
27
|
-
import { extendVirtualCoin } from './utils.js';
|
|
27
|
+
import { extendCoin, extendVirtualCoin } from './utils.js';
|
|
28
28
|
/**
|
|
29
29
|
* Main wallet implementation for Bitcoin transactions with Ark protocol support.
|
|
30
30
|
* The wallet does not store any data locally and relies on Ark and onchain
|
|
@@ -333,15 +333,12 @@ export class Wallet {
|
|
|
333
333
|
async getBoardingUtxos() {
|
|
334
334
|
const boardingAddress = await this.getBoardingAddress();
|
|
335
335
|
const boardingUtxos = await this.onchainProvider.getCoins(boardingAddress);
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
intentTapLeafScript: exit,
|
|
343
|
-
tapTree: encodedBoardingTapscript,
|
|
344
|
-
}));
|
|
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;
|
|
345
342
|
}
|
|
346
343
|
async sendBitcoin(params) {
|
|
347
344
|
if (params.amount <= 0) {
|
|
@@ -761,8 +758,6 @@ export class Wallet {
|
|
|
761
758
|
const vtxo = vtxos.find((vtxo) => vtxo.txid === input.txid && vtxo.vout === input.vout);
|
|
762
759
|
// boarding utxo, we need to sign the settlement tx
|
|
763
760
|
if (!vtxo) {
|
|
764
|
-
hasBoardingUtxos = true;
|
|
765
|
-
const inputIndexes = [];
|
|
766
761
|
for (let i = 0; i < settlementPsbt.inputsLength; i++) {
|
|
767
762
|
const settlementInput = settlementPsbt.getInput(i);
|
|
768
763
|
if (!settlementInput.txid ||
|
|
@@ -778,9 +773,12 @@ export class Wallet {
|
|
|
778
773
|
settlementPsbt.updateInput(i, {
|
|
779
774
|
tapLeafScript: [input.forfeitTapLeafScript],
|
|
780
775
|
});
|
|
781
|
-
|
|
776
|
+
settlementPsbt = await this.identity.sign(settlementPsbt, [
|
|
777
|
+
i,
|
|
778
|
+
]);
|
|
779
|
+
hasBoardingUtxos = true;
|
|
780
|
+
break;
|
|
782
781
|
}
|
|
783
|
-
settlementPsbt = await this.identity.sign(settlementPsbt, inputIndexes);
|
|
784
782
|
continue;
|
|
785
783
|
}
|
|
786
784
|
if (isRecoverable(vtxo) || isSubdust(vtxo, this.dustAmount)) {
|
package/dist/types/forfeit.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { Transaction } from "
|
|
1
|
+
import { Transaction } from "./utils/transaction";
|
|
2
2
|
import { TransactionInputUpdate } from "@scure/btc-signer/psbt.js";
|
|
3
3
|
export declare function buildForfeitTx(inputs: TransactionInputUpdate[], forfeitPkScript: Uint8Array, txLocktime?: number): Transaction;
|
|
@@ -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.
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
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";
|
|
@@ -33,5 +33,5 @@ import { Unroll } from "./wallet/unroll";
|
|
|
33
33
|
import { WalletRepositoryImpl } from "./repositories/walletRepository";
|
|
34
34
|
import { ContractRepositoryImpl } from "./repositories/contractRepository";
|
|
35
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, ArkPsbtFieldKey, ArkPsbtFieldKeyType, setArkPsbtField, getArkPsbtFields, CosignerPublicKey, VtxoTreeExpiry, VtxoTaprootTree, ConditionWitness, buildOffchainTx, verifyTapscriptSignatures, waitForIncomingFunds, hasBoardingTxExpired, ArkNote, networks, WalletRepositoryImpl, ContractRepositoryImpl, Intent, TxTree, P2A, Unroll, Transaction, ArkError, maybeArkError, };
|
|
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
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, };
|
|
@@ -49,8 +49,12 @@ export interface OnchainProvider {
|
|
|
49
49
|
*/
|
|
50
50
|
export declare class EsploraProvider implements OnchainProvider {
|
|
51
51
|
private baseUrl;
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
readonly pollingInterval: number;
|
|
53
|
+
readonly forcePolling: boolean;
|
|
54
|
+
constructor(baseUrl: string, opts?: {
|
|
55
|
+
pollingInterval?: number;
|
|
56
|
+
forcePolling?: boolean;
|
|
57
|
+
});
|
|
54
58
|
getCoins(address: string): Promise<Coin[]>;
|
|
55
59
|
getFeeRate(): Promise<number | undefined>;
|
|
56
60
|
broadcastTransaction(...txs: string[]): Promise<string>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { StorageAdapter } from "../storage";
|
|
2
|
-
import { ArkTransaction, ExtendedVirtualCoin } from "../wallet";
|
|
2
|
+
import { ArkTransaction, ExtendedCoin, ExtendedVirtualCoin } from "../wallet";
|
|
3
3
|
export interface WalletState {
|
|
4
4
|
lastSyncTime?: number;
|
|
5
5
|
settings?: Record<string, any>;
|
|
@@ -9,6 +9,10 @@ export interface WalletRepository {
|
|
|
9
9
|
saveVtxos(address: string, vtxos: ExtendedVirtualCoin[]): Promise<void>;
|
|
10
10
|
removeVtxo(address: string, vtxoId: string): Promise<void>;
|
|
11
11
|
clearVtxos(address: string): Promise<void>;
|
|
12
|
+
getUtxos(address: string): Promise<ExtendedCoin[]>;
|
|
13
|
+
saveUtxos(address: string, utxos: ExtendedCoin[]): Promise<void>;
|
|
14
|
+
removeUtxo(address: string, utxoId: string): Promise<void>;
|
|
15
|
+
clearUtxos(address: string): Promise<void>;
|
|
12
16
|
getTransactionHistory(address: string): Promise<ArkTransaction[]>;
|
|
13
17
|
saveTransactions(address: string, txs: ArkTransaction[]): Promise<void>;
|
|
14
18
|
clearTransactions(address: string): Promise<void>;
|
|
@@ -23,6 +27,10 @@ export declare class WalletRepositoryImpl implements WalletRepository {
|
|
|
23
27
|
saveVtxos(address: string, vtxos: ExtendedVirtualCoin[]): Promise<void>;
|
|
24
28
|
removeVtxo(address: string, vtxoId: string): Promise<void>;
|
|
25
29
|
clearVtxos(address: string): Promise<void>;
|
|
30
|
+
getUtxos(address: string): Promise<ExtendedCoin[]>;
|
|
31
|
+
saveUtxos(address: string, utxos: ExtendedCoin[]): Promise<void>;
|
|
32
|
+
removeUtxo(address: string, utxoId: string): Promise<void>;
|
|
33
|
+
clearUtxos(address: string): Promise<void>;
|
|
26
34
|
getTransactionHistory(address: string): Promise<ArkTransaction[]>;
|
|
27
35
|
saveTransactions(address: string, txs: ArkTransaction[]): Promise<void>;
|
|
28
36
|
clearTransactions(address: string): Promise<void>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { NETWORK } from "@scure/btc-signer";
|
|
2
|
+
import { PSBTOutput } from "@scure/btc-signer/psbt.js";
|
|
2
3
|
import { Bytes } from "@scure/btc-signer/utils.js";
|
|
3
4
|
import { ArkAddress } from "./address";
|
|
4
5
|
import { ConditionCSVMultisigTapscript, CSVMultisigTapscript } from "./tapscript";
|
|
@@ -10,6 +11,7 @@ export type TapLeafScript = [
|
|
|
10
11
|
},
|
|
11
12
|
Bytes
|
|
12
13
|
];
|
|
14
|
+
export declare const TapTreeCoder: (typeof PSBTOutput.tapTree)[2];
|
|
13
15
|
export declare function scriptFromTapLeafScript(leaf: TapLeafScript): Bytes;
|
|
14
16
|
/**
|
|
15
17
|
* VtxoScript is a script that contains a list of tapleaf scripts.
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import { Transaction } from "@scure/btc-signer";
|
|
2
|
-
import { Bytes } from "@scure/btc-signer/utils.js";
|
|
3
1
|
import { TransactionOutput } from "@scure/btc-signer/psbt.js";
|
|
4
2
|
import { ExtendedCoin, VirtualCoin } from "../wallet";
|
|
5
3
|
import { RelativeTimelock } from "../script/tapscript";
|
|
6
4
|
import { EncodedVtxoScript, TapLeafScript } from "../script/base";
|
|
7
5
|
import { CSVMultisigTapscript } from "../script/tapscript";
|
|
6
|
+
import { Transaction } from "./transaction";
|
|
8
7
|
export type ArkTxInput = {
|
|
9
8
|
tapLeafScript: TapLeafScript;
|
|
10
|
-
checkpointTapLeafScript?: Bytes;
|
|
11
9
|
} & EncodedVtxoScript & Pick<VirtualCoin, "txid" | "vout" | "value">;
|
|
12
10
|
export type OffchainTx = {
|
|
13
11
|
arkTx: Transaction;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Transaction as BtcSignerTransaction } from "@scure/btc-signer";
|
|
2
|
+
import { TxOpts } from "@scure/btc-signer/transaction";
|
|
3
|
+
import { Bytes } from "@scure/btc-signer/utils";
|
|
4
|
+
/**
|
|
5
|
+
* Transaction is a wrapper around the @scure/btc-signer Transaction class.
|
|
6
|
+
* It adds the Ark protocol specific options to the transaction.
|
|
7
|
+
*/
|
|
8
|
+
export declare class Transaction extends BtcSignerTransaction {
|
|
9
|
+
static ARK_TX_OPTS: TxOpts;
|
|
10
|
+
constructor(opts?: TxOpts);
|
|
11
|
+
static fromPSBT(psbt_: Bytes, opts?: TxOpts): Transaction;
|
|
12
|
+
static fromRaw(raw: Bytes, opts?: TxOpts): Transaction;
|
|
13
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Transaction } from "@scure/btc-signer";
|
|
2
1
|
import { P2TR } from "@scure/btc-signer/payment.js";
|
|
3
2
|
import { Coin, SendBitcoinParams } from ".";
|
|
4
3
|
import { Identity } from "../identity";
|
|
5
4
|
import { Network, NetworkName } from "../networks";
|
|
6
5
|
import { OnchainProvider } from "../providers/onchain";
|
|
7
6
|
import { AnchorBumper } from "../utils/anchor";
|
|
7
|
+
import { Transaction } from "../utils/transaction";
|
|
8
8
|
/**
|
|
9
9
|
* Onchain Bitcoin wallet implementation for traditional Bitcoin transactions.
|
|
10
10
|
*
|