@arkade-os/sdk 0.3.10 → 0.3.12

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 (37) hide show
  1. package/README.md +2 -3
  2. package/dist/cjs/arkfee/celenv.js +43 -0
  3. package/dist/cjs/arkfee/estimator.js +143 -0
  4. package/dist/cjs/arkfee/index.js +5 -0
  5. package/dist/cjs/arkfee/types.js +25 -0
  6. package/dist/cjs/index.js +15 -0
  7. package/dist/cjs/intent/index.js +21 -0
  8. package/dist/cjs/providers/ark.js +2 -9
  9. package/dist/cjs/providers/indexer.js +1 -0
  10. package/dist/cjs/utils/transactionHistory.js +120 -156
  11. package/dist/cjs/wallet/ramps.js +96 -11
  12. package/dist/cjs/wallet/serviceWorker/worker.js +4 -31
  13. package/dist/cjs/wallet/wallet.js +54 -34
  14. package/dist/esm/arkfee/celenv.js +40 -0
  15. package/dist/esm/arkfee/estimator.js +139 -0
  16. package/dist/esm/arkfee/index.js +1 -0
  17. package/dist/esm/arkfee/types.js +21 -0
  18. package/dist/esm/index.js +1 -0
  19. package/dist/esm/intent/index.js +21 -0
  20. package/dist/esm/providers/ark.js +2 -9
  21. package/dist/esm/providers/indexer.js +1 -0
  22. package/dist/esm/utils/transactionHistory.js +119 -155
  23. package/dist/esm/wallet/ramps.js +96 -11
  24. package/dist/esm/wallet/serviceWorker/worker.js +4 -31
  25. package/dist/esm/wallet/wallet.js +54 -34
  26. package/dist/types/arkfee/celenv.d.ts +25 -0
  27. package/dist/types/arkfee/estimator.d.ts +49 -0
  28. package/dist/types/arkfee/index.d.ts +2 -0
  29. package/dist/types/arkfee/types.d.ts +37 -0
  30. package/dist/types/index.d.ts +1 -0
  31. package/dist/types/intent/index.d.ts +1 -0
  32. package/dist/types/providers/ark.d.ts +3 -8
  33. package/dist/types/utils/transactionHistory.d.ts +12 -5
  34. package/dist/types/wallet/index.d.ts +1 -0
  35. package/dist/types/wallet/ramps.d.ts +2 -1
  36. package/dist/types/wallet/serviceWorker/worker.d.ts +0 -1
  37. package/package.json +3 -2
@@ -1,6 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Ramps = void 0;
4
+ const arkfee_1 = require("../arkfee");
5
+ const btc_signer_1 = require("@scure/btc-signer");
6
+ const base_1 = require("@scure/base");
7
+ const networks_1 = require("../networks");
8
+ const address_1 = require("../script/address");
4
9
  /**
5
10
  * Ramps is a class wrapping IWallet.settle method to provide a more convenient interface for onboarding and offboarding operations.
6
11
  *
@@ -18,22 +23,51 @@ class Ramps {
18
23
  /**
19
24
  * Onboard boarding utxos.
20
25
  *
26
+ * @param feeInfo - The fee info to deduct from the onboard amount.
21
27
  * @param boardingUtxos - The boarding utxos to onboard. If not provided, all boarding utxos will be used.
22
28
  * @param amount - The amount to onboard. If not provided, the total amount of boarding utxos will be onboarded.
23
29
  * @param eventCallback - The callback to receive settlement events. optional.
24
30
  */
