@arkade-os/sdk 0.4.22 → 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.
Files changed (74) hide show
  1. package/README.md +95 -12
  2. package/dist/cjs/contracts/arkcontract.js +2 -1
  3. package/dist/cjs/identity/descriptor.js +75 -4
  4. package/dist/cjs/identity/hdCapableIdentity.js +2 -0
  5. package/dist/cjs/identity/seedIdentity.js +225 -103
  6. package/dist/cjs/identity/serialize.js +5 -0
  7. package/dist/cjs/identity/staticDescriptorProvider.js +1 -1
  8. package/dist/cjs/index.js +12 -3
  9. package/dist/cjs/providers/electrum.js +285 -79
  10. package/dist/cjs/providers/expoIndexer.js +1 -1
  11. package/dist/cjs/providers/indexer.js +2 -2
  12. package/dist/cjs/providers/onchain.js +9 -3
  13. package/dist/cjs/repositories/migrations/walletRepositoryImpl.js +6 -2
  14. package/dist/cjs/repositories/realm/walletRepository.js +2 -2
  15. package/dist/cjs/repositories/serialization.js +34 -1
  16. package/dist/cjs/repositories/sqlite/walletRepository.js +4 -2
  17. package/dist/cjs/script/address.js +2 -1
  18. package/dist/cjs/utils/transactionHistory.js +4 -4
  19. package/dist/cjs/wallet/asset-manager.js +18 -18
  20. package/dist/cjs/wallet/asset.js +10 -8
  21. package/dist/cjs/wallet/delegator.js +2 -2
  22. package/dist/cjs/wallet/hdDescriptorProvider.js +159 -0
  23. package/dist/cjs/wallet/index.js +5 -1
  24. package/dist/cjs/wallet/onchain.js +2 -1
  25. package/dist/cjs/wallet/serviceWorker/wallet-message-handler.js +1 -1
  26. package/dist/cjs/wallet/serviceWorker/wallet.js +5 -4
  27. package/dist/cjs/wallet/validation.js +2 -3
  28. package/dist/cjs/wallet/wallet.js +13 -14
  29. package/dist/esm/contracts/arkcontract.js +2 -1
  30. package/dist/esm/identity/descriptor.js +74 -5
  31. package/dist/esm/identity/hdCapableIdentity.js +1 -0
  32. package/dist/esm/identity/seedIdentity.js +225 -103
  33. package/dist/esm/identity/serialize.js +5 -0
  34. package/dist/esm/identity/staticDescriptorProvider.js +1 -1
  35. package/dist/esm/index.js +7 -4
  36. package/dist/esm/providers/electrum.js +284 -78
  37. package/dist/esm/providers/expoIndexer.js +1 -1
  38. package/dist/esm/providers/indexer.js +2 -2
  39. package/dist/esm/providers/onchain.js +9 -3
  40. package/dist/esm/repositories/migrations/walletRepositoryImpl.js +6 -2
  41. package/dist/esm/repositories/realm/walletRepository.js +3 -3
  42. package/dist/esm/repositories/serialization.js +27 -0
  43. package/dist/esm/repositories/sqlite/walletRepository.js +5 -3
  44. package/dist/esm/script/address.js +2 -1
  45. package/dist/esm/utils/transactionHistory.js +4 -4
  46. package/dist/esm/wallet/asset-manager.js +18 -18
  47. package/dist/esm/wallet/asset.js +10 -8
  48. package/dist/esm/wallet/delegator.js +2 -2
  49. package/dist/esm/wallet/hdDescriptorProvider.js +155 -0
  50. package/dist/esm/wallet/index.js +4 -0
  51. package/dist/esm/wallet/onchain.js +2 -1
  52. package/dist/esm/wallet/serviceWorker/wallet-message-handler.js +1 -1
  53. package/dist/esm/wallet/serviceWorker/wallet.js +5 -4
  54. package/dist/esm/wallet/validation.js +2 -3
  55. package/dist/esm/wallet/wallet.js +12 -14
  56. package/dist/types/contracts/arkcontract.d.ts +1 -1
  57. package/dist/types/identity/descriptor.d.ts +26 -0
  58. package/dist/types/identity/descriptorProvider.d.ts +11 -4
  59. package/dist/types/identity/hdCapableIdentity.d.ts +44 -0
  60. package/dist/types/identity/index.d.ts +1 -0
  61. package/dist/types/identity/seedIdentity.d.ts +113 -29
  62. package/dist/types/identity/serialize.d.ts +12 -0
  63. package/dist/types/identity/staticDescriptorProvider.d.ts +1 -1
  64. package/dist/types/index.d.ts +6 -3
  65. package/dist/types/providers/electrum.d.ts +115 -15
  66. package/dist/types/providers/onchain.d.ts +6 -0
  67. package/dist/types/repositories/serialization.d.ts +26 -2
  68. package/dist/types/script/address.d.ts +1 -1
  69. package/dist/types/wallet/hdDescriptorProvider.d.ts +93 -0
  70. package/dist/types/wallet/index.d.ts +19 -10
  71. package/dist/types/wallet/onchain.d.ts +1 -1
  72. package/dist/types/wallet/serviceWorker/wallet.d.ts +1 -1
  73. package/dist/types/wallet/wallet.d.ts +4 -1
  74. package/package.json +1 -1
