@arkade-os/sdk 0.3.2 → 0.3.4
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/dist/cjs/identity/singleKey.js +2 -2
- package/dist/cjs/index.js +3 -2
- package/dist/cjs/providers/ark.js +1 -1
- package/dist/cjs/repositories/contractRepository.js +9 -38
- package/dist/cjs/repositories/walletRepository.js +25 -74
- package/dist/cjs/utils/arkTransaction.js +29 -0
- package/dist/cjs/wallet/serviceWorker/worker.js +4 -1
- package/dist/cjs/wallet/wallet.js +23 -8
- package/dist/esm/identity/singleKey.js +3 -3
- package/dist/esm/index.js +2 -2
- package/dist/esm/providers/ark.js +1 -1
- package/dist/esm/repositories/contractRepository.js +9 -38
- package/dist/esm/repositories/walletRepository.js +25 -74
- package/dist/esm/utils/arkTransaction.js +29 -1
- package/dist/esm/wallet/serviceWorker/worker.js +4 -1
- package/dist/esm/wallet/wallet.js +23 -8
- package/dist/types/index.d.ts +2 -2
- package/dist/types/repositories/contractRepository.d.ts +0 -1
- package/dist/types/repositories/walletRepository.d.ts +0 -1
- package/dist/types/utils/arkTransaction.d.ts +6 -0
- package/dist/types/wallet/wallet.d.ts +4 -3
- package/package.json +1 -1
|
@@ -83,8 +83,8 @@ class SingleKey {
|
|
|
83
83
|
}
|
|
84
84
|
async signMessage(message, signatureType = "schnorr") {
|
|
85
85
|
if (signatureType === "ecdsa")
|
|
86
|
-
return (0, secp256k1_1.
|
|
87
|
-
return secp256k1_1.schnorr.
|
|
86
|
+
return (0, secp256k1_1.signAsync)(message, this.key, { prehash: false });
|
|
87
|
+
return secp256k1_1.schnorr.signAsync(message, this.key);
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
exports.SingleKey = SingleKey;
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.maybeArkError = exports.ArkError = void 0;
|
|
3
|
+
exports.Unroll = exports.P2A = exports.TxTree = exports.Intent = exports.ContractRepositoryImpl = exports.WalletRepositoryImpl = exports.networks = exports.ArkNote = exports.combineTapscriptSigs = 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 = exports.Transaction = void 0;
|
|
5
5
|
const transaction_1 = require("./utils/transaction");
|
|
6
6
|
Object.defineProperty(exports, "Transaction", { enumerable: true, get: function () { return transaction_1.Transaction; } });
|
|
7
7
|
const singleKey_1 = require("./identity/singleKey");
|
|
@@ -55,6 +55,7 @@ const arkTransaction_1 = require("./utils/arkTransaction");
|
|
|
55
55
|
Object.defineProperty(exports, "hasBoardingTxExpired", { enumerable: true, get: function () { return arkTransaction_1.hasBoardingTxExpired; } });
|
|
56
56
|
Object.defineProperty(exports, "buildOffchainTx", { enumerable: true, get: function () { return arkTransaction_1.buildOffchainTx; } });
|
|
57
57
|
Object.defineProperty(exports, "verifyTapscriptSignatures", { enumerable: true, get: function () { return arkTransaction_1.verifyTapscriptSignatures; } });
|
|
58
|
+
Object.defineProperty(exports, "combineTapscriptSigs", { enumerable: true, get: function () { return arkTransaction_1.combineTapscriptSigs; } });
|
|
58
59
|
const unknownFields_1 = require("./utils/unknownFields");
|
|
59
60
|
Object.defineProperty(exports, "VtxoTaprootTree", { enumerable: true, get: function () { return unknownFields_1.VtxoTaprootTree; } });
|
|
60
61
|
Object.defineProperty(exports, "ConditionWitness", { enumerable: true, get: function () { return unknownFields_1.ConditionWitness; } });
|
|
@@ -1,22 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ContractRepositoryImpl = void 0;
|
|
4
|
+
const getContractStorageKey = (id, key) => `contract:${id}:${key}`;
|
|
5
|
+
const getCollectionStorageKey = (type) => `collection:${type}`;
|
|
4
6
|
class ContractRepositoryImpl {
|
|
5
7
|
constructor(storage) {
|
|
6
|
-
this.cache = new Map();
|
|
7
8
|
this.storage = storage;
|
|
8
9
|
}
|
|
9
10
|
async getContractData(contractId, key) {
|
|
10
|
-
const
|
|
11
|
-
const cached = this.cache.get(storageKey);
|
|
12
|
-
if (cached !== undefined)
|
|
13
|
-
return cached;
|
|
14
|
-
const stored = await this.storage.getItem(storageKey);
|
|
11
|
+
const stored = await this.storage.getItem(getContractStorageKey(contractId, key));
|
|
15
12
|
if (!stored)
|
|
16
13
|
return null;
|
|
17
14
|
try {
|
|
18
15
|
const data = JSON.parse(stored);
|
|
19
|
-
this.cache.set(storageKey, data);
|
|
20
16
|
return data;
|
|
21
17
|
}
|
|
22
18
|
catch (error) {
|
|
@@ -25,49 +21,33 @@ class ContractRepositoryImpl {
|
|
|
25
21
|
}
|
|
26
22
|
}
|
|
27
23
|
async setContractData(contractId, key, data) {
|
|
28
|
-
const storageKey = `contract:${contractId}:${key}`;
|
|
29
24
|
try {
|
|
30
|
-
|
|
31
|
-
await this.storage.setItem(storageKey, JSON.stringify(data));
|
|
32
|
-
this.cache.set(storageKey, data);
|
|
25
|
+
await this.storage.setItem(getContractStorageKey(contractId, key), JSON.stringify(data));
|
|
33
26
|
}
|
|
34
27
|
catch (error) {
|
|
35
|
-
// Storage operation failed, cache remains unchanged
|
|
36
28
|
console.error(`Failed to persist contract data for ${contractId}:${key}:`, error);
|
|
37
29
|
throw error; // Rethrow to notify caller of failure
|
|
38
30
|
}
|
|
39
31
|
}
|
|
40
32
|
async deleteContractData(contractId, key) {
|
|
41
|
-
const storageKey = `contract:${contractId}:${key}`;
|
|
42
33
|
try {
|
|
43
|
-
|
|
44
|
-
await this.storage.removeItem(storageKey);
|
|
45
|
-
this.cache.delete(storageKey);
|
|
34
|
+
await this.storage.removeItem(getContractStorageKey(contractId, key));
|
|
46
35
|
}
|
|
47
36
|
catch (error) {
|
|
48
|
-
// Storage operation failed, cache remains unchanged
|
|
49
37
|
console.error(`Failed to remove contract data for ${contractId}:${key}:`, error);
|
|
50
38
|
throw error; // Rethrow to notify caller of failure
|
|
51
39
|
}
|
|
52
40
|
}
|
|
53
41
|
async getContractCollection(contractType) {
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
if (cached !== undefined)
|
|
57
|
-
return cached;
|
|
58
|
-
const stored = await this.storage.getItem(storageKey);
|
|
59
|
-
if (!stored) {
|
|
60
|
-
this.cache.set(storageKey, []);
|
|
42
|
+
const stored = await this.storage.getItem(getCollectionStorageKey(contractType));
|
|
43
|
+
if (!stored)
|
|
61
44
|
return [];
|
|
62
|
-
}
|
|
63
45
|
try {
|
|
64
46
|
const collection = JSON.parse(stored);
|
|
65
|
-
this.cache.set(storageKey, collection);
|
|
66
47
|
return collection;
|
|
67
48
|
}
|
|
68
49
|
catch (error) {
|
|
69
50
|
console.error(`Failed to parse contract collection ${contractType}:`, error);
|
|
70
|
-
this.cache.set(storageKey, []);
|
|
71
51
|
return [];
|
|
72
52
|
}
|
|
73
53
|
}
|
|
@@ -94,14 +74,10 @@ class ContractRepositoryImpl {
|
|
|
94
74
|
// Add new item
|
|
95
75
|
newCollection = [...collection, item];
|
|
96
76
|
}
|
|
97
|
-
const storageKey = `collection:${contractType}`;
|
|
98
77
|
try {
|
|
99
|
-
|
|
100
|
-
await this.storage.setItem(storageKey, JSON.stringify(newCollection));
|
|
101
|
-
this.cache.set(storageKey, newCollection);
|
|
78
|
+
await this.storage.setItem(getCollectionStorageKey(contractType), JSON.stringify(newCollection));
|
|
102
79
|
}
|
|
103
80
|
catch (error) {
|
|
104
|
-
// Storage operation failed, cache remains unchanged
|
|
105
81
|
console.error(`Failed to persist contract collection ${contractType}:`, error);
|
|
106
82
|
throw error; // Rethrow to notify caller of failure
|
|
107
83
|
}
|
|
@@ -114,21 +90,16 @@ class ContractRepositoryImpl {
|
|
|
114
90
|
const collection = await this.getContractCollection(contractType);
|
|
115
91
|
// Build new collection without the specified item
|
|
116
92
|
const filtered = collection.filter((item) => item[idField] !== id);
|
|
117
|
-
const storageKey = `collection:${contractType}`;
|
|
118
93
|
try {
|
|
119
|
-
|
|
120
|
-
await this.storage.setItem(storageKey, JSON.stringify(filtered));
|
|
121
|
-
this.cache.set(storageKey, filtered);
|
|
94
|
+
await this.storage.setItem(getCollectionStorageKey(contractType), JSON.stringify(filtered));
|
|
122
95
|
}
|
|
123
96
|
catch (error) {
|
|
124
|
-
// Storage operation failed, cache remains unchanged
|
|
125
97
|
console.error(`Failed to persist contract collection removal for ${contractType}:`, error);
|
|
126
98
|
throw error; // Rethrow to notify caller of failure
|
|
127
99
|
}
|
|
128
100
|
}
|
|
129
101
|
async clearContractData() {
|
|
130
102
|
await this.storage.clear();
|
|
131
|
-
this.cache.clear();
|
|
132
103
|
}
|
|
133
104
|
}
|
|
134
105
|
exports.ContractRepositoryImpl = ContractRepositoryImpl;
|
|
@@ -3,6 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.WalletRepositoryImpl = void 0;
|
|
4
4
|
const base_1 = require("@scure/base");
|
|
5
5
|
const btc_signer_1 = require("@scure/btc-signer");
|
|
6
|
+
const getVtxosStorageKey = (address) => `vtxos:${address}`;
|
|
7
|
+
const getUtxosStorageKey = (address) => `utxos:${address}`;
|
|
8
|
+
const getTransactionsStorageKey = (address) => `tx:${address}`;
|
|
9
|
+
const walletStateStorageKey = "wallet:state";
|
|
6
10
|
// Utility functions for (de)serializing complex structures
|
|
7
11
|
const toHex = (b) => (b ? base_1.hex.encode(b) : undefined);
|
|
8
12
|
const fromHex = (h) => h ? base_1.hex.decode(h) : undefined;
|
|
@@ -47,33 +51,17 @@ const deserializeUtxo = (o) => ({
|
|
|
47
51
|
class WalletRepositoryImpl {
|
|
48
52
|
constructor(storage) {
|
|
49
53
|
this.storage = storage;
|
|
50
|
-
this.cache = {
|
|
51
|
-
vtxos: new Map(),
|
|
52
|
-
utxos: new Map(),
|
|
53
|
-
transactions: new Map(),
|
|
54
|
-
walletState: null,
|
|
55
|
-
initialized: new Set(),
|
|
56
|
-
};
|
|
57
54
|
}
|
|
58
55
|
async getVtxos(address) {
|
|
59
|
-
const
|
|
60
|
-
if (
|
|
61
|
-
return this.cache.vtxos.get(address);
|
|
62
|
-
}
|
|
63
|
-
const stored = await this.storage.getItem(cacheKey);
|
|
64
|
-
if (!stored) {
|
|
65
|
-
this.cache.vtxos.set(address, []);
|
|
56
|
+
const stored = await this.storage.getItem(getVtxosStorageKey(address));
|
|
57
|
+
if (!stored)
|
|
66
58
|
return [];
|
|
67
|
-
}
|
|
68
59
|
try {
|
|
69
60
|
const parsed = JSON.parse(stored);
|
|
70
|
-
|
|
71
|
-
this.cache.vtxos.set(address, vtxos.slice());
|
|
72
|
-
return vtxos.slice();
|
|
61
|
+
return parsed.map(deserializeVtxo);
|
|
73
62
|
}
|
|
74
63
|
catch (error) {
|
|
75
64
|
console.error(`Failed to parse VTXOs for address ${address}:`, error);
|
|
76
|
-
this.cache.vtxos.set(address, []);
|
|
77
65
|
return [];
|
|
78
66
|
}
|
|
79
67
|
}
|
|
@@ -88,39 +76,27 @@ class WalletRepositoryImpl {
|
|
|
88
76
|
storedVtxos.push(vtxo);
|
|
89
77
|
}
|
|
90
78
|
}
|
|
91
|
-
this.
|
|
92
|
-
await this.storage.setItem(`vtxos:${address}`, JSON.stringify(storedVtxos.map(serializeVtxo)));
|
|
79
|
+
await this.storage.setItem(getVtxosStorageKey(address), JSON.stringify(storedVtxos.map(serializeVtxo)));
|
|
93
80
|
}
|
|
94
81
|
async removeVtxo(address, vtxoId) {
|
|
95
82
|
const vtxos = await this.getVtxos(address);
|
|
96
83
|
const [txid, vout] = vtxoId.split(":");
|
|
97
84
|
const filtered = vtxos.filter((v) => !(v.txid === txid && v.vout === parseInt(vout, 10)));
|
|
98
|
-
this.
|
|
99
|
-
await this.storage.setItem(`vtxos:${address}`, JSON.stringify(filtered.map(serializeVtxo)));
|
|
85
|
+
await this.storage.setItem(getVtxosStorageKey(address), JSON.stringify(filtered.map(serializeVtxo)));
|
|
100
86
|
}
|
|
101
87
|
async clearVtxos(address) {
|
|
102
|
-
this.
|
|
103
|
-
await this.storage.removeItem(`vtxos:${address}`);
|
|
88
|
+
await this.storage.removeItem(getVtxosStorageKey(address));
|
|
104
89
|
}
|
|
105
90
|
async getUtxos(address) {
|
|
106
|
-
const
|
|
107
|
-
if (
|
|
108
|
-
return this.cache.utxos.get(address);
|
|
109
|
-
}
|
|
110
|
-
const stored = await this.storage.getItem(cacheKey);
|
|
111
|
-
if (!stored) {
|
|
112
|
-
this.cache.utxos.set(address, []);
|
|
91
|
+
const stored = await this.storage.getItem(getUtxosStorageKey(address));
|
|
92
|
+
if (!stored)
|
|
113
93
|
return [];
|
|
114
|
-
}
|
|
115
94
|
try {
|
|
116
95
|
const parsed = JSON.parse(stored);
|
|
117
|
-
|
|
118
|
-
this.cache.utxos.set(address, utxos.slice());
|
|
119
|
-
return utxos.slice();
|
|
96
|
+
return parsed.map(deserializeUtxo);
|
|
120
97
|
}
|
|
121
98
|
catch (error) {
|
|
122
99
|
console.error(`Failed to parse UTXOs for address ${address}:`, error);
|
|
123
|
-
this.cache.utxos.set(address, []);
|
|
124
100
|
return [];
|
|
125
101
|
}
|
|
126
102
|
}
|
|
@@ -135,38 +111,27 @@ class WalletRepositoryImpl {
|
|
|
135
111
|
storedUtxos.push(utxo);
|
|
136
112
|
}
|
|
137
113
|
});
|
|
138
|
-
this.
|
|
139
|
-
await this.storage.setItem(`utxos:${address}`, JSON.stringify(storedUtxos.map(serializeUtxo)));
|
|
114
|
+
await this.storage.setItem(getUtxosStorageKey(address), JSON.stringify(storedUtxos.map(serializeUtxo)));
|
|
140
115
|
}
|
|
141
116
|
async removeUtxo(address, utxoId) {
|
|
142
117
|
const utxos = await this.getUtxos(address);
|
|
143
118
|
const [txid, vout] = utxoId.split(":");
|
|
144
119
|
const filtered = utxos.filter((v) => !(v.txid === txid && v.vout === parseInt(vout, 10)));
|
|
145
|
-
this.
|
|
146
|
-
await this.storage.setItem(`utxos:${address}`, JSON.stringify(filtered.map(serializeUtxo)));
|
|
120
|
+
await this.storage.setItem(getUtxosStorageKey(address), JSON.stringify(filtered.map(serializeUtxo)));
|
|
147
121
|
}
|
|
148
122
|
async clearUtxos(address) {
|
|
149
|
-
this.
|
|
150
|
-
await this.storage.removeItem(`utxos:${address}`);
|
|
123
|
+
await this.storage.removeItem(getUtxosStorageKey(address));
|
|
151
124
|
}
|
|
152
125
|
async getTransactionHistory(address) {
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
const stored = await this.storage.getItem(cacheKey);
|
|
158
|
-
if (!stored) {
|
|
159
|
-
this.cache.transactions.set(address, []);
|
|
126
|
+
const storageKey = getTransactionsStorageKey(address);
|
|
127
|
+
const stored = await this.storage.getItem(storageKey);
|
|
128
|
+
if (!stored)
|
|
160
129
|
return [];
|
|
161
|
-
}
|
|
162
130
|
try {
|
|
163
|
-
|
|
164
|
-
this.cache.transactions.set(address, transactions);
|
|
165
|
-
return transactions.slice();
|
|
131
|
+
return JSON.parse(stored);
|
|
166
132
|
}
|
|
167
133
|
catch (error) {
|
|
168
134
|
console.error(`Failed to parse transactions for address ${address}:`, error);
|
|
169
|
-
this.cache.transactions.set(address, []);
|
|
170
135
|
return [];
|
|
171
136
|
}
|
|
172
137
|
}
|
|
@@ -181,40 +146,26 @@ class WalletRepositoryImpl {
|
|
|
181
146
|
storedTransactions.push(tx);
|
|
182
147
|
}
|
|
183
148
|
}
|
|
184
|
-
this.
|
|
185
|
-
await this.storage.setItem(`tx:${address}`, JSON.stringify(storedTransactions));
|
|
149
|
+
await this.storage.setItem(getTransactionsStorageKey(address), JSON.stringify(storedTransactions));
|
|
186
150
|
}
|
|
187
151
|
async clearTransactions(address) {
|
|
188
|
-
this.
|
|
189
|
-
await this.storage.removeItem(`tx:${address}`);
|
|
152
|
+
await this.storage.removeItem(getTransactionsStorageKey(address));
|
|
190
153
|
}
|
|
191
154
|
async getWalletState() {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
return this.cache.walletState;
|
|
195
|
-
}
|
|
196
|
-
const stored = await this.storage.getItem("wallet:state");
|
|
197
|
-
if (!stored) {
|
|
198
|
-
this.cache.walletState = null;
|
|
199
|
-
this.cache.initialized.add("walletState");
|
|
155
|
+
const stored = await this.storage.getItem(walletStateStorageKey);
|
|
156
|
+
if (!stored)
|
|
200
157
|
return null;
|
|
201
|
-
}
|
|
202
158
|
try {
|
|
203
159
|
const state = JSON.parse(stored);
|
|
204
|
-
this.cache.walletState = state;
|
|
205
|
-
this.cache.initialized.add("walletState");
|
|
206
160
|
return state;
|
|
207
161
|
}
|
|
208
162
|
catch (error) {
|
|
209
163
|
console.error("Failed to parse wallet state:", error);
|
|
210
|
-
this.cache.walletState = null;
|
|
211
|
-
this.cache.initialized.add("walletState");
|
|
212
164
|
return null;
|
|
213
165
|
}
|
|
214
166
|
}
|
|
215
167
|
async saveWalletState(state) {
|
|
216
|
-
this.
|
|
217
|
-
await this.storage.setItem("wallet:state", JSON.stringify(state));
|
|
168
|
+
await this.storage.setItem(walletStateStorageKey, JSON.stringify(state));
|
|
218
169
|
}
|
|
219
170
|
}
|
|
220
171
|
exports.WalletRepositoryImpl = WalletRepositoryImpl;
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.buildOffchainTx = buildOffchainTx;
|
|
4
4
|
exports.hasBoardingTxExpired = hasBoardingTxExpired;
|
|
5
5
|
exports.verifyTapscriptSignatures = verifyTapscriptSignatures;
|
|
6
|
+
exports.combineTapscriptSigs = combineTapscriptSigs;
|
|
6
7
|
const secp256k1_js_1 = require("@noble/curves/secp256k1.js");
|
|
7
8
|
const base_1 = require("@scure/base");
|
|
8
9
|
const btc_signer_1 = require("@scure/btc-signer");
|
|
@@ -25,6 +26,17 @@ const transaction_1 = require("./transaction");
|
|
|
25
26
|
* @returns Object containing the virtual transaction and checkpoint transactions
|
|
26
27
|
*/
|
|
27
28
|
function buildOffchainTx(inputs, outputs, serverUnrollScript) {
|
|
29
|
+
let hasOpReturn = false;
|
|
30
|
+
for (const [index, output] of outputs.entries()) {
|
|
31
|
+
if (!output.script)
|
|
32
|
+
throw new Error(`missing output script ${index}`);
|
|
33
|
+
const isOpReturn = btc_signer_1.Script.decode(output.script)[0] === "RETURN";
|
|
34
|
+
if (!isOpReturn)
|
|
35
|
+
continue;
|
|
36
|
+
if (hasOpReturn)
|
|
37
|
+
throw new Error("multiple OP_RETURN outputs");
|
|
38
|
+
hasOpReturn = true;
|
|
39
|
+
}
|
|
28
40
|
const checkpoints = inputs.map((input) => buildCheckpointTx(input, serverUnrollScript));
|
|
29
41
|
const arkTx = buildVirtualTx(checkpoints.map((c) => c.input), outputs);
|
|
30
42
|
return {
|
|
@@ -209,3 +221,20 @@ function verifyTapscriptSignatures(tx, inputIndex, requiredSigners, excludePubke
|
|
|
209
221
|
throw new Error(`Missing signatures from: ${missingSigners.map((pk) => pk.slice(0, 16)).join(", ")}...`);
|
|
210
222
|
}
|
|
211
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* Merges the signed transaction with the original transaction
|
|
226
|
+
* @param signedTx signed transaction
|
|
227
|
+
* @param originalTx original transaction
|
|
228
|
+
*/
|
|
229
|
+
function combineTapscriptSigs(signedTx, originalTx) {
|
|
230
|
+
for (let i = 0; i < signedTx.inputsLength; i++) {
|
|
231
|
+
const input = originalTx.getInput(i);
|
|
232
|
+
const signedInput = signedTx.getInput(i);
|
|
233
|
+
if (!input.tapScriptSig)
|
|
234
|
+
throw new Error("No tapScriptSig");
|
|
235
|
+
originalTx.updateInput(i, {
|
|
236
|
+
tapScriptSig: input.tapScriptSig?.concat(signedInput.tapScriptSig),
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
return originalTx;
|
|
240
|
+
}
|
|
@@ -77,7 +77,6 @@ class Worker {
|
|
|
77
77
|
const { boardingTxs, commitmentsToIgnore: roundsToIgnore } = await this.wallet.getBoardingTxs();
|
|
78
78
|
const { spendable, spent } = await this.getAllVtxos();
|
|
79
79
|
// convert VTXOs to offchain transactions
|
|
80
|
-
console.log("getTransactionHistory - vtxosToTxs:", spendable);
|
|
81
80
|
const offchainTxs = (0, transactionHistory_1.vtxosToTxs)(spendable, spent, roundsToIgnore);
|
|
82
81
|
txs = [...boardingTxs, ...offchainTxs];
|
|
83
82
|
// sort transactions by creation time in descending order (newest first)
|
|
@@ -142,6 +141,10 @@ class Worker {
|
|
|
142
141
|
// Get wallet address and save vtxos using unified repository
|
|
143
142
|
const address = await this.wallet.getAddress();
|
|
144
143
|
await this.walletRepository.saveVtxos(address, vtxos);
|
|
144
|
+
// Fetch boarding utxos and save using unified repository
|
|
145
|
+
const boardingAddress = await this.wallet.getBoardingAddress();
|
|
146
|
+
const coins = await this.wallet.onchainProvider.getCoins(boardingAddress);
|
|
147
|
+
await this.walletRepository.saveUtxos(boardingAddress, coins.map((utxo) => (0, utils_1.extendCoin)(this.wallet, utxo)));
|
|
145
148
|
// Get transaction history to cache boarding txs
|
|
146
149
|
const txs = await this.getTransactionHistory();
|
|
147
150
|
if (txs)
|
|
@@ -146,12 +146,29 @@ class Wallet {
|
|
|
146
146
|
const esploraUrl = config.esploraUrl || onchain_1.ESPLORA_URL[info.network];
|
|
147
147
|
// Use provided onchainProvider instance or create a new one
|
|
148
148
|
const onchainProvider = config.onchainProvider || new onchain_1.EsploraProvider(esploraUrl);
|
|
149
|
-
//
|
|
150
|
-
|
|
149
|
+
// validate unilateral exit timelock passed in config if any
|
|
150
|
+
if (config.exitTimelock) {
|
|
151
|
+
const { value, type } = config.exitTimelock;
|
|
152
|
+
if ((value < 512n && type !== "blocks") ||
|
|
153
|
+
(value >= 512n && type !== "seconds")) {
|
|
154
|
+
throw new Error("invalid exitTimelock");
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// create unilateral exit timelock
|
|
158
|
+
const exitTimelock = config.exitTimelock ?? {
|
|
151
159
|
value: info.unilateralExitDelay,
|
|
152
160
|
type: info.unilateralExitDelay < 512n ? "blocks" : "seconds",
|
|
153
161
|
};
|
|
154
|
-
|
|
162
|
+
// validate boarding timelock passed in config if any
|
|
163
|
+
if (config.boardingTimelock) {
|
|
164
|
+
const { value, type } = config.boardingTimelock;
|
|
165
|
+
if ((value < 512n && type !== "blocks") ||
|
|
166
|
+
(value >= 512n && type !== "seconds")) {
|
|
167
|
+
throw new Error("invalid boardingTimelock");
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// create boarding timelock
|
|
171
|
+
const boardingTimelock = config.boardingTimelock ?? {
|
|
155
172
|
value: info.boardingExitDelay,
|
|
156
173
|
type: info.boardingExitDelay < 512n ? "blocks" : "seconds",
|
|
157
174
|
};
|
|
@@ -869,13 +886,12 @@ class Wallet {
|
|
|
869
886
|
}
|
|
870
887
|
}
|
|
871
888
|
async makeRegisterIntentSignature(coins, outputs, onchainOutputsIndexes, cosignerPubKeys) {
|
|
872
|
-
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
873
889
|
const inputs = this.prepareIntentProofInputs(coins);
|
|
874
890
|
const message = {
|
|
875
891
|
type: "register",
|
|
876
892
|
onchain_output_indexes: onchainOutputsIndexes,
|
|
877
|
-
valid_at:
|
|
878
|
-
expire_at:
|
|
893
|
+
valid_at: 0,
|
|
894
|
+
expire_at: 0,
|
|
879
895
|
cosigners_public_keys: cosignerPubKeys,
|
|
880
896
|
};
|
|
881
897
|
const encodedMessage = JSON.stringify(message, null, 0);
|
|
@@ -887,11 +903,10 @@ class Wallet {
|
|
|
887
903
|
};
|
|
888
904
|
}
|
|
889
905
|
async makeDeleteIntentSignature(coins) {
|
|
890
|
-
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
891
906
|
const inputs = this.prepareIntentProofInputs(coins);
|
|
892
907
|
const message = {
|
|
893
908
|
type: "delete",
|
|
894
|
-
expire_at:
|
|
909
|
+
expire_at: 0,
|
|
895
910
|
};
|
|
896
911
|
const encodedMessage = JSON.stringify(message, null, 0);
|
|
897
912
|
const proof = intent_1.Intent.create(encodedMessage, inputs, []);
|
|
@@ -2,7 +2,7 @@ import { pubECDSA, pubSchnorr, randomPrivateKeyBytes, } from "@scure/btc-signer/
|
|
|
2
2
|
import { SigHash } from "@scure/btc-signer";
|
|
3
3
|
import { hex } from "@scure/base";
|
|
4
4
|
import { TreeSignerSession } from '../tree/signingSession.js';
|
|
5
|
-
import { schnorr,
|
|
5
|
+
import { schnorr, signAsync } from "@noble/secp256k1";
|
|
6
6
|
const ALL_SIGHASH = Object.values(SigHash).filter((x) => typeof x === "number");
|
|
7
7
|
/**
|
|
8
8
|
* In-memory single key implementation for Bitcoin transaction signing.
|
|
@@ -80,7 +80,7 @@ export class SingleKey {
|
|
|
80
80
|
}
|
|
81
81
|
async signMessage(message, signatureType = "schnorr") {
|
|
82
82
|
if (signatureType === "ecdsa")
|
|
83
|
-
return
|
|
84
|
-
return schnorr.
|
|
83
|
+
return signAsync(message, this.key, { prehash: false });
|
|
84
|
+
return schnorr.signAsync(message, this.key);
|
|
85
85
|
}
|
|
86
86
|
}
|
package/dist/esm/index.js
CHANGED
|
@@ -18,7 +18,7 @@ import { Response } from './wallet/serviceWorker/response.js';
|
|
|
18
18
|
import { ESPLORA_URL, EsploraProvider, } from './providers/onchain.js';
|
|
19
19
|
import { RestArkProvider, SettlementEventType, } from './providers/ark.js';
|
|
20
20
|
import { CLTVMultisigTapscript, ConditionCSVMultisigTapscript, ConditionMultisigTapscript, CSVMultisigTapscript, decodeTapscript, MultisigTapscript, } from './script/tapscript.js';
|
|
21
|
-
import { hasBoardingTxExpired, buildOffchainTx, verifyTapscriptSignatures, } from './utils/arkTransaction.js';
|
|
21
|
+
import { hasBoardingTxExpired, buildOffchainTx, verifyTapscriptSignatures, combineTapscriptSigs, } from './utils/arkTransaction.js';
|
|
22
22
|
import { VtxoTaprootTree, ConditionWitness, getArkPsbtFields, setArkPsbtField, ArkPsbtFieldKey, ArkPsbtFieldKeyType, CosignerPublicKey, VtxoTreeExpiry, } from './utils/unknownFields.js';
|
|
23
23
|
import { Intent } from './intent/index.js';
|
|
24
24
|
import { ArkNote } from './arknote/index.js';
|
|
@@ -45,7 +45,7 @@ decodeTapscript, MultisigTapscript, CSVMultisigTapscript, ConditionCSVMultisigTa
|
|
|
45
45
|
// Ark PSBT fields
|
|
46
46
|
ArkPsbtFieldKey, ArkPsbtFieldKeyType, setArkPsbtField, getArkPsbtFields, CosignerPublicKey, VtxoTreeExpiry, VtxoTaprootTree, ConditionWitness,
|
|
47
47
|
// Utils
|
|
48
|
-
buildOffchainTx, verifyTapscriptSignatures, waitForIncomingFunds, hasBoardingTxExpired,
|
|
48
|
+
buildOffchainTx, verifyTapscriptSignatures, waitForIncomingFunds, hasBoardingTxExpired, combineTapscriptSigs,
|
|
49
49
|
// Arknote
|
|
50
50
|
ArkNote,
|
|
51
51
|
// Network
|
|
@@ -1,19 +1,15 @@
|
|
|
1
|
+
const getContractStorageKey = (id, key) => `contract:${id}:${key}`;
|
|
2
|
+
const getCollectionStorageKey = (type) => `collection:${type}`;
|
|
1
3
|
export class ContractRepositoryImpl {
|
|
2
4
|
constructor(storage) {
|
|
3
|
-
this.cache = new Map();
|
|
4
5
|
this.storage = storage;
|
|
5
6
|
}
|
|
6
7
|
async getContractData(contractId, key) {
|
|
7
|
-
const
|
|
8
|
-
const cached = this.cache.get(storageKey);
|
|
9
|
-
if (cached !== undefined)
|
|
10
|
-
return cached;
|
|
11
|
-
const stored = await this.storage.getItem(storageKey);
|
|
8
|
+
const stored = await this.storage.getItem(getContractStorageKey(contractId, key));
|
|
12
9
|
if (!stored)
|
|
13
10
|
return null;
|
|
14
11
|
try {
|
|
15
12
|
const data = JSON.parse(stored);
|
|
16
|
-
this.cache.set(storageKey, data);
|
|
17
13
|
return data;
|
|
18
14
|
}
|
|
19
15
|
catch (error) {
|
|
@@ -22,49 +18,33 @@ export class ContractRepositoryImpl {
|
|
|
22
18
|
}
|
|
23
19
|
}
|
|
24
20
|
async setContractData(contractId, key, data) {
|
|
25
|
-
const storageKey = `contract:${contractId}:${key}`;
|
|
26
21
|
try {
|
|
27
|
-
|
|
28
|
-
await this.storage.setItem(storageKey, JSON.stringify(data));
|
|
29
|
-
this.cache.set(storageKey, data);
|
|
22
|
+
await this.storage.setItem(getContractStorageKey(contractId, key), JSON.stringify(data));
|
|
30
23
|
}
|
|
31
24
|
catch (error) {
|
|
32
|
-
// Storage operation failed, cache remains unchanged
|
|
33
25
|
console.error(`Failed to persist contract data for ${contractId}:${key}:`, error);
|
|
34
26
|
throw error; // Rethrow to notify caller of failure
|
|
35
27
|
}
|
|
36
28
|
}
|
|
37
29
|
async deleteContractData(contractId, key) {
|
|
38
|
-
const storageKey = `contract:${contractId}:${key}`;
|
|
39
30
|
try {
|
|
40
|
-
|
|
41
|
-
await this.storage.removeItem(storageKey);
|
|
42
|
-
this.cache.delete(storageKey);
|
|
31
|
+
await this.storage.removeItem(getContractStorageKey(contractId, key));
|
|
43
32
|
}
|
|
44
33
|
catch (error) {
|
|
45
|
-
// Storage operation failed, cache remains unchanged
|
|
46
34
|
console.error(`Failed to remove contract data for ${contractId}:${key}:`, error);
|
|
47
35
|
throw error; // Rethrow to notify caller of failure
|
|
48
36
|
}
|
|
49
37
|
}
|
|
50
38
|
async getContractCollection(contractType) {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
if (cached !== undefined)
|
|
54
|
-
return cached;
|
|
55
|
-
const stored = await this.storage.getItem(storageKey);
|
|
56
|
-
if (!stored) {
|
|
57
|
-
this.cache.set(storageKey, []);
|
|
39
|
+
const stored = await this.storage.getItem(getCollectionStorageKey(contractType));
|
|
40
|
+
if (!stored)
|
|
58
41
|
return [];
|
|
59
|
-
}
|
|
60
42
|
try {
|
|
61
43
|
const collection = JSON.parse(stored);
|
|
62
|
-
this.cache.set(storageKey, collection);
|
|
63
44
|
return collection;
|
|
64
45
|
}
|
|
65
46
|
catch (error) {
|
|
66
47
|
console.error(`Failed to parse contract collection ${contractType}:`, error);
|
|
67
|
-
this.cache.set(storageKey, []);
|
|
68
48
|
return [];
|
|
69
49
|
}
|
|
70
50
|
}
|
|
@@ -91,14 +71,10 @@ export class ContractRepositoryImpl {
|
|
|
91
71
|
// Add new item
|
|
92
72
|
newCollection = [...collection, item];
|
|
93
73
|
}
|
|
94
|
-
const storageKey = `collection:${contractType}`;
|
|
95
74
|
try {
|
|
96
|
-
|
|
97
|
-
await this.storage.setItem(storageKey, JSON.stringify(newCollection));
|
|
98
|
-
this.cache.set(storageKey, newCollection);
|
|
75
|
+
await this.storage.setItem(getCollectionStorageKey(contractType), JSON.stringify(newCollection));
|
|
99
76
|
}
|
|
100
77
|
catch (error) {
|
|
101
|
-
// Storage operation failed, cache remains unchanged
|
|
102
78
|
console.error(`Failed to persist contract collection ${contractType}:`, error);
|
|
103
79
|
throw error; // Rethrow to notify caller of failure
|
|
104
80
|
}
|
|
@@ -111,20 +87,15 @@ export class ContractRepositoryImpl {
|
|
|
111
87
|
const collection = await this.getContractCollection(contractType);
|
|
112
88
|
// Build new collection without the specified item
|
|
113
89
|
const filtered = collection.filter((item) => item[idField] !== id);
|
|
114
|
-
const storageKey = `collection:${contractType}`;
|
|
115
90
|
try {
|
|
116
|
-
|
|
117
|
-
await this.storage.setItem(storageKey, JSON.stringify(filtered));
|
|
118
|
-
this.cache.set(storageKey, filtered);
|
|
91
|
+
await this.storage.setItem(getCollectionStorageKey(contractType), JSON.stringify(filtered));
|
|
119
92
|
}
|
|
120
93
|
catch (error) {
|
|
121
|
-
// Storage operation failed, cache remains unchanged
|
|
122
94
|
console.error(`Failed to persist contract collection removal for ${contractType}:`, error);
|
|
123
95
|
throw error; // Rethrow to notify caller of failure
|
|
124
96
|
}
|
|
125
97
|
}
|
|
126
98
|
async clearContractData() {
|
|
127
99
|
await this.storage.clear();
|
|
128
|
-
this.cache.clear();
|
|
129
100
|
}
|
|
130
101
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { hex } from "@scure/base";
|
|
2
2
|
import { TaprootControlBlock } from "@scure/btc-signer";
|
|
3
|
+
const getVtxosStorageKey = (address) => `vtxos:${address}`;
|
|
4
|
+
const getUtxosStorageKey = (address) => `utxos:${address}`;
|
|
5
|
+
const getTransactionsStorageKey = (address) => `tx:${address}`;
|
|
6
|
+
const walletStateStorageKey = "wallet:state";
|
|
3
7
|
// Utility functions for (de)serializing complex structures
|
|
4
8
|
const toHex = (b) => (b ? hex.encode(b) : undefined);
|
|
5
9
|
const fromHex = (h) => h ? hex.decode(h) : undefined;
|
|
@@ -44,33 +48,17 @@ const deserializeUtxo = (o) => ({
|
|
|
44
48
|
export class WalletRepositoryImpl {
|
|
45
49
|
constructor(storage) {
|
|
46
50
|
this.storage = storage;
|
|
47
|
-
this.cache = {
|
|
48
|
-
vtxos: new Map(),
|
|
49
|
-
utxos: new Map(),
|
|
50
|
-
transactions: new Map(),
|
|
51
|
-
walletState: null,
|
|
52
|
-
initialized: new Set(),
|
|
53
|
-
};
|
|
54
51
|
}
|
|
55
52
|
async getVtxos(address) {
|
|
56
|
-
const
|
|
57
|
-
if (
|
|
58
|
-
return this.cache.vtxos.get(address);
|
|
59
|
-
}
|
|
60
|
-
const stored = await this.storage.getItem(cacheKey);
|
|
61
|
-
if (!stored) {
|
|
62
|
-
this.cache.vtxos.set(address, []);
|
|
53
|
+
const stored = await this.storage.getItem(getVtxosStorageKey(address));
|
|
54
|
+
if (!stored)
|
|
63
55
|
return [];
|
|
64
|
-
}
|
|
65
56
|
try {
|
|
66
57
|
const parsed = JSON.parse(stored);
|
|
67
|
-
|
|
68
|
-
this.cache.vtxos.set(address, vtxos.slice());
|
|
69
|
-
return vtxos.slice();
|
|
58
|
+
return parsed.map(deserializeVtxo);
|
|
70
59
|
}
|
|
71
60
|
catch (error) {
|
|
72
61
|
console.error(`Failed to parse VTXOs for address ${address}:`, error);
|
|
73
|
-
this.cache.vtxos.set(address, []);
|
|
74
62
|
return [];
|
|
75
63
|
}
|
|
76
64
|
}
|
|
@@ -85,39 +73,27 @@ export class WalletRepositoryImpl {
|
|
|
85
73
|
storedVtxos.push(vtxo);
|
|
86
74
|
}
|
|
87
75
|
}
|
|
88
|
-
this.
|
|
89
|
-
await this.storage.setItem(`vtxos:${address}`, JSON.stringify(storedVtxos.map(serializeVtxo)));
|
|
76
|
+
await this.storage.setItem(getVtxosStorageKey(address), JSON.stringify(storedVtxos.map(serializeVtxo)));
|
|
90
77
|
}
|
|
91
78
|
async removeVtxo(address, vtxoId) {
|
|
92
79
|
const vtxos = await this.getVtxos(address);
|
|
93
80
|
const [txid, vout] = vtxoId.split(":");
|
|
94
81
|
const filtered = vtxos.filter((v) => !(v.txid === txid && v.vout === parseInt(vout, 10)));
|
|
95
|
-
this.
|
|
96
|
-
await this.storage.setItem(`vtxos:${address}`, JSON.stringify(filtered.map(serializeVtxo)));
|
|
82
|
+
await this.storage.setItem(getVtxosStorageKey(address), JSON.stringify(filtered.map(serializeVtxo)));
|
|
97
83
|
}
|
|
98
84
|
async clearVtxos(address) {
|
|
99
|
-
this.
|
|
100
|
-
await this.storage.removeItem(`vtxos:${address}`);
|
|
85
|
+
await this.storage.removeItem(getVtxosStorageKey(address));
|
|
101
86
|
}
|
|
102
87
|
async getUtxos(address) {
|
|
103
|
-
const
|
|
104
|
-
if (
|
|
105
|
-
return this.cache.utxos.get(address);
|
|
106
|
-
}
|
|
107
|
-
const stored = await this.storage.getItem(cacheKey);
|
|
108
|
-
if (!stored) {
|
|
109
|
-
this.cache.utxos.set(address, []);
|
|
88
|
+
const stored = await this.storage.getItem(getUtxosStorageKey(address));
|
|
89
|
+
if (!stored)
|
|
110
90
|
return [];
|
|
111
|
-
}
|
|
112
91
|
try {
|
|
113
92
|
const parsed = JSON.parse(stored);
|
|
114
|
-
|
|
115
|
-
this.cache.utxos.set(address, utxos.slice());
|
|
116
|
-
return utxos.slice();
|
|
93
|
+
return parsed.map(deserializeUtxo);
|
|
117
94
|
}
|
|
118
95
|
catch (error) {
|
|
119
96
|
console.error(`Failed to parse UTXOs for address ${address}:`, error);
|
|
120
|
-
this.cache.utxos.set(address, []);
|
|
121
97
|
return [];
|
|
122
98
|
}
|
|
123
99
|
}
|
|
@@ -132,38 +108,27 @@ export class WalletRepositoryImpl {
|
|
|
132
108
|
storedUtxos.push(utxo);
|
|
133
109
|
}
|
|
134
110
|
});
|
|
135
|
-
this.
|
|
136
|
-
await this.storage.setItem(`utxos:${address}`, JSON.stringify(storedUtxos.map(serializeUtxo)));
|
|
111
|
+
await this.storage.setItem(getUtxosStorageKey(address), JSON.stringify(storedUtxos.map(serializeUtxo)));
|
|
137
112
|
}
|
|
138
113
|
async removeUtxo(address, utxoId) {
|
|
139
114
|
const utxos = await this.getUtxos(address);
|
|
140
115
|
const [txid, vout] = utxoId.split(":");
|
|
141
116
|
const filtered = utxos.filter((v) => !(v.txid === txid && v.vout === parseInt(vout, 10)));
|
|
142
|
-
this.
|
|
143
|
-
await this.storage.setItem(`utxos:${address}`, JSON.stringify(filtered.map(serializeUtxo)));
|
|
117
|
+
await this.storage.setItem(getUtxosStorageKey(address), JSON.stringify(filtered.map(serializeUtxo)));
|
|
144
118
|
}
|
|
145
119
|
async clearUtxos(address) {
|
|
146
|
-
this.
|
|
147
|
-
await this.storage.removeItem(`utxos:${address}`);
|
|
120
|
+
await this.storage.removeItem(getUtxosStorageKey(address));
|
|
148
121
|
}
|
|
149
122
|
async getTransactionHistory(address) {
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
const stored = await this.storage.getItem(cacheKey);
|
|
155
|
-
if (!stored) {
|
|
156
|
-
this.cache.transactions.set(address, []);
|
|
123
|
+
const storageKey = getTransactionsStorageKey(address);
|
|
124
|
+
const stored = await this.storage.getItem(storageKey);
|
|
125
|
+
if (!stored)
|
|
157
126
|
return [];
|
|
158
|
-
}
|
|
159
127
|
try {
|
|
160
|
-
|
|
161
|
-
this.cache.transactions.set(address, transactions);
|
|
162
|
-
return transactions.slice();
|
|
128
|
+
return JSON.parse(stored);
|
|
163
129
|
}
|
|
164
130
|
catch (error) {
|
|
165
131
|
console.error(`Failed to parse transactions for address ${address}:`, error);
|
|
166
|
-
this.cache.transactions.set(address, []);
|
|
167
132
|
return [];
|
|
168
133
|
}
|
|
169
134
|
}
|
|
@@ -178,39 +143,25 @@ export class WalletRepositoryImpl {
|
|
|
178
143
|
storedTransactions.push(tx);
|
|
179
144
|
}
|
|
180
145
|
}
|
|
181
|
-
this.
|
|
182
|
-
await this.storage.setItem(`tx:${address}`, JSON.stringify(storedTransactions));
|
|
146
|
+
await this.storage.setItem(getTransactionsStorageKey(address), JSON.stringify(storedTransactions));
|
|
183
147
|
}
|
|
184
148
|
async clearTransactions(address) {
|
|
185
|
-
this.
|
|
186
|
-
await this.storage.removeItem(`tx:${address}`);
|
|
149
|
+
await this.storage.removeItem(getTransactionsStorageKey(address));
|
|
187
150
|
}
|
|
188
151
|
async getWalletState() {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
return this.cache.walletState;
|
|
192
|
-
}
|
|
193
|
-
const stored = await this.storage.getItem("wallet:state");
|
|
194
|
-
if (!stored) {
|
|
195
|
-
this.cache.walletState = null;
|
|
196
|
-
this.cache.initialized.add("walletState");
|
|
152
|
+
const stored = await this.storage.getItem(walletStateStorageKey);
|
|
153
|
+
if (!stored)
|
|
197
154
|
return null;
|
|
198
|
-
}
|
|
199
155
|
try {
|
|
200
156
|
const state = JSON.parse(stored);
|
|
201
|
-
this.cache.walletState = state;
|
|
202
|
-
this.cache.initialized.add("walletState");
|
|
203
157
|
return state;
|
|
204
158
|
}
|
|
205
159
|
catch (error) {
|
|
206
160
|
console.error("Failed to parse wallet state:", error);
|
|
207
|
-
this.cache.walletState = null;
|
|
208
|
-
this.cache.initialized.add("walletState");
|
|
209
161
|
return null;
|
|
210
162
|
}
|
|
211
163
|
}
|
|
212
164
|
async saveWalletState(state) {
|
|
213
|
-
this.
|
|
214
|
-
await this.storage.setItem("wallet:state", JSON.stringify(state));
|
|
165
|
+
await this.storage.setItem(walletStateStorageKey, JSON.stringify(state));
|
|
215
166
|
}
|
|
216
167
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { schnorr } from "@noble/curves/secp256k1.js";
|
|
2
2
|
import { hex } from "@scure/base";
|
|
3
|
-
import { DEFAULT_SEQUENCE, SigHash } from "@scure/btc-signer";
|
|
3
|
+
import { DEFAULT_SEQUENCE, Script, 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';
|
|
@@ -20,6 +20,17 @@ import { Transaction } from './transaction.js';
|
|
|
20
20
|
* @returns Object containing the virtual transaction and checkpoint transactions
|
|
21
21
|
*/
|
|
22
22
|
export function buildOffchainTx(inputs, outputs, serverUnrollScript) {
|
|
23
|
+
let hasOpReturn = false;
|
|
24
|
+
for (const [index, output] of outputs.entries()) {
|
|
25
|
+
if (!output.script)
|
|
26
|
+
throw new Error(`missing output script ${index}`);
|
|
27
|
+
const isOpReturn = Script.decode(output.script)[0] === "RETURN";
|
|
28
|
+
if (!isOpReturn)
|
|
29
|
+
continue;
|
|
30
|
+
if (hasOpReturn)
|
|
31
|
+
throw new Error("multiple OP_RETURN outputs");
|
|
32
|
+
hasOpReturn = true;
|
|
33
|
+
}
|
|
23
34
|
const checkpoints = inputs.map((input) => buildCheckpointTx(input, serverUnrollScript));
|
|
24
35
|
const arkTx = buildVirtualTx(checkpoints.map((c) => c.input), outputs);
|
|
25
36
|
return {
|
|
@@ -204,3 +215,20 @@ export function verifyTapscriptSignatures(tx, inputIndex, requiredSigners, exclu
|
|
|
204
215
|
throw new Error(`Missing signatures from: ${missingSigners.map((pk) => pk.slice(0, 16)).join(", ")}...`);
|
|
205
216
|
}
|
|
206
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* Merges the signed transaction with the original transaction
|
|
220
|
+
* @param signedTx signed transaction
|
|
221
|
+
* @param originalTx original transaction
|
|
222
|
+
*/
|
|
223
|
+
export function combineTapscriptSigs(signedTx, originalTx) {
|
|
224
|
+
for (let i = 0; i < signedTx.inputsLength; i++) {
|
|
225
|
+
const input = originalTx.getInput(i);
|
|
226
|
+
const signedInput = signedTx.getInput(i);
|
|
227
|
+
if (!input.tapScriptSig)
|
|
228
|
+
throw new Error("No tapScriptSig");
|
|
229
|
+
originalTx.updateInput(i, {
|
|
230
|
+
tapScriptSig: input.tapScriptSig?.concat(signedInput.tapScriptSig),
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
return originalTx;
|
|
234
|
+
}
|
|
@@ -74,7 +74,6 @@ export class Worker {
|
|
|
74
74
|
const { boardingTxs, commitmentsToIgnore: roundsToIgnore } = await this.wallet.getBoardingTxs();
|
|
75
75
|
const { spendable, spent } = await this.getAllVtxos();
|
|
76
76
|
// convert VTXOs to offchain transactions
|
|
77
|
-
console.log("getTransactionHistory - vtxosToTxs:", spendable);
|
|
78
77
|
const offchainTxs = vtxosToTxs(spendable, spent, roundsToIgnore);
|
|
79
78
|
txs = [...boardingTxs, ...offchainTxs];
|
|
80
79
|
// sort transactions by creation time in descending order (newest first)
|
|
@@ -139,6 +138,10 @@ export class Worker {
|
|
|
139
138
|
// Get wallet address and save vtxos using unified repository
|
|
140
139
|
const address = await this.wallet.getAddress();
|
|
141
140
|
await this.walletRepository.saveVtxos(address, vtxos);
|
|
141
|
+
// Fetch boarding utxos and save using unified repository
|
|
142
|
+
const boardingAddress = await this.wallet.getBoardingAddress();
|
|
143
|
+
const coins = await this.wallet.onchainProvider.getCoins(boardingAddress);
|
|
144
|
+
await this.walletRepository.saveUtxos(boardingAddress, coins.map((utxo) => extendCoin(this.wallet, utxo)));
|
|
142
145
|
// Get transaction history to cache boarding txs
|
|
143
146
|
const txs = await this.getTransactionHistory();
|
|
144
147
|
if (txs)
|
|
@@ -109,12 +109,29 @@ export class Wallet {
|
|
|
109
109
|
const esploraUrl = config.esploraUrl || ESPLORA_URL[info.network];
|
|
110
110
|
// Use provided onchainProvider instance or create a new one
|
|
111
111
|
const onchainProvider = config.onchainProvider || new EsploraProvider(esploraUrl);
|
|
112
|
-
//
|
|
113
|
-
|
|
112
|
+
// validate unilateral exit timelock passed in config if any
|
|
113
|
+
if (config.exitTimelock) {
|
|
114
|
+
const { value, type } = config.exitTimelock;
|
|
115
|
+
if ((value < 512n && type !== "blocks") ||
|
|
116
|
+
(value >= 512n && type !== "seconds")) {
|
|
117
|
+
throw new Error("invalid exitTimelock");
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// create unilateral exit timelock
|
|
121
|
+
const exitTimelock = config.exitTimelock ?? {
|
|
114
122
|
value: info.unilateralExitDelay,
|
|
115
123
|
type: info.unilateralExitDelay < 512n ? "blocks" : "seconds",
|
|
116
124
|
};
|
|
117
|
-
|
|
125
|
+
// validate boarding timelock passed in config if any
|
|
126
|
+
if (config.boardingTimelock) {
|
|
127
|
+
const { value, type } = config.boardingTimelock;
|
|
128
|
+
if ((value < 512n && type !== "blocks") ||
|
|
129
|
+
(value >= 512n && type !== "seconds")) {
|
|
130
|
+
throw new Error("invalid boardingTimelock");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// create boarding timelock
|
|
134
|
+
const boardingTimelock = config.boardingTimelock ?? {
|
|
118
135
|
value: info.boardingExitDelay,
|
|
119
136
|
type: info.boardingExitDelay < 512n ? "blocks" : "seconds",
|
|
120
137
|
};
|
|
@@ -832,13 +849,12 @@ export class Wallet {
|
|
|
832
849
|
}
|
|
833
850
|
}
|
|
834
851
|
async makeRegisterIntentSignature(coins, outputs, onchainOutputsIndexes, cosignerPubKeys) {
|
|
835
|
-
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
836
852
|
const inputs = this.prepareIntentProofInputs(coins);
|
|
837
853
|
const message = {
|
|
838
854
|
type: "register",
|
|
839
855
|
onchain_output_indexes: onchainOutputsIndexes,
|
|
840
|
-
valid_at:
|
|
841
|
-
expire_at:
|
|
856
|
+
valid_at: 0,
|
|
857
|
+
expire_at: 0,
|
|
842
858
|
cosigners_public_keys: cosignerPubKeys,
|
|
843
859
|
};
|
|
844
860
|
const encodedMessage = JSON.stringify(message, null, 0);
|
|
@@ -850,11 +866,10 @@ export class Wallet {
|
|
|
850
866
|
};
|
|
851
867
|
}
|
|
852
868
|
async makeDeleteIntentSignature(coins) {
|
|
853
|
-
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
854
869
|
const inputs = this.prepareIntentProofInputs(coins);
|
|
855
870
|
const message = {
|
|
856
871
|
type: "delete",
|
|
857
|
-
expire_at:
|
|
872
|
+
expire_at: 0,
|
|
858
873
|
};
|
|
859
874
|
const encodedMessage = JSON.stringify(message, null, 0);
|
|
860
875
|
const proof = Intent.create(encodedMessage, inputs, []);
|
package/dist/types/index.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ import { Response } from "./wallet/serviceWorker/response";
|
|
|
20
20
|
import { ESPLORA_URL, EsploraProvider, OnchainProvider, ExplorerTransaction } from "./providers/onchain";
|
|
21
21
|
import { RestArkProvider, ArkProvider, SettlementEvent, SettlementEventType, ArkInfo, SignedIntent, Output, TxNotification, BatchFinalizationEvent, BatchFinalizedEvent, BatchFailedEvent, TreeSigningStartedEvent, TreeNoncesEvent, BatchStartedEvent, TreeTxEvent, TreeSignatureEvent, ScheduledSession } from "./providers/ark";
|
|
22
22
|
import { CLTVMultisigTapscript, ConditionCSVMultisigTapscript, ConditionMultisigTapscript, CSVMultisigTapscript, decodeTapscript, MultisigTapscript, TapscriptType, ArkTapscript, RelativeTimelock } from "./script/tapscript";
|
|
23
|
-
import { hasBoardingTxExpired, buildOffchainTx, verifyTapscriptSignatures, ArkTxInput, OffchainTx } from "./utils/arkTransaction";
|
|
23
|
+
import { hasBoardingTxExpired, buildOffchainTx, verifyTapscriptSignatures, ArkTxInput, OffchainTx, combineTapscriptSigs } from "./utils/arkTransaction";
|
|
24
24
|
import { VtxoTaprootTree, ConditionWitness, getArkPsbtFields, setArkPsbtField, ArkPsbtFieldCoder, ArkPsbtFieldKey, ArkPsbtFieldKeyType, CosignerPublicKey, VtxoTreeExpiry } from "./utils/unknownFields";
|
|
25
25
|
import { Intent } from "./intent";
|
|
26
26
|
import { ArkNote } from "./arknote";
|
|
@@ -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, TapTreeCoder, 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, combineTapscriptSigs, 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, };
|
|
@@ -10,7 +10,6 @@ export interface ContractRepository {
|
|
|
10
10
|
}
|
|
11
11
|
export declare class ContractRepositoryImpl implements ContractRepository {
|
|
12
12
|
private storage;
|
|
13
|
-
private cache;
|
|
14
13
|
constructor(storage: StorageAdapter);
|
|
15
14
|
getContractData<T>(contractId: string, key: string): Promise<T | null>;
|
|
16
15
|
setContractData<T>(contractId: string, key: string, data: T): Promise<void>;
|
|
@@ -21,7 +21,6 @@ export interface WalletRepository {
|
|
|
21
21
|
}
|
|
22
22
|
export declare class WalletRepositoryImpl implements WalletRepository {
|
|
23
23
|
private storage;
|
|
24
|
-
private cache;
|
|
25
24
|
constructor(storage: StorageAdapter);
|
|
26
25
|
getVtxos(address: string): Promise<ExtendedVirtualCoin[]>;
|
|
27
26
|
saveVtxos(address: string, vtxos: ExtendedVirtualCoin[]): Promise<void>;
|
|
@@ -35,3 +35,9 @@ export declare function hasBoardingTxExpired(coin: ExtendedCoin, boardingTimeloc
|
|
|
35
35
|
* @throws Error if verification fails
|
|
36
36
|
*/
|
|
37
37
|
export declare function verifyTapscriptSignatures(tx: Transaction, inputIndex: number, requiredSigners: string[], excludePubkeys?: string[], allowedSighashTypes?: number[]): void;
|
|
38
|
+
/**
|
|
39
|
+
* Merges the signed transaction with the original transaction
|
|
40
|
+
* @param signedTx signed transaction
|
|
41
|
+
* @param originalTx original transaction
|
|
42
|
+
*/
|
|
43
|
+
export declare function combineTapscriptSigs(signedTx: Transaction, originalTx: Transaction): Transaction;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { TransactionOutput } from "@scure/btc-signer/psbt.js";
|
|
1
2
|
import { Bytes } from "@scure/btc-signer/utils.js";
|
|
2
3
|
import { ArkAddress } from "../script/address";
|
|
3
4
|
import { DefaultVtxo } from "../script/default";
|
|
4
5
|
import { Network, NetworkName } from "../networks";
|
|
5
6
|
import { OnchainProvider } from "../providers/onchain";
|
|
6
|
-
import { SettlementEvent, ArkProvider } from "../providers/ark";
|
|
7
|
+
import { SettlementEvent, ArkProvider, SignedIntent } from "../providers/ark";
|
|
7
8
|
import { Identity } from "../identity";
|
|
8
9
|
import { ArkTransaction, Coin, ExtendedCoin, ExtendedVirtualCoin, GetVtxosFilter, IWallet, SendBitcoinParams, SettleParams, WalletBalance, WalletConfig } from ".";
|
|
9
10
|
import { CSVMultisigTapscript } from "../script/tapscript";
|
|
@@ -93,8 +94,8 @@ export declare class Wallet implements IWallet {
|
|
|
93
94
|
private handleSettlementSigningEvent;
|
|
94
95
|
private handleSettlementTreeNoncesEvent;
|
|
95
96
|
private handleSettlementFinalizationEvent;
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
makeRegisterIntentSignature(coins: ExtendedCoin[], outputs: TransactionOutput[], onchainOutputsIndexes: number[], cosignerPubKeys: string[]): Promise<SignedIntent>;
|
|
98
|
+
makeDeleteIntentSignature(coins: ExtendedCoin[]): Promise<SignedIntent>;
|
|
98
99
|
private prepareIntentProofInputs;
|
|
99
100
|
}
|
|
100
101
|
/**
|