@arkade-os/sdk 0.4.22 → 0.4.24
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 +116 -13
- package/dist/cjs/contracts/arkcontract.js +2 -1
- package/dist/cjs/contracts/contractManager.js +29 -4
- package/dist/cjs/contracts/contractWatcher.js +9 -3
- package/dist/cjs/contracts/handlers/default.js +3 -2
- package/dist/cjs/contracts/handlers/delegate.js +3 -2
- package/dist/cjs/contracts/handlers/helpers.js +2 -58
- package/dist/cjs/contracts/handlers/vhtlc.js +7 -6
- package/dist/cjs/contracts/vtxoOwnership.js +60 -0
- 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/script/base.js +12 -47
- package/dist/cjs/script/tapscript.js +97 -73
- package/dist/cjs/utils/timelock.js +59 -0
- package/dist/cjs/utils/transactionHistory.js +4 -4
- package/dist/cjs/utils/unknownFields.js +2 -39
- package/dist/cjs/wallet/asset-manager.js +18 -18
- package/dist/cjs/wallet/asset.js +10 -8
- package/dist/cjs/wallet/delegator.js +2 -2
- 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 +60 -10
- package/dist/cjs/wallet/serviceWorker/wallet.js +5 -4
- package/dist/cjs/wallet/unroll.js +79 -67
- package/dist/cjs/wallet/validation.js +2 -3
- package/dist/cjs/wallet/wallet.js +91 -22
- package/dist/cjs/worker/expo/processors/contractPollProcessor.js +7 -2
- package/dist/esm/contracts/arkcontract.js +2 -1
- package/dist/esm/contracts/contractManager.js +29 -4
- package/dist/esm/contracts/contractWatcher.js +9 -3
- package/dist/esm/contracts/handlers/default.js +2 -1
- package/dist/esm/contracts/handlers/delegate.js +2 -1
- package/dist/esm/contracts/handlers/helpers.js +1 -22
- package/dist/esm/contracts/handlers/vhtlc.js +2 -1
- package/dist/esm/contracts/vtxoOwnership.js +53 -0
- 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/script/base.js +12 -14
- package/dist/esm/script/tapscript.js +97 -40
- package/dist/esm/utils/timelock.js +22 -0
- package/dist/esm/utils/transactionHistory.js +4 -4
- package/dist/esm/utils/unknownFields.js +2 -6
- package/dist/esm/wallet/asset-manager.js +18 -18
- package/dist/esm/wallet/asset.js +10 -8
- package/dist/esm/wallet/delegator.js +2 -2
- 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 +60 -10
- package/dist/esm/wallet/serviceWorker/wallet.js +5 -4
- package/dist/esm/wallet/unroll.js +78 -67
- package/dist/esm/wallet/validation.js +2 -3
- package/dist/esm/wallet/wallet.js +88 -20
- package/dist/esm/worker/expo/processors/contractPollProcessor.js +7 -2
- package/dist/types/contracts/arkcontract.d.ts +1 -1
- package/dist/types/contracts/handlers/helpers.d.ts +0 -9
- package/dist/types/contracts/vtxoOwnership.d.ts +25 -0
- 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/script/tapscript.d.ts +4 -0
- package/dist/types/utils/timelock.d.ts +9 -0
- 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/unroll.d.ts +10 -0
- package/dist/types/wallet/wallet.d.ts +4 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { serializeVtxo, serializeUtxo, deserializeVtxo, deserializeUtxo, } from '../serialization.js';
|
|
1
|
+
import { serializeVtxo, serializeUtxo, deserializeVtxo, deserializeUtxo, serializeAssets, deserializeAssets, } from '../serialization.js';
|
|
2
2
|
import { scriptFromArkAddress } from '../scriptFromAddress.js';
|
|
3
3
|
/**
|
|
4
4
|
* SQLite-based implementation of WalletRepository.
|
|
@@ -299,7 +299,9 @@ export class SQLiteWalletRepository {
|
|
|
299
299
|
tx.amount,
|
|
300
300
|
tx.settled ? 1 : 0,
|
|
301
301
|
tx.createdAt,
|
|
302
|
-
tx.assets
|
|
302
|
+
tx.assets
|
|
303
|
+
? JSON.stringify(serializeAssets(tx.assets))
|
|
304
|
+
: null,
|
|
303
305
|
]);
|
|
304
306
|
}
|
|
305
307
|
}
|
|
@@ -406,7 +408,7 @@ function txRowToDomain(row) {
|
|
|
406
408
|
createdAt: row.created_at,
|
|
407
409
|
};
|
|
408
410
|
if (row.assets_json) {
|
|
409
|
-
tx.assets = JSON.parse(row.assets_json);
|
|
411
|
+
tx.assets = deserializeAssets(JSON.parse(row.assets_json));
|
|
410
412
|
}
|
|
411
413
|
return tx;
|
|
412
414
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { bech32m } from "@scure/base";
|
|
2
2
|
import { Script } from "@scure/btc-signer/script.js";
|
|
3
|
+
import { DEFAULT_ARKADE_HRP } from '../wallet/index.js';
|
|
3
4
|
/**
|
|
4
5
|
* ArkAddress allows creating and decoding bech32m-encoded Arkade addresses.
|
|
5
6
|
*
|
|
@@ -40,7 +41,7 @@ export class ArkAddress {
|
|
|
40
41
|
* @defaultValue `version = 0`
|
|
41
42
|
* @throws Error if either public key is not 32 bytes long
|
|
42
43
|
*/
|
|
43
|
-
constructor(serverPubKey, vtxoTaprootKey, hrp, version = 0) {
|
|
44
|
+
constructor(serverPubKey, vtxoTaprootKey, hrp = DEFAULT_ARKADE_HRP, version = 0) {
|
|
44
45
|
this.serverPubKey = serverPubKey;
|
|
45
46
|
this.vtxoTaprootKey = vtxoTaprootKey;
|
|
46
47
|
this.hrp = hrp;
|
package/dist/esm/script/base.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Script, Address, p2tr, taprootListToTree, TAPROOT_UNSPENDABLE_KEY, } from "@scure/btc-signer";
|
|
2
|
-
import * as bip68 from "bip68";
|
|
3
2
|
import { TAP_LEAF_VERSION } from "@scure/btc-signer/payment.js";
|
|
4
3
|
import { PSBTOutput } from "@scure/btc-signer/psbt.js";
|
|
5
4
|
import { hex } from "@scure/base";
|
|
6
5
|
import { ArkAddress } from './address.js';
|
|
6
|
+
import { timelockToSequence } from '../utils/timelock.js';
|
|
7
7
|
import { CLTVMultisigTapscript, ConditionCSVMultisigTapscript, CSVMultisigTapscript, } from './tapscript.js';
|
|
8
8
|
export const TapTreeCoder = PSBTOutput.tapTree[2];
|
|
9
9
|
export function scriptFromTapLeafScript(leaf) {
|
|
@@ -125,19 +125,19 @@ export class VtxoScript {
|
|
|
125
125
|
const paths = [];
|
|
126
126
|
for (const leaf of this.leaves) {
|
|
127
127
|
try {
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
catch (e) {
|
|
133
|
-
try {
|
|
134
|
-
const tapscript = ConditionCSVMultisigTapscript.decode(scriptFromTapLeafScript(leaf));
|
|
135
|
-
paths.push(tapscript);
|
|
128
|
+
const script = scriptFromTapLeafScript(leaf);
|
|
129
|
+
if (CSVMultisigTapscript.isScriptValid(script) === true) {
|
|
130
|
+
const tapScript = CSVMultisigTapscript.decode(script);
|
|
131
|
+
paths.push(tapScript);
|
|
136
132
|
}
|
|
137
|
-
|
|
138
|
-
|
|
133
|
+
else if (ConditionCSVMultisigTapscript.isScriptValid(script) === true) {
|
|
134
|
+
const tapScript = ConditionCSVMultisigTapscript.decode(script);
|
|
135
|
+
paths.push(tapScript);
|
|
139
136
|
}
|
|
140
137
|
}
|
|
138
|
+
catch (e) {
|
|
139
|
+
console.debug("Failed to decode script", e);
|
|
140
|
+
}
|
|
141
141
|
}
|
|
142
142
|
return paths;
|
|
143
143
|
}
|
|
@@ -167,9 +167,7 @@ export function getSequence(tapLeafScript) {
|
|
|
167
167
|
const script = scriptWithLeafVersion.subarray(0, scriptWithLeafVersion.length - 1);
|
|
168
168
|
try {
|
|
169
169
|
const params = CSVMultisigTapscript.decode(script).params;
|
|
170
|
-
sequence =
|
|
171
|
-
? { blocks: Number(params.timelock.value) }
|
|
172
|
-
: { seconds: Number(params.timelock.value) });
|
|
170
|
+
sequence = timelockToSequence(params.timelock);
|
|
173
171
|
}
|
|
174
172
|
catch {
|
|
175
173
|
const params = CLTVMultisigTapscript.decode(script).params;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import * as bip68 from "bip68";
|
|
2
1
|
import { Script, ScriptNum, p2tr_ms } from "@scure/btc-signer";
|
|
3
2
|
import { hex } from "@scure/base";
|
|
3
|
+
import { sequenceToTimelock, timelockToSequence } from '../utils/timelock.js';
|
|
4
4
|
const MinimalScriptNum = ScriptNum(undefined, true);
|
|
5
5
|
export var TapscriptType;
|
|
6
6
|
(function (TapscriptType) {
|
|
@@ -234,9 +234,7 @@ export var CSVMultisigTapscript;
|
|
|
234
234
|
throw new Error(`Invalid pubkey length: expected 32, got ${pubkey.length}`);
|
|
235
235
|
}
|
|
236
236
|
}
|
|
237
|
-
const sequence = MinimalScriptNum.encode(BigInt(
|
|
238
|
-
? { blocks: Number(params.timelock.value) }
|
|
239
|
-
: { seconds: Number(params.timelock.value) })));
|
|
237
|
+
const sequence = MinimalScriptNum.encode(BigInt(timelockToSequence(params.timelock)));
|
|
240
238
|
const asm = [
|
|
241
239
|
sequence.length === 1 ? sequence[0] : sequence,
|
|
242
240
|
"CHECKSEQUENCEVERIFY",
|
|
@@ -259,17 +257,12 @@ export var CSVMultisigTapscript;
|
|
|
259
257
|
if (script.length === 0) {
|
|
260
258
|
throw new Error("Failed to decode: script is empty");
|
|
261
259
|
}
|
|
262
|
-
const
|
|
263
|
-
if (
|
|
264
|
-
throw
|
|
260
|
+
const isValid = isScriptValid(script);
|
|
261
|
+
if (isValid instanceof Error) {
|
|
262
|
+
throw isValid;
|
|
265
263
|
}
|
|
264
|
+
const asm = Script.decode(script);
|
|
266
265
|
const sequence = asm[0];
|
|
267
|
-
if (typeof sequence === "string") {
|
|
268
|
-
throw new Error("Invalid script: expected sequence number");
|
|
269
|
-
}
|
|
270
|
-
if (asm[1] !== "CHECKSEQUENCEVERIFY" || asm[2] !== "DROP") {
|
|
271
|
-
throw new Error("Invalid script: expected CHECKSEQUENCEVERIFY DROP");
|
|
272
|
-
}
|
|
273
266
|
const multisigScript = new Uint8Array(Script.encode(asm.slice(3)));
|
|
274
267
|
let multisig;
|
|
275
268
|
try {
|
|
@@ -285,10 +278,7 @@ export var CSVMultisigTapscript;
|
|
|
285
278
|
else {
|
|
286
279
|
sequenceNum = Number(MinimalScriptNum.decode(sequence));
|
|
287
280
|
}
|
|
288
|
-
const
|
|
289
|
-
const timelock = decodedTimelock.blocks !== undefined
|
|
290
|
-
? { type: "blocks", value: BigInt(decodedTimelock.blocks) }
|
|
291
|
-
: { type: "seconds", value: BigInt(decodedTimelock.seconds) };
|
|
281
|
+
const timelock = sequenceToTimelock(sequenceNum);
|
|
292
282
|
const reconstructed = encode({
|
|
293
283
|
timelock,
|
|
294
284
|
...multisig.params,
|
|
@@ -311,6 +301,21 @@ export var CSVMultisigTapscript;
|
|
|
311
301
|
return tapscript.type === TapscriptType.CSVMultisig;
|
|
312
302
|
}
|
|
313
303
|
CSVMultisigTapscript.is = is;
|
|
304
|
+
function isScriptValid(script) {
|
|
305
|
+
const asm = Script.decode(script);
|
|
306
|
+
if (asm.length < 3) {
|
|
307
|
+
return new Error(`Invalid script: too short (expected at least 3)`);
|
|
308
|
+
}
|
|
309
|
+
const sequence = asm[0];
|
|
310
|
+
if (typeof sequence === "string") {
|
|
311
|
+
return new Error("Invalid script: expected sequence number");
|
|
312
|
+
}
|
|
313
|
+
if (asm[1] !== "CHECKSEQUENCEVERIFY" || asm[2] !== "DROP") {
|
|
314
|
+
return new Error("Invalid script: expected CHECKSEQUENCEVERIFY DROP");
|
|
315
|
+
}
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
CSVMultisigTapscript.isScriptValid = isScriptValid;
|
|
314
319
|
})(CSVMultisigTapscript || (CSVMultisigTapscript = {}));
|
|
315
320
|
/**
|
|
316
321
|
* Combines a condition script with an exit closure. The resulting script requires
|
|
@@ -345,18 +350,14 @@ export var ConditionCSVMultisigTapscript;
|
|
|
345
350
|
if (script.length === 0) {
|
|
346
351
|
throw new Error("Failed to decode: script is empty");
|
|
347
352
|
}
|
|
348
|
-
const
|
|
349
|
-
if (
|
|
350
|
-
throw
|
|
351
|
-
}
|
|
352
|
-
let verifyIndex = -1;
|
|
353
|
-
for (let i = asm.length - 1; i >= 0; i--) {
|
|
354
|
-
if (asm[i] === "VERIFY") {
|
|
355
|
-
verifyIndex = i;
|
|
356
|
-
}
|
|
353
|
+
const isValid = isScriptValid(script);
|
|
354
|
+
if (isValid instanceof Error) {
|
|
355
|
+
throw isValid;
|
|
357
356
|
}
|
|
357
|
+
const asm = Script.decode(script);
|
|
358
|
+
let verifyIndex = getVerifyIndex(asm);
|
|
358
359
|
if (verifyIndex === -1) {
|
|
359
|
-
throw
|
|
360
|
+
throw Error("Invalid script: missing VERIFY operation");
|
|
360
361
|
}
|
|
361
362
|
const conditionScript = new Uint8Array(Script.encode(asm.slice(0, verifyIndex)));
|
|
362
363
|
const csvMultisigScript = new Uint8Array(Script.encode(asm.slice(verifyIndex + 1)));
|
|
@@ -389,6 +390,28 @@ export var ConditionCSVMultisigTapscript;
|
|
|
389
390
|
return tapscript.type === TapscriptType.ConditionCSVMultisig;
|
|
390
391
|
}
|
|
391
392
|
ConditionCSVMultisigTapscript.is = is;
|
|
393
|
+
function getVerifyIndex(asm) {
|
|
394
|
+
let verifyIndex = -1;
|
|
395
|
+
for (let i = asm.length - 1; i >= 0; i--) {
|
|
396
|
+
if (asm[i] === "VERIFY") {
|
|
397
|
+
verifyIndex = i;
|
|
398
|
+
return verifyIndex;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return verifyIndex;
|
|
402
|
+
}
|
|
403
|
+
function isScriptValid(script) {
|
|
404
|
+
const asm = Script.decode(script);
|
|
405
|
+
if (asm.length < 1) {
|
|
406
|
+
return new Error(`Invalid script: too short (expected at least 1)`);
|
|
407
|
+
}
|
|
408
|
+
let verifyIndex = getVerifyIndex(asm);
|
|
409
|
+
if (verifyIndex === -1) {
|
|
410
|
+
return new Error("Invalid script: missing VERIFY operation");
|
|
411
|
+
}
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
ConditionCSVMultisigTapscript.isScriptValid = isScriptValid;
|
|
392
415
|
})(ConditionCSVMultisigTapscript || (ConditionCSVMultisigTapscript = {}));
|
|
393
416
|
/**
|
|
394
417
|
* Combines a condition script with a forfeit closure. The resulting script requires
|
|
@@ -423,18 +446,14 @@ export var ConditionMultisigTapscript;
|
|
|
423
446
|
if (script.length === 0) {
|
|
424
447
|
throw new Error("Failed to decode: script is empty");
|
|
425
448
|
}
|
|
426
|
-
const
|
|
427
|
-
if (
|
|
428
|
-
throw
|
|
429
|
-
}
|
|
430
|
-
let verifyIndex = -1;
|
|
431
|
-
for (let i = asm.length - 1; i >= 0; i--) {
|
|
432
|
-
if (asm[i] === "VERIFY") {
|
|
433
|
-
verifyIndex = i;
|
|
434
|
-
}
|
|
449
|
+
const isValid = isScriptValid(script);
|
|
450
|
+
if (isValid instanceof Error) {
|
|
451
|
+
throw isValid;
|
|
435
452
|
}
|
|
453
|
+
const asm = Script.decode(script);
|
|
454
|
+
let verifyIndex = getVerifyIndex(asm);
|
|
436
455
|
if (verifyIndex === -1) {
|
|
437
|
-
throw
|
|
456
|
+
throw Error("Invalid script: missing VERIFY operation");
|
|
438
457
|
}
|
|
439
458
|
const conditionScript = new Uint8Array(Script.encode(asm.slice(0, verifyIndex)));
|
|
440
459
|
const multisigScript = new Uint8Array(Script.encode(asm.slice(verifyIndex + 1)));
|
|
@@ -467,6 +486,28 @@ export var ConditionMultisigTapscript;
|
|
|
467
486
|
return tapscript.type === TapscriptType.ConditionMultisig;
|
|
468
487
|
}
|
|
469
488
|
ConditionMultisigTapscript.is = is;
|
|
489
|
+
function getVerifyIndex(asm) {
|
|
490
|
+
let verifyIndex = -1;
|
|
491
|
+
for (let i = asm.length - 1; i >= 0; i--) {
|
|
492
|
+
if (asm[i] === "VERIFY") {
|
|
493
|
+
verifyIndex = i;
|
|
494
|
+
return verifyIndex;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return verifyIndex;
|
|
498
|
+
}
|
|
499
|
+
function isScriptValid(script) {
|
|
500
|
+
const asm = Script.decode(script);
|
|
501
|
+
if (asm.length < 1) {
|
|
502
|
+
return new Error(`Invalid script: too short (expected at least 1)`);
|
|
503
|
+
}
|
|
504
|
+
let verifyIndex = getVerifyIndex(asm);
|
|
505
|
+
if (verifyIndex === -1) {
|
|
506
|
+
return new Error("Invalid script: missing VERIFY operation");
|
|
507
|
+
}
|
|
508
|
+
return true;
|
|
509
|
+
}
|
|
510
|
+
ConditionMultisigTapscript.isScriptValid = isScriptValid;
|
|
470
511
|
})(ConditionMultisigTapscript || (ConditionMultisigTapscript = {}));
|
|
471
512
|
/**
|
|
472
513
|
* Implements an absolute timelock (CLTV) script combined with a forfeit closure.
|
|
@@ -507,10 +548,11 @@ export var CLTVMultisigTapscript;
|
|
|
507
548
|
if (script.length === 0) {
|
|
508
549
|
throw new Error("Failed to decode: script is empty");
|
|
509
550
|
}
|
|
510
|
-
const
|
|
511
|
-
if (
|
|
512
|
-
throw
|
|
551
|
+
const isValid = isScriptValid(script);
|
|
552
|
+
if (isValid instanceof Error) {
|
|
553
|
+
throw isValid;
|
|
513
554
|
}
|
|
555
|
+
const asm = Script.decode(script);
|
|
514
556
|
const locktime = asm[0];
|
|
515
557
|
if (typeof locktime === "string") {
|
|
516
558
|
throw new Error("Invalid script: expected locktime number");
|
|
@@ -555,4 +597,19 @@ export var CLTVMultisigTapscript;
|
|
|
555
597
|
return tapscript.type === TapscriptType.CLTVMultisig;
|
|
556
598
|
}
|
|
557
599
|
CLTVMultisigTapscript.is = is;
|
|
600
|
+
function isScriptValid(script) {
|
|
601
|
+
const asm = Script.decode(script);
|
|
602
|
+
if (asm.length < 3) {
|
|
603
|
+
return new Error(`Invalid script: too short (expected at least 3)`);
|
|
604
|
+
}
|
|
605
|
+
const locktime = asm[0];
|
|
606
|
+
if (typeof locktime === "string") {
|
|
607
|
+
return new Error("Invalid script: expected locktime as number or bytes");
|
|
608
|
+
}
|
|
609
|
+
if (asm[1] !== "CHECKLOCKTIMEVERIFY" || asm[2] !== "DROP") {
|
|
610
|
+
return new Error("Invalid script: expected CHECKLOCKTIMEVERIFY DROP");
|
|
611
|
+
}
|
|
612
|
+
return true;
|
|
613
|
+
}
|
|
614
|
+
CLTVMultisigTapscript.isScriptValid = isScriptValid;
|
|
558
615
|
})(CLTVMultisigTapscript || (CLTVMultisigTapscript = {}));
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as bip68 from "bip68";
|
|
2
|
+
/**
|
|
3
|
+
* Convert RelativeTimelock to BIP68 sequence number.
|
|
4
|
+
*/
|
|
5
|
+
export function timelockToSequence(timelock) {
|
|
6
|
+
return bip68.encode(timelock.type === "blocks"
|
|
7
|
+
? { blocks: Number(timelock.value) }
|
|
8
|
+
: { seconds: Number(timelock.value) });
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Convert BIP68 sequence number back to RelativeTimelock.
|
|
12
|
+
*/
|
|
13
|
+
export function sequenceToTimelock(sequence) {
|
|
14
|
+
const decoded = bip68.decode(sequence);
|
|
15
|
+
if ("blocks" in decoded && decoded.blocks !== undefined) {
|
|
16
|
+
return { type: "blocks", value: BigInt(decoded.blocks) };
|
|
17
|
+
}
|
|
18
|
+
if ("seconds" in decoded && decoded.seconds !== undefined) {
|
|
19
|
+
return { type: "seconds", value: BigInt(decoded.seconds) };
|
|
20
|
+
}
|
|
21
|
+
throw new Error(`Invalid BIP68 sequence: ${sequence}`);
|
|
22
|
+
}
|
|
@@ -9,7 +9,7 @@ function collectAssets(vtxos) {
|
|
|
9
9
|
for (const vtxo of vtxos) {
|
|
10
10
|
if (vtxo.assets) {
|
|
11
11
|
for (const a of vtxo.assets) {
|
|
12
|
-
map.set(a.assetId, (map.get(a.assetId) ??
|
|
12
|
+
map.set(a.assetId, (map.get(a.assetId) ?? 0n) + a.amount);
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
}
|
|
@@ -22,16 +22,16 @@ function subtractAssets(spent, change) {
|
|
|
22
22
|
for (const vtxo of change) {
|
|
23
23
|
if (vtxo.assets) {
|
|
24
24
|
for (const a of vtxo.assets) {
|
|
25
|
-
map.set(a.assetId, (map.get(a.assetId) ??
|
|
25
|
+
map.set(a.assetId, (map.get(a.assetId) ?? 0n) + a.amount);
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
for (const vtxo of spent) {
|
|
30
30
|
if (vtxo.assets) {
|
|
31
31
|
for (const a of vtxo.assets) {
|
|
32
|
-
const current = map.get(a.assetId) ??
|
|
32
|
+
const current = map.get(a.assetId) ?? 0n;
|
|
33
33
|
const remaining = current - a.amount;
|
|
34
|
-
if (remaining !==
|
|
34
|
+
if (remaining !== 0n) {
|
|
35
35
|
map.set(a.assetId, remaining);
|
|
36
36
|
}
|
|
37
37
|
else {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import * as bip68 from "bip68";
|
|
2
1
|
import { RawWitness, ScriptNum } from "@scure/btc-signer";
|
|
3
2
|
import { hex } from "@scure/base";
|
|
3
|
+
import { sequenceToTimelock } from './timelock.js';
|
|
4
4
|
/**
|
|
5
5
|
* ArkPsbtFieldKey are the available key names for the Arkade PSBT custom fields.
|
|
6
6
|
*/
|
|
@@ -146,11 +146,7 @@ export const VtxoTreeExpiry = {
|
|
|
146
146
|
const v = ScriptNum(6, true).decode(unknown[1]);
|
|
147
147
|
if (!v)
|
|
148
148
|
return null;
|
|
149
|
-
|
|
150
|
-
return {
|
|
151
|
-
type: blocks ? "blocks" : "seconds",
|
|
152
|
-
value: BigInt(blocks ?? seconds ?? 0),
|
|
153
|
-
};
|
|
149
|
+
return sequenceToTimelock(Number(v));
|
|
154
150
|
}),
|
|
155
151
|
};
|
|
156
152
|
const encodedPsbtFieldKey = Object.fromEntries(Object.values(ArkPsbtFieldKey).map((key) => [
|
|
@@ -40,7 +40,7 @@ export class AssetManager extends ReadonlyAssetManager {
|
|
|
40
40
|
* ```
|
|
41
41
|
*/
|
|
42
42
|
async issue(params) {
|
|
43
|
-
if (params.amount <=
|
|
43
|
+
if (params.amount <= 0n) {
|
|
44
44
|
throw new Error(`Issue amount must be greater than 0, got ${params.amount}`);
|
|
45
45
|
}
|
|
46
46
|
const metadata = castMetadata(params.metadata);
|
|
@@ -60,12 +60,12 @@ export class AssetManager extends ReadonlyAssetManager {
|
|
|
60
60
|
continue;
|
|
61
61
|
for (const { assetId, amount } of coin.assets) {
|
|
62
62
|
const existing = assetChanges.get(assetId) ?? 0n;
|
|
63
|
-
assetChanges.set(assetId, existing +
|
|
63
|
+
assetChanges.set(assetId, existing + amount);
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
const groups = [];
|
|
67
67
|
// issued asset group
|
|
68
|
-
const issuedAssetOutput = AssetOutput.create(0,
|
|
68
|
+
const issuedAssetOutput = AssetOutput.create(0, params.amount);
|
|
69
69
|
const issuedAssetGroup = AssetGroup.create(null, controlAssetRef, [], [issuedAssetOutput], metadata);
|
|
70
70
|
groups.push(issuedAssetGroup);
|
|
71
71
|
// add asset groups for each asset change
|
|
@@ -78,7 +78,7 @@ export class AssetManager extends ReadonlyAssetManager {
|
|
|
78
78
|
for (const asset of assets) {
|
|
79
79
|
if (asset.assetId !== assetId)
|
|
80
80
|
continue;
|
|
81
|
-
changeInputs.push(AssetInput.create(inputIndex,
|
|
81
|
+
changeInputs.push(AssetInput.create(inputIndex, asset.amount));
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
// add the change asset group
|
|
@@ -119,7 +119,7 @@ export class AssetManager extends ReadonlyAssetManager {
|
|
|
119
119
|
* ```
|
|
120
120
|
*/
|
|
121
121
|
async reissue(params) {
|
|
122
|
-
if (params.amount <=
|
|
122
|
+
if (params.amount <= 0n) {
|
|
123
123
|
throw new Error(`Reissuance amount must be greater than 0, got ${params.amount}`);
|
|
124
124
|
}
|
|
125
125
|
const { controlAssetId } = await this.getAssetDetails(params.assetId);
|
|
@@ -140,11 +140,11 @@ export class AssetManager extends ReadonlyAssetManager {
|
|
|
140
140
|
continue;
|
|
141
141
|
for (const { assetId, amount } of coin.assets) {
|
|
142
142
|
if (assetId === params.assetId) {
|
|
143
|
-
assetToReissueAmount +=
|
|
143
|
+
assetToReissueAmount += amount;
|
|
144
144
|
continue;
|
|
145
145
|
}
|
|
146
146
|
const existing = assetChanges.get(assetId) ?? 0n;
|
|
147
|
-
assetChanges.set(assetId, existing +
|
|
147
|
+
assetChanges.set(assetId, existing + amount);
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
150
|
// select at least dust amount
|
|
@@ -159,11 +159,11 @@ export class AssetManager extends ReadonlyAssetManager {
|
|
|
159
159
|
continue;
|
|
160
160
|
for (const { assetId, amount } of coin.assets) {
|
|
161
161
|
if (assetId === params.assetId) {
|
|
162
|
-
assetToReissueAmount +=
|
|
162
|
+
assetToReissueAmount += amount;
|
|
163
163
|
continue;
|
|
164
164
|
}
|
|
165
165
|
const existing = assetChanges.get(assetId) ?? 0n;
|
|
166
|
-
assetChanges.set(assetId, existing +
|
|
166
|
+
assetChanges.set(assetId, existing + amount);
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
169
|
selectedCoins = [...selectedCoins, ...additional.inputs];
|
|
@@ -176,11 +176,11 @@ export class AssetManager extends ReadonlyAssetManager {
|
|
|
176
176
|
for (const asset of assets) {
|
|
177
177
|
if (asset.assetId !== params.assetId)
|
|
178
178
|
continue;
|
|
179
|
-
reissueInputs.push(AssetInput.create(inputIndex,
|
|
179
|
+
reissueInputs.push(AssetInput.create(inputIndex, asset.amount));
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
// the total output amount of the asset to reissue = new + (optional) selected amount
|
|
183
|
-
const totalAssetAmount = assetToReissueAmount +
|
|
183
|
+
const totalAssetAmount = assetToReissueAmount + params.amount;
|
|
184
184
|
const reissueAssetIdObj = AssetId.fromString(params.assetId);
|
|
185
185
|
// create the reissuance asset group
|
|
186
186
|
const reissueAssetGroup = AssetGroup.create(reissueAssetIdObj, null, reissueInputs, [AssetOutput.create(0, totalAssetAmount)], []);
|
|
@@ -192,7 +192,7 @@ export class AssetManager extends ReadonlyAssetManager {
|
|
|
192
192
|
for (const asset of assets) {
|
|
193
193
|
if (asset.assetId !== assetId)
|
|
194
194
|
continue;
|
|
195
|
-
changeInputs.push(AssetInput.create(inputIndex,
|
|
195
|
+
changeInputs.push(AssetInput.create(inputIndex, asset.amount));
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
groups.push(AssetGroup.create(AssetId.fromString(assetId), null, changeInputs, [AssetOutput.create(0, amount)], []));
|
|
@@ -226,7 +226,7 @@ export class AssetManager extends ReadonlyAssetManager {
|
|
|
226
226
|
* ```
|
|
227
227
|
*/
|
|
228
228
|
async burn(params) {
|
|
229
|
-
if (params.amount <=
|
|
229
|
+
if (params.amount <= 0n) {
|
|
230
230
|
throw new Error(`Burn amount must be greater than 0, got ${params.amount}`);
|
|
231
231
|
}
|
|
232
232
|
const virtualCoins = await this.wallet.getVtxos({
|
|
@@ -234,7 +234,7 @@ export class AssetManager extends ReadonlyAssetManager {
|
|
|
234
234
|
});
|
|
235
235
|
const assetChanges = new Map();
|
|
236
236
|
// select virtual outputs with the asset to burn
|
|
237
|
-
const { selected: assetCoins } = selectCoinsWithAsset(virtualCoins, params.assetId,
|
|
237
|
+
const { selected: assetCoins } = selectCoinsWithAsset(virtualCoins, params.assetId, params.amount);
|
|
238
238
|
const selectedCoins = [...assetCoins];
|
|
239
239
|
let totalBtcSelected = 0;
|
|
240
240
|
// add the selected coins to asset changes, including the asset to burn
|
|
@@ -244,11 +244,11 @@ export class AssetManager extends ReadonlyAssetManager {
|
|
|
244
244
|
continue;
|
|
245
245
|
for (const { assetId, amount } of coin.assets) {
|
|
246
246
|
const existing = assetChanges.get(assetId) ?? 0n;
|
|
247
|
-
assetChanges.set(assetId, existing +
|
|
247
|
+
assetChanges.set(assetId, existing + amount);
|
|
248
248
|
}
|
|
249
249
|
}
|
|
250
250
|
// subtract the amount to burn from the asset change
|
|
251
|
-
assetChanges.set(params.assetId, (assetChanges.get(params.assetId) ?? 0n) -
|
|
251
|
+
assetChanges.set(params.assetId, (assetChanges.get(params.assetId) ?? 0n) - params.amount);
|
|
252
252
|
const minBtcNeeded = Number(this.wallet.dustAmount);
|
|
253
253
|
// we need to ensure at least dust amount is selected
|
|
254
254
|
// if not, select additional coins
|
|
@@ -262,7 +262,7 @@ export class AssetManager extends ReadonlyAssetManager {
|
|
|
262
262
|
continue;
|
|
263
263
|
for (const { assetId, amount } of coin.assets) {
|
|
264
264
|
const existing = assetChanges.get(assetId) ?? 0n;
|
|
265
|
-
assetChanges.set(assetId, existing +
|
|
265
|
+
assetChanges.set(assetId, existing + amount);
|
|
266
266
|
}
|
|
267
267
|
}
|
|
268
268
|
selectedCoins.push(...additional.inputs);
|
|
@@ -276,7 +276,7 @@ export class AssetManager extends ReadonlyAssetManager {
|
|
|
276
276
|
for (const asset of assets) {
|
|
277
277
|
if (asset.assetId !== assetId)
|
|
278
278
|
continue;
|
|
279
|
-
changeInputs.push(AssetInput.create(inputIndex,
|
|
279
|
+
changeInputs.push(AssetInput.create(inputIndex, asset.amount));
|
|
280
280
|
}
|
|
281
281
|
}
|
|
282
282
|
groups.push(AssetGroup.create(AssetId.fromString(assetId), null, changeInputs, amount > 0n ? [AssetOutput.create(0, amount)] : [], []));
|
package/dist/esm/wallet/asset.js
CHANGED
|
@@ -15,7 +15,7 @@ export function createAssetPacket(assetInputs, receivers, changeReceiver) {
|
|
|
15
15
|
const existing = inputsByAssetId.get(asset.assetId);
|
|
16
16
|
inputsByAssetId.set(asset.assetId, [
|
|
17
17
|
...(existing ?? []),
|
|
18
|
-
AssetInput.create(inputIndex,
|
|
18
|
+
AssetInput.create(inputIndex, asset.amount),
|
|
19
19
|
]);
|
|
20
20
|
}
|
|
21
21
|
}
|
|
@@ -29,7 +29,7 @@ export function createAssetPacket(assetInputs, receivers, changeReceiver) {
|
|
|
29
29
|
const existing = outputsByAssetId.get(asset.assetId);
|
|
30
30
|
outputsByAssetId.set(asset.assetId, [
|
|
31
31
|
...(existing ?? []),
|
|
32
|
-
AssetOutput.create(outputIndex,
|
|
32
|
+
AssetOutput.create(outputIndex, asset.amount),
|
|
33
33
|
]);
|
|
34
34
|
}
|
|
35
35
|
}
|
|
@@ -41,7 +41,7 @@ export function createAssetPacket(assetInputs, receivers, changeReceiver) {
|
|
|
41
41
|
const existing = outputsByAssetId.get(asset.assetId);
|
|
42
42
|
outputsByAssetId.set(asset.assetId, [
|
|
43
43
|
...(existing ?? []),
|
|
44
|
-
AssetOutput.create(outputIndex,
|
|
44
|
+
AssetOutput.create(outputIndex, asset.amount),
|
|
45
45
|
]);
|
|
46
46
|
}
|
|
47
47
|
}
|
|
@@ -69,9 +69,11 @@ export function selectCoinsWithAsset(coins, assetId, requiredAmount) {
|
|
|
69
69
|
const coinsWithAsset = coins.filter((coin) => coin.assets?.some((a) => a.assetId === assetId));
|
|
70
70
|
// sort by asset amount (smallest first for better selection)
|
|
71
71
|
coinsWithAsset.sort((a, b) => {
|
|
72
|
-
const amountA = a.assets?.find((asset) => asset.assetId === assetId)?.amount ??
|
|
73
|
-
const amountB = b.assets?.find((asset) => asset.assetId === assetId)?.amount ??
|
|
74
|
-
|
|
72
|
+
const amountA = a.assets?.find((asset) => asset.assetId === assetId)?.amount ?? 0n;
|
|
73
|
+
const amountB = b.assets?.find((asset) => asset.assetId === assetId)?.amount ?? 0n;
|
|
74
|
+
// Array.sort callback returns number; reduce the bigint diff to
|
|
75
|
+
// -1/0/1 (the only thing sort actually consults).
|
|
76
|
+
return amountA < amountB ? -1 : amountA > amountB ? 1 : 0;
|
|
75
77
|
});
|
|
76
78
|
const selected = [];
|
|
77
79
|
let totalAssetAmount = 0n;
|
|
@@ -79,8 +81,8 @@ export function selectCoinsWithAsset(coins, assetId, requiredAmount) {
|
|
|
79
81
|
if (totalAssetAmount >= requiredAmount)
|
|
80
82
|
break;
|
|
81
83
|
selected.push(coin);
|
|
82
|
-
const assetAmount = coin.assets?.find((a) => a.assetId === assetId)?.amount ??
|
|
83
|
-
totalAssetAmount +=
|
|
84
|
+
const assetAmount = coin.assets?.find((a) => a.assetId === assetId)?.amount ?? 0n;
|
|
85
|
+
totalAssetAmount += assetAmount;
|
|
84
86
|
}
|
|
85
87
|
if (totalAssetAmount < requiredAmount) {
|
|
86
88
|
throw new Error(`Insufficient asset balance: have ${totalAssetAmount}, need ${requiredAmount}`);
|
|
@@ -240,12 +240,12 @@ async function makeSignedDelegateIntent(identity, coins, outputs, onchainOutputs
|
|
|
240
240
|
for (const [, assets] of assetInputs) {
|
|
241
241
|
for (const asset of assets) {
|
|
242
242
|
const existing = allAssets.get(asset.assetId) ?? 0n;
|
|
243
|
-
allAssets.set(asset.assetId, existing +
|
|
243
|
+
allAssets.set(asset.assetId, existing + asset.amount);
|
|
244
244
|
}
|
|
245
245
|
}
|
|
246
246
|
outputAssets = [];
|
|
247
247
|
for (const [assetId, amount] of allAssets) {
|
|
248
|
-
outputAssets.push({ assetId, amount
|
|
248
|
+
outputAssets.push({ assetId, amount });
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
251
|
const recipients = outputs.map((output, i) => ({
|