@@ -302,7 +302,9 @@ class SQLiteWalletRepository {
302
302
  tx.amount,
303
303
  tx.settled ? 1 : 0,
304
304
  tx.createdAt,
305
- tx.assets ? JSON.stringify(tx.assets) : null,
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) ?? 0) + a.amount);
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) ?? 0) + a.amount);
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) ?? 0;
35
+ const current = map.get(a.assetId) ?? 0n;
36
36
  const remaining = current - a.amount;
37
- if (remaining !== 0) {
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 <= 0) {
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 + BigInt(amount));
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, BigInt(params.amount));
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, BigInt(asset.amount)));
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 <= 0) {
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 += BigInt(amount);
147
+ assetToReissueAmount += amount;
148
148
  continue;
149
149
  }
150
150
  const existing = assetChanges.get(assetId) ?? 0n;
151
- assetChanges.set(assetId, existing + BigInt(amount));
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 += BigInt(amount);
166
+ assetToReissueAmount += amount;
167
167
  continue;
168
168
  }
169
169
  const existing = assetChanges.get(assetId) ?? 0n;
170
- assetChanges.set(assetId, existing + BigInt(amount));
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, BigInt(asset.amount)));
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 + BigInt(params.amount);
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, BigInt(asset.amount)));
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 <= 0) {
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, BigInt(params.amount));
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 + BigInt(amount));
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) - BigInt(params.amount));
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 + BigInt(amount));
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, BigInt(asset.amount)));
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)] : [], []));
@@ -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, BigInt(asset.amount)),
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, BigInt(asset.amount)),
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, BigInt(asset.amount)),
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 ?? 0;
79
- const amountB = b.assets?.find((asset) => asset.assetId === assetId)?.amount ?? 0;
80
- return amountA - amountB;
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 ?? 0;
89
- totalAssetAmount += BigInt(assetAmount);
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}`);
@@ -245,12 +245,12 @@ async function makeSignedDelegateIntent(identity, coins, outputs, onchainOutputs
245
245
  for (const [, assets] of assetInputs) {
246
246
  for (const asset of assets) {
247
247
  const existing = allAssets.get(asset.assetId) ?? 0n;
248
- allAssets.set(asset.assetId, existing + BigInt(asset.amount));
248
+ allAssets.set(asset.assetId, existing + asset.amount);
249
249
  }
250
250
  }
251
251
  outputAssets = [];
252
252
  for (const [assetId, amount] of allAssets) {
253
- outputAssets.push({ assetId, amount: Number(amount) });
253
+ outputAssets.push({ assetId, amount });
254
254
  }
255
255
  }
256
256
  const recipients = outputs.map((output, i) => ({
@@ -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;
@@ -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) ?? 0;
517
+ const current = assetBalances.get(a.assetId) ?? 0n;
518
518
  assetBalances.set(a.assetId, current + a.amount);
519
519
  }
520
520
  }
@@ -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.arkServerUrl,
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.arkServerUrl,
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.arkServerUrl,
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.arkServerUrl,
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
- const expectedAmountBigInt = BigInt(expectedAmount);
152
- if (assetOutput.amount !== expectedAmountBigInt) {
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
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Wallet = exports.ReadonlyWallet = void 0;
3
+ exports.Wallet = exports.ReadonlyWallet = exports.getArkadeServerUrl = void 0;
4
4
  exports.selectVirtualCoins = selectVirtualCoins;
5
5
  exports.waitForIncomingFunds = waitForIncomingFunds;
6
6
  const base_1 = require("@scure/base");
@@ -39,6 +39,8 @@ const contractManager_1 = require("../contracts/contractManager");
39
39
  const handlers_1 = require("../contracts/handlers");
40
40
  const helpers_1 = require("../contracts/handlers/helpers");
41
41
  const syncCursors_1 = require("../utils/syncCursors");
42
+ const getArkadeServerUrl = ({ arkServerUrl, }) => arkServerUrl || _1.DEFAULT_ARKADE_SERVER_URL;
43
+ exports.getArkadeServerUrl = getArkadeServerUrl;
42
44
  // Historical unilateral exit delay for mainnet (~7 days in seconds).
43
45
  // Kept so existing wallets can still discover and spend VTXOs sent to the
44
46
  // legacy address after arkd starts advertising a different delay.
@@ -125,10 +127,7 @@ class ReadonlyWallet {
125
127
  // Use provided arkProvider instance or create a new one from arkServerUrl
126
128
  const arkProvider = config.arkProvider ||
127
129
  (() => {
128
- if (!config.arkServerUrl) {
129
- throw new Error("Either arkProvider or arkServerUrl must be provided");
130
- }
131
- return new ark_1.RestArkProvider(config.arkServerUrl);
130
+ return new ark_1.RestArkProvider((0, exports.getArkadeServerUrl)(config));
132
131
  })();
133
132
  // Extract arkServerUrl from provider if not explicitly provided
134
133
  const arkServerUrl = config.arkServerUrl || arkProvider.serverUrl;
@@ -307,7 +306,7 @@ class ReadonlyWallet {
307
306
  continue;
308
307
  if (vtxo.assets) {
309
308
  for (const a of vtxo.assets) {
310
- const current = assetBalances.get(a.assetId) ?? 0;
309
+ const current = assetBalances.get(a.assetId) ?? 0n;
311
310
  assetBalances.set(a.assetId, current + a.amount);
312
311
  }
313
312
  }
@@ -1129,12 +1128,12 @@ class Wallet extends ReadonlyWallet {
1129
1128
  for (const [, assets] of assetInputs) {
1130
1129
  for (const asset of assets) {
1131
1130
  const existing = allAssets.get(asset.assetId) ?? 0n;
1132
- allAssets.set(asset.assetId, existing + BigInt(asset.amount));
1131
+ allAssets.set(asset.assetId, existing + asset.amount);
1133
1132
  }
1134
1133
  }
1135
1134
  outputAssets = [];
1136
1135
  for (const [assetId, amount] of allAssets) {
1137
- outputAssets.push({ assetId, amount: Number(amount) });
1136
+ outputAssets.push({ assetId, amount });
1138
1137
  }
1139
1138
  }
1140
1139
  const recipients = params.outputs.map((output, i) => ({
@@ -1569,7 +1568,7 @@ class Wallet extends ReadonlyWallet {
1569
1568
  * const txid = await wallet.send({
1570
1569
  * address: 'ark1q...',
1571
1570
  * amount: 1000, // (optional, default to dust) btc amount to send to the output
1572
- * assets: [{ assetId: 'abc123...', amount: 50 }] // (optional) list of assets to send
1571
+ * assets: [{ assetId: 'abc123...', amount: 50n }] // (optional) list of assets to send
1573
1572
  * });
1574
1573
  * ```
