@tradelayerprotocol/tradelayer 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/.claude/settings.local.json +13 -0
  2. package/.claude/skills/tl-algo/SKILL.md +255 -0
  3. package/.gitattributes +2 -0
  4. package/.github/workflows/publish.yaml +26 -0
  5. package/4mm.js +163 -0
  6. package/LICENSE +21 -0
  7. package/NPMSwapRefactor.zip +0 -0
  8. package/README.md +217 -0
  9. package/address.sh +26 -0
  10. package/algoAPI.js +581 -0
  11. package/analyzepsbt.js +92 -0
  12. package/apiEx.js +99 -0
  13. package/bb_hyperscalper.js +290 -0
  14. package/bbo_demo.js +111 -0
  15. package/buyer.js +622 -0
  16. package/client.js +50 -0
  17. package/createTxTest.js +26 -0
  18. package/createWallet.js +75 -0
  19. package/daytrader.js +531 -0
  20. package/decodeTest.js +69 -0
  21. package/fundingManager.js +144 -0
  22. package/index.js +4 -0
  23. package/listener.js +27 -0
  24. package/litecoreTxBuilder.js +1128 -0
  25. package/mmEx.js +356 -0
  26. package/networks.js +51 -0
  27. package/orderbook.js +200 -0
  28. package/package.json +34 -0
  29. package/perTradeQueue.js +36 -0
  30. package/projectsTLNPMTLNPM/package-lock.json +162 -0
  31. package/projectsTLNPMTLNPM/package.json +5 -0
  32. package/quick.js +32 -0
  33. package/quickFut.js +37 -0
  34. package/quickSell.js +37 -0
  35. package/relayerClient.js +117 -0
  36. package/run4mm.js +80 -0
  37. package/run_bbo_tracker.js +241 -0
  38. package/seller.js +443 -0
  39. package/session.js +45 -0
  40. package/setup-lin-ltc.sh +139 -0
  41. package/setup-lin.sh +203 -0
  42. package/setup-win-ltc.bat +108 -0
  43. package/setup-win.bat +167 -0
  44. package/spam_screamer_futures.js +222 -0
  45. package/tradelayer.js/.gitattributes +2 -0
  46. package/tradelayer.js/README.md +2 -0
  47. package/tradelayer.js/oldTests/activationTest.js +6 -0
  48. package/tradelayer.js/oldTests/base58.test.js +23 -0
  49. package/tradelayer.js/oldTests/base64Decode.test.js +16 -0
  50. package/tradelayer.js/oldTests/blocksRefactor.js +140 -0
  51. package/tradelayer.js/oldTests/checkVestBalance.js +25 -0
  52. package/tradelayer.js/oldTests/consensusHashProto.js +151 -0
  53. package/tradelayer.js/oldTests/contractOrderbook.js +243 -0
  54. package/tradelayer.js/oldTests/createPayload.js +0 -0
  55. package/tradelayer.js/oldTests/createTestnetAddr.js +43 -0
  56. package/tradelayer.js/oldTests/decode.js +205 -0
  57. package/tradelayer.js/oldTests/decodeTest.js +50 -0
  58. package/tradelayer.js/oldTests/displayTallyMap.js +19 -0
  59. package/tradelayer.js/oldTests/encodeDecode.js +340 -0
  60. package/tradelayer.js/oldTests/expressTest.js +29 -0
  61. package/tradelayer.js/oldTests/extractBlocksVanilla.js +214 -0
  62. package/tradelayer.js/oldTests/extractBlocksVanillaa.js +179 -0
  63. package/tradelayer.js/oldTests/extractPubkeyTest.js +60 -0
  64. package/tradelayer.js/oldTests/fillInputCacheProto.js +111 -0
  65. package/tradelayer.js/oldTests/getRawTxTest.js +22 -0
  66. package/tradelayer.js/oldTests/indexTest.js +26 -0
  67. package/tradelayer.js/oldTests/initTokensTest.js +32 -0
  68. package/tradelayer.js/oldTests/interfaceChild.js +129 -0
  69. package/tradelayer.js/oldTests/listenerChild.js +112 -0
  70. package/tradelayer.js/oldTests/opdecode.js +26 -0
  71. package/tradelayer.js/oldTests/options.js +79 -0
  72. package/tradelayer.js/oldTests/optxtest.js +116 -0
  73. package/tradelayer.js/oldTests/optxtest1.js +64 -0
  74. package/tradelayer.js/oldTests/oracle.test.js +32 -0
  75. package/tradelayer.js/oldTests/orderbook.test.js +36 -0
  76. package/tradelayer.js/oldTests/parsing.js +93 -0
  77. package/tradelayer.js/oldTests/payload.js +13 -0
  78. package/tradelayer.js/oldTests/persistenceUnitTest.js +23 -0
  79. package/tradelayer.js/oldTests/property.test.js +53 -0
  80. package/tradelayer.js/oldTests/propertyLevel.js +75 -0
  81. package/tradelayer.js/oldTests/propertyTest.js +32 -0
  82. package/tradelayer.js/oldTests/queryAddressTest.js +17 -0
  83. package/tradelayer.js/oldTests/salter.js +14 -0
  84. package/tradelayer.js/oldTests/tally.js +81 -0
  85. package/tradelayer.js/oldTests/tally.test.js +48 -0
  86. package/tradelayer.js/oldTests/tally2.js +124 -0
  87. package/tradelayer.js/oldTests/tally3.js +142 -0
  88. package/tradelayer.js/oldTests/tallyDiag.js +38 -0
  89. package/tradelayer.js/oldTests/testGetRaw.js +40 -0
  90. package/tradelayer.js/oldTests/testHexConvert.js +47 -0
  91. package/tradelayer.js/oldTests/testNewEncoding.js +96 -0
  92. package/tradelayer.js/oldTests/testNewEncoding2.js +113 -0
  93. package/tradelayer.js/oldTests/testNewEncoding3 +112 -0
  94. package/tradelayer.js/oldTests/testNewEncoding3.js +168 -0
  95. package/tradelayer.js/oldTests/testOPReturn.js +102 -0
  96. package/tradelayer.js/oldTests/testPayload.js +23 -0
  97. package/tradelayer.js/oldTests/testRaw.js +50 -0
  98. package/tradelayer.js/oldTests/testSendTooMuch.js +20 -0
  99. package/tradelayer.js/oldTests/testTxBuild +28 -0
  100. package/tradelayer.js/oldTests/testTxBuild.js +42 -0
  101. package/tradelayer.js/oldTests/tokenOrderbook.js +243 -0
  102. package/tradelayer.js/oldTests/txUtilsA.js +515 -0
  103. package/tradelayer.js/oldTests/validityUnitTest.js +53 -0
  104. package/tradelayer.js/oldTests/vaults.js +72 -0
  105. package/tradelayer.js/oldTests/volumeIndex.js +117 -0
  106. package/tradelayer.js/oldTests/volumeIndex2.js +88 -0
  107. package/tradelayer.js/output_base64.txt +1 -0
  108. package/tradelayer.js/package-lock.json +9967 -0
  109. package/tradelayer.js/package.json +61 -0
  110. package/tradelayer.js/server/index.js +88 -0
  111. package/tradelayer.js/server/litecoind.exe +0 -0
  112. package/tradelayer.js/src/activation.js +303 -0
  113. package/tradelayer.js/src/adjuster.js +77 -0
  114. package/tradelayer.js/src/amm.js +400 -0
  115. package/tradelayer.js/src/base256.js +55 -0
  116. package/tradelayer.js/src/base94.js +79 -0
  117. package/tradelayer.js/src/channels.js +1163 -0
  118. package/tradelayer.js/src/clearing.js +3109 -0
  119. package/tradelayer.js/src/clearlist.js +364 -0
  120. package/tradelayer.js/src/client.js +295 -0
  121. package/tradelayer.js/src/consensus.js +613 -0
  122. package/tradelayer.js/src/contractRegistry.js +964 -0
  123. package/tradelayer.js/src/db.js +89 -0
  124. package/tradelayer.js/src/init.js +24 -0
  125. package/tradelayer.js/src/insurance.js +347 -0
  126. package/tradelayer.js/src/interface.js +218 -0
  127. package/tradelayer.js/src/interfaceExpress.js +178 -0
  128. package/tradelayer.js/src/iou.js +509 -0
  129. package/tradelayer.js/src/listener.js +226 -0
  130. package/tradelayer.js/src/logic.js +1702 -0
  131. package/tradelayer.js/src/main.js +927 -0
  132. package/tradelayer.js/src/marginMap.js +2165 -0
  133. package/tradelayer.js/src/options.js +126 -0
  134. package/tradelayer.js/src/oracle.js +394 -0
  135. package/tradelayer.js/src/orderbook.js +4123 -0
  136. package/tradelayer.js/src/persistence.js +554 -0
  137. package/tradelayer.js/src/property.js +411 -0
  138. package/tradelayer.js/src/reOrg.js +41 -0
  139. package/tradelayer.js/src/scaling.js +145 -0
  140. package/tradelayer.js/src/tally.js +1275 -0
  141. package/tradelayer.js/src/tradeHistoryManager.js +552 -0
  142. package/tradelayer.js/src/txDecoder.js +584 -0
  143. package/tradelayer.js/src/txEncoder.js +610 -0
  144. package/tradelayer.js/src/txIndex.js +502 -0
  145. package/tradelayer.js/src/txUtils.js +1392 -0
  146. package/tradelayer.js/src/types.js +429 -0
  147. package/tradelayer.js/src/validity.js +3077 -0
  148. package/tradelayer.js/src/vaults.js +430 -0
  149. package/tradelayer.js/src/vesting.js +491 -0
  150. package/tradelayer.js/src/volumeIndex.js +618 -0
  151. package/tradelayer.js/src/walletInterface.js +220 -0
  152. package/tradelayer.js/src/walletListener.js +665 -0
  153. package/tradelayer.js/tests/256decode.js +82 -0
  154. package/tradelayer.js/tests/UTXOracle.js +205 -0
  155. package/tradelayer.js/tests/base94test.js +23 -0
  156. package/tradelayer.js/tests/cancelTxTest.js +62 -0
  157. package/tradelayer.js/tests/contractInterfaceTest.js +48 -0
  158. package/tradelayer.js/tests/decimalTest.js +65 -0
  159. package/tradelayer.js/tests/decoderTest.js +100 -0
  160. package/tradelayer.js/tests/deltaCount.js +47 -0
  161. package/tradelayer.js/tests/deltaCount2.js +60 -0
  162. package/tradelayer.js/tests/interfaceTest.js +37 -0
  163. package/tradelayer.js/tests/mainTest.js +53 -0
  164. package/tradelayer.js/tests/makeActivationTest.js +24 -0
  165. package/tradelayer.js/tests/maxHeightTest.js +49 -0
  166. package/tradelayer.js/tests/reverseHash.js +72 -0
  167. package/tradelayer.js/tests/sensitiveConsoleOutput.txt +267 -0
  168. package/tradelayer.js/tests/tallyTest.js +40 -0
  169. package/tradelayer.js/tests/testBuybacks.js +46 -0
  170. package/tradelayer.js/tests/testCodeHash.js +49 -0
  171. package/tradelayer.js/tests/testConsensusHash.js +91 -0
  172. package/tradelayer.js/tests/testDecode.js +30 -0
  173. package/tradelayer.js/tests/testEncodingLengths.js +129 -0
  174. package/tradelayer.js/tests/testGetTx +32 -0
  175. package/tradelayer.js/tests/testGetTx.js +32 -0
  176. package/tradelayer.js/tests/testHexHash.js +32 -0
  177. package/tradelayer.js/tests/testIndexHash.js +35 -0
  178. package/tradelayer.js/tests/testInitContracts.js +38 -0
  179. package/tradelayer.js/tests/testMaxConsensus.js +12 -0
  180. package/tradelayer.js/tests/testMaxSynth.js +44 -0
  181. package/tradelayer.js/tests/testMint.js +21 -0
  182. package/tradelayer.js/tests/testNetwork.js +33 -0
  183. package/tradelayer.js/tests/testOrderbookLoad.js +62 -0
  184. package/tradelayer.js/tests/testRebates.js +32 -0
  185. package/tradelayer.js/tests/testRedeem.js +22 -0
  186. package/tradelayer.js/tests/testTokenTrade.js +39 -0
  187. package/tradelayer.js/tests/testTxBuild.js +42 -0
  188. package/tradelayer.js/tests/testUTXOTrade.js +27 -0
  189. package/tradelayer.js/tests/tokenTradeHistory.js +27 -0
  190. package/tradelayer.js/tests/tradeFutures.js +40 -0
  191. package/tradelayer.js/tests/tradeHistoryExample.js +35 -0
  192. package/tradelayer.js/tests/tradeHistoryLoad.js +15 -0
  193. package/tradelayer.js/tests/txScanTest.js +134 -0
  194. package/tradelayer.js/tests/validateTest.js +136 -0
  195. package/tradelayer.js/tests/vestingTest.js +37 -0
  196. package/tradelayer.js/utils/activateMainnet.js +59 -0
  197. package/tradelayer.js/utils/activateMainnetDoge.js +63 -0
  198. package/tradelayer.js/utils/autocompactdb.js +23 -0
  199. package/tradelayer.js/utils/base64toHex.js +32 -0
  200. package/tradelayer.js/utils/broadcastDoge.js +38 -0
  201. package/tradelayer.js/utils/calcRedeem.js +19 -0
  202. package/tradelayer.js/utils/checkNetwork.js +27 -0
  203. package/tradelayer.js/utils/createAddress.js +48 -0
  204. package/tradelayer.js/utils/createAttestation.js +133 -0
  205. package/tradelayer.js/utils/createContract.js +118 -0
  206. package/tradelayer.js/utils/createOracle.js +94 -0
  207. package/tradelayer.js/utils/createwallet.js +20 -0
  208. package/tradelayer.js/utils/crossFuturesTrades.js +57 -0
  209. package/tradelayer.js/utils/crossTokenTrades.js +62 -0
  210. package/tradelayer.js/utils/dumpPriv.js +29 -0
  211. package/tradelayer.js/utils/generateChannel.js +34 -0
  212. package/tradelayer.js/utils/getInfo.js +21 -0
  213. package/tradelayer.js/utils/hardWipe.js +20 -0
  214. package/tradelayer.js/utils/hexTo64.js +16 -0
  215. package/tradelayer.js/utils/importAddress.js +28 -0
  216. package/tradelayer.js/utils/importpriv.js +20 -0
  217. package/tradelayer.js/utils/issueOracleContract.js +67 -0
  218. package/tradelayer.js/utils/issueTokens.js +41 -0
  219. package/tradelayer.js/utils/listunspent.js +66 -0
  220. package/tradelayer.js/utils/litecoinClient.js +30 -0
  221. package/tradelayer.js/utils/loadwallet.js +20 -0
  222. package/tradelayer.js/utils/publishOracle.js +113 -0
  223. package/tradelayer.js/utils/sendActivation.js +21 -0
  224. package/tradelayer.js/utils/sendChannelContractTrade.js +34 -0
  225. package/tradelayer.js/utils/sendChannelTokenTrade.js +34 -0
  226. package/tradelayer.js/utils/sendCommit.js +24 -0
  227. package/tradelayer.js/utils/sendDoge.js +62 -0
  228. package/tradelayer.js/utils/sendDogeMain.js +67 -0
  229. package/tradelayer.js/utils/sendDogeTx.js +46 -0
  230. package/tradelayer.js/utils/sendLTC.js +63 -0
  231. package/tradelayer.js/utils/sendMainnet.js +62 -0
  232. package/tradelayer.js/utils/sendTransfer.js +19 -0
  233. package/tradelayer.js/utils/sendVestTest.js +88 -0
  234. package/tradelayer.js/utils/sendWithdrawal.js +26 -0
  235. package/tradelayer.js/utils/simpleStart.js +8 -0
  236. package/tradelayer.js/utils/startStop.js +27 -0
  237. package/tradelayer.js/utils/structuredTrades.js +136 -0
  238. package/tradelayer.js/utils/verifySignature.js +90 -0
  239. package/tradelayer.js/utils/verifyWitnessAndScriptPubkey.js +41 -0
  240. package/tradelayer.js/utils/walletCache.js +172 -0
  241. package/tradelayer.js/utils/walletContractInterface.js +48 -0
  242. package/tradelayer.js/utils/walletFetchTxs.js +66 -0
  243. package/tradelayer.js/utils/walletUtils.js +97 -0
  244. package/tradelayer.js/utils/wipeDB.js +55 -0
  245. package/tradelayer.js/utils/wipeDBNotTx.js +50 -0
  246. package/txEncoder.js +529 -0
  247. package/utility.js +28 -0
  248. package/verifymessage.js +38 -0
  249. package/ws-transport.js +311 -0
