@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,1163 @@
1
+ const dbInstance = require('./db.js');
2
+ const TallyMap = require('./tally.js')
3
+ const BigNumber = require('bignumber.js')
4
+ const TxUtils = require('./txUtils.js')
5
+ const { v4: uuidv4 } = require('uuid');
6
+
7
+ class Channels {
8
+ // Initialize channelsRegistry as a static property
9
+ static channelsRegistry = new Map();
10
+ static pendingWithdrawals = []; // Array to store pending withdrawal objects
11
+
12
+
13
+ static async addToRegistry(channelAddress, commiterA, commiterB) {
14
+ // Add logic to register a new trade channel
15
+ this.channelsRegistry.set(channelAddress, { commiterA, commiterB });
16
+ await this.saveChannelsRegistry();
17
+ }
18
+
19
+ static async removeFromRegistry(channelAddress) {
20
+ // Add logic to remove a trade channel
21
+ this.channelsRegistry.delete(channelAddress);
22
+ await this.saveChannelsRegistry();
23
+ }
24
+
25
+ // Function to set a channel object and save it in the registry
26
+ static async setChannel(channelId, channelData) {
27
+ // Set the channel object in the registry
28
+ this.channelsRegistry.set(channelId, channelData);
29
+
30
+ // Save the updated channels registry to the database
31
+ await this.saveChannelsRegistry();
32
+ }
33
+
34
+ static async saveChannelsRegistry() {
35
+ // Persist the channels registry to NeDB
36
+ const channelsDB = await dbInstance.getDatabase('channels');
37
+ const entries = [...this.channelsRegistry.entries()].map(([channelId, channelData]) => {
38
+ return {
39
+ _id: `${channelId}`, // Unique identifier for each channel
40
+ data: channelData
41
+ };
42
+ });
43
+
44
+ for (const entry of entries) {
45
+ await channelsDB.updateAsync(
46
+ { _id: entry._id },
47
+ { $set: { data: entry.data } },
48
+ { upsert: true }
49
+ );
50
+ }
51
+ }
52
+
53
+ // channels.js (core)
54
+
55
+ /**
56
+ * Return all channel balances for a given commit address, optionally filtered by propertyId.
57
+ * Reads from NeDB collection "channels" where docs look like:
58
+ * { _id: <channelId>, data: { participants:{A,B}, channel, commits:[...], A:{pid:amt}, B:{pid:amt}, ... } }
59
+ *
60
+ * @param {object} deps
61
+ * @param {string} address // commit/trading address to inspect
62
+ * @param {number|undefined} propertyId // optional property filter
63
+ * @returns {Promise<Array<{channel:string, side:'A'|'B', propertyId:number, amount:number, lastCommitmentTime?:number}>>}
64
+ */
65
+ static async getChannelBalancesForAddress(address, propertyId) {
66
+ if (!address) throw new Error('address required');
67
+
68
+ const channelsDB = await dbInstance.getDatabase('channels');
69
+ const addr = address.trim();
70
+ const addrLC = addr.toLowerCase();
71
+
72
+ // Allow propertyId to be optional
73
+ const pidFilter =
74
+ propertyId === undefined || propertyId === null || propertyId === ''
75
+ ? null
76
+ : Number(propertyId);
77
+
78
+ if (pidFilter !== null && Number.isNaN(pidFilter)) {
79
+ throw new Error('propertyId must be a number');
80
+ }
81
+
82
+ // Find channels where we participate or have committed before
83
+ const entries = await channelsDB.findAsync({
84
+ $or: [
85
+ { 'data.participants.A': addr },
86
+ { 'data.participants.B': addr },
87
+ { 'data.participants.A': addrLC },
88
+ { 'data.participants.B': addrLC },
89
+ { 'data.commits': { $elemMatch: { senderAddress: addr } } },
90
+ { 'data.commits': { $elemMatch: { senderAddress: addrLC } } }
91
+ ]
92
+ });
93
+
94
+ const rows = [];
95
+
96
+ for (const doc of entries || []) {
97
+ const data = doc.data || {};
98
+ const chanId = data.channel || doc._id;
99
+
100
+ // Determine our column (A/B)
101
+ const aAddr = String(data?.participants?.A || '').toLowerCase();
102
+ const bAddr = String(data?.participants?.B || '').toLowerCase();
103
+
104
+ let side = null;
105
+ if (aAddr && aAddr === addrLC) side = 'A';
106
+ else if (bAddr && bAddr === addrLC) side = 'B';
107
+ else {
108
+ // Fallback: infer from our latest commit or lastUsedColumn
109
+ const lastMine = [...(data.commits || [])]
110
+ .reverse()
111
+ .find(c => String(c.senderAddress || '').toLowerCase() === addrLC);
112
+ if (lastMine?.columnAssigned === 'A' || lastMine?.columnAssigned === 'B') {
113
+ side = lastMine.columnAssigned;
114
+ } else if (data.lastUsedColumn === 'A' || data.lastUsedColumn === 'B') {
115
+ side = data.lastUsedColumn;
116
+ }
117
+ }
118
+ if (!side) continue;
119
+
120
+ // Balances for our column (e.g. data.A = { "5": 0.1, ... })
121
+ const sideBalances = data[side] || {};
122
+
123
+ // Helper to push one row with enriched columns for UI
124
+ const pushRow = (pid, amt) => {
125
+ const nPid = Number(pid);
126
+ const nAmt = Number(amt);
127
+ if (!isFinite(nAmt) || nAmt <= 0) return;
128
+ if (pidFilter !== null && nPid !== pidFilter) return;
129
+
130
+ rows.push({
131
+ channel: chanId, // channel id/address
132
+ column: side, // 'A' | 'B' (explicit UI label)
133
+ side, // keep original key for backwards compat
134
+ propertyId: nPid,
135
+ amount: nAmt,
136
+ // Useful for UI actions (withdraw/transfer) without more queries:
137
+ participants: {
138
+ A: data?.participants?.A || '',
139
+ B: data?.participants?.B || ''
140
+ },
141
+ counterparty: side === 'A'
142
+ ? (data?.participants?.B || '')
143
+ : (data?.participants?.A || ''),
144
+ lastCommitmentBlock: data?.lastCommitmentTime ?? null,
145
+ commitCount: Array.isArray(data?.commits) ? data.commits.length : 0,
146
+ // did *we* ever mark payEnabled=true in a commit? (handy hint for UI)
147
+ payEnabled: !!(Array.isArray(data?.commits) && data.commits.some(
148
+ c => String(c.senderAddress || '').toLowerCase() === addrLC && c.payEnabled === true
149
+ )),
150
+ // If you store pubkeys/redeemScript/scriptPubKey, pass them along for immediate actions:
151
+ channelPubkeys: data?.channelPubkeys || null,
152
+ redeemScript: data?.redeemScript || null,
153
+ scriptPubKey: data?.scriptPubKey || null
154
+ });
155
+ };
156
+
157
+ if (pidFilter !== null) {
158
+ pushRow(String(pidFilter), sideBalances[String(pidFilter)] || 0);
159
+ } else {
160
+ for (const [pid, val] of Object.entries(sideBalances)) {
161
+ pushRow(pid, val);
162
+ }
163
+ }
164
+ }
165
+
166
+ // Largest first looks nice in a table
167
+ rows.sort((a, b) => b.amount - a.amount);
168
+ return rows;
169
+ }
170
+
171
+
172
+ /** Optional: aggregate sum across channels (for UI footers) */
173
+ static sumBalance(rows) {
174
+ return rows.reduce((acc, r) => acc + (r.amount || 0), 0);
175
+ }
176
+
177
+ /** Optional: group by channel -> { [channel]: { [propertyId]: amount } } */
178
+ static toChannelPropMap(rows) {
179
+ const m = {};
180
+ for (const r of rows) {
181
+ if (!m[r.channel]) m[r.channel] = {};
182
+ m[r.channel][r.propertyId] = (m[r.channel][r.propertyId] || 0) + r.amount;
183
+ }
184
+ return m;
185
+ }
186
+
187
+ static async loadChannelsRegistry() {
188
+ // Load the channels registry from NeDB
189
+ const channelsDB = await dbInstance.getDatabase('channels');
190
+ try {
191
+ const entries = await channelsDB.findAsync({});
192
+ //console.log('loading channel DB '+JSON.stringify(entries))
193
+ this.channelsRegistry = new Map(entries.map(entry => [entry._id, entry.data]));
194
+ //console.log(JSON.stringify(Array.from(this.channelsRegistry.entries())));
195
+ return
196
+ } catch (error) {
197
+ if (error.message.includes('does not exist')) {
198
+ // If the collection does not exist, initialize an empty registry
199
+ this.channelsRegistry = new Map();
200
+ } else {
201
+ throw error;
202
+ }
203
+ }
204
+ }
205
+
206
+ // Function to save pending withdrawal object to the database
207
+ static async savePendingWithdrawalToDB(withdrawalObj) {
208
+ const withdrawalKey = `withdrawal-${withdrawalObj.blockHeight}-${withdrawalObj.senderAddress}`;
209
+ const withdrawalDB = await dbInstance.getDatabase('withdrawQueue');
210
+ await withdrawalDB.updateAsync(
211
+ { _id: withdrawalKey },
212
+ { $set: { data: withdrawalObj } },
213
+ { upsert: true }
214
+ );
215
+ }
216
+
217
+ // Function to load pending withdrawals from the database
218
+ static async loadPendingWithdrawalsFromDB() {
219
+ const withdrawalDB = await dbInstance.getDatabase('withdrawQueue');
220
+ const entries = await withdrawalDB.findAsync({ _id: { $regex: /^withdrawal-/ } });
221
+ return entries.map(entry => entry.data);
222
+ }
223
+
224
+ static async removePendingWithdrawalFromDB(withdrawalObj) {
225
+ const withdrawalKey = `withdrawal-${withdrawalObj.blockHeight}-${withdrawalObj.senderAddress}`;
226
+ const withdrawalDB = await dbInstance.getDatabase('withdrawQueue');
227
+
228
+ // Remove the withdrawal from the database
229
+ await withdrawalDB.removeAsync({ _id: withdrawalKey });
230
+ }
231
+
232
+ /**
233
+ * Record a channel delta event in the `channelDelta` database.
234
+ * @param {string} channelId - The channel id/address.
235
+ * @param {string} column - 'A' or 'B'.
236
+ * @param {number} propertyId - e.g. 1 for TL.
237
+ * @param {number} amount - Signed amount (+credit, -debit).
238
+ * @param {string} type - Type of event (e.g. 'debitInitMargin').
239
+ * @param {string} participant - Address of the actor (optional).
240
+ * @param {number} block - Block number.
241
+ * @param {string} txid - Txid (optional).
242
+ * @param {string} memo - Memo (optional).
243
+ */
244
+ static async recordChannelDelta({
245
+ channelId,
246
+ column,
247
+ propertyId,
248
+ amount,
249
+ type,
250
+ participant = '',
251
+ block = 0,
252
+ txid = '',
253
+ memo = ''
254
+ }) {
255
+ const newUuid = uuidv4();
256
+ const db = await dbInstance.getDatabase('channelDelta');
257
+ const deltaKey = `${channelId}-${propertyId}-${column}-${block}-${newUuid}`;
258
+ const delta = {
259
+ channelId,
260
+ column,
261
+ propertyId,
262
+ amount,
263
+ type,
264
+ participant,
265
+ block,
266
+ txid,
267
+ memo,
268
+ timestamp: Date.now()
269
+ };
270
+ console.log('[CHANNEL DELTA]', JSON.stringify(delta));
271
+
272
+ try {
273
+ await db.insertAsync({ _id: deltaKey, data: delta });
274
+ } catch (error) {
275
+ console.error('Error saving channelDelta:', error);
276
+ throw error;
277
+ }
278
+ }
279
+
280
+ // Record a token trade with specific key identifiers
281
+ static async recordTokenTrade(trade, blockHeight, txid) {
282
+ const tradeRecordKey = `token-${trade.offeredPropertyId}-${trade.desiredPropertyId}`;
283
+ const tradeRecord = {
284
+ key: tradeRecordKey,
285
+ type: 'token',
286
+ trade,
287
+ blockHeight,
288
+ txid
289
+ };
290
+ await this.saveTrade(tradeRecord);
291
+ }
292
+
293
+ // Record a contract trade with specific key identifiers
294
+ static async recordContractTrade(trade, blockHeight, txid) {
295
+ const tradeRecordKey = `contract-${trade.contractId}`;
296
+ const tradeRecord = {
297
+ key: tradeRecordKey,
298
+ type: 'contract',
299
+ trade,
300
+ blockHeight,
301
+ txid
302
+ };
303
+ await this.saveTrade(tradeRecord);
304
+ }
305
+
306
+ static async saveTrade(tradeRecord) {
307
+ const tradeDB = await dbInstance.getDatabase('tradeHistory');
308
+
309
+ // Use the key provided in the trade record for storage
310
+ const tradeId = `${tradeRecord.key}-${tradeRecord.txid}-${tradeRecord.blockHeight}`;
311
+
312
+ // Construct the document to be saved
313
+ const tradeDoc = {
314
+ _id: tradeId,
315
+ ...tradeRecord
316
+ };
317
+
318
+ // Save or update the trade record in the database
319
+ try {
320
+ await tradeDB.updateAsync(
321
+ { _id: tradeId },
322
+ tradeDoc,
323
+ { upsert: true }
324
+ );
325
+ console.log(`Trade record saved successfully: ${tradeId}`);
326
+ } catch (error) {
327
+ console.error(`Error saving trade record: ${tradeId}`, error);
328
+ throw error; // Rethrow the error for handling upstream
329
+ }
330
+ }
331
+
332
+
333
+ static async getChannel(channelId) {
334
+ // Ensure the channels registry is loaded
335
+ let channel = this.channelsRegistry.get(channelId)
336
+ //console.log('inside getChannel '+channelId+' '+JSON.stringify(Array.from(this.channelsRegistry.entries())));
337
+ console.log(Boolean(!channel),Boolean(channel==undefined),JSON.stringify(channel))
338
+ if(!channel||channel==undefined||channel==null){
339
+ await this.loadChannelsRegistry();
340
+ channel = this.channelsRegistry.get(channelId)
341
+ console.log('in getChannel 2nd hit '+JSON.stringify(channel));
342
+ if(!channel){
343
+ channel=null
344
+ }
345
+ }
346
+
347
+ return channel
348
+ }
349
+
350
+ static async isValidChannel(channelAddress) {
351
+ // Load the channel from the registry if not already loaded
352
+ let channel = this.channelsRegistry.get(channelAddress);
353
+ if (!channel) {
354
+ await this.loadChannelsRegistry();
355
+ channel = this.channelsRegistry.get(channelAddress);
356
+ }
357
+
358
+ // Check if the channel exists
359
+ if (!channel) {
360
+ console.log(`Channel ${channelAddress} does not exist`);
361
+ return false;
362
+ }
363
+ }
364
+
365
+ static async getCommitAddresses(channelAddress) {
366
+ let channel = this.channelsRegistry.get(channelAddress);
367
+ //console.log('inside getCommitAddresses '+JSON.stringify(channel)+' '+channelAddress)
368
+ if(!channel||channel==undefined||channel==null){
369
+ console.log('channel not found, loading from db')
370
+ await Channels.loadChannelsRegistry()
371
+ channel = this.channelsRegistry.get(channelAddress);
372
+ //console.log('checking channel obj again '+JSON.stringify(channel))
373
+ }
374
+ if (channel && channel.participants) {
375
+ const participants = channel.participants;
376
+ //console.log('inside getCommitAddresses '+participants.A+ ' '+ participants.B)
377
+ return {
378
+ commitAddressA: participants.A,
379
+ commitAddressB: participants.B
380
+ };
381
+ } else {
382
+ return {commitAddressA: null,commitAddressB: null}; // Return null if the channel or participants data is not found
383
+ }
384
+ }
385
+
386
+ static async addCommitment(channelId, commitment) {
387
+ await this.db.updateAsync(
388
+ { channelId: channelId },
389
+ { $push: { commitments: commitment } },
390
+ { upsert: true }
391
+ );
392
+ }
393
+
394
+ static async getCommitments(channelId) {
395
+ const channel = await this.db.findOneAsync({ channelId: channelId });
396
+ return channel ? channel.commitments : [];
397
+ }
398
+
399
+ static compareCharacters(charA, charB) {
400
+ if (charA === charB) {
401
+ return 0; // Characters are equal
402
+ } else {
403
+ const isNumA = !isNaN(charA);
404
+ const isNumB = !isNaN(charB);
405
+
406
+ if (isNumA && !isNumB) {
407
+ return -1; // Numbers come first
408
+ } else if (!isNumA && isNumB) {
409
+ return 1;
410
+ } else {
411
+ return charA < charB ? -1 : 1; // Compare ASCII values
412
+ }
413
+ }
414
+ }
415
+
416
+ /**
417
+ * Record a participant assignment or change in the channelDelta ledger.
418
+ *
419
+ * @param {string} channelId
420
+ * @param {'A'|'B'} column
421
+ * @param {string} newParticipant - The address now assigned to the column
422
+ * @param {number} block - Block height of the change
423
+ * @param {string} prevParticipant - (Optional) The old participant
424
+ * @param {string} memo - (Optional) Additional info (e.g. 'assigned on commit')
425
+ */
426
+ static async recordParticipantChange(channelId, column, newParticipant, block, prevParticipant = '', memo = '') {
427
+ await Channels.recordChannelDelta({
428
+ channelId,
429
+ column,
430
+ propertyId: null, // Not a token move, so leave propertyId empty
431
+ amount: 0, // No amount (not a balance change)
432
+ type: 'participantChange',
433
+ participant: newParticipant,
434
+ block,
435
+ txid: '',
436
+ memo: memo || `Set participant for ${column} to ${newParticipant}${prevParticipant ? ' (prev: ' + prevParticipant + ')' : ''}`
437
+ });
438
+ }
439
+
440
+
441
+ /**
442
+ * Debits initial margin from the channel's correct column (A or B) for a property.
443
+ * Updates the registry and saves the channel state.
444
+ *
445
+ * @param {string} channelId - The channel ID.
446
+ * @param {string} participantAddr - The participant address (debtor).
447
+ * @param {number} propertyId - The property to debit (e.g., 1 for TL).
448
+ * @param {number|string|BigNumber} amount - Amount to debit (positive).
449
+ * @param {number} block - Block number for logging/audit.
450
+ * @param {string} [type='debitChannelContractTradeInitMargin'] - For logging/audit.
451
+ */
452
+ static async debitInitMarginFromChannel(channelId, participantAddr, propertyId, amount, block, type = 'debitChannelContractTradeInitMargin', txid){
453
+ const BigNumber = require('bignumber.js');
454
+ // 1. Load channel from memory or DB if needed
455
+ let channel = await this.getChannel(channelId);
456
+ if (!channel || !channel.participants) {
457
+ throw new Error(`Channel ${channelId} not found or malformed`);
458
+ }
459
+ // 2. Decide column: 'A' or 'B'
460
+ let column = null;
461
+ if (channel.participants.A === participantAddr) {
462
+ column = 'A';
463
+ } else if (channel.participants.B === participantAddr) {
464
+ column = 'B';
465
+ }
466
+ // 3. Ensure balances exist (initialize to 0 if undefined)
467
+ if (!channel[column]) channel[column] = {};
468
+ if (typeof channel[column][propertyId] !== "number") channel[column][propertyId] = 0;
469
+ // 4. Check balance
470
+ let balBN = new BigNumber(channel[column][propertyId]);
471
+ let amtBN = new BigNumber(amount);
472
+ if (balBN.lt(amtBN)) {
473
+ throw new Error(`Insufficient channel balance: ${balBN} < ${amtBN} in ${channelId} ${column} ${propertyId}`);
474
+ }
475
+ // 5. Debit the column (8 dp, no underflow)
476
+ channel[column][propertyId] = balBN.minus(amtBN).decimalPlaces(8).toNumber();
477
+ // 6. Save back to registry/DB
478
+ await Channels.setChannel(channelId, channel);
479
+ await Channels.recordChannelDelta({
480
+ channelId,
481
+ column,
482
+ propertyId,
483
+ amount: -amtBN.decimalPlaces(8).toNumber(), // Always negative for debits
484
+ type,
485
+ participant: participantAddr,
486
+ block,
487
+ txid,
488
+ memo: '' // or whatever memo string you want
489
+ });
490
+
491
+ // 7. Optional: log to audit trail
492
+ console.log(`[CHANNEL][${type}] Debited ${amtBN} from ${column}.${propertyId} of channel ${channelId} (addr: ${participantAddr}) at block ${block}`);
493
+
494
+ return true;
495
+ }
496
+
497
+ static async assignColumnBasedOnAddress(channel, newCommitAddress, cpAddress,block){
498
+ const column = Channels.assignColumnBasedOnLastCharacter(newCommitAddress);
499
+ // 1) If the channel isn't initialized yet, fall back to last-character rule
500
+ if (!channel.participants?.A&&!channel?.participants.B) {
501
+ channel.participants[column]=newCommitAddress
502
+ await Channels.recordParticipantChange(
503
+ channel.channel, // channelId
504
+ column, // 'A' or 'B'
505
+ newCommitAddress, // new participant address
506
+ block, // current block
507
+ '', // prevParticipant (none yet)
508
+ 'Initial assignment'
509
+ );
510
+ return channel
511
+ }
512
+
513
+ // 2) If this address already committed, preserve its column
514
+ if (channel.participants.A === newCommitAddress||channel.participants.B === newCommitAddress){
515
+ return channel
516
+ }
517
+
518
+ // 3) the cp address is assigned and there's no conflict, includes crowded channel
519
+ if (channel.participants[column] !== cpAddress && !channel.participants[column]) {
520
+ const prev = channel.participants[column] || '';
521
+ channel.participants[column] = newCommitAddress;
522
+ await Channels.recordParticipantChange(
523
+ channel.channel,
524
+ column,
525
+ newCommitAddress,
526
+ block,
527
+ prev,
528
+ 'Assigned via open slot (no conflict)'
529
+ );
530
+ return channel;
531
+ }
532
+
533
+ // 4. Crowded: default spot is already filled, so we need tie-break and bump
534
+ if(channel.participants[column]==cpAddress||(channel.participants[column] && channel.participants[column] !== newCommitAddress)){
535
+ const tiebreak = Channels.tieBreakerByBackChar(newCommitAddress, cpAddress, column);
536
+ // winner must land on its computed winnerColumn; loser on the other
537
+ const desiredA = (tiebreak.winnerColumn === 'A') ? tiebreak.winner : tiebreak.loser;
538
+ const desiredB = (tiebreak.winnerColumn === 'B') ? tiebreak.winner : tiebreak.loser;
539
+ // If for any reason we can't identify both, keep previous other side (never blank)
540
+ const prevA = channel.participants.A || '';
541
+ const prevB = channel.participants.B || '';
542
+ const forceA = desiredA || prevA;
543
+ const forceB = desiredB || ((prevA && prevA !== forceA) ? prevA : prevB);
544
+ return Channels.bumpColumnAssignment(channel, forceA, forceB, block);
545
+ }
546
+
547
+ console.error(
548
+ `[Channel Assign] Unexpected case in channel assignment for channel ${channel.channel}.\n` +
549
+ `Participants: A=${channel.participants.A}, B=${channel.participants.B}\n` +
550
+ `Incoming commit: ${newCommitAddress}\n` +
551
+ `This should be handled earlier in the logic!`
552
+ );
553
+ }
554
+
555
+ static resolveColumn(channel, addr) {
556
+ const A = channel?.participants?.A ?? '';
557
+ const B = channel?.participants?.B ?? '';
558
+ if (!addr) return null;
559
+ if (A === addr) return 'A';
560
+ if (B === addr) return 'B';
561
+ // single-counterparty fallback
562
+ if (A && !B && A === addr) return 'A';
563
+ if (B && !A && B === addr) return 'B';
564
+ return null;
565
+ }
566
+
567
+ static assignColumnBasedOnLastCharacter(address, last = 1) {
568
+ if (typeof address !== 'string' || address.length === 0) {
569
+ console.warn('[assignColumnBasedOnLastCharacter] invalid address, defaulting to A');
570
+ return 'A'; // deterministic fallback
571
+ }
572
+
573
+ const idx = address.length - last;
574
+ if (idx < 0) {
575
+ console.warn('[assignColumnBasedOnLastCharacter] address too short, defaulting to A');
576
+ return 'A';
577
+ }
578
+
579
+ const lastChar = address[idx];
580
+ console.log('last char in assign column based on last character', lastChar);
581
+
582
+ const oddCharacters = [
583
+ 'A','C','E','G','I','K','M','O','Q','S','U','W','Y',
584
+ '1','3','5','7','9'
585
+ ];
586
+
587
+ const isOdd = oddCharacters.includes(lastChar.toUpperCase());
588
+ return isOdd ? 'A' : 'B';
589
+ }
590
+
591
+
592
+ /**
593
+ * Tie-breaker to assign addresses to columns based on their last N characters' parity.
594
+ * Returns: { winner: address, loser: address, winnerColumn: 'A' | 'B', loserColumn: 'A' | 'B' }
595
+ */
596
+ static tieBreakerByBackChar(addr1, addr2, column, assignColFunc = Channels.assignColumnBasedOnLastCharacter) {
597
+ const len = Math.min(addr1.length, addr2.length);
598
+ for (let n = 1; n <= len; n++) {
599
+ const col1 = assignColFunc(addr1, n);
600
+ const col2 = assignColFunc(addr2, n);
601
+
602
+ console.log(`[TieBreak] Char ${n}: ${addr1}[${col1}] vs ${addr2}[${col2}], competing for ${column}`);
603
+
604
+ if (col1 === column && col2 !== column) {
605
+ console.log(`[TieBreak WINNER] ${addr1} wins column ${column} at char -${n}`);
606
+ return {
607
+ winner: addr1,
608
+ loser: addr2,
609
+ winnerColumn: column,
610
+ loserColumn: column === 'A' ? 'B' : 'A'
611
+ };
612
+ }
613
+ if (col2 === column && col1 !== column) {
614
+ console.log(`[TieBreak WINNER] ${addr2} wins column ${column} at char -${n}`);
615
+ return {
616
+ winner: addr2,
617
+ loser: addr1,
618
+ winnerColumn: column,
619
+ loserColumn: column === 'A' ? 'B' : 'A'
620
+ };
621
+ }
622
+ }
623
+ // If no decisive winner, default addr1 to the requested column
624
+ console.log(`[TieBreak DEFAULT] ${addr1} assigned ${column} by default (no unique winner)`);
625
+ return {
626
+ winner: addr1,
627
+ loser: addr2,
628
+ winnerColumn: column,
629
+ loserColumn: column === 'A' ? 'B' : 'A'
630
+ };
631
+ }
632
+
633
+ static predictColumnForAddress(channel, newCommitAddress, cpAddress) {
634
+ // 1) If channel is empty, fallback to last-character rule
635
+ if (!channel.participants?.A && !channel.participants?.B) {
636
+ return Channels.assignColumnBasedOnLastCharacter(newCommitAddress);
637
+ }
638
+
639
+ // 2) If already present, preserve
640
+ if (channel.participants.A === newCommitAddress) return 'A';
641
+ if (channel.participants.B === newCommitAddress) return 'B';
642
+
643
+ // 3) If the computed column is free (not cpAddress), assign
644
+ const column = Channels.assignColumnBasedOnLastCharacter(newCommitAddress);
645
+ if (channel.participants[column] !== cpAddress && !channel.participants[column]) {
646
+ return column;
647
+ }
648
+
649
+ // 4) Crowded, tie-break logic
650
+ if (
651
+ channel.participants[column] === cpAddress ||
652
+ (channel.participants[column] && channel.participants[column] !== newCommitAddress)
653
+ ) {
654
+ const tiebreak = Channels.tieBreakerByBackChar(newCommitAddress, cpAddress, column);
655
+ return tiebreak.winnerColumn;
656
+ }
657
+
658
+ // fallback: no assignment possible (shouldn't hit)
659
+ return null;
660
+ }
661
+
662
+ static async bumpColumnAssignment(channel, forceAis, forceBis, block = 0) {
663
+ if (!channel) throw new Error('Channel object is required for bumpColumnAssignment');
664
+ if (!channel.participants) channel.participants = { A: '', B: '' };
665
+ channel.A = channel.A || {};
666
+ channel.B = channel.B || {};
667
+
668
+ const prevA = channel.participants.A;
669
+ const prevB = channel.participants.B;
670
+
671
+ // Normalize inputs so we never blank a side unintentionally
672
+ const hasForceA = forceAis !== undefined && forceAis !== null && forceAis !== '';
673
+ const hasForceB = forceBis !== undefined && forceBis !== null && forceBis !== '';
674
+ let nextA = hasForceA ? forceAis : (prevA || '');
675
+ let nextB = hasForceB ? forceBis : (prevB || '');
676
+
677
+ // Avoid A/B collapsing to the same address when we can preserve distinctness
678
+ if (nextA && nextA === nextB) {
679
+ if (prevA && prevA !== nextA) nextB = prevA;
680
+ else if (prevB && prevB !== nextA) nextB = prevB;
681
+ }
682
+
683
+ console.log(`[Bump] Current: A=${prevA}, B=${prevB} | Forcing(norm): A=${nextA}, B=${nextB}`);
684
+
685
+ // 1) No change needed
686
+ if (prevA === nextA && prevB === nextB) {
687
+ console.log('[Bump] No swap needed.');
688
+ return channel;
689
+ }
690
+
691
+ // 2) True swap: desired is exactly the other's current -> swap participants & balances
692
+ if (prevA === nextB && prevB === nextA) {
693
+ console.log(`[Bump] Swapping both participants and balances: A=${prevA}, B=${prevB}`);
694
+ [channel.participants.A, channel.participants.B] = [channel.participants.B, channel.participants.A];
695
+ [channel.A, channel.B] = [channel.B, channel.A];
696
+ }
697
+ // 3) One-sided match implies an A<->B swap too (keeps balances aligned with owners)
698
+ else if (prevA === nextB || prevB === nextA) {
699
+ console.log(`[Bump] Swapping participants & balances (one-sided match): A=${prevA}, B=${prevB}`);
700
+ [channel.participants.A, channel.participants.B] = [channel.participants.B, channel.participants.A];
701
+ [channel.A, channel.B] = [channel.B, channel.A];
702
+ // After swap, if still not at target, set explicitly without touching balances again
703
+ if (channel.participants.A !== nextA || channel.participants.B !== nextB) {
704
+ console.log(`[Bump] Aligning participants post-swap to target: A=${nextA}, B=${nextB}`);
705
+ channel.participants.A = nextA || channel.participants.A;
706
+ channel.participants.B = nextB || channel.participants.B;
707
+ }
708
+ }
709
+ // 4) Overwrite: assign both sides explicitly, keep balances on their current sides
710
+ else {
711
+ console.log(`[Bump] Overwriting participants: A=${nextA}, B=${nextB}`);
712
+ channel.participants.A = nextA;
713
+ channel.participants.B = nextB;
714
+ // (Optionally) reset balances for a brand-new pairing:
715
+ // channel.A = {};
716
+ // channel.B = {};
717
+ }
718
+
719
+ // Emit participantChange deltas if changed
720
+ if (channel.participants.A !== prevA) {
721
+ await this.recordParticipantChange(
722
+ channel.channel, 'A', channel.participants.A, block, prevA, 'Rotated (bumpColumnAssignment)'
723
+ );
724
+ }
725
+ if (channel.participants.B !== prevB) {
726
+ await this.recordParticipantChange(
727
+ channel.channel, 'B', channel.participants.B, block, prevB, 'Rotated (bumpColumnAssignment)'
728
+ );
729
+ }
730
+
731
+ console.log(`[Bump] Result: A=${channel.participants.A}, B=${channel.participants.B}`);
732
+ return channel;
733
+ }
734
+
735
+
736
+ // New function to process commitments and assign columns
737
+ static async processChannelCommits(tradeChannelManager, channelAddress) {
738
+ // Check if both parties have committed
739
+ if (Channels.areBothPartiesCommitted(channelAddress)) {
740
+ // Assign columns based on predefined logic
741
+ const columnAssignments = Channels.assignColumns(channelAddress);
742
+ Channels.updateChannelWithColumnAssignments(channelAddress, columnAssignments);
743
+ //console.log(`Columns assigned for channel ${channelAddress}`);
744
+ }
745
+ }
746
+
747
+ // This should be a static method of Channels, adjust class context as needed
748
+ // Returns: { channel, valid, reason }
749
+ static async handleChannelPubkey(channel, column, senderAddress, commitTxid) {
750
+ let valid = true;
751
+ let reason = '';
752
+
753
+ //try {
754
+ const tx = await TxUtils.getRawTransaction(commitTxid);
755
+ const vin = tx.vin[0]; // Always use first input
756
+
757
+ console.log('vin '+JSON.stringify(vin)+' '+commitTxid)
758
+ const scriptType = TxUtils.getAddressTypeUniversal(senderAddress);
759
+ console.log(scriptType)
760
+ const pubkey = await TxUtils.extractPubkeyByType(vin, scriptType) || [];
761
+ console.log('TxUtils pubkeys'+JSON.stringify(pubkey))
762
+ if (pubkey==null) return new Error('No pubkey found in commit tx');
763
+
764
+ // Store/overwrite pubkey for the column
765
+ channel.channelPubkeys[column] = pubkey;
766
+
767
+ // If both pubkeys are set, validate multisig address
768
+ const pubA = channel.channelPubkeys.A;
769
+ const pubB = channel.channelPubkeys.B;
770
+
771
+ if (pubA && pubB) {
772
+ const Vesting = require('./vesting.js')
773
+ const instance = await Vesting.getInstance()
774
+ const chain = instance.getChain();
775
+ const isTestnet = instance.getTest();
776
+ const multisig1 = await TxUtils.createMultisig(pubA, pubB, chain, isTestnet,senderAddress);
777
+ const multisig2 = await TxUtils.createMultisig(pubB, pubA, chain, isTestnet,senderAddress);
778
+
779
+ if (channel.channel !== multisig1 && channel.channel !== multisig2) {
780
+ valid = false;
781
+ reason = 'Multisig does not match channel address.';
782
+ return { channel, valid, reason };
783
+ }
784
+ }
785
+
786
+ // All good
787
+ return { channel, valid, reason };
788
+
789
+ //} catch (err) {
790
+ // valid = false;
791
+ // reason = err.message;
792
+ // return { channel, valid, reason };
793
+ //}
794
+ }
795
+
796
+
797
+ static async recordCommitToChannel(channelAddress, senderAddress, propertyId, tokenAmount, payEnabled, clearLists, blockHeight, txid){
798
+ console.log('inside record Commit '+channelAddress+' '+senderAddress+' '+propertyId+' '+tokenAmount+' '+blockHeight+ txid)
799
+ if (!this.channelsRegistry) {
800
+ await this.loadChannelsRegistry();
801
+ }
802
+ // Check if the channel exists in the registry
803
+ if (!this.channelsRegistry.has(channelAddress)) {
804
+ // Initialize a new channel record if it doesn't exist
805
+ this.channelsRegistry.set(channelAddress, {
806
+ participants: {'A':'','B':''},
807
+ channel: channelAddress,
808
+ commits: [],
809
+ A: {},
810
+ B: {},
811
+ clearLists: { A: [], B: [] },
812
+ payEnabled: { A: false, B: false },
813
+ lastCommitmentTime: blockHeight,
814
+ lastUsedColumn: null, // Initialize lastUsedColumn to null
815
+ channelPubkeys: {A:'',B:''}
816
+ });
817
+ }
818
+
819
+ // Get the channel from the registry
820
+ let channel = this.channelsRegistry.get(channelAddress);
821
+ console.log(JSON.stringify(channel))
822
+ // Determine the column for the sender address
823
+ let cpAddress = ''
824
+ if(channel.participants.A!==senderAddress&&channel.participants.A){
825
+ cpAddress = channel.participants.A
826
+ }else if(channel.participants.B!==senderAddress&&channel.participants.B){
827
+ cpAddress = channel.participants.B
828
+ }
829
+ let channelColumn = Channels.predictColumnForAddress(channel, senderAddress, cpAddress)
830
+ console.log('column prediction '+channelColumn)
831
+ console.log('about to handle pubkeys '+JSON.stringify(channel)+' '+senderAddress+' '+cpAddress)
832
+ const { channel: updatedChannel, valid, reason } = await Channels.handleChannelPubkey(channel, channelColumn, senderAddress, txid);
833
+ if (!valid) {
834
+ console.log('DISPLACED COMMIT USURPER')
835
+ return
836
+ }
837
+ channel = updatedChannel
838
+ console.log('channel after handle pubkeys '+JSON.stringify(channel))
839
+ channel = await Channels.assignColumnBasedOnAddress(channel, senderAddress, cpAddress,blockHeight);
840
+ const participants = channel.participants;
841
+ channelColumn = Channels.resolveColumn(channel, senderAddress);
842
+ console.log('resolved columen '+channelColumn)
843
+ // Guard: if we failed to resolve a column, bail safely
844
+ if (channelColumn == null) {
845
+ console.log('ERR WITH COMMIT '+senderAddress+' '+channelAddress+' '+blockHeight+' (channelColumn==null)');
846
+ return;
847
+ } console.log('assinging column in recordCommit' +channelColumn)
848
+ // Update the balance in the specified column
849
+ if (!channel[channelColumn][propertyId]) {
850
+ channel[channelColumn][propertyId] = 0;
851
+ }
852
+
853
+ const existingBalance = new BigNumber(channel[channelColumn][propertyId] || 0);
854
+
855
+ // Add the tokenAmount with 8-decimal precision
856
+ channel[channelColumn][propertyId] = existingBalance
857
+ .plus(new BigNumber(tokenAmount))
858
+ .decimalPlaces(8) // Ensure precision is limited to 8 decimals
859
+ .toNumber();
860
+ console.log('modifying column balance '+tokenAmount+' '+channel[channelColumn][propertyId])
861
+ // Add the commit record to the channel
862
+ const commitRecord = {
863
+ senderAddress,
864
+ propertyId,
865
+ tokenAmount,
866
+ block: blockHeight,
867
+ columnAssigned: channelColumn,
868
+ payEnabled: payEnabled
869
+ };
870
+
871
+ await Channels.recordChannelDelta({
872
+ channelId: channel.channel,
873
+ column: channelColumn,
874
+ propertyId,
875
+ amount: new BigNumber(tokenAmount).decimalPlaces(8).toNumber(), // Always positive for commit
876
+ type: 'creditCommit',
877
+ participant: senderAddress,
878
+ block: blockHeight,
879
+ txid,
880
+ memo: 'Commit'
881
+ });
882
+
883
+
884
+ // Always store clearLists on commit (controls counterparty restrictions)
885
+ if (!channel.clearLists) channel.clearLists = { A: [], B: [] };
886
+ if (!channel.payEnabled) channel.payEnabled = { A: false, B: false };
887
+ if (Array.isArray(clearLists) && clearLists.length > 0) {
888
+ channel.clearLists[channelColumn] = clearLists;
889
+ }
890
+ if (payEnabled) {
891
+ channel.payEnabled[channelColumn] = true;
892
+ }
893
+ channel.participants[channelColumn]=senderAddress;
894
+ channel.commits.push(commitRecord);
895
+
896
+ // Update the last commitment time and used column
897
+ channel.lastCommitmentTime = blockHeight;
898
+ channel.lastUsedColumn = channelColumn;
899
+ console.log(JSON.stringify(channel))
900
+ // Save the updated channel information
901
+ this.channelsRegistry.set(channelAddress,channel)
902
+ await this.saveChannelsRegistry();
903
+ return channel
904
+ console.log(`Committed ${tokenAmount} of propertyId ${propertyId} to ${channelColumn} in channel for ${senderAddress}`);
905
+ }
906
+
907
+ static areBothPartiesCommitted(channelAddress) {
908
+ const channel = this.channelsRegistry.get(channelAddress);
909
+ if (!channel) return false; // Channel does not exist
910
+ return channel.participants.size === 2; // True if two unique participants have committed
911
+ }
912
+
913
+ // Function to add a pending withdrawal object to the array
914
+ static async addToWithdrawalQueue(blockHeight, senderAddress, amount, channelAddress,propertyId, withdrawAll, column) {
915
+ if(column==false){
916
+ column ="A"
917
+ }else if(column == true){
918
+ column ="B"
919
+ }
920
+
921
+ const withdrawalObj = {
922
+ withdrawAll: withdrawAll,
923
+ blockHeight: blockHeight,
924
+ senderAddress: senderAddress,
925
+ amount: amount,
926
+ channel: channelAddress,
927
+ propertyId: propertyId,
928
+ column: column
929
+ };
930
+ this.pendingWithdrawals.push(withdrawalObj);
931
+ await this.savePendingWithdrawalToDB(withdrawalObj);
932
+ }
933
+
934
+ // Function to process withdrawals
935
+ static async processWithdrawals(blockHeight) {
936
+ if (this.pendingWithdrawals.length === 0) {
937
+ // Load pending withdrawals from the database if the array is empty
938
+ const pendingWithdrawalsFromDB = await this.loadPendingWithdrawalsFromDB();
939
+ if(pendingWithdrawalsFromDB.length!=0){
940
+ //console.log('inside process withdrawals '+JSON.stringify(Array.from(pendingWithdrawalsFromDB.entries())));
941
+ }
942
+ if (pendingWithdrawalsFromDB.length === 0) {
943
+ return; // No pending withdrawals to process
944
+ } else {
945
+ // Merge loaded pending withdrawals with existing array
946
+ this.pendingWithdrawals.push(...pendingWithdrawalsFromDB);
947
+ }
948
+ }
949
+ //console.log('about to process withdrawals '+blockHeight)
950
+ // Process pending withdrawals
951
+ for (let i = 0; i < this.pendingWithdrawals.length; i++) {
952
+ const withdrawal = this.pendingWithdrawals[i];
953
+ console.log('inside process withdrawals '+JSON.stringify(withdrawal))
954
+ const { block, senderAddress, amount, channel, propertyId, withdrawAll, column } = withdrawal;
955
+ //console.log('about to call getChannel in withdrawals '+channel+' ' +JSON.stringify(withdrawal))
956
+ let thisChannel = await this.getChannel(channel)
957
+ if(thisChannel==undefined){
958
+ //console.log('channel has been removed for 0 balances '+channel)
959
+ this.pendingWithdrawals.splice(i, 1);
960
+ i--;
961
+ await this.removePendingWithdrawalFromDB(withdrawal)
962
+ }
963
+ //console.log('checking thisChannel in withdraw '+JSON.stringify(thisChannel))
964
+ // Function to get current block height
965
+
966
+ // Check if it's time to process this withdrawal
967
+ //console.log('seeing if block is advanced enough to clear waiting period '+withdrawal.blockHeight,blockHeight)
968
+ if (blockHeight >= withdrawal.blockHeight + 7) {
969
+ // Check if sender has sufficient balance for withdrawal
970
+
971
+ //console.log('inside processing block '+JSON.stringify(thisChannel)+' '+channel)
972
+ let column
973
+ if(thisChannel.participants.A==senderAddress){
974
+ column = "A"
975
+ }else if(thisChannel.participants.B==senderAddress){
976
+ column = "B"
977
+ }else{
978
+ //console.log('sender not found on channel '+senderAddress + ' '+channel)
979
+ continue
980
+ }
981
+ if(withdrawAll==true){
982
+ await this.processWithdrawAll(senderAddress,thisChannel,column,blockHeight)
983
+ }
984
+ let balance
985
+ if(column=="A"){
986
+ balance = thisChannel.A[propertyId]
987
+ }else if(column=="B"){
988
+ balance = thisChannel.B[propertyId]
989
+ }
990
+ if (balance >= amount&&!isNaN(amount)) {
991
+ if(!withdrawAll){
992
+ await this.processWithdrawal(senderAddress,thisChannel,amount,propertyId,column,blockHeight)
993
+ }
994
+
995
+ // Remove processed withdrawal from the array
996
+ this.pendingWithdrawals.splice(i, 1);
997
+ i--; // Adjust index after removal
998
+ await this.removePendingWithdrawalFromDB(withdrawal)
999
+ } else {
1000
+ // Insufficient balance, eject the withdrawal from the queue
1001
+ console.log(`Insufficient balance for withdrawal: ${senderAddress}`+' amt'+amount+' prptyid'+propertyId);
1002
+ this.pendingWithdrawals.splice(i, 1);
1003
+ i--; // Adjust index after removal
1004
+ await this.removePendingWithdrawalFromDB(withdrawal)
1005
+ }
1006
+ }
1007
+ }
1008
+ await this.saveChannelsRegistry()
1009
+ return
1010
+ }
1011
+
1012
+ static async removeEmptyChannels() {
1013
+ for (const [channelAddress, channelData] of this.channelsRegistry.entries()) {
1014
+
1015
+ const empty = await this.isChannelEmpty(channelData);
1016
+ //console.log('inside remove Empty Channels '+channelAddress+' '+empty+' ' +JSON.stringify(channelData))
1017
+ if (empty) {
1018
+ this.channelsRegistry.delete(channelAddress);
1019
+ //console.log(`Removed empty channel: ${channelAddress}`);
1020
+ await this.removeChannelFromDB()
1021
+ }
1022
+ }
1023
+ }
1024
+
1025
+ static async isChannelEmpty(thisChannel) {
1026
+ if (!thisChannel || !thisChannel.participants) {
1027
+ return true; // Assuming channel is empty if it doesn't exist or has no participants
1028
+ }
1029
+
1030
+ const participantA = thisChannel.A || {};
1031
+ const participantB = thisChannel.B || {};
1032
+ //console.log('inside isChannelEmpty '+JSON.stringify(participantA)+' '+ JSON.stringify(participantB))
1033
+
1034
+ // Check if all properties in A and B are 0
1035
+ for (const propertyId in participantA) {
1036
+ //console.log(participantA[propertyId], Boolean(participantA[propertyId]!==0), Boolean(participantA[propertyId]==0))
1037
+ if (participantA[propertyId] !== 0) {
1038
+ return false; // Not empty if any property in participantA is not 0
1039
+ }
1040
+ }
1041
+ for (const propertyId in participantB) {
1042
+ //console.log(participantA[propertyId],Boolean(participantB[propertyId]!==0), Boolean(participantB[propertyId]==0))
1043
+ if (participantB[propertyId] !== 0) {
1044
+ return false; // Not empty if any property in participantB is not 0
1045
+ }
1046
+ }
1047
+ return true; // Empty if all properties in A and B are 0
1048
+ }
1049
+
1050
+ static async removeChannelFromDB(channelAddress) {
1051
+ const channelsDB = await dbInstance.getDatabase('channels');
1052
+ const withdrawalKey = `${channelAddress}`;
1053
+
1054
+ // Remove the channel entry from the database
1055
+ await channelsDB.removeAsync({ _id: withdrawalKey });
1056
+ }
1057
+
1058
+
1059
+
1060
+ static adjustChannelBalances(channelAddress, propertyId, amount, column) {
1061
+ // Logic to adjust the token balances within a channel
1062
+ // This could involve debiting or crediting the committed columns based on the PNL amount
1063
+ const channel = this.channelsRegistry.get(channelAddress);
1064
+ channel[column][propertyId]+=amount
1065
+ if (!channel) {
1066
+ throw new Error('Trade channel not found');
1067
+ }
1068
+ this.channelsRegistry.set(channelAddress, channel)
1069
+ // Example logic to adjust balances
1070
+ // Update the channel's token balances as needed
1071
+ }
1072
+
1073
+ // Transaction processing functions
1074
+ static async processWithdrawal(senderAddress,channel,amount,propertyId,column,block) {
1075
+ // Update balances and logic for withdrawal
1076
+ // Example logic, replace with actual business logic
1077
+ //console.log('checking channel obj in processWithdrawal '+JSON.stringify(channel))
1078
+ //console.log('in processWithdrawal '+channel[column][propertyId])
1079
+ const TallyLazy = require('./tally.js')
1080
+ let has = await TallyLazy.hasSufficientReserve(channel.channel,propertyId,amount)
1081
+ console.log(amount, has.hasSufficient)
1082
+ if(has.hasSufficient==false){
1083
+ amount-=has.shortfall
1084
+ }
1085
+ console.log(amount, has.shortfall)
1086
+ channel[column][propertyId] -= amount;
1087
+ console.log('about to modify tallyMap in processWithdrawal '+channel.channel,propertyId,amount,senderAddress)
1088
+ await TallyLazy.updateChannelBalance(channel.channel, propertyId, -amount, 'channelWithdrawalPull',block)
1089
+ await TallyLazy.updateBalance(senderAddress,propertyId, amount, 0, 0,0,'channelWithdrawalComplete',block)
1090
+ this.channelsRegistry.set(channel.channel, channel);
1091
+ return
1092
+ }
1093
+
1094
+ static async processWithdrawAll(senderAddress, thisChannel, column,blockHeight) {
1095
+ for (const [propertyId, amount] of Object.entries(thisChannel[column])) {
1096
+ console.log('in process withdraw all '+senderAddress,thisChannel, amount, propertyId, column)
1097
+ await this.processWithdrawal(senderAddress, thisChannel, amount, parseInt(propertyId), column,blockHeight);
1098
+ }
1099
+ }
1100
+
1101
+
1102
+ static async processTransfer(transaction) {
1103
+ const clearlistManager = require('./clearlist.js');
1104
+ // Process a transfer within a trade channel
1105
+ const { fromChannel, toChannel, amount, propertyId, transferorIsColumnA, destinationColumn } = transaction;
1106
+ const sourceChannel = this.channelsRegistry.get(fromChannel);
1107
+ const destinationChannel = this.channelsRegistry.get(toChannel);
1108
+
1109
+ if (!sourceChannel || !destinationChannel) {
1110
+ throw new Error('Channel(s) not found');
1111
+ }
1112
+
1113
+ // Enforce channel clearLists: source column's clearLists must approve destination committer
1114
+ const sourceColumn = transferorIsColumnA ? 'A' : 'B';
1115
+ const sourceClearLists = sourceChannel.clearLists?.[sourceColumn] || [];
1116
+ if (sourceClearLists.length > 0) {
1117
+ const destCommitter = destinationChannel.participants?.[destinationColumn];
1118
+ if (destCommitter) {
1119
+ let approved = false;
1120
+ for (const listId of sourceClearLists) {
1121
+ if (await clearlistManager.isAddressInClearlistOrDerived(listId, destCommitter)) {
1122
+ approved = true;
1123
+ break;
1124
+ }
1125
+ }
1126
+ if (!approved) {
1127
+ throw new Error(`Destination committer ${destCommitter} is not attested in source column clearLists [${sourceClearLists}]`);
1128
+ }
1129
+ }
1130
+ }
1131
+
1132
+ // Update balances and logic for transfer
1133
+ // Example logic, replace with actual business logic
1134
+ if(transferorIsColumnA&&destinationColumn=='A'){
1135
+ sourceChannel.A[propertyId] -= amount;
1136
+ destinationChannel.A[propertyId] += amount;
1137
+ }else if(transferorIsColumnA&&destinationColumn=='B'){
1138
+ sourceChannel.A[propertyId] -= amount;
1139
+ destinationChannel.B[propertyId] += amount;
1140
+ }else if(!transferorIsColumnA&&destinationColumn=='A'){
1141
+ sourceChannel.B[propertyId] -= amount
1142
+ destinationChannel.A +=amount
1143
+ }else if(!transferorIsColumnA&&destinationColumn=='B'){
1144
+ sourceChannel.A[propertyId] -= amount
1145
+ destinationChannel.B +=amount
1146
+ }
1147
+
1148
+ this.channelsRegistry.set(fromChannel, sourceChannel);
1149
+ this.channelsRegistry.set(toChannel, destinationChannel);
1150
+ }
1151
+
1152
+ static updateChannelWithColumnAssignments(channelAddress, columnAssignments) {
1153
+ const channel = this.channels.get(channelAddress);
1154
+ if (!channel) return; // Exit if channel does not exist
1155
+
1156
+ channel.commits = columnAssignments.map(commit => ({
1157
+ ...commit,
1158
+ columnAssigned: true
1159
+ }));
1160
+ }
1161
+ }
1162
+
1163
+ module.exports = Channels;