@arkade-os/sdk 0.3.10 → 0.3.11

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 (36) hide show
  1. package/dist/cjs/arkfee/celenv.js +43 -0
  2. package/dist/cjs/arkfee/estimator.js +143 -0
  3. package/dist/cjs/arkfee/index.js +5 -0
  4. package/dist/cjs/arkfee/types.js +25 -0
  5. package/dist/cjs/index.js +15 -0
  6. package/dist/cjs/intent/index.js +21 -0
  7. package/dist/cjs/providers/ark.js +2 -9
  8. package/dist/cjs/providers/indexer.js +1 -0
  9. package/dist/cjs/utils/transactionHistory.js +118 -156
  10. package/dist/cjs/wallet/ramps.js +96 -11
  11. package/dist/cjs/wallet/serviceWorker/worker.js +4 -31
  12. package/dist/cjs/wallet/wallet.js +51 -34
  13. package/dist/esm/arkfee/celenv.js +40 -0
  14. package/dist/esm/arkfee/estimator.js +139 -0
  15. package/dist/esm/arkfee/index.js +1 -0
  16. package/dist/esm/arkfee/types.js +21 -0
  17. package/dist/esm/index.js +1 -0
  18. package/dist/esm/intent/index.js +21 -0
  19. package/dist/esm/providers/ark.js +2 -9
  20. package/dist/esm/providers/indexer.js +1 -0
  21. package/dist/esm/utils/transactionHistory.js +117 -155
  22. package/dist/esm/wallet/ramps.js +96 -11
  23. package/dist/esm/wallet/serviceWorker/worker.js +4 -31
  24. package/dist/esm/wallet/wallet.js +51 -34
  25. package/dist/types/arkfee/celenv.d.ts +25 -0
  26. package/dist/types/arkfee/estimator.d.ts +49 -0
  27. package/dist/types/arkfee/index.d.ts +2 -0
  28. package/dist/types/arkfee/types.d.ts +37 -0
  29. package/dist/types/index.d.ts +1 -0
  30. package/dist/types/intent/index.d.ts +1 -0
  31. package/dist/types/providers/ark.d.ts +3 -8
  32. package/dist/types/utils/transactionHistory.d.ts +12 -5
  33. package/dist/types/wallet/index.d.ts +1 -0
  34. package/dist/types/wallet/ramps.d.ts +2 -1
  35. package/dist/types/wallet/serviceWorker/worker.d.ts +0 -1
  36. package/package.json +3 -2
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IntentOnchainInputEnv = exports.IntentOffchainInputEnv = exports.IntentOutputEnv = exports.OutputScriptVariableName = exports.InputTypeVariableName = exports.WeightVariableName = exports.BirthVariableName = exports.ExpiryVariableName = exports.AmountVariableName = void 0;
4
+ const cel_js_1 = require("@marcbachmann/cel-js");
5
+ /**
6
+ * Variable names used in CEL expressions
7
+ */
8
+ exports.AmountVariableName = "amount";
9
+ exports.ExpiryVariableName = "expiry";
10
+ exports.BirthVariableName = "birth";
11
+ exports.WeightVariableName = "weight";
12
+ exports.InputTypeVariableName = "inputType";
13
+ exports.OutputScriptVariableName = "script";
14
+ const nowFunction = {
15
+ signature: "now(): double",
16
+ implementation: () => Math.floor(Date.now() / 1000),
17
+ };
18
+ /**
19
+ * IntentOutputEnv is the CEL environment for output fee calculation
20
+ * Variables: amount, script
21
+ */
22
+ exports.IntentOutputEnv = new cel_js_1.Environment()
23
+ .registerVariable(exports.AmountVariableName, "double")
24
+ .registerVariable(exports.OutputScriptVariableName, "string")
25
+ .registerFunction(nowFunction.signature, nowFunction.implementation);
26
+ /**
27
+ * IntentOffchainInputEnv is the CEL environment for offchain input fee calculation
28
+ * Variables: amount, expiry, birth, weight, inputType
29
+ */
30
+ exports.IntentOffchainInputEnv = new cel_js_1.Environment()
31
+ .registerVariable(exports.AmountVariableName, "double")
32
+ .registerVariable(exports.ExpiryVariableName, "double")
33
+ .registerVariable(exports.BirthVariableName, "double")
34
+ .registerVariable(exports.WeightVariableName, "double")
35
+ .registerVariable(exports.InputTypeVariableName, "string")
36
+ .registerFunction(nowFunction.signature, nowFunction.implementation);
37
+ /**
38
+ * IntentOnchainInputEnv is the CEL environment for onchain input fee calculation
39
+ * Variables: amount
40
+ */
41
+ exports.IntentOnchainInputEnv = new cel_js_1.Environment()
42
+ .registerVariable(exports.AmountVariableName, "double")
43
+ .registerFunction(nowFunction.signature, nowFunction.implementation);
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Estimator = void 0;
4
+ const celenv_js_1 = require("./celenv.js");
5
+ const types_js_1 = require("./types.js");
6
+ /**
7
+ * Estimator evaluates CEL expressions to calculate fees for Ark intents
8
+ */
9
+ class Estimator {
10
+ /**
11
+ * Creates a new Estimator with the given config
12
+ * @param config - Configuration containing CEL programs for fee calculation
13
+ */
14
+ constructor(config) {
15
+ this.config = config;
16
+ this.intentOffchainInput = config.offchainInput
17
+ ? parseProgram(config.offchainInput, celenv_js_1.IntentOffchainInputEnv)
18
+ : undefined;
19
+ this.intentOnchainInput = config.onchainInput
20
+ ? parseProgram(config.onchainInput, celenv_js_1.IntentOnchainInputEnv)
21
+ : undefined;
22
+ this.intentOffchainOutput = config.offchainOutput
23
+ ? parseProgram(config.offchainOutput, celenv_js_1.IntentOutputEnv)
24
+ : undefined;
25
+ this.intentOnchainOutput = config.onchainOutput
26
+ ? parseProgram(config.onchainOutput, celenv_js_1.IntentOutputEnv)
27
+ : undefined;
28
+ }
29
+ /**
30
+ * Evaluates the fee for a given vtxo input
31
+ * @param input - The offchain input to evaluate
32
+ * @returns The fee amount for this input
33
+ */
34
+ evalOffchainInput(input) {
35
+ if (!this.intentOffchainInput) {
36
+ return types_js_1.FeeAmount.ZERO;
37
+ }
38
+ const args = inputToArgs(input);
39
+ return new types_js_1.FeeAmount(this.intentOffchainInput.program(args));
40
+ }
41
+ /**
42
+ * Evaluates the fee for a given boarding input
43
+ * @param input - The onchain input to evaluate
44
+ * @returns The fee amount for this input
45
+ */
46
+ evalOnchainInput(input) {
47
+ if (!this.intentOnchainInput) {
48
+ return types_js_1.FeeAmount.ZERO;
49
+ }
50
+ const args = {
51
+ amount: Number(input.amount),
52
+ };
53
+ return new types_js_1.FeeAmount(this.intentOnchainInput.program(args));
54
+ }
55
+ /**
56
+ * Evaluates the fee for a given vtxo output
57
+ * @param output - The output to evaluate
58
+ * @returns The fee amount for this output
59
+ */
60
+ evalOffchainOutput(output) {
61
+ if (!this.intentOffchainOutput) {
62
+ return types_js_1.FeeAmount.ZERO;
63
+ }
64
+ const args = outputToArgs(output);
65
+ return new types_js_1.FeeAmount(this.intentOffchainOutput.program(args));
66
+ }
67
+ /**
68
+ * Evaluates the fee for a given collaborative exit output
69
+ * @param output - The output to evaluate
70
+ * @returns The fee amount for this output
71
+ */
72
+ evalOnchainOutput(output) {
73
+ if (!this.intentOnchainOutput) {
74
+ return types_js_1.FeeAmount.ZERO;
75
+ }
76
+ const args = outputToArgs(output);
77
+ return new types_js_1.FeeAmount(this.intentOnchainOutput.program(args));
78
+ }
79
+ /**
80
+ * Evaluates the fee for a given set of inputs and outputs
81
+ * @param offchainInputs - Array of offchain inputs to evaluate
82
+ * @param onchainInputs - Array of onchain inputs to evaluate
83
+ * @param offchainOutputs - Array of offchain outputs to evaluate
84
+ * @param onchainOutputs - Array of onchain outputs to evaluate
85
+ * @returns The total fee amount
86
+ */
87
+ eval(offchainInputs, onchainInputs, offchainOutputs, onchainOutputs) {
88
+ let fee = types_js_1.FeeAmount.ZERO;
89
+ for (const input of offchainInputs) {
90
+ fee = fee.add(this.evalOffchainInput(input));
91
+ }
92
+ for (const input of onchainInputs) {
93
+ fee = fee.add(this.evalOnchainInput(input));
94
+ }
95
+ for (const output of offchainOutputs) {
96
+ fee = fee.add(this.evalOffchainOutput(output));
97
+ }
98
+ for (const output of onchainOutputs) {
99
+ fee = fee.add(this.evalOnchainOutput(output));
100
+ }
101
+ return fee;
102
+ }
103
+ }
104
+ exports.Estimator = Estimator;
105
+ function inputToArgs(input) {
106
+ const args = {
107
+ amount: Number(input.amount),
108
+ inputType: input.type,
109
+ weight: input.weight,
110
+ };
111
+ if (input.expiry) {
112
+ args.expiry = Math.floor(input.expiry.getTime() / 1000);
113
+ }
114
+ if (input.birth) {
115
+ args.birth = Math.floor(input.birth.getTime() / 1000);
116
+ }
117
+ return args;
118
+ }
119
+ function outputToArgs(output) {
120
+ return {
121
+ amount: Number(output.amount),
122
+ script: output.script,
123
+ };
124
+ }
125
+ /**
126
+ * Parses a CEL program and validates its return type
127
+ * @param text - The CEL program text to parse
128
+ * @param env - The CEL environment to use
129
+ * @returns parsed and validated program
130
+ */
131
+ function parseProgram(text, env) {
132
+ const program = env.parse(text);
133
+ // Type check the program
134
+ const checkResult = program.check();
135
+ if (!checkResult.valid) {
136
+ throw new Error(`type check failed: ${checkResult.error?.message ?? "unknown error"}`);
137
+ }
138
+ // Verify return type is double
139
+ if (checkResult.type !== "double") {
140
+ throw new Error(`expected return type double, got ${checkResult.type}`);
141
+ }
142
+ return { program, text };
143
+ }
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Estimator = void 0;
4
+ var estimator_1 = require("./estimator");
5
+ Object.defineProperty(exports, "Estimator", { enumerable: true, get: function () { return estimator_1.Estimator; } });
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FeeAmount = void 0;
4
+ /**
5
+ * FeeAmount is a wrapper around a number that represents a fee amount in satoshis floating point.
6
+ * @param value - The fee amount in floating point.
7
+ * @method satoshis - Returns the fee amount in satoshis as a integer.
8
+ * @example
9
+ * const fee = new FeeAmount(1.23456789);
10
+ * console.log(fee.value); // 1.23456789
11
+ * console.log(fee.satoshis); // 2
12
+ */
13
+ class FeeAmount {
14
+ constructor(value) {
15
+ this.value = value;
16
+ }
17
+ get satoshis() {
18
+ return this.value ? Math.ceil(this.value) : 0;
19
+ }
20
+ add(other) {
21
+ return new FeeAmount(this.value + other.value);
22
+ }
23
+ }
24
+ exports.FeeAmount = FeeAmount;
25
+ FeeAmount.ZERO = new FeeAmount(0);
package/dist/cjs/index.js CHANGED
@@ -1,4 +1,18 @@
1
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
2
16
  Object.defineProperty(exports, "__esModule", { value: true });