@@ -0,0 +1,1275 @@
1
+ var dbInstance = require('./db.js')
2
+ var TxUtils = require('./txUtils.js')
3
+ var PropertyList = require('./property.js')
4
+ const uuid = require('uuid');
5
+ const BigNumber = require('bignumber.js');
6
+ const Insurance = require('./insurance.js')
7
+ const Orderbooks = require('./orderbook.js')
8
+
9
+ const SATS = new BigNumber(1e8);
10
+ const RD = BigNumber.ROUND_DOWN;
11
+
12
+ function toSatsDecimal(x) {
13
+ // raw sats (can be fractional before flooring)
14
+ return new BigNumber(x).times(SATS);
15
+ }
16
+ function toSats(x) {
17
+ // integer sats, never creates value
18
+ return toSatsDecimal(x).integerValue(RD);
19
+ }
20
+ function fromSats(s) {
21
+ return new BigNumber(s).div(SATS);
22
+ }
23
+
24
+ /**
25
+ * Dust record is per (propertyId, contractId) in feeCache DB.
26
+ * We store potential fractional sats as a decimal BigNumber "dustSats"
27
+ * so many <1-sat remainders eventually roll up to whole sats.
28
+ *
29
+ * Schema: { dustSats: "decimal-string" }
30
+ * Key: `dust::<propertyId>-<contractId>`
31
+ */
32
+ async function _loadDust(db, key) {
33
+ try {
34
+ const row = await db.get(`dust::${key}`);
35
+ const dustSats = new BigNumber(row?.dustSats ?? 0);
36
+ return { dustSats };
37
+ } catch {
38
+ return { dustSats: new BigNumber(0) };
39
+ }
40
+ }
41
+
42
+ async function _saveDust(db, key, dustSats) {
43
+ await db.put(`dust::${key}`, { dustSats: dustSats.toString() });
44
+ }
45
+
46
+ /**
47
+ * Accumulate dust (can be fractional sat units). When it reaches ≥ 1 sat,
48
+ * credit whole sats to Insurance and keep the fractional remainder.
49
+ *
50
+ * creditFn({wholeSats}) must deposit exactly `wholeSats` (integer) sats to Insurance.
51
+ */
52
+ async function _accumulateDust(db, key, addDustSatsDecimal, creditFn) {
53
+ if (!addDustSatsDecimal || new BigNumber(addDustSatsDecimal).eq(0)) return;
54
+
55
+ const { dustSats } = await _loadDust(db, key);
56
+ const next = dustSats.plus(addDustSatsDecimal);
57
+ const whole = next.integerValue(RD);
58
+ const frac = next.minus(whole);
59
+
60
+ if (whole.gt(0)) {
61
+ await creditFn({ wholeSats: whole });
62
+ }
63
+ await _saveDust(db, key, frac);
64
+ }
65
+
66
+ class TallyMap {
67
+ static instance;
68
+
69
+ constructor(path) {
70
+ if (!TallyMap.instance) {
71
+ this.addresses = new Map();
72
+ this.feeCache = new Map(); // Map for storing fees for each propertyId
73
+ TallyMap.instance = this;
74
+ this.modFlag = false
75
+ }
76
+ return TallyMap.instance;
77
+ }
78
+
79
+ /**
80
+ * Ensures that only one instance of TallyMap exists and attempts to load it from dbInstance.
81
+ * @param {number} blockHeight - The block height for which to load the tally map.
82
+ * @returns {Promise<TallyMap>} - A promise that resolves to the singleton instance of the TallyMap.
83
+ */
84
+ static async getInstance() {
85
+ if (!TallyMap.instance) {
86
+ TallyMap.instance = new TallyMap();
87
+ }
88
+ await TallyMap.loadFromDB();
89
+ return TallyMap.instance;
90
+ }
91
+
92
+ static async setModFlag(flag){
93
+ this.modFlag = flag
94
+ return
95
+ }
96
+
97
+ async verifyPropertyIds() {
98
+ let propertyIndex = await PropertyList.getPropertyIndex()
99
+
100
+ for (const [address, properties] of this.addresses.entries()) {
101
+ for (const propertyId in properties) {
102
+ if (!this.propertyIndex.has(propertyId)) {
103
+ console.error(`Invalid propertyId ${propertyId} found for address ${address}`);
104
+ // Handle the error - either remove the invalid entry or log it for further investigation
105
+ }
106
+ }
107
+ }
108
+ }
109
+
110
+ static async updateBalance(address, propertyId, availableChange, reservedChange, marginChange, vestingChange, type, block,txid) {
111
+ console.log('inside updateBalance for '+address, propertyId, availableChange, reservedChange, marginChange, vestingChange, type, block)
112
+
113
+ if(availableChange==null||reservedChange==null||marginChange==null||vestingChange==null||isNaN(availableChange)||isNaN(reservedChange)||isNaN(marginChange)||isNaN(vestingChange)){
114
+ throw new Error('Somehow null passed into updateBalance... avail. '+availableChange + ' reserved '+ reservedChange + ' margin' + marginChange + ' vesting '+vestingChange )
115
+ }
116
+
117
+ if (typeof propertyId === 'string' && propertyId.startsWith('s-')) {
118
+ // Handle synthetic token
119
+ } else if (!Number.isInteger(propertyId)) {
120
+ return Error(`Invalid propertyId: ${propertyId}`);
121
+ }
122
+
123
+
124
+ if (typeof availableChange !== 'number'){
125
+ console.log(`string passed in: ${availableChange}`);
126
+ availableChange = new BigNumber(availableChange).toNumber()
127
+ console.log('new availableChange '+availableChange)
128
+ }
129
+ if(typeof reservedChange !== 'number'){
130
+ console.log(`string passed in: ${reservedChange}`);
131
+ reservedChange = new BigNumber(reservedChange).toNumber()
132
+ }
133
+ if(typeof marginChange !== 'number'){
134
+ console.log(`string passed in: ${marginChange}`);
135
+ marginChange = new BigNumber(marginChange).toNumber()
136
+ console.log('new margin Change '+marginChange)
137
+ }
138
+ if(typeof vestingChange !== 'number'){
139
+ console.log(`string passed in: ${vestingChange}`);
140
+ vestingChange = new BigNumber(vestingChange).toNumber()
141
+ }
142
+
143
+ await TallyMap.loadFromDB();
144
+ if (!TallyMap.addresses.has(address)) {
145
+ TallyMap.addresses.set(address, {});
146
+ }
147
+ const addressObj = TallyMap.addresses.get(address);
148
+
149
+ console.log('addressObj being changed '+propertyId + ' for addr '+JSON.stringify(addressObj[propertyId]))
150
+
151
+ if (!addressObj[propertyId]) {
152
+ addressObj[propertyId] = { amount: 0, available: 0, reserved: 0, margin: 0, vesting: 0 };
153
+ }
154
+
155
+ // Check and update available balance
156
+ // Assuming addressObj[propertyId] and the respective change variables are already BigNumber instances
157
+ // Example for available balance
158
+
159
+ const originalAvailableBalance = new BigNumber(addressObj[propertyId].available);
160
+ const newAvailableBalance = originalAvailableBalance.plus(availableChange);
161
+ console.log('avail. balance change '+originalAvailableBalance, newAvailableBalance.toNumber(),availableChange)
162
+ if (newAvailableBalance.isLessThan(0)) {
163
+ throw new Error("Available balance cannot go negative " + originalAvailableBalance.toString() + ' change ' + availableChange.toString());
164
+ }
165
+
166
+ addressObj[propertyId].available = newAvailableBalance.toNumber();
167
+
168
+ // Repeat the pattern for reserved, margin, and vesting balances
169
+
170
+ // Example for reserved balance
171
+ const originalReservedBalance = new BigNumber(addressObj[propertyId].reserved);
172
+ const newReservedBalance = originalReservedBalance.plus(reservedChange);
173
+ console.log('reserve. balance change '+originalReservedBalance, newReservedBalance.toNumber(),availableChange)
174
+
175
+ if (newReservedBalance.isLessThan(0)) {
176
+ throw new Error("Reserved balance cannot go negative " + originalReservedBalance.toString() + ' change ' + reservedChange.toString());
177
+ }
178
+
179
+ addressObj[propertyId].reserved = newReservedBalance.toNumber();
180
+
181
+ // Example for margin balance
182
+ const originalMarginBalance = new BigNumber(addressObj[propertyId].margin);
183
+ const newMarginBalance = originalMarginBalance.plus(marginChange);
184
+ console.log('old and new margin balance '+originalMarginBalance+' '+newMarginBalance)
185
+ if (newMarginBalance.isLessThan(0)) {
186
+ throw new Error("Margin balance cannot go negative " + originalMarginBalance.toString() + ' change ' + marginChange.toString());
187
+ }
188
+
189
+ addressObj[propertyId].margin = newMarginBalance.toNumber();
190
+
191
+ // Example for vesting balance
192
+ const originalVestingBalance = new BigNumber(addressObj[propertyId].vesting);
193
+ const newVestingBalance = originalVestingBalance.plus(vestingChange);
194
+
195
+ if (newVestingBalance.isLessThan(0)) {
196
+ throw new Error("Vesting balance cannot go negative " + originalVestingBalance.toString() + ' change ' + vestingChange.toString());
197
+ }
198
+
199
+ addressObj[propertyId].vesting = newVestingBalance.toNumber();
200
+
201
+ // Update the total amount
202
+ addressObj[propertyId].amount = this.calculateTotal(addressObj[propertyId]);
203
+
204
+ if (typeof addressObj[propertyId].channelBalance === 'undefined') {
205
+ addressObj[propertyId].channelBalance = 0;
206
+ }
207
+
208
+
209
+ if(availableChange==0&&reservedChange==0&&marginChange==0&&vestingChange==0){
210
+
211
+ }else{
212
+ await TallyMap.recordTallyMapDelta(address, block, propertyId, addressObj[propertyId].amount, availableChange, reservedChange, marginChange, vestingChange, 0, type,txid)
213
+ }
214
+ TallyMap.addresses.set(address, addressObj); // Update the map with the modified address object
215
+ //console.log('Updated balance for address:', JSON.stringify(addressObj), 'with propertyId:', propertyId);
216
+ await TallyMap.saveToDB(block); // Save changes to the database
217
+ }
218
+
219
+ static async updateChannelBalance(address, propertyId, channelChange, type,block) {
220
+ await TallyMap.loadFromDB();
221
+
222
+ // Initialize the address if it doesn't exist
223
+ if (!TallyMap.addresses.has(address)) {
224
+ TallyMap.addresses.set(address, {});
225
+ }
226
+ const addressObj = TallyMap.addresses.get(address);
227
+
228
+ // Initialize the propertyId if it doesn't exist
229
+ if (!addressObj[propertyId]) {
230
+ addressObj[propertyId] = { amount: 0, available: 0, reserved: 0, margin: 0, vesting: 0, channelBalance: 0 };
231
+ }
232
+
233
+ // Handle undefined channel balance and set it to 0 if necessary
234
+ if (typeof addressObj[propertyId].channelBalance === 'undefined') {
235
+ addressObj[propertyId].channelBalance = 0;
236
+ }
237
+
238
+ // Update channel balance
239
+ const originalChannelBalance = new BigNumber(addressObj[propertyId].channelBalance);
240
+ const newChannelBalance = originalChannelBalance.plus(channelChange);
241
+
242
+ if (newChannelBalance.isLessThan(0)) {
243
+ throw new Error(`Channel balance cannot go negative for property ${propertyId}`);
244
+ }
245
+
246
+ // Update the channel balance
247
+ addressObj[propertyId].channelBalance = newChannelBalance.toNumber();
248
+ addressObj[propertyId].amount = this.calculateTotal(addressObj[propertyId]);
249
+ // Record the channel balance change
250
+ if (channelChange !== 0) {
251
+ await TallyMap.recordTallyMapDelta(
252
+ address,
253
+ block,
254
+ propertyId,
255
+ addressObj[propertyId].amount,
256
+ 0, // No change in available
257
+ 0, // No change in reserved
258
+ 0, // No change in margin
259
+ 0, // No change in vesting
260
+ channelChange,
261
+ type
262
+ );
263
+ }
264
+
265
+ // Save the updated object back to the map
266
+ TallyMap.addresses.set(address, addressObj);
267
+ await TallyMap.saveToDB(block); // Save the updated balance to the database
268
+ }
269
+
270
+ static async getTotalForProperty(propertyId) {
271
+ const instance = await TallyMap.getInstance();
272
+ let totalBalance = new BigNumber(0);
273
+
274
+ // Convert propertyId to a string to match stored keys
275
+ const propertyKey = String(propertyId);
276
+
277
+ // Iterate over all addresses in tallyMap
278
+ for (const [address, properties] of TallyMap.addresses.entries()) {
279
+
280
+ if (properties[propertyKey]) {
281
+ const balance = properties[propertyKey];
282
+ console.log('property balance '+JSON.stringify(balance))
283
+ // Ensure all balance components are properly defined
284
+ const available = new BigNumber(balance.available || 0);
285
+ const reserved = new BigNumber(balance.reserved || 0);
286
+ const margin = new BigNumber(balance.margin || 0);
287
+ const channel = new BigNumber(balance.channelBalance || 0);
288
+ if (available.isNaN() || reserved.isNaN() || margin.isNaN() || channel.isNaN()) {
289
+ console.error(`🚨 NaN detected in balance calculation for property ${propertyKey}`, {
290
+ available: available.toFixed(),
291
+ reserved: reserved.toFixed(),
292
+ margin: margin.toFixed(),
293
+ channel: channel.toFixed(),
294
+ });
295
+ continue; // Skip this entry
296
+ }
297
+
298
+ // Add up all the valid balances
299
+ totalBalance = totalBalance.plus(available).plus(reserved).plus(margin).plus(channel);
300
+ }
301
+ }
302
+
303
+ return totalBalance;
304
+ }
305
+
306
+ static calculateTotal(balanceObj) {
307
+ return BigNumber(balanceObj.available).plus(balanceObj.reserved).plus(balanceObj.margin).plus(balanceObj.channel).decimalPlaces(8).toNumber();
308
+ }
309
+
310
+ static async setInitializationFlag() {
311
+ const db = await dbInstance.getDatabase('tallyMap');
312
+ await db.updateAsync(
313
+ { _id: '$TLinit' },
314
+ { _id: '$TLinit', initialized: true },
315
+ { upsert: true }
316
+ );
317
+ }
318
+
319
+ static async checkInitializationFlag() {
320
+ const db = await dbInstance.getDatabase('tallyMap');
321
+ const result = await db.findOneAsync({ _id: '$TLinit' });
322
+ if(result==undefined){return false}
323
+ return result ? result.initialized : false;
324
+ }
325
+
326
+
327
+ static async getAddressBalances(address) {
328
+ await TallyMap.loadFromDB();
329
+
330
+ // Log the serialized form of the data from the DB
331
+ //console.log('Serialized data from DB:', JSON.stringify([...instance.addresses]));
332
+
333
+ // Check if the address exists in the map
334
+ if (!TallyMap.addresses.has(address)) {
335
+ console.log(`No data found for address: ${address}`);
336
+ return [];
337
+ }
338
+
339
+ const addressObj = TallyMap.addresses.get(address);
340
+ //console.log(`Data for address ${address}:`, addressObj);
341
+ const balances = [];
342
+ for (const propertyId in addressObj) {
343
+ //console.log('bleh' +propertyId+' '+JSON.stringify(addressObj))
344
+ const info = await PropertyList.getPropertyData(propertyId)
345
+ if (Object.hasOwnProperty.call(addressObj, propertyId)) {
346
+ const balanceObj = addressObj[propertyId];
347
+ let ticker = ''
348
+ if(info!=null&&info.ticker){ticker=info.ticker}
349
+ //console.log(propertyId, JSON.stringify(balanceObj),JSON.stringify(info))
350
+ balances.push({
351
+ propertyId: propertyId,
352
+ ticker: info.ticker,
353
+ amount: balanceObj.amount,
354
+ available: balanceObj.available,
355
+ reserved: balanceObj.reserved,
356
+ margin: balanceObj.margin,
357
+ vesting: balanceObj.vesting,
358
+ channel: balanceObj.channelBalance
359
+ });
360
+ }
361
+ }
362
+ //console.log(`Balances for address ${address}:`, balances);
363
+ return balances;
364
+ }
365
+
366
+ /**
367
+ * Retrieves the total tally for a given property ID across all addresses.
368
+ * @param {number|string} propertyId - The property ID to aggregate balances for.
369
+ * @returns {Promise<Object>} - An object representing the total tally for the given property.
370
+ */
371
+ static async getTotalTally(propertyId) {
372
+ const instance = await TallyMap.getInstance();
373
+ const totalTally = {
374
+ amount: 0,
375
+ available: 0,
376
+ reserved: 0,
377
+ margin: 0,
378
+ vesting: 0,
379
+ channelBalance: 0
380
+ };
381
+
382
+ for (const properties of TallyMap.addresses.values()) {
383
+ if (properties[propertyId]) {
384
+ totalTally.amount += properties[propertyId].amount || 0;
385
+ totalTally.available += properties[propertyId].available || 0;
386
+ totalTally.reserved += properties[propertyId].reserved || 0;
387
+ totalTally.margin += properties[propertyId].margin || 0;
388
+ totalTally.vesting += properties[propertyId].vesting || 0;
389
+ totalTally.channelBalance += properties[propertyId].channelBalance || 0;
390
+ }
391
+ }
392
+
393
+ return totalTally;
394
+ }
395
+
396
+ /**
397
+ * Checks if a sender has a sufficient balance of a specific property.
398
+ * @param {string} senderAddress - The address of the sender.
399
+ * @param {number} propertyId - The ID of the property to check.
400
+ * @param {number} requiredAmount - The amount required for the transaction.
401
+ * @returns {Promise<{hasSufficient: boolean, reason: string}>} - An object indicating if the balance is sufficient and a reason if it's not.
402
+ */
403
+ static async hasSufficientBalance(senderAddress, propertyId, requiredAmount) {
404
+ try {
405
+ const senderTally = await this.getTally(senderAddress, propertyId);
406
+ console.log('Checking senderTally in has hasSufficientBalance', senderAddress, propertyId, requiredAmount, JSON.stringify(senderTally));
407
+
408
+ if(!senderTally || senderTally.available === undefined||senderTally==0){
409
+ return { hasSufficient: false, reason: 'undefined', shortfall: requiredAmount };
410
+ }
411
+
412
+ //console.log('Available tokens:', senderTally.available, 'Required amount:', requiredAmount);
413
+ if(senderTally.available < requiredAmount){
414
+ const availBN = new BigNumber(senderTally.available)
415
+ const shortfall = new BigNumber(requiredAmount).minus(availBN).decimalPlaces(8).toNumber()
416
+ console.log('shortfall calc '+requiredAmount+' '+senderTally.available+' '+shortfall)
417
+ return { hasSufficient: false, reason: 'Insufficient available balance', shortfall:shortfall, available:senderTally.available };
418
+ }
419
+
420
+ return { hasSufficient: true, reason: '' };
421
+ } catch (error) {
422
+ console.error('Error in hasSufficientBalance:', error);
423
+ return { hasSufficient: false, reason: 'Unexpected error checking balance' };
424
+ }
425
+ }
426
+
427
+ static async hasSufficientReserve(senderAddress, propertyId, requiredAmount) {
428
+ try {
429
+ const senderTally = await this.getTally(senderAddress, propertyId);
430
+ console.log('Checking senderTally in has hasSufficientReserve', senderAddress, propertyId, requiredAmount, JSON.stringify(senderTally));
431
+
432
+ if (!senderTally || senderTally.reserved === undefined) {
433
+ return { hasSufficient: false, reason: 'undefined', shortfall: requiredAmount };
434
+ }
435
+
436
+ console.log('Reserve tokens:', senderTally.reserved, 'Required amount:', requiredAmount);
437
+
438
+ if (senderTally.reserved < requiredAmount) {
439
+ let requiredBN = new BigNumber(requiredAmount)
440
+ let reservedBN = new BigNumber(senderTally.reserved)
441
+ let shortfall= requiredBN.minus(reservedBN).toNumber()
442
+ console.log('insufficient tokens ' +shortfall)
443
+ return { hasSufficient: false, reason: 'Insufficient available balance', shortfall: shortfall };
444
+ }
445
+
446
+ return { hasSufficient: true, reason: '' };
447
+ } catch (error) {
448
+ console.error('Error in hasSufficientBalance:', error);
449
+ return { hasSufficient: false, reason: 'Unexpected error checking balance' };
450
+ }
451
+ }
452
+
453
+ static async hasSufficientMargin(senderAddress, propertyId, requiredAmount) {
454
+ try {
455
+ const senderTally = await this.getTally(senderAddress, propertyId);
456
+ console.log('Checking senderTally in has hasSufficientMargin', senderAddress, propertyId, requiredAmount, JSON.stringify(senderTally));
457
+
458
+ if (!senderTally || senderTally.margin === undefined) {
459
+ return { hasSufficient: false, reason: 'undefined', shortfall: requiredAmount };
460
+ }
461
+
462
+ console.log('Margin tokens:', senderTally.margin, 'Required amount:', requiredAmount);
463
+
464
+ if (senderTally.margin < requiredAmount) {
465
+ let requiredBN = new BigNumber(requiredAmount)
466
+ let marginBN = new BigNumber(senderTally.margin)
467
+ let shortfall= requiredBN.minus(marginBN).toNumber()
468
+ console.log('insufficient tokens ' +shortfall)
469
+ return { hasSufficient: false, reason: 'Insufficient available balance', shortfall: shortfall };
470
+ }
471
+
472
+ return { hasSufficient: true, reason: '' };
473
+ } catch (error) {
474
+ console.error('Error in hasSufficientBalance:', error);
475
+ return { hasSufficient: false, reason: 'Unexpected error checking balance' };
476
+ }
477
+ }
478
+
479
+ static async hasSufficientChannel(senderAddress, propertyId, requiredAmount) {
480
+ try {
481
+ const senderTally = await this.getTally(senderAddress, propertyId);
482
+ console.log('Checking senderTally in has hasSufficientChannel', senderAddress, propertyId, requiredAmount, JSON.stringify(senderTally));
483
+
484
+ if (!senderTally || senderTally.channel === undefined) {
485
+ return { hasSufficient: false, reason: 'undefined' };
486
+ }
487
+
488
+ console.log('Channel tokens:', senderTally.channel, 'Required amount:', requiredAmount);
489
+
490
+ if (senderTally.channel < requiredAmount) {
491
+ let requiredBN = new BigNumber(requiredAmount)
492
+ let channelBN = new BigNumber(senderTally.channel)
493
+ let shortfall= requiredBN.minus(channelBN).toNumber()
494
+ console.log('insufficient tokens ' +shortfall)
495
+ return { hasSufficient: false, reason: 'Insufficient available balance', shortfall: shortfall };
496
+ }
497
+
498
+ return { hasSufficient: true, reason: '' };
499
+ } catch (error) {
500
+ console.error('Error in hasSufficientBalance:', error);
501
+ return { hasSufficient: false, reason: 'Unexpected error checking balance' };
502
+ }
503
+ }
504
+
505
+ static async saveToDB(block) {
506
+ try {
507
+ const db = await dbInstance.getDatabase('tallyMap');
508
+ const serializedData = JSON.stringify([...this.addresses]);
509
+ console.log('saving tallymap')
510
+ // Use upsert option
511
+ await db.updateAsync({ _id: 'tallyMap' }, { $set: {block: block, data: serializedData } }, { upsert: true });
512
+ //console.log('TallyMap saved successfully.');
513
+ } catch (error) {
514
+ console.error('Error saving TallyMap:', error);
515
+ }
516
+ }
517
+
518
+ static async loadFromDB() {
519
+ try {
520
+ const query = { _id: 'tallyMap' };
521
+ const db = await dbInstance.getDatabase('tallyMap')
522
+ const result = await db.findOneAsync(query);
523
+
524
+ if (result && result.data) {
525
+ // Deserialize the data from a JSON string to an array
526
+ const mapDataArray = JSON.parse(result.data);
527
+ // Convert the array back into a Map
528
+ this.addresses = new Map(mapDataArray.map(([key, value]) => [key, value]));
529
+ return this.addresses
530
+ } else {
531
+ console.log('failed to load tallyMap, starting a new map')
532
+ return this.addresses = new Map(); // Ensure addresses is always a Map
533
+ }
534
+ } catch (error) {
535
+ console.error('Error loading tally map from dbInstance:', error);
536
+ }
537
+ }
538
+
539
+ static async saveFeeCacheToDB(propertyId, feeAmount, contractid) {
540
+ if (propertyId === undefined || feeAmount === undefined) {
541
+ console.error('Property ID or fee amount is undefined.');
542
+ return;
543
+ }
544
+ console.log('Inside save fee cache ' + propertyId + ' ' + feeAmount);
545
+ const db = await dbInstance.getDatabase('feeCache');
546
+ try {
547
+ const roundedFee = Number(new BigNumber(feeAmount).toFixed(8)); // ✅ Ensures max 8 decimal places
548
+ const serializedFeeAmount = roundedFee
549
+ // Convert propertyId to a string if it's not already a string
550
+ const cacheId = String(propertyId)+String(contractid);
551
+
552
+ await db.updateAsync(
553
+ { _id: cacheId }, // Query to find the document
554
+ { $set: { value: serializedFeeAmount, contract: contractid } }, // Update the value field
555
+ { upsert: true } // Insert a new document if it doesn't exist
556
+ );
557
+ console.log('FeeCache for property ' + propertyId + ' saved successfully.');
558
+ } catch (error) {
559
+ console.error('Error saving FeeCache:', error);
560
+ }
561
+ }
562
+
563
+ static async loadFeeCacheFromDB() {
564
+ let fees = new Map();
565
+
566
+ try {
567
+ const db = await dbInstance.getDatabase('feeCache');
568
+ const results = await db.findAsync({});
569
+
570
+ if (!results || results.length === 0) {
571
+ //console.log("⚠️ No fee cache entries found.");
572
+ return fees;
573
+ }
574
+
575
+ //console.log(`✅ Loaded ${results.length} fee cache entries.`);
576
+
577
+ for (let result of results) {
578
+ //console.log(`📝 DB Entry: ${JSON.stringify(result)}`);
579
+
580
+ if (!result._id) {
581
+ console.warn(`⚠️ Skipping malformed entry (missing _id): ${JSON.stringify(result)}`);
582
+ continue;
583
+ }
584
+
585
+ if (typeof result.value === "undefined") {
586
+ continue;
587
+ }
588
+ if(!result.stash){result.stash=0}
589
+ let feeData = {
590
+ value: parseFloat(result.value), // ✅ Ensure value is a number
591
+ contract: result.contract || null,
592
+ stash: parseFloat(result.stash)
593
+ };
594
+
595
+ //console.log(`🔹 Fee Cache Parsed - Key: ${result._id}, Value: ${feeData.value}, Contract: ${feeData.contract}`);
596
+
597
+ fees.set(result._id, feeData);
598
+ }
599
+ } catch (error) {
600
+ console.error(`🚨 Error loading fee cache:`, error);
601
+ }
602
+
603
+ return fees;
604
+ }
605
+
606
+ static async loadFeeCacheForProperty(id) {
607
+ try {
608
+ const db = await dbInstance.getDatabase('feeCache');
609
+ const result = await db.findAsync({});
610
+ console.log('📄 Database contents:', JSON.stringify(result, null, 2));
611
+
612
+ let total = new BigNumber(0);
613
+
614
+ for(const doc of result){
615
+ if (doc._id.startsWith(`${id}-`)) {
616
+ const value = new BigNumber(doc.value || 0);
617
+ const stash = new BigNumber(doc.stash || 0);
618
+ total = total.plus(value).plus(stash);
619
+ console.log(`➕ Matched ${doc._id}: value=${value.toFixed()}, stash=${stash.toFixed()}, running total=${total.toFixed()}`);
620
+ }
621
+ }
622
+
623
+ console.log(`✅ FeeCache total for property ${id}: ${total.toFixed()}`);
624
+ return total;
625
+ } catch (error) {
626
+ console.error('❌ Error loading fee cache from dbInstance:', error);
627
+ return new BigNumber(0);
628
+ }
629
+ }
630
+
631
+
632
+ static async loadTallyBlobDirect() {
633
+ try {
634
+ const dbInstance = require('./db.js')
635
+ const db = await dbInstance.getDatabase('tallyMap');
636
+ const result = await db.findOneAsync({ _id: 'tallyMap' });
637
+
638
+ if (!result) {
639
+ console.log("[TALLY] No tallyMap found in DB.");
640
+ return null;
641
+ }
642
+
643
+ // result = { _id, block, data: "JSON_STRING" }
644
+ return result;
645
+
646
+ } catch (err) {
647
+ console.error("[TALLY] Error loading tally map:", err);
648
+ return null;
649
+ }
650
+ }
651
+
652
+ // Method to update fee cache for a property
653
+ // tally.js
654
+
655
+ static async resolveBlock(explicitBlock) {
656
+ if (explicitBlock !== undefined && explicitBlock !== null) return explicitBlock;
657
+ try {
658
+ const consensus = await dbInstance.getDatabase('consensus');
659
+ const t = await consensus.findOneAsync({ _id: 'TrackHeight' });
660
+ const m = await consensus.findOneAsync({ _id: 'MaxProcessedHeight' });
661
+ return (t && t.value) || (m && m.value) || null;
662
+ } catch { return null; }
663
+ }
664
+
665
+ static async loadFeeRow(db, cacheId) {
666
+ const row = await db.findOneAsync({ _id: cacheId });
667
+ return {
668
+ value: new BigNumber(row ? row.value || 0 : 0),
669
+ stash: new BigNumber(row ? row.stash || 0 : 0),
670
+ contract: row ? row.contract : undefined,
671
+ };
672
+ }
673
+ static async saveFeeRow(db, cacheId, { value, stash, contract }) {
674
+ await db.updateAsync(
675
+ { _id: cacheId },
676
+ {
677
+ $set: {
678
+ value: new BigNumber(value).decimalPlaces(8).toNumber(),
679
+ stash: new BigNumber(stash).decimalPlaces(8).toNumber(),
680
+ contract: contract,
681
+ },
682
+ },
683
+ { upsert: true }
684
+ );
685
+ }
686
+
687
+ /*
688
+ * updateFeeCache with full tracing:
689
+ * - Logs every decision point: spot, TL-spot, derivative, oracle/non-oracle.
690
+ * - Logs sat math: raw, rounded, half-split, dust.
691
+ * - Logs stash/insurance writes with exact integer sats.
692
+ */static async updateFeeCache(propertyId, amount, contractId, block) {
693
+ const ContractRegistry = require('./contractRegistry.js');
694
+
695
+ try {
696
+ const blockArg = arguments.length >= 6 ? arguments[5] : block;
697
+
698
+ console.log(`\n====== updateFeeCache() @ block ${blockArg} ======`);
699
+ console.log(`INPUT propertyId=${propertyId}, amount=${amount}, contractId=${contractId}`);
700
+
701
+ // Convert → sats
702
+ let rawSats = toSatsDecimal(amount);
703
+ console.log(`rawSats (pre-floor) = ${rawSats.toString()}`);
704
+
705
+ rawSats = rawSats.integerValue(BigNumber.ROUND_FLOOR);
706
+ console.log(`rawSats (floored sats) = ${rawSats.toString()}`);
707
+
708
+ // Require even sats (should happen automatically from calculateFee)
709
+ let feeSats = rawSats;
710
+
711
+ console.log(`feeSats (final even sats) = ${feeSats.toString()}`);
712
+
713
+ if (feeSats.lte(0)) {
714
+ console.log(`EXIT: feeSats <= 0`);
715
+ return;
716
+ }
717
+
718
+ const db = await dbInstance.getDatabase('feeCache');
719
+ const blk = await TallyMap.resolveBlock(blockArg);
720
+ const effContractId = (contractId == null) ? '1' : String(contractId);
721
+
722
+ const isOracleContract = await ContractRegistry.isOracleContract(contractId);
723
+ console.log(`Resolved: effContractId=${effContractId}, isOracle=${isOracleContract}`);
724
+
725
+ //
726
+ // 1) SPOT NON-TL
727
+ //
728
+ if (effContractId === '1' && propertyId != 1) {
729
+ const cacheId = `${propertyId}-1`;
730
+ console.log(`SPOT NON-TL route → stash only → cacheId=${cacheId}`);
731
+
732
+ const row0 = await TallyMap.loadFeeRow(db, cacheId);
733
+ console.log(`Pre-stash row: stash=${row0.stash.toString()}`);
734
+
735
+ const addTokens = fromSats(feeSats);
736
+ console.log(`Adding to stash: +${addTokens.toString()} tokens`);
737
+
738
+ await TallyMap.saveFeeRow(db, cacheId, {
739
+ stash: row0.stash.plus(addTokens),
740
+ contract: '1'
741
+ });
742
+
743
+ const row1 = await TallyMap.loadFeeRow(db, cacheId);
744
+ console.log(`Post-stash row: stash=${row1.stash.toString()}`);
745
+
746
+ console.log(`→ running buyback`);
747
+ return await TallyMap.feeCacheBuy(blockArg);
748
+ }
749
+
750
+ //
751
+ // 2) SPOT TL-TOKEN (propertyId==1)
752
+ //
753
+ if (effContractId === '1' && propertyId == 1) {
754
+ console.log(`SPOT TL route → 100% to insurance`);
755
+
756
+ const insurance = await Insurance.getInstance(effContractId, false);
757
+
758
+ console.log(`insurance.deposit: +${fromSats(feeSats).toFixed(8)} (LTC)`);
759
+ await insurance.deposit(propertyId, fromSats(feeSats).toFixed(8), blk);
760
+
761
+ return;
762
+ }
763
+
764
+ //
765
+ // 3) DERIVATIVES
766
+ //
767
+ console.log(`DERIVATIVES route (contract!=1)`);
768
+
769
+ const insuranceSats = feeSats.idiv(2); // floor
770
+ const stashSats = feeSats.minus(insuranceSats);
771
+
772
+ console.log(`50/50 split: insuranceSats=${insuranceSats}, stashSats=${stashSats}`);
773
+
774
+ if (!insuranceSats.plus(stashSats).eq(feeSats)) {
775
+ console.log(`⚠ Split mismatch (summing glitch), fixing stash = fee-insurance`);
776
+ stashSats = stashSats.minus(1);
777
+ }
778
+
779
+ //
780
+ // Insurance write
781
+ //
782
+ try {
783
+ console.log(`Insurance deposit: contract=${effContractId}, isOracle=${isOracleContract}`);
784
+ console.log(`insurance.deposit: +${fromSats(insuranceSats).toFixed(8)} (LTC)`);
785
+
786
+ const insurance = await Insurance.getInstance(effContractId, isOracleContract);
787
+
788
+ await insurance.deposit(
789
+ propertyId,
790
+ fromSats(insuranceSats).toFixed(8),
791
+ blk
792
+ );
793
+ } catch (e) {
794
+ console.error(`❌ Insurance deposit ERROR for contract ${effContractId}:`, e);
795
+ }
796
+
797
+ //
798
+ // Stash write
799
+ //
800
+ const cacheId = `${propertyId}-${effContractId}`;
801
+ console.log(`Stash write → cacheId=${cacheId}`);
802
+
803
+ const stashRow0 = await TallyMap.loadFeeRow(db, cacheId);
804
+ console.log(`Pre-stash stash=${stashRow0.stash.toString()}`);
805
+
806
+ await TallyMap.saveFeeRow(db, cacheId, {
807
+ stash: stashRow0.stash.plus(fromSats(stashSats)),
808
+ contract: effContractId
809
+ });
810
+
811
+ const stashRow1 = await TallyMap.loadFeeRow(db, cacheId);
812
+ console.log(`Post-stash stash=${stashRow1.stash.toString()}`);
813
+
814
+ //
815
+ // 4) Buyback
816
+ //
817
+ console.log(`→ running buyback`);
818
+ const ret = await TallyMap.feeCacheBuy(blockArg);
819
+
820
+ console.log(`====== END updateFeeCache() @ block ${blockArg} ======\n`);
821
+ return ret;
822
+
823
+ } catch (e) {
824
+ console.error('🚨 Error in updateFeeCache:', e);
825
+ }
826
+ }
827
+
828
+
829
+ static async feeCacheBuy(block) {
830
+ const fees = await TallyMap.loadFeeCacheFromDB();
831
+ if (!fees || fees.size === 0) return;
832
+
833
+ for (const [key, feeData] of fees.entries()) {
834
+ if (!feeData || !feeData.contract) continue;
835
+
836
+ const [property, contractId] = key.split('-');
837
+ const value = new BigNumber(feeData.value || 0);
838
+ const stash = new BigNumber(feeData.stash || 0);
839
+ const total = value.plus(stash);
840
+ if (total.lte(0)) continue;
841
+
842
+ const orderBookKey = `1-${property}`;
843
+ const orderbook = await Orderbooks.getOrderbookInstance(orderBookKey);
844
+ const ob = orderbook.orderBooks[orderBookKey] || { buy: [], sell: [] };
845
+ const hasSell = Array.isArray(ob.sell) && ob.sell.length > 0;
846
+
847
+ if (!hasSell) continue; // nothing to do this block
848
+
849
+ // Place a single buy using total (value + stash)
850
+ const order = {
851
+ offeredPropertyId: property,
852
+ desiredPropertyId: 1,
853
+ amountOffered: total.toNumber(),
854
+ amountExpected: 0.00000001,
855
+ blockTime: block,
856
+ sender: 'feeCache',
857
+ };
858
+ order.price = orderbook.calculatePrice(order.amountOffered, order.amountExpected);
859
+
860
+ const reply = await orderbook.insertOrder(order, orderBookKey, false, false);
861
+
862
+ // Pre-deduct: set value→0 and spend all stash
863
+ if (value.gt(0)) await TallyMap.adjustFeeCache(property, contractId, { valueDelta: value.negated() });
864
+ if (stash.gt(0)) await TallyMap.adjustFeeCache(property, contractId, { stashDelta: stash.negated() });
865
+
866
+ const matchResult = await orderbook.matchTokenOrders(reply);
867
+ if (matchResult.matches && matchResult.matches.length > 0) {
868
+ await orderbook.processTokenMatches(matchResult.matches, block, null, false);
869
+ } else {
870
+ // restore back to stash if no matches (race)
871
+ if (value.gt(0)) await TallyMap.adjustFeeCache(property, contractId, { stashDelta: value });
872
+ if (stash.gt(0)) await TallyMap.adjustFeeCache(property, contractId, { stashDelta: stash });
873
+ }
874
+
875
+ await orderbook.saveOrderBook(orderBookKey);
876
+ }
877
+ }
878
+
879
+
880
+
881
+ /**
882
+ * accrueFee:
883
+ * - SPOT (contractId null/undefined): 50/50 split in integer sats → half Insurance NOW, half -> VALUE.
884
+ * - CONTRACT:
885
+ * - Native -> 100% to VALUE (no insurance now).
886
+ * - Oracle -> 50/50 split in integer sats → half Insurance NOW, half -> STASH.
887
+ * - All rounding remainders funnel into dust and later pay to Insurance.
888
+ */
889
+ static async accrueFee(propertyId, amount, contractId, block) {
890
+ const db = await dbInstance.getDatabase('feeCache');
891
+
892
+ // Work in sats
893
+ const rawSats = toSatsDecimal(amount);
894
+ const feeSats = rawSats.integerValue(RD);
895
+ if (feeSats.lte(0)) return;
896
+
897
+ const isSpot = (contractId === null || contractId === undefined);
898
+ const effContractId = isSpot ? '1' : String(contractId);
899
+ const cacheId = `${propertyId}-${effContractId}`;
900
+ const dustKey = cacheId;
901
+ const blk = await TallyMap.resolveBlock(block);
902
+
903
+ // Accumulate conversion dust
904
+ const convDust = rawSats.minus(feeSats); // [0,1)
905
+ await _accumulateDust(db, dustKey, convDust, async ({ wholeSats }) => {
906
+ const insurance = await Insurance.getInstance(effContractId, effContractId !== '1');
907
+ await insurance.deposit(propertyId, fromSats(wholeSats).toNumber(), blk);
908
+ });
909
+
910
+ const row = await TallyMap.loadFeeRow(db, cacheId);
911
+
912
+ if (isSpot) {
913
+ // 50/50 split in integer sats
914
+ const insuranceSats = feeSats.idiv(2);
915
+ const valueSats = feeSats.minus(insuranceSats);
916
+
917
+ try {
918
+ const ins = await Insurance.getInstance('1', false);
919
+ await ins.deposit('1', fromSats(insuranceSats).toNumber(), blk);
920
+ } catch (e) {
921
+ console.error('❌ Spot fee insurance deposit failed:', e);
922
+ }
923
+
924
+ await TallyMap.saveFeeRow(db, cacheId, {
925
+ value: row.value.plus(fromSats(valueSats)),
926
+ stash: row.stash,
927
+ contract: '1',
928
+ });
929
+ return;
930
+ }
931
+
932
+ // Contract path
933
+ const isNative = await ContractRegistry.isNativeContract(effContractId).catch(() => false);
934
+ if (!isNative) {
935
+ // ORACLE: half to insurance now, exact remainder to STASH
936
+ const insuranceSats = feeSats.idiv(2);
937
+ const stashSats = feeSats.minus(insuranceSats);
938
+
939
+ try {
940
+ const ins = await Insurance.getInstance(effContractId, true);
941
+ await ins.deposit(propertyId, fromSats(insuranceSats).toNumber(), blk);
942
+ } catch (e) {
943
+ console.error(`❌ Insurance deposit failed for contract ${effContractId}:`, e);
944
+ }
945
+
946
+ await TallyMap.saveFeeRow(db, cacheId, {
947
+ value: row.value,
948
+ stash: row.stash.plus(fromSats(stashSats)),
949
+ contract: effContractId,
950
+ });
951
+ return;
952
+ }
953
+
954
+ // NATIVE: 100% to VALUE
955
+ await TallyMap.saveFeeRow(db, cacheId, {
956
+ value: row.value.plus(fromSats(feeSats)),
957
+ stash: row.stash,
958
+ contract: effContractId,
959
+ });
960
+ }
961
+ /**
962
+ * NEW: adjustFeeCache(propertyId, contractId, { valueDelta?, stashDelta? })
963
+ * - Used by clearing to spend from VALUE/STASH when matching.
964
+ */
965
+ static async adjustFeeCache(propertyId, contractId, deltas) {
966
+ const db = await dbInstance.getDatabase('feeCache');
967
+ const effectiveContractId = (contractId === null || contractId === undefined) ? '1' : String(contractId);
968
+ const cacheId = `${propertyId}-${effectiveContractId}`;
969
+ const row = await TallyMap.loadFeeRow(db, cacheId);
970
+
971
+ const valueDelta = new BigNumber(deltas?.valueDelta || 0).decimalPlaces(8);
972
+ const stashDelta = new BigNumber(deltas?.stashDelta || 0).decimalPlaces(8);
973
+
974
+ await TallyMap.saveFeeRow(db, cacheId, {
975
+ value: row.value.plus(valueDelta),
976
+ stash: row.stash.plus(stashDelta),
977
+ contract: effectiveContractId,
978
+ });
979
+ }
980
+
981
+ // ---------- 4) drawOnFeeCache (compat shim; pull from VALUE/STASH) ----------
982
+ /**
983
+ * drawOnFeeCache(propertyId, contractId='1', opts?)
984
+ * - Minimal compat: withdraw up to `opts.max` from VALUE first (then STASH if allowStash).
985
+ * - Returns { spent } in token units (8 d.p.).
986
+ */
987
+ static async drawOnFeeCache(propertyId, contractId = '1', opts = {}) {
988
+ const { max = null, allowStash = false } = opts;
989
+ const db = await dbInstance.getDatabase('feeCache');
990
+ const effContractId = (contractId == null) ? '1' : String(contractId);
991
+ const cacheId = `${propertyId}-${effContractId}`;
992
+ const row = await TallyMap.loadFeeRow(db, cacheId);
993
+
994
+ const want = (max == null) ? row.value : BigNumber.min(new BigNumber(max), row.value);
995
+ if (want.lte(0)) {
996
+ if (!allowStash) return { spent: new BigNumber(0) };
997
+ const wantFromStash = (max == null) ? row.stash : BigNumber.min(new BigNumber(max), row.stash);
998
+ if (wantFromStash.lte(0)) return { spent: new BigNumber(0) };
999
+
1000
+ // spend from STASH (sat-accurate)
1001
+ const raw = toSatsDecimal(wantFromStash);
1002
+ const sats = raw.integerValue(RD);
1003
+ const spent = fromSats(sats);
1004
+
1005
+ await _accumulateDust(db, `adj:${cacheId}:stash`, raw.minus(sats), async ({ wholeSats }) => {
1006
+ const r = await TallyMap.loadFeeRow(db, cacheId);
1007
+ await TallyMap.saveFeeRow(db, cacheId, {
1008
+ value: r.value,
1009
+ stash: r.stash.plus(fromSats(wholeSats)),
1010
+ contract: effContractId,
1011
+ });
1012
+ });
1013
+
1014
+ await TallyMap.saveFeeRow(db, cacheId, {
1015
+ value: row.value,
1016
+ stash: row.stash.minus(spent),
1017
+ contract: effContractId,
1018
+ });
1019
+ return { spent };
1020
+ }
1021
+
1022
+ // spend from VALUE
1023
+ const raw = toSatsDecimal(want);
1024
+ const sats = raw.integerValue(RD);
1025
+ const spent = fromSats(sats);
1026
+
1027
+ await _accumulateDust(db, `adj:${cacheId}:value`, raw.minus(sats), async ({ wholeSats }) => {
1028
+ const r = await TallyMap.loadFeeRow(db, cacheId);
1029
+ await TallyMap.saveFeeRow(db, cacheId, {
1030
+ value: r.value.plus(fromSats(wholeSats)),
1031
+ stash: r.stash,
1032
+ contract: effContractId,
1033
+ });
1034
+ });
1035
+
1036
+ await TallyMap.saveFeeRow(db, cacheId, {
1037
+ value: row.value.minus(spent),
1038
+ stash: row.stash,
1039
+ contract: effContractId,
1040
+ });
1041
+ return { spent };
1042
+ }
1043
+
1044
+ async applyDeltasSinceLastHeight(lastHeight) {
1045
+ // Retrieve and apply all deltas from lastHeight to the current height
1046
+ for (let height = lastHeight + 1; height <= currentBlockHeight; height++) {
1047
+ const serializedDelta = await dbInstance.get(`tallyMapDelta-${height}`);
1048
+ if (serializedDelta) {
1049
+ const delta = JSON.parse(serializedDelta);
1050
+ this.applyDelta(delta);
1051
+ }
1052
+ }
1053
+ }
1054
+
1055
+ // Function to record a delta
1056
+ static async recordTallyMapDelta(address, block, propertyId, total, availableChange, reservedChange, marginChange, vestingChange, channelChange, type,txid){
1057
+ const newUuid = uuid.v4();
1058
+ const db = await dbInstance.getDatabase('tallyMapDelta');
1059
+ let deltaKey = `${address}-${propertyId}-${newUuid}`;
1060
+ deltaKey+='-'+block
1061
+ const tally = TallyMap.getTally(address, propertyId)
1062
+ if(!txid){txid=''}
1063
+ total = tally.available+tally.reserved+tally.margin+tally.channel+tally.vesting
1064
+ const delta = { address, block, property: propertyId, total: total, avail: availableChange, res: reservedChange, mar: marginChange, vest: vestingChange, channel: channelChange, type, tx: txid };
1065
+
1066
+ console.log('saving delta ' + JSON.stringify(delta));
1067
+
1068
+ try {
1069
+ // Try to find an existing document based on the key
1070
+ const existingDocument = await db.findOneAsync({ _id: deltaKey });
1071
+
1072
+ if (existingDocument) {
1073
+ // If the document exists, update it
1074
+ await db.updateAsync({ _id: deltaKey }, { $set: { data: delta } });
1075
+ } else {
1076
+ // If the document doesn't exist, insert a new one
1077
+ await db.insertAsync({ _id: deltaKey, data: delta });
1078
+ }
1079
+ TallyMap.setModFlag(true)
1080
+
1081
+ return; // Return success or handle as needed
1082
+ } catch (error) {
1083
+ console.error('Error saving delta:', error);
1084
+ throw error; // Rethrow the error or handle as needed
1085
+ }
1086
+ }
1087
+
1088
+ static async didReceiveClearingProfitThisBlock(address, blockHeight) {
1089
+ const db = await dbInstance.getDatabase('tallyMapDelta');
1090
+
1091
+ return new Promise((resolve, reject) => {
1092
+ db.find(
1093
+ {
1094
+ "data.address": address,
1095
+ "data.block": blockHeight
1096
+ },
1097
+ (err, docs) => {
1098
+ if (err) return reject(err);
1099
+
1100
+ const hasClearing = docs.some(doc =>
1101
+ (doc.data.type === 'clearing')
1102
+ );
1103
+
1104
+ resolve(hasClearing);
1105
+ }
1106
+ );
1107
+ });
1108
+ }
1109
+
1110
+ // Function to apply a delta to the TallyMap
1111
+ applyDeltaToTallyMap(delta) {
1112
+ const { address, propertyId, amountChange } = delta;
1113
+ // Logic to apply the change to TallyMap
1114
+ TallyMap.updateBalance(address, propertyId, amountChange);
1115
+ }
1116
+
1117
+ async saveDeltaTodbInstance(blockHeight, delta) {
1118
+ const serializedDelta = JSON.stringify(delta);
1119
+ await dbInstance.getDatabase('tallyMap').insert(`tallyMapDelta-${blockHeight}`, serializedDelta);
1120
+ }
1121
+
1122
+ // Function to save the aggregated block delta
1123
+ async saveBlockDelta(blockHeight, blockDelta) {
1124
+ const deltaKey = `blockDelta-${blockHeight}`;
1125
+ await dbInstance.getDatabase('tallyMap').insert(deltaKey, JSON.stringify(blockDelta));
1126
+ }
1127
+
1128
+ // Function to load all deltas for a block
1129
+ async loadDeltasForBlock(blockHeight) {
1130
+ // Load and parse all deltas from the database for the given block height
1131
+ }
1132
+
1133
+ totalTokens(propertyId) {
1134
+ let total = 0;
1135
+ for (const addressObj of this.addresses.values()) {
1136
+ if (addressObj[propertyId]) {
1137
+ total += addressObj[propertyId].available + addressObj[propertyId].reserved;
1138
+ }
1139
+ }
1140
+ return total;
1141
+ }
1142
+ // Get the tally for a specific address and property
1143
+ static async getTally(address, propertyId) {
1144
+ await TallyMap.loadFromDB(); // Ensure instance is loaded
1145
+ if (!TallyMap.addresses.has(address)) {
1146
+ console.log("can't find address in tallyMap")
1147
+ return 0;
1148
+ }
1149
+ const addressObj = TallyMap.addresses.get(address);
1150
+ console.log('inside getTally '+propertyId+' '+JSON.stringify(addressObj))
1151
+ if (!addressObj[propertyId]) {
1152
+ console.log("can't find property in address "+address+propertyId+ ' '+JSON.stringify(addressObj) )
1153
+ return 0;
1154
+ }
1155
+
1156
+ const returnObj = {amount: addressObj[propertyId].amount,
1157
+ available: addressObj[propertyId].available,
1158
+ reserved: addressObj[propertyId].reserved,
1159
+ margin: addressObj[propertyId].margin,
1160
+ vesting:addressObj[propertyId].vesting,
1161
+ channel: addressObj[propertyId].channelBalance}
1162
+
1163
+ console.log('return obj '+address+' '+JSON.stringify(returnObj))
1164
+
1165
+ return returnObj
1166
+ }
1167
+
1168
+ getAddressBalances(address) {
1169
+ //console.log('ze tally map'+this.addresses)
1170
+ const balances = [];
1171
+ if (this.addresses.has(address)) {
1172
+ const properties = this.addresses.get(address);
1173
+ for (const [propertyId, balanceData] of Object.entries(properties)) {
1174
+ balances.push({
1175
+ propertyId: propertyId,
1176
+ balance: balanceData
1177
+ });
1178
+ }
1179
+ }
1180
+ return balances;
1181
+ }
1182
+
1183
+ /**
1184
+ * Retrieves all addresses that have a balance for a given property.
1185
+ * @param {number} propertyId - The property ID to check balances for.
1186
+ * @return {Array} - An array of addresses that have a balance for the specified property.
1187
+ */
1188
+ static async getAddressesWithBalanceForProperty(propertyId) {
1189
+ const addressesWithBalances = [];
1190
+
1191
+ try {
1192
+ // Get the tallyMap document
1193
+ const tallyMapDoc = await dbInstance.getDatabase('tallyMap').findOneAsync({ _id: 'tallyMap' });
1194
+
1195
+ // Ensure we got the document and the data field exists
1196
+ if (!tallyMapDoc || !tallyMapDoc.data) {
1197
+ console.error('No tallyMap document found or data is missing');
1198
+ return addressesWithBalances;
1199
+ }
1200
+
1201
+ // Parse the stringified data into a usable array
1202
+ const parsedData = JSON.parse(tallyMapDoc.data);
1203
+
1204
+ // Iterate over the parsed data and find addresses with the specified propertyId
1205
+ for (const [address, balances] of parsedData) {
1206
+ if (balances[propertyId]) {
1207
+ const balanceInfo = balances[propertyId];
1208
+ if (balanceInfo.available > 0 || balanceInfo.vesting > 0) {
1209
+ addressesWithBalances.push({
1210
+ address: address,
1211
+ available: balanceInfo.available,
1212
+ reserved: balanceInfo.reserved,
1213
+ margin: balanceInfo.margin,
1214
+ vesting: balanceInfo.vesting,
1215
+ channelBalance: balanceInfo.channelBalance
1216
+ });
1217
+ }
1218
+ }
1219
+ }
1220
+
1221
+ console.log('Found addresses for property', propertyId, addressesWithBalances);
1222
+ } catch (error) {
1223
+ console.error('Error querying addresses with balance for propertyId:', propertyId, error);
1224
+ }
1225
+
1226
+ return addressesWithBalances;
1227
+ }
1228
+
1229
+ static async applyVesting(propertyId, vestingAmount, block) {
1230
+ console.log('insideApply vesting '+vestingAmount)
1231
+ if(vestingAmount<1e-8){return}
1232
+ // Get the list of addresses with balances for the given propertyId
1233
+ const addressesWithBalances = await this.getAddressesWithBalanceForProperty(propertyId);
1234
+ const propertyInfo = await PropertyList.getPropertyData(propertyId)
1235
+ // Retrieve the total number of tokens for the propertyId from the propertyList
1236
+ const totalTokens = propertyInfo.totalInCirculation;
1237
+ vestingAmount = new BigNumber(vestingAmount)
1238
+ // Iterate over each address to apply the vesting amount
1239
+ for (const { address, available, reserved, margin, vesting, channelBalance } of addressesWithBalances) {
1240
+ console.log(JSON.stringify(addressesWithBalances))
1241
+ console.log('inside apply vesting '+address+' '+available+' '+vesting+' '+totalTokens)
1242
+ // Calculate the total balance for this address (amount + reserved)
1243
+ const totalBalanceForAddress = new BigNumber(available);
1244
+
1245
+ // Calculate the percentage this balance represents of the total tokens
1246
+ const percentageOfTotalTokens = totalBalanceForAddress.dividedBy(totalTokens);
1247
+ console.log('percentage '+percentageOfTotalTokens, vestingAmount)
1248
+ // Apply the vesting amount proportionally to this address
1249
+ const vestingShare = vestingAmount.multipliedBy(percentageOfTotalTokens);
1250
+ console.log(vestingAmount)
1251
+ console.log(vestingShare.toNumber()+' '+totalBalanceForAddress+' '+percentageOfTotalTokens)
1252
+ // Depending on propertyId, apply the vesting rules:
1253
+ if (propertyId === 2) {
1254
+ // Move tokens from vesting in propertyId 2 to available in propertyId 1
1255
+ await this.updateBalance(
1256
+ address, 2, 0, 0, 0, vestingShare.negated().toNumber(), 'vestingDebit', block // Debit vesting from propertyId 2
1257
+ );
1258
+ await this.updateBalance(
1259
+ address, 1, vestingShare.toNumber(), 0, 0, 0, 'vestingCredit', block // Credit available in propertyId 1
1260
+ );
1261
+ } else if (propertyId === 3) {
1262
+ // Move tokens from vesting in propertyId 3 to available in propertyId 4
1263
+ await this.updateBalance(
1264
+ address, 3, 0, 0, 0, vestingShare.negated().toNumber(), 'vestingDebit', block // Debit vesting from propertyId 3
1265
+ );
1266
+ await this.updateBalance(
1267
+ address, 4, vestingShare.toNumber(), 0, 0, 0, 'vestingCredit', block // Credit available in propertyId 4
1268
+ );
1269
+ }
1270
+ }
1271
+ return
1272
+ }
1273
+ }
1274
+
1275
+ module.exports = TallyMap;