25
- async onboard(boardingUtxos, amount, eventCallback) {
31
+ async onboard(feeInfo, boardingUtxos, amount, eventCallback) {
26
32
  boardingUtxos = boardingUtxos ?? (await this.wallet.getBoardingUtxos());
27
- const totalAmount = boardingUtxos.reduce((acc, coin) => acc + BigInt(coin.value), 0n);
33
+ // Calculate input fees and filter out utxos where fee >= value
34
+ const estimator = new arkfee_1.Estimator(feeInfo?.intentFee ?? {});
35
+ const filteredBoardingUtxos = [];
36
+ let totalAmount = 0n;
37
+ for (const utxo of boardingUtxos) {
38
+ const inputFee = estimator.evalOnchainInput({
39
+ amount: BigInt(utxo.value),
40
+ });
41
+ if (inputFee.satoshis >= utxo.value) {
42
+ // skip if fees are greater than or equal to the utxo value
43
+ continue;
44
+ }
45
+ filteredBoardingUtxos.push(utxo);
46
+ totalAmount += BigInt(utxo.value) - BigInt(inputFee.satoshis);
47
+ }
48
+ if (filteredBoardingUtxos.length === 0) {
49
+ throw new Error("No boarding utxos available after deducting fees");
50
+ }
28
51
  let change = 0n;
29
52
  if (amount) {
30
53
  if (amount > totalAmount) {
31
- throw new Error("Amount is greater than total amount of boarding utxos");
54
+ throw new Error("Amount is greater than total amount of boarding utxos after fees");
32
55
  }
33
56
  change = totalAmount - amount;
34
57
  }
35
58
  amount = amount ?? totalAmount;
59
+ // Calculate offchain output fee using Estimator
36
60
  const offchainAddress = await this.wallet.getAddress();
61
+ const offchainAddr = address_1.ArkAddress.decode(offchainAddress);
62
+ const offchainScript = base_1.hex.encode(offchainAddr.pkScript);
63
+ const outputFee = estimator.evalOffchainOutput({
64
+ amount,
65
+ script: offchainScript,
66
+ });
67
+ if (BigInt(outputFee.satoshis) > amount) {
68
+ throw new Error(`can't deduct fees from onboard amount (${outputFee.satoshis} > ${amount})`);
69
+ }
70
+ amount -= BigInt(outputFee.satoshis);
37
71
  const outputs = [
38
72
  {
39
73
  address: offchainAddress,
@@ -48,7 +82,7 @@ class Ramps {
48
82
  });
49
83
  }
50
84
  return this.wallet.settle({
51
- inputs: boardingUtxos,
85
+ inputs: filteredBoardingUtxos,
52
86
  outputs,
53
87
  }, eventCallback);
54
88
  }
@@ -65,20 +99,71 @@ class Ramps {
65
99
  withRecoverable: true,
66
100
  withUnrolled: false,
67
101
  });
68
- const totalAmount = vtxos.reduce((acc, coin) => acc + BigInt(coin.value), 0n);
102
+ // Calculate input fees and filter out vtxos where fee >= value
103
+ const estimator = new arkfee_1.Estimator(feeInfo?.intentFee ?? {});
104
+ const filteredVtxos = [];
105
+ let totalAmount = 0n;
106
+ for (const vtxo of vtxos) {
107
+ const inputFee = estimator.evalOffchainInput({
108
+ amount: BigInt(vtxo.value),
109
+ type: vtxo.virtualStatus.state === "swept"
110
+ ? "recoverable"
111
+ : "vtxo",
112
+ weight: 0,
113
+ birth: vtxo.createdAt,
114
+ expiry: vtxo.virtualStatus.batchExpiry
115
+ ? new Date(vtxo.virtualStatus.batchExpiry * 1000)
116
+ : undefined,
117
+ });
118
+ if (inputFee.satoshis >= vtxo.value) {
119
+ // skip if fees are greater than or equal to the vtxo value
120
+ continue;
121
+ }
122
+ filteredVtxos.push(vtxo);
123
+ totalAmount += BigInt(vtxo.value) - BigInt(inputFee.satoshis);
124
+ }
125
+ if (filteredVtxos.length === 0) {
126
+ throw new Error("No vtxos available after deducting fees");
127
+ }
69
128
  let change = 0n;
70
129
  if (amount) {
71
130
  if (amount > totalAmount) {
72
- throw new Error("Amount is greater than total amount of vtxos");
131
+ throw new Error("Amount is greater than total amount of vtxos after fees");
73
132
  }
74
133
  change = totalAmount - amount;
75
134
  }