3
17
  exports.ContractRepositoryImpl = exports.WalletRepositoryImpl = exports.networks = exports.ArkNote = exports.isVtxoExpiringSoon = exports.combineTapscriptSigs = exports.hasBoardingTxExpired = exports.waitForIncomingFunds = exports.verifyTapscriptSignatures = exports.buildOffchainTx = exports.ConditionWitness = exports.VtxoTaprootTree = exports.VtxoTreeExpiry = exports.CosignerPublicKey = exports.getArkPsbtFields = exports.setArkPsbtField = exports.ArkPsbtFieldKeyType = exports.ArkPsbtFieldKey = exports.TapTreeCoder = exports.CLTVMultisigTapscript = exports.ConditionMultisigTapscript = exports.ConditionCSVMultisigTapscript = exports.CSVMultisigTapscript = exports.MultisigTapscript = exports.decodeTapscript = exports.Response = exports.Request = exports.ServiceWorkerReadonlyWallet = exports.ServiceWorkerWallet = exports.Worker = exports.setupServiceWorker = exports.SettlementEventType = exports.ChainTxType = exports.IndexerTxType = exports.TxType = exports.VHTLC = exports.VtxoScript = exports.DefaultVtxo = exports.ArkAddress = exports.RestIndexerProvider = exports.RestArkProvider = exports.EsploraProvider = exports.ESPLORA_URL = exports.VtxoManager = exports.Ramps = exports.OnchainWallet = exports.ReadonlySingleKey = exports.SingleKey = exports.ReadonlyWallet = exports.Wallet = void 0;
