@arkade-os/sdk 0.3.3 → 0.3.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 +7 -1
- package/dist/cjs/identity/singleKey.js +2 -2
- package/dist/cjs/index.js +3 -2
- package/dist/cjs/providers/ark.js +9 -2
- 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/utils/transactionHistory.js +29 -13
- package/dist/cjs/wallet/ramps.js +7 -1
- package/dist/cjs/wallet/serviceWorker/worker.js +0 -1
- package/dist/cjs/wallet/wallet.js +98 -12
- package/dist/esm/identity/singleKey.js +3 -3
- package/dist/esm/index.js +2 -2
- package/dist/esm/providers/ark.js +9 -2
- 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/utils/transactionHistory.js +29 -13
- package/dist/esm/wallet/ramps.js +7 -1
- package/dist/esm/wallet/serviceWorker/worker.js +0 -1
- package/dist/esm/wallet/wallet.js +98 -12
- package/dist/types/index.d.ts +4 -4
- package/dist/types/providers/ark.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/ramps.d.ts +3 -2
- package/dist/types/wallet/wallet.d.ts +4 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -171,7 +171,13 @@ Collaborative exit or "offboarding" allows you to withdraw your virtual funds to
|
|
|
171
171
|
```typescript
|
|
172
172
|
import { Ramps } from '@arkade-os/sdk'
|
|
173
173
|
|
|
174
|
-
|
|
174
|
+
// Get fee information from the server
|
|
175
|
+
const info = await wallet.arkProvider.getInfo();
|
|
176
|
+
|
|
177
|
+
const exitTxid = await new Ramps(wallet).offboard(
|
|
178
|
+
onchainAddress,
|
|
179
|
+
info.fees
|
|
180
|
+
);
|
|
175
181
|
```
|
|
176
182
|
|
|
177
183
|
### Unilateral Exit
|
|
@@ -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; } });
|
|
@@ -46,7 +46,14 @@ class RestArkProvider {
|
|
|
46
46
|
})) ?? [],
|
|
47
47
|
digest: fromServer.digest ?? "",
|
|
48
48
|
dust: BigInt(fromServer.dust ?? 0),
|
|
49
|
-
fees:
|
|
49
|
+
fees: {
|
|
50
|
+
intentFee: {
|
|
51
|
+
...fromServer.fees?.intentFee,
|
|
52
|
+
onchainInput: BigInt(fromServer.fees?.intentFee?.onchainInput ?? 0),
|
|
53
|
+
onchainOutput: BigInt(fromServer.fees?.intentFee?.onchainOutput ?? 0),
|
|
54
|
+
},
|
|
55
|
+
txFeeRate: fromServer?.fees?.txFeeRate ?? "",
|
|
56
|
+
},
|
|
50
57
|
forfeitAddress: fromServer.forfeitAddress ?? "",
|
|
51
58
|
forfeitPubkey: fromServer.forfeitPubkey ?? "",
|
|
52
59
|
network: fromServer.network ?? "",
|
|
@@ -139,7 +146,7 @@ class RestArkProvider {
|
|
|
139
146
|
"Content-Type": "application/json",
|
|
140
147
|
},
|
|
141
148
|
body: JSON.stringify({
|
|
142
|
-
|
|
149
|
+
intent: {
|
|
143
150
|
proof: intent.proof,
|
|
144
151
|
message: intent.message,
|
|
145
152
|
},
|
|
@@ -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
|
+
}
|
|
@@ -14,6 +14,10 @@ function vtxosToTxs(spendable, spent, boardingBatchTxids) {
|
|
|
14
14
|
// All vtxos are received unless:
|
|
15
15
|
// - they resulted from a settlement (either boarding or refresh)
|
|
16
16
|
// - they are the change of a spend tx
|
|
17
|
+
// - they were spent in a payment (have arkTxId set)
|
|
18
|
+
// - they resulted from a payment (their txid matches an arkTxId of a spent vtxo)
|
|
19
|
+
// First, collect all arkTxIds from spent vtxos to identify payment transactions
|
|
20
|
+
const paymentArkTxIds = new Set(spent.filter((v) => v.arkTxId).map((v) => v.arkTxId));
|
|
17
21
|
let vtxosLeftToCheck = [...spent];
|
|
18
22
|
for (const vtxo of [...spendable, ...spent]) {
|
|
19
23
|
if (vtxo.virtualStatus.state !== "preconfirmed" &&
|
|
@@ -21,6 +25,16 @@ function vtxosToTxs(spendable, spent, boardingBatchTxids) {
|
|
|
21
25
|
vtxo.virtualStatus.commitmentTxIds.some((txid) => boardingBatchTxids.has(txid))) {
|
|
22
26
|
continue;
|
|
23
27
|
}
|
|
28
|
+
// Skip vtxos that were spent in a payment transaction
|
|
29
|
+
// These will be handled in the sent transaction section below
|
|
30
|
+
if (vtxo.arkTxId) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
// Skip vtxos that resulted from a payment transaction
|
|
34
|
+
// (their txid matches an arkTxId from a spent vtxo)
|
|
35
|
+
if (paymentArkTxIds.has(vtxo.txid)) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
24
38
|
const settleVtxos = findVtxosSpentInSettlement(vtxosLeftToCheck, vtxo);
|
|
25
39
|
vtxosLeftToCheck = removeVtxosFromList(vtxosLeftToCheck, settleVtxos);
|
|
26
40
|
const settleAmount = reduceVtxosAmount(settleVtxos);
|
|
@@ -56,21 +70,17 @@ function vtxosToTxs(spendable, spent, boardingBatchTxids) {
|
|
|
56
70
|
// vtxos by settled by or ark txid
|
|
57
71
|
const vtxosByTxid = new Map();
|
|
58
72
|
for (const v of spent) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const currentVtxos = vtxosByTxid.get(v.settledBy);
|
|
64
|
-
vtxosByTxid.set(v.settledBy, [...currentVtxos, v]);
|
|
65
|
-
}
|
|
66
|
-
if (!v.arkTxId) {
|
|
73
|
+
// Prefer arkTxId over settledBy to avoid duplicates
|
|
74
|
+
// A vtxo should only be grouped once
|
|
75
|
+
const groupKey = v.arkTxId || v.settledBy;
|
|
76
|
+
if (!groupKey) {
|
|
67
77
|
continue;
|
|
68
78
|
}
|
|
69
|
-
if (!vtxosByTxid.has(
|
|
70
|
-
vtxosByTxid.set(
|
|
79
|
+
if (!vtxosByTxid.has(groupKey)) {
|
|
80
|
+
vtxosByTxid.set(groupKey, []);
|
|
71
81
|
}
|
|
72
|
-
const currentVtxos = vtxosByTxid.get(
|
|
73
|
-
vtxosByTxid.set(
|
|
82
|
+
const currentVtxos = vtxosByTxid.get(groupKey);
|
|
83
|
+
vtxosByTxid.set(groupKey, [...currentVtxos, v]);
|
|
74
84
|
}
|
|
75
85
|
for (const [sb, vtxos] of vtxosByTxid) {
|
|
76
86
|
const resultedVtxos = findVtxosResultedFromTxid([...spendable, ...spent], sb);
|
|
@@ -85,7 +95,13 @@ function vtxosToTxs(spendable, spent, boardingBatchTxids) {
|
|
|
85
95
|
boardingTxid: "",
|
|
86
96
|
arkTxid: "",
|
|
87
97
|
};
|
|
88
|
-
|
|
98
|
+
// Use the grouping key (sb) as arkTxid if it looks like an arkTxId
|
|
99
|
+
// (i.e., if the spent vtxos had arkTxId set, use that instead of result vtxo's txid)
|
|
100
|
+
const isArkTxId = vtxos.some((v) => v.arkTxId === sb);
|
|
101
|
+
if (isArkTxId) {
|
|
102
|
+
txKey.arkTxid = sb;
|
|
103
|
+
}
|
|
104
|
+
else if (vtxo.virtualStatus.state === "preconfirmed") {
|
|
89
105
|
txKey.arkTxid = vtxo.txid;
|
|
90
106
|
}
|
|
91
107
|
txs.push({
|
package/dist/cjs/wallet/ramps.js
CHANGED
|
@@ -56,10 +56,11 @@ class Ramps {
|
|
|
56
56
|
* Offboard vtxos, or "collaborative exit" vtxos to onchain address.
|
|
57
57
|
*
|
|
58
58
|
* @param destinationAddress - The destination address to offboard to.
|
|
59
|
+
* @param feeInfo - The fee info to deduct from the offboard amount.
|
|
59
60
|
* @param amount - The amount to offboard. If not provided, the total amount of vtxos will be offboarded.
|
|
60
61
|
* @param eventCallback - The callback to receive settlement events. optional.
|
|
61
62
|
*/
|
|
62
|
-
async offboard(destinationAddress, amount, eventCallback) {
|
|
63
|
+
async offboard(destinationAddress, feeInfo, amount, eventCallback) {
|
|
63
64
|
const vtxos = await this.wallet.getVtxos({
|
|
64
65
|
withRecoverable: true,
|
|
65
66
|
withUnrolled: false,
|
|
@@ -73,6 +74,11 @@ class Ramps {
|
|
|
73
74
|
change = totalAmount - amount;
|
|
74
75
|
}
|
|
75
76
|
amount = amount ?? totalAmount;
|
|
77
|
+
const fees = feeInfo.intentFee.onchainOutput;
|
|
78
|
+
if (fees > amount) {
|
|
79
|
+
throw new Error(`can't deduct fees from offboard amount (${fees} > ${amount})`);
|
|
80
|
+
}
|
|
81
|
+
amount -= fees;
|
|
76
82
|
const outputs = [
|
|
77
83
|
{
|
|
78
84
|
address: destinationAddress,
|
|
@@ -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)
|