@arkade-os/sdk 0.1.4 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.md +157 -174
  2. package/dist/cjs/arknote/index.js +61 -58
  3. package/dist/cjs/bip322/errors.js +13 -0
  4. package/dist/cjs/bip322/index.js +178 -0
  5. package/dist/cjs/forfeit.js +14 -25
  6. package/dist/cjs/identity/singleKey.js +68 -0
  7. package/dist/cjs/index.js +43 -17
  8. package/dist/cjs/providers/ark.js +261 -321
  9. package/dist/cjs/providers/indexer.js +525 -0
  10. package/dist/cjs/providers/onchain.js +193 -15
  11. package/dist/cjs/script/address.js +48 -17
  12. package/dist/cjs/script/base.js +120 -3
  13. package/dist/cjs/script/default.js +18 -4
  14. package/dist/cjs/script/tapscript.js +61 -20
  15. package/dist/cjs/script/vhtlc.js +85 -7
  16. package/dist/cjs/tree/signingSession.js +63 -106
  17. package/dist/cjs/tree/txTree.js +193 -0
  18. package/dist/cjs/tree/validation.js +79 -155
  19. package/dist/cjs/utils/anchor.js +35 -0
  20. package/dist/cjs/utils/arkTransaction.js +108 -0
  21. package/dist/cjs/utils/transactionHistory.js +84 -72
  22. package/dist/cjs/utils/txSizeEstimator.js +12 -0
  23. package/dist/cjs/utils/unknownFields.js +211 -0
  24. package/dist/cjs/wallet/index.js +12 -0
  25. package/dist/cjs/wallet/onchain.js +201 -0
  26. package/dist/cjs/wallet/ramps.js +95 -0
  27. package/dist/cjs/wallet/serviceWorker/db/vtxo/idb.js +32 -0
  28. package/dist/cjs/wallet/serviceWorker/request.js +15 -12
  29. package/dist/cjs/wallet/serviceWorker/response.js +22 -27
  30. package/dist/cjs/wallet/serviceWorker/utils.js +8 -0
  31. package/dist/cjs/wallet/serviceWorker/wallet.js +61 -34
  32. package/dist/cjs/wallet/serviceWorker/worker.js +120 -108
  33. package/dist/cjs/wallet/unroll.js +270 -0
  34. package/dist/cjs/wallet/wallet.js +701 -454
  35. package/dist/esm/arknote/index.js +61 -57
  36. package/dist/esm/bip322/errors.js +9 -0
  37. package/dist/esm/bip322/index.js +174 -0
  38. package/dist/esm/forfeit.js +15 -26
  39. package/dist/esm/identity/singleKey.js +64 -0
  40. package/dist/esm/index.js +31 -12
  41. package/dist/esm/providers/ark.js +259 -320
  42. package/dist/esm/providers/indexer.js +521 -0
  43. package/dist/esm/providers/onchain.js +193 -15
  44. package/dist/esm/script/address.js +48 -17
  45. package/dist/esm/script/base.js +120 -3
  46. package/dist/esm/script/default.js +18 -4
  47. package/dist/esm/script/tapscript.js +61 -20
  48. package/dist/esm/script/vhtlc.js +85 -7
  49. package/dist/esm/tree/signingSession.js +65 -108
  50. package/dist/esm/tree/txTree.js +189 -0
  51. package/dist/esm/tree/validation.js +75 -152
  52. package/dist/esm/utils/anchor.js +31 -0
  53. package/dist/esm/utils/arkTransaction.js +105 -0
  54. package/dist/esm/utils/transactionHistory.js +84 -72
  55. package/dist/esm/utils/txSizeEstimator.js +12 -0
  56. package/dist/esm/utils/unknownFields.js +173 -0
  57. package/dist/esm/wallet/index.js +9 -0
  58. package/dist/esm/wallet/onchain.js +196 -0
  59. package/dist/esm/wallet/ramps.js +91 -0
  60. package/dist/esm/wallet/serviceWorker/db/vtxo/idb.js +32 -0
  61. package/dist/esm/wallet/serviceWorker/request.js +15 -12
  62. package/dist/esm/wallet/serviceWorker/response.js +22 -27
  63. package/dist/esm/wallet/serviceWorker/utils.js +8 -0
  64. package/dist/esm/wallet/serviceWorker/wallet.js +62 -35
  65. package/dist/esm/wallet/serviceWorker/worker.js +120 -108
  66. package/dist/esm/wallet/unroll.js +267 -0
  67. package/dist/esm/wallet/wallet.js +674 -461
  68. package/dist/types/arknote/index.d.ts +40 -13
  69. package/dist/types/bip322/errors.d.ts +6 -0
  70. package/dist/types/bip322/index.d.ts +57 -0
  71. package/dist/types/forfeit.d.ts +2 -14
  72. package/dist/types/identity/singleKey.d.ts +27 -0
  73. package/dist/types/index.d.ts +24 -12
  74. package/dist/types/providers/ark.d.ts +114 -95
  75. package/dist/types/providers/indexer.d.ts +186 -0
  76. package/dist/types/providers/onchain.d.ts +41 -11
  77. package/dist/types/script/address.d.ts +26 -2
  78. package/dist/types/script/base.d.ts +13 -3
  79. package/dist/types/script/default.d.ts +22 -0
  80. package/dist/types/script/tapscript.d.ts +61 -5
  81. package/dist/types/script/vhtlc.d.ts +27 -0
  82. package/dist/types/tree/signingSession.d.ts +5 -5
  83. package/dist/types/tree/txTree.d.ts +28 -0
  84. package/dist/types/tree/validation.d.ts +15 -22
  85. package/dist/types/utils/anchor.d.ts +19 -0
  86. package/dist/types/utils/arkTransaction.d.ts +27 -0
  87. package/dist/types/utils/transactionHistory.d.ts +7 -1
  88. package/dist/types/utils/txSizeEstimator.d.ts +3 -0
  89. package/dist/types/utils/unknownFields.d.ts +83 -0
  90. package/dist/types/wallet/index.d.ts +51 -50
  91. package/dist/types/wallet/onchain.d.ts +49 -0
  92. package/dist/types/wallet/ramps.d.ts +32 -0
  93. package/dist/types/wallet/serviceWorker/db/vtxo/idb.d.ts +2 -0
  94. package/dist/types/wallet/serviceWorker/db/vtxo/index.d.ts +2 -0
  95. package/dist/types/wallet/serviceWorker/request.d.ts +14 -16
  96. package/dist/types/wallet/serviceWorker/response.d.ts +17 -19
  97. package/dist/types/wallet/serviceWorker/utils.d.ts +8 -0
  98. package/dist/types/wallet/serviceWorker/wallet.d.ts +36 -8
  99. package/dist/types/wallet/serviceWorker/worker.d.ts +7 -3
  100. package/dist/types/wallet/unroll.d.ts +102 -0
  101. package/dist/types/wallet/wallet.d.ts +71 -25
  102. package/package.json +37 -35
  103. package/dist/cjs/identity/inMemoryKey.js +0 -40
  104. package/dist/cjs/tree/vtxoTree.js +0 -231
  105. package/dist/cjs/utils/coinselect.js +0 -73
  106. package/dist/cjs/utils/psbt.js +0 -137
  107. package/dist/esm/identity/inMemoryKey.js +0 -36
  108. package/dist/esm/tree/vtxoTree.js +0 -191
  109. package/dist/esm/utils/coinselect.js +0 -69
  110. package/dist/esm/utils/psbt.js +0 -131
  111. package/dist/types/identity/inMemoryKey.d.ts +0 -12
  112. package/dist/types/tree/vtxoTree.d.ts +0 -33
  113. package/dist/types/utils/coinselect.d.ts +0 -21
  114. package/dist/types/utils/psbt.d.ts +0 -11