76
135
  amount = amount ?? totalAmount;
77
- const fees = feeInfo.intentFee.onchainOutput;
78
- if (fees > amount) {
79
- throw new Error(`can't deduct fees from offboard amount (${fees} > ${amount})`);
136
+ const networkNames = [
137
+ "bitcoin",
138
+ "regtest",
139
+ "testnet",
140
+ "signet",
141
+ "mutinynet",
142
+ ];
143
+ let destinationScript;
144
+ for (const networkName of networkNames) {
145
+ try {
146
+ const network = networks_1.networks[networkName];
147
+ const addr = (0, btc_signer_1.Address)(network).decode(destinationAddress);
148
+ destinationScript = btc_signer_1.OutScript.encode(addr);
149
+ break;
150
+ }
151
+ catch {
152
+ // Try next network
153
+ continue;
154
+ }
155
+ }
156
+ if (!destinationScript) {
157
+ throw new Error(`Failed to decode destination address: ${destinationAddress}`);
158
+ }
159
+ const outputFee = estimator.evalOnchainOutput({
160
+ amount,
161
+ script: base_1.hex.encode(destinationScript),
162
+ });
163
+ if (BigInt(outputFee.satoshis) > amount) {
164
+ throw new Error(`can't deduct fees from offboard amount (${outputFee.satoshis} > ${amount})`);
80
165
  }
81
- amount -= fees;
166
+ amount -= BigInt(outputFee.satoshis);
82
167
  const outputs = [
83
168
  {
84
169
  address: destinationAddress,
@@ -93,7 +178,7 @@ class Ramps {
93
178
  });
94
179
  }
95
180
  return this.wallet.settle({
96
- inputs: vtxos,
181
+ inputs: filteredVtxos,
97
182
  outputs,
98
183
  }, eventCallback);
99
184
  }
@@ -8,7 +8,6 @@ const wallet_1 = require("../wallet");
8
8
  const request_1 = require("./request");
9
9
  const response_1 = require("./response");
10
10
  const ark_1 = require("../../providers/ark");
11
- const transactionHistory_1 = require("../../utils/transactionHistory");
12
11
  const indexer_1 = require("../../providers/indexer");
13
12
  const base_1 = require("@scure/base");
14
13
  const indexedDB_1 = require("../../storage/indexedDB");
