@ledgerhq/wallet-pnl 0.2.0-nightly.20260513030606

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 (215) hide show
  1. package/.eslintrc.js +28 -0
  2. package/CHANGELOG.md +15 -0
  3. package/LICENSE.txt +21 -0
  4. package/jest.config.js +25 -0
  5. package/lib/assetPnL.d.ts +6 -0
  6. package/lib/assetPnL.d.ts.map +1 -0
  7. package/lib/assetPnL.js +66 -0
  8. package/lib/assetPnL.js.map +1 -0
  9. package/lib/classifyOperation.d.ts +29 -0
  10. package/lib/classifyOperation.d.ts.map +1 -0
  11. package/lib/classifyOperation.js +49 -0
  12. package/lib/classifyOperation.js.map +1 -0
  13. package/lib/costBasis.d.ts +14 -0
  14. package/lib/costBasis.d.ts.map +1 -0
  15. package/lib/costBasis.js +139 -0
  16. package/lib/costBasis.js.map +1 -0
  17. package/lib/costBasisCache.d.ts +7 -0
  18. package/lib/costBasisCache.d.ts.map +1 -0
  19. package/lib/costBasisCache.js +62 -0
  20. package/lib/costBasisCache.js.map +1 -0
  21. package/lib/costBasisReconciliation.d.ts +59 -0
  22. package/lib/costBasisReconciliation.d.ts.map +1 -0
  23. package/lib/costBasisReconciliation.js +133 -0
  24. package/lib/costBasisReconciliation.js.map +1 -0
  25. package/lib/hooks/index.d.ts +3 -0
  26. package/lib/hooks/index.d.ts.map +1 -0
  27. package/lib/hooks/index.js +8 -0
  28. package/lib/hooks/index.js.map +1 -0
  29. package/lib/hooks/useAssetPnL.d.ts +6 -0
  30. package/lib/hooks/useAssetPnL.d.ts.map +1 -0
  31. package/lib/hooks/useAssetPnL.js +9 -0
  32. package/lib/hooks/useAssetPnL.js.map +1 -0
  33. package/lib/hooks/usePortfolioPnL.d.ts +6 -0
  34. package/lib/hooks/usePortfolioPnL.d.ts.map +1 -0
  35. package/lib/hooks/usePortfolioPnL.js +9 -0
  36. package/lib/hooks/usePortfolioPnL.js.map +1 -0
  37. package/lib/index.d.ts +9 -0
  38. package/lib/index.d.ts.map +1 -0
  39. package/lib/index.js +25 -0
  40. package/lib/index.js.map +1 -0
  41. package/lib/percentage.d.ts +3 -0
  42. package/lib/percentage.d.ts.map +1 -0
  43. package/lib/percentage.js +9 -0
  44. package/lib/percentage.js.map +1 -0
  45. package/lib/portfolioPnL.d.ts +6 -0
  46. package/lib/portfolioPnL.d.ts.map +1 -0
  47. package/lib/portfolioPnL.js +29 -0
  48. package/lib/portfolioPnL.js.map +1 -0
  49. package/lib/scenarios/accounts.d.ts +10 -0
  50. package/lib/scenarios/accounts.d.ts.map +1 -0
  51. package/lib/scenarios/accounts.js +68 -0
  52. package/lib/scenarios/accounts.js.map +1 -0
  53. package/lib/scenarios/countervalues.d.ts +24 -0
  54. package/lib/scenarios/countervalues.d.ts.map +1 -0
  55. package/lib/scenarios/countervalues.js +75 -0
  56. package/lib/scenarios/countervalues.js.map +1 -0
  57. package/lib/scenarios/currencies.d.ts +11 -0
  58. package/lib/scenarios/currencies.d.ts.map +1 -0
  59. package/lib/scenarios/currencies.js +34 -0
  60. package/lib/scenarios/currencies.js.map +1 -0
  61. package/lib/scenarios/hodler.d.ts +37 -0
  62. package/lib/scenarios/hodler.d.ts.map +1 -0
  63. package/lib/scenarios/hodler.js +93 -0
  64. package/lib/scenarios/hodler.js.map +1 -0
  65. package/lib/scenarios/index.d.ts +18 -0
  66. package/lib/scenarios/index.d.ts.map +1 -0
  67. package/lib/scenarios/index.js +39 -0
  68. package/lib/scenarios/index.js.map +1 -0
  69. package/lib/scenarios/multiAsset.d.ts +48 -0
  70. package/lib/scenarios/multiAsset.d.ts.map +1 -0
  71. package/lib/scenarios/multiAsset.js +130 -0
  72. package/lib/scenarios/multiAsset.js.map +1 -0
  73. package/lib/scenarios/operations.d.ts +28 -0
  74. package/lib/scenarios/operations.d.ts.map +1 -0
  75. package/lib/scenarios/operations.js +54 -0
  76. package/lib/scenarios/operations.js.map +1 -0
  77. package/lib/scenarios/singleTrade.d.ts +48 -0
  78. package/lib/scenarios/singleTrade.d.ts.map +1 -0
  79. package/lib/scenarios/singleTrade.js +84 -0
  80. package/lib/scenarios/singleTrade.js.map +1 -0
  81. package/lib/scenarios/staker.d.ts +33 -0
  82. package/lib/scenarios/staker.d.ts.map +1 -0
  83. package/lib/scenarios/staker.js +71 -0
  84. package/lib/scenarios/staker.js.map +1 -0
  85. package/lib/scenarios/trader.d.ts +37 -0
  86. package/lib/scenarios/trader.d.ts.map +1 -0
  87. package/lib/scenarios/trader.js +105 -0
  88. package/lib/scenarios/trader.js.map +1 -0
  89. package/lib/types.d.ts +111 -0
  90. package/lib/types.d.ts.map +1 -0
  91. package/lib/types.js +3 -0
  92. package/lib/types.js.map +1 -0
  93. package/lib-es/assetPnL.d.ts +6 -0
  94. package/lib-es/assetPnL.d.ts.map +1 -0
  95. package/lib-es/assetPnL.js +60 -0
  96. package/lib-es/assetPnL.js.map +1 -0
  97. package/lib-es/classifyOperation.d.ts +29 -0
  98. package/lib-es/classifyOperation.d.ts.map +1 -0
  99. package/lib-es/classifyOperation.js +45 -0
  100. package/lib-es/classifyOperation.js.map +1 -0
  101. package/lib-es/costBasis.d.ts +14 -0
  102. package/lib-es/costBasis.d.ts.map +1 -0
  103. package/lib-es/costBasis.js +132 -0
  104. package/lib-es/costBasis.js.map +1 -0
  105. package/lib-es/costBasisCache.d.ts +7 -0
  106. package/lib-es/costBasisCache.d.ts.map +1 -0
  107. package/lib-es/costBasisCache.js +58 -0
  108. package/lib-es/costBasisCache.js.map +1 -0
  109. package/lib-es/costBasisReconciliation.d.ts +59 -0
  110. package/lib-es/costBasisReconciliation.d.ts.map +1 -0
  111. package/lib-es/costBasisReconciliation.js +125 -0
  112. package/lib-es/costBasisReconciliation.js.map +1 -0
  113. package/lib-es/hooks/index.d.ts +3 -0
  114. package/lib-es/hooks/index.d.ts.map +1 -0
  115. package/lib-es/hooks/index.js +3 -0
  116. package/lib-es/hooks/index.js.map +1 -0
  117. package/lib-es/hooks/useAssetPnL.d.ts +6 -0
  118. package/lib-es/hooks/useAssetPnL.d.ts.map +1 -0
  119. package/lib-es/hooks/useAssetPnL.js +6 -0
  120. package/lib-es/hooks/useAssetPnL.js.map +1 -0
  121. package/lib-es/hooks/usePortfolioPnL.d.ts +6 -0
  122. package/lib-es/hooks/usePortfolioPnL.d.ts.map +1 -0
  123. package/lib-es/hooks/usePortfolioPnL.js +6 -0
  124. package/lib-es/hooks/usePortfolioPnL.js.map +1 -0
  125. package/lib-es/index.d.ts +9 -0
  126. package/lib-es/index.d.ts.map +1 -0
  127. package/lib-es/index.js +8 -0
  128. package/lib-es/index.js.map +1 -0
  129. package/lib-es/percentage.d.ts +3 -0
  130. package/lib-es/percentage.d.ts.map +1 -0
  131. package/lib-es/percentage.js +6 -0
  132. package/lib-es/percentage.js.map +1 -0
  133. package/lib-es/portfolioPnL.d.ts +6 -0
  134. package/lib-es/portfolioPnL.d.ts.map +1 -0
  135. package/lib-es/portfolioPnL.js +23 -0
  136. package/lib-es/portfolioPnL.js.map +1 -0
  137. package/lib-es/scenarios/accounts.d.ts +10 -0
  138. package/lib-es/scenarios/accounts.d.ts.map +1 -0
  139. package/lib-es/scenarios/accounts.js +60 -0
  140. package/lib-es/scenarios/accounts.js.map +1 -0
  141. package/lib-es/scenarios/countervalues.d.ts +24 -0
  142. package/lib-es/scenarios/countervalues.d.ts.map +1 -0
  143. package/lib-es/scenarios/countervalues.js +70 -0
  144. package/lib-es/scenarios/countervalues.js.map +1 -0
  145. package/lib-es/scenarios/currencies.d.ts +11 -0
  146. package/lib-es/scenarios/currencies.d.ts.map +1 -0
  147. package/lib-es/scenarios/currencies.js +28 -0
  148. package/lib-es/scenarios/currencies.js.map +1 -0
  149. package/lib-es/scenarios/hodler.d.ts +37 -0
  150. package/lib-es/scenarios/hodler.d.ts.map +1 -0
  151. package/lib-es/scenarios/hodler.js +87 -0
  152. package/lib-es/scenarios/hodler.js.map +1 -0
  153. package/lib-es/scenarios/index.d.ts +18 -0
  154. package/lib-es/scenarios/index.d.ts.map +1 -0
  155. package/lib-es/scenarios/index.js +10 -0
  156. package/lib-es/scenarios/index.js.map +1 -0
  157. package/lib-es/scenarios/multiAsset.d.ts +48 -0
  158. package/lib-es/scenarios/multiAsset.d.ts.map +1 -0
  159. package/lib-es/scenarios/multiAsset.js +126 -0
  160. package/lib-es/scenarios/multiAsset.js.map +1 -0
  161. package/lib-es/scenarios/operations.d.ts +28 -0
  162. package/lib-es/scenarios/operations.d.ts.map +1 -0
  163. package/lib-es/scenarios/operations.js +45 -0
  164. package/lib-es/scenarios/operations.js.map +1 -0
  165. package/lib-es/scenarios/singleTrade.d.ts +48 -0
  166. package/lib-es/scenarios/singleTrade.d.ts.map +1 -0
  167. package/lib-es/scenarios/singleTrade.js +78 -0
  168. package/lib-es/scenarios/singleTrade.js.map +1 -0
  169. package/lib-es/scenarios/staker.d.ts +33 -0
  170. package/lib-es/scenarios/staker.d.ts.map +1 -0
  171. package/lib-es/scenarios/staker.js +65 -0
  172. package/lib-es/scenarios/staker.js.map +1 -0
  173. package/lib-es/scenarios/trader.d.ts +37 -0
  174. package/lib-es/scenarios/trader.d.ts.map +1 -0
  175. package/lib-es/scenarios/trader.js +99 -0
  176. package/lib-es/scenarios/trader.js.map +1 -0
  177. package/lib-es/types.d.ts +111 -0
  178. package/lib-es/types.d.ts.map +1 -0
  179. package/lib-es/types.js +2 -0
  180. package/lib-es/types.js.map +1 -0
  181. package/package.json +117 -0
  182. package/src/__tests__/classifyOperation.test.ts +100 -0
  183. package/src/__tests__/costBasisCache.test.ts +97 -0
  184. package/src/__tests__/costBasisReconciliationUnit.test.ts +180 -0
  185. package/src/__tests__/helpers/bn.ts +42 -0
  186. package/src/__tests__/helpers/pnl.ts +20 -0
  187. package/src/__tests__/helpers/testing.ts +8 -0
  188. package/src/__tests__/hooks.test.ts +206 -0
  189. package/src/__tests__/percentage.test.ts +38 -0
  190. package/src/__tests__/reconciliation.test.ts +173 -0
  191. package/src/__tests__/scenarios.test.ts +337 -0
  192. package/src/assetPnL.ts +74 -0
  193. package/src/classifyOperation.ts +53 -0
  194. package/src/costBasis.ts +156 -0
  195. package/src/costBasisCache.ts +89 -0
  196. package/src/costBasisReconciliation.ts +154 -0
  197. package/src/hooks/index.ts +2 -0
  198. package/src/hooks/useAssetPnL.ts +18 -0
  199. package/src/hooks/usePortfolioPnL.ts +18 -0
  200. package/src/index.ts +19 -0
  201. package/src/percentage.ts +6 -0
  202. package/src/portfolioPnL.ts +34 -0
  203. package/src/scenarios/accounts.ts +86 -0
  204. package/src/scenarios/countervalues.ts +98 -0
  205. package/src/scenarios/currencies.ts +32 -0
  206. package/src/scenarios/hodler.ts +114 -0
  207. package/src/scenarios/index.ts +25 -0
  208. package/src/scenarios/multiAsset.ts +150 -0
  209. package/src/scenarios/operations.ts +82 -0
  210. package/src/scenarios/singleTrade.ts +108 -0
  211. package/src/scenarios/staker.ts +88 -0
  212. package/src/scenarios/trader.ts +124 -0
  213. package/src/types.ts +117 -0
  214. package/tsconfig.build.json +20 -0
  215. package/tsconfig.json +15 -0