@@ -1,42 +1,88 @@
1
1
  import { ArkAddress } from "../script/address";
2
2
  import { DefaultVtxo } from "../script/default";
3
- import { SettlementEvent } from "../providers/ark";
4
- import { Addresses, AddressInfo, ArkTransaction, Coin, ExtendedCoin, ExtendedVirtualCoin, IWallet, Outpoint, SendBitcoinParams, SettleParams, WalletBalance, WalletConfig } from ".";
3
+ import { Network, NetworkName } from "../networks";
4
+ import { OnchainProvider } from "../providers/onchain";
5
+ import { SettlementEvent, ArkProvider } from "../providers/ark";
6
+ import { Identity } from "../identity";
7
+ import { ArkTransaction, Coin, ExtendedCoin, ExtendedVirtualCoin, GetVtxosFilter, IWallet, SendBitcoinParams, SettleParams, VirtualCoin, WalletBalance, WalletConfig } from ".";
8
+ import { Bytes } from "@scure/btc-signer/utils";
9
+ import { CSVMultisigTapscript } from "../script/tapscript";
10
+ import { IndexerProvider } from "../providers/indexer";
11
+ export type IncomingFunds = {
12
+ type: "utxo";
13
+ coins: Coin[];
14
+ } | {
15
+ type: "vtxo";
16
+ vtxos: VirtualCoin[];
17
+ };
18
+ /**
19
+ * Main wallet implementation for Bitcoin transactions with Ark protocol support.
20
+ * The wallet does not store any data locally and relies on Ark and onchain
21
+ * providers to fetch UTXOs and VTXOs.
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * // Create a wallet
26
+ * const wallet = await Wallet.create({
27
+ * identity: SingleKey.fromHex('your_private_key'),
28
+ * arkServerUrl: 'https://ark.example.com',
29
+ * esploraUrl: 'https://mempool.space/api'
30
+ * });
31
+ *
32
+ * // Get addresses
33
+ * const arkAddress = await wallet.getAddress();
34
+ * const boardingAddress = await wallet.getBoardingAddress();
35
+ *
36
+ * // Send bitcoin
37
+ * const txid = await wallet.sendBitcoin({
38
+ * address: 'tb1...',
39
+ * amount: 50000
40
+ * });
41
+ * ```
42
+ */
5
43
  export declare class Wallet implements IWallet {
6
- private identity;
7
- private network;
8
- private onchainProvider;
9
- private onchainP2TR;
10
- private arkProvider?;
11
- private arkServerPublicKey?;
12
- readonly offchainTapscript?: DefaultVtxo.Script | undefined;
13
- readonly boardingTapscript?: DefaultVtxo.Script | undefined;
14
- static FEE_RATE: number;
44
+ readonly identity: Identity;
45
+ readonly network: Network;
46
+ readonly networkName: NetworkName;
47
+ readonly onchainProvider: OnchainProvider;
48
+ readonly arkProvider: ArkProvider;
49
+ readonly indexerProvider: IndexerProvider;
50
+ readonly arkServerPublicKey: Bytes;
51
+ readonly offchainTapscript: DefaultVtxo.Script;
52
+ readonly boardingTapscript: DefaultVtxo.Script;
53
+ readonly serverUnrollScript: CSVMultisigTapscript.Type;
54
+ readonly forfeitOutputScript: Bytes;
55
+ readonly dustAmount: bigint;
56
+ static MIN_FEE_RATE: number;
15
57
  private constructor();
16
58
  static create(config: WalletConfig): Promise<Wallet>;
17
- get onchainAddress(): string;
18
- get boardingAddress(): ArkAddress;
19
- get boardingOnchainAddress(): string;
20
- get offchainAddress(): ArkAddress;
21
- getAddress(): Promise<Addresses>;
22
- getAddressInfo(): Promise<AddressInfo>;
59
+ get arkAddress(): ArkAddress;
60
+ getAddress(): Promise<string>;
61
+ getBoardingAddress(): Promise<string>;
23
62
  getBalance(): Promise<WalletBalance>;
24
- getCoins(): Promise<Coin[]>;
25
- getVtxos(): Promise<ExtendedVirtualCoin[]>;
63
+ getVtxos(filter?: GetVtxosFilter): Promise<ExtendedVirtualCoin[]>;
26
64
  private getVirtualCoins;
27
65
  getTransactionHistory(): Promise<ArkTransaction[]>;
28
66
  getBoardingTxs(): Promise<{
29
67
  boardingTxs: ArkTransaction[];
30
- roundsToIgnore: Set<string>;
68
+ commitmentsToIgnore: Set<string>;
31
69
  }>;
32
70
  getBoardingUtxos(): Promise<ExtendedCoin[]>;
33
- sendBitcoin(params: SendBitcoinParams, zeroFee?: boolean): Promise<string>;
34
- private isOffchainSuitable;
35
- private sendOnchain;
36
- private sendOffchain;
71
+ sendBitcoin(params: SendBitcoinParams): Promise<string>;
37
72
  settle(params?: SettleParams, eventCallback?: (event: SettlementEvent) => void): Promise<string>;
38
- exit(outpoints?: Outpoint[]): Promise<void>;
73
+ notifyIncomingFunds(eventCallback: (coins: IncomingFunds) => void): Promise<() => void>;
74
+ private handleBatchStartedEvent;
39
75
  private handleSettlementSigningEvent;
40
76
  private handleSettlementSigningNoncesGeneratedEvent;
41
77
  private handleSettlementFinalizationEvent;
78
+ private makeRegisterIntentSignature;
79
+ private makeDeleteIntentSignature;
80
+ private prepareBIP322Inputs;
81
+ private makeBIP322Signature;
42
82
  }