4
18
  exports.getSequence = exports.isExpired = exports.isSubdust = exports.isSpendable = exports.isRecoverable = exports.buildForfeitTx = exports.validateConnectorsTxGraph = exports.validateVtxoTxGraph = exports.Batch = exports.maybeArkError = exports.ArkError = exports.Transaction = exports.Unroll = exports.P2A = exports.TxTree = exports.Intent = void 0;
@@ -102,3 +116,4 @@ Object.defineProperty(exports, "validateVtxoTxGraph", { enumerable: true, get: f
102
116
  Object.defineProperty(exports, "validateConnectorsTxGraph", { enumerable: true, get: function () { return validation_1.validateConnectorsTxGraph; } });
103
117
  const forfeit_1 = require("./forfeit");
104
118
  Object.defineProperty(exports, "buildForfeitTx", { enumerable: true, get: function () { return forfeit_1.buildForfeitTx; } });
119
+ __exportStar(require("./arkfee"), exports);
@@ -57,6 +57,27 @@ var Intent;
57
57
  return craftToSignTx(toSpend, inputs, outputs);
58
58
  }
59
59
  Intent.create = create;
60
+ function fee(proof) {
61
+ let sumOfInputs = 0n;
62
+ for (let i = 0; i < proof.inputsLength; i++) {
63
+ const input = proof.getInput(i);
64
+ if (input.witnessUtxo === undefined)
65
+ throw new Error("intent proof input requires witness utxo");
66
+ sumOfInputs += input.witnessUtxo.amount;
67
+ }
68
+ let sumOfOutputs = 0n;
69
+ for (let i = 0; i < proof.outputsLength; i++) {
70
+ const output = proof.getOutput(i);
71
+ if (output.amount === undefined)
72
+ throw new Error("intent proof output requires amount");
73
+ sumOfOutputs += output.amount;
74
+ }
75
+ if (sumOfOutputs > sumOfInputs) {
76
+ throw new Error(`intent proof output amount is greater than input amount: ${sumOfOutputs} > ${sumOfInputs}`);
77
+ }
78
+ return Number(sumOfInputs - sumOfOutputs);
79
+ }
80
+ Intent.fee = fee;
60
81
  function encodeMessage(message) {
61
82
  switch (message.type) {
62
83
  case "register":
@@ -48,15 +48,7 @@ class RestArkProvider {
48
48
  digest: fromServer.digest ?? "",
49
49
  dust: BigInt(fromServer.dust ?? 0),
50
50
  fees: {
51
- intentFee: {
52
- ...fromServer.fees?.intentFee,
53
- onchainInput: BigInt(
54
- // split(".")[0] to remove the decimal part
55
- (fromServer.fees?.intentFee?.onchainInput ?? "0").split(".")[0] ?? 0),
56
- onchainOutput: BigInt(
57
- // split(".")[0] to remove the decimal part
58
- (fromServer.fees?.intentFee?.onchainOutput ?? "0").split(".")[0] ?? 0),
59
- },
51
+ intentFee: fromServer.fees?.intentFee ?? {},
60
52
  txFeeRate: fromServer?.fees?.txFeeRate ?? "",
61
53
  },
62
54
  forfeitAddress: fromServer.forfeitAddress ?? "",
@@ -69,6 +61,7 @@ class RestArkProvider {
69
61
  nextStartTime: BigInt(fromServer.scheduledSession.nextStartTime ?? 0),
70
62
  nextEndTime: BigInt(fromServer.scheduledSession.nextEndTime ?? 0),
71
63
  period: BigInt(fromServer.scheduledSession.period ?? 0),
64
+ fees: fromServer.scheduledSession.fees ?? {},
72
65
  }
73
66
  : undefined,
74
67
  serviceStatus: fromServer.serviceStatus ?? {},
@@ -342,6 +342,7 @@ function convertVtxo(vtxo) {
342
342
  value: Number(vtxo.amount),
343
343
  status: {
344
344
  confirmed: !vtxo.isSwept && !vtxo.isPreconfirmed,
345
+ isLeaf: !vtxo.isPreconfirmed,
345
346
  },
346
347
  virtualStatus: {
347
348
  state: vtxo.isSwept
@@ -1,168 +1,130 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.vtxosToTxs = vtxosToTxs;
3
+ exports.buildTransactionHistory = buildTransactionHistory;
4
4
  const wallet_1 = require("../wallet");
5
+ const txKey = {
6
+ commitmentTxid: "",
7
+ boardingTxid: "",
8
+ arkTxid: "",
9
+ };
5
10
  /**
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
11
+ * Builds the transaction history by analyzing virtual coins (VTXOs), boarding transactions, and ignored commitments.
12
+ * History is sorted from newest to oldest and is composed only of SENT and RECEIVED transactions.
13
+ *
14
+ * @param {VirtualCoin[]} vtxos - An array of virtual coins representing the user's transactions and balances.
15
+ * @param {ArkTransaction[]} allBoardingTxs - An array of boarding transactions to include in the history.
16
+ * @param {Set<string>} commitmentsToIgnore - A set of commitment IDs that should be excluded from processing.
17
+ * @return {ExtendedArkTransaction[]} A sorted array of extended Ark transactions, representing the transaction history.
10
18
  */
11
- function vtxosToTxs(spendable, spent, boardingBatchTxids) {
12
- const txs = [];
13
- // Receive case
14
- // All vtxos are received unless:
15
- // - they resulted from a settlement (either boarding or refresh)
16
- // - they are the change of a spend tx
17
- let vtxosLeftToCheck = [...spent];
18
- for (const vtxo of [...spendable, ...spent]) {
19
- if (vtxo.virtualStatus.state !== "preconfirmed" &&
20
- vtxo.virtualStatus.commitmentTxIds &&
21
- vtxo.virtualStatus.commitmentTxIds.some((txid) => boardingBatchTxids.has(txid))) {
22
- continue;
23
- }
24
- const settleVtxos = findVtxosSpentInSettlement(vtxosLeftToCheck, vtxo);
25
- vtxosLeftToCheck = removeVtxosFromList(vtxosLeftToCheck, settleVtxos);
26
- const settleAmount = reduceVtxosAmount(settleVtxos);
27
- if (vtxo.value <= settleAmount) {
28
- continue; // settlement or change, ignore
29
- }
30
- const spentVtxos = findVtxosSpentInPayment(vtxosLeftToCheck, vtxo);
31
- vtxosLeftToCheck = removeVtxosFromList(vtxosLeftToCheck, spentVtxos);
32
- const spentAmount = reduceVtxosAmount(spentVtxos);
33
- if (vtxo.value <= spentAmount) {
34
- continue; // settlement or change, ignore
35
- }
36
- const txKey = {
37
- commitmentTxid: "",
38
- boardingTxid: "",
39
- arkTxid: "",
40
- };
41
- let settled = vtxo.virtualStatus.state !== "preconfirmed";
42
- if (vtxo.virtualStatus.state === "preconfirmed") {
43
- txKey.arkTxid = vtxo.txid;
44
- if (vtxo.spentBy) {
45
- settled = true;
19
+ function buildTransactionHistory(vtxos, allBoardingTxs, commitmentsToIgnore) {
20
+ const fromOldestVtxo = [...vtxos].sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
21
+ const sent = [];
22
+ let received = [];
23
+ for (const vtxo of fromOldestVtxo) {
24
+ if (vtxo.status.isLeaf) {
25
+ // If this vtxo is a leaf and it's not the settlement of a boarding or there's no vtxo refreshed by it,
26
+ // it's translated into a received batch transaction
27
+ if (!commitmentsToIgnore.has(vtxo.virtualStatus.commitmentTxIds[0]) &&
28
+ fromOldestVtxo.filter((v) => v.settledBy === vtxo.virtualStatus.commitmentTxIds[0]).length === 0) {
29
+ received.push({
30
+ key: {
31
+ ...txKey,
32
+ commitmentTxid: vtxo.virtualStatus.commitmentTxIds[0],
33
+ },
34
+ tag: "batch",
35
+ type: wallet_1.TxType.TxReceived,
36
+ amount: vtxo.value,
37
+ settled: vtxo.status.isLeaf || vtxo.isSpent,
38
+ createdAt: vtxo.createdAt.getTime(),
39
+ });
46
40
  }
47
41
  }
48
- else {
49
- txKey.commitmentTxid =
50
- vtxo.virtualStatus.commitmentTxIds?.[0] || "";
42
+ else if (fromOldestVtxo.filter((v) => v.arkTxId === vtxo.txid).length === 0) {
43
+ // If this vtxo is preconfirmed and does not spend any other vtxos,
44
+ // it's translated into a received offchain transaction
45
+ received.push({
46
+ key: { ...txKey, arkTxid: vtxo.txid },
47
+ tag: "offchain",
48
+ type: wallet_1.TxType.TxReceived,
49
+ amount: vtxo.value,
50
+ settled: vtxo.status.isLeaf || vtxo.isSpent,
51
+ createdAt: vtxo.createdAt.getTime(),
52
+ });
51
53
  }
52
- txs.push({
53
- key: txKey,
54
- amount: vtxo.value - settleAmount - spentAmount,
55
- type: wallet_1.TxType.TxReceived,
56
- createdAt: vtxo.createdAt.getTime(),
57
- settled,
58
- });
59
- }
60
- // vtxos by settled by or ark txid
61
- const vtxosByTxid = new Map();
62
- for (const v of spent) {
63
- if (v.settledBy) {
64
- if (!vtxosByTxid.has(v.settledBy)) {
65
- vtxosByTxid.set(v.settledBy, []);
54
+ // If the vtxo is spent, it's translated into a sent transaction unless:
55
+ // - it's been refreshed (we don't want to add any record in this case)
56
+ // - a sent transaction has been already added to avoid duplicates (can happen if many vtxos have been spent in the same tx or forfeited in the same batch)
57
+ if (vtxo.isSpent) {
58
+ // If the vtxo is spent offchain, it's translated into offchain sent tx
59
+ if (vtxo.arkTxId &&
60
+ !sent.some((s) => s.key.arkTxid === vtxo.arkTxId)) {
61
+ const changes = fromOldestVtxo.filter((_) => _.txid === vtxo.arkTxId);
62
+ let txAmount = 0;
63
+ let txTime = 0;
64
+ if (changes.length > 0) {
65
+ const changeAmount = changes.reduce((acc, v) => acc + v.value, 0);
66
+ // We want to find all the other VTXOs spent by the same transaction to
67
+ // calculate the full amount of the change.
68
+ const allSpent = fromOldestVtxo.filter((v) => v.arkTxId === vtxo.arkTxId);
69
+ const spentAmount = allSpent.reduce((acc, v) => acc + v.value, 0);
70
+ txAmount = spentAmount - changeAmount;
71
+ txTime = changes[0].createdAt.getTime();
72
+ }
73
+ else {
74
+ txAmount = vtxo.value;
75
+ // TODO: fetch the vtxo with /v1/indexer/vtxos?outpoints=<vtxo.arkTxid:0> to know when the tx was made
76
+ txTime = vtxo.createdAt.getTime() + 1;
77
+ }
78
+ sent.push({
79
+ key: { ...txKey, arkTxid: vtxo.arkTxId },
80
+ tag: "offchain",
81
+ type: wallet_1.TxType.TxSent,
82
+ amount: txAmount,
83
+ settled: true,
84
+ createdAt: txTime,
85
+ });
66
86
  }
67
- const currentVtxos = vtxosByTxid.get(v.settledBy);
68
- vtxosByTxid.set(v.settledBy, [...currentVtxos, v]);
69
- }
70
- if (!v.arkTxId) {
71
- continue;
72
- }
73
- if (!vtxosByTxid.has(v.arkTxId)) {
74
- vtxosByTxid.set(v.arkTxId, []);
75
- }
76
- const currentVtxos = vtxosByTxid.get(v.arkTxId);
77
- vtxosByTxid.set(v.arkTxId, [...currentVtxos, v]);
78
- }
79
- for (const [sb, vtxos] of vtxosByTxid) {
80
- const resultedVtxos = findVtxosResultedFromTxid([...spendable, ...spent], sb);
81
- const resultedAmount = reduceVtxosAmount(resultedVtxos);
82
- const spentAmount = reduceVtxosAmount(vtxos);
83
- if (spentAmount <= resultedAmount) {
84
- continue; // settlement or change, ignore
85
- }
86
- const vtxo = getVtxo(resultedVtxos, vtxos);
87
- const txKey = {
88
- commitmentTxid: "",
89
- boardingTxid: "",
90
- arkTxid: "",
91
- };
92
- if (vtxo.virtualStatus.state === "preconfirmed") {
93
- txKey.arkTxid = resultedAmount === 0 ? vtxo.arkTxId : vtxo.txid;
94
- }
95
- else {
96
- txKey.commitmentTxid =
97
- vtxo.virtualStatus.commitmentTxIds?.[0] || "";
98
- }
99
- txs.push({
100
- key: txKey,
101
- amount: spentAmount - resultedAmount,
102
- type: wallet_1.TxType.TxSent,
103
- createdAt: vtxo.createdAt.getTime(),
104
- settled: true,
105
- });
106
- }
107
- return txs;
108
- }
109
- /**
110
- * Helper function to find vtxos that were spent in a settlement
111
- */
112
- function findVtxosSpentInSettlement(vtxos, vtxo) {
113
- if (vtxo.virtualStatus.state === "preconfirmed") {
114
- return [];
115
- }
116
- return vtxos.filter((v) => {
117
- if (!v.settledBy)
118
- return false;
119
- return (vtxo.virtualStatus.commitmentTxIds?.includes(v.settledBy) ?? false);
120
- });
121
- }
122
- /**
123
- * Helper function to find vtxos that were spent in a payment
124
- */
125
- function findVtxosSpentInPayment(vtxos, vtxo) {
126
- return vtxos.filter((v) => {
127
- if (!v.arkTxId)
128
- return false;
129
- return v.arkTxId === vtxo.txid;
130
- });
131
- }
132
- /**
133
- * Helper function to find vtxos that resulted from a spentBy transaction
134
- */
135
- function findVtxosResultedFromTxid(vtxos, txid) {
136
- return vtxos.filter((v) => {
137
- if (v.virtualStatus.state !== "preconfirmed" &&
138
- v.virtualStatus.commitmentTxIds?.includes(txid)) {
139
- return true;
140
- }
141
- return v.txid === txid;
142
- });
143
- }
144
- /**
145
- * Helper function to reduce vtxos to their total amount
146
- */
147
- function reduceVtxosAmount(vtxos) {
148
- return vtxos.reduce((sum, v) => sum + v.value, 0);
149
- }
150
- /**
151
- * Helper function to get a vtxo from a list of vtxos
152
- */
153
- function getVtxo(resultedVtxos, spentVtxos) {
154
- if (resultedVtxos.length === 0) {
155
- return spentVtxos[0];
156
- }
157
- return resultedVtxos[0];
158
- }
159
- function removeVtxosFromList(vtxos, vtxosToRemove) {
160
- return vtxos.filter((v) => {
161
- for (const vtxoToRemove of vtxosToRemove) {
162
- if (v.txid === vtxoToRemove.txid && v.vout === vtxoToRemove.vout) {
163
- return false;
87
+ // If the vtxo is forfeited in a batch and the total sum of forfeited vtxos is bigger than the sum of new vtxos,
88
+ // it's translated into an exit sent tx
89
+ if (vtxo.settledBy &&
90
+ !commitmentsToIgnore.has(vtxo.settledBy) &&
91
+ !sent.some((s) => s.key.commitmentTxid === vtxo.settledBy)) {
92
+ const changes = fromOldestVtxo.filter((v) => v.status.isLeaf &&
93
+ v.virtualStatus.commitmentTxIds?.every((_) => vtxo.settledBy === _));
94
+ const forfeitVtxos = fromOldestVtxo.filter((v) => v.settledBy === vtxo.settledBy);
95
+ const forfeitAmount = forfeitVtxos.reduce((acc, v) => acc + v.value, 0);
96
+ if (changes.length > 0) {
97
+ const settledAmount = changes.reduce((acc, v) => acc + v.value, 0);
98
+ // forfeitAmount > settledAmount --> collaborative exit with offchain change
99
+ // TODO: make this support fees!
100
+ if (forfeitAmount > settledAmount) {
101
+ sent.push({
102
+ key: { ...txKey, commitmentTxid: vtxo.settledBy },
103
+ tag: "exit",
104
+ type: wallet_1.TxType.TxSent,
105
+ amount: forfeitAmount - settledAmount,
106
+ settled: true,
107
+ createdAt: changes[0].createdAt.getTime(),
108
+ });
109
+ }
110
+ }
111
+ else {
112
+ // forfeitAmount > 0 && settledAmount == 0 --> collaborative exit without any offchain change
113
+ sent.push({
114
+ key: { ...txKey, commitmentTxid: vtxo.settledBy },
115
+ tag: "exit",
116
+ type: wallet_1.TxType.TxSent,
117
+ amount: forfeitAmount,
118
+ settled: true,
119
+ // TODO: fetch commitment tx with /v1/indexer/commitmentTx/<commitmentTxid> to know when the tx was made
120
+ createdAt: vtxo.createdAt.getTime() + 1,
121
+ });
122
+ }
164
123
  }
165
124
  }
166
- return true;
167
- });
125
+ }
126
+ // Boardings are always inbound amounts, and we only hide the ones to ignore.
127
+ const boardingTx = allBoardingTxs.map((tx) => ({ ...tx, tag: "boarding" }));
128
+ const sorted = [...boardingTx, ...sent, ...received].sort((a, b) => b.createdAt - a.createdAt);
129
+ return sorted;
168
130
  }