@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/README.md
CHANGED
|
@@ -130,22 +130,12 @@ const manager = new VtxoManager(wallet, {
|
|
|
130
130
|
Renew VTXOs before they expire to keep your liquidity accessible. This settles all VTXOs (including recoverable ones) back to your wallet with a fresh expiration time.
|
|
131
131
|
|
|
132
132
|
```typescript
|
|
133
|
+
// Renew all VTXOs to prevent expiration
|
|
134
|
+
const txid = await manager.renewVtxos()
|
|
135
|
+
console.log('Renewed:', txid)
|
|
136
|
+
|
|
133
137
|
// Check which VTXOs are expiring soon
|
|
134
138
|
const expiringVtxos = await manager.getExpiringVtxos()
|
|
135
|
-
if (expiringVtxos.length > 0) {
|
|
136
|
-
console.log(`${expiringVtxos.length} VTXOs expiring soon`)
|
|
137
|
-
|
|
138
|
-
expiringVtxos.forEach(vtxo => {
|
|
139
|
-
const timeLeft = vtxo.virtualStatus.batchExpiry! - Date.now()
|
|
140
|
-
const hoursLeft = Math.floor(timeLeft / (1000 * 60 * 60))
|
|
141
|
-
console.log(`VTXO ${vtxo.txid} expires in ${hoursLeft} hours`)
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
// Renew all VTXOs to prevent expiration
|
|
145
|
-
const txid = await manager.renewVtxos()
|
|
146
|
-
console.log('Renewed:', txid)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
139
|
// Override threshold percentage (e.g., renew when 5% of time remains)
|
|
150
140
|
const urgentlyExpiring = await manager.getExpiringVtxos(5)
|
|
151
141
|
```
|
|
@@ -156,20 +146,13 @@ const urgentlyExpiring = await manager.getExpiringVtxos(5)
|
|
|
156
146
|
Recover VTXOs that have been swept by the server or consolidate small amounts (subdust).
|
|
157
147
|
|
|
158
148
|
```typescript
|
|
149
|
+
// Recover swept VTXOs and preconfirmed subdust
|
|
150
|
+
const txid = await manager.recoverVtxos((event) => {
|
|
151
|
+
console.log('Settlement event:', event.type)
|
|
152
|
+
})
|
|
153
|
+
console.log('Recovered:', txid)
|
|
159
154
|
// Check what's recoverable
|
|
160
155
|
const balance = await manager.getRecoverableBalance()
|
|
161
|
-
console.log(`Recoverable: ${balance.recoverable} sats`)
|
|
162
|
-
console.log(`Subdust: ${balance.subdust} sats`)
|
|
163
|
-
console.log(`Subdust included: ${balance.includesSubdust}`)
|
|
164
|
-
console.log(`VTXO count: ${balance.vtxoCount}`)
|
|
165
|
-
|
|
166
|
-
if (balance.recoverable > 0n) {
|
|
167
|
-
// Recover swept VTXOs and preconfirmed subdust
|
|
168
|
-
const txid = await manager.recoverVtxos((event) => {
|
|
169
|
-
console.log('Settlement event:', event.type)
|
|
170
|
-
})
|
|
171
|
-
console.log('Recovered:', txid)
|
|
172
|
-
}
|
|
173
156
|
```
|
|
174
157
|
|
|
175
158
|
|
package/dist/cjs/forfeit.js
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.buildForfeitTx = buildForfeitTx;
|
|
4
|
-
const
|
|
4
|
+
const transaction_1 = require("./utils/transaction");
|
|
5
5
|
const anchor_1 = require("./utils/anchor");
|
|
6
6
|
function buildForfeitTx(inputs, forfeitPkScript, txLocktime) {
|
|
7
|
-
const tx = new
|
|
7
|
+
const tx = new transaction_1.Transaction({
|
|
8
8
|
version: 3,
|
|
9
9
|
lockTime: txLocktime,
|
|
10
|
-
allowUnknownOutputs: true,
|
|
11
|
-
allowUnknown: true,
|
|
12
|
-
allowUnknownInputs: true,
|
|
13
10
|
});
|
|
14
11
|
let amount = 0n;
|
|
15
12
|
for (const input of inputs) {
|
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SingleKey = void 0;
|
|
4
4
|
const utils_js_1 = require("@scure/btc-signer/utils.js");
|
|
5
|
-
const
|
|
5
|
+
const btc_signer_1 = require("@scure/btc-signer");
|
|
6
6
|
const base_1 = require("@scure/base");
|
|
7
7
|
const signingSession_1 = require("../tree/signingSession");
|
|
8
8
|
const secp256k1_1 = require("@noble/secp256k1");
|
|
9
|
-
const
|
|
10
|
-
const ALL_SIGHASH = Object.values(transaction_js_1.SigHash).filter((x) => typeof x === "number");
|
|
9
|
+
const ALL_SIGHASH = Object.values(btc_signer_1.SigHash).filter((x) => typeof x === "number");
|
|
11
10
|
/**
|
|
12
11
|
* In-memory single key implementation for Bitcoin transaction signing.
|
|
13
12
|
*
|
|
@@ -51,7 +50,7 @@ class SingleKey {
|
|
|
51
50
|
const txCpy = tx.clone();
|
|
52
51
|
if (!inputIndexes) {
|
|
53
52
|
try {
|
|
54
|
-
if (!txCpy.sign(this.key, ALL_SIGHASH
|
|
53
|
+
if (!txCpy.sign(this.key, ALL_SIGHASH)) {
|
|
55
54
|
throw new Error("Failed to sign transaction");
|
|
56
55
|
}
|
|
57
56
|
}
|
|
@@ -67,7 +66,7 @@ class SingleKey {
|
|
|
67
66
|
return txCpy;
|
|
68
67
|
}
|
|
69
68
|
for (const inputIndex of inputIndexes) {
|
|
70
|
-
if (!txCpy.signIdx(this.key, inputIndex, ALL_SIGHASH
|
|
69
|
+
if (!txCpy.signIdx(this.key, inputIndex, ALL_SIGHASH)) {
|
|
71
70
|
throw new Error(`Failed to sign input #${inputIndex}`);
|
|
72
71
|
}
|
|
73
72
|
}
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.maybeArkError = void 0;
|
|
5
|
-
const
|
|
6
|
-
Object.defineProperty(exports, "Transaction", { enumerable: true, get: function () { return
|
|
3
|
+
exports.Transaction = exports.Unroll = exports.P2A = exports.TxTree = exports.Intent = exports.ContractRepositoryImpl = exports.WalletRepositoryImpl = exports.networks = exports.ArkNote = exports.hasBoardingTxExpired = exports.waitForIncomingFunds = exports.verifyTapscriptSignatures = exports.buildOffchainTx = exports.ConditionWitness = exports.VtxoTaprootTree = exports.VtxoTreeExpiry = exports.CosignerPublicKey = exports.getArkPsbtFields = exports.setArkPsbtField = exports.ArkPsbtFieldKeyType = exports.ArkPsbtFieldKey = exports.TapTreeCoder = exports.CLTVMultisigTapscript = exports.ConditionMultisigTapscript = exports.ConditionCSVMultisigTapscript = exports.CSVMultisigTapscript = exports.MultisigTapscript = exports.decodeTapscript = exports.Response = exports.Request = exports.ServiceWorkerWallet = exports.Worker = exports.setupServiceWorker = exports.SettlementEventType = exports.ChainTxType = exports.IndexerTxType = exports.TxType = exports.VHTLC = exports.VtxoScript = exports.DefaultVtxo = exports.ArkAddress = exports.RestIndexerProvider = exports.RestArkProvider = exports.EsploraProvider = exports.ESPLORA_URL = exports.VtxoManager = exports.Ramps = exports.OnchainWallet = exports.SingleKey = exports.Wallet = void 0;
|
|
4
|
+
exports.maybeArkError = exports.ArkError = void 0;
|
|
5
|
+
const transaction_1 = require("./utils/transaction");
|
|
6
|
+
Object.defineProperty(exports, "Transaction", { enumerable: true, get: function () { return transaction_1.Transaction; } });
|
|
7
7
|
const singleKey_1 = require("./identity/singleKey");
|
|
8
8
|
Object.defineProperty(exports, "SingleKey", { enumerable: true, get: function () { return singleKey_1.SingleKey; } });
|
|
9
9
|
const address_1 = require("./script/address");
|
|
@@ -14,6 +14,7 @@ const default_1 = require("./script/default");
|
|
|
14
14
|
Object.defineProperty(exports, "DefaultVtxo", { enumerable: true, get: function () { return default_1.DefaultVtxo; } });
|
|
15
15
|
const base_1 = require("./script/base");
|
|
16
16
|
Object.defineProperty(exports, "VtxoScript", { enumerable: true, get: function () { return base_1.VtxoScript; } });
|
|
17
|
+
Object.defineProperty(exports, "TapTreeCoder", { enumerable: true, get: function () { return base_1.TapTreeCoder; } });
|
|
17
18
|
const wallet_1 = require("./wallet");
|
|
18
19
|
Object.defineProperty(exports, "TxType", { enumerable: true, get: function () { return wallet_1.TxType; } });
|
|
19
20
|
const wallet_2 = require("./wallet/wallet");
|
package/dist/cjs/intent/index.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Intent = void 0;
|
|
4
4
|
const btc_signer_1 = require("@scure/btc-signer");
|
|
5
5
|
const secp256k1_js_1 = require("@noble/curves/secp256k1.js");
|
|
6
|
+
const transaction_1 = require("../utils/transaction");
|
|
6
7
|
/**
|
|
7
8
|
* Intent proof implementation for Bitcoin message signing.
|
|
8
9
|
*
|
|
@@ -85,11 +86,8 @@ function validateOutputs(outputs) {
|
|
|
85
86
|
// craftToSpendTx creates the initial transaction that will be spent in the proof
|
|
86
87
|
function craftToSpendTx(message, pkScript) {
|
|
87
88
|
const messageHash = hashMessage(message);
|
|
88
|
-
const tx = new
|
|
89
|
+
const tx = new transaction_1.Transaction({
|
|
89
90
|
version: 0,
|
|
90
|
-
allowUnknownOutputs: true,
|
|
91
|
-
allowUnknown: true,
|
|
92
|
-
allowUnknownInputs: true,
|
|
93
91
|
});
|
|
94
92
|
// add input with zero hash and max index
|
|
95
93
|
tx.addInput({
|
|
@@ -110,11 +108,8 @@ function craftToSpendTx(message, pkScript) {
|
|
|
110
108
|
// craftToSignTx creates the transaction that will be signed for the proof
|
|
111
109
|
function craftToSignTx(toSpend, inputs, outputs) {
|
|
112
110
|
const firstInput = inputs[0];
|
|
113
|
-
const tx = new
|
|
111
|
+
const tx = new transaction_1.Transaction({
|
|
114
112
|
version: 2,
|
|
115
|
-
allowUnknownOutputs: outputs.length === 0,
|
|
116
|
-
allowUnknown: true,
|
|
117
|
-
allowUnknownInputs: true,
|
|
118
113
|
lockTime: 0,
|
|
119
114
|
});
|
|
120
115
|
// add the first "toSpend" input
|
|
@@ -21,9 +21,10 @@ exports.ESPLORA_URL = {
|
|
|
21
21
|
* ```
|
|
22
22
|
*/
|
|
23
23
|
class EsploraProvider {
|
|
24
|
-
constructor(baseUrl) {
|
|
24
|
+
constructor(baseUrl, opts) {
|
|
25
25
|
this.baseUrl = baseUrl;
|
|
26
|
-
this.
|
|
26
|
+
this.pollingInterval = opts?.pollingInterval ?? 15000;
|
|
27
|
+
this.forcePolling = opts?.forcePolling ?? false;
|
|
27
28
|
}
|
|
28
29
|
async getCoins(address) {
|
|
29
30
|
const response = await fetch(`${this.baseUrl}/address/${address}/utxo`);
|
|
@@ -94,13 +95,9 @@ class EsploraProvider {
|
|
|
94
95
|
let intervalId = null;
|
|
95
96
|
const wsUrl = this.baseUrl.replace(/^http(s)?:/, "ws$1:") + "/v1/ws";
|
|
96
97
|
const poll = async () => {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
// websocket is not reliable, so we will fallback to polling
|
|
101
|
-
const pollingInterval = 5000; // 5 seconds
|
|
102
|
-
const getAllTxs = () => {
|
|
103
|
-
return Promise.all(addresses.map((address) => this.getTransactions(address))).then((txArrays) => txArrays.flat());
|
|
98
|
+
const getAllTxs = async () => {
|
|
99
|
+
const txArrays = await Promise.all(addresses.map((address) => this.getTransactions(address)));
|
|
100
|
+
return txArrays.flat();
|
|
104
101
|
};
|
|
105
102
|
// initial fetch to get existing transactions
|
|
106
103
|
const initialTxs = await getAllTxs();
|
|
@@ -125,9 +122,19 @@ class EsploraProvider {
|
|
|
125
122
|
catch (error) {
|
|
126
123
|
console.error("Error in polling mechanism:", error);
|
|
127
124
|
}
|
|
128
|
-
}, pollingInterval);
|
|
125
|
+
}, this.pollingInterval);
|
|
129
126
|
};
|
|
130
127
|
let ws = null;
|
|
128
|
+
const stopFunc = () => {
|
|
129
|
+
if (ws)
|
|
130
|
+
ws.close();
|
|
131
|
+
if (intervalId)
|
|
132
|
+
clearInterval(intervalId);
|
|
133
|
+
};
|
|
134
|
+
if (this.forcePolling) {
|
|
135
|
+
await poll();
|
|
136
|
+
return stopFunc;
|
|
137
|
+
}
|
|
131
138
|
try {
|
|
132
139
|
ws = new WebSocket(wsUrl);
|
|
133
140
|
ws.addEventListener("open", () => {
|
|
@@ -174,13 +181,6 @@ class EsploraProvider {
|
|
|
174
181
|
// if websocket is not available, fallback to polling
|
|
175
182
|
await poll();
|
|
176
183
|
}
|
|
177
|
-
const stopFunc = () => {
|
|
178
|
-
if (ws && ws.readyState === WebSocket.OPEN)
|
|
179
|
-
ws.close();
|
|
180
|
-
if (intervalId)
|
|
181
|
-
clearInterval(intervalId);
|
|
182
|
-
this.polling = false;
|
|
183
|
-
};
|
|
184
184
|
return stopFunc;
|
|
185
185
|
}
|
|
186
186
|
async getChainTip() {
|
|
@@ -249,8 +249,7 @@ const isExplorerTransaction = (tx) => {
|
|
|
249
249
|
return (typeof tx.txid === "string" &&
|
|
250
250
|
Array.isArray(tx.vout) &&
|
|
251
251
|
tx.vout.every((vout) => typeof vout.scriptpubkey_address === "string" &&
|
|
252
|
-
typeof vout.value === "
|
|
252
|
+
typeof vout.value === "number") &&
|
|
253
253
|
typeof tx.status === "object" &&
|
|
254
|
-
typeof tx.status.confirmed === "boolean"
|
|
255
|
-
typeof tx.status.block_time === "number");
|
|
254
|
+
typeof tx.status.confirmed === "boolean");
|
|
256
255
|
};
|
|
@@ -15,7 +15,14 @@ const serializeVtxo = (v) => ({
|
|
|
15
15
|
tapTree: toHex(v.tapTree),
|
|
16
16
|
forfeitTapLeafScript: serializeTapLeaf(v.forfeitTapLeafScript),
|
|
17
17
|
intentTapLeafScript: serializeTapLeaf(v.intentTapLeafScript),
|
|
18
|
-
extraWitness: v.extraWitness?.map(
|
|
18
|
+
extraWitness: v.extraWitness?.map(toHex),
|
|
19
|
+
});
|
|
20
|
+
const serializeUtxo = (u) => ({
|
|
21
|
+
...u,
|
|
22
|
+
tapTree: toHex(u.tapTree),
|
|
23
|
+
forfeitTapLeafScript: serializeTapLeaf(u.forfeitTapLeafScript),
|
|
24
|
+
intentTapLeafScript: serializeTapLeaf(u.intentTapLeafScript),
|
|
25
|
+
extraWitness: u.extraWitness?.map(toHex),
|
|
19
26
|
});
|
|
20
27
|
const deserializeTapLeaf = (t) => {
|
|
21
28
|
const cb = btc_signer_1.TaprootControlBlock.decode(fromHex(t.cb));
|
|
@@ -27,13 +34,21 @@ const deserializeVtxo = (o) => ({
|
|
|
27
34
|
tapTree: fromHex(o.tapTree),
|
|
28
35
|
forfeitTapLeafScript: deserializeTapLeaf(o.forfeitTapLeafScript),
|
|
29
36
|
intentTapLeafScript: deserializeTapLeaf(o.intentTapLeafScript),
|
|
30
|
-
extraWitness: o.extraWitness?.map(
|
|
37
|
+
extraWitness: o.extraWitness?.map(fromHex),
|
|
38
|
+
});
|
|
39
|
+
const deserializeUtxo = (o) => ({
|
|
40
|
+
...o,
|
|
41
|
+
tapTree: fromHex(o.tapTree),
|
|
42
|
+
forfeitTapLeafScript: deserializeTapLeaf(o.forfeitTapLeafScript),
|
|
43
|
+
intentTapLeafScript: deserializeTapLeaf(o.intentTapLeafScript),
|
|
44
|
+
extraWitness: o.extraWitness?.map(fromHex),
|
|
31
45
|
});
|
|
32
46
|
class WalletRepositoryImpl {
|
|
33
47
|
constructor(storage) {
|
|
34
48
|
this.storage = storage;
|
|
35
49
|
this.cache = {
|
|
36
50
|
vtxos: new Map(),
|
|
51
|
+
utxos: new Map(),
|
|
37
52
|
transactions: new Map(),
|
|
38
53
|
walletState: null,
|
|
39
54
|
initialized: new Set(),
|
|
@@ -86,6 +101,53 @@ class WalletRepositoryImpl {
|
|
|
86
101
|
this.cache.vtxos.set(address, []);
|
|
87
102
|
await this.storage.removeItem(`vtxos:${address}`);
|
|
88
103
|
}
|
|
104
|
+
async getUtxos(address) {
|
|
105
|
+
const cacheKey = `utxos:${address}`;
|
|
106
|
+
if (this.cache.utxos.has(address)) {
|
|
107
|
+
return this.cache.utxos.get(address);
|
|
108
|
+
}
|
|
109
|
+
const stored = await this.storage.getItem(cacheKey);
|
|
110
|
+
if (!stored) {
|
|
111
|
+
this.cache.utxos.set(address, []);
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
const parsed = JSON.parse(stored);
|
|
116
|
+
const utxos = parsed.map(deserializeUtxo);
|
|
117
|
+
this.cache.utxos.set(address, utxos.slice());
|
|
118
|
+
return utxos.slice();
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
console.error(`Failed to parse UTXOs for address ${address}:`, error);
|
|
122
|
+
this.cache.utxos.set(address, []);
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async saveUtxos(address, utxos) {
|
|
127
|
+
const storedUtxos = await this.getUtxos(address);
|
|
128
|
+
utxos.forEach((utxo) => {
|
|
129
|
+
const existing = storedUtxos.findIndex((u) => u.txid === utxo.txid && u.vout === utxo.vout);
|
|
130
|
+
if (existing !== -1) {
|
|
131
|
+
storedUtxos[existing] = utxo;
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
storedUtxos.push(utxo);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
this.cache.utxos.set(address, storedUtxos.slice());
|
|
138
|
+
await this.storage.setItem(`utxos:${address}`, JSON.stringify(storedUtxos.map(serializeUtxo)));
|
|
139
|
+
}
|
|
140
|
+
async removeUtxo(address, utxoId) {
|
|
141
|
+
const utxos = await this.getUtxos(address);
|
|
142
|
+
const [txid, vout] = utxoId.split(":");
|
|
143
|
+
const filtered = utxos.filter((v) => !(v.txid === txid && v.vout === parseInt(vout, 10)));
|
|
144
|
+
this.cache.utxos.set(address, filtered.slice());
|
|
145
|
+
await this.storage.setItem(`utxos:${address}`, JSON.stringify(filtered.map(serializeUtxo)));
|
|
146
|
+
}
|
|
147
|
+
async clearUtxos(address) {
|
|
148
|
+
this.cache.utxos.set(address, []);
|
|
149
|
+
await this.storage.removeItem(`utxos:${address}`);
|
|
150
|
+
}
|
|
89
151
|
async getTransactionHistory(address) {
|
|
90
152
|
const cacheKey = `tx:${address}`;
|
|
91
153
|
if (this.cache.transactions.has(address)) {
|
package/dist/cjs/script/base.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.VtxoScript = void 0;
|
|
3
|
+
exports.VtxoScript = exports.TapTreeCoder = void 0;
|
|
4
4
|
exports.scriptFromTapLeafScript = scriptFromTapLeafScript;
|
|
5
5
|
const btc_signer_1 = require("@scure/btc-signer");
|
|
6
6
|
const payment_js_1 = require("@scure/btc-signer/payment.js");
|
|
@@ -8,7 +8,7 @@ const psbt_js_1 = require("@scure/btc-signer/psbt.js");
|
|
|
8
8
|
const base_1 = require("@scure/base");
|
|
9
9
|
const address_1 = require("./address");
|
|
10
10
|
const tapscript_1 = require("./tapscript");
|
|
11
|
-
|
|
11
|
+
exports.TapTreeCoder = psbt_js_1.PSBTOutput.tapTree[2];
|
|
12
12
|
function scriptFromTapLeafScript(leaf) {
|
|
13
13
|
return leaf[1].subarray(0, leaf[1].length - 1); // remove the version byte
|
|
14
14
|
}
|
|
@@ -22,13 +22,22 @@ function scriptFromTapLeafScript(leaf) {
|
|
|
22
22
|
*/
|
|
23
23
|
class VtxoScript {
|
|
24
24
|
static decode(tapTree) {
|
|
25
|
-
const leaves = TapTreeCoder.decode(tapTree);
|
|
25
|
+
const leaves = exports.TapTreeCoder.decode(tapTree);
|
|
26
26
|
const scripts = leaves.map((leaf) => leaf.script);
|
|
27
27
|
return new VtxoScript(scripts);
|
|
28
28
|
}
|
|
29
29
|
constructor(scripts) {
|
|
30
30
|
this.scripts = scripts;
|
|
31
|
-
|
|
31
|
+
// reverse the scripts if the number of scripts is odd
|
|
32
|
+
// this is to be compatible with arkd algorithm computing taproot tree from list of tapscripts
|
|
33
|
+
// the scripts must be reversed only HERE while we compute the tweaked public key
|
|
34
|
+
// but the original order should be preserved while encoding as taptree
|
|
35
|
+
// note: .slice().reverse() is used instead of .reverse() to avoid mutating the original array
|
|
36
|
+
const list = scripts.length % 2 !== 0 ? scripts.slice().reverse() : scripts;
|
|
37
|
+
const tapTree = (0, btc_signer_1.taprootListToTree)(list.map((script) => ({
|
|
38
|
+
script,
|
|
39
|
+
leafVersion: payment_js_1.TAP_LEAF_VERSION,
|
|
40
|
+
})));
|
|
32
41
|
const payment = (0, btc_signer_1.p2tr)(btc_signer_1.TAPROOT_UNSPENDABLE_KEY, tapTree, undefined, true);
|
|
33
42
|
if (!payment.tapLeafScript ||
|
|
34
43
|
payment.tapLeafScript.length !== scripts.length) {
|
|
@@ -38,7 +47,7 @@ class VtxoScript {
|
|
|
38
47
|
this.tweakedPublicKey = payment.tweakedPubkey;
|
|
39
48
|
}
|
|
40
49
|
encode() {
|
|
41
|
-
const tapTree = TapTreeCoder.encode(this.scripts.map((script) => ({
|
|
50
|
+
const tapTree = exports.TapTreeCoder.encode(this.scripts.map((script) => ({
|
|
42
51
|
depth: 1,
|
|
43
52
|
version: payment_js_1.TAP_LEAF_VERSION,
|
|
44
53
|
script,
|
|
@@ -11,6 +11,7 @@ const tapscript_1 = require("../script/tapscript");
|
|
|
11
11
|
const base_2 = require("../script/base");
|
|
12
12
|
const anchor_1 = require("./anchor");
|
|
13
13
|
const unknownFields_1 = require("./unknownFields");
|
|
14
|
+
const transaction_1 = require("./transaction");
|
|
14
15
|
/**
|
|
15
16
|
* Builds an offchain transaction with checkpoint transactions.
|
|
16
17
|
*
|
|
@@ -48,10 +49,8 @@ function buildVirtualTx(inputs, outputs) {
|
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
|
-
const tx = new
|
|
52
|
+
const tx = new transaction_1.Transaction({
|
|
52
53
|
version: 3,
|
|
53
|
-
allowUnknown: true,
|
|
54
|
-
allowUnknownOutputs: true,
|
|
55
54
|
lockTime: Number(lockTime),
|
|
56
55
|
});
|
|
57
56
|
for (const [i, input] of inputs.entries()) {
|
|
@@ -76,8 +75,7 @@ function buildVirtualTx(inputs, outputs) {
|
|
|
76
75
|
}
|
|
77
76
|
function buildCheckpointTx(vtxo, serverUnrollScript) {
|
|
78
77
|
// create the checkpoint vtxo script from collaborative closure
|
|
79
|
-
const collaborativeClosure = (0, tapscript_1.decodeTapscript)(vtxo.
|
|
80
|
-
(0, base_2.scriptFromTapLeafScript)(vtxo.tapLeafScript));
|
|
78
|
+
const collaborativeClosure = (0, tapscript_1.decodeTapscript)((0, base_2.scriptFromTapLeafScript)(vtxo.tapLeafScript));
|
|
81
79
|
// create the checkpoint vtxo script combining collaborative closure and server unroll script
|
|
82
80
|
const checkpointVtxoScript = new base_2.VtxoScript([
|
|
83
81
|
serverUnrollScript.script,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Transaction = void 0;
|
|
4
|
+
const btc_signer_1 = require("@scure/btc-signer");
|
|
5
|
+
/**
|
|
6
|
+
* Transaction is a wrapper around the @scure/btc-signer Transaction class.
|
|
7
|
+
* It adds the Ark protocol specific options to the transaction.
|
|
8
|
+
*/
|
|
9
|
+
class Transaction extends btc_signer_1.Transaction {
|
|
10
|
+
constructor(opts) {
|
|
11
|
+
super(withArkOpts(opts));
|
|
12
|
+
}
|
|
13
|
+
static fromPSBT(psbt_, opts) {
|
|
14
|
+
return btc_signer_1.Transaction.fromPSBT(psbt_, withArkOpts(opts));
|
|
15
|
+
}
|
|
16
|
+
static fromRaw(raw, opts) {
|
|
17
|
+
return btc_signer_1.Transaction.fromRaw(raw, withArkOpts(opts));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.Transaction = Transaction;
|
|
21
|
+
Transaction.ARK_TX_OPTS = {
|
|
22
|
+
allowUnknown: true,
|
|
23
|
+
allowUnknownOutputs: true,
|
|
24
|
+
allowUnknownInputs: true,
|
|
25
|
+
};
|
|
26
|
+
function withArkOpts(opts) {
|
|
27
|
+
return { ...Transaction.ARK_TX_OPTS, ...opts };
|
|
28
|
+
}
|
|
@@ -7,6 +7,7 @@ const networks_1 = require("../networks");
|
|
|
7
7
|
const onchain_1 = require("../providers/onchain");
|
|
8
8
|
const anchor_1 = require("../utils/anchor");
|
|
9
9
|
const txSizeEstimator_1 = require("../utils/txSizeEstimator");
|
|
10
|
+
const transaction_1 = require("../utils/transaction");
|
|
10
11
|
/**
|
|
11
12
|
* Onchain Bitcoin wallet implementation for traditional Bitcoin transactions.
|
|
12
13
|
*
|
|
@@ -79,7 +80,7 @@ class OnchainWallet {
|
|
|
79
80
|
// Select coins
|
|
80
81
|
const selected = selectCoins(coins, totalNeeded);
|
|
81
82
|
// Create transaction
|
|
82
|
-
let tx = new
|
|
83
|
+
let tx = new transaction_1.Transaction();
|
|
83
84
|
// Add inputs
|
|
84
85
|
for (const input of selected.inputs) {
|
|
85
86
|
tx.addInput({
|
|
@@ -107,10 +108,9 @@ class OnchainWallet {
|
|
|
107
108
|
}
|
|
108
109
|
async bumpP2A(parent) {
|
|
109
110
|
const parentVsize = parent.vsize;
|
|
110
|
-
let child = new
|
|
111
|
-
allowUnknownInputs: true,
|
|
112
|
-
allowLegacyWitnessUtxo: true,
|
|
111
|
+
let child = new transaction_1.Transaction({
|
|
113
112
|
version: 3,
|
|
113
|
+
allowLegacyWitnessUtxo: true,
|
|
114
114
|
});
|
|
115
115
|
child.addInput((0, anchor_1.findP2AOutput)(parent)); // throws if not found
|
|
116
116
|
const childVsize = txSizeEstimator_1.TxWeightEstimator.create()
|
|
@@ -60,6 +60,15 @@ class Worker {
|
|
|
60
60
|
spent: allVtxos.filter((vtxo) => !(0, __1.isSpendable)(vtxo)),
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Get all boarding utxos from wallet repository
|
|
65
|
+
*/
|
|
66
|
+
async getAllBoardingUtxos() {
|
|
67
|
+
if (!this.wallet)
|
|
68
|
+
return [];
|
|
69
|
+
const address = await this.wallet.getBoardingAddress();
|
|
70
|
+
return await this.walletRepository.getUtxos(address);
|
|
71
|
+
}
|
|
63
72
|
async start(withServiceWorkerUpdate = true) {
|
|
64
73
|
self.addEventListener("message", async (event) => {
|
|
65
74
|
await this.handleMessage(event);
|
|
@@ -133,6 +142,14 @@ class Worker {
|
|
|
133
142
|
this.sendMessageToAllClients(response_1.Response.vtxoUpdate(newVtxos, spentVtxos));
|
|
134
143
|
}
|
|
135
144
|
if (funds.type === "utxo") {
|
|
145
|
+
const newUtxos = funds.coins.map((utxo) => (0, utils_1.extendCoin)(this.wallet, utxo));
|
|
146
|
+
if (newUtxos.length === 0) {
|
|
147
|
+
this.sendMessageToAllClients(response_1.Response.utxoUpdate([]));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const boardingAddress = await this.wallet?.getBoardingAddress();
|
|
151
|
+
// save utxos using unified repository
|
|
152
|
+
await this.walletRepository.saveUtxos(boardingAddress, newUtxos);
|
|
136
153
|
// notify all clients about the utxo update
|
|
137
154
|
this.sendMessageToAllClients(response_1.Response.utxoUpdate(funds.coins));
|
|
138
155
|
}
|
|
@@ -291,7 +308,7 @@ class Worker {
|
|
|
291
308
|
}
|
|
292
309
|
try {
|
|
293
310
|
const [boardingUtxos, spendableVtxos, sweptVtxos] = await Promise.all([
|
|
294
|
-
this.
|
|
311
|
+
this.getAllBoardingUtxos(),
|
|
295
312
|
this.getSpendableVtxos(),
|
|
296
313
|
this.getSweptVtxos(),
|
|
297
314
|
]);
|
|
@@ -396,7 +413,7 @@ class Worker {
|
|
|
396
413
|
return;
|
|
397
414
|
}
|
|
398
415
|
try {
|
|
399
|
-
const boardingUtxos = await this.
|
|
416
|
+
const boardingUtxos = await this.getAllBoardingUtxos();
|
|
400
417
|
event.source?.postMessage(response_1.Response.boardingUtxos(message.id, boardingUtxos));
|
|
401
418
|
}
|
|
402
419
|
catch (error) {
|
|
@@ -7,6 +7,7 @@ const indexer_1 = require("../providers/indexer");
|
|
|
7
7
|
const base_2 = require("../script/base");
|
|
8
8
|
const txSizeEstimator_1 = require("../utils/txSizeEstimator");
|
|
9
9
|
const wallet_1 = require("./wallet");
|
|
10
|
+
const transaction_1 = require("../utils/transaction");
|
|
10
11
|
var Unroll;
|
|
11
12
|
(function (Unroll) {
|
|
12
13
|
let StepType;
|
|
@@ -105,9 +106,7 @@ var Unroll;
|
|
|
105
106
|
if (virtualTxs.txs.length === 0) {
|
|
106
107
|
throw new Error(`Tx ${nextTxToBroadcast.txid} not found`);
|
|
107
108
|
}
|
|
108
|
-
const tx =
|
|
109
|
-
allowUnknownInputs: true,
|
|
110
|
-
});
|
|
109
|
+
const tx = transaction_1.Transaction.fromPSBT(base_1.base64.decode(virtualTxs.txs[0]));
|
|
111
110
|
// finalize the tree transaction
|
|
112
111
|
if (nextTxToBroadcast.type === indexer_1.ChainTxType.TREE) {
|
|
113
112
|
const input = tx.getInput(0);
|
|
@@ -200,7 +199,7 @@ var Unroll;
|
|
|
200
199
|
});
|
|
201
200
|
txWeightEstimator.addTapscriptInput(64, spendingLeaf[1].length, btc_signer_1.TaprootControlBlock.encode(spendingLeaf[0]).length);
|
|
202
201
|
}
|
|
203
|
-
const tx = new
|
|
202
|
+
const tx = new transaction_1.Transaction({ version: 2 });
|
|
204
203
|
for (const input of inputs) {
|
|
205
204
|
tx.addInput(input);
|
|
206
205
|
}
|
package/dist/cjs/wallet/utils.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.extendVirtualCoin = extendVirtualCoin;
|
|
4
|
+
exports.extendCoin = extendCoin;
|
|
4
5
|
function extendVirtualCoin(wallet, vtxo) {
|
|
5
6
|
return {
|
|
6
7
|
...vtxo,
|
|
@@ -9,3 +10,11 @@ function extendVirtualCoin(wallet, vtxo) {
|
|
|
9
10
|
tapTree: wallet.offchainTapscript.encode(),
|
|
10
11
|
};
|
|
11
12
|
}
|
|
13
|
+
function extendCoin(wallet, utxo) {
|
|
14
|
+
return {
|
|
15
|
+
...utxo,
|
|
16
|
+
forfeitTapLeafScript: wallet.boardingTapscript.forfeit(),
|
|
17
|
+
intentTapLeafScript: wallet.boardingTapscript.exit(),
|
|
18
|
+
tapTree: wallet.boardingTapscript.encode(),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -370,15 +370,12 @@ class Wallet {
|
|
|
370
370
|
async getBoardingUtxos() {
|
|
371
371
|
const boardingAddress = await this.getBoardingAddress();
|
|
372
372
|
const boardingUtxos = await this.onchainProvider.getCoins(boardingAddress);
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
intentTapLeafScript: exit,
|
|
380
|
-
tapTree: encodedBoardingTapscript,
|
|
381
|
-
}));
|
|
373
|
+
const utxos = boardingUtxos.map((utxo) => {
|
|
374
|
+
return (0, utils_1.extendCoin)(this, utxo);
|
|
375
|
+
});
|
|
376
|
+
// Save boardingUtxos using unified repository
|
|
377
|
+
await this.walletRepository.saveUtxos(boardingAddress, utxos);
|
|
378
|
+
return utxos;
|
|
382
379
|
}
|
|
383
380
|
async sendBitcoin(params) {
|
|
384
381
|
if (params.amount <= 0) {
|
|
@@ -798,8 +795,6 @@ class Wallet {
|
|
|
798
795
|
const vtxo = vtxos.find((vtxo) => vtxo.txid === input.txid && vtxo.vout === input.vout);
|
|
799
796
|
// boarding utxo, we need to sign the settlement tx
|
|
800
797
|
if (!vtxo) {
|
|
801
|
-
hasBoardingUtxos = true;
|
|
802
|
-
const inputIndexes = [];
|
|
803
798
|
for (let i = 0; i < settlementPsbt.inputsLength; i++) {
|
|
804
799
|
const settlementInput = settlementPsbt.getInput(i);
|
|
805
800
|
if (!settlementInput.txid ||
|
|
@@ -815,9 +810,12 @@ class Wallet {
|
|
|
815
810
|
settlementPsbt.updateInput(i, {
|
|
816
811
|
tapLeafScript: [input.forfeitTapLeafScript],
|
|
817
812
|
});
|
|
818
|
-
|
|
813
|
+
settlementPsbt = await this.identity.sign(settlementPsbt, [
|
|
814
|
+
i,
|
|
815
|
+
]);
|
|
816
|
+
hasBoardingUtxos = true;
|
|
817
|
+
break;
|
|
819
818
|
}
|
|
820
|
-
settlementPsbt = await this.identity.sign(settlementPsbt, inputIndexes);
|
|
821
819
|
continue;
|
|
822
820
|
}
|
|
823
821
|
if ((0, _1.isRecoverable)(vtxo) || (0, _1.isSubdust)(vtxo, this.dustAmount)) {
|
package/dist/esm/forfeit.js
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import { Transaction } from
|
|
1
|
+
import { Transaction } from './utils/transaction.js';
|
|
2
2
|
import { P2A } from './utils/anchor.js';
|
|
3
3
|
export function buildForfeitTx(inputs, forfeitPkScript, txLocktime) {
|
|
4
4
|
const tx = new Transaction({
|
|
5
5
|
version: 3,
|
|
6
6
|
lockTime: txLocktime,
|
|
7
|
-
allowUnknownOutputs: true,
|
|
8
|
-
allowUnknown: true,
|
|
9
|
-
allowUnknownInputs: true,
|
|
10
7
|
});
|
|
11
8
|
let amount = 0n;
|
|
12
9
|
for (const input of inputs) {
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { pubECDSA, pubSchnorr, randomPrivateKeyBytes, } from "@scure/btc-signer/utils.js";
|
|
2
|
-
import { SigHash } from "@scure/btc-signer
|
|
2
|
+
import { SigHash } from "@scure/btc-signer";
|
|
3
3
|
import { hex } from "@scure/base";
|
|
4
4
|
import { TreeSignerSession } from '../tree/signingSession.js';
|
|
5
5
|
import { schnorr, sign } from "@noble/secp256k1";
|
|
6
|
-
const ZERO_32 = new Uint8Array(32).fill(0);
|
|
7
6
|
const ALL_SIGHASH = Object.values(SigHash).filter((x) => typeof x === "number");
|
|
8
7
|
/**
|
|
9
8
|
* In-memory single key implementation for Bitcoin transaction signing.
|
|
@@ -48,7 +47,7 @@ export class SingleKey {
|
|
|
48
47
|
const txCpy = tx.clone();
|
|
49
48
|
if (!inputIndexes) {
|
|
50
49
|
try {
|
|
51
|
-
if (!txCpy.sign(this.key, ALL_SIGHASH
|
|
50
|
+
if (!txCpy.sign(this.key, ALL_SIGHASH)) {
|
|
52
51
|
throw new Error("Failed to sign transaction");
|
|
53
52
|
}
|
|
54
53
|
}
|
|
@@ -64,7 +63,7 @@ export class SingleKey {
|
|
|
64
63
|
return txCpy;
|
|
65
64
|
}
|
|
66
65
|
for (const inputIndex of inputIndexes) {
|
|
67
|
-
if (!txCpy.signIdx(this.key, inputIndex, ALL_SIGHASH
|
|
66
|
+
if (!txCpy.signIdx(this.key, inputIndex, ALL_SIGHASH)) {
|
|
68
67
|
throw new Error(`Failed to sign input #${inputIndex}`);
|
|
69
68
|
}
|
|
70
69
|
}
|