@@ -43,8 +42,8 @@ class ReadonlyHandler {
43
42
  getBoardingAddress() {
44
43
  return this.wallet.getBoardingAddress();
45
44
  }
46
- getBoardingTxs() {
47
- return this.wallet.getBoardingTxs();
45
+ getTransactionHistory() {
46
+ return this.wallet.getTransactionHistory();
48
47
  }
49
48
  async handleReload(_) {
50
49
  const pending = await this.wallet.fetchPendingTxs();
@@ -130,32 +129,6 @@ class Worker {
130
129
  const address = await this.handler.getBoardingAddress();
131
130
  return await this.walletRepository.getUtxos(address);
132
131
  }
133
- async getTransactionHistory() {
134
- if (!this.handler)
135
- return [];
136
- let txs = [];
137
- try {
138
- const { boardingTxs, commitmentsToIgnore: roundsToIgnore } = await this.handler.getBoardingTxs();
139
- const { spendable, spent } = await this.getAllVtxos();
140
- // convert VTXOs to offchain transactions
141
- const offchainTxs = (0, transactionHistory_1.vtxosToTxs)(spendable, spent, roundsToIgnore);
142
- txs = [...boardingTxs, ...offchainTxs];
143
- // sort transactions by creation time in descending order (newest first)
144
- txs.sort(
145
- // place createdAt = 0 (unconfirmed txs) first, then descending
146
- (a, b) => {
147
- if (a.createdAt === 0)
148
- return -1;
149
- if (b.createdAt === 0)
150
- return 1;
151
- return b.createdAt - a.createdAt;
152
- });
153
- }
154
- catch (error) {
155
- console.error("Error getting transaction history:", error);
156
- }
157
- return txs;
158
- }
159
132
  async start(withServiceWorkerUpdate = true) {
160
133
  self.addEventListener("message", async (event) => {
161
134
  await this.handleMessage(event);
@@ -215,7 +188,7 @@ class Worker {
215
188
  const coins = await this.handler.onchainProvider.getCoins(boardingAddress);
216
189
  await this.walletRepository.saveUtxos(boardingAddress, coins.map((utxo) => (0, utils_1.extendCoin)(this.handler, utxo)));
217
190
  // Get transaction history to cache boarding txs
218
- const txs = await this.getTransactionHistory();
191
+ const txs = await this.handler.getTransactionHistory();
219
192
  if (txs)
220
193
  await this.walletRepository.saveTransactions(address, txs);
221
194
  // unsubscribe previous subscription if any
@@ -564,7 +537,7 @@ class Worker {
564
537
  return;
565
538
  }
566
539
  try {
567
- const txs = await this.getTransactionHistory();
540
+ const txs = await this.handler.getTransactionHistory();
568
541
  event.source?.postMessage(response_1.Response.transactionHistory(message.id, txs));
569
542
  }
570
543
  catch (error) {
@@ -41,7 +41,6 @@ const bip68 = __importStar(require("bip68"));
41
41
  const payment_js_1 = require("@scure/btc-signer/payment.js");
42
42
  const btc_signer_1 = require("@scure/btc-signer");
43
43
  const utils_js_1 = require("@scure/btc-signer/utils.js");
44
- const transactionHistory_1 = require("../utils/transactionHistory");
45
44
  const address_1 = require("../script/address");
46
45
  const default_1 = require("../script/default");
47
46
  const networks_1 = require("../networks");
@@ -64,6 +63,8 @@ const contractRepository_1 = require("../repositories/contractRepository");
64
63
  const utils_1 = require("./utils");
65
64
  const errors_1 = require("../providers/errors");
66
65
  const batch_1 = require("./batch");
66
+ const arkfee_1 = require("../arkfee");
67
+ const transactionHistory_1 = require("../utils/transactionHistory");
67
68
  /**
68
69
  * Type guard function to check if an identity has a toReadonly method.
69
70
  */
@@ -267,30 +268,10 @@ class ReadonlyWallet {
267
268
  scripts: [base_1.hex.encode(this.offchainTapscript.pkScript)],
268
269
  });
269
270
  const { boardingTxs, commitmentsToIgnore } = await this.getBoardingTxs();
270
- const spendableVtxos = [];
271
- const spentVtxos = [];
272
- for (const vtxo of response.vtxos) {
273
- if ((0, _1.isSpendable)(vtxo)) {
274
- spendableVtxos.push(vtxo);
275
- }
276
- else {
277
- spentVtxos.push(vtxo);
278
- }
279
- }
280
- // convert VTXOs to offchain transactions
281
- const offchainTxs = (0, transactionHistory_1.vtxosToTxs)(spendableVtxos, spentVtxos, commitmentsToIgnore);
282
- const txs = [...boardingTxs, ...offchainTxs];
283
- // sort transactions by creation time in descending order (newest first)
284
- txs.sort(
285
- // place createdAt = 0 (unconfirmed txs) first, then descending
286
- (a, b) => {
287
- if (a.createdAt === 0)
288
- return -1;
289
- if (b.createdAt === 0)
290
- return 1;
291
- return b.createdAt - a.createdAt;
292
- });
293
- return txs;
271
+ const getTxCreatedAt = (txid) => this.indexerProvider
272
+ .getVtxos({ outpoints: [{ txid, vout: 0 }] })
273
+ .then((res) => res.vtxos[0]?.createdAt.getTime() || 0);
274
+ return (0, transactionHistory_1.buildTransactionHistory)(response.vtxos, boardingTxs, commitmentsToIgnore, getTxCreatedAt);
294
275
  }
295
276
  async getBoardingTxs() {
296
277
  const utxos = [];
@@ -696,25 +677,64 @@ class Wallet extends ReadonlyWallet {
696
677
  // if no params are provided, use all non expired boarding utxos and offchain vtxos as inputs
697
678
  // and send all to the offchain address
698
679
  if (!params) {
680
+ const { fees } = await this.arkProvider.getInfo();
681
+ const estimator = new arkfee_1.Estimator(fees.intentFee);
699
682
  let amount = 0;
700
683
  const exitScript = tapscript_1.CSVMultisigTapscript.decode(base_1.hex.decode(this.boardingTapscript.exitScript));
701
684
  const boardingTimelock = exitScript.params.timelock;
702
685
  const boardingUtxos = (await this.getBoardingUtxos()).filter((utxo) => !(0, arkTransaction_1.hasBoardingTxExpired)(utxo, boardingTimelock));
703
- amount += boardingUtxos.reduce((sum, input) => sum + input.value, 0);
686
+ const filteredBoardingUtxos = [];
687
+ for (const utxo of boardingUtxos) {
688
+ const inputFee = estimator.evalOnchainInput({
689
+ amount: BigInt(utxo.value),
690
+ });
691
+ if (inputFee.value >= utxo.value) {
692
+ // skip if fees are greater than the utxo value
693
+ continue;
694
+ }
695
+ filteredBoardingUtxos.push(utxo);
696
+ amount += utxo.value - inputFee.satoshis;
697
+ }
704
698
  const vtxos = await this.getVtxos({ withRecoverable: true });
705
- amount += vtxos.reduce((sum, input) => sum + input.value, 0);
706
- const inputs = [...boardingUtxos, ...vtxos];
699
+ const filteredVtxos = [];
700
+ for (const vtxo of vtxos) {
701
+ const inputFee = estimator.evalOffchainInput({
702
+ amount: BigInt(vtxo.value),
703
+ type: vtxo.virtualStatus.state === "swept"
704
+ ? "recoverable"
705
+ : "vtxo",
706
+ weight: 0,
707
+ birth: vtxo.createdAt,
708
+ expiry: vtxo.virtualStatus.batchExpiry
709
+ ? new Date(vtxo.virtualStatus.batchExpiry * 1000)
710
+ : new Date(),
711
+ });
712
+ if (inputFee.value >= vtxo.value) {
713
+ // skip if fees are greater than the vtxo value
714
+ continue;
715
+ }
716
+ filteredVtxos.push(vtxo);
717
+ amount += vtxo.value - inputFee.satoshis;
718
+ }
719
+ const inputs = [...filteredBoardingUtxos, ...filteredVtxos];
707
720
  if (inputs.length === 0) {
708
721
  throw new Error("No inputs found");
709
722
  }
723
+ const output = {
724
+ address: await this.getAddress(),
725
+ amount: BigInt(amount),
726
+ };
727
+ const outputFee = estimator.evalOffchainOutput({
728
+ amount: output.amount,
729
+ script: base_1.hex.encode(address_1.ArkAddress.decode(output.address).pkScript),
730
+ });
731
+ output.amount -= BigInt(outputFee.satoshis);
732
+ if (output.amount <= this.dustAmount) {
733
+ throw new Error("Output amount is below dust limit");
734
+ }
710
735
  params = {
711
736
  inputs,
712
- outputs: [
713
- {
714
- address: await this.getAddress(),
715
- amount: BigInt(amount),
716
- },
717
- ],
737
+ outputs: [output],
718
738
  };
719
739
  }
720
740
  const onchainOutputIndexes = [];
@@ -0,0 +1,40 @@
1
+ import { Environment } from "@marcbachmann/cel-js";
2
+ /**
3
+ * Variable names used in CEL expressions
4
+ */
5
+ export const AmountVariableName = "amount";
6
+ export const ExpiryVariableName = "expiry";
7
+ export const BirthVariableName = "birth";
8
+ export const WeightVariableName = "weight";
9
+ export const InputTypeVariableName = "inputType";
10
+ export const OutputScriptVariableName = "script";
11
+ const nowFunction = {
12
+ signature: "now(): double",
13
+ implementation: () => Math.floor(Date.now() / 1000),
14
+ };
15
+ /**
16
+ * IntentOutputEnv is the CEL environment for output fee calculation
17
+ * Variables: amount, script
18
+ */
19
+ export const IntentOutputEnv = new Environment()
20
+ .registerVariable(AmountVariableName, "double")
21
+ .registerVariable(OutputScriptVariableName, "string")
22
+ .registerFunction(nowFunction.signature, nowFunction.implementation);
23
+ /**
24
+ * IntentOffchainInputEnv is the CEL environment for offchain input fee calculation
25
+ * Variables: amount, expiry, birth, weight, inputType
26
+ */
27
+ export const IntentOffchainInputEnv = new Environment()
28
+ .registerVariable(AmountVariableName, "double")
29
+ .registerVariable(ExpiryVariableName, "double")
30
+ .registerVariable(BirthVariableName, "double")
31
+ .registerVariable(WeightVariableName, "double")
32
+ .registerVariable(InputTypeVariableName, "string")
33
+ .registerFunction(nowFunction.signature, nowFunction.implementation);
34
+ /**
35
+ * IntentOnchainInputEnv is the CEL environment for onchain input fee calculation
36
+ * Variables: amount
37
+ */
38
+ export const IntentOnchainInputEnv = new Environment()
39
+ .registerVariable(AmountVariableName, "double")
40
+ .registerFunction(nowFunction.signature, nowFunction.implementation);
@@ -0,0 +1,139 @@
1
+ import { IntentOffchainInputEnv, IntentOnchainInputEnv, IntentOutputEnv, } from "./celenv.js";
2
+ import { FeeAmount, } from "./types.js";
3
+ /**
4
+ * Estimator evaluates CEL expressions to calculate fees for Ark intents
5
+ */
6
+ export class Estimator {
7
+ /**
8
+ * Creates a new Estimator with the given config
9
+ * @param config - Configuration containing CEL programs for fee calculation
10
+ */
11
+ constructor(config) {
12
+ this.config = config;
13
+ this.intentOffchainInput = config.offchainInput
14
+ ? parseProgram(config.offchainInput, IntentOffchainInputEnv)
15
+ : undefined;
16
+ this.intentOnchainInput = config.onchainInput
17
+ ? parseProgram(config.onchainInput, IntentOnchainInputEnv)
18
+ : undefined;
19
+ this.intentOffchainOutput = config.offchainOutput
20
+ ? parseProgram(config.offchainOutput, IntentOutputEnv)
21
+ : undefined;
22
+ this.intentOnchainOutput = config.onchainOutput
23
+ ? parseProgram(config.onchainOutput, IntentOutputEnv)
24
+ : undefined;
25
+ }
26
+ /**
27
+ * Evaluates the fee for a given vtxo input
28
+ * @param input - The offchain input to evaluate
29
+ * @returns The fee amount for this input
30
+ */
31
+ evalOffchainInput(input) {
32
+ if (!this.intentOffchainInput) {
33
+ return FeeAmount.ZERO;
34
+ }
35
+ const args = inputToArgs(input);
36
+ return new FeeAmount(this.intentOffchainInput.program(args));
37
+ }
38
+ /**
39
+ * Evaluates the fee for a given boarding input
40
+ * @param input - The onchain input to evaluate
41
+ * @returns The fee amount for this input
42
+ */
43
+ evalOnchainInput(input) {
44
+ if (!this.intentOnchainInput) {
45
+ return FeeAmount.ZERO;
46
+ }
47
+ const args = {
48
+ amount: Number(input.amount),
49
+ };
50
+ return new FeeAmount(this.intentOnchainInput.program(args));
51
+ }
52
+ /**
53
+ * Evaluates the fee for a given vtxo output
54
+ * @param output - The output to evaluate
55
+ * @returns The fee amount for this output
56
+ */
57
+ evalOffchainOutput(output) {
58
+ if (!this.intentOffchainOutput) {
59
+ return FeeAmount.ZERO;
60
+ }
61
+ const args = outputToArgs(output);
62
+ return new FeeAmount(this.intentOffchainOutput.program(args));
63
+ }
64
+ /**
65
+ * Evaluates the fee for a given collaborative exit output
66
+ * @param output - The output to evaluate
67
+ * @returns The fee amount for this output
68
+ */
69
+ evalOnchainOutput(output) {
70
+ if (!this.intentOnchainOutput) {
71
+ return FeeAmount.ZERO;
72
+ }
73
+ const args = outputToArgs(output);
74
+ return new FeeAmount(this.intentOnchainOutput.program(args));
75
+ }
76
+ /**
77
+ * Evaluates the fee for a given set of inputs and outputs
78
+ * @param offchainInputs - Array of offchain inputs to evaluate
79
+ * @param onchainInputs - Array of onchain inputs to evaluate
80
+ * @param offchainOutputs - Array of offchain outputs to evaluate
81
+ * @param onchainOutputs - Array of onchain outputs to evaluate
82
+ * @returns The total fee amount
83
+ */
84
+ eval(offchainInputs, onchainInputs, offchainOutputs, onchainOutputs) {
85
+ let fee = FeeAmount.ZERO;
86
+ for (const input of offchainInputs) {
87
+ fee = fee.add(this.evalOffchainInput(input));
88
+ }
89
+ for (const input of onchainInputs) {
90
+ fee = fee.add(this.evalOnchainInput(input));
91
+ }
92
+ for (const output of offchainOutputs) {
93
+ fee = fee.add(this.evalOffchainOutput(output));
94
+ }
95
+ for (const output of onchainOutputs) {
96
+ fee = fee.add(this.evalOnchainOutput(output));
97
+ }
98
+ return fee;
99
+ }
100
+ }
101
+ function inputToArgs(input) {
102
+ const args = {
103
+ amount: Number(input.amount),
104
+ inputType: input.type,
105
+ weight: input.weight,
106
+ };
107
+ if (input.expiry) {
108
+ args.expiry = Math.floor(input.expiry.getTime() / 1000);
109
+ }
110
+ if (input.birth) {
111
+ args.birth = Math.floor(input.birth.getTime() / 1000);
112
+ }
113
+ return args;
114
+ }
115
+ function outputToArgs(output) {
116
+ return {
117
+ amount: Number(output.amount),
118
+ script: output.script,
119
+ };
120
+ }
121
+ /**
122
+ * Parses a CEL program and validates its return type
123
+ * @param text - The CEL program text to parse
124
+ * @param env - The CEL environment to use
125
+ * @returns parsed and validated program
126
+ */
127
+ function parseProgram(text, env) {
128
+ const program = env.parse(text);
129
+ // Type check the program
130
+ const checkResult = program.check();
131
+ if (!checkResult.valid) {
132
+ throw new Error(`type check failed: ${checkResult.error?.message ?? "unknown error"}`);
133
+ }
134
+ // Verify return type is double
135
+ if (checkResult.type !== "double") {
136
+ throw new Error(`expected return type double, got ${checkResult.type}`);
137
+ }
138
+ return { program, text };
139
+ }
@@ -0,0 +1 @@
1
+ export { Estimator } from './estimator.js';
@@ -0,0 +1,21 @@
1
+ /**
2
+ * FeeAmount is a wrapper around a number that represents a fee amount in satoshis floating point.
3
+ * @param value - The fee amount in floating point.
4
+ * @method satoshis - Returns the fee amount in satoshis as a integer.
5
+ * @example
6
+ * const fee = new FeeAmount(1.23456789);
7
+ * console.log(fee.value); // 1.23456789
8
+ * console.log(fee.satoshis); // 2
9
+ */
10
+ export class FeeAmount {
11
+ constructor(value) {
12
+ this.value = value;
13
+ }
14
+ get satoshis() {
15
+ return this.value ? Math.ceil(this.value) : 0;
16
+ }
17
+ add(other) {
18
+ return new FeeAmount(this.value + other.value);
19
+ }
20
+ }
21
+ FeeAmount.ZERO = new FeeAmount(0);
package/dist/esm/index.js CHANGED
@@ -32,6 +32,7 @@ import { ContractRepositoryImpl } from './repositories/contractRepository.js';
32
32
  import { ArkError, maybeArkError } from './providers/errors.js';
33
33
  import { validateVtxoTxGraph, validateConnectorsTxGraph, } from './tree/validation.js';
34
34
  import { buildForfeitTx } from './forfeit.js';
35
+ export * from './arkfee/index.js';
35
36
  export {
36
37
  // Wallets
37
38
  Wallet, ReadonlyWallet, SingleKey, ReadonlySingleKey, OnchainWallet, Ramps, VtxoManager,
@@ -54,6 +54,27 @@ export var Intent;
54
54
  return craftToSignTx(toSpend, inputs, outputs);
55
55
  }
56
56
  Intent.create = create;
57
+ function fee(proof) {
58
+ let sumOfInputs = 0n;
59
+ for (let i = 0; i < proof.inputsLength; i++) {
60
+ const input = proof.getInput(i);
61
+ if (input.witnessUtxo === undefined)
62
+ throw new Error("intent proof input requires witness utxo");
63
+ sumOfInputs += input.witnessUtxo.amount;
64
+ }
65
+ let sumOfOutputs = 0n;
66
+ for (let i = 0; i < proof.outputsLength; i++) {
67
+ const output = proof.getOutput(i);
68
+ if (output.amount === undefined)
69
+ throw new Error("intent proof output requires amount");
70
+ sumOfOutputs += output.amount;
71
+ }
72
+ if (sumOfOutputs > sumOfInputs) {
73
+ throw new Error(`intent proof output amount is greater than input amount: ${sumOfOutputs} > ${sumOfInputs}`);
74
+ }
75
+ return Number(sumOfInputs - sumOfOutputs);
76
+ }
77
+ Intent.fee = fee;
57
78
  function encodeMessage(message) {
58
79
  switch (message.type) {
59
80
  case "register":
@@ -44,15 +44,7 @@ export class RestArkProvider {
44
44
  digest: fromServer.digest ?? "",
45
45
  dust: BigInt(fromServer.dust ?? 0),
46
46
  fees: {
47
- intentFee: {
48
- ...fromServer.fees?.intentFee,
49
- onchainInput: BigInt(
50
- // split(".")[0] to remove the decimal part
51
- (fromServer.fees?.intentFee?.onchainInput ?? "0").split(".")[0] ?? 0),
52
- onchainOutput: BigInt(
53
- // split(".")[0] to remove the decimal part
54
- (fromServer.fees?.intentFee?.onchainOutput ?? "0").split(".")[0] ?? 0),
55
- },
47
+ intentFee: fromServer.fees?.intentFee ?? {},
56
48
  txFeeRate: fromServer?.fees?.txFeeRate ?? "",
57
49
  },
58
50
  forfeitAddress: fromServer.forfeitAddress ?? "",
@@ -65,6 +57,7 @@ export class RestArkProvider {
65
57
  nextStartTime: BigInt(fromServer.scheduledSession.nextStartTime ?? 0),
66
58
  nextEndTime: BigInt(fromServer.scheduledSession.nextEndTime ?? 0),
67
59
  period: BigInt(fromServer.scheduledSession.period ?? 0),
60
+ fees: fromServer.scheduledSession.fees ?? {},
68
61
  }
69
62
  : undefined,
70
63
  serviceStatus: fromServer.serviceStatus ?? {},
@@ -338,6 +338,7 @@ function convertVtxo(vtxo) {
338
338
  value: Number(vtxo.amount),
339
339
  status: {
340
340
  confirmed: !vtxo.isSwept && !vtxo.isPreconfirmed,
341
+ isLeaf: !vtxo.isPreconfirmed,
341
342
  },
342
343
  virtualStatus: {
343
344
  state: vtxo.isSwept