83
+ /**
84
+ * Wait for incoming funds to the wallet
85
+ * @param wallet - The wallet to wait for incoming funds
86
+ * @returns A promise that resolves the next new coins received by the wallet's address
87
+ */
88
+ export declare function waitForIncomingFunds(wallet: Wallet): Promise<IncomingFunds>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkade-os/sdk",
3
- "version": "0.1.4",
3
+ "version": "0.2.1",
4
4
  "description": "Bitcoin wallet SDK with Taproot and Ark integration",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
@@ -22,6 +22,37 @@
22
22
  "access": "public",
23
23
  "registry": "https://registry.npmjs.org/"
24
24
  },
25
+ "scripts": {
26
+ "build": "rimraf dist && pnpm run build:esm && node scripts/add-extensions.js && pnpm run build:cjs && pnpm run build:types && node scripts/generate-package-files.js",
27
+ "build:esm": "tsc -p tsconfig.esm.json --outDir dist/esm",
28
+ "build:cjs": "tsc -p tsconfig.cjs.json --outDir dist/cjs",
29
+ "build:types": "tsc -p tsconfig.json --outDir dist/types --emitDeclarationOnly",
30
+ "build:browser": "node scripts/build-browser.js",
31
+ "docs:build": "pnpm run build:types && pnpm typedoc",
32
+ "docs:open": "open ./docs/index.html",
33
+ "test": "vitest run",
34
+ "test:master": "ARK_ENV=master vitest run",
35
+ "test:unit": "vitest run --exclude test/e2e",
36
+ "test:setup": "node test/setup.js",
37
+ "test:setup-docker": "node test/setup.js docker",
38
+ "test:build-docker": "docker compose -f docker-compose.yml build --no-cache",
39
+ "test:up-docker": "docker compose -f docker-compose.yml up -d",
40
+ "test:down-docker": "docker compose -f docker-compose.yml down",
41
+ "test:integration": "vitest run test/e2e/**",
42
+ "test:integration-docker": "ARK_ENV=docker vitest run test/e2e/**",
43
+ "test:watch": "vitest",
44
+ "test:coverage": "vitest run --coverage",
45
+ "test:sw": "pnpm run build:browser && node test/serviceWorker/serve.js",
46
+ "format": "prettier --write src test examples",
47
+ "lint": "prettier --check src test examples",
48
+ "audit": "pnpm audit",
49
+ "preinstall": "npx only-allow pnpm",
50
+ "prepare": "husky",
51
+ "prepublishOnly": "pnpm run build",
52
+ "release": "bash scripts/release.sh",
53
+ "release:dry-run": "bash scripts/release.sh --dry-run",
54
+ "release:cleanup": "bash scripts/release.sh --cleanup"
55
+ },
25
56
  "dependencies": {
26
57
  "@noble/curves": "1.9.1",
27
58
  "@noble/hashes": "1.8.0",
@@ -32,17 +63,13 @@
32
63
  },