package/.eslintrc.js ADDED
@@ -0,0 +1,28 @@
1
+ module.exports = {
2
+ env: {
3
+ browser: true,
4
+ es6: true,
5
+ },
6
+ globals: {
7
+ Atomics: "readonly",
8
+ SharedArrayBuffer: "readonly",
9
+ },
10
+ extends: ["plugin:import/typescript"],
11
+ plugins: ["import"],
12
+ rules: {
13
+ "no-console": ["error", { allow: ["warn", "error"] }],
14
+ "linebreak-style": ["error", "unix"],
15
+ "@typescript-eslint/no-empty-function": "off",
16
+ "@typescript-eslint/no-namespace": ["error", { allowDeclarations: true }],
17
+ "@typescript-eslint/no-explicit-any": "warn",
18
+ },
19
+ overrides: [
20
+ {
21
+ files: ["src/**/*.test.{ts,tsx}"],
22
+ env: {
23
+ "jest/globals": true,
24
+ },
25
+ plugins: ["jest"],
26
+ },
27
+ ],
28
+ };
package/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # @ledgerhq/wallet-pnl
2
+
3
+ ## 0.2.0-nightly.20260513030606
4
+
5
+ ### Minor Changes
6
+
7
+ - [#17343](https://github.com/LedgerHQ/ledger-live/pull/17343) [`f753ec7`](https://github.com/LedgerHQ/ledger-live/commit/f753ec7f73a870fc4b9f24d213f399773bc50600) Thanks [@mcayuelas-ledger](https://github.com/mcayuelas-ledger)! - Add `@ledgerhq/wallet-pnl`: average cost basis PnL (per-asset and portfolio), operation classification, countervalue-backed cost basis cache.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [[`f39fede`](https://github.com/LedgerHQ/ledger-live/commit/f39fede0a6eb4e427a15219e5a3c8fbc3302815f), [`ed0dc8a`](https://github.com/LedgerHQ/ledger-live/commit/ed0dc8abc2c8f5054e655c4e12efe6fb433fbaca), [`b812751`](https://github.com/LedgerHQ/ledger-live/commit/b8127519474e63c543b1b937a2d3b11ad162a78e), [`4ddd97a`](https://github.com/LedgerHQ/ledger-live/commit/4ddd97a99bab5b581ad5ccfd36eb420ec4ee6352), [`3b746ee`](https://github.com/LedgerHQ/ledger-live/commit/3b746eea7f3f2be633947e8e9112987457c864a5), [`7fafa10`](https://github.com/LedgerHQ/ledger-live/commit/7fafa10d8af581f4433a60ea908980a726d3a777), [`ac26c8b`](https://github.com/LedgerHQ/ledger-live/commit/ac26c8bffa9b5cc9f28bed5ce3d44e32982d655c), [`1368afd`](https://github.com/LedgerHQ/ledger-live/commit/1368afdc7218a68c803672e6e412f8f9f6e62142), [`fb79639`](https://github.com/LedgerHQ/ledger-live/commit/fb79639eb81258bae4830ed6ffe375ae625054ad), [`0d11df6`](https://github.com/LedgerHQ/ledger-live/commit/0d11df6ef8dc781171071824ad1c39e3beed7730), [`321a0e2`](https://github.com/LedgerHQ/ledger-live/commit/321a0e2ce948fac11f7bdf0e106eb0af57168caa), [`3cd7abb`](https://github.com/LedgerHQ/ledger-live/commit/3cd7abb4d6f6072bad62073108d797faf23f9e8c), [`6e832a0`](https://github.com/LedgerHQ/ledger-live/commit/6e832a044bd7abb704f0a45ea782e55c1b25487c), [`21e69fe`](https://github.com/LedgerHQ/ledger-live/commit/21e69fea49cffc0b1204903e539a64b83e4b28f0), [`2257d43`](https://github.com/LedgerHQ/ledger-live/commit/2257d43630933127549300f39ade1e2b01f94cb8), [`08762c2`](https://github.com/LedgerHQ/ledger-live/commit/08762c286e38136293108c19efa72ae8fbd1286b), [`fb4d165`](https://github.com/LedgerHQ/ledger-live/commit/fb4d1656be8dc8e933e55600970a2e991fbaeebb), [`73bfe05`](https://github.com/LedgerHQ/ledger-live/commit/73bfe055ec23e0d630f2da9f4dbc9731b6fe5190)]:
12
+ - @ledgerhq/types-live@6.107.0-nightly.20260513030606
13
+ - @ledgerhq/ledger-wallet-framework@1.4.0-nightly.20260513030606
14
+ - @ledgerhq/cryptoassets@13.47.0-nightly.20260513030606
15
+ - @ledgerhq/live-countervalues@0.18.3-nightly.20260513030606
package/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2017-present Ledger https://www.ledger.com/
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/jest.config.js ADDED
@@ -0,0 +1,25 @@
1
+ module.exports = {
2
+ transform: {
3
+ "^.+\\.(ts|tsx)?$": [
4
+ "@swc/jest",
5
+ {
6
+ jsc: {
7
+ target: "esnext",
8
+ transform: {
9
+ react: {
10
+ runtime: "automatic",
11
+ },
12
+ },
13
+ },
14
+ },
15
+ ],
16
+ },
17
+ testEnvironment: "jsdom",
18
+ testPathIgnorePatterns: ["lib/", "lib-es/", "helpers/"],
19
+ coverageReporters: ["json", ["lcov", { file: "lcov.info", projectRoot: "../../" }], "text"],
20
+ reporters: [
21
+ "default",
22
+ ...(process.env.CI ? ["github-actions"] : []),
23
+ ["jest-sonar", { outputName: "sonar-executionTests-report.xml", reportedFilePath: "absolute" }],
24
+ ],
25
+ };
@@ -0,0 +1,6 @@
1
+ import type { AccountLike } from "@ledgerhq/types-live";
2
+ import type { Currency } from "@ledgerhq/types-cryptoassets";
3
+ import type { CounterValuesState } from "@ledgerhq/live-countervalues/types";
4
+ import type { AssetPnL, ComputePnLOptions } from "./types";
5
+ export declare function computeAssetPnL(account: AccountLike, countervalues: CounterValuesState, fiat: Currency, options?: ComputePnLOptions): AssetPnL | null;
6
+ //# sourceMappingURL=assetPnL.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assetPnL.d.ts","sourceRoot":"","sources":["../src/assetPnL.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAE7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAI7E,OAAO,KAAK,EAAE,QAAQ,EAAE,iBAAiB,EAAkB,MAAM,SAAS,CAAC;AAI3E,wBAAgB,eAAe,CAC7B,OAAO,EAAE,WAAW,EACpB,aAAa,EAAE,kBAAkB,EACjC,IAAI,EAAE,QAAQ,EACd,OAAO,CAAC,EAAE,iBAAiB,GAC1B,QAAQ,GAAG,IAAI,CAwDjB"}
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.computeAssetPnL = computeAssetPnL;
7
+ const bignumber_js_1 = __importDefault(require("bignumber.js"));
8
+ const logic_1 = require("@ledgerhq/live-countervalues/logic");
9
+ const account_1 = require("@ledgerhq/ledger-wallet-framework/account");
10
+ const costBasisCache_1 = require("./costBasisCache");
11
+ const costBasisReconciliation_1 = require("./costBasisReconciliation");
12
+ const ZERO = new bignumber_js_1.default(0);
13
+ function computeAssetPnL(account, countervalues, fiat, options) {
14
+ const asset = (0, account_1.getAccountCurrency)(account);
15
+ const rawState = (0, costBasisCache_1.getCostBasis)(account, fiat, countervalues, options);
16
+ const hasOps = account.operations.length > 0;
17
+ const hasBalance = account.balance.gt(0);
18
+ if (!hasOps && !hasBalance)
19
+ return null;
20
+ // Reconciliation defaults to ON: if the chain says the wallet holds X and
21
+ // the cost-basis reducer thinks Y, we close the gap so consumers don't see
22
+ // a wildly negative `unrealisedPnL` on tokens whose disposals weren't
23
+ // captured (typical ERC-20 `transferFrom`-after-`APPROVE` flows). Callers
24
+ // that need raw, op-only values can pass `reconcileWithBalance: false`.
25
+ //
26
+ // Detection (`detectBalanceGap`) is pure and always cheap; only the actual
27
+ // repair (`applyBalanceReconciliation`) is gated on the opt-in flag. This
28
+ // keeps the diagnostic available for downstream UIs even when the repair
29
+ // is suppressed.
30
+ const apply = options?.reconcileWithBalance !== false;
31
+ const gap = (0, costBasisReconciliation_1.detectBalanceGap)(rawState, account.balance);
32
+ let state = rawState;
33
+ let applied = false;
34
+ if (apply && !gap.isClean) {
35
+ const result = (0, costBasisReconciliation_1.applyBalanceReconciliation)(rawState, gap, asset, fiat, countervalues);
36
+ state = result.state;
37
+ applied = result.applied;
38
+ }
39
+ const reconciliation = { ...gap, applied };
40
+ const hasHistory = !state.totalAmount.isZero() || !state.realisedPnL.isZero();
41
+ let unrealisedPnL = ZERO;
42
+ if (hasBalance) {
43
+ const latestCV = (0, logic_1.calculate)(countervalues, {
44
+ value: account.balance.toNumber(),
45
+ from: asset,
46
+ to: fiat,
47
+ disableRounding: true,
48
+ });
49
+ if (typeof latestCV === "number") {
50
+ unrealisedPnL = new bignumber_js_1.default(latestCV).minus(state.totalCostInCounterValue);
51
+ }
52
+ else if (!hasHistory) {
53
+ return null;
54
+ }
55
+ }
56
+ return {
57
+ unrealisedPnL,
58
+ realisedPnL: state.realisedPnL,
59
+ totalPnL: state.realisedPnL.plus(unrealisedPnL),
60
+ costBasis: state.totalCostInCounterValue,
61
+ lifetimeCost: state.lifetimeCostInCounterValue,
62
+ averageEntryPrice: state.averageEntryPrice,
63
+ reconciliation,
64
+ };
65
+ }
66
+ //# sourceMappingURL=assetPnL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assetPnL.js","sourceRoot":"","sources":["../src/assetPnL.ts"],"names":[],"mappings":";;;;;AAYA,0CA6DC;AAzED,gEAAqC;AAGrC,8DAA+D;AAE/D,uEAA+E;AAC/E,qDAAgD;AAChD,uEAAyF;AAGzF,MAAM,IAAI,GAAG,IAAI,sBAAS,CAAC,CAAC,CAAC,CAAC;AAE9B,SAAgB,eAAe,CAC7B,OAAoB,EACpB,aAAiC,EACjC,IAAc,EACd,OAA2B;IAE3B,MAAM,KAAK,GAAG,IAAA,4BAAkB,EAAC,OAAO,CAAC,CAAC;IAC1C,MAAM,QAAQ,GAAG,IAAA,6BAAY,EAAC,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,CAAC,CAAC;IAErE,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC;IAC7C,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAEzC,IAAI,CAAC,MAAM,IAAI,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IAExC,0EAA0E;IAC1E,2EAA2E;IAC3E,sEAAsE;IACtE,0EAA0E;IAC1E,wEAAwE;IACxE,EAAE;IACF,2EAA2E;IAC3E,0EAA0E;IAC1E,yEAAyE;IACzE,iBAAiB;IACjB,MAAM,KAAK,GAAG,OAAO,EAAE,oBAAoB,KAAK,KAAK,CAAC;IACtD,MAAM,GAAG,GAAG,IAAA,0CAAgB,EAAC,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IACxD,IAAI,KAAK,GAAG,QAAQ,CAAC;IACrB,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,KAAK,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QAC1B,MAAM,MAAM,GAAG,IAAA,oDAA0B,EAAC,QAAQ,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC;QACrF,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;QACrB,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;IAC3B,CAAC;IACD,MAAM,cAAc,GAAmB,EAAE,GAAG,GAAG,EAAE,OAAO,EAAE,CAAC;IAE3D,MAAM,UAAU,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC;IAE9E,IAAI,aAAa,GAAG,IAAI,CAAC;IACzB,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,QAAQ,GAAG,IAAA,iBAAS,EAAC,aAAa,EAAE;YACxC,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE;YACjC,IAAI,EAAE,KAAK;YACX,EAAE,EAAE,IAAI;YACR,eAAe,EAAE,IAAI;SACtB,CAAC,CAAC;QACH,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACjC,aAAa,GAAG,IAAI,sBAAS,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;QAC/E,CAAC;aAAM,IAAI,CAAC,UAAU,EAAE,CAAC;YACvB,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,OAAO;QACL,aAAa;QACb,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,QAAQ,EAAE,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,aAAa,CAAC;QAC/C,SAAS,EAAE,KAAK,CAAC,uBAAuB;QACxC,YAAY,EAAE,KAAK,CAAC,0BAA0B;QAC9C,iBAAiB,EAAE,KAAK,CAAC,iBAAiB;QAC1C,cAAc;KACf,CAAC;AACJ,CAAC"}
@@ -0,0 +1,29 @@
1
+ import type { Operation, OperationType } from "@ledgerhq/types-live";
2
+ import type { OperationFlow } from "./types";
3
+ /**
4
+ * Operation types that move asset INTO the account from a cost-basis
5
+ * perspective. Derived from the canonical
6
+ * {@link OPERATION_TYPE_IN_FAMILY} taxonomy of `@ledgerhq/ledger-wallet-framework`,
7
+ * extended with DeFi types not modelled at the framework level
8
+ * (`SUPPLY`, `NFT_IN`).
9
+ */
10
+ export declare const INFLOWS: ReadonlySet<OperationType | string>;
11
+ /**
12
+ * Operation types that move asset OUT of the account from a cost-basis
13
+ * perspective. Derived from the canonical
14
+ * {@link OPERATION_TYPE_OUT_FAMILY} taxonomy of `@ledgerhq/ledger-wallet-framework`,
15
+ * extended with DeFi types not modelled at the framework level
16
+ * (`REDEEM`, `SELL`, `NFT_OUT`).
17
+ *
18
+ * Note: `OPERATION_TYPE_STAKE_FAMILY` ops (`BOND`, `STAKE`, `APPROVE`,
19
+ * `WITHDRAW_UNBONDED`, ...) are intentionally NOT included — they only move
20
+ * fees, not principal, so they're treated as `"ignored"` by `classifyOperation`.
21
+ */
22
+ export declare const OUTFLOWS: ReadonlySet<OperationType | string>;
23
+ /**
24
+ * Stake-family ops are exposed for callers (and tests) that need to assert
25
+ * they're treated as no-ops by the cost-basis reducer.
26
+ */
27
+ export declare const STAKE_FAMILY: ReadonlySet<OperationType | string>;
28
+ export declare function classifyOperation(op: Operation): OperationFlow;
29
+ //# sourceMappingURL=classifyOperation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classifyOperation.d.ts","sourceRoot":"","sources":["../src/classifyOperation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAMrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,EAAE,WAAW,CAAC,aAAa,GAAG,MAAM,CAItD,CAAC;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,QAAQ,EAAE,WAAW,CAAC,aAAa,GAAG,MAAM,CAKvD,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,YAAY,EAAE,WAAW,CAAC,aAAa,GAAG,MAAM,CAE5D,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,SAAS,GAAG,aAAa,CAK9D"}
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.STAKE_FAMILY = exports.OUTFLOWS = exports.INFLOWS = void 0;
4
+ exports.classifyOperation = classifyOperation;
5
+ const operation_1 = require("@ledgerhq/ledger-wallet-framework/operation");
6
+ /**
7
+ * Operation types that move asset INTO the account from a cost-basis
8
+ * perspective. Derived from the canonical
9
+ * {@link OPERATION_TYPE_IN_FAMILY} taxonomy of `@ledgerhq/ledger-wallet-framework`,
10
+ * extended with DeFi types not modelled at the framework level
11
+ * (`SUPPLY`, `NFT_IN`).
12
+ */
13
+ exports.INFLOWS = new Set([
14
+ ...operation_1.OPERATION_TYPE_IN_FAMILY,
15
+ "NFT_IN",
16
+ "SUPPLY",
17
+ ]);
18
+ /**
19
+ * Operation types that move asset OUT of the account from a cost-basis
20
+ * perspective. Derived from the canonical
21
+ * {@link OPERATION_TYPE_OUT_FAMILY} taxonomy of `@ledgerhq/ledger-wallet-framework`,
22
+ * extended with DeFi types not modelled at the framework level
23
+ * (`REDEEM`, `SELL`, `NFT_OUT`).
24
+ *
25
+ * Note: `OPERATION_TYPE_STAKE_FAMILY` ops (`BOND`, `STAKE`, `APPROVE`,
26
+ * `WITHDRAW_UNBONDED`, ...) are intentionally NOT included — they only move
27
+ * fees, not principal, so they're treated as `"ignored"` by `classifyOperation`.
28
+ */
29
+ exports.OUTFLOWS = new Set([
30
+ ...operation_1.OPERATION_TYPE_OUT_FAMILY,
31
+ "NFT_OUT",
32
+ "REDEEM",
33
+ "SELL",
34
+ ]);
35
+ /**
36
+ * Stake-family ops are exposed for callers (and tests) that need to assert
37
+ * they're treated as no-ops by the cost-basis reducer.
38
+ */
39
+ exports.STAKE_FAMILY = new Set(operation_1.OPERATION_TYPE_STAKE_FAMILY);
40
+ function classifyOperation(op) {
41
+ if (op.hasFailed)
42
+ return "ignored";
43
+ if (exports.INFLOWS.has(op.type))
44
+ return "inflow";
45
+ if (exports.OUTFLOWS.has(op.type))
46
+ return "outflow";
47
+ return "ignored";
48
+ }
49
+ //# sourceMappingURL=classifyOperation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classifyOperation.js","sourceRoot":"","sources":["../src/classifyOperation.ts"],"names":[],"mappings":";;;AA+CA,8CAKC;AAnDD,2EAIqD;AAGrD;;;;;;GAMG;AACU,QAAA,OAAO,GAAwC,IAAI,GAAG,CAAyB;IAC1F,GAAG,oCAAwB;IAC3B,QAAQ;IACR,QAAQ;CACT,CAAC,CAAC;AAEH;;;;;;;;;;GAUG;AACU,QAAA,QAAQ,GAAwC,IAAI,GAAG,CAAyB;IAC3F,GAAG,qCAAyB;IAC5B,SAAS;IACT,QAAQ;IACR,MAAM;CACP,CAAC,CAAC;AAEH;;;GAGG;AACU,QAAA,YAAY,GAAwC,IAAI,GAAG,CACtE,uCAA2B,CAC5B,CAAC;AAEF,SAAgB,iBAAiB,CAAC,EAAa;IAC7C,IAAI,EAAE,CAAC,SAAS;QAAE,OAAO,SAAS,CAAC;IACnC,IAAI,eAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC1C,IAAI,gBAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IAC5C,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { AccountLike, Operation } from "@ledgerhq/types-live";
2
+ import type { Currency } from "@ledgerhq/types-cryptoassets";
3
+ import type { CounterValuesState } from "@ledgerhq/live-countervalues/types";
4
+ import type { ComputePnLOptions, CostBasisState } from "./types";
5
+ export declare const initialCostBasisState: CostBasisState;
6
+ /**
7
+ * Append-only ACB reducer. Folds `newOps` into `prev` and returns a new
8
+ * `CostBasisState`. Reads ONLY historical countervalues at op dates — never
9
+ * `latest` — so the result is stable under price ticks.
10
+ *
11
+ * Returns `prev` (same reference) when nothing new applies.
12
+ */
13
+ export declare function reduceCostBasis(prev: CostBasisState, newOps: Operation[], account: AccountLike, countervalues: CounterValuesState, fiat: Currency, options?: ComputePnLOptions): CostBasisState;
14
+ //# sourceMappingURL=costBasis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"costBasis.d.ts","sourceRoot":"","sources":["../src/costBasis.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAE7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAG7E,OAAO,KAAK,EAAE,iBAAiB,EAAgB,cAAc,EAAiB,MAAM,SAAS,CAAC;AAI9F,eAAO,MAAM,qBAAqB,EAAE,cAQnC,CAAC;AA2EF;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,cAAc,EACpB,MAAM,EAAE,SAAS,EAAE,EACnB,OAAO,EAAE,WAAW,EACpB,aAAa,EAAE,kBAAkB,EACjC,IAAI,EAAE,QAAQ,EACd,OAAO,CAAC,EAAE,iBAAiB,GAC1B,cAAc,CA+ChB"}
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.initialCostBasisState = void 0;
7
+ exports.reduceCostBasis = reduceCostBasis;
8
+ const bignumber_js_1 = __importDefault(require("bignumber.js"));
9
+ const logic_1 = require("@ledgerhq/live-countervalues/logic");
10
+ const account_1 = require("@ledgerhq/ledger-wallet-framework/account");
11
+ const classifyOperation_1 = require("./classifyOperation");
12
+ const ZERO = new bignumber_js_1.default(0);
13
+ exports.initialCostBasisState = {
14
+ totalAmount: ZERO,
15
+ totalCostInCounterValue: ZERO,
16
+ lifetimeCostInCounterValue: ZERO,
17
+ realisedPnL: ZERO,
18
+ averageEntryPrice: ZERO,
19
+ lastOperationId: null,
20
+ lastOperationDate: null,
21
+ };
22
+ function compareOps(a, b) {
23
+ const da = a.date.getTime();
24
+ const db = b.date.getTime();
25
+ if (da !== db)
26
+ return da - db;
27
+ if (a.id < b.id)
28
+ return -1;
29
+ if (a.id > b.id)
30
+ return 1;
31
+ return 0;
32
+ }
33
+ function isAfterBookmark(op, prev) {
34
+ if (prev.lastOperationDate === null)
35
+ return true;
36
+ const opTime = op.date.getTime();
37
+ const bookTime = prev.lastOperationDate.getTime();
38
+ if (opTime > bookTime)
39
+ return true;
40
+ if (opTime < bookTime)
41
+ return false;
42
+ return prev.lastOperationId !== null && op.id > prev.lastOperationId;
43
+ }
44
+ function applyInflow(acc, value, cvBN) {
45
+ const totalCost = acc.totalCost.plus(cvBN);
46
+ const totalAmount = acc.totalAmount.plus(value);
47
+ return {
48
+ totalAmount,
49
+ totalCost,
50
+ lifetimeCost: acc.lifetimeCost.plus(cvBN),
51
+ realised: acc.realised,
52
+ averageEntryPrice: totalAmount.gt(0) ? totalCost.div(totalAmount) : acc.averageEntryPrice,
53
+ };
54
+ }
55
+ function applyOutflow(acc, value, cvBN) {
56
+ if (acc.totalAmount.lte(0))
57
+ return acc;
58
+ const sold = bignumber_js_1.default.minimum(value, acc.totalAmount);
59
+ const costOfSale = sold.times(acc.averageEntryPrice);
60
+ // Scale exit CV proportionally when we clamped a sell that exceeded held amount.
61
+ const exitCV = sold.eq(value) ? cvBN : cvBN.times(sold).div(value);
62
+ return {
63
+ totalAmount: acc.totalAmount.minus(sold),
64
+ totalCost: acc.totalCost.minus(costOfSale),
65
+ // `lifetimeCost` is intentionally untouched on sells — it represents
66
+ // "money put in over the lifetime of the position", not the running
67
+ // basis of remaining coins.
68
+ lifetimeCost: acc.lifetimeCost,
69
+ realised: acc.realised.plus(exitCV.minus(costOfSale)),
70
+ averageEntryPrice: acc.averageEntryPrice,
71
+ };
72
+ }
73
+ function applyOperation(acc, op, flow, asset, fiat, countervalues) {
74
+ if (op.value.isZero() || op.value.isNegative())
75
+ return acc;
76
+ const cvAtDate = (0, logic_1.calculate)(countervalues, {
77
+ value: op.value.toNumber(),
78
+ from: asset,
79
+ to: fiat,
80
+ date: op.date,
81
+ disableRounding: true,
82
+ });
83
+ if (typeof cvAtDate !== "number")
84
+ return acc;
85
+ const cvBN = new bignumber_js_1.default(cvAtDate);
86
+ return flow === "inflow" ? applyInflow(acc, op.value, cvBN) : applyOutflow(acc, op.value, cvBN);
87
+ }
88
+ /**
89
+ * Append-only ACB reducer. Folds `newOps` into `prev` and returns a new
90
+ * `CostBasisState`. Reads ONLY historical countervalues at op dates — never
91
+ * `latest` — so the result is stable under price ticks.
92
+ *
93
+ * Returns `prev` (same reference) when nothing new applies.
94
+ */
95
+ function reduceCostBasis(prev, newOps, account, countervalues, fiat, options) {
96
+ if (newOps.length === 0)
97
+ return prev;
98
+ const pending = [];
99
+ for (const op of newOps) {
100
+ if (isAfterBookmark(op, prev))
101
+ pending.push(op);
102
+ }
103
+ if (pending.length === 0)
104
+ return prev;
105
+ pending.sort(compareOps);
106
+ const asset = (0, account_1.getAccountCurrency)(account);
107
+ const isSpam = options?.isSpamOperation;
108
+ let acc = {
109
+ totalAmount: prev.totalAmount,
110
+ totalCost: prev.totalCostInCounterValue,
111
+ lifetimeCost: prev.lifetimeCostInCounterValue,
112
+ realised: prev.realisedPnL,
113
+ averageEntryPrice: prev.averageEntryPrice,
114
+ };
115
+ let lastOperationId = prev.lastOperationId;
116
+ let lastOperationDate = prev.lastOperationDate;
117
+ for (const op of pending) {
118
+ // Advance the bookmark for every seen op (even ignored ones) so that
119
+ // re-passing the same ops is a true no-op.
120
+ lastOperationId = op.id;
121
+ lastOperationDate = op.date;
122
+ if (isSpam?.(op, account))
123
+ continue;
124
+ const flow = (0, classifyOperation_1.classifyOperation)(op);
125
+ if (flow === "ignored")
126
+ continue;
127
+ acc = applyOperation(acc, op, flow, asset, fiat, countervalues);
128
+ }
129
+ return {
130
+ totalAmount: acc.totalAmount,
131
+ totalCostInCounterValue: acc.totalCost,
132
+ lifetimeCostInCounterValue: acc.lifetimeCost,
133
+ realisedPnL: acc.realised,
134
+ averageEntryPrice: acc.averageEntryPrice,
135
+ lastOperationId,
136
+ lastOperationDate,
137
+ };
138
+ }
139
+ //# sourceMappingURL=costBasis.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"costBasis.js","sourceRoot":"","sources":["../src/costBasis.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { AccountLike } from "@ledgerhq/types-live";
2
+ import type { Currency } from "@ledgerhq/types-cryptoassets";
3
+ import type { CounterValuesState } from "@ledgerhq/live-countervalues/types";
4
+ import type { ComputePnLOptions, CostBasisState } from "./types";
5
+ export declare function getCostBasis(account: AccountLike, fiat: Currency, countervalues: CounterValuesState, options?: ComputePnLOptions): CostBasisState;
6
+ export declare function invalidatePnLCache(accountId?: string): void;
7
+ //# sourceMappingURL=costBasisCache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"costBasisCache.d.ts","sourceRoot":"","sources":["../src/costBasisCache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAG7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAG7E,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAiCjE,wBAAgB,YAAY,CAC1B,OAAO,EAAE,WAAW,EACpB,IAAI,EAAE,QAAQ,EACd,aAAa,EAAE,kBAAkB,EACjC,OAAO,CAAC,EAAE,iBAAiB,GAC1B,cAAc,CAgChB;AAED,wBAAgB,kBAAkB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAS3D"}
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getCostBasis = getCostBasis;
4
+ exports.invalidatePnLCache = invalidatePnLCache;
5
+ const logic_1 = require("@ledgerhq/live-countervalues/logic");
6
+ const helpers_1 = require("@ledgerhq/live-countervalues/helpers");
7
+ const account_1 = require("@ledgerhq/ledger-wallet-framework/account");
8
+ const costBasis_1 = require("./costBasis");
9
+ const cache = new Map();
10
+ function getHistoryKey(state, from, to, lastOpDate) {
11
+ if ((0, helpers_1.inferCurrencyAPIID)(from) === (0, helpers_1.inferCurrencyAPIID)(to))
12
+ return "identity";
13
+ const pairCache = (0, logic_1.lenseRateMap)(state, { from, to });
14
+ if (!pairCache)
15
+ return "noCV";
16
+ const { oldest, earliest, earliestStableDate } = pairCache.stats;
17
+ const bucket = lastOpDate ? (0, helpers_1.formatCounterValueDay)(lastOpDate) : "0";
18
+ const earliestRelevant = earliest && earliest <= bucket ? earliest : "";
19
+ return `${oldest ?? "_"}|${earliestStableDate ?? "_"}|${earliestRelevant}`;
20
+ }
21
+ function getLastOpBookmark(account) {
22
+ const ops = account.operations;
23
+ if (ops.length === 0)
24
+ return { id: "_", date: null };
25
+ let best = ops[0];
26
+ for (let i = 1; i < ops.length; i++) {
27
+ const op = ops[i];
28
+ const opTime = op.date.getTime();
29
+ const bestTime = best.date.getTime();
30
+ if (opTime > bestTime || (opTime === bestTime && op.id > best.id))
31
+ best = op;
32
+ }
33
+ return { id: best.id, date: best.date };
34
+ }
35
+ function getCostBasis(account, fiat, countervalues, options) {
36
+ const asset = (0, account_1.getAccountCurrency)(account);
37
+ if (options?.isSpamOperation) {
38
+ return (0, costBasis_1.reduceCostBasis)(costBasis_1.initialCostBasisState, account.operations, account, countervalues, fiat, options);
39
+ }
40
+ const { id: lastOpId, date: lastOpDate } = getLastOpBookmark(account);
41
+ const historyKey = getHistoryKey(countervalues, asset, fiat, lastOpDate);
42
+ const fiatKey = (0, helpers_1.inferCurrencyAPIID)(fiat);
43
+ const key = `${account.id}|${fiatKey}|${lastOpId}|${historyKey}`;
44
+ const hit = cache.get(key);
45
+ if (hit)
46
+ return hit;
47
+ const fresh = (0, costBasis_1.reduceCostBasis)(costBasis_1.initialCostBasisState, account.operations, account, countervalues, fiat, options);
48
+ cache.set(key, fresh);
49
+ return fresh;
50
+ }
51
+ function invalidatePnLCache(accountId) {
52
+ if (!accountId) {
53
+ cache.clear();
54
+ return;
55
+ }
56
+ const prefix = `${accountId}|`;
57
+ for (const key of cache.keys()) {
58
+ if (key.startsWith(prefix))
59
+ cache.delete(key);
60
+ }
61
+ }
62
+ //# sourceMappingURL=costBasisCache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"costBasisCache.js","sourceRoot":"","sources":["../src/costBasisCache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ import BigNumber from "bignumber.js";
2
+ import type { Currency } from "@ledgerhq/types-cryptoassets";
3
+ import type { CounterValuesState } from "@ledgerhq/live-countervalues/types";
4
+ import type { CostBasisState, Reconciliation } from "./types";
5
+ /**
6
+ * Pure detection: compares the operations-derived `state.totalAmount` to the
7
+ * on-chain `balance` and returns the diagnostic. **Never** does any I/O on
8
+ * countervalues, **never** mutates state, **always** returns `applied: false`.
9
+ *
10
+ * The repair step is a separate concern — see {@link applyBalanceReconciliation}.
11
+ * This split lets callers detect anomalies (UI badges, audit logs) without
12
+ * paying the cost of the heuristic repair, and keeps the reducer pipeline
13
+ * trivially testable.
14
+ */
15
+ export declare function detectBalanceGap(state: CostBasisState, balance: BigNumber): Reconciliation;
16
+ /**
17
+ * Opinionated repair: when the `gap` shows a non-zero `delta`, folds a
18
+ * synthetic operation into the cost-basis state to close it.
19
+ *
20
+ * - `delta > 0` → synthetic INFLOW valued at the latest counter-value
21
+ * (rebase tokens, missing IN op…). Pushes the average
22
+ * entry price toward the latest market.
23
+ * - `delta < 0` → synthetic OUTFLOW priced at `state.lastOperationDate`
24
+ * (best-effort proxy for the unknown disposal date).
25
+ * The realised gain/loss equals `delta_value − sold × AEP`.
26
+ *
27
+ * `lifetimeCostInCounterValue` is intentionally NEVER touched here, even on
28
+ * a positive delta: rebase yield / silent accruals are not real cash
29
+ * investments, so they would distort a "% vs invested" KPI.
30
+ *
31
+ * Returns `applied: false` (and an unchanged state) when:
32
+ * - the gap is clean,
33
+ * - the counter-value lookup for the delta fails,
34
+ * - or a negative delta hits an empty position.
35
+ *
36
+ * Callers that want the combined detect+repair flow with the legacy signature
37
+ * should use {@link reconcileCostBasisWithBalance}.
38
+ */
39
+ export declare function applyBalanceReconciliation(state: CostBasisState, gap: Reconciliation, asset: Currency, fiat: Currency, countervalues: CounterValuesState): {
40
+ state: CostBasisState;
41
+ applied: boolean;
42
+ };
43
+ /**
44
+ * Legacy combined detect+repair entry point, preserved for API stability.
45
+ * New code should prefer composing {@link detectBalanceGap} and
46
+ * {@link applyBalanceReconciliation} explicitly so the conditional mutation
47
+ * is obvious to the reader.
48
+ *
49
+ * Always returns the {@link Reconciliation} diagnostic so callers can flag
50
+ * the result in the UI (e.g. show a warning badge on the asset row).
51
+ *
52
+ * If `apply` is `false`, the gap is detected but no repair is attempted —
53
+ * `applied` will be `false` regardless of the delta.
54
+ */
55
+ export declare function reconcileCostBasisWithBalance(state: CostBasisState, balance: BigNumber, asset: Currency, fiat: Currency, countervalues: CounterValuesState, apply: boolean): {
56
+ state: CostBasisState;
57
+ reconciliation: Reconciliation;
58
+ };
59
+ //# sourceMappingURL=costBasisReconciliation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"costBasisReconciliation.d.ts","sourceRoot":"","sources":["../src/costBasisReconciliation.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,cAAc,CAAC;AACrC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAE7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAC7E,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAI9D;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,SAAS,GAAG,cAAc,CAS1F;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,cAAc,EACrB,GAAG,EAAE,cAAc,EACnB,KAAK,EAAE,QAAQ,EACf,IAAI,EAAE,QAAQ,EACd,aAAa,EAAE,kBAAkB,GAChC;IAAE,KAAK,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CA8D7C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,SAAS,EAClB,KAAK,EAAE,QAAQ,EACf,IAAI,EAAE,QAAQ,EACd,aAAa,EAAE,kBAAkB,EACjC,KAAK,EAAE,OAAO,GACb;IAAE,KAAK,EAAE,cAAc,CAAC;IAAC,cAAc,EAAE,cAAc,CAAA;CAAE,CAY3D"}