1575
1574
  */
@@ -1600,7 +1599,7 @@ class Wallet extends ReadonlyWallet {
1600
1599
  continue;
1601
1600
  }
1602
1601
  for (const receiverAsset of recipient.assets) {
1603
- let amountToSelect = BigInt(receiverAsset.amount);
1602
+ let amountToSelect = receiverAsset.amount;
1604
1603
  // check if existing change covers the needed amount
1605
1604
  const existingChange = assetChanges.get(receiverAsset.assetId) ?? 0n;
1606
1605
  if (existingChange >= amountToSelect) {
@@ -1627,7 +1626,7 @@ class Wallet extends ReadonlyWallet {
1627
1626
  continue;
1628
1627
  }
1629
1628
  const existing = assetChanges.get(a.assetId) ?? 0n;
1630
- assetChanges.set(a.assetId, existing + BigInt(a.amount));
1629
+ assetChanges.set(a.assetId, existing + a.amount);
1631
1630
  }
1632
1631
  }
1633
1632
  }
@@ -1647,7 +1646,7 @@ class Wallet extends ReadonlyWallet {
1647
1646
  if (coin.assets) {
1648
1647
  for (const asset of coin.assets) {
1649
1648
  const existing = assetChanges.get(asset.assetId) ?? 0n;
1650
- assetChanges.set(asset.assetId, existing + BigInt(asset.amount));
1649
+ assetChanges.set(asset.assetId, existing + asset.amount);
1651
1650
  }
1652
1651
  }
1653
1652
  }