33
64
  "devDependencies": {
34
65
  "rimraf": "^5.0.0",
35
- "@eslint/js": "^9.17.0",
36
66
  "@types/node": "22.10.2",
37
- "@typescript-eslint/eslint-plugin": "8.18.2",
38
- "@typescript-eslint/parser": "8.18.2",
39
67
  "@vitest/coverage-v8": "2.1.9",
40
68
  "esbuild": "^0.20.1",
41
- "eslint": "^9.17.0",
42
69
  "glob": "11.0.1",
43
70
  "husky": "9.1.7",
44
- "lint-staged": "15.3.0",
45
71
  "prettier": "3.4.2",
72
+ "typedoc": "^0.27.1",
46
73
  "typescript": "5.7.2",
47
74
  "vitest": "2.1.9"
48
75
  },
@@ -51,38 +78,13 @@
51
78
  "wallet",
52
79
  "taproot",
53
80
  "ark",
54
- "sdk"
81
+ "sdk",
82
+ "arkade"
55
83
  ],
56
84
  "author": "Ark Labs",
57
85
  "license": "MIT",
86
+ "packageManager": "pnpm@9.15.2+sha512.93e57b0126f0df74ce6bff29680394c0ba54ec47246b9cf321f0121d8d9bb03f750a705f24edc3c1180853afd7c2c3b94196d0a3d53d3e069d9e2793ef11f321",
58
87
  "engines": {
59
88
  "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
60
- },
61
- "scripts": {
62
- "build": "rimraf dist && pnpm run build:esm && node scripts/add-extensions.js && pnpm run build:cjs && pnpm run build:types && node scripts/generate-package-files.js",
63
- "build:esm": "tsc -p tsconfig.esm.json --outDir dist/esm",
64
- "build:cjs": "tsc -p tsconfig.cjs.json --outDir dist/cjs",
65
- "build:types": "tsc -p tsconfig.json --outDir dist/types --emitDeclarationOnly",
66
- "build:browser": "node scripts/build-browser.js",
67
- "test": "vitest run",
68
- "test:master": "ARK_ENV=master vitest run",
69
- "test:unit": "vitest run --exclude test/integration.test.ts",
70
- "test:setup": "node test/setup.js",
71
- "test:setup-master": "node test/setup.js master",
72
- "test:up-master": "docker compose -f docker-compose.yml up -d --build",
73
- "test:down-master": "docker compose -f docker-compose.yml down",
74
- "test:integration": "vitest run test/integration.test.ts",
75
- "test:integration-master": "ARK_ENV=master vitest run test/integration.test.ts",
76
- "test:watch": "vitest",
77
- "test:coverage": "vitest run --coverage",
78
- "test:sw": "pnpm run build:browser && node test/serviceWorker/serve.js",
79
- "format": "prettier --write src test examples",
80
- "lint": "prettier --check src test examples",
81
- "audit": "pnpm audit",
82
- "preinstall": "npx only-allow pnpm",
83
- "release": "bash scripts/release.sh",
84
- "release:dry-run": "bash scripts/release.sh --dry-run",
85
- "release:cleanup": "bash scripts/release.sh --cleanup",
86
- "precommit": "lint-staged"
87
89
  }
