@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
@@ -3,56 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.vtxosToTxs = vtxosToTxs;
4
4
  const wallet_1 = require("../wallet");
5
5
  /**
6
- * Helper function to find vtxos that were spent in a settlement
7
- */
8
- function findVtxosSpentInSettlement(vtxos, vtxo) {
9
- if (vtxo.virtualStatus.state === "pending") {
10
- return [];
11
- }
12
- return vtxos.filter((v) => {
13
- if (!v.spentBy)
14
- return false;
15
- return v.spentBy === vtxo.virtualStatus.batchTxID;
16
- });
17
- }
18
- /**
19
- * Helper function to find vtxos that were spent in a payment
20
- */
21
- function findVtxosSpentInPayment(vtxos, vtxo) {
22
- return vtxos.filter((v) => {
23
- if (!v.spentBy)
24
- return false;
25
- return v.spentBy === vtxo.txid;
26
- });
27
- }
28
- /**
29
- * Helper function to find vtxos that resulted from a spentBy transaction
30
- */
31
- function findVtxosResultedFromSpentBy(vtxos, spentBy) {
32
- return vtxos.filter((v) => {
33
- if (v.virtualStatus.state !== "pending" &&
34
- v.virtualStatus.batchTxID === spentBy) {
35
- return true;
36
- }
37
- return v.txid === spentBy;
38
- });
39
- }
40
- /**
41
- * Helper function to reduce vtxos to their total amount
6
+ * @param spendable - Vtxos that are spendable
7
+ * @param spent - Vtxos that are spent
8
+ * @param boardingBatchTxids - Set of boarding batch txids
9
+ * @returns Ark transactions
42
10
  */