@@ -1669,7 +1668,7 @@ class Wallet extends ReadonlyWallet {
1669
1668
  if (coin.assets) {
1670
1669
  for (const asset of coin.assets) {
1671
1670
  const existing = assetChanges.get(asset.assetId) ?? 0n;
1672
- assetChanges.set(asset.assetId, existing + BigInt(asset.amount));
1671
+ assetChanges.set(asset.assetId, existing + asset.amount);
1673
1672
  }
1674
1673
  }
1675
1674
  }
@@ -1684,7 +1683,7 @@ class Wallet extends ReadonlyWallet {
1684
1683
  const changeAssets = [];
1685
1684
  for (const [assetId, amount] of assetChanges) {
1686
1685
  if (amount > 0n) {
1687
- changeAssets.push({ assetId, amount: Number(amount) });
1686
+ changeAssets.push({ assetId, amount });
1688
1687
  }
1689
1688
  }
1690
1689
  changeIndex = outputs.length;
@@ -1,5 +1,6 @@
1
1
  import { hex } from "@scure/base";
2
2
  import { contractHandlers } from './handlers/index.js';
3
+ import { DEFAULT_ARKADE_HRP } from '../wallet/index.js';
3
4
  /**
4
5
  * Prefix for arkcontract strings.
5
6
  */
@@ -115,7 +116,7 @@ export function contractFromArkContract(encoded, options = {}) {
115
116
  * @param options - Additional options
116
117
  * @returns A complete Contract object
117
118
  */
118
- export function contractFromArkContractWithAddress(encoded, serverPubKey, addressPrefix, options = {}) {
119
+ export function contractFromArkContractWithAddress(encoded, serverPubKey, addressPrefix = DEFAULT_ARKADE_HRP, options = {}) {
119
120
  const parsed = decodeArkContract(encoded);
120
121
  const handler = contractHandlers.getOrThrow(parsed.type);
121
122
  const params = parsed.data;