@arkade-os/sdk 0.4.53 → 0.4.55
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-B69ugeJw.d.cts → ark-Do9Lqfsp.d.cts} +36 -2
- package/dist/{ark-B69ugeJw.d.ts → ark-Do9Lqfsp.d.ts} +36 -2
- package/dist/{asyncStorageTaskQueue-B0SUQMXw.d.cts → asyncStorageTaskQueue-C3DYxvAT.d.cts} +1 -1
- package/dist/{asyncStorageTaskQueue-DWqxuIAs.d.ts → asyncStorageTaskQueue-DOnBOEDn.d.ts} +1 -1
- package/dist/{chunk-CZTQLA55.cjs → chunk-C6UYPHTQ.cjs} +9 -9
- package/dist/{chunk-CZTQLA55.cjs.map → chunk-C6UYPHTQ.cjs.map} +1 -1
- package/dist/{chunk-4GB73TRR.cjs → chunk-CSS74VCN.cjs} +17 -17
- package/dist/{chunk-4GB73TRR.cjs.map → chunk-CSS74VCN.cjs.map} +1 -1
- package/dist/{chunk-LHYNNDY6.js → chunk-ENWR2F2P.js} +8 -1327
- package/dist/chunk-ENWR2F2P.js.map +1 -0
- package/dist/{chunk-RNMLFY2V.cjs → chunk-G7DOBFQN.cjs} +19 -1367
- package/dist/chunk-G7DOBFQN.cjs.map +1 -0
- package/dist/chunk-JCJ5WFS3.js +2326 -0
- package/dist/chunk-JCJ5WFS3.js.map +1 -0
- package/dist/{chunk-B4WZOJAI.js → chunk-LAONNMM5.js} +4 -4
- package/dist/{chunk-B4WZOJAI.js.map → chunk-LAONNMM5.js.map} +1 -1
- package/dist/chunk-OWCPXRGP.cjs +2415 -0
- package/dist/chunk-OWCPXRGP.cjs.map +1 -0
- package/dist/{chunk-UZLQUWYP.js → chunk-QMPNPZ2T.js} +23 -4
- package/dist/chunk-QMPNPZ2T.js.map +1 -0
- package/dist/{chunk-3MMUKLCI.js → chunk-QO3IKBYL.js} +827 -130
- package/dist/chunk-QO3IKBYL.js.map +1 -0
- package/dist/{chunk-3XWDMLOI.js → chunk-SG4X7SEC.js} +4 -4
- package/dist/{chunk-3XWDMLOI.js.map → chunk-SG4X7SEC.js.map} +1 -1
- package/dist/{chunk-IEMPH7J6.cjs → chunk-V2TOHHPW.cjs} +60 -40
- package/dist/chunk-V2TOHHPW.cjs.map +1 -0
- package/dist/{chunk-3YC3LQ2E.cjs → chunk-ZPL34T2Q.cjs} +1191 -471
- package/dist/chunk-ZPL34T2Q.cjs.map +1 -0
- package/dist/contracts/handlers/index.cjs +8 -8
- 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-C_Y2YrXC.d.cts → delegate-C0iIBxen.d.cts} +1 -1
- package/dist/{delegate-BU-Cpyjw.d.ts → delegate-DmhFrtdq.d.ts} +1 -1
- package/dist/{index-BWF-C9eh.d.ts → index-BNt4wcSh.d.ts} +2 -2
- package/dist/{index-tzAInyiX.d.cts → index-BnLM1XxA.d.cts} +2 -2
- package/dist/index.cjs +348 -240
- package/dist/index.d.cts +424 -14
- package/dist/index.d.ts +424 -14
- 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-nzJFH8qb.d.ts → taskRunner-Bgs-0I3x.d.ts} +1 -1
- package/dist/{taskRunner-B7svzMhT.d.cts → taskRunner-RwUvgQ5M.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 +5 -5
- package/dist/wallet/expo/index.d.ts +5 -5
- package/dist/wallet/expo/index.js +5 -5
- package/dist/{wallet-B6Xiferv.d.cts → wallet-5Aou-V5n.d.ts} +83 -6
- package/dist/{wallet-CuHAI-eW.d.ts → wallet-Ck7BH8zR.d.cts} +83 -6
- 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-3MMUKLCI.js.map +0 -1
- package/dist/chunk-3YC3LQ2E.cjs.map +0 -1
- package/dist/chunk-E7HB3GKK.cjs +0 -1030
- package/dist/chunk-E7HB3GKK.cjs.map +0 -1
- package/dist/chunk-EFNLTS6Q.js +0 -976
- package/dist/chunk-EFNLTS6Q.js.map +0 -1
- package/dist/chunk-IEMPH7J6.cjs.map +0 -1
- package/dist/chunk-LHYNNDY6.js.map +0 -1
- package/dist/chunk-RNMLFY2V.cjs.map +0 -1
- package/dist/chunk-UZLQUWYP.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
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, speculativeReceiveMetadata, DelegateVtxo,
|
|
3
|
-
import { VtxoScript, isRecoverable, CSVMultisigTapscript, DEFAULT_NETWORK, hasTerminalSpend, getNormalizedVtxos,
|
|
1
|
+
import { AssetId, AssetGroup, AssetInput, AssetOutput, Metadata, Packet, BufferReader, BufferWriter, AssetRef, RestIndexerProvider } from './chunk-ENWR2F2P.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, deriveDescriptorLeafPubKey, deriveDescriptorLeafCompressedPubKey, DefaultVtxo, speculativeReceiveMetadata, DelegateVtxo, WALLET_RECEIVE_SOURCE, BoardingContractHandler } from './chunk-QMPNPZ2T.js';
|
|
3
|
+
import { VtxoScript, Transaction, isRecoverable, CSVMultisigTapscript, DEFAULT_NETWORK, hasTerminalSpend, getNormalizedVtxos, setArkPsbtField, PrevArkTxField, ConditionWitness, craftToSpendTx, ArkAddress, isVirtualCoin, getArkPsbtFields, CosignerPublicKey, ServerResponseMismatchError, OP_RETURN_EMPTY_PKSCRIPT, sequenceToTimelock, decodeTapscript, scriptFromTapLeafScript, CLTVMultisigTapscript, VtxoTaprootTree, timelockToSequence, baseFetch, Intent, rateGate, DEFAULT_NETWORK_NAME, normalizeVtxo, getSequence, toBatchExpiry, maybeArkError, isArkError, ArkErrorName, toOffchainInputFeeParams, canRecoverOnchain, getNetwork, MultisigTapscript, isEventSourceError, isRetryableProviderError, getAllNormalizedVtxos, DEFAULT_PAGE_SIZE, RestArkProvider, canSpendOffchain, toVirtualStatus, ArkError, resolveTimeHeight, parseLegacyExpiry, isExpired, DEFAULT_ARKADE_SERVER_URL, ProviderUnavailableError, networks as networks$1, isPastExpiry, fetchVtxoCreatedAtByTxid } from './chunk-JCJ5WFS3.js';
|
|
4
4
|
import { __export } from './chunk-NSBPE2FW.js';
|
|
5
5
|
import { sha256, equalBytes, hash160, sha256x2, concatBytes, randomPrivateKeyBytes, pubECDSA, pubSchnorr } from '@scure/btc-signer/utils.js';
|
|
6
6
|
import { SigHash, Script, OP, OutScript, Address, RawWitness, p2tr, p2wpkh, TaprootControlBlock, DEFAULT_SEQUENCE, Transaction as Transaction$2 } from '@scure/btc-signer';
|
|
@@ -25,8 +25,8 @@ function aggregateNonces(pubNonces) {
|
|
|
25
25
|
return musig2.nonceAggregate(pubNonces);
|
|
26
26
|
}
|
|
27
27
|
function aggregateKeys(publicKeys, sort, options = {}) {
|
|
28
|
-
{
|
|
29
|
-
publicKeys = musig2.sortKeys(publicKeys);
|
|
28
|
+
if (sort) {
|
|
29
|
+
publicKeys = musig2.sortKeys([...publicKeys]);
|
|
30
30
|
}
|
|
31
31
|
const { aggPublicKey: preTweakedKey } = musig2.keyAggregate(publicKeys);
|
|
32
32
|
if (!options.taprootTweak) {
|
|
@@ -88,24 +88,27 @@ var PartialSig = class _PartialSig {
|
|
|
88
88
|
return new _PartialSig(bytes, R);
|
|
89
89
|
}
|
|
90
90
|
};
|
|
91
|
-
function
|
|
91
|
+
function createSession(combinedNonce, publicKeys, message, options) {
|
|
92
|
+
const keys = musig2.sortKeys([...publicKeys]) ;
|
|
92
93
|
let tweakBytes;
|
|
93
94
|
if (options?.taprootTweak !== void 0) {
|
|
94
|
-
const { preTweakedKey } = aggregateKeys(
|
|
95
|
-
musig2.sortKeys(publicKeys) );
|
|
95
|
+
const { preTweakedKey } = aggregateKeys(keys, false);
|
|
96
96
|
tweakBytes = schnorr.utils.taggedHash(
|
|
97
97
|
"TapTweak",
|
|
98
98
|
preTweakedKey.subarray(1),
|
|
99
99
|
options.taprootTweak
|
|
100
100
|
);
|
|
101
101
|
}
|
|
102
|
-
|
|
102
|
+
return new musig2.Session(
|
|
103
103
|
combinedNonce,
|
|
104
|
-
|
|
104
|
+
keys,
|
|
105
105
|
message,
|
|
106
106
|
tweakBytes ? [tweakBytes] : void 0,
|
|
107
107
|
tweakBytes ? [true] : void 0
|
|
108
108
|
);
|
|
109
|
+
}
|
|
110
|
+
function sign(secNonce, privateKey, combinedNonce, publicKeys, message, options) {
|
|
111
|
+
const session = createSession(combinedNonce, publicKeys, message, options);
|
|
109
112
|
const partialSig = session.sign(secNonce, privateKey);
|
|
110
113
|
return PartialSig.decode(partialSig);
|
|
111
114
|
}
|
|
@@ -514,6 +517,18 @@ var SeedIdentity = class _SeedIdentity {
|
|
|
514
517
|
const key = this.derivePrivateKeyForDescriptor(descriptor);
|
|
515
518
|
return this.signMessageWithKey(key, message, signatureType);
|
|
516
519
|
}
|
|
520
|
+
/**
|
|
521
|
+
* BIP-340 sign `messageHash` with the key derived from `descriptor`,
|
|
522
|
+
* aux_rand = 0. Backs {@link DescriptorIdentity.signSchnorrDeterministic},
|
|
523
|
+
* which swap preimage derivation reads through.
|
|
524
|
+
*/
|
|
525
|
+
async signSchnorrDeterministicWithDescriptor(descriptor, messageHash) {
|
|
526
|
+
if (!this.isOurs(descriptor)) {
|
|
527
|
+
throw new Error(`Descriptor ${descriptor} does not belong to this identity`);
|
|
528
|
+
}
|
|
529
|
+
const key = this.derivePrivateKeyForDescriptor(descriptor);
|
|
530
|
+
return schnorr$1.signAsync(messageHash, key, new Uint8Array(32));
|
|
531
|
+
}
|
|
517
532
|
// ── internal helpers ─────────────────────────────────────────────
|
|
518
533
|
derivePrivateKeyForDescriptor(descriptor) {
|
|
519
534
|
const network = isMainnetDescriptor(descriptor) ? networks.bitcoin : networks.testnet;
|
|
@@ -800,6 +815,59 @@ function assertValidSerializedIdentity(s) {
|
|
|
800
815
|
}
|
|
801
816
|
}
|
|
802
817
|
|
|
818
|
+
// src/identity/descriptorIdentity.ts
|
|
819
|
+
function isHDDeterministicSignCapable(value) {
|
|
820
|
+
return typeof value === "object" && value !== null && typeof value.signSchnorrDeterministicWithDescriptor === "function";
|
|
821
|
+
}
|
|
822
|
+
var DescriptorIdentity = class {
|
|
823
|
+
descriptor;
|
|
824
|
+
signer;
|
|
825
|
+
base;
|
|
826
|
+
constructor({ descriptor, signer, base }) {
|
|
827
|
+
this.descriptor = descriptor;
|
|
828
|
+
this.signer = signer;
|
|
829
|
+
this.base = base;
|
|
830
|
+
}
|
|
831
|
+
async xOnlyPublicKey() {
|
|
832
|
+
return deriveDescriptorLeafPubKey(this.descriptor);
|
|
833
|
+
}
|
|
834
|
+
async compressedPublicKey() {
|
|
835
|
+
return deriveDescriptorLeafCompressedPubKey(this.descriptor);
|
|
836
|
+
}
|
|
837
|
+
async sign(tx, inputIndexes) {
|
|
838
|
+
const [signed] = await this.signer.signWithDescriptor([
|
|
839
|
+
{ descriptor: this.descriptor, tx, inputIndexes }
|
|
840
|
+
]);
|
|
841
|
+
if (!signed) {
|
|
842
|
+
throw new Error(`Descriptor signer returned no transaction for ${this.descriptor}`);
|
|
843
|
+
}
|
|
844
|
+
return signed;
|
|
845
|
+
}
|
|
846
|
+
async signMessage(message, signatureType = "schnorr") {
|
|
847
|
+
return this.signer.signMessageWithDescriptor(this.descriptor, message, signatureType);
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* Delegated: `TreeSignerSession.random()` consults no key material, so the
|
|
851
|
+
* session is index-independent.
|
|
852
|
+
*/
|
|
853
|
+
signerSession() {
|
|
854
|
+
return this.base.signerSession();
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Throws rather than falling back to the baseline key: a preimage derived
|
|
858
|
+
* from the wrong index is unrecoverable, and callers probe this member
|
|
859
|
+
* through a structural guard that cannot tell the two apart.
|
|
860
|
+
*/
|
|
861
|
+
async signSchnorrDeterministic(messageHash) {
|
|
862
|
+
if (!isHDDeterministicSignCapable(this.base)) {
|
|
863
|
+
throw new Error(
|
|
864
|
+
`Identity cannot sign deterministically for descriptor ${this.descriptor}`
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
return this.base.signSchnorrDeterministicWithDescriptor(this.descriptor, messageHash);
|
|
868
|
+
}
|
|
869
|
+
};
|
|
870
|
+
|
|
803
871
|
// src/identity/hdCapableIdentity.ts
|
|
804
872
|
function isHDCapableIdentity(value) {
|
|
805
873
|
if (typeof value !== "object" || value === null) return false;
|
|
@@ -812,6 +880,13 @@ function isBatchSignable(identity) {
|
|
|
812
880
|
return "signMultiple" in identity && typeof identity.signMultiple === "function";
|
|
813
881
|
}
|
|
814
882
|
|
|
883
|
+
// src/wallet/hdWalletCapable.ts
|
|
884
|
+
function isHDWalletCapable(value) {
|
|
885
|
+
if (typeof value !== "object" || value === null) return false;
|
|
886
|
+
const v = value;
|
|
887
|
+
return typeof v.getCurrentSigningDescriptor === "function" && typeof v.getUsedSigningDescriptors === "function" && typeof v.signerForDescriptor === "function";
|
|
888
|
+
}
|
|
889
|
+
|
|
815
890
|
// src/worker/browser/service-worker-manager.ts
|
|
816
891
|
var registrations = /* @__PURE__ */ new Map();
|
|
817
892
|
var handshakes = /* @__PURE__ */ new WeakSet();
|
|
@@ -1805,14 +1880,28 @@ function buildOpReturnScript(data) {
|
|
|
1805
1880
|
}
|
|
1806
1881
|
return script;
|
|
1807
1882
|
}
|
|
1808
|
-
var ErrOffchainOutputNotFound = (address) => new
|
|
1809
|
-
var ErrInvalidAssetOutputAmount = (got, want, assetId) => new
|
|
1810
|
-
|
|
1811
|
-
|
|
1883
|
+
var ErrOffchainOutputNotFound = (address) => new ServerResponseMismatchError(`offchain send output not found: ${address}`);
|
|
1884
|
+
var ErrInvalidAssetOutputAmount = (got, want, assetId) => new ServerResponseMismatchError(
|
|
1885
|
+
`invalid asset output amount for ${assetId}: got ${got}, want ${want}`
|
|
1886
|
+
);
|
|
1887
|
+
var ErrAssetGroupNotFound = (assetId) => new ServerResponseMismatchError(`asset group not found in batch leaf: ${assetId}`);
|
|
1888
|
+
var ErrAssetOutputNotFound = (assetId, outputIndex) => new ServerResponseMismatchError(
|
|
1889
|
+
`asset output not found in asset group ${assetId} at index ${outputIndex}`
|
|
1890
|
+
);
|
|
1891
|
+
var ErrOnchainOutputNotFound = (address) => new ServerResponseMismatchError(`onchain output not found: ${address}`);
|
|
1892
|
+
var ErrUnvalidatedOffchainOutput = (address) => new ServerResponseMismatchError(
|
|
1893
|
+
`offchain output ${address} cannot be validated: virtual output tree signing did not run`
|
|
1894
|
+
);
|
|
1812
1895
|
var ErrInvalidOnchainOutputAmount = (address) => new Error(`invalid onchain output amount: ${address}`);
|
|
1813
1896
|
var ErrInvalidOnchainOutputAssets = (address) => new Error(`onchain output ${address} cannot have assets`);
|
|
1814
|
-
var ErrOnchainOutputNotFound = (address) => new Error(`onchain output not found: ${address}`);
|
|
1815
1897
|
var ErrInvalidOffchainOutputAmount = (address) => new Error(`invalid offchain output ${address}, missing amount`);
|
|
1898
|
+
function assertFinalCommitmentMatchesValidated(finalCommitmentTx, validatedCommitmentTxid, context) {
|
|
1899
|
+
if (!validatedCommitmentTxid) return;
|
|
1900
|
+
if (finalCommitmentTx.id === validatedCommitmentTxid) return;
|
|
1901
|
+
throw new ServerResponseMismatchError(
|
|
1902
|
+
`${context}: finalization commitment tx ${finalCommitmentTx.id} differs from the validated commitment tx ${validatedCommitmentTxid}`
|
|
1903
|
+
);
|
|
1904
|
+
}
|
|
1816
1905
|
function validateBatchRecipients(commitmentTx, vtxoTreeLeaves, recipients, network) {
|
|
1817
1906
|
const usedOutputs = /* @__PURE__ */ new Set();
|
|
1818
1907
|
const usedOnchainOutputs = /* @__PURE__ */ new Set();
|
|
@@ -1827,6 +1916,18 @@ function validateBatchRecipients(commitmentTx, vtxoTreeLeaves, recipients, netwo
|
|
|
1827
1916
|
validateOffchainRecipient(vtxoTreeLeaves, arkAddress, recipient, usedOutputs);
|
|
1828
1917
|
}
|
|
1829
1918
|
}
|
|
1919
|
+
function validateBatchRecipientsWithoutTree(commitmentTx, recipients, network) {
|
|
1920
|
+
const usedOnchainOutputs = /* @__PURE__ */ new Set();
|
|
1921
|
+
for (const recipient of recipients) {
|
|
1922
|
+
try {
|
|
1923
|
+
ArkAddress.decode(recipient.address);
|
|
1924
|
+
} catch {
|
|
1925
|
+
validateOnchainRecipient(commitmentTx, recipient, network, usedOnchainOutputs);
|
|
1926
|
+
continue;
|
|
1927
|
+
}
|
|
1928
|
+
throw ErrUnvalidatedOffchainOutput(recipient.address);
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1830
1931
|
function validateOnchainRecipient(commitmentTx, recipient, network, usedOutputs) {
|
|
1831
1932
|
const addr = Address(network).decode(recipient.address);
|
|
1832
1933
|
const expectedPkScript = OutScript.encode(addr);
|
|
@@ -1919,8 +2020,6 @@ function validateAssetGroupOutput(packet, outputIndex, assetId, expectedAmount)
|
|
|
1919
2020
|
throw ErrInvalidAssetOutputAmount(assetOutput.amount, expectedAmount, assetId);
|
|
1920
2021
|
}
|
|
1921
2022
|
}
|
|
1922
|
-
|
|
1923
|
-
// src/wallet/activity.ts
|
|
1924
2023
|
async function buildActivities(txs, resolvers) {
|
|
1925
2024
|
const preparedResolvers = (await Promise.all(
|
|
1926
2025
|
resolvers.map(async (r) => {
|
|
@@ -2030,9 +2129,49 @@ function boardingResolver() {
|
|
|
2030
2129
|
}
|
|
2031
2130
|
};
|
|
2032
2131
|
}
|
|
2132
|
+
function collabExitResolver() {
|
|
2133
|
+
return {
|
|
2134
|
+
id: "collab-exit",
|
|
2135
|
+
resolve(tx) {
|
|
2136
|
+
if (tx.tag !== "exit" || !tx.key.commitmentTxid) return void 0;
|
|
2137
|
+
return [
|
|
2138
|
+
{
|
|
2139
|
+
groupId: `exit:${tx.key.commitmentTxid}`,
|
|
2140
|
+
label: "Collaborative exit",
|
|
2141
|
+
kind: "exit"
|
|
2142
|
+
}
|
|
2143
|
+
];
|
|
2144
|
+
}
|
|
2145
|
+
};
|
|
2146
|
+
}
|
|
2147
|
+
function assetMintResolver() {
|
|
2148
|
+
return {
|
|
2149
|
+
id: "asset-mint",
|
|
2150
|
+
resolve(tx) {
|
|
2151
|
+
if (!tx.assets?.length || !tx.key.arkTxid) return void 0;
|
|
2152
|
+
const minted = tx.assets.filter((a) => {
|
|
2153
|
+
try {
|
|
2154
|
+
return hex.encode(AssetId.fromString(a.assetId).txid) === tx.key.arkTxid;
|
|
2155
|
+
} catch {
|
|
2156
|
+
return false;
|
|
2157
|
+
}
|
|
2158
|
+
});
|
|
2159
|
+
if (minted.length === 0) return void 0;
|
|
2160
|
+
const received = tx.type === "RECEIVED" /* TxReceived */;
|
|
2161
|
+
return minted.map((a) => ({
|
|
2162
|
+
groupId: `mint:${a.assetId}`,
|
|
2163
|
+
label: received ? "Asset receive" : "Asset mint",
|
|
2164
|
+
kind: received ? "asset-receive" : "asset-mint",
|
|
2165
|
+
metadata: { assetId: a.assetId, amount: a.amount.toString() }
|
|
2166
|
+
}));
|
|
2167
|
+
}
|
|
2168
|
+
};
|
|
2169
|
+
}
|
|
2033
2170
|
function createDefaultActivityRegistry() {
|
|
2034
2171
|
const registry = new ActivityRegistry();
|
|
2035
2172
|
registry.use(boardingResolver());
|
|
2173
|
+
registry.use(collabExitResolver());
|
|
2174
|
+
registry.use(assetMintResolver());
|
|
2036
2175
|
return registry;
|
|
2037
2176
|
}
|
|
2038
2177
|
|
|
@@ -2279,7 +2418,7 @@ function hasBoardingTxExpired(coin, boardingTimelock, chainTipHeight) {
|
|
|
2279
2418
|
function formatSighash(type) {
|
|
2280
2419
|
return `0x${type.toString(16).padStart(2, "0")}`;
|
|
2281
2420
|
}
|
|
2282
|
-
function verifyTapscriptSignatures(tx, inputIndex, requiredSigners, excludePubkeys = [], allowedSighashTypes = [SigHash.DEFAULT]) {
|
|
2421
|
+
function verifyTapscriptSignatures(tx, inputIndex, requiredSigners, excludePubkeys = [], allowedSighashTypes = [SigHash.DEFAULT], expectedLeafHash) {
|
|
2283
2422
|
const input = tx.getInput(inputIndex);
|
|
2284
2423
|
const prevoutScripts = [];
|
|
2285
2424
|
const prevoutAmounts = [];
|
|
@@ -2294,12 +2433,21 @@ function verifyTapscriptSignatures(tx, inputIndex, requiredSigners, excludePubke
|
|
|
2294
2433
|
if (!input.tapScriptSig || input.tapScriptSig.length === 0) {
|
|
2295
2434
|
throw new Error(`Input ${inputIndex} is missing tapScriptSig`);
|
|
2296
2435
|
}
|
|
2436
|
+
const expectedLeafHashHex = expectedLeafHash ? hex.encode(expectedLeafHash) : void 0;
|
|
2297
2437
|
for (const [tapScriptSigData, signature] of input.tapScriptSig) {
|
|
2298
2438
|
const pubKey = tapScriptSigData.pubKey;
|
|
2299
2439
|
const pubKeyHex = hex.encode(pubKey);
|
|
2300
2440
|
if (excludePubkeys.includes(pubKeyHex)) {
|
|
2301
2441
|
continue;
|
|
2302
2442
|
}
|
|
2443
|
+
if (expectedLeafHashHex !== void 0) {
|
|
2444
|
+
const signedLeafHex = hex.encode(tapScriptSigData.leafHash);
|
|
2445
|
+
if (signedLeafHex !== expectedLeafHashHex) {
|
|
2446
|
+
throw new Error(
|
|
2447
|
+
`Input ${inputIndex}: signature from ${pubKeyHex} commits to leaf ${signedLeafHex}, expected ${expectedLeafHashHex}`
|
|
2448
|
+
);
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2303
2451
|
const sighashType = signature.length === 65 ? signature[64] : SigHash.DEFAULT;
|
|
2304
2452
|
const sig = signature.subarray(0, 64);
|
|
2305
2453
|
if (!allowedSighashTypes.includes(sighashType)) {
|
|
@@ -2383,6 +2531,70 @@ function isValidArkAddress(address) {
|
|
|
2383
2531
|
return false;
|
|
2384
2532
|
}
|
|
2385
2533
|
}
|
|
2534
|
+
function matchServerCheckpoints(serverCheckpointTxs, expectedCheckpoints, context) {
|
|
2535
|
+
if (serverCheckpointTxs.length !== expectedCheckpoints.length) {
|
|
2536
|
+
throw new ServerResponseMismatchError(
|
|
2537
|
+
`${context} returned ${serverCheckpointTxs.length} checkpoints, expected ${expectedCheckpoints.length}`
|
|
2538
|
+
);
|
|
2539
|
+
}
|
|
2540
|
+
const byTxid = new Map(expectedCheckpoints.map((c) => [c.id, c]));
|
|
2541
|
+
return serverCheckpointTxs.map((encoded, index) => {
|
|
2542
|
+
const server = Transaction.fromPSBT(base64.decode(encoded));
|
|
2543
|
+
const local = byTxid.get(server.id);
|
|
2544
|
+
if (!local) {
|
|
2545
|
+
throw new ServerResponseMismatchError(
|
|
2546
|
+
`${context} checkpoint ${index} txid ${server.id} does not match any submitted checkpoint`
|
|
2547
|
+
);
|
|
2548
|
+
}
|
|
2549
|
+
byTxid.delete(server.id);
|
|
2550
|
+
return { server, local };
|
|
2551
|
+
});
|
|
2552
|
+
}
|
|
2553
|
+
function assertSubmittedArkTxid(response, signedArkTx, context) {
|
|
2554
|
+
if (response.arkTxid !== signedArkTx.id) {
|
|
2555
|
+
throw new ServerResponseMismatchError(
|
|
2556
|
+
`${context} returned ark txid ${response.arkTxid}, expected ${signedArkTx.id}`
|
|
2557
|
+
);
|
|
2558
|
+
}
|
|
2559
|
+
if (response.finalArkTx === void 0) return;
|
|
2560
|
+
const finalTxid = Transaction.fromPSBT(base64.decode(response.finalArkTx)).id;
|
|
2561
|
+
if (finalTxid !== signedArkTx.id) {
|
|
2562
|
+
throw new ServerResponseMismatchError(
|
|
2563
|
+
`${context} returned final ark tx ${finalTxid}, expected ${signedArkTx.id}`
|
|
2564
|
+
);
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
function assertCheckpointsMatchInputs(checkpoints, inputs, unrollCandidates, context) {
|
|
2568
|
+
const byOutpoint = new Map(inputs.map((input) => [`${input.txid}:${input.vout}`, input]));
|
|
2569
|
+
for (const [index, checkpoint] of checkpoints.entries()) {
|
|
2570
|
+
if (checkpoint.inputsLength !== 1) {
|
|
2571
|
+
throw new ServerResponseMismatchError(
|
|
2572
|
+
`${context}: checkpoint ${index} spends ${checkpoint.inputsLength} inputs, expected 1`
|
|
2573
|
+
);
|
|
2574
|
+
}
|
|
2575
|
+
const spent = checkpoint.getInput(0);
|
|
2576
|
+
if (!spent.txid || spent.index === void 0) {
|
|
2577
|
+
throw new ServerResponseMismatchError(
|
|
2578
|
+
`${context}: checkpoint ${index} has no input outpoint`
|
|
2579
|
+
);
|
|
2580
|
+
}
|
|
2581
|
+
const outpoint = `${hex.encode(spent.txid)}:${spent.index}`;
|
|
2582
|
+
const input = byOutpoint.get(outpoint);
|
|
2583
|
+
if (!input) {
|
|
2584
|
+
throw new ServerResponseMismatchError(
|
|
2585
|
+
`${context}: checkpoint ${index} spends ${outpoint}, which is not one of the requested virtual outputs`
|
|
2586
|
+
);
|
|
2587
|
+
}
|
|
2588
|
+
const rebuilt = unrollCandidates.some(
|
|
2589
|
+
(unroll) => buildCheckpointTx(input, unroll).tx.id === checkpoint.id
|
|
2590
|
+
);
|
|
2591
|
+
if (!rebuilt) {
|
|
2592
|
+
throw new ServerResponseMismatchError(
|
|
2593
|
+
`${context}: checkpoint ${index} txid ${checkpoint.id} differs from the checkpoint built locally for ${outpoint}`
|
|
2594
|
+
);
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2386
2598
|
async function submitOffchainTx(provider, offchainTx, signer, hooks) {
|
|
2387
2599
|
const { arkTx: signedArkTx, userSignedCheckpoints } = await signer.signArkTx(
|
|
2388
2600
|
offchainTx.arkTx,
|
|
@@ -2394,27 +2606,26 @@ async function submitOffchainTx(provider, offchainTx, signer, hooks) {
|
|
|
2394
2606
|
);
|
|
2395
2607
|
}
|
|
2396
2608
|
await hooks?.beforeSubmit?.();
|
|
2397
|
-
const
|
|
2609
|
+
const response = await provider.submitTx(
|
|
2398
2610
|
base64.encode(signedArkTx.toPSBT()),
|
|
2399
2611
|
offchainTx.checkpoints.map((c) => base64.encode(c.toPSBT()))
|
|
2400
2612
|
);
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
);
|
|
2405
|
-
}
|
|
2613
|
+
assertSubmittedArkTxid(response, signedArkTx, "submitTx");
|
|
2614
|
+
const { arkTxid, signedCheckpointTxs } = response;
|
|
2615
|
+
const matched = matchServerCheckpoints(signedCheckpointTxs, offchainTx.checkpoints, "submitTx");
|
|
2406
2616
|
let finalCheckpoints;
|
|
2407
2617
|
if (userSignedCheckpoints) {
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2618
|
+
const userByTxid = new Map(
|
|
2619
|
+
userSignedCheckpoints.map((c, i) => [offchainTx.checkpoints[i].id, c])
|
|
2620
|
+
);
|
|
2621
|
+
finalCheckpoints = matched.map(({ server, local }) => {
|
|
2622
|
+
combineTapscriptSigs(userByTxid.get(local.id), server);
|
|
2623
|
+
return base64.encode(server.toPSBT());
|
|
2412
2624
|
});
|
|
2413
2625
|
} else {
|
|
2414
2626
|
finalCheckpoints = await Promise.all(
|
|
2415
|
-
|
|
2416
|
-
const
|
|
2417
|
-
const signed = await signer.signCheckpoint(tx);
|
|
2627
|
+
matched.map(async ({ server }) => {
|
|
2628
|
+
const signed = await signer.signCheckpoint(server);
|
|
2418
2629
|
return base64.encode(signed.toPSBT());
|
|
2419
2630
|
})
|
|
2420
2631
|
);
|
|
@@ -2681,6 +2892,14 @@ function getRandomId() {
|
|
|
2681
2892
|
const randomValue = crypto.getRandomValues(new Uint8Array(16));
|
|
2682
2893
|
return hex.encode(randomValue);
|
|
2683
2894
|
}
|
|
2895
|
+
function isValidArkAddress2(address) {
|
|
2896
|
+
try {
|
|
2897
|
+
ArkAddress.decode(address);
|
|
2898
|
+
return true;
|
|
2899
|
+
} catch (e) {
|
|
2900
|
+
return false;
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2684
2903
|
function validateRecipients(recipients, dustAmount) {
|
|
2685
2904
|
const validatedRecipients = [];
|
|
2686
2905
|
for (const recipient of recipients) {
|
|
@@ -4900,11 +5119,6 @@ function createExitChainResolver(params) {
|
|
|
4900
5119
|
return new OrderedExitChainResolver(sources, params.repository);
|
|
4901
5120
|
}
|
|
4902
5121
|
|
|
4903
|
-
// src/providers/availability.ts
|
|
4904
|
-
function isRetryableProviderError(error) {
|
|
4905
|
-
return error instanceof ProviderUnavailableError || error instanceof FetchError || isFetchTimeoutError(error);
|
|
4906
|
-
}
|
|
4907
|
-
|
|
4908
5122
|
// src/utils/syncCursors.ts
|
|
4909
5123
|
var SAFETY_LAG_MS = 3e4;
|
|
4910
5124
|
var OVERLAP_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -5527,8 +5741,20 @@ function subtractAssets(spent, change) {
|
|
|
5527
5741
|
if (map.size === 0) return void 0;
|
|
5528
5742
|
return Array.from(map, ([assetId, amount]) => ({ assetId, amount }));
|
|
5529
5743
|
}
|
|
5530
|
-
|
|
5744
|
+
function collectArkTxidsNeedingCreatedAt(vtxos) {
|
|
5745
|
+
const ownTxids = new Set(vtxos.map((v) => v.txid));
|
|
5746
|
+
const txids = /* @__PURE__ */ new Set();
|
|
5747
|
+
for (const vtxo of vtxos) {
|
|
5748
|
+
if (vtxo.isSpent && vtxo.arkTxId && !ownTxids.has(vtxo.arkTxId)) {
|
|
5749
|
+
txids.add(vtxo.arkTxId);
|
|
5750
|
+
}
|
|
5751
|
+
}
|
|
5752
|
+
return [...txids];
|
|
5753
|
+
}
|
|
5754
|
+
async function buildTransactionHistory(vtxos, allBoardingTxs, commitmentsToIgnore, resolveTxCreatedAt) {
|
|
5531
5755
|
const fromOldestVtxo = vtxos.map(normalizeVtxo).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
|
|
5756
|
+
const txidsNeedingCreatedAt = resolveTxCreatedAt ? collectArkTxidsNeedingCreatedAt(fromOldestVtxo) : [];
|
|
5757
|
+
const resolvedCreatedAt = resolveTxCreatedAt && txidsNeedingCreatedAt.length > 0 ? await resolveTxCreatedAt(txidsNeedingCreatedAt) : /* @__PURE__ */ new Map();
|
|
5532
5758
|
const unmatchedSettledBoardingTxs = allBoardingTxs.filter(isSettledBoardingReceive).sort((a, b) => a.createdAt - b.createdAt);
|
|
5533
5759
|
const sent = [];
|
|
5534
5760
|
let received = [];
|
|
@@ -5588,7 +5814,7 @@ async function buildTransactionHistory(vtxos, allBoardingTxs, commitmentsToIgnor
|
|
|
5588
5814
|
txTime = changes[0].createdAt.getTime();
|
|
5589
5815
|
} else {
|
|
5590
5816
|
txAmount = spentAmount;
|
|
5591
|
-
txTime =
|
|
5817
|
+
txTime = resolvedCreatedAt.get(vtxo.arkTxId) ?? vtxo.createdAt.getTime() + 1;
|
|
5592
5818
|
}
|
|
5593
5819
|
const assets = subtractAssets(allSpent, changes);
|
|
5594
5820
|
if (txAmount !== 0 || assets) {
|
|
@@ -11088,11 +11314,13 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
11088
11314
|
const response = await contractManager.getContractsWithVtxos();
|
|
11089
11315
|
const allVtxos = response.flatMap((_) => _.vtxos);
|
|
11090
11316
|
const { boardingTxs, commitmentsToIgnore } = await this.getBoardingTxs();
|
|
11091
|
-
const
|
|
11092
|
-
|
|
11093
|
-
|
|
11094
|
-
|
|
11095
|
-
|
|
11317
|
+
const resolveTxCreatedAt = (txids) => fetchVtxoCreatedAtByTxid(this.indexerProvider, txids);
|
|
11318
|
+
return buildTransactionHistory(
|
|
11319
|
+
allVtxos,
|
|
11320
|
+
boardingTxs,
|
|
11321
|
+
commitmentsToIgnore,
|
|
11322
|
+
resolveTxCreatedAt
|
|
11323
|
+
);
|
|
11096
11324
|
}
|
|
11097
11325
|
/**
|
|
11098
11326
|
* Wallet history grouped by registered activity resolvers. With no resolver match,
|
|
@@ -11923,6 +12151,51 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
11923
12151
|
}
|
|
11924
12152
|
return newBoarding.onchainAddress(this.network);
|
|
11925
12153
|
}
|
|
12154
|
+
/**
|
|
12155
|
+
* @see HDWalletCapable.getCurrentSigningDescriptor
|
|
12156
|
+
*/
|
|
12157
|
+
async getCurrentSigningDescriptor() {
|
|
12158
|
+
const provider = this._descriptorProvider;
|
|
12159
|
+
if (!(provider instanceof HDDescriptorProvider)) return void 0;
|
|
12160
|
+
return provider.getCurrentSigningDescriptor();
|
|
12161
|
+
}
|
|
12162
|
+
/**
|
|
12163
|
+
* @see HDWalletCapable.getUsedSigningDescriptors
|
|
12164
|
+
*
|
|
12165
|
+
* Union of the watermark band and the descriptors persisted on contracts:
|
|
12166
|
+
* the band alone would miss rows a restore scan wrote, and the contracts
|
|
12167
|
+
* alone would miss indices allocated for something the wallet never
|
|
12168
|
+
* persisted (a swap, an externally issued invoice).
|
|
12169
|
+
*/
|
|
12170
|
+
async getUsedSigningDescriptors() {
|
|
12171
|
+
const provider = this._descriptorProvider;
|
|
12172
|
+
const descriptors = /* @__PURE__ */ new Set();
|
|
12173
|
+
if (provider instanceof HDDescriptorProvider) {
|
|
12174
|
+
const lastIndexUsed = await provider.getLastIndexUsed();
|
|
12175
|
+
for (let i = 0; i <= (lastIndexUsed ?? -1); i++) {
|
|
12176
|
+
descriptors.add(provider.materializeDescriptorAt(i));
|
|
12177
|
+
}
|
|
12178
|
+
}
|
|
12179
|
+
for (const contract of await this.contractRepository.getContracts()) {
|
|
12180
|
+
const descriptor = contract.metadata?.signingDescriptor;
|
|
12181
|
+
if (typeof descriptor === "string" && descriptor.length > 0) {
|
|
12182
|
+
descriptors.add(descriptor);
|
|
12183
|
+
}
|
|
12184
|
+
}
|
|
12185
|
+
return [...descriptors].sort(
|
|
12186
|
+
(a, b) => signingDescriptorIndex(a) - signingDescriptorIndex(b)
|
|
12187
|
+
);
|
|
12188
|
+
}
|
|
12189
|
+
/**
|
|
12190
|
+
* @see HDWalletCapable.signerForDescriptor
|
|
12191
|
+
*/
|
|
12192
|
+
async signerForDescriptor(descriptor) {
|
|
12193
|
+
const provider = this._descriptorProvider;
|
|
12194
|
+
if (!(provider instanceof HDDescriptorProvider) || !provider.isOurs(descriptor)) {
|
|
12195
|
+
return this.identity;
|
|
12196
|
+
}
|
|
12197
|
+
return new DescriptorIdentity({ descriptor, signer: provider, base: this.identity });
|
|
12198
|
+
}
|
|
11926
12199
|
/**
|
|
11927
12200
|
* Mid-session server-signer rotation (plan §4). When arkd rotates its
|
|
11928
12201
|
* active signer mid-session — the case the long-lived service worker and
|
|
@@ -12733,9 +13006,17 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
12733
13006
|
console.warn("Failed to rotate boarding address after board", e);
|
|
12734
13007
|
}
|
|
12735
13008
|
}
|
|
12736
|
-
async handleSettlementFinalizationEvent(event, inputs, forfeitOutputScript, connectorsGraph) {
|
|
13009
|
+
async handleSettlementFinalizationEvent(event, inputs, forfeitOutputScript, expectedRecipients, connectorsGraph, validatedCommitmentTxid) {
|
|
12737
13010
|
const signedForfeits = [];
|
|
12738
13011
|
let settlementPsbt = Transaction$2.fromPSBT(base64.decode(event.commitmentTx));
|
|
13012
|
+
assertFinalCommitmentMatchesValidated(
|
|
13013
|
+
settlementPsbt,
|
|
13014
|
+
validatedCommitmentTxid,
|
|
13015
|
+
"settlement finalization"
|
|
13016
|
+
);
|
|
13017
|
+
if (!validatedCommitmentTxid && expectedRecipients.length > 0) {
|
|
13018
|
+
validateBatchRecipientsWithoutTree(settlementPsbt, expectedRecipients, this.network);
|
|
13019
|
+
}
|
|
12739
13020
|
let hasBoardingUtxos = false;
|
|
12740
13021
|
let connectorIndex = 0;
|
|
12741
13022
|
const connectorsLeaves = connectorsGraph?.leaves() || [];
|
|
@@ -12858,6 +13139,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
12858
13139
|
*/
|
|
12859
13140
|
createBatchHandler(intentId, inputs, expectedRecipients, session) {
|
|
12860
13141
|
let sweepTapTreeRoot;
|
|
13142
|
+
let validatedCommitmentTxid;
|
|
12861
13143
|
return {
|
|
12862
13144
|
onBatchStarted: async (event) => {
|
|
12863
13145
|
const utf8IntentId = new TextEncoder().encode(intentId);
|
|
@@ -12915,6 +13197,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
12915
13197
|
if (!sharedOutput?.amount) {
|
|
12916
13198
|
throw new Error("Shared output not found");
|
|
12917
13199
|
}
|
|
13200
|
+
validatedCommitmentTxid = commitmentTx.id;
|
|
12918
13201
|
await session.init(vtxoTree, sweepTapTreeRoot, sharedOutput.amount);
|
|
12919
13202
|
const pubkey = hex.encode(await session.getPublicKey());
|
|
12920
13203
|
const nonces = await session.getNonces();
|
|
@@ -12943,7 +13226,9 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
12943
13226
|
event,
|
|
12944
13227
|
inputs,
|
|
12945
13228
|
this.forfeitOutputScript,
|
|
12946
|
-
|
|
13229
|
+
expectedRecipients,
|
|
13230
|
+
connectorTree,
|
|
13231
|
+
validatedCommitmentTxid
|
|
12947
13232
|
);
|
|
12948
13233
|
}
|
|
12949
13234
|
};
|
|
@@ -13102,6 +13387,11 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
13102
13387
|
for (let i = 0; i < vtxos.length; i += MAX_INPUTS_PER_INTENT) {
|
|
13103
13388
|
batches.push(vtxos.slice(i, i + MAX_INPUTS_PER_INTENT));
|
|
13104
13389
|
}
|
|
13390
|
+
const unrollCandidates = this.checkpointUnrollCandidates(await this.arkProvider.getInfo());
|
|
13391
|
+
const checkpointInputs = vtxos.map((vtxo) => ({
|
|
13392
|
+
...vtxo,
|
|
13393
|
+
tapLeafScript: vtxo.forfeitTapLeafScript
|
|
13394
|
+
}));
|
|
13105
13395
|
const seen = /* @__PURE__ */ new Set();
|
|
13106
13396
|
const results = await Promise.all(
|
|
13107
13397
|
batches.map(async (batch) => {
|
|
@@ -13117,6 +13407,12 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
13117
13407
|
const checkpointTxs = pendingTx.signedCheckpointTxs.map(
|
|
13118
13408
|
(c) => Transaction$2.fromPSBT(base64.decode(c))
|
|
13119
13409
|
);
|
|
13410
|
+
assertCheckpointsMatchInputs(
|
|
13411
|
+
checkpointTxs,
|
|
13412
|
+
checkpointInputs,
|
|
13413
|
+
unrollCandidates,
|
|
13414
|
+
`pending tx ${pendingTx.arkTxid}`
|
|
13415
|
+
);
|
|
13120
13416
|
const checkpointJobs = checkpointTxs.map(
|
|
13121
13417
|
(tx) => this.inputSigningJobsFromWitnessUtxos(tx)
|
|
13122
13418
|
);
|
|
@@ -13172,6 +13468,32 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
13172
13468
|
}
|
|
13173
13469
|
return { finalized, pending };
|
|
13174
13470
|
}
|
|
13471
|
+
/**
|
|
13472
|
+
* The server unroll scripts a pending checkpoint may legitimately have been
|
|
13473
|
+
* built under: this wallet's configured one, the server's current one, and
|
|
13474
|
+
* one per deprecated signer (same closure, deprecated key). A tx submitted
|
|
13475
|
+
* before a signer rotation was built under the old key, so rebuilding
|
|
13476
|
+
* against the current key alone would leave it pending forever.
|
|
13477
|
+
*/
|
|
13478
|
+
checkpointUnrollCandidates(info) {
|
|
13479
|
+
const current = CSVMultisigTapscript.decode(hex.decode(info.checkpointTapscript));
|
|
13480
|
+
const candidates = [this._serverUnrollScript, current];
|
|
13481
|
+
for (const deprecated of signerSetFromInfo(info).deprecated.keys()) {
|
|
13482
|
+
candidates.push(
|
|
13483
|
+
CSVMultisigTapscript.encode({
|
|
13484
|
+
...current.params,
|
|
13485
|
+
pubkeys: [hex.decode(deprecated)]
|
|
13486
|
+
})
|
|
13487
|
+
);
|
|
13488
|
+
}
|
|
13489
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13490
|
+
return candidates.filter((candidate) => {
|
|
13491
|
+
const script = hex.encode(candidate.script);
|
|
13492
|
+
if (seen.has(script)) return false;
|
|
13493
|
+
seen.add(script);
|
|
13494
|
+
return true;
|
|
13495
|
+
});
|
|
13496
|
+
}
|
|
13175
13497
|
async hasPendingTxFlag() {
|
|
13176
13498
|
const state = await this.walletRepository.getWalletState();
|
|
13177
13499
|
return state?.settings?.hasPendingTx === true;
|
|
@@ -13404,12 +13726,23 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
13404
13726
|
pendingTxs.push(pendingTx);
|
|
13405
13727
|
}
|
|
13406
13728
|
}
|
|
13729
|
+
if (pendingTxs.length === 0) return result;
|
|
13407
13730
|
const myPkScriptHex = hex.encode(myPkScript);
|
|
13731
|
+
const unrollCandidates = this.checkpointUnrollCandidates(await this.arkProvider.getInfo());
|
|
13408
13732
|
for (const pendingTx of pendingTxs) {
|
|
13409
13733
|
try {
|
|
13410
13734
|
const checkpointTxs = pendingTx.signedCheckpointTxs.map(
|
|
13411
13735
|
(checkpoint) => Transaction$2.fromPSBT(base64.decode(checkpoint))
|
|
13412
13736
|
);
|
|
13737
|
+
assertCheckpointsMatchInputs(
|
|
13738
|
+
checkpointTxs,
|
|
13739
|
+
inputs.map((input) => ({
|
|
13740
|
+
...input,
|
|
13741
|
+
tapLeafScript: input.forfeitTapLeafScript
|
|
13742
|
+
})),
|
|
13743
|
+
unrollCandidates,
|
|
13744
|
+
`pending arkadeCash tx ${pendingTx.arkTxid}`
|
|
13745
|
+
);
|
|
13413
13746
|
const finalCheckpoints = await Promise.all(
|
|
13414
13747
|
checkpointTxs.map(
|
|
13415
13748
|
async (checkpoint) => base64.encode((await identity.sign(checkpoint)).toPSBT())
|
|
@@ -13819,28 +14152,29 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
13819
14152
|
`updateDbAfterOffchainTx: inputs length (${inputs.length}) differs from signedCheckpointTxs length (${signedCheckpointTxs.length})`
|
|
13820
14153
|
);
|
|
13821
14154
|
}
|
|
13822
|
-
const
|
|
14155
|
+
const checkpointIdByOutpoint = /* @__PURE__ */ new Map();
|
|
14156
|
+
for (const encoded of signedCheckpointTxs) {
|
|
14157
|
+
const checkpoint = Transaction$2.fromPSBT(base64.decode(encoded));
|
|
14158
|
+
for (let i = 0; i < checkpoint.inputsLength; i++) {
|
|
14159
|
+
const input = checkpoint.getInput(i);
|
|
14160
|
+
if (!input.txid) continue;
|
|
14161
|
+
checkpointIdByOutpoint.set(
|
|
14162
|
+
`${hex.encode(input.txid)}:${input.index}`,
|
|
14163
|
+
checkpoint.id
|
|
14164
|
+
);
|
|
14165
|
+
}
|
|
14166
|
+
}
|
|
13823
14167
|
const cm = await this.getContractManager();
|
|
13824
14168
|
const annotatedInputs = await cm.annotateVtxos(inputs);
|
|
13825
|
-
for (const
|
|
14169
|
+
for (const vtxo of annotatedInputs) {
|
|
13826
14170
|
const spentFacts = { ...vtxo, isSpent: true };
|
|
13827
|
-
|
|
13828
|
-
|
|
13829
|
-
|
|
13830
|
-
)
|
|
13831
|
-
|
|
13832
|
-
|
|
13833
|
-
|
|
13834
|
-
spentBy: checkpoint.id,
|
|
13835
|
-
arkTxId: arkTxid
|
|
13836
|
-
});
|
|
13837
|
-
} else {
|
|
13838
|
-
spentVtxos.push({
|
|
13839
|
-
...spentFacts,
|
|
13840
|
-
virtualStatus: toVirtualStatus(spentFacts),
|
|
13841
|
-
arkTxId: arkTxid
|
|
13842
|
-
});
|
|
13843
|
-
}
|
|
14171
|
+
const spentBy = checkpointIdByOutpoint.get(`${vtxo.txid}:${vtxo.vout}`);
|
|
14172
|
+
spentVtxos.push({
|
|
14173
|
+
...spentFacts,
|
|
14174
|
+
virtualStatus: toVirtualStatus(spentFacts),
|
|
14175
|
+
...spentBy ? { spentBy } : {},
|
|
14176
|
+
arkTxId: arkTxid
|
|
14177
|
+
});
|
|
13844
14178
|
for (const id of vtxo.commitmentTxIds) {
|
|
13845
14179
|
commitmentTxIds.add(id);
|
|
13846
14180
|
}
|
|
@@ -14607,6 +14941,25 @@ var DustChangeError = class extends Error {
|
|
|
14607
14941
|
this.dustAmount = dustAmount;
|
|
14608
14942
|
}
|
|
14609
14943
|
};
|
|
14944
|
+
var offboardNetworkNames = [
|
|
14945
|
+
"bitcoin",
|
|
14946
|
+
"regtest",
|
|
14947
|
+
"testnet",
|
|
14948
|
+
"signet",
|
|
14949
|
+
"mutinynet"
|
|
14950
|
+
];
|
|
14951
|
+
function offboardDestinationScript(destinationAddress) {
|
|
14952
|
+
for (const networkName of offboardNetworkNames) {
|
|
14953
|
+
try {
|
|
14954
|
+
const network = networks$1[networkName];
|
|
14955
|
+
const addr = Address(network).decode(destinationAddress);
|
|
14956
|
+
return OutScript.encode(addr);
|
|
14957
|
+
} catch {
|
|
14958
|
+
continue;
|
|
14959
|
+
}
|
|
14960
|
+
}
|
|
14961
|
+
throw new Error(`Failed to decode destination address: ${destinationAddress}`);
|
|
14962
|
+
}
|
|
14610
14963
|
var Ramps = class {
|
|
14611
14964
|
/**
|
|
14612
14965
|
* Create convenience wrappers for onboarding and offboarding flows.
|
|
@@ -14744,27 +15097,7 @@ var Ramps = class {
|
|
|
14744
15097
|
throw new DustChangeError(change, dustAmount);
|
|
14745
15098
|
}
|
|
14746
15099
|
amount = amount ?? totalAmount;
|
|
14747
|
-
const
|
|
14748
|
-
"bitcoin",
|
|
14749
|
-
"regtest",
|
|
14750
|
-
"testnet",
|
|
14751
|
-
"signet",
|
|
14752
|
-
"mutinynet"
|
|
14753
|
-
];
|
|
14754
|
-
let destinationScript;
|
|
14755
|
-
for (const networkName of networkNames) {
|
|
14756
|
-
try {
|
|
14757
|
-
const network = networks$1[networkName];
|
|
14758
|
-
const addr = Address(network).decode(destinationAddress);
|
|
14759
|
-
destinationScript = OutScript.encode(addr);
|
|
14760
|
-
break;
|
|
14761
|
-
} catch {
|
|
14762
|
-
continue;
|
|
14763
|
-
}
|
|
14764
|
-
}
|
|
14765
|
-
if (!destinationScript) {
|
|
14766
|
-
throw new Error(`Failed to decode destination address: ${destinationAddress}`);
|
|
14767
|
-
}
|
|
15100
|
+
const destinationScript = offboardDestinationScript(destinationAddress);
|
|
14768
15101
|
const outputFee = estimator.evalOnchainOutput({
|
|
14769
15102
|
amount,
|
|
14770
15103
|
script: hex.encode(destinationScript)
|
|
@@ -15796,47 +16129,15 @@ var WalletMessageHandler = class {
|
|
|
15796
16129
|
return allVtxos;
|
|
15797
16130
|
}
|
|
15798
16131
|
/**
|
|
15799
|
-
* Build transaction history from cached virtual outputs
|
|
15800
|
-
*
|
|
16132
|
+
* Build transaction history from cached virtual outputs, hitting the indexer only for
|
|
16133
|
+
* uncached timestamps. Best-effort, like the plain Wallet path.
|
|
15801
16134
|
*/
|
|
15802
16135
|
async buildTransactionHistoryFromCache(vtxos) {
|
|
15803
16136
|
if (!this.readonlyWallet) return null;
|
|
15804
16137
|
const { boardingTxs, commitmentsToIgnore } = await this.readonlyWallet.getBoardingTxs();
|
|
15805
|
-
const
|
|
15806
|
-
|
|
15807
|
-
|
|
15808
|
-
const ts = vtxo.createdAt.getTime();
|
|
15809
|
-
if (existing === void 0 || ts < existing) {
|
|
15810
|
-
vtxoCreatedAt.set(vtxo.txid, ts);
|
|
15811
|
-
}
|
|
15812
|
-
}
|
|
15813
|
-
if (this.indexerProvider) {
|
|
15814
|
-
const uncachedTxids = /* @__PURE__ */ new Set();
|
|
15815
|
-
for (const vtxo of vtxos) {
|
|
15816
|
-
if (vtxo.isSpent && vtxo.arkTxId && !vtxoCreatedAt.has(vtxo.arkTxId) && !vtxos.some((v) => v.txid === vtxo.arkTxId)) {
|
|
15817
|
-
uncachedTxids.add(vtxo.arkTxId);
|
|
15818
|
-
}
|
|
15819
|
-
}
|
|
15820
|
-
if (uncachedTxids.size > 0) {
|
|
15821
|
-
const outpoints = [...uncachedTxids].map((txid) => ({
|
|
15822
|
-
txid,
|
|
15823
|
-
vout: 0
|
|
15824
|
-
}));
|
|
15825
|
-
const BATCH_SIZE = 100;
|
|
15826
|
-
for (let i = 0; i < outpoints.length; i += BATCH_SIZE) {
|
|
15827
|
-
const res = await getNormalizedVtxos(this.indexerProvider, {
|
|
15828
|
-
outpoints: outpoints.slice(i, i + BATCH_SIZE)
|
|
15829
|
-
});
|
|
15830
|
-
for (const v of res.vtxos) {
|
|
15831
|
-
vtxoCreatedAt.set(v.txid, v.createdAt.getTime());
|
|
15832
|
-
}
|
|
15833
|
-
}
|
|
15834
|
-
}
|
|
15835
|
-
}
|
|
15836
|
-
const getTxCreatedAt = async (txid) => {
|
|
15837
|
-
return vtxoCreatedAt.get(txid);
|
|
15838
|
-
};
|
|
15839
|
-
return buildTransactionHistory(vtxos, boardingTxs, commitmentsToIgnore, getTxCreatedAt);
|
|
16138
|
+
const indexerProvider = this.indexerProvider;
|
|
16139
|
+
const resolveTxCreatedAt = indexerProvider ? (txids) => fetchVtxoCreatedAtByTxid(indexerProvider, txids) : void 0;
|
|
16140
|
+
return buildTransactionHistory(vtxos, boardingTxs, commitmentsToIgnore, resolveTxCreatedAt);
|
|
15840
16141
|
}
|
|
15841
16142
|
async ensureContractEventBroadcasting() {
|
|
15842
16143
|
if (!this.readonlyWallet) return;
|
|
@@ -19013,19 +19314,48 @@ async function estimate(opts) {
|
|
|
19013
19314
|
}
|
|
19014
19315
|
|
|
19015
19316
|
// src/wallet/exit/executor.ts
|
|
19317
|
+
function abortErrorFor(signal) {
|
|
19318
|
+
if (signal.reason !== void 0) return signal.reason;
|
|
19319
|
+
const e = new Error("The operation was aborted");
|
|
19320
|
+
e.name = "AbortError";
|
|
19321
|
+
return e;
|
|
19322
|
+
}
|
|
19323
|
+
function throwIfAborted(signal) {
|
|
19324
|
+
if (signal?.aborted) throw abortErrorFor(signal);
|
|
19325
|
+
}
|
|
19016
19326
|
var Executor = class {
|
|
19017
19327
|
constructor(pkg, provider, opts) {
|
|
19018
19328
|
this.pkg = pkg;
|
|
19019
19329
|
this.provider = provider;
|
|
19020
19330
|
this.pollIntervalMs = opts?.pollIntervalMs ?? 5e3;
|
|
19021
19331
|
this.feeWallet = opts?.feeWallet;
|
|
19332
|
+
this.signal = opts?.signal;
|
|
19022
19333
|
}
|
|
19023
19334
|
pkg;
|
|
19024
19335
|
provider;
|
|
19025
19336
|
pollIntervalMs;
|
|
19026
19337
|
feeWallet;
|
|
19338
|
+
signal;
|
|
19339
|
+
/**
|
|
19340
|
+
* Poll delay. Nearly all wall-clock time is spent here, so it must be the
|
|
19341
|
+
* thing that reacts to abort — checking only between polls would leave
|
|
19342
|
+
* cancellation up to a full interval late and the timer still pending.
|
|
19343
|
+
*/
|
|
19027
19344
|
sleep() {
|
|
19028
|
-
|
|
19345
|
+
const signal = this.signal;
|
|
19346
|
+
if (!signal) return new Promise((r) => setTimeout(r, this.pollIntervalMs));
|
|
19347
|
+
return new Promise((resolve, reject) => {
|
|
19348
|
+
if (signal.aborted) return reject(abortErrorFor(signal));
|
|
19349
|
+
const onAbort = () => {
|
|
19350
|
+
clearTimeout(timer);
|
|
19351
|
+
reject(abortErrorFor(signal));
|
|
19352
|
+
};
|
|
19353
|
+
const timer = setTimeout(() => {
|
|
19354
|
+
signal.removeEventListener("abort", onAbort);
|
|
19355
|
+
resolve();
|
|
19356
|
+
}, this.pollIntervalMs);
|
|
19357
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
19358
|
+
});
|
|
19029
19359
|
}
|
|
19030
19360
|
async status(txid) {
|
|
19031
19361
|
try {
|
|
@@ -19038,10 +19368,12 @@ var Executor = class {
|
|
|
19038
19368
|
for (; ; ) {
|
|
19039
19369
|
const s = await this.status(txid);
|
|
19040
19370
|
if (s?.confirmed) return s;
|
|
19371
|
+
throwIfAborted(this.signal);
|
|
19041
19372
|
await this.sleep();
|
|
19042
19373
|
}
|
|
19043
19374
|
}
|
|
19044
19375
|
async *[Symbol.asyncIterator]() {
|
|
19376
|
+
throwIfAborted(this.signal);
|
|
19045
19377
|
const dead = /* @__PURE__ */ new Set();
|
|
19046
19378
|
if (this.pkg.validUntil && Date.now() / 1e3 > this.pkg.validUntil) {
|
|
19047
19379
|
yield {
|
|
@@ -19153,6 +19485,7 @@ var Executor = class {
|
|
|
19153
19485
|
while (done.size < pending.length) {
|
|
19154
19486
|
for (const { index, step } of pending) {
|
|
19155
19487
|
if (done.has(index)) continue;
|
|
19488
|
+
throwIfAborted(this.signal);
|
|
19156
19489
|
const swept = await this.status(step.txid);
|
|
19157
19490
|
if (swept?.confirmed) {
|
|
19158
19491
|
done.add(index);
|
|
@@ -19500,6 +19833,7 @@ var isVtxoCoin = (input) => isVirtualCoin(input);
|
|
|
19500
19833
|
function createArkadeBatchHandler(intentId, inputs, signer, signedProof, message, session, arkProvider, emulator, network, recipients) {
|
|
19501
19834
|
let batchId;
|
|
19502
19835
|
let sweepTapTreeRoot;
|
|
19836
|
+
let validatedCommitmentTxid;
|
|
19503
19837
|
return {
|
|
19504
19838
|
onBatchStarted: async (event) => {
|
|
19505
19839
|
const utf8IntentId = new TextEncoder().encode(intentId);
|
|
@@ -19535,6 +19869,7 @@ function createArkadeBatchHandler(intentId, inputs, signer, signedProof, message
|
|
|
19535
19869
|
if (!sharedOutput?.amount) {
|
|
19536
19870
|
throw new Error("Shared output not found");
|
|
19537
19871
|
}
|
|
19872
|
+
validatedCommitmentTxid = commitmentTx.id;
|
|
19538
19873
|
await session.init(vtxoTree, sweepTapTreeRoot, sharedOutput.amount);
|
|
19539
19874
|
const pubkey = hex.encode(await session.getPublicKey());
|
|
19540
19875
|
const nonces = await session.getNonces();
|
|
@@ -19558,6 +19893,14 @@ function createArkadeBatchHandler(intentId, inputs, signer, signedProof, message
|
|
|
19558
19893
|
validateConnectorsTxGraph(event.commitmentTx, connectorTree);
|
|
19559
19894
|
}
|
|
19560
19895
|
let commitmentPsbt = Transaction.fromPSBT(base64.decode(event.commitmentTx));
|
|
19896
|
+
assertFinalCommitmentMatchesValidated(
|
|
19897
|
+
commitmentPsbt,
|
|
19898
|
+
validatedCommitmentTxid,
|
|
19899
|
+
"arkade batch finalization"
|
|
19900
|
+
);
|
|
19901
|
+
if (!validatedCommitmentTxid && recipients && recipients.length > 0) {
|
|
19902
|
+
validateBatchRecipientsWithoutTree(commitmentPsbt, recipients, network);
|
|
19903
|
+
}
|
|
19561
19904
|
const signedForfeits = [];
|
|
19562
19905
|
let connectorIndex = 0;
|
|
19563
19906
|
const connectorLeaves = connectorTree?.leaves() || [];
|
|
@@ -20024,11 +20367,11 @@ var ArkadeTransactionBuilder = class {
|
|
|
20024
20367
|
base64.encode(signedArk.toPSBT()),
|
|
20025
20368
|
checkpoints.map((c) => base64.encode(c.toPSBT()))
|
|
20026
20369
|
);
|
|
20370
|
+
assertSubmittedArkTxid(res, signedArk, "submitTx");
|
|
20371
|
+
const matched = matchServerCheckpoints(res.signedCheckpointTxs, checkpoints, "submitTx");
|
|
20027
20372
|
const finalCps = await Promise.all(
|
|
20028
|
-
|
|
20029
|
-
async (
|
|
20030
|
-
(await client.identity.sign(Transaction.fromPSBT(base64.decode(b)), [0])).toPSBT()
|
|
20031
|
-
)
|
|
20373
|
+
matched.map(
|
|
20374
|
+
async ({ server }) => base64.encode((await client.identity.sign(server, [0])).toPSBT())
|
|
20032
20375
|
)
|
|
20033
20376
|
);
|
|
20034
20377
|
await client.arkProvider.finalizeTx(res.arkTxid, finalCps);
|
|
@@ -20112,6 +20455,360 @@ function attachExtension(tx, newPackets) {
|
|
|
20112
20455
|
}
|
|
20113
20456
|
tx.addOutput({ script: newOut.script, amount: newOut.amount });
|
|
20114
20457
|
}
|
|
20458
|
+
|
|
20459
|
+
// src/utils/bip21.ts
|
|
20460
|
+
var BIP21Error = /* @__PURE__ */ ((BIP21Error2) => {
|
|
20461
|
+
BIP21Error2["INVALID_URI"] = "Invalid BIP21 URI";
|
|
20462
|
+
BIP21Error2["INVALID_ADDRESS"] = "Invalid address";
|
|
20463
|
+
return BIP21Error2;
|
|
20464
|
+
})(BIP21Error || {});
|
|
20465
|
+
var BIP21 = class _BIP21 {
|
|
20466
|
+
/**
|
|
20467
|
+
* Create a BIP21 URI from the provided parameters.
|
|
20468
|
+
*
|
|
20469
|
+
* @param params - BIP21 parameters to encode
|
|
20470
|
+
* @returns Encoded BIP21 URI
|
|
20471
|
+
*/
|
|
20472
|
+
static create(params) {
|
|
20473
|
+
const { address, ...options } = params;
|
|
20474
|
+
const queryParams = {};
|
|
20475
|
+
for (const [key, value] of Object.entries(options)) {
|
|
20476
|
+
if (value === void 0) continue;
|
|
20477
|
+
if (key === "amount") {
|
|
20478
|
+
const amount = value;
|
|
20479
|
+
if (!isFinite(amount) || !Number.isSafeInteger(Math.trunc(amount))) {
|
|
20480
|
+
console.warn("Invalid amount");
|
|
20481
|
+
continue;
|
|
20482
|
+
}
|
|
20483
|
+
if (amount < 0) {
|
|
20484
|
+
continue;
|
|
20485
|
+
}
|
|
20486
|
+
queryParams[key] = value;
|
|
20487
|
+
} else if (key === "ark") {
|
|
20488
|
+
if (typeof value === "string" && (value.startsWith("ark") || value.startsWith("tark"))) {
|
|
20489
|
+
queryParams[key] = value;
|
|
20490
|
+
} else {
|
|
20491
|
+
console.warn("Invalid ARK address format");
|
|
20492
|
+
}
|
|
20493
|
+
} else if (key === "sp") {
|
|
20494
|
+
if (typeof value === "string" && value.startsWith("sp")) {
|
|
20495
|
+
queryParams[key] = value;
|
|
20496
|
+
} else {
|
|
20497
|
+
console.warn("Invalid Silent Payment address format");
|
|
20498
|
+
}
|
|
20499
|
+
} else if (typeof value === "string" || typeof value === "number") {
|
|
20500
|
+
queryParams[key] = value;
|
|
20501
|
+
}
|
|
20502
|
+
}
|
|
20503
|
+
const query = Object.keys(queryParams).length > 0 ? "?" + new URLSearchParams(
|
|
20504
|
+
Object.fromEntries(
|
|
20505
|
+
Object.entries(queryParams).map(([k, v]) => [k, String(v)])
|
|
20506
|
+
)
|
|
20507
|
+
).toString() : "";
|
|
20508
|
+
return `bitcoin:${address ? address.toLowerCase() : ""}${query}`;
|
|
20509
|
+
}
|
|
20510
|
+
/**
|
|
20511
|
+
* Integer-sats amount encoded in a BIP21 URI (`amount=` is BTC), or
|
|
20512
|
+
* `undefined` when absent or the URI is unparseable. Shared by the payment
|
|
20513
|
+
* rails, which accept either a bare address or a BIP21 URI.
|
|
20514
|
+
*
|
|
20515
|
+
* @param uri - BIP21 URI (or bare address) to read the amount from
|
|
20516
|
+
* @returns The amount in satoshis, or `undefined`
|
|
20517
|
+
*/
|
|
20518
|
+
static amountSats(uri) {
|
|
20519
|
+
try {
|
|
20520
|
+
const btc = _BIP21.parse(uri).params.amount;
|
|
20521
|
+
return typeof btc === "number" ? Math.round(btc * 1e8) : void 0;
|
|
20522
|
+
} catch {
|
|
20523
|
+
return void 0;
|
|
20524
|
+
}
|
|
20525
|
+
}
|
|
20526
|
+
/**
|
|
20527
|
+
* Parse a BIP21 URI and return its decoded parameters.
|
|
20528
|
+
*
|
|
20529
|
+
* @param uri - BIP21 URI to parse
|
|
20530
|
+
* @returns Parsed BIP21 URI data
|
|
20531
|
+
* @throws Error if the URI does not start with the `bitcoin:` scheme
|
|
20532
|
+
*/
|
|
20533
|
+
static parse(uri) {
|
|
20534
|
+
if (!uri.toLowerCase().startsWith("bitcoin:")) {
|
|
20535
|
+
throw new Error("Invalid BIP21 URI" /* INVALID_URI */);
|
|
20536
|
+
}
|
|
20537
|
+
const withoutPrefix = uri.slice(uri.toLowerCase().indexOf("bitcoin:") + 8);
|
|
20538
|
+
const [address, query] = withoutPrefix.split("?");
|
|
20539
|
+
const params = {};
|
|
20540
|
+
if (address) {
|
|
20541
|
+
params.address = address.toLowerCase();
|
|
20542
|
+
}
|
|
20543
|
+
if (query) {
|
|
20544
|
+
const queryParams = new URLSearchParams(query);
|
|
20545
|
+
for (const [key, value] of queryParams.entries()) {
|
|
20546
|
+
if (!value) continue;
|
|
20547
|
+
if (key === "amount") {
|
|
20548
|
+
if (!/^(?:\d+\.?\d*|\.\d+)$/.test(value)) {
|
|
20549
|
+
continue;
|
|
20550
|
+
}
|
|
20551
|
+
const amount = Number(value);
|
|
20552
|
+
if (!isFinite(amount) || !Number.isSafeInteger(Math.trunc(amount))) {
|
|
20553
|
+
continue;
|
|
20554
|
+
}
|
|
20555
|
+
params[key] = amount;
|
|
20556
|
+
} else if (key === "ark") {
|
|
20557
|
+
if (value.startsWith("ark") || value.startsWith("tark")) {
|
|
20558
|
+
params[key] = value;
|
|
20559
|
+
} else {
|
|
20560
|
+
console.warn("Invalid ARK address format");
|
|
20561
|
+
}
|
|
20562
|
+
} else if (key === "sp") {
|
|
20563
|
+
if (value.startsWith("sp")) {
|
|
20564
|
+
params[key] = value;
|
|
20565
|
+
} else {
|
|
20566
|
+
console.warn("Invalid Silent Payment address format");
|
|
20567
|
+
}
|
|
20568
|
+
} else {
|
|
20569
|
+
params[key] = value;
|
|
20570
|
+
}
|
|
20571
|
+
}
|
|
20572
|
+
}
|
|
20573
|
+
return {
|
|
20574
|
+
originalString: uri,
|
|
20575
|
+
params
|
|
20576
|
+
};
|
|
20577
|
+
}
|
|
20578
|
+
};
|
|
20579
|
+
|
|
20580
|
+
// src/payment/predicates.ts
|
|
20581
|
+
var isLightningInvoice = (raw) => /^ln(bcrt|bc|tbs|tb|sb)[0-9a-z]+$/i.test(raw.replace(/^lightning:/i, ""));
|
|
20582
|
+
var isLnurl = (raw) => /^lnurl/i.test(raw) || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(raw);
|
|
20583
|
+
var BECH32_BTC = /^(bc1|tb1|bcrt1)[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{6,90}$/;
|
|
20584
|
+
var isBech32Btc = (raw) => (raw === raw.toLowerCase() || raw === raw.toUpperCase()) && BECH32_BTC.test(raw.toLowerCase());
|
|
20585
|
+
var isBtcAddress = (raw) => isBech32Btc(raw) || /^[13mn2][a-km-zA-HJ-NP-Z1-9]{25,39}$/.test(raw);
|
|
20586
|
+
|
|
20587
|
+
// src/payment/targets.ts
|
|
20588
|
+
var fromBip21 = (raw, key, accept) => {
|
|
20589
|
+
try {
|
|
20590
|
+
const v = BIP21.parse(raw).params[key];
|
|
20591
|
+
return typeof v === "string" && accept(v) ? v : void 0;
|
|
20592
|
+
} catch {
|
|
20593
|
+
return void 0;
|
|
20594
|
+
}
|
|
20595
|
+
};
|
|
20596
|
+
var arkTarget = (raw) => isValidArkAddress2(raw) ? raw : fromBip21(raw, "ark", isValidArkAddress2);
|
|
20597
|
+
var btcTarget = (raw) => isBtcAddress(raw) ? raw : fromBip21(raw, "address", isBtcAddress);
|
|
20598
|
+
var invoiceTarget = (raw) => {
|
|
20599
|
+
const invoice = isLightningInvoice(raw) ? raw : fromBip21(raw, "lightning", isLightningInvoice);
|
|
20600
|
+
return invoice?.replace(/^lightning:/i, "");
|
|
20601
|
+
};
|
|
20602
|
+
|
|
20603
|
+
// src/payment/amount.ts
|
|
20604
|
+
function assertSendableAmount(railId, amt) {
|
|
20605
|
+
if (!Number.isInteger(amt) || amt <= 0) {
|
|
20606
|
+
throw new Error(`${railId}: invalid amount ${amt} sats (expected a positive integer)`);
|
|
20607
|
+
}
|
|
20608
|
+
}
|
|
20609
|
+
function resolveSendAmount(railId, raw, amount) {
|
|
20610
|
+
const amt = amount ?? BIP21.amountSats(raw);
|
|
20611
|
+
if (amt === void 0) {
|
|
20612
|
+
throw new Error(
|
|
20613
|
+
`${railId}: an amount is required (none provided or encoded in the request)`
|
|
20614
|
+
);
|
|
20615
|
+
}
|
|
20616
|
+
assertSendableAmount(railId, amt);
|
|
20617
|
+
return amt;
|
|
20618
|
+
}
|
|
20619
|
+
function tryResolveSendAmount(raw, amount) {
|
|
20620
|
+
const amt = amount ?? BIP21.amountSats(raw);
|
|
20621
|
+
return amt !== void 0 && Number.isInteger(amt) && amt > 0 ? amt : void 0;
|
|
20622
|
+
}
|
|
20623
|
+
|
|
20624
|
+
// src/payment/handle.ts
|
|
20625
|
+
function makeHandle(id, run) {
|
|
20626
|
+
let status = "pending";
|
|
20627
|
+
let last = { status };
|
|
20628
|
+
const subs = /* @__PURE__ */ new Set();
|
|
20629
|
+
const notify = (f, u) => {
|
|
20630
|
+
try {
|
|
20631
|
+
f(u);
|
|
20632
|
+
} catch {
|
|
20633
|
+
}
|
|
20634
|
+
};
|
|
20635
|
+
const isTerminal = (s) => s === "settled" || s === "failed";
|
|
20636
|
+
const emit = (u) => {
|
|
20637
|
+
status = u.status;
|
|
20638
|
+
last = u;
|
|
20639
|
+
for (const f of subs) notify(f, u);
|
|
20640
|
+
if (isTerminal(u.status)) subs.clear();
|
|
20641
|
+
};
|
|
20642
|
+
const done = run(emit);
|
|
20643
|
+
done.catch((error) => {
|
|
20644
|
+
if (status !== "settled") emit({ status: "failed", error });
|
|
20645
|
+
});
|
|
20646
|
+
return {
|
|
20647
|
+
id,
|
|
20648
|
+
get status() {
|
|
20649
|
+
return status;
|
|
20650
|
+
},
|
|
20651
|
+
subscribe(fn) {
|
|
20652
|
+
notify(fn, last);
|
|
20653
|
+
if (isTerminal(status)) return () => {
|
|
20654
|
+
};
|
|
20655
|
+
subs.add(fn);
|
|
20656
|
+
return () => subs.delete(fn);
|
|
20657
|
+
},
|
|
20658
|
+
settled(opts) {
|
|
20659
|
+
if (!opts?.timeoutMs) return done;
|
|
20660
|
+
let timer;
|
|
20661
|
+
return Promise.race([
|
|
20662
|
+
done,
|
|
20663
|
+
new Promise((_, rej) => {
|
|
20664
|
+
timer = setTimeout(() => rej(new Error("settle timeout")), opts.timeoutMs);
|
|
20665
|
+
})
|
|
20666
|
+
]).finally(() => clearTimeout(timer));
|
|
20667
|
+
}
|
|
20668
|
+
};
|
|
20669
|
+
}
|
|
20670
|
+
|
|
20671
|
+
// src/payment/router.ts
|
|
20672
|
+
var AmbiguousRouteError = class extends Error {
|
|
20673
|
+
constructor(railIds) {
|
|
20674
|
+
super(`ambiguous route: ${railIds.join(", ")}`);
|
|
20675
|
+
this.railIds = railIds;
|
|
20676
|
+
this.name = "AmbiguousRouteError";
|
|
20677
|
+
}
|
|
20678
|
+
railIds;
|
|
20679
|
+
};
|
|
20680
|
+
var PaymentRouter = class {
|
|
20681
|
+
constructor(ctx) {
|
|
20682
|
+
this.ctx = ctx;
|
|
20683
|
+
}
|
|
20684
|
+
ctx;
|
|
20685
|
+
rails = /* @__PURE__ */ new Map();
|
|
20686
|
+
/** Register a rail (or overwrite one with the same id). */
|
|
20687
|
+
use(rail) {
|
|
20688
|
+
this.rails.set(rail.id, rail);
|
|
20689
|
+
return this;
|
|
20690
|
+
}
|
|
20691
|
+
/** Remove a rail by id. */
|
|
20692
|
+
remove(id) {
|
|
20693
|
+
this.rails.delete(id);
|
|
20694
|
+
return this;
|
|
20695
|
+
}
|
|
20696
|
+
/** Rails matching `req`, filtered by `disabled`/`available()`, ranked by
|
|
20697
|
+
* `priority` (unlisted rails keep insertion order, after listed ones). */
|
|
20698
|
+
async options(req, prefs) {
|
|
20699
|
+
const merged = { ...this.ctx.prefs, ...prefs };
|
|
20700
|
+
const ctx = { ...this.ctx, prefs: merged };
|
|
20701
|
+
const matched = [...this.rails.values()].filter(
|
|
20702
|
+
(rail) => !merged.disabled?.includes(rail.id) && rail.match(req, ctx)
|
|
20703
|
+
);
|
|
20704
|
+
const available = (await Promise.all(
|
|
20705
|
+
matched.map(async (rail) => {
|
|
20706
|
+
try {
|
|
20707
|
+
return await rail.available?.(req, ctx) ?? true ? rail : null;
|
|
20708
|
+
} catch (e) {
|
|
20709
|
+
console.warn(`payment rail "${rail.id}": available() threw`, e);
|
|
20710
|
+
return null;
|
|
20711
|
+
}
|
|
20712
|
+
})
|
|
20713
|
+
)).filter((rail) => rail !== null);
|
|
20714
|
+
const rank = (id) => {
|
|
20715
|
+
const i = merged.priority?.indexOf(id) ?? -1;
|
|
20716
|
+
return i < 0 ? Number.MAX_SAFE_INTEGER : i;
|
|
20717
|
+
};
|
|
20718
|
+
available.sort((a, b) => rank(a.id) - rank(b.id));
|
|
20719
|
+
return available.map((rail) => ({
|
|
20720
|
+
railId: rail.id,
|
|
20721
|
+
quote: () => rail.quote(req, ctx)
|
|
20722
|
+
}));
|
|
20723
|
+
}
|
|
20724
|
+
/** Top-ranked option's quote. Throws when nothing routes, or
|
|
20725
|
+
* {@link AmbiguousRouteError} when `tieBreak` is `"require-choice"` and
|
|
20726
|
+
* more than one option survives. */
|
|
20727
|
+
async route(req, prefs) {
|
|
20728
|
+
const options = await this.options(req, prefs);
|
|
20729
|
+
if (options.length === 0) {
|
|
20730
|
+
throw new Error(`no rail for: ${req.raw}`);
|
|
20731
|
+
}
|
|
20732
|
+
const tieBreak = { ...this.ctx.prefs, ...prefs }.tieBreak ?? "first";
|
|
20733
|
+
if (tieBreak === "require-choice" && options.length > 1) {
|
|
20734
|
+
throw new AmbiguousRouteError(options.map((o) => o.railId));
|
|
20735
|
+
}
|
|
20736
|
+
return options[0].quote();
|
|
20737
|
+
}
|
|
20738
|
+
};
|
|
20739
|
+
|
|
20740
|
+
// src/payment/rails/ark.ts
|
|
20741
|
+
function arkRail() {
|
|
20742
|
+
return {
|
|
20743
|
+
id: "ark",
|
|
20744
|
+
match: (req) => arkTarget(req.raw) !== void 0,
|
|
20745
|
+
quote: async (req, ctx) => {
|
|
20746
|
+
const address = arkTarget(req.raw);
|
|
20747
|
+
const amt = resolveSendAmount("ark", req.raw, req.amount);
|
|
20748
|
+
return {
|
|
20749
|
+
railId: "ark",
|
|
20750
|
+
amount: amt,
|
|
20751
|
+
fee: 0,
|
|
20752
|
+
total: amt,
|
|
20753
|
+
send: async () => makeHandle("ark", async (emit) => {
|
|
20754
|
+
const txid = await ctx.wallet.send({ address, amount: amt });
|
|
20755
|
+
const result = { railId: "ark", txid };
|
|
20756
|
+
emit({ status: "settled", result });
|
|
20757
|
+
return result;
|
|
20758
|
+
})
|
|
20759
|
+
};
|
|
20760
|
+
}
|
|
20761
|
+
};
|
|
20762
|
+
}
|
|
20763
|
+
var GROSS_UP_MAX_ROUNDS = 8;
|
|
20764
|
+
function grossUpOffboard(net, feeInfo, script) {
|
|
20765
|
+
const estimator = new Estimator(feeInfo?.intentFee ?? {});
|
|
20766
|
+
const feeAt = (amount) => estimator.evalOnchainOutput({ amount: BigInt(amount), script }).satoshis;
|
|
20767
|
+
let gross = net;
|
|
20768
|
+
for (let i = 0; i < GROSS_UP_MAX_ROUNDS; i++) {
|
|
20769
|
+
const next = net + feeAt(gross);
|
|
20770
|
+
if (next === gross) break;
|
|
20771
|
+
gross = next;
|
|
20772
|
+
}
|
|
20773
|
+
return { gross, fee: gross - net };
|
|
20774
|
+
}
|
|
20775
|
+
function onchainRail() {
|
|
20776
|
+
return {
|
|
20777
|
+
id: "onchain",
|
|
20778
|
+
match: (req) => btcTarget(req.raw) !== void 0,
|
|
20779
|
+
quote: async (req, ctx) => {
|
|
20780
|
+
const address = btcTarget(req.raw);
|
|
20781
|
+
const amt = resolveSendAmount("onchain", req.raw, req.amount);
|
|
20782
|
+
const { fees } = await ctx.wallet.arkProvider.getInfo();
|
|
20783
|
+
const script = hex.encode(offboardDestinationScript(address));
|
|
20784
|
+
const { gross, fee } = grossUpOffboard(amt, fees, script);
|
|
20785
|
+
return {
|
|
20786
|
+
railId: "onchain",
|
|
20787
|
+
amount: amt,
|
|
20788
|
+
fee,
|
|
20789
|
+
total: gross,
|
|
20790
|
+
send: async () => makeHandle("onchain", async (emit) => {
|
|
20791
|
+
const txid = await new Ramps(ctx.wallet).offboard(
|
|
20792
|
+
address,
|
|
20793
|
+
fees,
|
|
20794
|
+
BigInt(gross)
|
|
20795
|
+
);
|
|
20796
|
+
const result = { railId: "onchain", txid };
|
|
20797
|
+
emit({ status: "settled", result });
|
|
20798
|
+
return result;
|
|
20799
|
+
})
|
|
20800
|
+
};
|
|
20801
|
+
}
|
|
20802
|
+
};
|
|
20803
|
+
}
|
|
20804
|
+
|
|
20805
|
+
// src/payment/index.ts
|
|
20806
|
+
function createDefaultPaymentRouter(wallet) {
|
|
20807
|
+
return new PaymentRouter({
|
|
20808
|
+
wallet,
|
|
20809
|
+
prefs: { priority: ["ark", "lightning", "onchain"] }
|
|
20810
|
+
}).use(arkRail()).use(onchainRail());
|
|
20811
|
+
}
|
|
20115
20812
|
var ARKCONTRACT_PREFIX = "arkcontract";
|
|
20116
20813
|
function encodeArkContract(contract) {
|
|
20117
20814
|
const params = new URLSearchParams();
|
|
@@ -20171,6 +20868,6 @@ function isArkContract(str) {
|
|
|
20171
20868
|
return str.startsWith(ARKCONTRACT_PREFIX + "=");
|
|
20172
20869
|
}
|
|
20173
20870
|
|
|
20174
|
-
export { ARKADE_MAGIC, ActivityRegistry, ArkNote, ArkadeCash, ArkadeCashCreateError, 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, hasCandidates, intentMatchesFilter, intentPageBounds, isArkContract, isBatchSignable, isCooperativelyMigratable, isDiscoverable, isMessageBusInitializingError, isMessageBusNotInitializedError,
|
|
20175
|
-
//# sourceMappingURL=chunk-
|
|
20176
|
-
//# sourceMappingURL=chunk-
|
|
20871
|
+
export { ARKADE_MAGIC, ActivityRegistry, AmbiguousRouteError, ArkNote, ArkadeCash, ArkadeCashCreateError, AssetManager, BIP21, BIP21Error, BIP322, Batch, ChainedTxType, ContractManager, ContractRepositoryImpl, ContractWatcher, DB_VERSION, DEFAULT_MESSAGE_TIMEOUTS, DelegateManagerImpl, DelegateNotConfiguredError, DelegatorManagerImpl, DelegatorNotConfiguredError, DescriptorIdentity, 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, PaymentRouter, 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, arkRail, arkTarget, arkade_exports, assertIntentIdUnique, assertSendableAmount, assertSubmittedArkTxid, assetMintResolver, boardingResolver, btcTarget, buildForfeitTx, buildOffchainTx, classifyAgainstSignerSet, classifyContractSigner, closeDatabase, collabExitResolver, combineTapscriptSigs, contractFromArkContract, contractFromArkContractWithAddress, createAssetPacket, createDefaultActivityRegistry, createDefaultPaymentRouter, createExitChainResolver, decodeArkContract, deserializeAssets, deserializeExitPackage, deserializeUtxo, deserializeVtxo, encodeArkContract, extendVirtualCoinForContract, getMigrationStatus, getRandomId, hasBoardingTxExpired, hasCandidates, intentMatchesFilter, intentPageBounds, invoiceTarget, isArkContract, isBatchSignable, isBtcAddress, isCooperativelyMigratable, isDiscoverable, isHDDeterministicSignCapable, isHDWalletCapable, isLightningInvoice, isLnurl, isMessageBusInitializingError, isMessageBusNotInitializedError, isSubdust, isTerminalIntentState, isValidArkAddress, isVtxoExpiringSoon, isVtxoForScript, makeHandle, matchServerCheckpoints, mergeChainedTxType, migrateWalletRepository, onchainRail, openDatabase, requiresMigration, resolveSendAmount, rollbackMigration, saveVtxosForContract, scriptFromArkAddress, selectCoinsWithAsset, selectVirtualCoins, serializeAssets, serializeExitPackage, serializeUtxo, serializeVtxo, setupServiceWorker, signerSetFromInfo, toXOnlySignerHex, tryResolveSendAmount, validateConnectorsTxGraph, validateVtxoTxGraph, verifyTapscriptSignatures, waitForIncomingFunds, warnAndFilterVtxosForScript };
|
|
20872
|
+
//# sourceMappingURL=chunk-QO3IKBYL.js.map
|
|
20873
|
+
//# sourceMappingURL=chunk-QO3IKBYL.js.map
|