43
- function reduceVtxosAmount(vtxos) {
44
- return vtxos.reduce((sum, v) => sum + v.value, 0);
45
- }
46
- /**
47
- * Helper function to get a vtxo from a list of vtxos
48
- */
49
- function getVtxo(resultedVtxos, spentVtxos) {
50
- if (resultedVtxos.length === 0) {
51
- return spentVtxos[0];
52
- }
53
- return resultedVtxos[0];
54
- }
55
- function vtxosToTxs(spendable, spent, boardingRounds) {
11
+ function vtxosToTxs(spendable, spent, boardingBatchTxids) {
56
12
  const txs = [];
57
13
  // Receive case
58
14
  // All vtxos are received unless:
@@ -60,8 +16,9 @@ function vtxosToTxs(spendable, spent, boardingRounds) {
60
16
  // - they are the change of a spend tx
61
17
  let vtxosLeftToCheck = [...spent];
62
18
  for (const vtxo of [...spendable, ...spent]) {
63
- if (vtxo.virtualStatus.state !== "pending" &&
64
- boardingRounds.has(vtxo.virtualStatus.batchTxID || "")) {
19
+ if (vtxo.virtualStatus.state !== "preconfirmed" &&
20
+ vtxo.virtualStatus.commitmentTxIds &&
21
+ vtxo.virtualStatus.commitmentTxIds.some((txid) => boardingBatchTxids.has(txid))) {
65
22
  continue;
66
23
  }
67
24
  const settleVtxos = findVtxosSpentInSettlement(vtxosLeftToCheck, vtxo);
@@ -77,13 +34,13 @@ function vtxosToTxs(spendable, spent, boardingRounds) {
77
34
  continue; // settlement or change, ignore
78
35
  }
79
36
  const txKey = {
80
- roundTxid: vtxo.virtualStatus.batchTxID || "",
37
+ commitmentTxid: vtxo.spentBy || "",
81
38
  boardingTxid: "",
82
- redeemTxid: "",
39
+ arkTxid: "",
83
40
  };
84
- let settled = vtxo.virtualStatus.state !== "pending";
85
- if (vtxo.virtualStatus.state === "pending") {
86
- txKey.redeemTxid = vtxo.txid;
41
+ let settled = vtxo.virtualStatus.state !== "preconfirmed";
42
+ if (vtxo.virtualStatus.state === "preconfirmed") {
43
+ txKey.arkTxid = vtxo.txid;
87
44
  if (vtxo.spentBy) {
88
45
  settled = true;
89
46
  }
@@ -96,22 +53,27 @@ function vtxosToTxs(spendable, spent, boardingRounds) {
96
53
  settled,
97
54
  });
98
55
  }
99
- // send case
100
- // All "spentBy" vtxos are payments unless:
101
- // - they are settlements
102
- // aggregate spent by spentId
103
- const vtxosBySpentBy = new Map();
56
+ // vtxos by settled by or ark txid
57
+ const vtxosByTxid = new Map();
104
58
  for (const v of spent) {
105
- if (!v.spentBy)
59
+ if (v.settledBy) {
60
+ if (!vtxosByTxid.has(v.settledBy)) {
61
+ vtxosByTxid.set(v.settledBy, []);
62
+ }
63
+ const currentVtxos = vtxosByTxid.get(v.settledBy);
64
+ vtxosByTxid.set(v.settledBy, [...currentVtxos, v]);
65
+ }
66
+ if (!v.arkTxId) {
106
67
  continue;
107
- if (!vtxosBySpentBy.has(v.spentBy)) {
108
- vtxosBySpentBy.set(v.spentBy, []);
109
68
  }
110
- const currentVtxos = vtxosBySpentBy.get(v.spentBy);
111
- vtxosBySpentBy.set(v.spentBy, [...currentVtxos, v]);
69
+ if (!vtxosByTxid.has(v.arkTxId)) {
70
+ vtxosByTxid.set(v.arkTxId, []);
71
+ }
72
+ const currentVtxos = vtxosByTxid.get(v.arkTxId);
73
+ vtxosByTxid.set(v.arkTxId, [...currentVtxos, v]);
112
74
  }
113
- for (const [sb, vtxos] of vtxosBySpentBy) {
114
- const resultedVtxos = findVtxosResultedFromSpentBy([...spendable, ...spent], sb);
75
+ for (const [sb, vtxos] of vtxosByTxid) {
76
+ const resultedVtxos = findVtxosResultedFromTxid([...spendable, ...spent], sb);
115
77
  const resultedAmount = reduceVtxosAmount(resultedVtxos);
116
78
  const spentAmount = reduceVtxosAmount(vtxos);
117
79
  if (spentAmount <= resultedAmount) {
@@ -119,12 +81,12 @@ function vtxosToTxs(spendable, spent, boardingRounds) {
119
81
  }
120
82
  const vtxo = getVtxo(resultedVtxos, vtxos);
121
83
  const txKey = {
122
- roundTxid: vtxo.virtualStatus.batchTxID || "",
84
+ commitmentTxid: vtxo.virtualStatus.commitmentTxIds?.[0] || "",
123
85
  boardingTxid: "",
124
- redeemTxid: "",
86
+ arkTxid: "",
125
87
  };
126
- if (vtxo.virtualStatus.state === "pending") {
127
- txKey.redeemTxid = vtxo.txid;
88
+ if (vtxo.virtualStatus.state === "preconfirmed") {
89
+ txKey.arkTxid = vtxo.txid;
128
90
  }
129
91
  txs.push({
130
92
  key: txKey,
@@ -136,6 +98,56 @@ function vtxosToTxs(spendable, spent, boardingRounds) {
136
98
  }
137
99
  return txs;
138
100
  }
101
+ /**
102
+ * Helper function to find vtxos that were spent in a settlement
103
+ */
104
+ function findVtxosSpentInSettlement(vtxos, vtxo) {
105
+ if (vtxo.virtualStatus.state === "preconfirmed") {
106
+ return [];
107
+ }
108
+ return vtxos.filter((v) => {
109
+ if (!v.settledBy)
110
+ return false;
111
+ return (vtxo.virtualStatus.commitmentTxIds?.includes(v.settledBy) ?? false);
112
+ });
113
+ }
114
+ /**
115
+ * Helper function to find vtxos that were spent in a payment
116
+ */
117
+ function findVtxosSpentInPayment(vtxos, vtxo) {
118
+ return vtxos.filter((v) => {
119
+ if (!v.arkTxId)
120
+ return false;
121
+ return v.arkTxId === vtxo.txid;
122
+ });
123
+ }
124
+ /**
125
+ * Helper function to find vtxos that resulted from a spentBy transaction
126
+ */
127
+ function findVtxosResultedFromTxid(vtxos, txid) {
128
+ return vtxos.filter((v) => {
129
+ if (v.virtualStatus.state !== "preconfirmed" &&
130
+ v.virtualStatus.commitmentTxIds?.includes(txid)) {
131
+ return true;
132
+ }
133
+ return v.txid === txid;
134
+ });
135
+ }
136
+ /**
137
+ * Helper function to reduce vtxos to their total amount
138
+ */
139
+ function reduceVtxosAmount(vtxos) {
140
+ return vtxos.reduce((sum, v) => sum + v.value, 0);
141
+ }
142
+ /**
143
+ * Helper function to get a vtxo from a list of vtxos
144
+ */
145
+ function getVtxo(resultedVtxos, spentVtxos) {
146
+ if (resultedVtxos.length === 0) {
147
+ return spentVtxos[0];
148
+ }
149
+ return resultedVtxos[0];
150
+ }
139
151
  function removeVtxosFromList(vtxos, vtxosToRemove) {
140
152
  return vtxos.filter((v) => {
141
153
  for (const vtxoToRemove of vtxosToRemove) {
@@ -13,6 +13,11 @@ class TxWeightEstimator {
13
13
  static create() {
14
14
  return new TxWeightEstimator(false, 0, 0, 0, 0, 0);
15
15
  }
16
+ addP2AInput() {
17
+ this.inputCount++;
18
+ this.inputSize += TxWeightEstimator.INPUT_SIZE;
19
+ return this;
20
+ }
16
21
  addKeySpendInput(isDefault = true) {
17
22
  this.inputCount++;
18
23
  this.inputWitnessSize += 64 + 1 + (isDefault ? 0 : 1);
@@ -48,6 +53,12 @@ class TxWeightEstimator {
48
53
  TxWeightEstimator.OUTPUT_SIZE + TxWeightEstimator.P2WKH_OUTPUT_SIZE;
49
54
  return this;
50
55
  }
56
+ addP2TROutput() {
57
+ this.outputCount++;
58
+ this.outputSize +=
59
+ TxWeightEstimator.OUTPUT_SIZE + TxWeightEstimator.P2TR_OUTPUT_SIZE;
60
+ return this;
61
+ }
51
62
  vsize() {
52
63
  const getVarIntSize = (n) => {
53
64
  if (n < 0xfd)
@@ -86,6 +97,7 @@ TxWeightEstimator.P2WKH_OUTPUT_SIZE = 1 + 1 + 20;
86
97
  TxWeightEstimator.BASE_TX_SIZE = 8 + 2; // Version + LockTime
87
98
  TxWeightEstimator.WITNESS_HEADER_SIZE = 2; // Flag + Marker
88
99
  TxWeightEstimator.WITNESS_SCALE_FACTOR = 4;
100
+ TxWeightEstimator.P2TR_OUTPUT_SIZE = 1 + 1 + 32;
89
101
  const vsize = (weight) => {
90
102
  const value = BigInt(Math.ceil(weight / TxWeightEstimator.WITNESS_SCALE_FACTOR));
91
103
  return {
@@ -0,0 +1,211 @@
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.VtxoTreeExpiry = exports.CosignerPublicKey = exports.ConditionWitness = exports.VtxoTaprootTree = exports.ArkPsbtFieldKeyType = exports.ArkPsbtFieldKey = void 0;
37
+ exports.setArkPsbtField = setArkPsbtField;
38
+ exports.getArkPsbtFields = getArkPsbtFields;
39
+ const bip68 = __importStar(require("bip68"));
40
+ const btc_signer_1 = require("@scure/btc-signer");
41
+ const base_1 = require("@scure/base");
42
+ /**
43
+ * ArkPsbtFieldKey is the key values for ark psbt fields.
44
+ */
45
+ var ArkPsbtFieldKey;
46
+ (function (ArkPsbtFieldKey) {
47
+ ArkPsbtFieldKey["VtxoTaprootTree"] = "taptree";
48
+ ArkPsbtFieldKey["VtxoTreeExpiry"] = "expiry";
49
+ ArkPsbtFieldKey["Cosigner"] = "cosigner";
50
+ ArkPsbtFieldKey["ConditionWitness"] = "condition";
51
+ })(ArkPsbtFieldKey || (exports.ArkPsbtFieldKey = ArkPsbtFieldKey = {}));
52
+ /**
53
+ * ArkPsbtFieldKeyType is the type of the ark psbt field key.
54
+ * Every ark psbt field has key type 255.
55
+ */
56
+ exports.ArkPsbtFieldKeyType = 255;
57
+ /**
58
+ * setArkPsbtField appends a new unknown field to the input at inputIndex
59
+ *
60
+ * @example
61
+ * ```typescript
62
+ * setArkPsbtField(tx, 0, VtxoTaprootTree, myTaprootTree);
63
+ * setArkPsbtField(tx, 0, VtxoTreeExpiry, myVtxoTreeExpiry);
64
+ * ```
65
+ */
66
+ function setArkPsbtField(tx, inputIndex, coder, value) {
67
+ tx.updateInput(inputIndex, {
68
+ unknown: [
69
+ ...(tx.getInput(inputIndex)?.unknown ?? []),
70
+ coder.encode(value),
71
+ ],
72
+ });
73
+ }
74
+ /**
75
+ * getArkPsbtFields returns all the values of the given coder for the input at inputIndex
76
+ * Multiple fields of the same type can exist in a single input.
77
+ *
78
+ * @example
79
+ * ```typescript
80
+ * const vtxoTaprootTreeFields = getArkPsbtFields(tx, 0, VtxoTaprootTree);
81
+ * console.log(`input has ${vtxoTaprootTreeFields.length} vtxoTaprootTree fields`);
82
+ */
83
+ function getArkPsbtFields(tx, inputIndex, coder) {
84
+ const unknown = tx.getInput(inputIndex)?.unknown ?? [];
85
+ const fields = [];
86
+ for (const u of unknown) {
87
+ const v = coder.decode(u);
88
+ if (v)
89
+ fields.push(v);
90
+ }
91
+ return fields;
92
+ }
93
+ /**
94
+ * VtxoTaprootTree is set to pass all spending leaves of the vtxo input
95
+ *
96
+ * @example
97
+ * ```typescript
98
+ * const vtxoTaprootTree = VtxoTaprootTree.encode(myTaprootTree);
99
+ */
100
+ exports.VtxoTaprootTree = {
101
+ key: ArkPsbtFieldKey.VtxoTaprootTree,
102
+ encode: (value) => [
103
+ {
104
+ type: exports.ArkPsbtFieldKeyType,
105
+ key: encodedPsbtFieldKey[ArkPsbtFieldKey.VtxoTaprootTree],
106
+ },
107
+ value,
108
+ ],
109
+ decode: (value) => nullIfCatch(() => {
110
+ if (!checkKeyIncludes(value[0], ArkPsbtFieldKey.VtxoTaprootTree))
111
+ return null;
112
+ return value[1];
113
+ }),
114
+ };
115
+ /**
116
+ * ConditionWitness is set to pass the witness data used to finalize the conditionMultisigClosure
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * const conditionWitness = ConditionWitness.encode(myConditionWitness);
121
+ */
122
+ exports.ConditionWitness = {
123
+ key: ArkPsbtFieldKey.ConditionWitness,
124
+ encode: (value) => [
125
+ {
126
+ type: exports.ArkPsbtFieldKeyType,
127
+ key: encodedPsbtFieldKey[ArkPsbtFieldKey.ConditionWitness],
128
+ },
129
+ btc_signer_1.RawWitness.encode(value),
130
+ ],
131
+ decode: (value) => nullIfCatch(() => {
132
+ if (!checkKeyIncludes(value[0], ArkPsbtFieldKey.ConditionWitness))
133
+ return null;
134
+ return btc_signer_1.RawWitness.decode(value[1]);
135
+ }),
136
+ };
137
+ /**
138
+ * CosignerPublicKey is set on every TxGraph transactions to identify the musig2 public keys
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * const cosignerPublicKey = CosignerPublicKey.encode(myCosignerPublicKey);
143
+ */
144
+ exports.CosignerPublicKey = {
145
+ key: ArkPsbtFieldKey.Cosigner,
146
+ encode: (value) => [
147
+ {
148
+ type: exports.ArkPsbtFieldKeyType,
149
+ key: new Uint8Array([
150
+ ...encodedPsbtFieldKey[ArkPsbtFieldKey.Cosigner],
151
+ value.index,
152
+ ]),
153
+ },
154
+ value.key,
155
+ ],
156
+ decode: (unknown) => nullIfCatch(() => {
157
+ if (!checkKeyIncludes(unknown[0], ArkPsbtFieldKey.Cosigner))
158
+ return null;
159
+ return {
160
+ index: unknown[0].key[unknown[0].key.length - 1],
161
+ key: unknown[1],
162
+ };
163
+ }),
164
+ };
165
+ /**
166
+ * VtxoTreeExpiry is set to pass the expiry time of the input
167
+ *
168
+ * @example
169
+ * ```typescript
170
+ * const vtxoTreeExpiry = VtxoTreeExpiry.encode(myVtxoTreeExpiry);
171
+ */
172
+ exports.VtxoTreeExpiry = {
173
+ key: ArkPsbtFieldKey.VtxoTreeExpiry,
174
+ encode: (value) => [
175
+ {
176
+ type: exports.ArkPsbtFieldKeyType,
177
+ key: encodedPsbtFieldKey[ArkPsbtFieldKey.VtxoTreeExpiry],
178
+ },
179
+ (0, btc_signer_1.ScriptNum)(6, true).encode(value.value === 0n ? 0n : value.value),
180
+ ],
181
+ decode: (unknown) => nullIfCatch(() => {
182
+ if (!checkKeyIncludes(unknown[0], ArkPsbtFieldKey.VtxoTreeExpiry))
183
+ return null;
184
+ const v = (0, btc_signer_1.ScriptNum)(6, true).decode(unknown[1]);
185
+ if (!v)
186
+ return null;
187
+ const { blocks, seconds } = bip68.decode(Number(v));
188
+ return {
189
+ type: blocks ? "blocks" : "seconds",
190
+ value: BigInt(blocks ?? seconds ?? 0),
191
+ };
192
+ }),
193
+ };
194
+ const encodedPsbtFieldKey = Object.fromEntries(Object.values(ArkPsbtFieldKey).map((key) => [
195
+ key,
196
+ new TextEncoder().encode(key),
197
+ ]));
198
+ const nullIfCatch = (fn) => {
199
+ try {
200
+ return fn();
201
+ }
202
+ catch (err) {
203
+ return null;
204
+ }
205
+ };
206
+ function checkKeyIncludes(key, arkPsbtFieldKey) {
207
+ const expected = base_1.hex.encode(encodedPsbtFieldKey[arkPsbtFieldKey]);
208
+ return base_1.hex
209
+ .encode(new Uint8Array([key.type, ...key.key]))
210
+ .includes(expected);
211
+ }
@@ -1,8 +1,20 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TxType = void 0;
4
+ exports.isSpendable = isSpendable;
5
+ exports.isRecoverable = isRecoverable;
6
+ exports.isSubdust = isSubdust;
4
7
  var TxType;
5
8
  (function (TxType) {
6
9
  TxType["TxSent"] = "SENT";
7
10
  TxType["TxReceived"] = "RECEIVED";
8
11
  })(TxType || (exports.TxType = TxType = {}));
12
+ function isSpendable(vtxo) {
13
+ return vtxo.spentBy === undefined || vtxo.spentBy === "";
14
+ }
15
+ function isRecoverable(vtxo) {
16
+ return vtxo.virtualStatus.state === "swept" && isSpendable(vtxo);
17
+ }
18
+ function isSubdust(vtxo, dust) {
19
+ return vtxo.value < dust;
20
+ }
@@ -0,0 +1,201 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OnchainWallet = void 0;
4
+ exports.selectCoins = selectCoins;
5
+ const payment_1 = require("@scure/btc-signer/payment");
6
+ const networks_1 = require("../networks");
7
+ const onchain_1 = require("../providers/onchain");
8
+ const btc_signer_1 = require("@scure/btc-signer");
9
+ const anchor_1 = require("../utils/anchor");
10
+ const txSizeEstimator_1 = require("../utils/txSizeEstimator");
11
+ /**
12
+ * Onchain Bitcoin wallet implementation for traditional Bitcoin transactions.
13
+ *
14
+ * This wallet handles regular Bitcoin transactions on the blockchain without
15
+ * using the Ark protocol. It supports P2TR (Pay-to-Taproot) addresses and
16
+ * provides basic Bitcoin wallet functionality.
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * const wallet = new OnchainWallet(identity, 'mainnet');
21
+ * const balance = await wallet.getBalance();
22
+ * const txid = await wallet.send({
23
+ * address: 'bc1...',
24
+ * amount: 50000
25
+ * });
26
+ * ```
27
+ */
28
+ class OnchainWallet {
29
+ constructor(identity, network, provider) {
30
+ this.identity = identity;
31
+ const pubkey = identity.xOnlyPublicKey();
32
+ if (!pubkey) {
33
+ throw new Error("Invalid configured public key");
34
+ }
35
+ this.provider = provider || new onchain_1.EsploraProvider(onchain_1.ESPLORA_URL[network]);
36
+ this.network = (0, networks_1.getNetwork)(network);
37
+ this.onchainP2TR = (0, payment_1.p2tr)(pubkey, undefined, this.network);
38
+ }
39
+ get address() {
40
+ return this.onchainP2TR.address || "";
41
+ }
42
+ async getCoins() {
43
+ return this.provider.getCoins(this.address);
44
+ }
45
+ async getBalance() {
46
+ const coins = await this.getCoins();
47
+ const onchainConfirmed = coins
48
+ .filter((coin) => coin.status.confirmed)
49
+ .reduce((sum, coin) => sum + coin.value, 0);
50
+ const onchainUnconfirmed = coins
51
+ .filter((coin) => !coin.status.confirmed)
52
+ .reduce((sum, coin) => sum + coin.value, 0);
53
+ const onchainTotal = onchainConfirmed + onchainUnconfirmed;
54
+ return onchainTotal;
55
+ }
56
+ async send(params) {
57
+ if (params.amount <= 0) {
58
+ throw new Error("Amount must be positive");
59
+ }
60
+ if (params.amount < OnchainWallet.DUST_AMOUNT) {
61
+ throw new Error("Amount is below dust limit");
62
+ }
63
+ const coins = await this.getCoins();
64
+ let feeRate = params.feeRate;
65
+ if (!feeRate) {
66
+ feeRate = await this.provider.getFeeRate();
67
+ }
68
+ if (!feeRate || feeRate < OnchainWallet.MIN_FEE_RATE) {
69
+ feeRate = OnchainWallet.MIN_FEE_RATE;
70
+ }
71
+ // Ensure fee is an integer by rounding up
72
+ const estimatedFee = Math.ceil(174 * feeRate);
73
+ const totalNeeded = params.amount + estimatedFee;
74
+ // Select coins
75
+ const selected = selectCoins(coins, totalNeeded);
76
+ // Create transaction
77
+ let tx = new btc_signer_1.Transaction();
78
+ // Add inputs
79
+ for (const input of selected.inputs) {
80
+ tx.addInput({
81
+ txid: input.txid,
82
+ index: input.vout,
83
+ witnessUtxo: {
84
+ script: this.onchainP2TR.script,
85
+ amount: BigInt(input.value),
86
+ },
87
+ tapInternalKey: this.onchainP2TR.tapInternalKey,
88
+ });
89
+ }
90
+ // Add payment output
91
+ tx.addOutputAddress(params.address, BigInt(params.amount), this.network);
92
+ // Add change output if needed
93
+ if (selected.changeAmount > 0n) {
94
+ tx.addOutputAddress(this.address, selected.changeAmount, this.network);
95
+ }
96
+ // Sign inputs and Finalize
97
+ tx = await this.identity.sign(tx);
98
+ tx.finalize();
99
+ // Broadcast
100
+ const txid = await this.provider.broadcastTransaction(tx.hex);
101
+ return txid;
102
+ }
103
+ async bumpP2A(parent) {
104
+ const parentVsize = parent.vsize;
105
+ let child = new btc_signer_1.Transaction({
106
+ allowUnknownInputs: true,
107
+ allowLegacyWitnessUtxo: true,
108
+ version: 3,
109
+ });
110
+ child.addInput((0, anchor_1.findP2AOutput)(parent)); // throws if not found
111
+ const childVsize = txSizeEstimator_1.TxWeightEstimator.create()
112
+ .addKeySpendInput(true)
113
+ .addP2AInput()
114
+ .addP2TROutput()
115
+ .vsize().value;
116
+ const packageVSize = parentVsize + Number(childVsize);
117
+ let feeRate = await this.provider.getFeeRate();
118
+ if (!feeRate || feeRate < OnchainWallet.MIN_FEE_RATE) {
119
+ feeRate = OnchainWallet.MIN_FEE_RATE;
120
+ }
121
+ const fee = Math.ceil(feeRate * packageVSize);
122
+ if (!fee) {
123
+ throw new Error(`invalid fee, got ${fee} with vsize ${packageVSize}, feeRate ${feeRate}`);
124
+ }
125
+ // Select coins
126
+ const coins = await this.getCoins();
127
+ const selected = selectCoins(coins, fee, true);
128
+ for (const input of selected.inputs) {
129
+ child.addInput({
130
+ txid: input.txid,
131
+ index: input.vout,
132
+ witnessUtxo: {
133
+ script: this.onchainP2TR.script,
134
+ amount: BigInt(input.value),
135
+ },
136
+ tapInternalKey: this.onchainP2TR.tapInternalKey,
137
+ });
138
+ }
139
+ child.addOutputAddress(this.address, anchor_1.P2A.amount + selected.changeAmount, this.network);
140
+ // Sign inputs and Finalize
141
+ child = await this.identity.sign(child);
142
+ for (let i = 1; i < child.inputsLength; i++) {
143
+ child.finalizeIdx(i);
144
+ }
145
+ try {
146
+ await this.provider.broadcastTransaction(parent.hex, child.hex);
147
+ }
148
+ catch (error) {
149
+ console.error(error);
150
+ }
151
+ finally {
152
+ return [parent.hex, child.hex];
153
+ }
154
+ }
155
+ }
156
+ exports.OnchainWallet = OnchainWallet;
157
+ OnchainWallet.MIN_FEE_RATE = 1; // sat/vbyte
158
+ OnchainWallet.DUST_AMOUNT = 546; // sats
159
+ /**
160
+ * Select coins to reach a target amount, prioritizing those closer to expiry
161
+ * @param coins List of coins to select from
162
+ * @param targetAmount Target amount to reach in satoshis
163
+ * @param forceChange If true, ensure the coin selection will require a change output
164
+ * @returns Selected coins and change amount, or null if insufficient funds
165
+ */
166
+ function selectCoins(coins, targetAmount, forceChange = false) {
167
+ if (isNaN(targetAmount)) {
168
+ throw new Error("Target amount is NaN, got " + targetAmount);
169
+ }
170
+ if (targetAmount < 0) {
171
+ throw new Error("Target amount is negative, got " + targetAmount);
172
+ }
173
+ if (targetAmount === 0) {
174
+ return { inputs: [], changeAmount: 0n };
175
+ }
176
+ // Sort coins by amount (descending)
177
+ const sortedCoins = [...coins].sort((a, b) => b.value - a.value);
178
+ const selectedCoins = [];
179
+ let selectedAmount = 0;
180
+ // Select coins until we have enough
181
+ for (const coin of sortedCoins) {
182
+ selectedCoins.push(coin);
183
+ selectedAmount += coin.value;
184
+ if (forceChange
185
+ ? selectedAmount > targetAmount
186
+ : selectedAmount >= targetAmount) {
187
+ break;
188
+ }
189
+ }
190
+ if (selectedAmount === targetAmount) {
191
+ return { inputs: selectedCoins, changeAmount: 0n };
192
+ }
193
+ if (selectedAmount < targetAmount) {
194
+ throw new Error("Insufficient funds");
195
+ }
196
+ const changeAmount = BigInt(selectedAmount - targetAmount);
197
+ return {
198
+ inputs: selectedCoins,
199
+ changeAmount,
200
+ };
201
+ }