@arkade-os/sdk 0.4.21 → 0.4.23
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 +95 -12
- package/dist/cjs/contracts/arkcontract.js +2 -1
- package/dist/cjs/contracts/contractWatcher.js +16 -18
- package/dist/cjs/identity/descriptor.js +75 -4
- package/dist/cjs/identity/hdCapableIdentity.js +2 -0
- package/dist/cjs/identity/seedIdentity.js +225 -103
- package/dist/cjs/identity/serialize.js +5 -0
- package/dist/cjs/identity/staticDescriptorProvider.js +1 -1
- package/dist/cjs/index.js +12 -3
- package/dist/cjs/providers/electrum.js +285 -79
- package/dist/cjs/providers/expoIndexer.js +1 -1
- package/dist/cjs/providers/indexer.js +2 -2
- package/dist/cjs/providers/onchain.js +9 -3
- package/dist/cjs/repositories/migrations/walletRepositoryImpl.js +6 -2
- package/dist/cjs/repositories/realm/walletRepository.js +2 -2
- package/dist/cjs/repositories/serialization.js +34 -1
- package/dist/cjs/repositories/sqlite/walletRepository.js +4 -2
- package/dist/cjs/script/address.js +2 -1
- package/dist/cjs/utils/transactionHistory.js +4 -4
- package/dist/cjs/wallet/asset-manager.js +18 -18
- package/dist/cjs/wallet/asset.js +10 -8
- package/dist/cjs/wallet/delegator.js +29 -20
- package/dist/cjs/wallet/hdDescriptorProvider.js +159 -0
- package/dist/cjs/wallet/index.js +5 -1
- package/dist/cjs/wallet/onchain.js +2 -1
- package/dist/cjs/wallet/serviceWorker/wallet-message-handler.js +4 -2
- package/dist/cjs/wallet/serviceWorker/wallet.js +5 -4
- package/dist/cjs/wallet/validation.js +2 -3
- package/dist/cjs/wallet/vtxo-manager.js +7 -5
- package/dist/cjs/wallet/wallet.js +13 -14
- package/dist/esm/contracts/arkcontract.js +2 -1
- package/dist/esm/contracts/contractWatcher.js +14 -16
- package/dist/esm/identity/descriptor.js +74 -5
- package/dist/esm/identity/hdCapableIdentity.js +1 -0
- package/dist/esm/identity/seedIdentity.js +225 -103
- package/dist/esm/identity/serialize.js +5 -0
- package/dist/esm/identity/staticDescriptorProvider.js +1 -1
- package/dist/esm/index.js +7 -4
- package/dist/esm/providers/electrum.js +284 -78
- package/dist/esm/providers/expoIndexer.js +1 -1
- package/dist/esm/providers/indexer.js +2 -2
- package/dist/esm/providers/onchain.js +9 -3
- package/dist/esm/repositories/migrations/walletRepositoryImpl.js +6 -2
- package/dist/esm/repositories/realm/walletRepository.js +3 -3
- package/dist/esm/repositories/serialization.js +27 -0
- package/dist/esm/repositories/sqlite/walletRepository.js +5 -3
- package/dist/esm/script/address.js +2 -1
- package/dist/esm/utils/transactionHistory.js +4 -4
- package/dist/esm/wallet/asset-manager.js +18 -18
- package/dist/esm/wallet/asset.js +10 -8
- package/dist/esm/wallet/delegator.js +29 -20
- package/dist/esm/wallet/hdDescriptorProvider.js +155 -0
- package/dist/esm/wallet/index.js +4 -0
- package/dist/esm/wallet/onchain.js +2 -1
- package/dist/esm/wallet/serviceWorker/wallet-message-handler.js +4 -2
- package/dist/esm/wallet/serviceWorker/wallet.js +5 -4
- package/dist/esm/wallet/validation.js +2 -3
- package/dist/esm/wallet/vtxo-manager.js +7 -5
- package/dist/esm/wallet/wallet.js +12 -14
- package/dist/types/contracts/arkcontract.d.ts +1 -1
- package/dist/types/contracts/types.d.ts +5 -5
- package/dist/types/identity/descriptor.d.ts +26 -0
- package/dist/types/identity/descriptorProvider.d.ts +11 -4
- package/dist/types/identity/hdCapableIdentity.d.ts +44 -0
- package/dist/types/identity/index.d.ts +1 -0
- package/dist/types/identity/seedIdentity.d.ts +113 -29
- package/dist/types/identity/serialize.d.ts +12 -0
- package/dist/types/identity/staticDescriptorProvider.d.ts +1 -1
- package/dist/types/index.d.ts +6 -3
- package/dist/types/providers/electrum.d.ts +115 -15
- package/dist/types/providers/onchain.d.ts +6 -0
- package/dist/types/repositories/serialization.d.ts +26 -2
- package/dist/types/script/address.d.ts +1 -1
- package/dist/types/wallet/delegator.d.ts +8 -3
- package/dist/types/wallet/hdDescriptorProvider.d.ts +93 -0
- package/dist/types/wallet/index.d.ts +19 -10
- package/dist/types/wallet/onchain.d.ts +1 -1
- package/dist/types/wallet/serviceWorker/wallet.d.ts +1 -1
- package/dist/types/wallet/wallet.d.ts +4 -1
- package/package.json +4 -4
|
@@ -302,7 +302,9 @@ class SQLiteWalletRepository {
|
|
|
302
302
|
tx.amount,
|
|
303
303
|
tx.settled ? 1 : 0,
|
|
304
304
|
tx.createdAt,
|
|
305
|
-
tx.assets
|
|
305
|
+
tx.assets
|
|
306
|
+
? JSON.stringify((0, serialization_1.serializeAssets)(tx.assets))
|
|
307
|
+
: null,
|
|
306
308
|
]);
|
|
307
309
|
}
|
|
308
310
|
}
|
|
@@ -410,7 +412,7 @@ function txRowToDomain(row) {
|
|
|
410
412
|
createdAt: row.created_at,
|
|
411
413
|
};
|
|
412
414
|
if (row.assets_json) {
|
|
413
|
-
tx.assets = JSON.parse(row.assets_json);
|
|
415
|
+
tx.assets = (0, serialization_1.deserializeAssets)(JSON.parse(row.assets_json));
|
|
414
416
|
}
|
|
415
417
|
return tx;
|
|
416
418
|
}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ArkAddress = void 0;
|
|
4
4
|
const base_1 = require("@scure/base");
|
|
5
5
|
const script_js_1 = require("@scure/btc-signer/script.js");
|
|
6
|
+
const wallet_1 = require("../wallet");
|
|
6
7
|
/**
|
|
7
8
|
* ArkAddress allows creating and decoding bech32m-encoded Arkade addresses.
|
|
8
9
|
*
|
|
@@ -43,7 +44,7 @@ class ArkAddress {
|
|
|
43
44
|
* @defaultValue `version = 0`
|
|
44
45
|
* @throws Error if either public key is not 32 bytes long
|
|
45
46
|
*/
|
|
46
|
-
constructor(serverPubKey, vtxoTaprootKey, hrp, version = 0) {
|
|
47
|
+
constructor(serverPubKey, vtxoTaprootKey, hrp = wallet_1.DEFAULT_ARKADE_HRP, version = 0) {
|
|
47
48
|
this.serverPubKey = serverPubKey;
|
|
48
49
|
this.vtxoTaprootKey = vtxoTaprootKey;
|
|
49
50
|
this.hrp = hrp;
|
|
@@ -12,7 +12,7 @@ function collectAssets(vtxos) {
|
|
|
12
12
|
for (const vtxo of vtxos) {
|
|
13
13
|
if (vtxo.assets) {
|
|
14
14
|
for (const a of vtxo.assets) {
|
|
15
|
-
map.set(a.assetId, (map.get(a.assetId) ??
|
|
15
|
+
map.set(a.assetId, (map.get(a.assetId) ?? 0n) + a.amount);
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
}
|
|
@@ -25,16 +25,16 @@ function subtractAssets(spent, change) {
|
|
|
25
25
|
for (const vtxo of change) {
|
|
26
26
|
if (vtxo.assets) {
|
|
27
27
|
for (const a of vtxo.assets) {
|
|
28
|
-
map.set(a.assetId, (map.get(a.assetId) ??
|
|
28
|
+
map.set(a.assetId, (map.get(a.assetId) ?? 0n) + a.amount);
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
for (const vtxo of spent) {
|
|
33
33
|
if (vtxo.assets) {
|
|
34
34
|
for (const a of vtxo.assets) {
|
|
35
|
-
const current = map.get(a.assetId) ??
|
|
35
|
+
const current = map.get(a.assetId) ?? 0n;
|
|
36
36
|
const remaining = current - a.amount;
|
|
37
|
-
if (remaining !==
|
|
37
|
+
if (remaining !== 0n) {
|
|
38
38
|
map.set(a.assetId, remaining);
|
|
39
39
|
}
|
|
40
40
|
else {
|
|
@@ -44,7 +44,7 @@ class AssetManager extends ReadonlyAssetManager {
|
|
|
44
44
|
* ```
|
|
45
45
|
*/
|
|
46
46
|
async issue(params) {
|
|
47
|
-
if (params.amount <=
|
|
47
|
+
if (params.amount <= 0n) {
|
|
48
48
|
throw new Error(`Issue amount must be greater than 0, got ${params.amount}`);
|
|
49
49
|
}
|
|
50
50
|
const metadata = castMetadata(params.metadata);
|
|
@@ -64,12 +64,12 @@ class AssetManager extends ReadonlyAssetManager {
|
|
|
64
64
|
continue;
|
|
65
65
|
for (const { assetId, amount } of coin.assets) {
|
|
66
66
|
const existing = assetChanges.get(assetId) ?? 0n;
|
|
67
|
-
assetChanges.set(assetId, existing +
|
|
67
|
+
assetChanges.set(assetId, existing + amount);
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
const groups = [];
|
|
71
71
|
// issued asset group
|
|
72
|
-
const issuedAssetOutput = asset_1.AssetOutput.create(0,
|
|
72
|
+
const issuedAssetOutput = asset_1.AssetOutput.create(0, params.amount);
|
|
73
73
|
const issuedAssetGroup = asset_1.AssetGroup.create(null, controlAssetRef, [], [issuedAssetOutput], metadata);
|
|
74
74
|
groups.push(issuedAssetGroup);
|
|
75
75
|
// add asset groups for each asset change
|
|
@@ -82,7 +82,7 @@ class AssetManager extends ReadonlyAssetManager {
|
|
|
82
82
|
for (const asset of assets) {
|
|
83
83
|
if (asset.assetId !== assetId)
|
|
84
84
|
continue;
|
|
85
|
-
changeInputs.push(asset_1.AssetInput.create(inputIndex,
|
|
85
|
+
changeInputs.push(asset_1.AssetInput.create(inputIndex, asset.amount));
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
// add the change asset group
|
|
@@ -123,7 +123,7 @@ class AssetManager extends ReadonlyAssetManager {
|
|
|
123
123
|
* ```
|
|
124
124
|
*/
|
|
125
125
|
async reissue(params) {
|
|
126
|
-
if (params.amount <=
|
|
126
|
+
if (params.amount <= 0n) {
|
|
127
127
|
throw new Error(`Reissuance amount must be greater than 0, got ${params.amount}`);
|
|
128
128
|
}
|
|
129
129
|
const { controlAssetId } = await this.getAssetDetails(params.assetId);
|
|
@@ -144,11 +144,11 @@ class AssetManager extends ReadonlyAssetManager {
|
|
|
144
144
|
continue;
|
|
145
145
|
for (const { assetId, amount } of coin.assets) {
|
|
146
146
|
if (assetId === params.assetId) {
|
|
147
|
-
assetToReissueAmount +=
|
|
147
|
+
assetToReissueAmount += amount;
|
|
148
148
|
continue;
|
|
149
149
|
}
|
|
150
150
|
const existing = assetChanges.get(assetId) ?? 0n;
|
|
151
|
-
assetChanges.set(assetId, existing +
|
|
151
|
+
assetChanges.set(assetId, existing + amount);
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
// select at least dust amount
|
|
@@ -163,11 +163,11 @@ class AssetManager extends ReadonlyAssetManager {
|
|
|
163
163
|
continue;
|
|
164
164
|
for (const { assetId, amount } of coin.assets) {
|
|
165
165
|
if (assetId === params.assetId) {
|
|
166
|
-
assetToReissueAmount +=
|
|
166
|
+
assetToReissueAmount += amount;
|
|
167
167
|
continue;
|
|
168
168
|
}
|
|
169
169
|
const existing = assetChanges.get(assetId) ?? 0n;
|
|
170
|
-
assetChanges.set(assetId, existing +
|
|
170
|
+
assetChanges.set(assetId, existing + amount);
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
173
|
selectedCoins = [...selectedCoins, ...additional.inputs];
|
|
@@ -180,11 +180,11 @@ class AssetManager extends ReadonlyAssetManager {
|
|
|
180
180
|
for (const asset of assets) {
|
|
181
181
|
if (asset.assetId !== params.assetId)
|
|
182
182
|
continue;
|
|
183
|
-
reissueInputs.push(asset_1.AssetInput.create(inputIndex,
|
|
183
|
+
reissueInputs.push(asset_1.AssetInput.create(inputIndex, asset.amount));
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
186
|
// the total output amount of the asset to reissue = new + (optional) selected amount
|
|
187
|
-
const totalAssetAmount = assetToReissueAmount +
|
|
187
|
+
const totalAssetAmount = assetToReissueAmount + params.amount;
|
|
188
188
|
const reissueAssetIdObj = asset_1.AssetId.fromString(params.assetId);
|
|
189
189
|
// create the reissuance asset group
|
|
190
190
|
const reissueAssetGroup = asset_1.AssetGroup.create(reissueAssetIdObj, null, reissueInputs, [asset_1.AssetOutput.create(0, totalAssetAmount)], []);
|
|
@@ -196,7 +196,7 @@ class AssetManager extends ReadonlyAssetManager {
|
|
|
196
196
|
for (const asset of assets) {
|
|
197
197
|
if (asset.assetId !== assetId)
|
|
198
198
|
continue;
|
|
199
|
-
changeInputs.push(asset_1.AssetInput.create(inputIndex,
|
|
199
|
+
changeInputs.push(asset_1.AssetInput.create(inputIndex, asset.amount));
|
|
200
200
|
}
|
|
201
201
|
}
|
|
202
202
|
groups.push(asset_1.AssetGroup.create(asset_1.AssetId.fromString(assetId), null, changeInputs, [asset_1.AssetOutput.create(0, amount)], []));
|
|
@@ -230,7 +230,7 @@ class AssetManager extends ReadonlyAssetManager {
|
|
|
230
230
|
* ```
|
|
231
231
|
*/
|
|
232
232
|
async burn(params) {
|
|
233
|
-
if (params.amount <=
|
|
233
|
+
if (params.amount <= 0n) {
|
|
234
234
|
throw new Error(`Burn amount must be greater than 0, got ${params.amount}`);
|
|
235
235
|
}
|
|
236
236
|
const virtualCoins = await this.wallet.getVtxos({
|
|
@@ -238,7 +238,7 @@ class AssetManager extends ReadonlyAssetManager {
|
|
|
238
238
|
});
|
|
239
239
|
const assetChanges = new Map();
|
|
240
240
|
// select virtual outputs with the asset to burn
|
|
241
|
-
const { selected: assetCoins } = (0, asset_2.selectCoinsWithAsset)(virtualCoins, params.assetId,
|
|
241
|
+
const { selected: assetCoins } = (0, asset_2.selectCoinsWithAsset)(virtualCoins, params.assetId, params.amount);
|
|
242
242
|
const selectedCoins = [...assetCoins];
|
|
243
243
|
let totalBtcSelected = 0;
|
|
244
244
|
// add the selected coins to asset changes, including the asset to burn
|
|
@@ -248,11 +248,11 @@ class AssetManager extends ReadonlyAssetManager {
|
|
|
248
248
|
continue;
|
|
249
249
|
for (const { assetId, amount } of coin.assets) {
|
|
250
250
|
const existing = assetChanges.get(assetId) ?? 0n;
|
|
251
|
-
assetChanges.set(assetId, existing +
|
|
251
|
+
assetChanges.set(assetId, existing + amount);
|
|
252
252
|
}
|
|
253
253
|
}
|
|
254
254
|
// subtract the amount to burn from the asset change
|
|
255
|
-
assetChanges.set(params.assetId, (assetChanges.get(params.assetId) ?? 0n) -
|
|
255
|
+
assetChanges.set(params.assetId, (assetChanges.get(params.assetId) ?? 0n) - params.amount);
|
|
256
256
|
const minBtcNeeded = Number(this.wallet.dustAmount);
|
|
257
257
|
// we need to ensure at least dust amount is selected
|
|
258
258
|
// if not, select additional coins
|
|
@@ -266,7 +266,7 @@ class AssetManager extends ReadonlyAssetManager {
|
|
|
266
266
|
continue;
|
|
267
267
|
for (const { assetId, amount } of coin.assets) {
|
|
268
268
|
const existing = assetChanges.get(assetId) ?? 0n;
|
|
269
|
-
assetChanges.set(assetId, existing +
|
|
269
|
+
assetChanges.set(assetId, existing + amount);
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
272
|
selectedCoins.push(...additional.inputs);
|
|
@@ -280,7 +280,7 @@ class AssetManager extends ReadonlyAssetManager {
|
|
|
280
280
|
for (const asset of assets) {
|
|
281
281
|
if (asset.assetId !== assetId)
|
|
282
282
|
continue;
|
|
283
|
-
changeInputs.push(asset_1.AssetInput.create(inputIndex,
|
|
283
|
+
changeInputs.push(asset_1.AssetInput.create(inputIndex, asset.amount));
|
|
284
284
|
}
|
|
285
285
|
}
|
|
286
286
|
groups.push(asset_1.AssetGroup.create(asset_1.AssetId.fromString(assetId), null, changeInputs, amount > 0n ? [asset_1.AssetOutput.create(0, amount)] : [], []));
|
package/dist/cjs/wallet/asset.js
CHANGED
|
@@ -21,7 +21,7 @@ function createAssetPacket(assetInputs, receivers, changeReceiver) {
|
|
|
21
21
|
const existing = inputsByAssetId.get(asset.assetId);
|
|
22
22
|
inputsByAssetId.set(asset.assetId, [
|
|
23
23
|
...(existing ?? []),
|
|
24
|
-
asset_1.AssetInput.create(inputIndex,
|
|
24
|
+
asset_1.AssetInput.create(inputIndex, asset.amount),
|
|
25
25
|
]);
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -35,7 +35,7 @@ function createAssetPacket(assetInputs, receivers, changeReceiver) {
|
|
|
35
35
|
const existing = outputsByAssetId.get(asset.assetId);
|
|
36
36
|
outputsByAssetId.set(asset.assetId, [
|
|
37
37
|
...(existing ?? []),
|
|
38
|
-
asset_1.AssetOutput.create(outputIndex,
|
|
38
|
+
asset_1.AssetOutput.create(outputIndex, asset.amount),
|
|
39
39
|
]);
|
|
40
40
|
}
|
|
41
41
|
}
|
|
@@ -47,7 +47,7 @@ function createAssetPacket(assetInputs, receivers, changeReceiver) {
|
|
|
47
47
|
const existing = outputsByAssetId.get(asset.assetId);
|
|
48
48
|
outputsByAssetId.set(asset.assetId, [
|
|
49
49
|
...(existing ?? []),
|
|
50
|
-
asset_1.AssetOutput.create(outputIndex,
|
|
50
|
+
asset_1.AssetOutput.create(outputIndex, asset.amount),
|
|
51
51
|
]);
|
|
52
52
|
}
|
|
53
53
|
}
|
|
@@ -75,9 +75,11 @@ function selectCoinsWithAsset(coins, assetId, requiredAmount) {
|
|
|
75
75
|
const coinsWithAsset = coins.filter((coin) => coin.assets?.some((a) => a.assetId === assetId));
|
|
76
76
|
// sort by asset amount (smallest first for better selection)
|
|
77
77
|
coinsWithAsset.sort((a, b) => {
|
|
78
|
-
const amountA = a.assets?.find((asset) => asset.assetId === assetId)?.amount ??
|
|
79
|
-
const amountB = b.assets?.find((asset) => asset.assetId === assetId)?.amount ??
|
|
80
|
-
|
|
78
|
+
const amountA = a.assets?.find((asset) => asset.assetId === assetId)?.amount ?? 0n;
|
|
79
|
+
const amountB = b.assets?.find((asset) => asset.assetId === assetId)?.amount ?? 0n;
|
|
80
|
+
// Array.sort callback returns number; reduce the bigint diff to
|
|
81
|
+
// -1/0/1 (the only thing sort actually consults).
|
|
82
|
+
return amountA < amountB ? -1 : amountA > amountB ? 1 : 0;
|
|
81
83
|
});
|
|
82
84
|
const selected = [];
|
|
83
85
|
let totalAssetAmount = 0n;
|
|
@@ -85,8 +87,8 @@ function selectCoinsWithAsset(coins, assetId, requiredAmount) {
|
|
|
85
87
|
if (totalAssetAmount >= requiredAmount)
|
|
86
88
|
break;
|
|
87
89
|
selected.push(coin);
|
|
88
|
-
const assetAmount = coin.assets?.find((a) => a.assetId === assetId)?.amount ??
|
|
89
|
-
totalAssetAmount +=
|
|
90
|
+
const assetAmount = coin.assets?.find((a) => a.assetId === assetId)?.amount ?? 0n;
|
|
91
|
+
totalAssetAmount += assetAmount;
|
|
90
92
|
}
|
|
91
93
|
if (totalAssetAmount < requiredAmount) {
|
|
92
94
|
throw new Error(`Insufficient asset balance: have ${totalAssetAmount}, need ${requiredAmount}`);
|
|
@@ -29,20 +29,30 @@ class DelegatorManagerImpl {
|
|
|
29
29
|
// fetch server and delegator info once, shared across all groups
|
|
30
30
|
const arkInfo = await this.arkInfoProvider.getInfo();
|
|
31
31
|
const delegateInfo = await this.delegatorProvider.getDelegateInfo();
|
|
32
|
+
// keep only vtxos that can be signed by the delegate
|
|
33
|
+
const eligible = vtxos
|
|
34
|
+
.filter((v) => findDelegateTapLeaf(v, delegateInfo.pubkey) !== undefined)
|
|
35
|
+
.map((v) => v);
|
|
36
|
+
if (eligible.length === 0) {
|
|
37
|
+
return { delegated: [], failed: [] };
|
|
38
|
+
}
|
|
32
39
|
// if explicit delegateAt is provided, delegate all virtual outputs at once without sorting
|
|
33
40
|
if (delegateAt) {
|
|
34
41
|
try {
|
|
35
|
-
await delegate(this.identity, this.delegatorProvider, arkInfo, delegateInfo,
|
|
42
|
+
await delegate(this.identity, this.delegatorProvider, arkInfo, delegateInfo, eligible, destinationScript, delegateAt);
|
|
36
43
|
}
|
|
37
44
|
catch (error) {
|
|
38
|
-
return {
|
|
45
|
+
return {
|
|
46
|
+
delegated: [],
|
|
47
|
+
failed: [{ outpoints: eligible, error }],
|
|
48
|
+
};
|
|
39
49
|
}
|
|
40
|
-
return { delegated:
|
|
50
|
+
return { delegated: eligible, failed: [] };
|
|
41
51
|
}
|
|
42
52
|
// if no explicit delegateAt is provided, sort virtual outputs by expiry and delegate in groups of the same expiry day
|
|
43
53
|
const groupByExpiry = new Map();
|
|
44
54
|
let recoverableVtxos = [];
|
|
45
|
-
for (const vtxo of
|
|
55
|
+
for (const vtxo of eligible) {
|
|
46
56
|
if ((0, __1.isRecoverable)(vtxo)) {
|
|
47
57
|
recoverableVtxos.push(vtxo);
|
|
48
58
|
continue;
|
|
@@ -190,20 +200,7 @@ async function delegate(identity, delegatorProvider, arkInfo, delegateInfo, vtxo
|
|
|
190
200
|
await delegatorProvider.delegate(registerIntent, forfeits);
|
|
191
201
|
}
|
|
192
202
|
async function makeDelegateForfeitTx(input, connectorAmount, delegatePubkey, forfeitOutputScript, identity) {
|
|
193
|
-
|
|
194
|
-
delegatePubkey = delegatePubkey.slice(2);
|
|
195
|
-
}
|
|
196
|
-
const vtxoScript = __1.VtxoScript.decode(input.tapTree);
|
|
197
|
-
const delegateTapLeaf = vtxoScript.leaves.find((tapLeaf) => {
|
|
198
|
-
const arkTapscript = (0, __1.decodeTapscript)((0, base_2.scriptFromTapLeafScript)(tapLeaf));
|
|
199
|
-
if (!__1.MultisigTapscript.is(arkTapscript))
|
|
200
|
-
return false;
|
|
201
|
-
if (!arkTapscript.params.pubkeys
|
|
202
|
-
.map(base_1.hex.encode)
|
|
203
|
-
.includes(delegatePubkey))
|
|
204
|
-
return false;
|
|
205
|
-
return true;
|
|
206
|
-
});
|
|
203
|
+
const delegateTapLeaf = findDelegateTapLeaf(input, delegatePubkey);
|
|
207
204
|
if (!delegateTapLeaf) {
|
|
208
205
|
throw new Error(`delegate tap leaf not found for input: ${input.txid}:${input.vout}`);
|
|
209
206
|
}
|
|
@@ -248,12 +245,12 @@ async function makeSignedDelegateIntent(identity, coins, outputs, onchainOutputs
|
|
|
248
245
|
for (const [, assets] of assetInputs) {
|
|
249
246
|
for (const asset of assets) {
|
|
250
247
|
const existing = allAssets.get(asset.assetId) ?? 0n;
|
|
251
|
-
allAssets.set(asset.assetId, existing +
|
|
248
|
+
allAssets.set(asset.assetId, existing + asset.amount);
|
|
252
249
|
}
|
|
253
250
|
}
|
|
254
251
|
outputAssets = [];
|
|
255
252
|
for (const [assetId, amount] of allAssets) {
|
|
256
|
-
outputAssets.push({ assetId, amount
|
|
253
|
+
outputAssets.push({ assetId, amount });
|
|
257
254
|
}
|
|
258
255
|
}
|
|
259
256
|
const recipients = outputs.map((output, i) => ({
|
|
@@ -291,3 +288,15 @@ function getDayTimestamp(timestamp) {
|
|
|
291
288
|
date.setUTCHours(0, 0, 0, 0);
|
|
292
289
|
return date.getTime();
|
|
293
290
|
}
|
|
291
|
+
function findDelegateTapLeaf(vtxo, delegatePubkey) {
|
|
292
|
+
if (!vtxo.tapTree)
|
|
293
|
+
return undefined;
|
|
294
|
+
const pk = delegatePubkey.length === 66 ? delegatePubkey.slice(2) : delegatePubkey;
|
|
295
|
+
const vtxoScript = __1.VtxoScript.decode(vtxo.tapTree);
|
|
296
|
+
return vtxoScript.leaves.find((tapLeaf) => {
|
|
297
|
+
const arkTapscript = (0, __1.decodeTapscript)((0, base_2.scriptFromTapLeafScript)(tapLeaf));
|
|
298
|
+
if (!__1.MultisigTapscript.is(arkTapscript))
|
|
299
|
+
return false;
|
|
300
|
+
return arkTapscript.params.pubkeys.map(base_1.hex.encode).includes(pk);
|
|
301
|
+
});
|
|
302
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HDDescriptorProvider = void 0;
|
|
4
|
+
const descriptors_scure_1 = require("@bitcoinerlab/descriptors-scure");
|
|
5
|
+
const descriptor_1 = require("../identity/descriptor");
|
|
6
|
+
const syncCursors_1 = require("../utils/syncCursors");
|
|
7
|
+
/** Settings key under {@link WalletState.settings} where HD state lives. */
|
|
8
|
+
const HD_SETTINGS_KEY = "hd";
|
|
9
|
+
/**
|
|
10
|
+
* HD-wallet {@link DescriptorProvider} that allocates a fresh signing
|
|
11
|
+
* descriptor on every call. The provider holds no notion of "current" — it
|
|
12
|
+
* is a pure rotating allocator. The question of "which descriptor is the
|
|
13
|
+
* wallet currently bound to?" is answered by querying the contract
|
|
14
|
+
* repository for active contracts, not by asking this provider.
|
|
15
|
+
*
|
|
16
|
+
* State is persisted under `WalletRepository.getWalletState().settings.hd` so
|
|
17
|
+
* that no storage-schema migration is required when switching a wallet from
|
|
18
|
+
* single-key to HD. The provider is backed by an {@link HDCapableIdentity},
|
|
19
|
+
* which carries the wildcard account descriptor template (for derivation)
|
|
20
|
+
* and the signing primitives.
|
|
21
|
+
*
|
|
22
|
+
* The read-modify-write of the persisted index runs inside the shared per-
|
|
23
|
+
* repo `updateWalletState` mutex, so two `getNextSigningDescriptor` callers
|
|
24
|
+
* — including those driving separate `HDDescriptorProvider` instances on
|
|
25
|
+
* the same repo — can never observe the same index.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* const provider = await HDDescriptorProvider.create(identity, walletRepo);
|
|
30
|
+
* const descriptor = await provider.getNextSigningDescriptor();
|
|
31
|
+
* // descriptor: tr([fp/86'/0'/0']xpub/0/0)
|
|
32
|
+
* const next = await provider.getNextSigningDescriptor();
|
|
33
|
+
* // next: tr([fp/86'/0'/0']xpub/0/1)
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
class HDDescriptorProvider {
|
|
37
|
+
constructor(identity, walletRepository) {
|
|
38
|
+
this.identity = identity;
|
|
39
|
+
this.walletRepository = walletRepository;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Construct an HDDescriptorProvider. No I/O is performed here;
|
|
43
|
+
* persisted state is read lazily on the first call to
|
|
44
|
+
* `getNextSigningDescriptor`. A descriptor-mismatch error surfaces on
|
|
45
|
+
* first use rather than at boot.
|
|
46
|
+
*/
|
|
47
|
+
static async create(identity, walletRepository) {
|
|
48
|
+
return new HDDescriptorProvider(identity, walletRepository);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Allocate the next descriptor and return it. The first call on a fresh
|
|
52
|
+
* wallet returns descriptor at index 0; subsequent calls return 1, 2, 3,
|
|
53
|
+
* ... in order. Each call is atomic with respect to other rotations on
|
|
54
|
+
* the same repo: two concurrent callers can never observe the same
|
|
55
|
+
* index.
|
|
56
|
+
*/
|
|
57
|
+
async getNextSigningDescriptor() {
|
|
58
|
+
return this.mutate((settings) => {
|
|
59
|
+
const next = settings.lastIndexUsed === undefined
|
|
60
|
+
? 0
|
|
61
|
+
: settings.lastIndexUsed + 1;
|
|
62
|
+
settings.lastIndexUsed = next;
|
|
63
|
+
return this.materializeAt(next);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Returns true when the given descriptor is derivable from this wallet's
|
|
68
|
+
* seed. Delegates to the underlying identity, which handles both HD and
|
|
69
|
+
* simple `tr(pubkey)` descriptors.
|
|
70
|
+
*/
|
|
71
|
+
isOurs(descriptor) {
|
|
72
|
+
return this.identity.isOurs(descriptor);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Signs each request with the key derived from its descriptor. Delegates
|
|
76
|
+
* to the identity's signing primitives — the identity, not the provider,
|
|
77
|
+
* holds the seed.
|
|
78
|
+
*/
|
|
79
|
+
async signWithDescriptor(requests) {
|
|
80
|
+
return this.identity.signWithDescriptor(requests);
|
|
81
|
+
}
|
|
82
|
+
/** Signs a message using the key derived from `descriptor`. */
|
|
83
|
+
async signMessageWithDescriptor(descriptor, message, signatureType = "schnorr") {
|
|
84
|
+
return this.identity.signMessageWithDescriptor(descriptor, message, signatureType);
|
|
85
|
+
}
|
|
86
|
+
// ── internals ────────────────────────────────────────────────────
|
|
87
|
+
/**
|
|
88
|
+
* Substitute the wildcard in the identity's account-descriptor template
|
|
89
|
+
* with a concrete index, going through the descriptors-scure parser
|
|
90
|
+
* rather than ad-hoc string substitution. The parser's `expand({ index })`
|
|
91
|
+
* call validates that the input is a ranged template AND produces a
|
|
92
|
+
* canonical materialized key expression at the given index.
|
|
93
|
+
*/
|
|
94
|
+
materializeAt(index) {
|
|
95
|
+
const descriptor = this.identity.descriptor;
|
|
96
|
+
const network = (0, descriptor_1.isMainnetDescriptor)(descriptor)
|
|
97
|
+
? descriptors_scure_1.networks.bitcoin
|
|
98
|
+
: descriptors_scure_1.networks.testnet;
|
|
99
|
+
const expansion = (0, descriptors_scure_1.expand)({ descriptor, network, index });
|
|
100
|
+
const keyInfo = expansion.expansionMap?.["@0"];
|
|
101
|
+
if (!keyInfo?.keyExpression) {
|
|
102
|
+
throw new Error(`HDDescriptorProvider: cannot materialize descriptor at index ${index}`);
|
|
103
|
+
}
|
|
104
|
+
return `tr(${keyInfo.keyExpression})`;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Run the read-modify-write of HD settings inside the shared per-repo
|
|
108
|
+
* wallet-state mutex. The closure receives a freshly-validated settings
|
|
109
|
+
* snapshot, mutates it, and returns whatever value the caller wants to
|
|
110
|
+
* surface; the mutated settings are then persisted as part of the same
|
|
111
|
+
* atomic update.
|
|
112
|
+
*
|
|
113
|
+
* Doing the read inside the lock is what prevents two providers (or two
|
|
114
|
+
* concurrent callers on the same provider) from racing on a stale index.
|
|
115
|
+
*/
|
|
116
|
+
async mutate(fn) {
|
|
117
|
+
let result;
|
|
118
|
+
await (0, syncCursors_1.updateWalletState)(this.walletRepository, (state) => {
|
|
119
|
+
const settings = this.parseSettings(state);
|
|
120
|
+
result = fn(settings);
|
|
121
|
+
return {
|
|
122
|
+
...state,
|
|
123
|
+
settings: {
|
|
124
|
+
...(state.settings ?? {}),
|
|
125
|
+
[HD_SETTINGS_KEY]: settings,
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Validate the persisted HD settings (or initialize a fresh record when
|
|
133
|
+
* absent) and return a clone safe for the caller to mutate.
|
|
134
|
+
*
|
|
135
|
+
* The cast to `HDWalletSettings` trusts storage; a corrupted or
|
|
136
|
+
* partially-migrated repo could otherwise produce `NaN` descriptors.
|
|
137
|
+
* Fail loud rather than silently derive garbage.
|
|
138
|
+
*/
|
|
139
|
+
parseSettings(state) {
|
|
140
|
+
const stored = state.settings?.[HD_SETTINGS_KEY];
|
|
141
|
+
const expected = this.identity.descriptor;
|
|
142
|
+
if (!stored) {
|
|
143
|
+
return { descriptor: expected };
|
|
144
|
+
}
|
|
145
|
+
if (stored.descriptor !== expected) {
|
|
146
|
+
throw new Error(`HD descriptor mismatch: stored "${stored.descriptor}", expected "${expected}". ` +
|
|
147
|
+
`Refusing to reuse HD state from a different identity.`);
|
|
148
|
+
}
|
|
149
|
+
if (stored.lastIndexUsed !== undefined &&
|
|
150
|
+
(typeof stored.lastIndexUsed !== "number" ||
|
|
151
|
+
!Number.isInteger(stored.lastIndexUsed) ||
|
|
152
|
+
stored.lastIndexUsed < 0)) {
|
|
153
|
+
throw new Error(`Corrupt HD settings: lastIndexUsed is not a non-negative integer (got ${String(stored.lastIndexUsed)}).`);
|
|
154
|
+
}
|
|
155
|
+
// Shallow clone so the closure may mutate without aliasing the repo's copy.
|
|
156
|
+
return { ...stored };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
exports.HDDescriptorProvider = HDDescriptorProvider;
|
package/dist/cjs/wallet/index.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TxType = void 0;
|
|
3
|
+
exports.TxType = exports.DEFAULT_NETWORK_NAME = exports.DEFAULT_ARKADE_HRP = exports.DEFAULT_ARKADE_SERVER_URL = void 0;
|
|
4
4
|
exports.isSpendable = isSpendable;
|
|
5
5
|
exports.isRecoverable = isRecoverable;
|
|
6
6
|
exports.isExpired = isExpired;
|
|
7
7
|
exports.isSubdust = isSubdust;
|
|
8
|
+
/** Defaults */
|
|
9
|
+
exports.DEFAULT_ARKADE_SERVER_URL = "https://arkade.computer";
|
|
10
|
+
exports.DEFAULT_ARKADE_HRP = "ark";
|
|
11
|
+
exports.DEFAULT_NETWORK_NAME = "bitcoin";
|
|
8
12
|
/** Wallet transaction direction. */
|
|
9
13
|
var TxType;
|
|
10
14
|
(function (TxType) {
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.OnchainWallet = void 0;
|
|
4
4
|
exports.selectCoins = selectCoins;
|
|
5
5
|
const btc_signer_1 = require("@scure/btc-signer");
|
|
6
|
+
const _1 = require(".");
|
|
6
7
|
const networks_1 = require("../networks");
|
|
7
8
|
const onchain_1 = require("../providers/onchain");
|
|
8
9
|
const anchor_1 = require("../utils/anchor");
|
|
@@ -43,7 +44,7 @@ class OnchainWallet {
|
|
|
43
44
|
* @defaultValue `provider = new EsploraProvider('https://mempool.space/api')`
|
|
44
45
|
* @throws Error if the configured identity cannot produce a valid x-only public key
|
|
45
46
|
*/
|
|
46
|
-
static async create(identity, networkName, provider) {
|
|
47
|
+
static async create(identity, networkName = _1.DEFAULT_NETWORK_NAME, provider) {
|
|
47
48
|
const pubkey = await identity.xOnlyPublicKey();
|
|
48
49
|
if (!pubkey) {
|
|
49
50
|
throw new Error("Invalid configured public key");
|
|
@@ -514,7 +514,7 @@ class WalletMessageHandler {
|
|
|
514
514
|
for (const vtxo of spendableVtxos) {
|
|
515
515
|
if (vtxo.assets) {
|
|
516
516
|
for (const a of vtxo.assets) {
|
|
517
|
-
const current = assetBalances.get(a.assetId) ??
|
|
517
|
+
const current = assetBalances.get(a.assetId) ?? 0n;
|
|
518
518
|
assetBalances.set(a.assetId, current + a.amount);
|
|
519
519
|
}
|
|
520
520
|
}
|
|
@@ -706,7 +706,9 @@ class WalletMessageHandler {
|
|
|
706
706
|
const { vtxoOutpoints, destination, delegateAt } = message.payload;
|
|
707
707
|
const allVtxos = await wallet.getVtxos();
|
|
708
708
|
const outpointSet = new Set(vtxoOutpoints.map((o) => `${o.txid}:${o.vout}`));
|
|
709
|
-
const filtered = allVtxos
|
|
709
|
+
const filtered = allVtxos
|
|
710
|
+
.filter((v) => outpointSet.has(`${v.txid}:${v.vout}`))
|
|
711
|
+
.map((v) => ({ ...v, contractScript: v.script }));
|
|
710
712
|
const result = await delegatorManager.delegate(filtered, destination, delegateAt !== undefined ? new Date(delegateAt) : undefined);
|
|
711
713
|
return {
|
|
712
714
|
tag: this.messageTag,
|
|
@@ -8,6 +8,7 @@ const repositories_1 = require("../../repositories");
|
|
|
8
8
|
const wallet_message_handler_1 = require("./wallet-message-handler");
|
|
9
9
|
const utils_2 = require("../utils");
|
|
10
10
|
const errors_1 = require("../../worker/errors");
|
|
11
|
+
const wallet_1 = require("../wallet");
|
|
11
12
|
// Check by error message content instead of instanceof because postMessage uses the
|
|
12
13
|
// structured clone algorithm which strips the prototype chain — the page
|
|
13
14
|
// receives a plain Error, not the original MessageBusNotInitializedError.
|
|
@@ -214,7 +215,7 @@ class ServiceWorkerReadonlyWallet {
|
|
|
214
215
|
.then(base_1.hex.encode);
|
|
215
216
|
const initWalletPayload = {
|
|
216
217
|
key: { publicKey },
|
|
217
|
-
arkServerUrl: options
|
|
218
|
+
arkServerUrl: (0, wallet_1.getArkadeServerUrl)(options),
|
|
218
219
|
arkServerPublicKey: options.arkServerPublicKey,
|
|
219
220
|
delegatorUrl: options.delegatorUrl,
|
|
220
221
|
};
|
|
@@ -229,7 +230,7 @@ class ServiceWorkerReadonlyWallet {
|
|
|
229
230
|
const busInitConfig = {
|
|
230
231
|
wallet: serializedWallet,
|
|
231
232
|
arkServer: {
|
|
232
|
-
url: options
|
|
233
|
+
url: (0, wallet_1.getArkadeServerUrl)(options),
|
|
233
234
|
publicKey: options.arkServerPublicKey,
|
|
234
235
|
},
|
|
235
236
|
delegatorUrl: options.delegatorUrl,
|
|
@@ -888,7 +889,7 @@ class ServiceWorkerWallet extends ServiceWorkerReadonlyWallet {
|
|
|
888
889
|
: null;
|
|
889
890
|
const initWalletPayload = {
|
|
890
891
|
key: legacyPrivateKey ? { privateKey: legacyPrivateKey } : {},
|
|
891
|
-
arkServerUrl: options
|
|
892
|
+
arkServerUrl: (0, wallet_1.getArkadeServerUrl)(options),
|
|
892
893
|
arkServerPublicKey: options.arkServerPublicKey,
|
|
893
894
|
delegatorUrl: options.delegatorUrl,
|
|
894
895
|
};
|
|
@@ -903,7 +904,7 @@ class ServiceWorkerWallet extends ServiceWorkerReadonlyWallet {
|
|
|
903
904
|
const busInitConfig = {
|
|
904
905
|
wallet: serializedWallet,
|
|
905
906
|
arkServer: {
|
|
906
|
-
url: options
|
|
907
|
+
url: (0, wallet_1.getArkadeServerUrl)(options),
|
|
907
908
|
publicKey: options.arkServerPublicKey,
|
|
908
909
|
},
|
|
909
910
|
delegatorUrl: options.delegatorUrl,
|
|
@@ -148,8 +148,7 @@ function validateAssetGroupOutput(packet, outputIndex, assetId, expectedAmount)
|
|
|
148
148
|
if (!assetOutput) {
|
|
149
149
|
throw (0, exports.ErrAssetOutputNotFound)(assetId, outputIndex);
|
|
150
150
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
throw (0, exports.ErrInvalidAssetOutputAmount)(assetOutput.amount, expectedAmountBigInt, assetId);
|
|
151
|
+
if (assetOutput.amount !== expectedAmount) {
|
|
152
|
+
throw (0, exports.ErrInvalidAssetOutputAmount)(assetOutput.amount, expectedAmount, assetId);
|
|
154
153
|
}
|
|
155
154
|
}
|
|
@@ -698,11 +698,13 @@ class VtxoManager {
|
|
|
698
698
|
console.error("Error renewing VTXOs:", e);
|
|
699
699
|
});
|
|
700
700
|
}
|
|
701
|
-
delegatorManager
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
701
|
+
if (delegatorManager) {
|
|
702
|
+
delegatorManager
|
|
703
|
+
.delegate(event.vtxos, destination)
|
|
704
|
+
.catch((e) => {
|
|
705
|
+
console.error("Error delegating VTXOs:", e);
|
|
706
|
+
});
|
|
707
|
+
}
|
|
706
708
|
});
|
|
707
709
|
return stopWatching;
|
|
708
710
|
}
|