@arkade-os/sdk 0.4.46 → 0.4.48-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/expo.cjs +5 -5
- package/dist/adapters/expo.d.cts +2 -2
- package/dist/adapters/expo.d.ts +2 -2
- package/dist/adapters/expo.js +3 -3
- package/dist/adapters/indexedDB.cjs +5 -5
- package/dist/adapters/indexedDB.js +4 -4
- package/dist/{ark-C9ognsOV.d.cts → ark-CoJgwTi_.d.cts} +26 -2
- package/dist/{ark-C9ognsOV.d.ts → ark-CoJgwTi_.d.ts} +26 -2
- package/dist/{asyncStorageTaskQueue-CmBYVlxw.d.cts → asyncStorageTaskQueue-CwknfM_j.d.cts} +1 -1
- package/dist/{asyncStorageTaskQueue-BAKZ5cS5.d.ts → asyncStorageTaskQueue-D2Zl7yAB.d.ts} +1 -1
- package/dist/{chunk-SOXGAA52.cjs → chunk-3HUZAIKI.cjs} +14 -14
- package/dist/{chunk-SOXGAA52.cjs.map → chunk-3HUZAIKI.cjs.map} +1 -1
- package/dist/{chunk-R2SVAM5Q.js → chunk-4AIHKC65.js} +3 -3
- package/dist/{chunk-R2SVAM5Q.js.map → chunk-4AIHKC65.js.map} +1 -1
- package/dist/{chunk-FWG4TSPN.js → chunk-C7TXXTMS.js} +4 -5
- package/dist/chunk-C7TXXTMS.js.map +1 -0
- package/dist/{chunk-7IYIFMF4.cjs → chunk-CFQO5MCM.cjs} +94 -20
- package/dist/chunk-CFQO5MCM.cjs.map +1 -0
- package/dist/{chunk-6TQJOD72.cjs → chunk-EA3IWZ4W.cjs} +7 -7
- package/dist/{chunk-6TQJOD72.cjs.map → chunk-EA3IWZ4W.cjs.map} +1 -1
- package/dist/{chunk-CWJ7ZCP7.js → chunk-GM6JRQP5.js} +87 -16
- package/dist/chunk-GM6JRQP5.js.map +1 -0
- package/dist/{chunk-MQB5H4X5.cjs → chunk-L5ND2FJJ.cjs} +4 -5
- package/dist/chunk-L5ND2FJJ.cjs.map +1 -0
- package/dist/{chunk-D46YDM36.cjs → chunk-RJMFTTRJ.cjs} +1586 -286
- package/dist/chunk-RJMFTTRJ.cjs.map +1 -0
- package/dist/chunk-RQSA7ADZ.cjs +2286 -0
- package/dist/chunk-RQSA7ADZ.cjs.map +1 -0
- package/dist/{chunk-P6VBP3CT.js → chunk-SV4PCGKM.js} +1388 -100
- package/dist/chunk-SV4PCGKM.js.map +1 -0
- package/dist/chunk-XN3554FT.js +2244 -0
- package/dist/chunk-XN3554FT.js.map +1 -0
- package/dist/{chunk-JTCQ6QXQ.js → chunk-ZRY2KSK5.js} +4 -4
- package/dist/{chunk-JTCQ6QXQ.js.map → chunk-ZRY2KSK5.js.map} +1 -1
- package/dist/contracts/handlers/index.cjs +11 -7
- package/dist/contracts/handlers/index.d.cts +3 -3
- package/dist/contracts/handlers/index.d.ts +3 -3
- package/dist/contracts/handlers/index.js +2 -2
- package/dist/{delegate-aOd5qINr.d.ts → delegate-CuaDqqQi.d.ts} +1 -1
- package/dist/{delegate-DVEgyRwI.d.cts → delegate-Dl87_zMA.d.cts} +1 -1
- package/dist/index-BC_G0VTh.d.cts +512 -0
- package/dist/index-CKwpZEpw.d.ts +512 -0
- package/dist/index.cjs +221 -161
- package/dist/index.d.cts +3129 -2089
- package/dist/index.d.ts +3129 -2089
- package/dist/index.js +4 -4
- package/dist/repositories/realm/index.cjs +19 -19
- package/dist/repositories/realm/index.d.cts +2 -2
- package/dist/repositories/realm/index.d.ts +2 -2
- package/dist/repositories/realm/index.js +5 -5
- package/dist/repositories/sqlite/index.cjs +18 -18
- package/dist/repositories/sqlite/index.d.cts +2 -2
- package/dist/repositories/sqlite/index.d.ts +2 -2
- package/dist/repositories/sqlite/index.js +5 -5
- package/dist/{taskRunner-B48-V-Pb.d.ts → taskRunner-Df8YQpn1.d.ts} +1 -1
- package/dist/{taskRunner-nNecmQ2C.d.cts → taskRunner-RYihSj9E.d.cts} +1 -1
- package/dist/wallet/expo/background.cjs +14 -14
- package/dist/wallet/expo/background.d.cts +3 -3
- package/dist/wallet/expo/background.d.ts +3 -3
- package/dist/wallet/expo/background.js +6 -6
- package/dist/wallet/expo/index.cjs +14 -14
- package/dist/wallet/expo/index.d.cts +4 -4
- package/dist/wallet/expo/index.d.ts +4 -4
- package/dist/wallet/expo/index.js +5 -5
- package/dist/{wallet-BUg3D4WJ.d.cts → wallet-B6s7_5bJ.d.cts} +136 -3
- package/dist/{wallet-DM0oShTu.d.ts → wallet-C61huH8i.d.ts} +136 -3
- package/dist/worker/expo/index.cjs +9 -9
- package/dist/worker/expo/index.d.cts +4 -4
- package/dist/worker/expo/index.d.ts +4 -4
- package/dist/worker/expo/index.js +5 -5
- package/package.json +1 -1
- package/dist/chunk-4KXOCVXP.cjs +0 -973
- package/dist/chunk-4KXOCVXP.cjs.map +0 -1
- package/dist/chunk-7IYIFMF4.cjs.map +0 -1
- package/dist/chunk-CWJ7ZCP7.js.map +0 -1
- package/dist/chunk-D46YDM36.cjs.map +0 -1
- package/dist/chunk-FWG4TSPN.js.map +0 -1
- package/dist/chunk-MQB5H4X5.cjs.map +0 -1
- package/dist/chunk-P6VBP3CT.js.map +0 -1
- package/dist/chunk-QKC2T54O.js +0 -960
- package/dist/chunk-QKC2T54O.js.map +0 -1
- package/dist/index-CPzL4tqg.d.ts +0 -259
- package/dist/index-D8vmtWWO.d.cts +0 -259
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { contractHandlers, isMainnetDescriptor, descriptorIsOurs, DEFAULT_PAGE_SIZE, DelegateVtxo, WALLET_RECEIVE_SOURCE, deriveDescriptorLeafPubKey,
|
|
3
|
-
import { VtxoScript,
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { Transaction, setArkPsbtField, PrevArkTxField, ConditionWitness, AssetId, AssetGroup, AssetInput, AssetOutput, Metadata, Packet, craftToSpendTx, getArkPsbtFields, CosignerPublicKey, BufferReader, BufferWriter, OP_RETURN_EMPTY_PKSCRIPT, VtxoTaprootTree, baseFetch, Intent, maybeArkError, isArkError, ArkErrorName, ProviderUnavailableError, FetchError, isFetchTimeoutError, AssetRef, isEventSourceError, RestArkProvider, RestIndexerProvider, ArkError } from './chunk-GM6JRQP5.js';
|
|
2
|
+
import { witnessRefToBytes, validateProgram, toASM, stringifyArtifact, serializeArkadeContractParams, resolveAsm, parseArtifact, getOpcodeValue, getOpcodeName, fromASM, deserializeArkadeContractParams, computeArkadeScriptPublicKey, bytesToASM, asmToBytes, arkadeWitnessHash, arkadeScriptHash, SUPPORTED_PROGRAM_VERSION, OPCODE_VALUES, OPCODE_NAMES, bignum_exports, ArkadeScript, ArkadeProgramScript, ARKADE_OPS, ARKADE_OPCODE_VALUES, ARKADE_OPCODE_NAMES, ARKADE_OPCODES, ARKADE_OP, inputName, contractHandlers, isMainnetDescriptor, descriptorIsOurs, DefaultVtxo, DEFAULT_PAGE_SIZE, DelegateVtxo, WALLET_RECEIVE_SOURCE, deriveDescriptorLeafPubKey, BoardingContractHandler } from './chunk-XN3554FT.js';
|
|
3
|
+
import { VtxoScript, CSVMultisigTapscript, DEFAULT_NETWORK, ArkAddress, sequenceToTimelock, decodeTapscript, scriptFromTapLeafScript, CLTVMultisigTapscript, timelockToSequence, DEFAULT_NETWORK_NAME, getSequence, getNetwork, MultisigTapscript, networks as networks$1, DEFAULT_ARKADE_SERVER_URL } from './chunk-C7TXXTMS.js';
|
|
4
|
+
import { __export } from './chunk-NSBPE2FW.js';
|
|
5
|
+
import { sha256, equalBytes, hash160, sha256x2, concatBytes, randomPrivateKeyBytes, pubECDSA, pubSchnorr } from '@scure/btc-signer/utils.js';
|
|
6
|
+
import { SigHash, Script, OP, OutScript, Address, RawWitness, p2tr, p2wpkh, TaprootControlBlock, DEFAULT_SEQUENCE, Transaction as Transaction$2 } from '@scure/btc-signer';
|
|
7
|
+
import { hex, base58, base64, bech32m } from '@scure/base';
|
|
7
8
|
import * as musig2 from '@scure/btc-signer/musig2.js';
|
|
8
|
-
import { equalBytes, bytesToNumberBE } from '@noble/curves/utils.js';
|
|
9
|
+
import { equalBytes as equalBytes$1, bytesToNumberBE } from '@noble/curves/utils.js';
|
|
9
10
|
import { signAsync, schnorr as schnorr$1, Point } from '@noble/secp256k1';
|
|
10
11
|
import { secp256k1, schnorr } from '@noble/curves/secp256k1.js';
|
|
11
12
|
import { Script as Script$1 } from '@scure/btc-signer/script.js';
|
|
@@ -1512,6 +1513,62 @@ var UnknownPacket = class {
|
|
|
1512
1513
|
}
|
|
1513
1514
|
};
|
|
1514
1515
|
|
|
1516
|
+
// src/extension/emulator/packet.ts
|
|
1517
|
+
var EmulatorPacket = class _EmulatorPacket {
|
|
1518
|
+
constructor(entries) {
|
|
1519
|
+
this.entries = entries;
|
|
1520
|
+
}
|
|
1521
|
+
entries;
|
|
1522
|
+
/** PACKET_TYPE is the 1-byte TLV type tag used in the Extension envelope. */
|
|
1523
|
+
static PACKET_TYPE = 1;
|
|
1524
|
+
static create(entries) {
|
|
1525
|
+
if (entries.length === 0) {
|
|
1526
|
+
throw new Error("empty emulator packet");
|
|
1527
|
+
}
|
|
1528
|
+
for (const entry of entries) {
|
|
1529
|
+
if (entry.script.length === 0) {
|
|
1530
|
+
throw new Error(`empty script for vin ${entry.vin}`);
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1534
|
+
for (const entry of entries) {
|
|
1535
|
+
if (seen.has(entry.vin)) {
|
|
1536
|
+
throw new Error(`duplicate vin ${entry.vin}`);
|
|
1537
|
+
}
|
|
1538
|
+
seen.add(entry.vin);
|
|
1539
|
+
}
|
|
1540
|
+
return new _EmulatorPacket(entries);
|
|
1541
|
+
}
|
|
1542
|
+
static fromBytes(data) {
|
|
1543
|
+
const reader = new BufferReader(data);
|
|
1544
|
+
const entryCount = reader.readCompactSize();
|
|
1545
|
+
const entries = [];
|
|
1546
|
+
for (let i = 0; i < entryCount; i++) {
|
|
1547
|
+
const vin = reader.readUint16LE();
|
|
1548
|
+
const script = reader.readCompactSlice();
|
|
1549
|
+
const witness = reader.readCompactSlice();
|
|
1550
|
+
entries.push({ vin, script, witness });
|
|
1551
|
+
}
|
|
1552
|
+
if (reader.remaining() > 0) {
|
|
1553
|
+
throw new Error(`unexpected ${reader.remaining()} trailing bytes`);
|
|
1554
|
+
}
|
|
1555
|
+
return _EmulatorPacket.create(entries);
|
|
1556
|
+
}
|
|
1557
|
+
type() {
|
|
1558
|
+
return _EmulatorPacket.PACKET_TYPE;
|
|
1559
|
+
}
|
|
1560
|
+
serialize() {
|
|
1561
|
+
const writer = new BufferWriter();
|
|
1562
|
+
writer.writeCompactSize(this.entries.length);
|
|
1563
|
+
for (const entry of this.entries) {
|
|
1564
|
+
writer.writeUint16LE(entry.vin);
|
|
1565
|
+
writer.writeCompactSlice(entry.script);
|
|
1566
|
+
writer.writeCompactSlice(entry.witness ?? new Uint8Array(0));
|
|
1567
|
+
}
|
|
1568
|
+
return writer.toBytes();
|
|
1569
|
+
}
|
|
1570
|
+
};
|
|
1571
|
+
|
|
1515
1572
|
// src/extension/index.ts
|
|
1516
1573
|
var ARKADE_MAGIC = new Uint8Array([65, 82, 75]);
|
|
1517
1574
|
var ExtensionNotFoundError = class extends Error {
|
|
@@ -1531,10 +1588,14 @@ var Extension = class _Extension {
|
|
|
1531
1588
|
}
|
|
1532
1589
|
const seen = /* @__PURE__ */ new Set();
|
|
1533
1590
|
for (const p of packets) {
|
|
1534
|
-
|
|
1535
|
-
|
|
1591
|
+
const type = p.type();
|
|
1592
|
+
if (!Number.isInteger(type) || type < 0 || type > 255) {
|
|
1593
|
+
throw new Error(`invalid packet type ${type}: must be an integer in [0, 255]`);
|
|
1536
1594
|
}
|
|
1537
|
-
seen.
|
|
1595
|
+
if (seen.has(type)) {
|
|
1596
|
+
throw new Error(`duplicate packet type ${type}`);
|
|
1597
|
+
}
|
|
1598
|
+
seen.add(type);
|
|
1538
1599
|
}
|
|
1539
1600
|
return new _Extension(packets);
|
|
1540
1601
|
}
|
|
@@ -1548,7 +1609,7 @@ var Extension = class _Extension {
|
|
|
1548
1609
|
if (decoded.length < 2 || decoded[0] !== "RETURN") return false;
|
|
1549
1610
|
const data = decoded[1];
|
|
1550
1611
|
if (!(data instanceof Uint8Array)) return false;
|
|
1551
|
-
return data.length >= ARKADE_MAGIC.length && equalBytes
|
|
1612
|
+
return data.length >= ARKADE_MAGIC.length && equalBytes(data.slice(0, ARKADE_MAGIC.length), ARKADE_MAGIC);
|
|
1552
1613
|
} catch {
|
|
1553
1614
|
return false;
|
|
1554
1615
|
}
|
|
@@ -1579,7 +1640,7 @@ var Extension = class _Extension {
|
|
|
1579
1640
|
payload.set(d, offset);
|
|
1580
1641
|
offset += d.length;
|
|
1581
1642
|
}
|
|
1582
|
-
if (payload.length < ARKADE_MAGIC.length || !equalBytes
|
|
1643
|
+
if (payload.length < ARKADE_MAGIC.length || !equalBytes(payload.slice(0, ARKADE_MAGIC.length), ARKADE_MAGIC)) {
|
|
1583
1644
|
throw new Error(
|
|
1584
1645
|
`expected magic prefix ${hex.encode(ARKADE_MAGIC)}, got ${hex.encode(payload.slice(0, Math.min(payload.length, ARKADE_MAGIC.length)))}`
|
|
1585
1646
|
);
|
|
@@ -1664,12 +1725,45 @@ var Extension = class _Extension {
|
|
|
1664
1725
|
}
|
|
1665
1726
|
return null;
|
|
1666
1727
|
}
|
|
1728
|
+
/**
|
|
1729
|
+
* getEmulatorPacket returns the embedded EmulatorPacket, or null if not present.
|
|
1730
|
+
*/
|
|
1731
|
+
getEmulatorPacket() {
|
|
1732
|
+
for (const p of this.packets) {
|
|
1733
|
+
if (p instanceof EmulatorPacket) {
|
|
1734
|
+
return p;
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
return null;
|
|
1738
|
+
}
|
|
1739
|
+
/**
|
|
1740
|
+
* getPacketByType returns the first packet matching the given type tag, or null.
|
|
1741
|
+
*/
|
|
1742
|
+
getPacketByType(packetType) {
|
|
1743
|
+
for (const p of this.packets) {
|
|
1744
|
+
if (p.type() === packetType) {
|
|
1745
|
+
return p;
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
return null;
|
|
1749
|
+
}
|
|
1750
|
+
/**
|
|
1751
|
+
* Returns all embedded packets in insertion order. Used when callers need
|
|
1752
|
+
* to rebuild an Extension from an existing one (e.g. appending a new packet).
|
|
1753
|
+
*/
|
|
1754
|
+
getPackets() {
|
|
1755
|
+
return this.packets;
|
|
1756
|
+
}
|
|
1667
1757
|
};
|
|
1668
1758
|
function parsePacket(packetType, data) {
|
|
1669
|
-
|
|
1670
|
-
|
|
1759
|
+
switch (packetType) {
|
|
1760
|
+
case Packet.PACKET_TYPE:
|
|
1761
|
+
return Packet.fromBytes(data);
|
|
1762
|
+
case EmulatorPacket.PACKET_TYPE:
|
|
1763
|
+
return EmulatorPacket.fromBytes(data);
|
|
1764
|
+
default:
|
|
1765
|
+
return new UnknownPacket(packetType, data);
|
|
1671
1766
|
}
|
|
1672
|
-
return new UnknownPacket(packetType, data);
|
|
1673
1767
|
}
|
|
1674
1768
|
function encodeVarUint(value) {
|
|
1675
1769
|
const bytes = [];
|
|
@@ -1750,7 +1844,7 @@ function validateOnchainRecipient(commitmentTx, recipient, network, usedOutputs)
|
|
|
1750
1844
|
if (!output?.script || output.script.length === 0) {
|
|
1751
1845
|
continue;
|
|
1752
1846
|
}
|
|
1753
|
-
if (equalBytes
|
|
1847
|
+
if (equalBytes(output.script, expectedPkScript)) {
|
|
1754
1848
|
if (output.amount !== BigInt(recipient.amount)) {
|
|
1755
1849
|
continue;
|
|
1756
1850
|
}
|
|
@@ -1774,7 +1868,7 @@ function validateOffchainRecipient(leaves, arkAddress, recipient, usedOutputs) {
|
|
|
1774
1868
|
if (!output?.script || output.script.length === 0) {
|
|
1775
1869
|
continue;
|
|
1776
1870
|
}
|
|
1777
|
-
if (!equalBytes
|
|
1871
|
+
if (!equalBytes(output.script, expectedPkScript)) {
|
|
1778
1872
|
continue;
|
|
1779
1873
|
}
|
|
1780
1874
|
if (output.amount !== expectedAmount) {
|
|
@@ -2303,6 +2397,55 @@ function isValidArkAddress(address) {
|
|
|
2303
2397
|
return false;
|
|
2304
2398
|
}
|
|
2305
2399
|
}
|
|
2400
|
+
async function submitOffchainTx(provider, offchainTx, signer, hooks) {
|
|
2401
|
+
const { arkTx: signedArkTx, userSignedCheckpoints } = await signer.signArkTx(
|
|
2402
|
+
offchainTx.arkTx,
|
|
2403
|
+
offchainTx.checkpoints
|
|
2404
|
+
);
|
|
2405
|
+
if (userSignedCheckpoints && userSignedCheckpoints.length !== offchainTx.checkpoints.length) {
|
|
2406
|
+
throw new Error(
|
|
2407
|
+
`signer returned ${userSignedCheckpoints.length} signed checkpoints, expected ${offchainTx.checkpoints.length}`
|
|
2408
|
+
);
|
|
2409
|
+
}
|
|
2410
|
+
await hooks?.beforeSubmit?.();
|
|
2411
|
+
const { arkTxid, signedCheckpointTxs } = await provider.submitTx(
|
|
2412
|
+
base64.encode(signedArkTx.toPSBT()),
|
|
2413
|
+
offchainTx.checkpoints.map((c) => base64.encode(c.toPSBT()))
|
|
2414
|
+
);
|
|
2415
|
+
if (signedCheckpointTxs.length !== offchainTx.checkpoints.length) {
|
|
2416
|
+
throw new Error(
|
|
2417
|
+
`submitTx returned ${signedCheckpointTxs.length} checkpoints, expected ${offchainTx.checkpoints.length}`
|
|
2418
|
+
);
|
|
2419
|
+
}
|
|
2420
|
+
let finalCheckpoints;
|
|
2421
|
+
if (userSignedCheckpoints) {
|
|
2422
|
+
finalCheckpoints = signedCheckpointTxs.map((c, i) => {
|
|
2423
|
+
const serverSigned = Transaction.fromPSBT(base64.decode(c));
|
|
2424
|
+
combineTapscriptSigs(userSignedCheckpoints[i], serverSigned);
|
|
2425
|
+
return base64.encode(serverSigned.toPSBT());
|
|
2426
|
+
});
|
|
2427
|
+
} else {
|
|
2428
|
+
finalCheckpoints = await Promise.all(
|
|
2429
|
+
signedCheckpointTxs.map(async (c) => {
|
|
2430
|
+
const tx = Transaction.fromPSBT(base64.decode(c));
|
|
2431
|
+
const signed = await signer.signCheckpoint(tx);
|
|
2432
|
+
return base64.encode(signed.toPSBT());
|
|
2433
|
+
})
|
|
2434
|
+
);
|
|
2435
|
+
}
|
|
2436
|
+
await provider.finalizeTx(arkTxid, finalCheckpoints);
|
|
2437
|
+
await hooks?.afterFinalize?.();
|
|
2438
|
+
return { arkTxid, signedCheckpointTxs };
|
|
2439
|
+
}
|
|
2440
|
+
async function signAndSubmitOffchainTx(params) {
|
|
2441
|
+
const offchainTx = buildOffchainTx(params.inputs, params.outputs, params.serverUnrollScript);
|
|
2442
|
+
const signer = {
|
|
2443
|
+
signArkTx: async (arkTx) => ({ arkTx: await params.identity.sign(arkTx) }),
|
|
2444
|
+
signCheckpoint: (checkpoint) => params.identity.sign(checkpoint)
|
|
2445
|
+
};
|
|
2446
|
+
const { arkTxid } = await submitOffchainTx(params.provider, offchainTx, signer);
|
|
2447
|
+
return arkTxid;
|
|
2448
|
+
}
|
|
2306
2449
|
var AmountVariableName = "amount";
|
|
2307
2450
|
var ExpiryVariableName = "expiry";
|
|
2308
2451
|
var BirthVariableName = "birth";
|
|
@@ -2458,6 +2601,14 @@ function parseProgram(text, env) {
|
|
|
2458
2601
|
}
|
|
2459
2602
|
return { program, text };
|
|
2460
2603
|
}
|
|
2604
|
+
|
|
2605
|
+
// src/contracts/types.ts
|
|
2606
|
+
function isDiscoverable(handler) {
|
|
2607
|
+
return !!handler && typeof handler.discoverAt === "function";
|
|
2608
|
+
}
|
|
2609
|
+
function isTapscriptDeriving(handler) {
|
|
2610
|
+
return !!handler && typeof handler.deriveTapscripts === "function";
|
|
2611
|
+
}
|
|
2461
2612
|
var DUST_AMOUNT = 546;
|
|
2462
2613
|
var FALLBACK_WALLET_DUST_AMOUNT = 330n;
|
|
2463
2614
|
function getDustAmount(wallet) {
|
|
@@ -2477,10 +2628,14 @@ function deriveContractTapscripts(contract) {
|
|
|
2477
2628
|
throw new Error(`No handler for contract type '${contract.type}'`);
|
|
2478
2629
|
}
|
|
2479
2630
|
const script = handler.createScript(contract.params);
|
|
2631
|
+
if (isTapscriptDeriving(handler)) {
|
|
2632
|
+
return handler.deriveTapscripts(script, contract);
|
|
2633
|
+
}
|
|
2634
|
+
const legacy = script;
|
|
2480
2635
|
return {
|
|
2481
|
-
forfeitTapLeafScript:
|
|
2482
|
-
intentTapLeafScript:
|
|
2483
|
-
tapTree:
|
|
2636
|
+
forfeitTapLeafScript: legacy.forfeit(),
|
|
2637
|
+
intentTapLeafScript: legacy.forfeit(),
|
|
2638
|
+
tapTree: legacy.encode()
|
|
2484
2639
|
};
|
|
2485
2640
|
}
|
|
2486
2641
|
function cloneTapLeafScript([
|
|
@@ -2598,6 +2753,7 @@ async function runWithCrossInstanceLock(name, fn) {
|
|
|
2598
2753
|
});
|
|
2599
2754
|
}
|
|
2600
2755
|
var MAX_VTXOS_PER_SETTLEMENT = 50;
|
|
2756
|
+
var MAX_INPUTS_PER_INTENT = 20;
|
|
2601
2757
|
function byValueDescending(vtxos) {
|
|
2602
2758
|
return [...vtxos].sort((a, b) => b.value - a.value);
|
|
2603
2759
|
}
|
|
@@ -4187,6 +4343,96 @@ function readUInt32BE(array, offset) {
|
|
|
4187
4343
|
function noteTapscript(preimageHash) {
|
|
4188
4344
|
return Script.encode(["SHA256", preimageHash, "EQUAL"]);
|
|
4189
4345
|
}
|
|
4346
|
+
var ArkCash = class _ArkCash {
|
|
4347
|
+
constructor(privateKey, serverPubKey, csvTimelock, hrp = _ArkCash.DefaultHRP) {
|
|
4348
|
+
this.csvTimelock = csvTimelock;
|
|
4349
|
+
this.hrp = hrp;
|
|
4350
|
+
if (privateKey.length !== 32) {
|
|
4351
|
+
throw new Error(
|
|
4352
|
+
`Invalid private key length: expected 32 bytes, got ${privateKey.length}`
|
|
4353
|
+
);
|
|
4354
|
+
}
|
|
4355
|
+
if (serverPubKey.length !== 32) {
|
|
4356
|
+
throw new Error(
|
|
4357
|
+
`Invalid server public key length: expected 32 bytes, got ${serverPubKey.length}`
|
|
4358
|
+
);
|
|
4359
|
+
}
|
|
4360
|
+
this._privateKey = privateKey.slice();
|
|
4361
|
+
this._serverPubKey = serverPubKey.slice();
|
|
4362
|
+
this._publicKey = pubSchnorr(this._privateKey);
|
|
4363
|
+
}
|
|
4364
|
+
csvTimelock;
|
|
4365
|
+
hrp;
|
|
4366
|
+
static DefaultHRP = "arkcash";
|
|
4367
|
+
static Version = 0;
|
|
4368
|
+
static PayloadLength = 1 + 32 + 32 + 4;
|
|
4369
|
+
// 69 bytes
|
|
4370
|
+
// Keys are held as private copies so a caller mutating the array it passed
|
|
4371
|
+
// in — or one it reads back from a getter — cannot alter this note's
|
|
4372
|
+
// identity or the outputs derived from it.
|
|
4373
|
+
_privateKey;
|
|
4374
|
+
_serverPubKey;
|
|
4375
|
+
_publicKey;
|
|
4376
|
+
/** The note's private key. Returns a fresh copy; mutating it is a no-op. */
|
|
4377
|
+
get privateKey() {
|
|
4378
|
+
return this._privateKey.slice();
|
|
4379
|
+
}
|
|
4380
|
+
/** The server's public key. Returns a fresh copy; mutating it is a no-op. */
|
|
4381
|
+
get serverPubKey() {
|
|
4382
|
+
return this._serverPubKey.slice();
|
|
4383
|
+
}
|
|
4384
|
+
/** The note's public key. Returns a fresh copy; mutating it is a no-op. */
|
|
4385
|
+
get publicKey() {
|
|
4386
|
+
return this._publicKey.slice();
|
|
4387
|
+
}
|
|
4388
|
+
static generate(serverPubKey, csvTimelock, hrp) {
|
|
4389
|
+
return new _ArkCash(randomPrivateKeyBytes(), serverPubKey, csvTimelock, hrp);
|
|
4390
|
+
}
|
|
4391
|
+
static fromString(encoded) {
|
|
4392
|
+
const decoded = bech32m.decodeUnsafe(encoded.trim().toLowerCase(), 1023);
|
|
4393
|
+
if (!decoded) {
|
|
4394
|
+
throw new Error("Invalid arkcash string: failed to decode bech32m");
|
|
4395
|
+
}
|
|
4396
|
+
const data = new Uint8Array(bech32m.fromWords(decoded.words));
|
|
4397
|
+
if (data.length !== _ArkCash.PayloadLength) {
|
|
4398
|
+
throw new Error(
|
|
4399
|
+
`Invalid arkcash data length: expected ${_ArkCash.PayloadLength} bytes, got ${data.length}`
|
|
4400
|
+
);
|
|
4401
|
+
}
|
|
4402
|
+
const version = data[0];
|
|
4403
|
+
if (version !== _ArkCash.Version) {
|
|
4404
|
+
throw new Error(`Unsupported arkcash version: ${version}`);
|
|
4405
|
+
}
|
|
4406
|
+
const privateKey = data.slice(1, 33);
|
|
4407
|
+
const serverPubKey = data.slice(33, 65);
|
|
4408
|
+
const sequence = new DataView(data.buffer, data.byteOffset + 65, 4).getUint32(0, false);
|
|
4409
|
+
const csvTimelock = sequenceToTimelock(sequence);
|
|
4410
|
+
return new _ArkCash(privateKey, serverPubKey, csvTimelock, decoded.prefix);
|
|
4411
|
+
}
|
|
4412
|
+
toString() {
|
|
4413
|
+
const data = new Uint8Array(_ArkCash.PayloadLength);
|
|
4414
|
+
data[0] = _ArkCash.Version;
|
|
4415
|
+
data.set(this._privateKey, 1);
|
|
4416
|
+
data.set(this._serverPubKey, 33);
|
|
4417
|
+
const sequence = timelockToSequence(this.csvTimelock);
|
|
4418
|
+
new DataView(data.buffer, data.byteOffset + 65, 4).setUint32(0, sequence, false);
|
|
4419
|
+
const words = bech32m.toWords(data);
|
|
4420
|
+
return bech32m.encode(this.hrp, words, 1023);
|
|
4421
|
+
}
|
|
4422
|
+
get identity() {
|
|
4423
|
+
return SingleKey.fromPrivateKey(this._privateKey);
|
|
4424
|
+
}
|
|
4425
|
+
get vtxoScript() {
|
|
4426
|
+
return new DefaultVtxo.Script({
|
|
4427
|
+
pubKey: this._publicKey,
|
|
4428
|
+
serverPubKey: this._serverPubKey,
|
|
4429
|
+
csvTimelock: this.csvTimelock
|
|
4430
|
+
});
|
|
4431
|
+
}
|
|
4432
|
+
address(addressHrp) {
|
|
4433
|
+
return this.vtxoScript.address(addressHrp, this._serverPubKey);
|
|
4434
|
+
}
|
|
4435
|
+
};
|
|
4190
4436
|
|
|
4191
4437
|
// src/repositories/intentRepository.ts
|
|
4192
4438
|
var ALL_INTENT_STATES = [
|
|
@@ -5989,7 +6235,7 @@ async function makeSignedDelegateIntent(identity, coins, outputs, onchainOutputs
|
|
|
5989
6235
|
};
|
|
5990
6236
|
}
|
|
5991
6237
|
function findDestinationOutputIndex(outputs, destinationScript) {
|
|
5992
|
-
return outputs.findIndex((o) => o.script && equalBytes
|
|
6238
|
+
return outputs.findIndex((o) => o.script && equalBytes(o.script, destinationScript));
|
|
5993
6239
|
}
|
|
5994
6240
|
function getDayTimestamp(timestamp) {
|
|
5995
6241
|
const date = new Date(timestamp);
|
|
@@ -7841,11 +8087,6 @@ var ContractRepositoryImpl = class {
|
|
|
7841
8087
|
}
|
|
7842
8088
|
};
|
|
7843
8089
|
|
|
7844
|
-
// src/contracts/types.ts
|
|
7845
|
-
function isDiscoverable(handler) {
|
|
7846
|
-
return !!handler && typeof handler.discoverAt === "function";
|
|
7847
|
-
}
|
|
7848
|
-
|
|
7849
8090
|
// src/contracts/contractWatcher.ts
|
|
7850
8091
|
function computeReconnectDelay(attempt, baseMs, maxMs) {
|
|
7851
8092
|
return Math.min(baseMs * Math.pow(2, attempt - 1), maxMs);
|
|
@@ -9495,7 +9736,7 @@ var WalletReceiveRotator = class _WalletReceiveRotator {
|
|
|
9495
9736
|
throw e;
|
|
9496
9737
|
}
|
|
9497
9738
|
if (!boot) return void 0;
|
|
9498
|
-
const offchainTapscript = equalBytes
|
|
9739
|
+
const offchainTapscript = equalBytes(
|
|
9499
9740
|
boot.receivePubkey,
|
|
9500
9741
|
setup.offchainTapscript.options.pubKey
|
|
9501
9742
|
) ? setup.offchainTapscript : rebuildTapscript(setup.offchainTapscript, boot.receivePubkey);
|
|
@@ -9986,6 +10227,24 @@ async function spendableVtxosExcludingLocked(vtxos, intentRepository) {
|
|
|
9986
10227
|
}
|
|
9987
10228
|
return excludeLockedOutpoints(vtxos, locked);
|
|
9988
10229
|
}
|
|
10230
|
+
var cashReport = (vtxo, reason) => ({
|
|
10231
|
+
txid: vtxo.txid,
|
|
10232
|
+
vout: vtxo.vout,
|
|
10233
|
+
value: vtxo.value,
|
|
10234
|
+
reason
|
|
10235
|
+
});
|
|
10236
|
+
var ArkCashCreateError = class extends Error {
|
|
10237
|
+
constructor(cash, cause) {
|
|
10238
|
+
super(
|
|
10239
|
+
`Failed to create ArkCash: send failed after the note may have been submitted. Recover with claimCash using the token on this error's \`cash\` field.`
|
|
10240
|
+
);
|
|
10241
|
+
this.cash = cash;
|
|
10242
|
+
this.cause = cause;
|
|
10243
|
+
this.name = "ArkCashCreateError";
|
|
10244
|
+
}
|
|
10245
|
+
cash;
|
|
10246
|
+
cause;
|
|
10247
|
+
};
|
|
9989
10248
|
var ReadonlyWallet = class _ReadonlyWallet {
|
|
9990
10249
|
constructor(identity, network, onchainProvider, indexerProvider, arkServerPublicKey, offchainTapscript, boardingTapscript, dustAmount, walletRepository, contractRepository, delegateProvider, watcherConfig, walletContractTimelocks) {
|
|
9991
10250
|
this.identity = identity;
|
|
@@ -11342,7 +11601,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
11342
11601
|
} catch (e) {
|
|
11343
11602
|
throw new Error("Invalid checkpointTapscript from server");
|
|
11344
11603
|
}
|
|
11345
|
-
if (equalBytes
|
|
11604
|
+
if (equalBytes(xonly, this.arkServerPublicKey)) return;
|
|
11346
11605
|
if (this._receiveRotator) {
|
|
11347
11606
|
await this._receiveRotator.runExclusive(
|
|
11348
11607
|
() => this._doRotateServerSigner(xonly, newServerUnrollScript)
|
|
@@ -11357,7 +11616,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
11357
11616
|
return run;
|
|
11358
11617
|
}
|
|
11359
11618
|
async _doRotateServerSigner(xonly, newServerUnrollScript) {
|
|
11360
|
-
if (equalBytes
|
|
11619
|
+
if (equalBytes(xonly, this.arkServerPublicKey)) return;
|
|
11361
11620
|
const manager = await this.getContractManager();
|
|
11362
11621
|
const [currentOffchainRow] = await manager.getContracts({
|
|
11363
11622
|
script: this.defaultContractScript
|
|
@@ -12445,7 +12704,6 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
12445
12704
|
if (!hasPending) {
|
|
12446
12705
|
return { finalized: [], pending: [] };
|
|
12447
12706
|
}
|
|
12448
|
-
const MAX_INPUTS_PER_INTENT = 20;
|
|
12449
12707
|
if (!vtxos || vtxos.length === 0) {
|
|
12450
12708
|
const scriptMap = await this.getScriptMap();
|
|
12451
12709
|
const allExtended = [];
|
|
@@ -12555,6 +12813,265 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
12555
12813
|
settings: { ...state.settings, hasPendingTx: value }
|
|
12556
12814
|
}));
|
|
12557
12815
|
}
|
|
12816
|
+
/**
|
|
12817
|
+
* Create an ArkCash bearer instrument.
|
|
12818
|
+
*
|
|
12819
|
+
* Generates a fresh keypair, sends the specified amount to a DefaultVtxo
|
|
12820
|
+
* controlled by the new key, and returns the encoded arkcash string.
|
|
12821
|
+
* The receiver can claim the funds using `claimCash()` without ever
|
|
12822
|
+
* sharing their address.
|
|
12823
|
+
*
|
|
12824
|
+
* ArkCash is a short-lived instrument: it carries a private key, so it is
|
|
12825
|
+
* meant to be handed over and claimed promptly, not held. A note left
|
|
12826
|
+
* unclaimed past its batch expiry is swept by the server and `claimCash`
|
|
12827
|
+
* can only report it, not move it.
|
|
12828
|
+
*
|
|
12829
|
+
* @param amount - Amount in satoshis to send. Must be a whole number of
|
|
12830
|
+
* sats at or above the dust threshold — a below-dust amount would mint an
|
|
12831
|
+
* OP_RETURN output that is unspendable as cash.
|
|
12832
|
+
* @returns The encoded arkcash string (e.g., "arkcash1...")
|
|
12833
|
+
*/
|
|
12834
|
+
async createCash(amount) {
|
|
12835
|
+
if (!Number.isSafeInteger(amount) || amount < Number(this.dustAmount)) {
|
|
12836
|
+
throw new Error(
|
|
12837
|
+
`Invalid ArkCash amount ${amount}: must be a whole number of sats >= dust (${this.dustAmount})`
|
|
12838
|
+
);
|
|
12839
|
+
}
|
|
12840
|
+
const cashHrp = this.network.hrp.replace(/ark$/, "arkcash");
|
|
12841
|
+
const cash = ArkCash.generate(
|
|
12842
|
+
this.arkServerPublicKey,
|
|
12843
|
+
this.offchainTapscript.options.csvTimelock,
|
|
12844
|
+
cashHrp
|
|
12845
|
+
);
|
|
12846
|
+
const address = cash.address(this.network.hrp).encode();
|
|
12847
|
+
const cashStr = cash.toString();
|
|
12848
|
+
try {
|
|
12849
|
+
await this.send({ address, amount });
|
|
12850
|
+
} catch (error) {
|
|
12851
|
+
throw new ArkCashCreateError(cashStr, error);
|
|
12852
|
+
}
|
|
12853
|
+
return cashStr;
|
|
12854
|
+
}
|
|
12855
|
+
/**
|
|
12856
|
+
* Claim an ArkCash bearer instrument: sweep what can be swept, report the
|
|
12857
|
+
* rest.
|
|
12858
|
+
*
|
|
12859
|
+
* Every spendable VTXO at the arkcash address is swept to this wallet in
|
|
12860
|
+
* its own offchain transaction, signed with the key carried by the arkcash
|
|
12861
|
+
* string. Nothing is ever persisted: no contract is imported, and the
|
|
12862
|
+
* arkcash key is not stored. Anything that cannot be swept — server-swept,
|
|
12863
|
+
* subdust, already claimed, or asset-bearing — is returned in `unclaimed`
|
|
12864
|
+
* with a per-VTXO reason and otherwise ignored.
|
|
12865
|
+
*
|
|
12866
|
+
* The arkcash string is the recovery token: a claim interrupted between
|
|
12867
|
+
* submit and finalize is completed by simply re-running `claimCash`, which
|
|
12868
|
+
* drains any pending arkcash transaction on the server before sweeping.
|
|
12869
|
+
*
|
|
12870
|
+
* @param cashStr - The encoded arkcash string (e.g., "arkcash1...")
|
|
12871
|
+
* @returns The swept total and the report of what was left behind. The
|
|
12872
|
+
* shape is open: further buckets may be added alongside `unclaimed`.
|
|
12873
|
+
*/
|
|
12874
|
+
async claimCash(cashStr) {
|
|
12875
|
+
const cash = ArkCash.fromString(cashStr);
|
|
12876
|
+
const cashScript = cash.vtxoScript;
|
|
12877
|
+
const cashPkScript = hex.encode(cashScript.pkScript);
|
|
12878
|
+
const scripts = [cashPkScript];
|
|
12879
|
+
let vtxos = await this.fetchAllVtxos(scripts);
|
|
12880
|
+
if (vtxos.length === 0) {
|
|
12881
|
+
throw new Error("No VTXOs found for this arkcash");
|
|
12882
|
+
}
|
|
12883
|
+
const myPkScript = ArkAddress.decode(await this.getAddress()).pkScript;
|
|
12884
|
+
let { spendable, unclaimed } = this.classifyCashVtxos(vtxos);
|
|
12885
|
+
let swept = 0;
|
|
12886
|
+
const drainable = vtxos.filter((vtxo) => !isSubdust(vtxo, this.dustAmount));
|
|
12887
|
+
if (drainable.length > 0) {
|
|
12888
|
+
let drained = { count: 0, swept: 0, claimed: /* @__PURE__ */ new Set() };
|
|
12889
|
+
try {
|
|
12890
|
+
drained = await this.finalizePendingCashTxs(
|
|
12891
|
+
cash,
|
|
12892
|
+
drainable,
|
|
12893
|
+
cashScript,
|
|
12894
|
+
myPkScript
|
|
12895
|
+
);
|
|
12896
|
+
} catch (error) {
|
|
12897
|
+
console.error("Failed to drain pending arkcash txs:", error);
|
|
12898
|
+
}
|
|
12899
|
+
if (drained.count > 0) {
|
|
12900
|
+
vtxos = await this.fetchAllVtxos(scripts);
|
|
12901
|
+
({ spendable, unclaimed } = this.classifyCashVtxos(vtxos));
|
|
12902
|
+
swept += drained.swept;
|
|
12903
|
+
unclaimed = unclaimed.filter(
|
|
12904
|
+
(vtxo) => !drained.claimed.has(`${vtxo.txid}:${vtxo.vout}`)
|
|
12905
|
+
);
|
|
12906
|
+
}
|
|
12907
|
+
}
|
|
12908
|
+
if (spendable.length > 0) {
|
|
12909
|
+
const info = await this.arkProvider.getInfo();
|
|
12910
|
+
const serverUnrollScript = CSVMultisigTapscript.decode(
|
|
12911
|
+
hex.decode(info.checkpointTapscript)
|
|
12912
|
+
);
|
|
12913
|
+
for (const vtxo of spendable) {
|
|
12914
|
+
try {
|
|
12915
|
+
await signAndSubmitOffchainTx({
|
|
12916
|
+
identity: cash.identity,
|
|
12917
|
+
provider: this.arkProvider,
|
|
12918
|
+
inputs: [
|
|
12919
|
+
{
|
|
12920
|
+
txid: vtxo.txid,
|
|
12921
|
+
vout: vtxo.vout,
|
|
12922
|
+
value: vtxo.value,
|
|
12923
|
+
tapLeafScript: cashScript.forfeit(),
|
|
12924
|
+
tapTree: cashScript.encode()
|
|
12925
|
+
}
|
|
12926
|
+
],
|
|
12927
|
+
outputs: [{ script: myPkScript, amount: BigInt(vtxo.value) }],
|
|
12928
|
+
serverUnrollScript
|
|
12929
|
+
});
|
|
12930
|
+
swept += vtxo.value;
|
|
12931
|
+
} catch (error) {
|
|
12932
|
+
console.error(`Failed to sweep arkcash VTXO ${vtxo.txid}:${vtxo.vout}:`, error);
|
|
12933
|
+
unclaimed.push(cashReport(vtxo, "sweep-failed"));
|
|
12934
|
+
}
|
|
12935
|
+
}
|
|
12936
|
+
}
|
|
12937
|
+
return {
|
|
12938
|
+
swept,
|
|
12939
|
+
unclaimed: {
|
|
12940
|
+
amount: unclaimed.reduce((total, vtxo) => total + vtxo.value, 0),
|
|
12941
|
+
vtxos: unclaimed
|
|
12942
|
+
}
|
|
12943
|
+
};
|
|
12944
|
+
}
|
|
12945
|
+
/**
|
|
12946
|
+
* Split the VTXOs at an arkcash address into the ones the thin sweep can
|
|
12947
|
+
* move and the ones it can only report.
|
|
12948
|
+
*/
|
|
12949
|
+
classifyCashVtxos(vtxos) {
|
|
12950
|
+
const spendable = [];
|
|
12951
|
+
const unclaimed = [];
|
|
12952
|
+
for (const vtxo of vtxos) {
|
|
12953
|
+
if (!isSpendable(vtxo)) {
|
|
12954
|
+
unclaimed.push(cashReport(vtxo, "already-spent"));
|
|
12955
|
+
} else if (isSubdust(vtxo, this.dustAmount)) {
|
|
12956
|
+
unclaimed.push(cashReport(vtxo, "subdust"));
|
|
12957
|
+
} else if (isRecoverable(vtxo)) {
|
|
12958
|
+
unclaimed.push(cashReport(vtxo, "swept"));
|
|
12959
|
+
} else if (vtxo.assets && vtxo.assets.length > 0) {
|
|
12960
|
+
unclaimed.push(cashReport(vtxo, "has-assets"));
|
|
12961
|
+
} else {
|
|
12962
|
+
spendable.push(vtxo);
|
|
12963
|
+
}
|
|
12964
|
+
}
|
|
12965
|
+
return { spendable, unclaimed };
|
|
12966
|
+
}
|
|
12967
|
+
/** Read every page of an unfiltered VTXO query for the given scripts. */
|
|
12968
|
+
async fetchAllVtxos(scripts) {
|
|
12969
|
+
const pageSize = 100;
|
|
12970
|
+
const all = [];
|
|
12971
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12972
|
+
for (let pageIndex = 0; ; pageIndex++) {
|
|
12973
|
+
const { vtxos, page } = await this.indexerProvider.getVtxos({
|
|
12974
|
+
scripts,
|
|
12975
|
+
pageIndex,
|
|
12976
|
+
pageSize
|
|
12977
|
+
});
|
|
12978
|
+
for (const vtxo of vtxos) {
|
|
12979
|
+
const outpoint = `${vtxo.txid}:${vtxo.vout}`;
|
|
12980
|
+
if (seen.has(outpoint)) continue;
|
|
12981
|
+
seen.add(outpoint);
|
|
12982
|
+
all.push(vtxo);
|
|
12983
|
+
}
|
|
12984
|
+
if (!page || vtxos.length < pageSize) return all;
|
|
12985
|
+
}
|
|
12986
|
+
}
|
|
12987
|
+
/**
|
|
12988
|
+
* Complete any sweep a previous `claimCash` registered but never finalized.
|
|
12989
|
+
* A thin variant of {@link finalizePendingTxs}, scoped to the arkcash key:
|
|
12990
|
+
* the intent is signed with it, and so are the returned checkpoints.
|
|
12991
|
+
*
|
|
12992
|
+
* A drained sweep is not automatically *ours*. Whoever registered it chose
|
|
12993
|
+
* the destination, so a tx a different claimer left half-finished pays that
|
|
12994
|
+
* claimer — finalizing it is still right (the input is spent either way, and
|
|
12995
|
+
* completing it unsticks the funds), but only the value it actually pays to
|
|
12996
|
+
* `myPkScript` may be counted as claimed here.
|
|
12997
|
+
*
|
|
12998
|
+
* @param vtxos - VTXOs at the arkcash contract's own pkScript, spent ones
|
|
12999
|
+
* included (a registered sweep marks its input spent). Any other outpoint —
|
|
13000
|
+
* subdust above all — would make the proof invalid, since it describes every
|
|
13001
|
+
* input by that pkScript.
|
|
13002
|
+
* @returns The number of pending txs finalized, the sats among them paid to
|
|
13003
|
+
* this wallet, and the outpoints those txs consumed to pay it.
|
|
13004
|
+
*/
|
|
13005
|
+
async finalizePendingCashTxs(cash, vtxos, cashScript, myPkScript) {
|
|
13006
|
+
const result = { count: 0, swept: 0, claimed: /* @__PURE__ */ new Set() };
|
|
13007
|
+
const inputs = vtxos.map((vtxo) => ({
|
|
13008
|
+
...vtxo,
|
|
13009
|
+
forfeitTapLeafScript: cashScript.forfeit(),
|
|
13010
|
+
intentTapLeafScript: cashScript.forfeit(),
|
|
13011
|
+
tapTree: cashScript.encode()
|
|
13012
|
+
}));
|
|
13013
|
+
if (inputs.length === 0) return result;
|
|
13014
|
+
const identity = cash.identity;
|
|
13015
|
+
const seenTxids = /* @__PURE__ */ new Set();
|
|
13016
|
+
const pendingTxs = [];
|
|
13017
|
+
for (let i = 0; i < inputs.length; i += MAX_INPUTS_PER_INTENT) {
|
|
13018
|
+
const batch = inputs.slice(i, i + MAX_INPUTS_PER_INTENT);
|
|
13019
|
+
const message = { type: "get-pending-tx", expire_at: 0 };
|
|
13020
|
+
const proof = Intent.create(message, batch, []);
|
|
13021
|
+
const signedProof = await identity.sign(
|
|
13022
|
+
proof,
|
|
13023
|
+
Array.from({ length: batch.length + 1 }, (_, j) => j)
|
|
13024
|
+
);
|
|
13025
|
+
const batchPending = await this.arkProvider.getPendingTxs({
|
|
13026
|
+
proof: base64.encode(signedProof.toPSBT()),
|
|
13027
|
+
message
|
|
13028
|
+
});
|
|
13029
|
+
for (const pendingTx of batchPending) {
|
|
13030
|
+
if (seenTxids.has(pendingTx.arkTxid)) continue;
|
|
13031
|
+
seenTxids.add(pendingTx.arkTxid);
|
|
13032
|
+
pendingTxs.push(pendingTx);
|
|
13033
|
+
}
|
|
13034
|
+
}
|
|
13035
|
+
const myPkScriptHex = hex.encode(myPkScript);
|
|
13036
|
+
for (const pendingTx of pendingTxs) {
|
|
13037
|
+
try {
|
|
13038
|
+
const checkpointTxs = pendingTx.signedCheckpointTxs.map(
|
|
13039
|
+
(checkpoint) => Transaction$2.fromPSBT(base64.decode(checkpoint))
|
|
13040
|
+
);
|
|
13041
|
+
const finalCheckpoints = await Promise.all(
|
|
13042
|
+
checkpointTxs.map(
|
|
13043
|
+
async (checkpoint) => base64.encode((await identity.sign(checkpoint)).toPSBT())
|
|
13044
|
+
)
|
|
13045
|
+
);
|
|
13046
|
+
await this.arkProvider.finalizeTx(pendingTx.arkTxid, finalCheckpoints);
|
|
13047
|
+
result.count++;
|
|
13048
|
+
const paidToMe = this.arkTxAmountPaidTo(pendingTx.finalArkTx, myPkScriptHex);
|
|
13049
|
+
if (paidToMe === 0) continue;
|
|
13050
|
+
result.swept += paidToMe;
|
|
13051
|
+
for (const checkpoint of checkpointTxs) {
|
|
13052
|
+
for (let i = 0; i < checkpoint.inputsLength; i++) {
|
|
13053
|
+
const input = checkpoint.getInput(i);
|
|
13054
|
+
if (!input.txid) continue;
|
|
13055
|
+
result.claimed.add(`${hex.encode(input.txid)}:${input.index}`);
|
|
13056
|
+
}
|
|
13057
|
+
}
|
|
13058
|
+
} catch (error) {
|
|
13059
|
+
console.error(`Failed to finalize pending arkcash tx ${pendingTx.arkTxid}:`, error);
|
|
13060
|
+
}
|
|
13061
|
+
}
|
|
13062
|
+
return result;
|
|
13063
|
+
}
|
|
13064
|
+
/** Sum the outputs of an ark tx PSBT that pay the given scriptPubKey. */
|
|
13065
|
+
arkTxAmountPaidTo(arkTxPsbt, pkScriptHex) {
|
|
13066
|
+
const arkTx = Transaction$2.fromPSBT(base64.decode(arkTxPsbt));
|
|
13067
|
+
let total = 0;
|
|
13068
|
+
for (let i = 0; i < arkTx.outputsLength; i++) {
|
|
13069
|
+
const output = arkTx.getOutput(i);
|
|
13070
|
+
if (!output.script || output.amount === void 0) continue;
|
|
13071
|
+
if (hex.encode(output.script) === pkScriptHex) total += Number(output.amount);
|
|
13072
|
+
}
|
|
13073
|
+
return total;
|
|
13074
|
+
}
|
|
12558
13075
|
/**
|
|
12559
13076
|
* Send BTC and/or assets to one or more recipients.
|
|
12560
13077
|
*
|
|
@@ -12704,9 +13221,24 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
12704
13221
|
}
|
|
12705
13222
|
const assetInputs = selectedCoinsToAssetInputs(selectedCoins);
|
|
12706
13223
|
const hasAssets = assetInputs.size > 0 || recipients.some((r) => r.assets && r.assets.length > 0);
|
|
13224
|
+
const customExtPackets = [];
|
|
13225
|
+
for (const r of args) {
|
|
13226
|
+
if (r.extensions) {
|
|
13227
|
+
for (const ext of r.extensions) {
|
|
13228
|
+
customExtPackets.push({
|
|
13229
|
+
type: () => ext.type,
|
|
13230
|
+
serialize: () => ext.payload
|
|
13231
|
+
});
|
|
13232
|
+
}
|
|
13233
|
+
}
|
|
13234
|
+
}
|
|
13235
|
+
const allExtPackets = [];
|
|
12707
13236
|
if (hasAssets) {
|
|
12708
|
-
|
|
12709
|
-
|
|
13237
|
+
allExtPackets.push(createAssetPacket(assetInputs, recipients, changeReceiver));
|
|
13238
|
+
}
|
|
13239
|
+
allExtPackets.push(...customExtPackets);
|
|
13240
|
+
if (allExtPackets.length > 0) {
|
|
13241
|
+
outputs.push(Extension.create(allExtPackets).txOut());
|
|
12710
13242
|
}
|
|
12711
13243
|
const sentAmount = recipients.reduce((sum, r) => sum + r.amount, 0);
|
|
12712
13244
|
return this._submitOffchainSpend(selectedCoins, outputs, {
|
|
@@ -12845,72 +13377,52 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
12845
13377
|
index,
|
|
12846
13378
|
lookupScript: VtxoScript.decode(input.tapTree).pkScript
|
|
12847
13379
|
}));
|
|
12848
|
-
const checkpointJobs = offchainTx.checkpoints.map(
|
|
12849
|
-
(c) => this.inputSigningJobsFromWitnessUtxos(c)
|
|
12850
|
-
);
|
|
12851
|
-
let signedVirtualTx;
|
|
12852
|
-
let userSignedCheckpoints;
|
|
12853
13380
|
const identity = this.identity;
|
|
12854
|
-
const
|
|
12855
|
-
|
|
12856
|
-
|
|
12857
|
-
|
|
12858
|
-
tx: offchainTx.arkTx.clone(),
|
|
12859
|
-
inputIndexes: arkTxJobs.map((j) => j.index)
|
|
12860
|
-
},
|
|
12861
|
-
...offchainTx.checkpoints.map((c, i) => ({
|
|
12862
|
-
tx: c.clone(),
|
|
12863
|
-
inputIndexes: checkpointJobs[i].map((j) => j.index)
|
|
12864
|
-
}))
|
|
12865
|
-
];
|
|
12866
|
-
const signed = await identity.signMultiple(requests);
|
|
12867
|
-
if (signed.length !== requests.length) {
|
|
12868
|
-
throw new Error(
|
|
12869
|
-
`signMultiple returned ${signed.length} transactions, expected ${requests.length}`
|
|
12870
|
-
);
|
|
12871
|
-
}
|
|
12872
|
-
const [firstSignedTx, ...signedCheckpoints] = signed;
|
|
12873
|
-
signedVirtualTx = firstSignedTx;
|
|
12874
|
-
userSignedCheckpoints = signedCheckpoints;
|
|
12875
|
-
} else {
|
|
12876
|
-
signedVirtualTx = await this._signerRouter.sign(offchainTx.arkTx, arkTxJobs);
|
|
12877
|
-
}
|
|
12878
|
-
await this.setPendingTxFlag(true);
|
|
12879
|
-
const { arkTxid, signedCheckpointTxs } = await this.arkProvider.submitTx(
|
|
12880
|
-
base64.encode(signedVirtualTx.toPSBT()),
|
|
12881
|
-
offchainTx.checkpoints.map((c) => base64.encode(c.toPSBT()))
|
|
12882
|
-
);
|
|
12883
|
-
let finalCheckpoints;
|
|
12884
|
-
if (userSignedCheckpoints) {
|
|
12885
|
-
if (signedCheckpointTxs.length !== userSignedCheckpoints.length) {
|
|
12886
|
-
throw new Error(
|
|
12887
|
-
`submitTx returned ${signedCheckpointTxs.length} checkpoints, expected ${userSignedCheckpoints.length}`
|
|
13381
|
+
const signer = {
|
|
13382
|
+
signArkTx: async (arkTx, checkpoints) => {
|
|
13383
|
+
const checkpointJobs = checkpoints.map(
|
|
13384
|
+
(c) => this.inputSigningJobsFromWitnessUtxos(c)
|
|
12888
13385
|
);
|
|
13386
|
+
const batchEligible = isBatchSignable(identity) && await this._signerRouter.canBatch(arkTxJobs, ...checkpointJobs);
|
|
13387
|
+
if (batchEligible) {
|
|
13388
|
+
const requests = [
|
|
13389
|
+
{
|
|
13390
|
+
tx: arkTx.clone(),
|
|
13391
|
+
inputIndexes: arkTxJobs.map((j) => j.index)
|
|
13392
|
+
},
|
|
13393
|
+
...checkpoints.map((c, i) => ({
|
|
13394
|
+
tx: c.clone(),
|
|
13395
|
+
inputIndexes: checkpointJobs[i].map((j) => j.index)
|
|
13396
|
+
}))
|
|
13397
|
+
];
|
|
13398
|
+
const signed = await identity.signMultiple(requests);
|
|
13399
|
+
if (signed.length !== requests.length) {
|
|
13400
|
+
throw new Error(
|
|
13401
|
+
`signMultiple returned ${signed.length} transactions, expected ${requests.length}`
|
|
13402
|
+
);
|
|
13403
|
+
}
|
|
13404
|
+
const [firstSignedTx, ...signedCheckpoints] = signed;
|
|
13405
|
+
return { arkTx: firstSignedTx, userSignedCheckpoints: signedCheckpoints };
|
|
13406
|
+
}
|
|
13407
|
+
return { arkTx: await this._signerRouter.sign(arkTx, arkTxJobs) };
|
|
13408
|
+
},
|
|
13409
|
+
signCheckpoint: (checkpoint) => this._signerRouter.sign(
|
|
13410
|
+
checkpoint,
|
|
13411
|
+
this.inputSigningJobsFromWitnessUtxos(checkpoint)
|
|
13412
|
+
)
|
|
13413
|
+
};
|
|
13414
|
+
return submitOffchainTx(this.arkProvider, offchainTx, signer, {
|
|
13415
|
+
// Mark pending before submitting — if we crash between submit and
|
|
13416
|
+
// finalize, the next init recovers via finalizePendingTxs.
|
|
13417
|
+
beforeSubmit: () => this.setPendingTxFlag(true),
|
|
13418
|
+
afterFinalize: async () => {
|
|
13419
|
+
try {
|
|
13420
|
+
await this.setPendingTxFlag(false);
|
|
13421
|
+
} catch (error) {
|
|
13422
|
+
console.error("Failed to clear pending tx flag:", error);
|
|
13423
|
+
}
|
|
12889
13424
|
}
|
|
12890
|
-
|
|
12891
|
-
const serverSigned = Transaction$2.fromPSBT(base64.decode(c));
|
|
12892
|
-
combineTapscriptSigs(userSignedCheckpoints[i], serverSigned);
|
|
12893
|
-
return base64.encode(serverSigned.toPSBT());
|
|
12894
|
-
});
|
|
12895
|
-
} else {
|
|
12896
|
-
finalCheckpoints = await Promise.all(
|
|
12897
|
-
signedCheckpointTxs.map(async (c) => {
|
|
12898
|
-
const tx = Transaction$2.fromPSBT(base64.decode(c));
|
|
12899
|
-
const signedCheckpoint = await this._signerRouter.sign(
|
|
12900
|
-
tx,
|
|
12901
|
-
this.inputSigningJobsFromWitnessUtxos(tx)
|
|
12902
|
-
);
|
|
12903
|
-
return base64.encode(signedCheckpoint.toPSBT());
|
|
12904
|
-
})
|
|
12905
|
-
);
|
|
12906
|
-
}
|
|
12907
|
-
await this.arkProvider.finalizeTx(arkTxid, finalCheckpoints);
|
|
12908
|
-
try {
|
|
12909
|
-
await this.setPendingTxFlag(false);
|
|
12910
|
-
} catch (error) {
|
|
12911
|
-
console.error("Failed to clear pending tx flag:", error);
|
|
12912
|
-
}
|
|
12913
|
-
return { arkTxid, signedCheckpointTxs };
|
|
13425
|
+
});
|
|
12914
13426
|
}
|
|
12915
13427
|
// mark virtual outputs as spent, save change outputs if any.
|
|
12916
13428
|
// `offchainTapscript` and `serverPubKey` are the epoch snapshot the
|
|
@@ -17316,7 +17828,7 @@ function verifyP2WPKH(message, witnessItems, pkScript, addressHash) {
|
|
|
17316
17828
|
return false;
|
|
17317
17829
|
}
|
|
17318
17830
|
const derived = p2wpkh(pubkey);
|
|
17319
|
-
if (!equalBytes(derived.hash, addressHash)) {
|
|
17831
|
+
if (!equalBytes$1(derived.hash, addressHash)) {
|
|
17320
17832
|
return false;
|
|
17321
17833
|
}
|
|
17322
17834
|
const sighashType = sigWithHash[sigWithHash.length - 1];
|
|
@@ -17346,7 +17858,7 @@ function verifyLegacy(message, sigBytes, addressHash) {
|
|
|
17346
17858
|
const sig = secp256k1.Signature.fromBytes(compactSig, "compact").addRecoveryBit(recoveryId);
|
|
17347
17859
|
const point = sig.recoverPublicKey(msgHash);
|
|
17348
17860
|
const pubkeyBytes = point.toBytes(compressed);
|
|
17349
|
-
return equalBytes(hash160(pubkeyBytes), addressHash);
|
|
17861
|
+
return equalBytes$1(hash160(pubkeyBytes), addressHash);
|
|
17350
17862
|
} catch {
|
|
17351
17863
|
return false;
|
|
17352
17864
|
}
|
|
@@ -17409,6 +17921,132 @@ function craftBIP322ToSignSimple(toSpend, pkScript) {
|
|
|
17409
17921
|
});
|
|
17410
17922
|
return tx;
|
|
17411
17923
|
}
|
|
17924
|
+
|
|
17925
|
+
// src/providers/emulator.ts
|
|
17926
|
+
var RestEmulatorProvider = class {
|
|
17927
|
+
constructor(serverUrl) {
|
|
17928
|
+
this.serverUrl = serverUrl;
|
|
17929
|
+
}
|
|
17930
|
+
serverUrl;
|
|
17931
|
+
async getInfo() {
|
|
17932
|
+
const url = `${this.serverUrl}/v1/info`;
|
|
17933
|
+
const response = await fetch(url);
|
|
17934
|
+
if (!response.ok) {
|
|
17935
|
+
const errorText2 = await response.text();
|
|
17936
|
+
throw new Error(`Failed to get emulator info: ${errorText2}`);
|
|
17937
|
+
}
|
|
17938
|
+
const data = await response.json();
|
|
17939
|
+
const signerPubkey = data.signerPubkey;
|
|
17940
|
+
if (typeof signerPubkey !== "string" || !signerPubkey) {
|
|
17941
|
+
throw new Error("Invalid emulator info response: missing signerPubkey");
|
|
17942
|
+
}
|
|
17943
|
+
return { signerPubkey };
|
|
17944
|
+
}
|
|
17945
|
+
async submitTx(arkTx, checkpointTxs) {
|
|
17946
|
+
const url = `${this.serverUrl}/v1/tx`;
|
|
17947
|
+
const response = await fetch(url, {
|
|
17948
|
+
method: "POST",
|
|
17949
|
+
headers: { "Content-Type": "application/json" },
|
|
17950
|
+
body: JSON.stringify({
|
|
17951
|
+
arkTx,
|
|
17952
|
+
checkpointTxs
|
|
17953
|
+
})
|
|
17954
|
+
});
|
|
17955
|
+
if (!response.ok) {
|
|
17956
|
+
const errorText2 = await response.text();
|
|
17957
|
+
throw new Error(`Failed to submit tx to emulator: ${errorText2}`);
|
|
17958
|
+
}
|
|
17959
|
+
const data = await response.json();
|
|
17960
|
+
if (typeof data.signedArkTx !== "string" || !data.signedArkTx) {
|
|
17961
|
+
throw new Error("Invalid emulator submitTx response: missing signedArkTx");
|
|
17962
|
+
}
|
|
17963
|
+
if (!Array.isArray(data.signedCheckpointTxs) || !data.signedCheckpointTxs.every((item) => typeof item === "string")) {
|
|
17964
|
+
throw new Error(
|
|
17965
|
+
"Invalid emulator submitTx response: signedCheckpointTxs must be an array of strings"
|
|
17966
|
+
);
|
|
17967
|
+
}
|
|
17968
|
+
return {
|
|
17969
|
+
signedArkTx: data.signedArkTx,
|
|
17970
|
+
signedCheckpointTxs: data.signedCheckpointTxs
|
|
17971
|
+
};
|
|
17972
|
+
}
|
|
17973
|
+
async submitIntent(intent) {
|
|
17974
|
+
const url = `${this.serverUrl}/v1/intent`;
|
|
17975
|
+
const response = await fetch(url, {
|
|
17976
|
+
method: "POST",
|
|
17977
|
+
headers: { "Content-Type": "application/json" },
|
|
17978
|
+
body: JSON.stringify({
|
|
17979
|
+
intent: {
|
|
17980
|
+
proof: intent.proof,
|
|
17981
|
+
message: JSON.stringify(intent.message)
|
|
17982
|
+
}
|
|
17983
|
+
})
|
|
17984
|
+
});
|
|
17985
|
+
if (!response.ok) {
|
|
17986
|
+
const errorText2 = await response.text();
|
|
17987
|
+
throw new Error(`Failed to submit intent to emulator: ${errorText2}`);
|
|
17988
|
+
}
|
|
17989
|
+
const data = await response.json();
|
|
17990
|
+
if (typeof data.signedProof !== "string" || !data.signedProof) {
|
|
17991
|
+
throw new Error("Invalid emulator submitIntent response: missing signedProof");
|
|
17992
|
+
}
|
|
17993
|
+
return data.signedProof;
|
|
17994
|
+
}
|
|
17995
|
+
async submitFinalization(intent, forfeits, connectorTree, commitmentTx) {
|
|
17996
|
+
const url = `${this.serverUrl}/v1/finalization`;
|
|
17997
|
+
const response = await fetch(url, {
|
|
17998
|
+
method: "POST",
|
|
17999
|
+
headers: { "Content-Type": "application/json" },
|
|
18000
|
+
// Uses "signedIntent" (not "intent") because the proof was already
|
|
18001
|
+
// co-signed by the emulator via submitIntent in a prior step.
|
|
18002
|
+
body: JSON.stringify({
|
|
18003
|
+
signedIntent: {
|
|
18004
|
+
proof: intent.proof,
|
|
18005
|
+
message: JSON.stringify(intent.message)
|
|
18006
|
+
},
|
|
18007
|
+
forfeits,
|
|
18008
|
+
connectorTree,
|
|
18009
|
+
commitmentTx
|
|
18010
|
+
})
|
|
18011
|
+
});
|
|
18012
|
+
if (!response.ok) {
|
|
18013
|
+
const errorText2 = await response.text();
|
|
18014
|
+
throw new Error(`Failed to submit finalization to emulator: ${errorText2}`);
|
|
18015
|
+
}
|
|
18016
|
+
const data = await response.json();
|
|
18017
|
+
if (!Array.isArray(data.signedForfeits) || !data.signedForfeits.every((item) => typeof item === "string")) {
|
|
18018
|
+
throw new Error(
|
|
18019
|
+
"Invalid emulator submitFinalization response: signedForfeits must be an array of strings"
|
|
18020
|
+
);
|
|
18021
|
+
}
|
|
18022
|
+
if ("signedCommitmentTx" in data && typeof data.signedCommitmentTx !== "string") {
|
|
18023
|
+
throw new Error(
|
|
18024
|
+
"Invalid emulator submitFinalization response: invalid signedCommitmentTx"
|
|
18025
|
+
);
|
|
18026
|
+
}
|
|
18027
|
+
return {
|
|
18028
|
+
signedForfeits: data.signedForfeits,
|
|
18029
|
+
signedCommitmentTx: data.signedCommitmentTx
|
|
18030
|
+
};
|
|
18031
|
+
}
|
|
18032
|
+
async submitOnchainTx(tx) {
|
|
18033
|
+
const url = `${this.serverUrl}/v1/onchain-tx`;
|
|
18034
|
+
const response = await fetch(url, {
|
|
18035
|
+
method: "POST",
|
|
18036
|
+
headers: { "Content-Type": "application/json" },
|
|
18037
|
+
body: JSON.stringify({ tx })
|
|
18038
|
+
});
|
|
18039
|
+
if (!response.ok) {
|
|
18040
|
+
const errorText2 = await response.text();
|
|
18041
|
+
throw new Error(`Failed to submit onchain tx to emulator: ${errorText2}`);
|
|
18042
|
+
}
|
|
18043
|
+
const data = await response.json();
|
|
18044
|
+
if (typeof data.signedTx !== "string" || !data.signedTx) {
|
|
18045
|
+
throw new Error("Invalid emulator submitOnchainTx response: missing signedTx");
|
|
18046
|
+
}
|
|
18047
|
+
return { signedTx: data.signedTx };
|
|
18048
|
+
}
|
|
18049
|
+
};
|
|
17412
18050
|
function finalizeVirtualTx(type, psbtBase64) {
|
|
17413
18051
|
const tx = Transaction.fromPSBT(base64.decode(psbtBase64));
|
|
17414
18052
|
if (type === "INDEXER_CHAINED_TX_TYPE_TREE" /* TREE */) {
|
|
@@ -18441,6 +19079,656 @@ var UnilateralExit = {
|
|
|
18441
19079
|
prepare,
|
|
18442
19080
|
Executor
|
|
18443
19081
|
};
|
|
19082
|
+
|
|
19083
|
+
// src/arkade/index.ts
|
|
19084
|
+
var arkade_exports = {};
|
|
19085
|
+
__export(arkade_exports, {
|
|
19086
|
+
ARKADE_OP: () => ARKADE_OP,
|
|
19087
|
+
ARKADE_OPCODES: () => ARKADE_OPCODES,
|
|
19088
|
+
ARKADE_OPCODE_NAMES: () => ARKADE_OPCODE_NAMES,
|
|
19089
|
+
ARKADE_OPCODE_VALUES: () => ARKADE_OPCODE_VALUES,
|
|
19090
|
+
ARKADE_OPS: () => ARKADE_OPS,
|
|
19091
|
+
Arkade: () => Arkade,
|
|
19092
|
+
ArkadeContract: () => ArkadeContract,
|
|
19093
|
+
ArkadeProgramScript: () => ArkadeProgramScript,
|
|
19094
|
+
ArkadeScript: () => ArkadeScript,
|
|
19095
|
+
ArkadeTransactionBuilder: () => ArkadeTransactionBuilder,
|
|
19096
|
+
BigNum: () => bignum_exports,
|
|
19097
|
+
OP: () => OP,
|
|
19098
|
+
OPCODE_NAMES: () => OPCODE_NAMES,
|
|
19099
|
+
OPCODE_VALUES: () => OPCODE_VALUES,
|
|
19100
|
+
SUPPORTED_PROGRAM_VERSION: () => SUPPORTED_PROGRAM_VERSION,
|
|
19101
|
+
Script: () => Script,
|
|
19102
|
+
arkadeScriptHash: () => arkadeScriptHash,
|
|
19103
|
+
arkadeWitnessHash: () => arkadeWitnessHash,
|
|
19104
|
+
asmToBytes: () => asmToBytes,
|
|
19105
|
+
bytesToASM: () => bytesToASM,
|
|
19106
|
+
computeArkadeScriptPublicKey: () => computeArkadeScriptPublicKey,
|
|
19107
|
+
createArkadeBatchHandler: () => createArkadeBatchHandler,
|
|
19108
|
+
deserializeArkadeContractParams: () => deserializeArkadeContractParams,
|
|
19109
|
+
fromASM: () => fromASM,
|
|
19110
|
+
getOpcodeName: () => getOpcodeName,
|
|
19111
|
+
getOpcodeValue: () => getOpcodeValue,
|
|
19112
|
+
parseArtifact: () => parseArtifact,
|
|
19113
|
+
resolveAsm: () => resolveAsm,
|
|
19114
|
+
serializeArkadeContractParams: () => serializeArkadeContractParams,
|
|
19115
|
+
stringifyArtifact: () => stringifyArtifact,
|
|
19116
|
+
toASM: () => toASM,
|
|
19117
|
+
validateProgram: () => validateProgram,
|
|
19118
|
+
witnessRefToBytes: () => witnessRefToBytes
|
|
19119
|
+
});
|
|
19120
|
+
var isVtxoCoin = (input) => "virtualStatus" in input;
|
|
19121
|
+
function createArkadeBatchHandler(intentId, inputs, signer, signedProof, message, session, arkProvider, emulator, network, recipients) {
|
|
19122
|
+
let batchId;
|
|
19123
|
+
let sweepTapTreeRoot;
|
|
19124
|
+
return {
|
|
19125
|
+
onBatchStarted: async (event) => {
|
|
19126
|
+
const utf8IntentId = new TextEncoder().encode(intentId);
|
|
19127
|
+
const intentIdHash = sha256(utf8IntentId);
|
|
19128
|
+
const intentIdHashStr = hex.encode(intentIdHash);
|
|
19129
|
+
if (!event.intentIdHashes.includes(intentIdHashStr)) return { skip: true };
|
|
19130
|
+
await arkProvider.confirmRegistration(intentId);
|
|
19131
|
+
batchId = event.id;
|
|
19132
|
+
const sweepTapscript = CSVMultisigTapscript.encode({
|
|
19133
|
+
timelock: {
|
|
19134
|
+
value: event.batchExpiry,
|
|
19135
|
+
// BIP-65: values >= 512 are interpreted as seconds, below as blocks
|
|
19136
|
+
type: event.batchExpiry >= 512n ? "seconds" : "blocks"
|
|
19137
|
+
},
|
|
19138
|
+
pubkeys: [hex.decode((await arkProvider.getInfo()).forfeitPubkey).subarray(1)]
|
|
19139
|
+
}).script;
|
|
19140
|
+
sweepTapTreeRoot = tapLeafHash(sweepTapscript);
|
|
19141
|
+
return { skip: false };
|
|
19142
|
+
},
|
|
19143
|
+
onTreeSigningStarted: async (event, vtxoTree) => {
|
|
19144
|
+
const signerPubKey = await session.getPublicKey();
|
|
19145
|
+
const xonlySignerPubKey = signerPubKey.subarray(1);
|
|
19146
|
+
const xOnlyPubkeys = event.cosignersPublicKeys.map((k) => k.slice(2));
|
|
19147
|
+
if (!xOnlyPubkeys.includes(hex.encode(xonlySignerPubKey))) {
|
|
19148
|
+
return { skip: true };
|
|
19149
|
+
}
|
|
19150
|
+
const commitmentTx = Transaction.fromPSBT(base64.decode(event.unsignedCommitmentTx));
|
|
19151
|
+
validateVtxoTxGraph(vtxoTree, commitmentTx, sweepTapTreeRoot);
|
|
19152
|
+
if (recipients && recipients.length > 0) {
|
|
19153
|
+
validateBatchRecipients(commitmentTx, vtxoTree.leaves(), recipients, network);
|
|
19154
|
+
}
|
|
19155
|
+
const sharedOutput = commitmentTx.getOutput(0);
|
|
19156
|
+
if (!sharedOutput?.amount) {
|
|
19157
|
+
throw new Error("Shared output not found");
|
|
19158
|
+
}
|
|
19159
|
+
await session.init(vtxoTree, sweepTapTreeRoot, sharedOutput.amount);
|
|
19160
|
+
const pubkey = hex.encode(await session.getPublicKey());
|
|
19161
|
+
const nonces = await session.getNonces();
|
|
19162
|
+
await arkProvider.submitTreeNonces(batchId, pubkey, nonces);
|
|
19163
|
+
return { skip: false };
|
|
19164
|
+
},
|
|
19165
|
+
onTreeNonces: async (event) => {
|
|
19166
|
+
const { hasAllNonces } = await session.aggregatedNonces(event.txid, event.nonces);
|
|
19167
|
+
if (!hasAllNonces) return { fullySigned: false };
|
|
19168
|
+
const signatures = await session.sign();
|
|
19169
|
+
const pubkey = hex.encode(await session.getPublicKey());
|
|
19170
|
+
await arkProvider.submitTreeSignatures(batchId, pubkey, signatures);
|
|
19171
|
+
return { fullySigned: true };
|
|
19172
|
+
},
|
|
19173
|
+
onBatchFinalization: async (event, _vtxoTree, connectorTree) => {
|
|
19174
|
+
const info = await arkProvider.getInfo();
|
|
19175
|
+
const forfeitOutputScript = OutScript.encode(
|
|
19176
|
+
Address(network).decode(info.forfeitAddress)
|
|
19177
|
+
);
|
|
19178
|
+
if (connectorTree) {
|
|
19179
|
+
validateConnectorsTxGraph(event.commitmentTx, connectorTree);
|
|
19180
|
+
}
|
|
19181
|
+
let commitmentPsbt = Transaction.fromPSBT(base64.decode(event.commitmentTx));
|
|
19182
|
+
const signedForfeits = [];
|
|
19183
|
+
let connectorIndex = 0;
|
|
19184
|
+
const connectorLeaves = connectorTree?.leaves() || [];
|
|
19185
|
+
const boardingIndices = [];
|
|
19186
|
+
for (const input of inputs) {
|
|
19187
|
+
if (!isVtxoCoin(input)) {
|
|
19188
|
+
let boardingIdx = null;
|
|
19189
|
+
for (let i = 0; i < commitmentPsbt.inputsLength; i++) {
|
|
19190
|
+
const psbtInput = commitmentPsbt.getInput(i);
|
|
19191
|
+
if (!psbtInput.txid) continue;
|
|
19192
|
+
if (hex.encode(psbtInput.txid) === input.txid && psbtInput.index === input.vout) {
|
|
19193
|
+
boardingIdx = i;
|
|
19194
|
+
break;
|
|
19195
|
+
}
|
|
19196
|
+
}
|
|
19197
|
+
if (boardingIdx === null) continue;
|
|
19198
|
+
commitmentPsbt.updateInput(boardingIdx, {
|
|
19199
|
+
tapLeafScript: [input.forfeitTapLeafScript]
|
|
19200
|
+
});
|
|
19201
|
+
boardingIndices.push(boardingIdx);
|
|
19202
|
+
continue;
|
|
19203
|
+
}
|
|
19204
|
+
if (isRecoverable(input) || isSubdust(input, info.dust)) {
|
|
19205
|
+
continue;
|
|
19206
|
+
}
|
|
19207
|
+
if (connectorIndex >= connectorLeaves.length) {
|
|
19208
|
+
throw new Error("not enough connectors received");
|
|
19209
|
+
}
|
|
19210
|
+
const connectorLeaf = connectorLeaves[connectorIndex++];
|
|
19211
|
+
const connectorTxId = connectorLeaf.id;
|
|
19212
|
+
const connectorOutput = connectorLeaf.getOutput(0);
|
|
19213
|
+
if (!connectorOutput?.amount || !connectorOutput?.script) {
|
|
19214
|
+
throw new Error(
|
|
19215
|
+
`Invalid connector output at index ${connectorIndex - 1}: missing amount or script`
|
|
19216
|
+
);
|
|
19217
|
+
}
|
|
19218
|
+
let forfeitTx = buildForfeitTx(
|
|
19219
|
+
[
|
|
19220
|
+
{
|
|
19221
|
+
txid: input.txid,
|
|
19222
|
+
index: input.vout,
|
|
19223
|
+
witnessUtxo: {
|
|
19224
|
+
amount: BigInt(input.value),
|
|
19225
|
+
script: VtxoScript.decode(input.tapTree).pkScript
|
|
19226
|
+
},
|
|
19227
|
+
sighashType: SigHash.DEFAULT,
|
|
19228
|
+
tapLeafScript: [input.forfeitTapLeafScript]
|
|
19229
|
+
},
|
|
19230
|
+
{
|
|
19231
|
+
txid: connectorTxId,
|
|
19232
|
+
index: 0,
|
|
19233
|
+
witnessUtxo: {
|
|
19234
|
+
amount: connectorOutput.amount,
|
|
19235
|
+
script: connectorOutput.script
|
|
19236
|
+
}
|
|
19237
|
+
}
|
|
19238
|
+
],
|
|
19239
|
+
forfeitOutputScript
|
|
19240
|
+
);
|
|
19241
|
+
forfeitTx = await signer.sign(forfeitTx, [0]);
|
|
19242
|
+
signedForfeits.push(base64.encode(forfeitTx.toPSBT()));
|
|
19243
|
+
}
|
|
19244
|
+
if (boardingIndices.length > 0) {
|
|
19245
|
+
commitmentPsbt = await signer.sign(commitmentPsbt, boardingIndices);
|
|
19246
|
+
}
|
|
19247
|
+
let connectorTreeNodes = null;
|
|
19248
|
+
if (connectorTree) {
|
|
19249
|
+
connectorTreeNodes = [];
|
|
19250
|
+
for (const subtree of connectorTree.iterator()) {
|
|
19251
|
+
const children = {};
|
|
19252
|
+
for (const [outputIndex, child] of subtree.children) {
|
|
19253
|
+
children[String(outputIndex)] = child.txid;
|
|
19254
|
+
}
|
|
19255
|
+
connectorTreeNodes.push({
|
|
19256
|
+
txid: subtree.txid,
|
|
19257
|
+
tx: base64.encode(subtree.root.toPSBT()),
|
|
19258
|
+
children
|
|
19259
|
+
});
|
|
19260
|
+
}
|
|
19261
|
+
}
|
|
19262
|
+
const hasBoardingInputs = boardingIndices.length > 0;
|
|
19263
|
+
const commitmentB64 = hasBoardingInputs ? base64.encode(commitmentPsbt.toPSBT()) : event.commitmentTx;
|
|
19264
|
+
const emuResult = await emulator.submitFinalization(
|
|
19265
|
+
{ proof: signedProof, message },
|
|
19266
|
+
signedForfeits,
|
|
19267
|
+
connectorTreeNodes,
|
|
19268
|
+
commitmentB64
|
|
19269
|
+
);
|
|
19270
|
+
await arkProvider.submitSignedForfeitTxs(
|
|
19271
|
+
emuResult.signedForfeits,
|
|
19272
|
+
emuResult.signedCommitmentTx || (hasBoardingInputs ? commitmentB64 : void 0)
|
|
19273
|
+
);
|
|
19274
|
+
}
|
|
19275
|
+
};
|
|
19276
|
+
}
|
|
19277
|
+
var Arkade = class _Arkade {
|
|
19278
|
+
arkProvider;
|
|
19279
|
+
/** The co-signing service, or undefined for emulator-less (pure tapscript) usage. */
|
|
19280
|
+
emulator;
|
|
19281
|
+
network;
|
|
19282
|
+
serverKey;
|
|
19283
|
+
/** The co-signer's x-only key, present only when an emulator is configured. */
|
|
19284
|
+
emulatorKey;
|
|
19285
|
+
checkpoint;
|
|
19286
|
+
indexer;
|
|
19287
|
+
identity;
|
|
19288
|
+
/** The signing identity's x-only public key, resolved at connect — identifies which inputs the wallet signs. */
|
|
19289
|
+
userKey;
|
|
19290
|
+
/** The wallet's contract manager, when contract persistence is wired up. */
|
|
19291
|
+
contractManager;
|
|
19292
|
+
constructor(fields) {
|
|
19293
|
+
this.arkProvider = fields.arkProvider;
|
|
19294
|
+
this.emulator = fields.emulator;
|
|
19295
|
+
this.network = fields.network;
|
|
19296
|
+
this.serverKey = fields.serverKey;
|
|
19297
|
+
this.emulatorKey = fields.emulatorKey;
|
|
19298
|
+
this.checkpoint = fields.checkpoint;
|
|
19299
|
+
this.indexer = fields.indexer;
|
|
19300
|
+
this.identity = fields.identity;
|
|
19301
|
+
this.userKey = fields.userKey;
|
|
19302
|
+
this.contractManager = fields.contractManager;
|
|
19303
|
+
}
|
|
19304
|
+
/** Connect and resolve the server key, checkpoint closure and (if present) the co-signer key. */
|
|
19305
|
+
static async connect(opts) {
|
|
19306
|
+
const info = await opts.arkade.getInfo();
|
|
19307
|
+
const serverKey = hex.decode(info.signerPubkey).slice(1);
|
|
19308
|
+
const checkpoint = CSVMultisigTapscript.decode(hex.decode(info.checkpointTapscript));
|
|
19309
|
+
let emulatorKey;
|
|
19310
|
+
if (opts.emulator) {
|
|
19311
|
+
const eInfo = await opts.emulator.getInfo();
|
|
19312
|
+
emulatorKey = hex.decode(eInfo.signerPubkey);
|
|
19313
|
+
}
|
|
19314
|
+
let userKey;
|
|
19315
|
+
if (opts.identity) {
|
|
19316
|
+
const pub = await opts.identity.xOnlyPublicKey();
|
|
19317
|
+
userKey = pub.length === 33 ? pub.slice(1) : pub;
|
|
19318
|
+
}
|
|
19319
|
+
return new _Arkade({
|
|
19320
|
+
arkProvider: opts.arkade,
|
|
19321
|
+
emulator: opts.emulator,
|
|
19322
|
+
network: opts.network ?? DEFAULT_NETWORK,
|
|
19323
|
+
serverKey,
|
|
19324
|
+
emulatorKey,
|
|
19325
|
+
checkpoint,
|
|
19326
|
+
indexer: opts.indexer,
|
|
19327
|
+
identity: opts.identity,
|
|
19328
|
+
userKey,
|
|
19329
|
+
contractManager: opts.contractManager
|
|
19330
|
+
});
|
|
19331
|
+
}
|
|
19332
|
+
/**
|
|
19333
|
+
* Instantiate a contract from a program and its constructor arguments. The
|
|
19334
|
+
* `program`'s literal type is preserved (`const` inference) so the resulting
|
|
19335
|
+
* contract's `functions` map is strongly typed — `functions.<name>(...)`
|
|
19336
|
+
* knows each argument's type from the function's `inputs` descriptors.
|
|
19337
|
+
*
|
|
19338
|
+
* When the program declares a `server` or `user` param and the caller does
|
|
19339
|
+
* not bind it, it defaults to the client's server key or the identity's
|
|
19340
|
+
* key respectively; explicit args always win.
|
|
19341
|
+
*/
|
|
19342
|
+
contract(program, args = {}) {
|
|
19343
|
+
const declared = (program.params ?? []).map(inputName);
|
|
19344
|
+
if (declared.includes("server") && args.server === void 0) {
|
|
19345
|
+
args = { ...args, server: this.serverKey };
|
|
19346
|
+
}
|
|
19347
|
+
if (declared.includes("user") && args.user === void 0 && this.userKey) {
|
|
19348
|
+
args = { ...args, user: this.userKey };
|
|
19349
|
+
}
|
|
19350
|
+
return new ArkadeContract(this, program, args);
|
|
19351
|
+
}
|
|
19352
|
+
};
|
|
19353
|
+
var ArkadeContract = class _ArkadeContract {
|
|
19354
|
+
constructor(client, program, args = {}, keys) {
|
|
19355
|
+
this.client = client;
|
|
19356
|
+
this.program = program;
|
|
19357
|
+
this.args = args;
|
|
19358
|
+
this.keys = keys ?? {
|
|
19359
|
+
serverKey: client.serverKey,
|
|
19360
|
+
userKey: client.userKey,
|
|
19361
|
+
emulatorKey: client.emulatorKey
|
|
19362
|
+
};
|
|
19363
|
+
this.vtxoScript = new ArkadeProgramScript(program, args, this.keys);
|
|
19364
|
+
this.tapTree = this.vtxoScript.encode();
|
|
19365
|
+
this.compiled = this.vtxoScript.compiled;
|
|
19366
|
+
}
|
|
19367
|
+
client;
|
|
19368
|
+
program;
|
|
19369
|
+
args;
|
|
19370
|
+
/** The compiled taproot tree of spending-path leaves. */
|
|
19371
|
+
vtxoScript;
|
|
19372
|
+
/** Encoded taproot tree (shared spend context for every path). */
|
|
19373
|
+
tapTree;
|
|
19374
|
+
/** The signer keys the program was compiled against. */
|
|
19375
|
+
keys;
|
|
19376
|
+
/** Spending paths in declaration order. */
|
|
19377
|
+
compiled;
|
|
19378
|
+
/**
|
|
19379
|
+
* Rebuild a callable contract from a persisted `"arkade"` contract row
|
|
19380
|
+
* (see {@link ArkadeContract.register}). The stored keys are used for
|
|
19381
|
+
* compilation — not the client's current ones — so the derived script and
|
|
19382
|
+
* address stay identical to the registered contract even after a server
|
|
19383
|
+
* signer rotation.
|
|
19384
|
+
*/
|
|
19385
|
+
static fromContract(client, contract) {
|
|
19386
|
+
if (contract.type !== "arkade") {
|
|
19387
|
+
throw new Error(
|
|
19388
|
+
`ArkadeContract.fromContract: expected contract type 'arkade', got '${contract.type}'`
|
|
19389
|
+
);
|
|
19390
|
+
}
|
|
19391
|
+
const typed = deserializeArkadeContractParams(contract.params);
|
|
19392
|
+
return new _ArkadeContract(client, typed.program, typed.args, {
|
|
19393
|
+
serverKey: typed.serverKey,
|
|
19394
|
+
userKey: typed.userKey,
|
|
19395
|
+
emulatorKey: typed.emulatorKey
|
|
19396
|
+
});
|
|
19397
|
+
}
|
|
19398
|
+
/** Resolve the {@link TapLeafScript} for a spending path by its index. */
|
|
19399
|
+
leafScript(index) {
|
|
19400
|
+
const fn = this.compiled[index];
|
|
19401
|
+
if (!fn) throw new Error(`leaf index ${index} out of range`);
|
|
19402
|
+
return fn.tapLeafScript;
|
|
19403
|
+
}
|
|
19404
|
+
/** Arkade funding address. */
|
|
19405
|
+
get address() {
|
|
19406
|
+
return this.vtxoScript.address(this.client.network.hrp, this.keys.serverKey).encode();
|
|
19407
|
+
}
|
|
19408
|
+
/** Taproot output script. */
|
|
19409
|
+
get pkScript() {
|
|
19410
|
+
return this.vtxoScript.pkScript;
|
|
19411
|
+
}
|
|
19412
|
+
/**
|
|
19413
|
+
* Callable spending paths: `contract.functions.<name>(...args)`. Strongly
|
|
19414
|
+
* typed from the program's literal type — each function's argument types are
|
|
19415
|
+
* derived from its `inputs` descriptors (see {@link ContractFunctions}).
|
|
19416
|
+
*/
|
|
19417
|
+
get functions() {
|
|
19418
|
+
const out = {};
|
|
19419
|
+
for (const fn of this.compiled) {
|
|
19420
|
+
out[fn.name] = (...callArgs) => new ArkadeTransactionBuilder(this, fn, bindInputs(fn, callArgs));
|
|
19421
|
+
}
|
|
19422
|
+
return out;
|
|
19423
|
+
}
|
|
19424
|
+
/**
|
|
19425
|
+
* The `createContract` payload for this contract — the serialized program,
|
|
19426
|
+
* args and keys plus the derived script/address. Useful when registering
|
|
19427
|
+
* through a manager the client does not hold.
|
|
19428
|
+
*/
|
|
19429
|
+
toContractParams() {
|
|
19430
|
+
return {
|
|
19431
|
+
type: "arkade",
|
|
19432
|
+
params: serializeArkadeContractParams({
|
|
19433
|
+
program: this.program,
|
|
19434
|
+
args: this.args,
|
|
19435
|
+
serverKey: this.keys.serverKey,
|
|
19436
|
+
userKey: this.keys.userKey,
|
|
19437
|
+
emulatorKey: this.keys.emulatorKey
|
|
19438
|
+
}),
|
|
19439
|
+
script: hex.encode(this.pkScript),
|
|
19440
|
+
address: this.address
|
|
19441
|
+
};
|
|
19442
|
+
}
|
|
19443
|
+
/**
|
|
19444
|
+
* Persist this contract through the wallet's {@link IContractManager} so it
|
|
19445
|
+
* is tracked like any other contract type: stored in the contract
|
|
19446
|
+
* repository, watched for VTXO events, counted in repository-backed
|
|
19447
|
+
* balances, and re-derivable offline via the `"arkade"` contract handler.
|
|
19448
|
+
* Idempotent — re-registering the same script is a no-op.
|
|
19449
|
+
*/
|
|
19450
|
+
async register(options) {
|
|
19451
|
+
const manager = this.client.contractManager;
|
|
19452
|
+
if (!manager) {
|
|
19453
|
+
throw new Error(
|
|
19454
|
+
"ArkadeContract.register requires a `contractManager` on the Arkade client \u2014 pass one to Arkade.connect"
|
|
19455
|
+
);
|
|
19456
|
+
}
|
|
19457
|
+
return manager.createContract({
|
|
19458
|
+
...this.toContractParams(),
|
|
19459
|
+
label: options?.label,
|
|
19460
|
+
metadata: options?.metadata
|
|
19461
|
+
});
|
|
19462
|
+
}
|
|
19463
|
+
/**
|
|
19464
|
+
* Spendable VTXOs locked by this contract.
|
|
19465
|
+
*
|
|
19466
|
+
* When a `contractManager` is configured and this contract is registered,
|
|
19467
|
+
* reads the repository-backed state (offline-first, kept fresh by the
|
|
19468
|
+
* contract watcher). Otherwise falls back to a direct indexer query.
|
|
19469
|
+
*/
|
|
19470
|
+
async getUtxos() {
|
|
19471
|
+
const manager = this.client.contractManager;
|
|
19472
|
+
const scriptHex = hex.encode(this.pkScript);
|
|
19473
|
+
if (manager) {
|
|
19474
|
+
const [registered] = await manager.getContracts({ script: scriptHex });
|
|
19475
|
+
if (registered) {
|
|
19476
|
+
const [withVtxos] = await manager.getContractsWithVtxos({ script: scriptHex });
|
|
19477
|
+
return (withVtxos?.vtxos ?? []).filter(isSpendable);
|
|
19478
|
+
}
|
|
19479
|
+
}
|
|
19480
|
+
if (!this.client.indexer) {
|
|
19481
|
+
throw new Error("ArkadeContract.getUtxos: an indexer is required");
|
|
19482
|
+
}
|
|
19483
|
+
const { vtxos } = await this.client.indexer.getVtxos({
|
|
19484
|
+
scripts: [scriptHex],
|
|
19485
|
+
spendableOnly: true
|
|
19486
|
+
});
|
|
19487
|
+
return vtxos;
|
|
19488
|
+
}
|
|
19489
|
+
/** Total spendable balance (requires an indexer). */
|
|
19490
|
+
async getBalance() {
|
|
19491
|
+
const utxos = await this.getUtxos();
|
|
19492
|
+
return utxos.reduce((sum, u) => sum + BigInt(u.value), 0n);
|
|
19493
|
+
}
|
|
19494
|
+
};
|
|
19495
|
+
var ArkadeTransactionBuilder = class {
|
|
19496
|
+
/** @internal */
|
|
19497
|
+
constructor(contract, fn, args) {
|
|
19498
|
+
this.contract = contract;
|
|
19499
|
+
this.fn = fn;
|
|
19500
|
+
this.args = args;
|
|
19501
|
+
}
|
|
19502
|
+
contract;
|
|
19503
|
+
fn;
|
|
19504
|
+
args;
|
|
19505
|
+
outputs = [];
|
|
19506
|
+
fundingCoins = [];
|
|
19507
|
+
assetSpecs = [];
|
|
19508
|
+
coin;
|
|
19509
|
+
changeScript;
|
|
19510
|
+
from(coin) {
|
|
19511
|
+
this.coin = coin;
|
|
19512
|
+
return this;
|
|
19513
|
+
}
|
|
19514
|
+
/**
|
|
19515
|
+
* Add extra inputs the caller funds (e.g. a taker's own coins in a swap).
|
|
19516
|
+
* These become inputs 1..n and are signed with the client identity.
|
|
19517
|
+
*/
|
|
19518
|
+
fund(coins) {
|
|
19519
|
+
this.fundingCoins.push(...coins);
|
|
19520
|
+
return this;
|
|
19521
|
+
}
|
|
19522
|
+
/** Transfer an asset group: which inputs supply it and which outputs receive it. */
|
|
19523
|
+
withAsset(spec) {
|
|
19524
|
+
this.assetSpecs.push(spec);
|
|
19525
|
+
return this;
|
|
19526
|
+
}
|
|
19527
|
+
/** Destination for any surplus (inputs − outputs). Required when the spend is not exact. */
|
|
19528
|
+
change(script) {
|
|
19529
|
+
this.changeScript = script;
|
|
19530
|
+
return this;
|
|
19531
|
+
}
|
|
19532
|
+
to(scriptOrOutputs, amount) {
|
|
19533
|
+
if (Array.isArray(scriptOrOutputs)) {
|
|
19534
|
+
for (const [i, out] of scriptOrOutputs.entries()) {
|
|
19535
|
+
if (out.amount === void 0) {
|
|
19536
|
+
throw new Error(`to(outputs): output ${i} is missing an amount`);
|
|
19537
|
+
}
|
|
19538
|
+
}
|
|
19539
|
+
this.outputs.push(...scriptOrOutputs);
|
|
19540
|
+
} else {
|
|
19541
|
+
if (amount === void 0) throw new Error("to(script, amount): amount is required");
|
|
19542
|
+
this.outputs.push({ script: scriptOrOutputs, amount });
|
|
19543
|
+
}
|
|
19544
|
+
return this;
|
|
19545
|
+
}
|
|
19546
|
+
/** Assemble the unsigned ark transaction and its checkpoints. */
|
|
19547
|
+
async build() {
|
|
19548
|
+
if (this.outputs.length === 0) {
|
|
19549
|
+
throw new Error("ArkadeTransactionBuilder: at least one output is required");
|
|
19550
|
+
}
|
|
19551
|
+
const outputsSum = this.outputs.reduce((s, o) => s + (o.amount ?? 0n), 0n);
|
|
19552
|
+
const coin = this.coin ?? await this.selectCoin(outputsSum);
|
|
19553
|
+
const def = this.fn.def;
|
|
19554
|
+
const outputs = [...this.outputs];
|
|
19555
|
+
const fundingSum = this.fundingCoins.reduce((s, f) => s + BigInt(f.value), 0n);
|
|
19556
|
+
const surplus = BigInt(coin.value) + fundingSum - outputsSum;
|
|
19557
|
+
if (surplus < 0n) {
|
|
19558
|
+
throw new Error(
|
|
19559
|
+
`ArkadeTransactionBuilder: insufficient inputs \u2014 outputs ${outputsSum} exceed inputs ${BigInt(coin.value) + fundingSum}`
|
|
19560
|
+
);
|
|
19561
|
+
}
|
|
19562
|
+
if (surplus > 0n) {
|
|
19563
|
+
if (!this.changeScript) {
|
|
19564
|
+
throw new Error(
|
|
19565
|
+
`ArkadeTransactionBuilder: ${surplus} sats surplus with no change output \u2014 call .change(script)`
|
|
19566
|
+
);
|
|
19567
|
+
}
|
|
19568
|
+
outputs.push({ script: this.changeScript, amount: surplus });
|
|
19569
|
+
}
|
|
19570
|
+
const { arkTx, checkpoints } = buildOffchainTx(
|
|
19571
|
+
[
|
|
19572
|
+
{
|
|
19573
|
+
txid: coin.txid,
|
|
19574
|
+
vout: coin.vout,
|
|
19575
|
+
value: coin.value,
|
|
19576
|
+
tapLeafScript: this.fn.tapLeafScript,
|
|
19577
|
+
tapTree: this.contract.tapTree
|
|
19578
|
+
},
|
|
19579
|
+
...this.fundingCoins
|
|
19580
|
+
],
|
|
19581
|
+
outputs,
|
|
19582
|
+
this.contract.client.checkpoint
|
|
19583
|
+
);
|
|
19584
|
+
if (coin.sourceTx) {
|
|
19585
|
+
setArkPsbtField(arkTx, 0, PrevArkTxField, coin.sourceTx);
|
|
19586
|
+
}
|
|
19587
|
+
const condition = (def.tapscript.witness ?? []).map((w) => this.witnessBytes(w));
|
|
19588
|
+
if (condition.length > 0) {
|
|
19589
|
+
setArkPsbtField(arkTx, 0, ConditionWitness, condition);
|
|
19590
|
+
setArkPsbtField(checkpoints[0], 0, ConditionWitness, condition);
|
|
19591
|
+
}
|
|
19592
|
+
const packets = [];
|
|
19593
|
+
if (this.assetSpecs.length > 0) {
|
|
19594
|
+
packets.push(this.buildAssetPacket());
|
|
19595
|
+
}
|
|
19596
|
+
const arkadeScript = this.fn.arkadeScript;
|
|
19597
|
+
if (arkadeScript) {
|
|
19598
|
+
const stack = (def.arkadeScript?.witness ?? []).map((w) => this.witnessBytes(w));
|
|
19599
|
+
packets.push(
|
|
19600
|
+
EmulatorPacket.create([
|
|
19601
|
+
{ vin: 0, script: arkadeScript, witness: RawWitness.encode(stack) }
|
|
19602
|
+
])
|
|
19603
|
+
);
|
|
19604
|
+
}
|
|
19605
|
+
if (packets.length > 0) {
|
|
19606
|
+
attachExtension(arkTx, packets);
|
|
19607
|
+
}
|
|
19608
|
+
return { arkTx, checkpoints };
|
|
19609
|
+
}
|
|
19610
|
+
/** Build, submit and return the finalized transaction. */
|
|
19611
|
+
async send() {
|
|
19612
|
+
const { arkTx, checkpoints } = await this.build();
|
|
19613
|
+
const client = this.contract.client;
|
|
19614
|
+
const userInputs = this.userInputIndexes();
|
|
19615
|
+
if (this.fn.arkadeScript) {
|
|
19616
|
+
if (!client.emulator) {
|
|
19617
|
+
throw new Error("covenant spends require an `emulator` on the Arkade client");
|
|
19618
|
+
}
|
|
19619
|
+
const signedArk2 = await this.signArk(arkTx, userInputs);
|
|
19620
|
+
const signedCps = userInputs.length > 0 ? await Promise.all(
|
|
19621
|
+
checkpoints.map(
|
|
19622
|
+
(c, i) => userInputs.includes(i) ? client.identity.sign(c, [0]) : c
|
|
19623
|
+
)
|
|
19624
|
+
) : checkpoints;
|
|
19625
|
+
const res2 = await client.emulator.submitTx(
|
|
19626
|
+
base64.encode(signedArk2.toPSBT()),
|
|
19627
|
+
signedCps.map((c) => base64.encode(c.toPSBT()))
|
|
19628
|
+
);
|
|
19629
|
+
const txid = Transaction.fromPSBT(base64.decode(res2.signedArkTx)).id;
|
|
19630
|
+
return {
|
|
19631
|
+
txid,
|
|
19632
|
+
signedArkTx: res2.signedArkTx,
|
|
19633
|
+
signedCheckpointTxs: res2.signedCheckpointTxs
|
|
19634
|
+
};
|
|
19635
|
+
}
|
|
19636
|
+
if (!client.identity) {
|
|
19637
|
+
throw new Error("a signing identity is required for non-covenant spends");
|
|
19638
|
+
}
|
|
19639
|
+
const signedArk = await this.signArk(arkTx, userInputs);
|
|
19640
|
+
const res = await client.arkProvider.submitTx(
|
|
19641
|
+
base64.encode(signedArk.toPSBT()),
|
|
19642
|
+
checkpoints.map((c) => base64.encode(c.toPSBT()))
|
|
19643
|
+
);
|
|
19644
|
+
const finalCps = await Promise.all(
|
|
19645
|
+
res.signedCheckpointTxs.map(
|
|
19646
|
+
async (b) => base64.encode(
|
|
19647
|
+
(await client.identity.sign(Transaction.fromPSBT(base64.decode(b)), [0])).toPSBT()
|
|
19648
|
+
)
|
|
19649
|
+
)
|
|
19650
|
+
);
|
|
19651
|
+
await client.arkProvider.finalizeTx(res.arkTxid, finalCps);
|
|
19652
|
+
return {
|
|
19653
|
+
txid: res.arkTxid,
|
|
19654
|
+
signedArkTx: res.finalArkTx,
|
|
19655
|
+
signedCheckpointTxs: res.signedCheckpointTxs
|
|
19656
|
+
};
|
|
19657
|
+
}
|
|
19658
|
+
/** Sign the client-owned inputs of the ark tx (no-op when there are none). */
|
|
19659
|
+
async signArk(arkTx, userInputs) {
|
|
19660
|
+
if (userInputs.length === 0) return arkTx;
|
|
19661
|
+
const { identity } = this.contract.client;
|
|
19662
|
+
if (!identity) {
|
|
19663
|
+
throw new Error("this spend requires an `identity` to sign its user/funding inputs");
|
|
19664
|
+
}
|
|
19665
|
+
return identity.sign(arkTx, userInputs);
|
|
19666
|
+
}
|
|
19667
|
+
/** Indexes of inputs the client owns and must sign (contract input + funded inputs). */
|
|
19668
|
+
userInputIndexes() {
|
|
19669
|
+
const idxs = [];
|
|
19670
|
+
const userKey = this.contract.keys.userKey;
|
|
19671
|
+
if (userKey && this.fn.signerKeys.some((k) => equalBytes(k, userKey))) {
|
|
19672
|
+
idxs.push(0);
|
|
19673
|
+
}
|
|
19674
|
+
for (let i = 0; i < this.fundingCoins.length; i++) {
|
|
19675
|
+
idxs.push(i + 1);
|
|
19676
|
+
}
|
|
19677
|
+
return idxs;
|
|
19678
|
+
}
|
|
19679
|
+
async selectCoin(amount) {
|
|
19680
|
+
const utxos = await this.contract.getUtxos();
|
|
19681
|
+
if (utxos.length === 0) throw new Error("no spendable coins for this contract");
|
|
19682
|
+
const covering = utxos.filter((u) => BigInt(u.value) >= amount).sort((a, b) => a.value - b.value);
|
|
19683
|
+
if (covering.length > 0) return covering[0];
|
|
19684
|
+
return [...utxos].sort((a, b) => b.value - a.value)[0];
|
|
19685
|
+
}
|
|
19686
|
+
buildAssetPacket() {
|
|
19687
|
+
const groups = this.assetSpecs.map((s) => {
|
|
19688
|
+
const id = typeof s.assetId === "string" ? AssetId.fromString(s.assetId) : AssetId.fromBytes(s.assetId);
|
|
19689
|
+
return AssetGroup.create(
|
|
19690
|
+
id,
|
|
19691
|
+
null,
|
|
19692
|
+
s.inputs.map((i) => AssetInput.create(i.vin, i.amount)),
|
|
19693
|
+
s.outputs.map((o) => AssetOutput.create(o.vout, o.amount)),
|
|
19694
|
+
(s.metadata ?? []).map((m) => Metadata.create(m.key, m.value))
|
|
19695
|
+
);
|
|
19696
|
+
});
|
|
19697
|
+
return Packet.create(groups);
|
|
19698
|
+
}
|
|
19699
|
+
witnessBytes(ref) {
|
|
19700
|
+
return witnessRefToBytes(ref, this.args, this.contract.args);
|
|
19701
|
+
}
|
|
19702
|
+
};
|
|
19703
|
+
function bindInputs(fn, callArgs) {
|
|
19704
|
+
const names = (fn.def.inputs ?? []).map(inputName);
|
|
19705
|
+
if (callArgs.length !== names.length) {
|
|
19706
|
+
throw new Error(`${fn.name}: expected ${names.length} argument(s), got ${callArgs.length}`);
|
|
19707
|
+
}
|
|
19708
|
+
const bound = {};
|
|
19709
|
+
names.forEach((n, i) => bound[n] = callArgs[i]);
|
|
19710
|
+
return bound;
|
|
19711
|
+
}
|
|
19712
|
+
function attachExtension(tx, newPackets) {
|
|
19713
|
+
for (let i = 0; i < tx.outputsLength; i++) {
|
|
19714
|
+
const out = tx.getOutput(i);
|
|
19715
|
+
if (!out?.script || !Extension.isExtension(out.script)) continue;
|
|
19716
|
+
const existing = Extension.fromBytes(out.script);
|
|
19717
|
+
const merged = Extension.create([...existing.getPackets(), ...newPackets]);
|
|
19718
|
+
tx.updateOutput(i, { script: merged.serialize(), amount: 0n });
|
|
19719
|
+
return;
|
|
19720
|
+
}
|
|
19721
|
+
const ext = Extension.create(newPackets);
|
|
19722
|
+
const newOut = ext.txOut();
|
|
19723
|
+
const lastIdx = tx.outputsLength - 1;
|
|
19724
|
+
const lastOut = tx.getOutput(lastIdx);
|
|
19725
|
+
if (lastOut?.script && lastOut.script.length === ANCHOR_PKSCRIPT.length && lastOut.script.every((b, j) => b === ANCHOR_PKSCRIPT[j])) {
|
|
19726
|
+
tx.updateOutput(lastIdx, { script: newOut.script, amount: newOut.amount });
|
|
19727
|
+
tx.addOutput({ script: lastOut.script, amount: lastOut.amount ?? 0n });
|
|
19728
|
+
return;
|
|
19729
|
+
}
|
|
19730
|
+
tx.addOutput({ script: newOut.script, amount: newOut.amount });
|
|
19731
|
+
}
|
|
18444
19732
|
var ARKCONTRACT_PREFIX = "arkcontract";
|
|
18445
19733
|
function encodeArkContract(contract) {
|
|
18446
19734
|
const params = new URLSearchParams();
|
|
@@ -18500,6 +19788,6 @@ function isArkContract(str) {
|
|
|
18500
19788
|
return str.startsWith(ARKCONTRACT_PREFIX + "=");
|
|
18501
19789
|
}
|
|
18502
19790
|
|
|
18503
|
-
export { ActivityRegistry, ArkNote, AssetManager, BIP322, Batch, ChainedTxType, ContractManager, ContractRepositoryImpl, ContractWatcher, DB_VERSION, DEFAULT_MESSAGE_TIMEOUTS, DelegateManagerImpl, DelegateNotConfiguredError, DelegatorManagerImpl, DelegatorNotConfiguredError, DescriptorSigningProviderMissingError, DustChangeError, ELECTRUM_TCP_HOST, ELECTRUM_WS_URL, ESPLORA_URL, ElectrumOnchainProvider, EsploraProvider, Estimator, HDDescriptorProvider, INTENT_TERMINAL_STATES, InMemoryContractRepository, InMemoryIntentRepository, InMemoryVirtualTxRepository, InMemoryWalletRepository, IndexedDBContractRepository, IndexedDBIntentRepository, IndexedDBVirtualTxRepository, IndexedDBWalletRepository, MESSAGE_BUS_INITIALIZING, MESSAGE_BUS_NOT_INITIALIZED, MIGRATION_KEY, MessageBus, MessageBusInitializingError, MessageBusNotInitializedError, MissingSigningDescriptorError, MnemonicIdentity, OnchainWallet, P2A, Ramps, ReadonlyAssetManager, ReadonlyDescriptorIdentity, ReadonlySingleKey, ReadonlyWallet, ReadonlyWalletError, RestDelegateProvider, RestDelegatorProvider, SeedIdentity, ServiceWorkerReadonlyWallet, ServiceWorkerTimeoutError, ServiceWorkerWallet, SingleKey, TxTree, TxType, TxWeightEstimator, UnilateralExit, Unroll, VtxoManager, Wallet2 as Wallet, WalletMessageHandler, WalletNotInitializedError, WalletRepositoryImpl, WsElectrumChainSource, assertIntentIdUnique, boardingResolver, buildForfeitTx, buildOffchainTx, classifyAgainstSignerSet, classifyContractSigner, closeDatabase, combineTapscriptSigs, contractFromArkContract, contractFromArkContractWithAddress, createDefaultActivityRegistry, createExitChainResolver, decodeArkContract, deserializeAssets, deserializeExitPackage, deserializeUtxo, deserializeVtxo, encodeArkContract, extendVirtualCoinForContract, getMigrationStatus, getRandomId, hasBoardingTxExpired, intentMatchesFilter, intentPageBounds, isArkContract, isBatchSignable, isCooperativelyMigratable, isDiscoverable, isExpired, isRecoverable, isRetryableProviderError, isSpendable, isSubdust, isTerminalIntentState, isValidArkAddress, isVtxoExpiringSoon, isVtxoForScript, mergeChainedTxType, migrateWalletRepository, openDatabase, requiresMigration, rollbackMigration, saveVtxosForContract, scriptFromArkAddress, serializeAssets, serializeExitPackage, serializeUtxo, serializeVtxo, setupServiceWorker, signerSetFromInfo, toXOnlySignerHex, validateConnectorsTxGraph, validateVtxoTxGraph, verifyTapscriptSignatures, waitForIncomingFunds, warnAndFilterVtxosForScript };
|
|
18504
|
-
//# sourceMappingURL=chunk-
|
|
18505
|
-
//# sourceMappingURL=chunk-
|
|
19791
|
+
export { ARKADE_MAGIC, ActivityRegistry, ArkCash, ArkCashCreateError, ArkNote, AssetManager, BIP322, Batch, ChainedTxType, ContractManager, ContractRepositoryImpl, ContractWatcher, DB_VERSION, DEFAULT_MESSAGE_TIMEOUTS, DelegateManagerImpl, DelegateNotConfiguredError, DelegatorManagerImpl, DelegatorNotConfiguredError, DescriptorSigningProviderMissingError, DustChangeError, ELECTRUM_TCP_HOST, ELECTRUM_WS_URL, ESPLORA_URL, ElectrumOnchainProvider, EmulatorPacket, EsploraProvider, Estimator, Extension, ExtensionNotFoundError, HDDescriptorProvider, INTENT_TERMINAL_STATES, InMemoryContractRepository, InMemoryIntentRepository, InMemoryVirtualTxRepository, InMemoryWalletRepository, IndexedDBContractRepository, IndexedDBIntentRepository, IndexedDBVirtualTxRepository, IndexedDBWalletRepository, MESSAGE_BUS_INITIALIZING, MESSAGE_BUS_NOT_INITIALIZED, MIGRATION_KEY, MessageBus, MessageBusInitializingError, MessageBusNotInitializedError, MissingSigningDescriptorError, MnemonicIdentity, OnchainWallet, P2A, Ramps, ReadonlyAssetManager, ReadonlyDescriptorIdentity, ReadonlySingleKey, ReadonlyWallet, ReadonlyWalletError, RestDelegateProvider, RestDelegatorProvider, RestEmulatorProvider, SeedIdentity, ServiceWorkerReadonlyWallet, ServiceWorkerTimeoutError, ServiceWorkerWallet, SingleKey, TxTree, TxType, TxWeightEstimator, UnilateralExit, UnknownPacket, Unroll, VtxoManager, Wallet2 as Wallet, WalletMessageHandler, WalletNotInitializedError, WalletRepositoryImpl, WsElectrumChainSource, arkade_exports, assertIntentIdUnique, boardingResolver, buildForfeitTx, buildOffchainTx, classifyAgainstSignerSet, classifyContractSigner, closeDatabase, combineTapscriptSigs, contractFromArkContract, contractFromArkContractWithAddress, createAssetPacket, createDefaultActivityRegistry, createExitChainResolver, decodeArkContract, deserializeAssets, deserializeExitPackage, deserializeUtxo, deserializeVtxo, encodeArkContract, extendVirtualCoinForContract, getMigrationStatus, getRandomId, hasBoardingTxExpired, intentMatchesFilter, intentPageBounds, isArkContract, isBatchSignable, isCooperativelyMigratable, isDiscoverable, isExpired, isRecoverable, isRetryableProviderError, isSpendable, isSubdust, isTerminalIntentState, isValidArkAddress, isVtxoExpiringSoon, isVtxoForScript, mergeChainedTxType, migrateWalletRepository, openDatabase, requiresMigration, rollbackMigration, saveVtxosForContract, scriptFromArkAddress, selectCoinsWithAsset, selectVirtualCoins, serializeAssets, serializeExitPackage, serializeUtxo, serializeVtxo, setupServiceWorker, signerSetFromInfo, toXOnlySignerHex, validateConnectorsTxGraph, validateVtxoTxGraph, verifyTapscriptSignatures, waitForIncomingFunds, warnAndFilterVtxosForScript };
|
|
19792
|
+
//# sourceMappingURL=chunk-SV4PCGKM.js.map
|
|
19793
|
+
//# sourceMappingURL=chunk-SV4PCGKM.js.map
|