88
- }
90
+ }
@@ -1,40 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.InMemoryKey = void 0;
4
- const utils_1 = require("@scure/btc-signer/utils");
5
- const base_1 = require("@scure/base");
6
- const signingSession_1 = require("../tree/signingSession");
7
- const ZERO_32 = new Uint8Array(32).fill(0);
8
- class InMemoryKey {
9
- constructor(key) {
10
- this.key = key || (0, utils_1.randomPrivateKeyBytes)();
11
- }
12
- static fromPrivateKey(privateKey) {
13
- return new InMemoryKey(privateKey);
14
- }
15
- static fromHex(privateKeyHex) {
16
- return new InMemoryKey(base_1.hex.decode(privateKeyHex));
17
- }
18
- async sign(tx, inputIndexes) {
19
- const txCpy = tx.clone();
20
- if (!inputIndexes) {
21
- if (!txCpy.sign(this.key, undefined, ZERO_32)) {
22
- throw new Error("Failed to sign transaction");
23
- }
24
- return txCpy;
25
- }
26
- for (const inputIndex of inputIndexes) {
27
- if (!txCpy.signIdx(this.key, inputIndex, undefined, ZERO_32)) {
28
- throw new Error(`Failed to sign input #${inputIndex}`);
29
- }
30
- }
31
- return txCpy;
32
- }
33
- xOnlyPublicKey() {
34
- return (0, utils_1.pubSchnorr)(this.key);
35
- }
36
- signerSession() {
37
- return signingSession_1.TreeSignerSession.random();
38
- }
39
- }
40
- exports.InMemoryKey = InMemoryKey;
@@ -1,231 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.TxTree = exports.ErrParentNotFound = exports.ErrLeafNotFound = exports.TxTreeError = void 0;
37
- exports.getVtxoTreeExpiry = getVtxoTreeExpiry;
38
- exports.getCosignerKeys = getCosignerKeys;
39
- const bip68 = __importStar(require("bip68"));
40
- const btc_signer_1 = require("@scure/btc-signer");
41
- const utils_1 = require("@scure/btc-signer/utils");
42
- const base_1 = require("@scure/base");
43
- class TxTreeError extends Error {
44
- constructor(message) {
45
- super(message);
46
- this.name = "TxTreeError";
47
- }
48
- }
49
- exports.TxTreeError = TxTreeError;
50
- exports.ErrLeafNotFound = new TxTreeError("leaf not found in tx tree");
51
- exports.ErrParentNotFound = new TxTreeError("parent not found");
52
- // TxTree is represented as a matrix of Node objects
53
- // the first level of the matrix is the root of the tree
54
- class TxTree {
55
- constructor(tree) {
56
- this.tree = tree;
57
- }
58
- get levels() {
59
- return this.tree;
60
- }
61
- // Returns the root node of the vtxo tree
62
- root() {
63
- if (this.tree.length <= 0 || this.tree[0].length <= 0) {
64
- throw new TxTreeError("empty vtxo tree");
65
- }
66
- return this.tree[0][0];
67
- }
68
- // Returns the leaves of the vtxo tree
69
- leaves() {
70
- const leaves = [...this.tree[this.tree.length - 1]];
71
- // Check other levels for leaf nodes
72
- for (let i = 0; i < this.tree.length - 1; i++) {
73
- for (const node of this.tree[i]) {
74
- if (node.leaf) {
75
- leaves.push(node);
76
- }
77
- }
78
- }
79
- return leaves;
80
- }
81
- // Returns all nodes that have the given node as parent
82
- children(nodeTxid) {
83
- const children = [];
84
- for (const level of this.tree) {
85
- for (const node of level) {
86
- if (node.parentTxid === nodeTxid) {
87
- children.push(node);
88
- }
89
- }
90
- }
91
- return children;
92
- }
93
- // Returns the total number of nodes in the vtxo tree
94
- numberOfNodes() {
95
- return this.tree.reduce((count, level) => count + level.length, 0);
96
- }
97
- // Returns the branch of the given vtxo txid from root to leaf
98
- branch(vtxoTxid) {
99
- const branch = [];
100
- const leaves = this.leaves();
101
- // Check if the vtxo is a leaf
102
- const leaf = leaves.find((leaf) => leaf.txid === vtxoTxid);
103
- if (!leaf) {
104
- throw exports.ErrLeafNotFound;
105
- }
106
- branch.push(leaf);
107
- const rootTxid = this.root().txid;
108
- while (branch[0].txid !== rootTxid) {
109
- const parent = this.findParent(branch[0]);
110
- branch.unshift(parent);
111
- }
112
- return branch;
113
- }
114
- // Returns the remaining transactions to broadcast in order to exit the vtxo
115
- async exitBranch(vtxoTxid, isTxConfirmed) {
116
- const offchainPart = await getOffchainPart(this.branch(vtxoTxid), isTxConfirmed);
117
- return offchainPart.map(getExitTransaction);
118
- }
119
- // Helper method to find parent of a node
120
- findParent(node) {
121
- for (const level of this.tree) {
122
- for (const potentialParent of level) {
123
- if (potentialParent.txid === node.parentTxid) {
124
- return potentialParent;
125
- }
126
- }
127
- }
128
- throw exports.ErrParentNotFound;
129
- }
130
- // Validates that the tree is coherent by checking txids and parent relationships
131
- validate() {
132
- // Skip the root level, validate from level 1 onwards
133
- for (let i = 1; i < this.tree.length; i++) {
134
- for (const node of this.tree[i]) {
135
- // Verify that the node's transaction matches its claimed txid
136
- const tx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(node.tx));
137
- const txid = base_1.hex.encode((0, utils_1.sha256x2)(tx.toBytes(true)).reverse());
138
- if (txid !== node.txid) {
139
- throw new TxTreeError(`node ${node.txid} has txid ${node.txid}, but computed txid is ${txid}`);
140
- }
141
- // Verify that the node has a valid parent
142
- try {
143
- this.findParent(node);
144
- }
145
- catch (err) {
146
- throw new TxTreeError(`node ${node.txid} has no parent: ${err instanceof Error ? err.message : String(err)}`);
147
- }
148
- }
149
- }
150
- }
151
- }
152
- exports.TxTree = TxTree;
153
- const COSIGNER_KEY_PREFIX = new Uint8Array("cosigner".split("").map((c) => c.charCodeAt(0)));
154
- const VTXO_TREE_EXPIRY_PSBT_KEY = new Uint8Array("expiry".split("").map((c) => c.charCodeAt(0)));
155
- function getVtxoTreeExpiry(input) {
156
- if (!input.unknown)
157
- return null;
158
- for (const u of input.unknown) {
159
- // Check if key contains the VTXO tree expiry key
160
- if (u.key.length < VTXO_TREE_EXPIRY_PSBT_KEY.length)
161
- continue;
162
- let found = true;
163
- for (let i = 0; i < VTXO_TREE_EXPIRY_PSBT_KEY.length; i++) {
164
- if (u.key[i] !== VTXO_TREE_EXPIRY_PSBT_KEY[i]) {
165
- found = false;
166
- break;
167
- }
168
- }
169
- if (found) {
170
- const value = (0, btc_signer_1.ScriptNum)(6, true).decode(u.value);
171
- const { blocks, seconds } = bip68.decode(Number(value));
172
- return {
173
- type: blocks ? "blocks" : "seconds",
174
- value: BigInt(blocks ?? seconds ?? 0),
175
- };
176
- }
177
- }
178
- return null;
179
- }
180
- function parsePrefixedCosignerKey(key) {
181
- if (key.length < COSIGNER_KEY_PREFIX.length)
182
- return false;
183
- for (let i = 0; i < COSIGNER_KEY_PREFIX.length; i++) {
184
- if (key[i] !== COSIGNER_KEY_PREFIX[i])
185
- return false;
186
- }
187
- return true;
188
- }
189
- function getCosignerKeys(tx) {
190
- const keys = [];
191
- const input = tx.getInput(0);
192
- if (!input.unknown)
193
- return keys;
194
- for (const unknown of input.unknown) {
195
- const ok = parsePrefixedCosignerKey(new Uint8Array([unknown[0].type, ...unknown[0].key]));
196
- if (!ok)
197
- continue;
198
- // Assuming the value is already a valid public key in compressed format
199
- keys.push(unknown[1]);
200
- }
201
- return keys;
202
- }
203
- async function getOffchainPart(branch, isTxConfirmed) {
204
- let offchainPath = [...branch];
205
- // Iterate from the end of the branch (leaf) to the beginning (root)
206
- for (let i = branch.length - 1; i >= 0; i--) {
207
- const node = branch[i];
208
- // check if the transaction is confirmed on-chain
209
- if (await isTxConfirmed(node.txid)) {
210
- // if this is the leaf node, return empty array as everything is confirmed
211
- if (i === branch.length - 1) {
212
- return [];
213
- }
214
- // otherwise, return the unconfirmed part of the branch
215
- return branch.slice(i + 1);
216
- }
217
- }
218
- // no confirmation: everything is offchain
219
- return offchainPath;
220
- }
221
- // getExitTransaction finalizes the psbt's input using the musig2 tapkey signature
222
- function getExitTransaction(treeNode) {
223
- const tx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(treeNode.tx));
224
- const input = tx.getInput(0);
225
- if (!input.tapKeySig)
226
- throw new TxTreeError("missing tapkey signature");
227
- const rawTx = btc_signer_1.RawTx.decode(tx.unsignedTx);
228
- rawTx.witnesses = [[input.tapKeySig]];
229
- rawTx.segwitFlag = true;
230
- return base_1.hex.encode(btc_signer_1.RawTx.encode(rawTx));
231
- }
@@ -1,73 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.selectCoins = selectCoins;
4
- exports.selectVirtualCoins = selectVirtualCoins;
5
- /**
6
- * Select coins to reach a target amount, prioritizing those closer to expiry
7
- * @param coins List of coins to select from
8
- * @param targetAmount Target amount to reach in satoshis
9
- * @returns Selected coins and change amount, or null if insufficient funds
10
- */
11
- function selectCoins(coins, targetAmount) {
12
- // Sort coins by amount (descending)
13
- const sortedCoins = [...coins].sort((a, b) => b.value - a.value);
14
- const selectedCoins = [];
15
- let selectedAmount = 0;
16
- // Select coins until we have enough
17
- for (const coin of sortedCoins) {
18
- selectedCoins.push(coin);
19
- selectedAmount += coin.value;
20
- if (selectedAmount >= targetAmount) {
21
- break;
22
- }
23
- }
24
- // Check if we have enough
25
- if (selectedAmount < targetAmount) {
26
- return { inputs: null, changeAmount: 0 };
27
- }
28
- // Calculate change
29
- const changeAmount = selectedAmount - targetAmount;
30
- return {
31
- inputs: selectedCoins,
32
- changeAmount,
33
- };
34
- }
35
- /**
36
- * Select virtual coins to reach a target amount, prioritizing those closer to expiry
37
- * @param coins List of virtual coins to select from
38
- * @param targetAmount Target amount to reach in satoshis
39
- * @returns Selected coins and change amount, or null if insufficient funds
40
- */
41
- function selectVirtualCoins(coins, targetAmount) {
42
- // Sort VTXOs by expiry (ascending) and amount (descending)
43
- const sortedCoins = [...coins].sort((a, b) => {
44
- // First sort by expiry if available
45
- const expiryA = a.virtualStatus.batchExpiry || Number.MAX_SAFE_INTEGER;
46
- const expiryB = b.virtualStatus.batchExpiry || Number.MAX_SAFE_INTEGER;
47
- if (expiryA !== expiryB) {
48
- return expiryA - expiryB; // Earlier expiry first
49
- }
50
- // Then sort by amount
51
- return b.value - a.value; // Larger amount first
52
- });
53
- const selectedCoins = [];
54
- let selectedAmount = 0;
55
- // Select coins until we have enough
56
- for (const coin of sortedCoins) {
57
- selectedCoins.push(coin);
58
- selectedAmount += coin.value;
59
- if (selectedAmount >= targetAmount) {
60
- break;
61
- }
62
- }
63
- // Check if we have enough
64
- if (selectedAmount < targetAmount) {
65
- return { inputs: null, changeAmount: 0 };
66
- }
67
- // Calculate change
68
- const changeAmount = selectedAmount - targetAmount;
69
- return {
70
- inputs: selectedCoins,
71
- changeAmount,
72
- };
73
- }