@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,3077 @@
1
+ const TxUtils = require('./txUtils.js')
2
+ const db = require('./db')
3
+ const Activation = require('./activation.js')
4
+ const activationInstance = Activation.getInstance();
5
+ const PropertyList = require('./property.js')
6
+ const OracleList = require('./oracle.js')
7
+ const ContractRegistry = require('./contractRegistry.js')
8
+ const TallyMap = require('./tally.js')
9
+ const BigNumber = require('bignumber.js')
10
+ const Orderbook = require('./orderbook.js')
11
+ const Channels = require('./channels.js')
12
+ const MarginMap = require('./marginMap.js')
13
+ const ClearList = require('./clearlist.js')
14
+ const VolumeIndex = require('./volumeIndex.js')
15
+ const SyntheticRegistry = require('./vaults.js')
16
+ const Vesting = require('./vesting.js')
17
+ const Scaling = require('./scaling.js')
18
+ //const whiteLists = require('./whitelists.js')
19
+ const bannedCountries = ["US", "KP", "RU", "IR", "CU"];
20
+ const OptionsEngine = require('./options.js');
21
+
22
+ const Validity = {
23
+
24
+ isActivated: async (block,txid,txType) => {
25
+ /*let is = false
26
+ const activationBlock = await activationInstance.checkActivationBlock(txType)
27
+ const tx = await TxUtils.getRawTransaction(txid,true)
28
+ console.log('inside isActivated '+JSON.stringify(tx) + ' '+ activationBlock+ ' '+txType)
29
+ //if(!tx){return true}
30
+ if(block>activationBlock&&activationBlock!=null){
31
+ is = true
32
+ }
33
+ return is*/
34
+ return true
35
+ },
36
+
37
+ isValidNumber(x) {
38
+ // Add to validation
39
+ const MAX_SAFE_AMOUNT = 9007199254740991; // 2^53 - 1
40
+ const MAX_PRICE = 1e15; // 1 quadrillion - reasonable upper bound
41
+
42
+ if (x <= 0 || x > MAX_SAFE_AMOUNT) return false;
43
+ return (typeof x === 'number' && Number.isFinite(x) && !isNaN(x) && x > 0)
44
+ },
45
+ //Type 0: Activation
46
+ validateActivateTradeLayer: async (sender, params, txid) => {
47
+ params.valid = true;
48
+ console.log('Raw txTypeToActivate:', JSON.stringify(params.txTypesToActivate));
49
+
50
+ let txTypes = [];
51
+
52
+ // Sanitize txTypeToActivate into an array of valid numbers
53
+ if (Array.isArray(params.txTypesToActivate)) {
54
+ txTypes = params.txTypesToActivate
55
+ .map(tx => Number(tx)) // Convert all elements to numbers
56
+ .filter(tx => !isNaN(tx)); // Remove any invalid numbers (NaN)
57
+ } else if (params.txTypeToActivate !== undefined && params.txTypesToActivate !== null) {
58
+ const num = Number(params.txTypesToActivate);
59
+ if (!isNaN(num)) {
60
+ txTypes = [num];
61
+ }
62
+ }
63
+
64
+ console.log('Sanitized txTypes:', txTypes);
65
+
66
+ // Check if txTypes array is empty (invalid input)
67
+ if (txTypes.length === 0) {
68
+ params.valid = false;
69
+ params.reason = 'Tx Type contains non-integer or invalid values';
70
+ return params;
71
+ }
72
+
73
+ // Check if sender is the admin address
74
+ const admin = activationInstance.getAdmin();
75
+ console.log('Sender vs Admin:', sender, admin);
76
+ if (sender !== admin) {
77
+ params.valid = false;
78
+ params.reason = 'Not sent from admin address';
79
+ return params;
80
+ }
81
+
82
+ // Check if txTypes are within valid bounds
83
+ if (txTypes.some(txType => txType > 35 || txType < 0)) {
84
+ params.valid = false;
85
+ params.reason = 'Tx Type out of bounds';
86
+ return params;
87
+ }
88
+
89
+ // All checks passed
90
+ params.txTypesToActivate = txTypes;
91
+ console.log('Validated txTypesToActivate:', txTypes);
92
+ return params;
93
+ },
94
+
95
+
96
+ // 1: Token Issue
97
+ validateTokenIssue: async (sender, params,txid) => {
98
+ params.valid=true
99
+ console.log('inside issuance validation '+JSON.stringify(params))
100
+ const isAlreadyActivated = await activationInstance.isTxTypeActive(1);
101
+ if(isAlreadyActivated==false){
102
+ params.valid=false
103
+ params.reason += 'Tx type not yet activated '
104
+ }
105
+
106
+ if (!(Number.isInteger(params.initialAmount) && params.initialAmount > 0)) {
107
+ params.valid=false
108
+ params.reason += 'Invalid initial amount; ';
109
+ }
110
+
111
+ if (!(typeof params.ticker === 'string' && params.ticker.length <= 6)) {
112
+ params.valid=false
113
+ params.reason += 'Invalid ticker; ';
114
+ }
115
+ // Add check for existing ticker using the isTickerExist method
116
+
117
+ const tickerExists = await PropertyList.doesTickerExist(params.ticker);
118
+ if (tickerExists) {
119
+ params.valid = false;
120
+ params.reason += 'Ticker already exists; ';
121
+ }
122
+
123
+ // Invalidate if the ticker starts with "s"
124
+ if (params.ticker.startsWith('s')) {
125
+ params.valid = false;
126
+ params.reason += 'Ticker cannot start with "s"; ';
127
+ }
128
+
129
+ if (params.type === 'native' && (params.propertyId !== 1||params.propertyId !==4)) {
130
+ params.valid=false
131
+ params.reason += 'Invalid property ID for native type; ';
132
+ }
133
+
134
+ if (params.type === 'vesting' && (params.propertyId !== 2||params.propertyId !==3)){
135
+ params.valid=false
136
+ params.reason += 'Invalid property ID for vesting type; ';
137
+ }
138
+
139
+ const is = await Validity.isActivated(params.block,txid,1)
140
+ console.log(is)
141
+ if (!is) {
142
+ params.valid = false;
143
+ params.reason = 'Transaction type activated after tx';
144
+ }
145
+
146
+ return params
147
+ },
148
+
149
+ // 2: Send
150
+ validateSend: async (sender, params, txid) => {
151
+ params.reason = '';
152
+ params.valid= true
153
+ //console.log('send params ' +JSON.stringify(params))
154
+
155
+ const isAlreadyActivated = await activationInstance.isTxTypeActive(2);
156
+ if(isAlreadyActivated==false){
157
+ params.valid=false
158
+ params.reason += 'Tx type not yet activated '
159
+ }
160
+
161
+ const is = await Validity.isActivated(params.block,txid,2)
162
+ console.log(is)
163
+ if (!is) {
164
+ params.valid = false;
165
+ params.reason = 'Transaction type activated after tx';
166
+ }
167
+
168
+ if(!validateAddress(params.address)){
169
+ const valid = await TxUtils.validateAddressWrapper(params.address)
170
+ console.log('double checking validity with rpc '+JSON.stringify(valid))
171
+ console.log('valid? '+valid.isvalid)
172
+ if(!valid.isvalid){
173
+ params.valid= false
174
+ params.reason = 'Destination address is not validly formed.'
175
+ }
176
+ }
177
+
178
+ const propertyData = await PropertyList.getPropertyData(params.propertyIds)
179
+ console.log(JSON.stringify(propertyData))
180
+ if(propertyData==null||propertyData==undefined){
181
+ params.valid = false
182
+ params.reason = 'propertyId not found in Property List'
183
+ return params
184
+ }
185
+
186
+ const admin = activationInstance.getAdmin()
187
+ console.log('admin in send '+admin)
188
+ if(sender!=admin&&(params.propertyIds == 1||params.propertyIds == 4)){
189
+ let bans = await ClearList.getBanlist()
190
+ console.log('banlist '+JSON.stringify(bans))
191
+ if(bans==null){bans = bannedCountries}
192
+ console.log('bans again '+bans)
193
+ const senderCountryInfo = await ClearList.getCountryCodeByAddress(sender);
194
+ const isAcc = await ClearList.isAddressInClearlist(3,sender)
195
+ console.log('sender country info '+JSON.stringify(senderCountryInfo))
196
+ if ((!senderCountryInfo || bans.includes(senderCountryInfo.countryCode))&&!isAcc){
197
+ if(activationInstance.areActivationsAboveThreshold()){
198
+ params.valid = false;
199
+ params.reason += 'Sender cannot handle TL or TLI from a banned country or lacking country code attestation';
200
+
201
+ }
202
+ }
203
+ }
204
+
205
+
206
+ const TallyMap = require('./tally.js')
207
+ const senderTally = await TallyMap.getTally(sender, params.propertyIds);
208
+ console.log('checking senderTally '+ JSON.stringify(params) + ' '+ params.senderAddress, params.propertyIds, JSON.stringify(senderTally))
209
+ if (senderTally==0) {
210
+ var balances = await TallyMap.getAddressBalances(sender)
211
+ if(balances ==[]){
212
+ TallyMap.diagonistic(sender, params.propertyIds)
213
+ }
214
+ }
215
+
216
+ console.log('checking we have enough tokens '+senderTally.available+ ' '+ params.amounts)
217
+ if(senderTally.available<params.amounts||senderTally.available==undefined){
218
+ params.valid=false
219
+ params.reason += 'Insufficient available balance'
220
+ //console.log(params.valid, params.reason)
221
+ }
222
+ /*const hasSufficientBalance = await TallyMap.hasSufficientBalance(params.senderAddress, params.propertyId, params.amounts)
223
+ console.log('validating send '+JSON.stringify(hasSufficientBalance))
224
+ if(hasSufficientBalance.hasSufficient==false){
225
+ params.valid=false
226
+ params.reason += 'Insufficient available balance'
227
+ console.log(params.valid, params.reason)
228
+ }*/
229
+
230
+ // Whitelist validation logic
231
+ let propertyIds = [];
232
+
233
+ if (Array.isArray(params.propertyIds)) {
234
+ propertyIds = params.propertyIds;
235
+ } else if (Number.isInteger(params.propertyIds)) {
236
+ propertyIds = [params.propertyIds];
237
+ }
238
+
239
+ const senderWhitelists = Array.isArray(propertyData.whitelistId) ? propertyData.whitelistId : [propertyData.whitelistId];
240
+
241
+ // Get recipient whitelist IDs from the attestation map
242
+ const recipientAttestations = await ClearList.getAttestations(params.recipientAddress);
243
+ const recipientWhitelists = recipientAttestations.map(att => att.data.clearlistId);
244
+ var passesSend = false
245
+
246
+ for (const whitelistId of senderWhitelists) {
247
+
248
+ const senderWhitelisted = await ClearList.isAddressInClearlist(whitelistId, sender);
249
+ if (senderWhitelisted) {
250
+ passesSend=true
251
+ }
252
+ }
253
+ if(!passesSend&&propertyData.whitelistId!=0){
254
+ params.valid=false
255
+ params.reason += `Sender address not whitelisted in clearlist`;
256
+ }
257
+
258
+ var passesReceive = false
259
+
260
+ for (const whitelistId of recipientWhitelists) {
261
+ const recipientWhitelisted = await ClearList.isAddressInClearlist(whitelistId, params.recipientAddress);
262
+ if (recipientWhitelisted) {
263
+ passesReceive=true
264
+
265
+ break; // No need to check further if one fails
266
+ }
267
+ }
268
+ if(!passesReceive&&propertyData.whitelistId!=0){
269
+ params.valid = false;
270
+ params.reason += `Recipient address not whitelisted in clearlist; `;
271
+ }
272
+
273
+ if (Array.isArray(params.amounts)) {
274
+ params.amounts = params.amounts.filter(isValidAmount);
275
+ if (params.amounts.length === 0) {
276
+ params.valid = false;
277
+ params.reason += 'No valid amounts; ';
278
+ return params;
279
+ }
280
+ } else if (!Validity.isValidNumber(params.amounts)) {
281
+ params.valid = false;
282
+ params.reason += 'Invalid or missing amount; ';
283
+ return params;
284
+ }
285
+
286
+ return params
287
+ },
288
+
289
+ // 3: Trade Token for UTXO
290
+ validateTradeTokenForUTXO: async (sender, params, txid,outputs) => {
291
+ params.reason = '';
292
+ params.valid = true;
293
+ console.log('inside validate UTXO outputs '+JSON.stringify(outputs))
294
+ const isAlreadyActivated = await activationInstance.isTxTypeActive(3);
295
+ if (!isAlreadyActivated) {
296
+ params.valid = false;
297
+ params.reason += 'Tx type not yet activated ';
298
+ }
299
+
300
+ const is = await Validity.isActivated(params.block,txid,3)
301
+ console.log(is)
302
+ if (!is) {
303
+ params.valid = false;
304
+ params.reason = 'Transaction type activated after tx';
305
+ }
306
+
307
+
308
+ const property = PropertyList.getPropertyData(params.propertyId)
309
+
310
+ if (property==null) {
311
+ params.valid = false;
312
+ params.reason += 'Invalid property ID; ';
313
+ }
314
+
315
+ if (!Validity.isValidNumber(params.amount)) {
316
+ params.valid = false;
317
+ params.reason += 'Invalid or missing token amount; ';
318
+ return params;
319
+ }
320
+
321
+ let has = await TallyMap.hasSufficientChannel(sender, params.propertyId, params.amount);
322
+ console.log(JSON.stringify(has))
323
+ if (!has.hasSufficient) {
324
+ params.valid = true; // Adjust according to logic
325
+ params.reason += ' Insufficient Tokens ';
326
+ console.log('reducing tokens to available '+params.amount+' '+has.shortfall)
327
+ params.amount -= has.shortfall;
328
+ }
329
+
330
+ if(!params.amount||params.amount==0||isNaN(params.amount)){
331
+ params.valid = false;
332
+ params.reason += 'Invalid amount'
333
+ }
334
+
335
+ if (!(Number.isInteger(params.satsExpected) && params.satsExpected >= 0)) {
336
+ params.valid = true; // Maintain the transaction but log the issue
337
+ params.reason += 'Invalid sats expected; ';
338
+ }
339
+
340
+ if (outputs.length == 0) {
341
+ params.valid = false
342
+ params.reason += 'No outputs; ';
343
+ return
344
+ }
345
+
346
+ const outs = (Array.isArray(outputs) && outputs.length) ? outputs : await TxUtils.getTransactionOutputs(txid);
347
+
348
+ // Find sats paid to the intended recipient
349
+ let sats = 0;
350
+ if (Number.isInteger(params.payToAddress)) {
351
+ const paymentOut = outs[params.payToAddress]
352
+ sats += paymentOut.satoshis
353
+ }else{
354
+ params.valid = true
355
+ params.reason += 'missing payToAddress, defaulting to 1'
356
+ params.payToAddress=1
357
+ params.satsReceived = outs[1]
358
+ }
359
+ params.satsReceived= sats
360
+
361
+ // Validate the payToAddress corresponds to the correct vOut
362
+ const satsExpectedFloat = new BigNumber(params.satsExpected).dividedBy(100000000).decimalPlaces(8).toNumber()
363
+ params.price = new BigNumber(satsExpectedFloat).dividedBy(params.amount).decimalPlaces(8).toNumber()
364
+ if (params.satsReceived < satsExpectedFloat) { // convert satsExpected to LTC
365
+ params.valid = true;
366
+ params.reason += `Received LTC (${params.satsRecieved}) is less than expected; `;
367
+ params.paymentPercent = new BigNumber(params.satsRecieved).dividedBy(params.satsRecieved).dividedBy(100000000).decimalPlaces(8).toNumber()
368
+ }else{
369
+ params.paymentPercent=100
370
+ }
371
+
372
+ if (!Number.isInteger(params.tokenOutput)) {
373
+ params.valid = true;
374
+ params.reason += 'tokenOutput not an integer';
375
+ if(params.payToAddress == 0){params.tokenOutput = 1
376
+ }else if(reference.length<3){params.tokenOutput=0
377
+ }else{params.tokenOutput=3}
378
+
379
+ params.tokenDeliveryAddress = reference.find(ref => ref.vout === params.tokenOutput);
380
+ }
381
+
382
+ console.log('Inside validate UTXO trade', JSON.stringify(params));
383
+ return params;
384
+ },
385
+
386
+ // 4: Commit Token
387
+ validateCommit: async (sender, params, txid) => {
388
+ params.reason = '';
389
+ params.valid = true;
390
+ params.txid = txid
391
+ console.log('tagging txid to params obj '+txid +' '+params.txid)
392
+ console.log('inside validate commit '+JSON.stringify(params))
393
+ if(params.ref){
394
+ //console.log(params.ref)
395
+ const outputs = await TxUtils.getTransactionOutputs(txid)
396
+
397
+ let matchingOutput = null;
398
+ //console.log(JSON.stringify(outputs))
399
+ // Loop through the outputs array to find the one with the matching vout
400
+ for (let i = 0; i < outputs.length; i++) {
401
+ //console.log('in the for '+i+' '+outputs[i].vout+' '+params.ref)
402
+ if (outputs[i].vout === Number(params.ref)) {
403
+ matchingOutput = outputs[i];
404
+ //console.log('match output '+matchingOutput)
405
+ break; // Exit loop once the matching output is found
406
+ }
407
+ }
408
+
409
+ if (matchingOutput) {
410
+ // Access the matching output's address and satoshis
411
+ params.channelAddress = matchingOutput.address;
412
+ console.log('params.channelAddress '+params.channelAddress)
413
+ }else{
414
+ params.valid = false
415
+ params.reason += "No channel address detectable in payload or ref: output"
416
+ }
417
+ }
418
+
419
+ if(params.channelAddress!=''){
420
+ if(!validateAddress(params.channelAddress)){
421
+ const valid = await TxUtils.validateAddressWrapper(params.channelAddress)
422
+ if(!valid.isvalid){
423
+ params.valid= false
424
+ params.reason = 'Destination address is not validly formed.'
425
+ }
426
+ }
427
+ }
428
+
429
+ if (!Validity.isValidNumber(params.amount)) {
430
+ console.log('invalid amount in commit '+params.amount)
431
+ params.valid = false;
432
+ params.reason += 'Invalid or missing amount; ';
433
+ return params;
434
+ }
435
+
436
+ console.log('about to check tally for commit '+params.senderAddress+' '+params.propertyId+' '+params.amount)
437
+ let hasSufficientBalance = await TallyMap.hasSufficientBalance(params.senderAddress, params.propertyId, params.amount)
438
+ console.log('checking balance in commit '+JSON.stringify(hasSufficientBalance)+params.amount)
439
+ // Check if the sender has sufficient balance
440
+ if (hasSufficientBalance.hasSufficient==false){
441
+ params.valid = false
442
+ params.reason += 'Insufficient token balance for commitment';
443
+ }
444
+
445
+ const isAlreadyActivated = await activationInstance.isTxTypeActive(4);
446
+ if(isAlreadyActivated==false){
447
+ params.valid=false
448
+ params.reason += 'Tx type not yet activated '
449
+ }
450
+
451
+ if(params.propertyId==2||params.propertyId==3){
452
+ params.valid=false
453
+ params.reason="Cannot trade vesting tokens"
454
+ }
455
+
456
+ const propertyData = await PropertyList.getPropertyData(params.propertyId)
457
+ console.log('getting propertyId in validate commit '+JSON.stringify(propertyData))
458
+ if(propertyData==null){
459
+ console.log('offending propertyId value '+params.propertyId)
460
+ params.valid=false
461
+ params.reason="Null returning for propertyData"
462
+ return params
463
+ }
464
+ // Whitelist validation logic
465
+
466
+ const senderWhitelists = Array.isArray(propertyData.whitelistId) ? propertyData.whitelistId : [propertyData.whitelistId];
467
+ var passes = false
468
+ for (const whitelistId of senderWhitelists) {
469
+ const senderWhitelisted = await ClearList.isAddressInClearlist(whitelistId, sender);
470
+ if (senderWhitelisted) {
471
+ passes=true
472
+ break
473
+ }
474
+ }
475
+ const admin = activationInstance.getAdmin()
476
+ if(sender!=admin){
477
+ let bans = await ClearList.getBanlist()
478
+ if(bans==null){bans = bannedCountries}
479
+ const senderCountryInfo = await ClearList.getCountryCodeByAddress(sender);
480
+ console.log('sender country info '+JSON.stringify(senderCountryInfo))
481
+ if(params.propertyId == 1||params.propertyId == 2||params.propertyId == 3||params.propertyId == 4){
482
+ const isAcc = await ClearList.isAddressInClearlist(3,sender)
483
+ console.log('sender country info '+JSON.stringify(senderCountryInfo))
484
+ if ((!senderCountryInfo || bans.includes(senderCountryInfo.countryCode))&&!isAcc){
485
+ if(activationInstance.areActivationsAboveThreshold()){
486
+ params.valid = false;
487
+ params.reason += 'Sender cannot handle TL or TLI from a banned country or lacking country code attestation';
488
+ }
489
+ }
490
+ }
491
+ }
492
+
493
+ const channelData =await Channels.getChannel(params.channelAddress)
494
+ console.log('glaiven '+JSON.stringify(channelData)+' '+JSON.stringify(params))
495
+ if (channelData) {
496
+ const tx = await TxUtils.getRawTransaction(txid);
497
+
498
+ // Find the vin corresponding to the sender
499
+ let senderVin = tx.vin[0];
500
+
501
+ // Detect script type
502
+ const scriptType = TxUtils.getAddressTypeUniversal(sender);
503
+ // Extract pubkey(s)
504
+ const pubkeys = TxUtils.extractPubkeyByType(senderVin, scriptType)|| [];
505
+
506
+ if (!pubkeys || pubkeys.length === 0) {
507
+ params.valid = false;
508
+ params.reason += "Could not extract pubkey from sender's input.";
509
+ return params;
510
+ }
511
+
512
+ // Compare pubkey to expected pubkeys for the channel
513
+ // (This depends on how you store the multisig pubkeys, but let's say channelData has channelPubkeys: {A: '...', B: '...'})
514
+ const expectedPubkeys = [
515
+ channelData.channelPubkeys?.A,
516
+ channelData.channelPubkeys?.B
517
+ ].filter(Boolean);
518
+ console.log('channel pubkeys '+JSON.stringify(expectedPubkeys)+' '+'sender addr '+sender)
519
+ if(expectedPubkeys.length==2){
520
+ const instance = await Vesting.getInstance()
521
+ const chain = instance.getChain()
522
+ const isTestnet = instance.getTest()
523
+ console.log('params for sim multisig '+chain+' '+isTestnet)
524
+ const multiA = await TxUtils.createMultisig(expectedPubkeys[0],expectedPubkeys[1], chain, isTestnet,sender)
525
+ const multiB = await TxUtils.createMultisig(expectedPubkeys[1],expectedPubkeys[0], chain, isTestnet,sender)
526
+ console.log('multiA and B '+multiA+' '+multiB+' '+params.channelAddress)
527
+ if(multiA!==params.channelAddress&&multiB!==params.channelAddress){
528
+ params.valid = false;
529
+ params.reason += "Commiter is not a party to the multisig channel.";
530
+ return params;
531
+ }
532
+ }
533
+ }
534
+
535
+ if(!passes&&propertyData.whitelistId!=0){
536
+ params.valid = false;
537
+ params.reason += `Sender address not listed in clearlist for the token`;
538
+ }
539
+
540
+ if (typeof params.payEnabled !== 'boolean') {
541
+ params.valid = false;
542
+ params.reason += 'payEnabled is not a boolean. ';
543
+ }
544
+
545
+ // Validate clearLists
546
+ if (params.clearLists) {
547
+ const invalidClearListItems = params.clearLists.filter(num => !Number.isInteger(num));
548
+ if (invalidClearListItems.length > 0) {
549
+ params.valid = false;
550
+ params.reason += 'clearLists contains non-integer values. ';
551
+ }
552
+ }
553
+
554
+ return params;
555
+ },
556
+
557
+ // 5: On-chain Token for Token
558
+ validateOnChainTokenForToken: async (sender, params, txid) => {
559
+ params.reason = '';
560
+ params.valid = true;
561
+
562
+ if (!params.propertyIdOffered || !params.propertyIdDesired || !params.amountOffered || !params.amountExpected) {
563
+ params.valid= false
564
+ params.reason += 'Missing required parameters for tradeTokens '
565
+ }
566
+
567
+ if (!Validity.isValidNumber(params.amountOffered)||!Validity.isValidNumber(params.amountExpected)) {
568
+ params.valid = false;
569
+ params.reason += 'Invalid or missing amount; ';
570
+ return params;
571
+ }
572
+
573
+ const isAlreadyActivated = await activationInstance.isTxTypeActive(5);
574
+ if(isAlreadyActivated==false){
575
+ params.valid=false
576
+ params.reason += 'Tx type not yet activated '
577
+ }
578
+
579
+ const is = await Validity.isActivated(params.block,txid,5)
580
+ console.log(is)
581
+ if (!is) {
582
+ params.valid = false;
583
+ params.reason = 'Transaction type activated after tx';
584
+ }
585
+
586
+ const isVEST= (parseInt(params.propertyIdDesired)==2||parseInt(params.propertyIdOffered)==2||parseInt(params.propertyIdDesired)==3||parseInt(params.propertyIdOffered)==3)
587
+ if(isVEST){
588
+ params.valid =false
589
+ params.reason += "Vesting tokens cannot be traded"
590
+ }
591
+
592
+ if(params.propertyIdOffered==params.propertyIdDesired){
593
+ params.valid =false
594
+ params.reason += "Cannot trade token against its own type"
595
+ }
596
+ const admin = activationInstance.getAdmin()
597
+ console.log('admin '+admin)
598
+ if(sender!=admin){
599
+ let bans = await ClearList.getBanlist()
600
+ if(bans==null){bans = bannedCountries}
601
+ const senderCountryInfo = await ClearList.getCountryCodeByAddress(sender);
602
+ if(params.propertyIdOffered == 1||params.propertyIdOffered == 4||params.propertyIdDesired == 1||params.propertyIdDesired == 4){
603
+ const isAcc = await ClearList.isAddressInClearlist(3,sender)
604
+ console.log('sender country info '+JSON.stringify(senderCountryInfo))
605
+ if ((!senderCountryInfo || bans.includes(senderCountryInfo.countryCode))&&!isAcc){
606
+ params.valid = false;
607
+ params.reason += 'Sender cannot handle TL or TLI from a banned country or lacking country code attestation';
608
+ }
609
+ }
610
+ }
611
+
612
+ const TallyMap = require('./tally.js')
613
+ const hasSufficientBalance = await TallyMap.hasSufficientBalance(sender, params.propertyIdOffered, params.amountOffered);
614
+ if (!hasSufficientBalance.hasSufficient) {
615
+ params.valid = false;
616
+ params.reason += 'Insufficient balance for offered token; ';
617
+ }
618
+ console.log('inside validate commit '+params.propertyIdDesired+' '+params.propertyIdOffered)
619
+ const propertyData1 = await PropertyList.getPropertyData(params.propertyIdDesired)
620
+ const propertyData2 = await PropertyList.getPropertyData(params.propertyIdOffered)
621
+
622
+ // Whitelist validation logic
623
+ if(propertyData1==null||propertyData2==null){
624
+ console.log('offending propertyId value '+params.propertyIdDesired,params.propertyIdOffered)
625
+ params.valid = false
626
+ params.reason += 'Null returning for propertyData'
627
+ return params
628
+ }
629
+
630
+ const senderWhitelists = Array.isArray(propertyData1.whitelistId) ? propertyData1.whitelistId : [propertyData1.whitelistId];
631
+ const desiredLists = Array.isArray(propertyData2.whitelistId) ? propertyData2.whitelistId : [propertyData2.whitelistId];
632
+
633
+ var passes1 = false
634
+ for (const whitelistId of senderWhitelists) {
635
+ const senderWhitelisted = await ClearList.isAddressInClearlist(whitelistId, sender);
636
+ if (senderWhitelisted) {
637
+ passes1 = true
638
+ break
639
+ }
640
+ }
641
+ if(!passes1&&propertyData1.whitelistId!=0&&propertyData2.whitelistId!=0){
642
+ params.valid = false;
643
+ params.reason += `Sender address not listed in clearlist for offered token `;
644
+ }
645
+
646
+ var passes2 = false
647
+
648
+ for (const whitelistId of desiredLists) {
649
+ const recipientWhitelisted = await ClearList.isAddressInClearlist(whitelistId, sender);
650
+ if (recipientWhitelisted) {
651
+ passes2 = true
652
+ break
653
+ }
654
+ }
655
+ if(!passes2&&propertyData1.whitelistId!=0&&propertyData2.whitelistId!=0){
656
+ params.valid = false;
657
+ params.reason += `Trader address not listed in clearlist `;
658
+ }
659
+
660
+ return params;
661
+ },
662
+
663
+
664
+ // 6: Cancel Order
665
+ validateCancelOrder: async (sender, params, txid) => {
666
+ params.reason = '';
667
+ params.valid = true;
668
+ let key
669
+ //console.log('validating cancel order '+JSON.stringify(params), sender, txid)
670
+ const isAlreadyActivated = await activationInstance.isTxTypeActive(6);
671
+ if (!isAlreadyActivated) {
672
+ params.valid = false;
673
+ params.reason += 'Tx type not yet activated ';
674
+ }
675
+
676
+ const is = await Validity.isActivated(params.block,txid,6)
677
+ console.log(is)
678
+ if (!is) {
679
+ params.valid = false;
680
+ params.reason = 'Transaction type activated after tx';
681
+ }
682
+
683
+ if (!(typeof sender === 'string')) {
684
+ params.valid = false;
685
+ params.reason += 'Invalid from address; ';
686
+ }
687
+
688
+ if(params.offeredPropertyId==2||params.offeredPropertyId==3||params.desiredPropertyId==2||params.desiredPropertyId==3){
689
+ params.valid = false
690
+ params.reason += "Cannot have orderbooks for untradeable vesting tokens"
691
+ }
692
+
693
+ if(params.isContract==false){
694
+ key = params.offeredPropertyId+'-'+params.desiredPropertyId
695
+ // Validate offered property ID
696
+ if (params.offeredPropertyId && Number.isInteger(params.offeredPropertyId)) {
697
+ const propertyExists = await PropertyList.getPropertyData(params.offeredPropertyId);
698
+ if (!propertyExists) {
699
+ params.valid = false;
700
+ params.reason += 'Invalid offered property ID; ';
701
+ }
702
+ } else {
703
+ params.valid = false;
704
+ params.reason += 'Invalid offered property ID; ';
705
+ }
706
+
707
+ // Validate desired property ID
708
+ if (params.desiredPropertyId && Number.isInteger(params.desiredPropertyId)) {
709
+ const propertyExists = await PropertyList.getPropertyData(params.desiredPropertyId);
710
+ if (!propertyExists) {
711
+ params.valid = false;
712
+ params.reason += 'Invalid desired property ID; ';
713
+ }
714
+ } else {
715
+ params.valid = false;
716
+ params.reason += 'Invalid desired property ID; ';
717
+ }
718
+ }
719
+
720
+ if (params.isContract) {
721
+ key= params.offeredPropertyId
722
+ //console.log('cancelling contract order '+JSON.stringify(params) + '')
723
+ // Check the validity of the contract ID
724
+ if (params.offeredPropertyId && Number.isInteger(params.offeredPropertyId)) {
725
+ console.log('calling get contract Info in validate cancel'+params.block)
726
+ const contractExists = await ContractRegistry.getContractInfo(params.offeredPropertyId);
727
+ console.log('checking contract data for isContract cancel '+params.offeredPropertyId+' '+JSON.stringify(contractExists))
728
+ if (!contractExists) {
729
+ params.valid = false;
730
+ params.reason += 'Invalid contract ID; ';
731
+ }
732
+ } else {
733
+ params.valid = false;
734
+ params.reason += 'Invalid contract ID; ';
735
+ }
736
+ }
737
+
738
+ // Check if the sender has orders in the relevant orderbook
739
+ const orderbook = await Orderbook.getOrderbookInstance(key)
740
+ let senderOrders
741
+
742
+ if(params.isContract){
743
+ senderOrders = orderbook.getOrdersForAddress(params.fromAddress, params.contractId);
744
+ }else{
745
+ senderOrders = orderbook.getOrdersForAddress(params.fromAddress, null, params.offeredPropertyId, params.desiredPropertyId)
746
+ }
747
+
748
+ if (senderOrders.length === 0) {
749
+ params.valid = false;
750
+ params.reason += 'No orders found for the sender in the relevant orderbook; ';
751
+ }
752
+
753
+ if (!(typeof params.cancelParams === 'object')) {
754
+ params.valid = false;
755
+ params.reason += 'Invalid cancel parameters; ';
756
+ } else {
757
+ if (params.cancelParams.price && typeof params.cancelParams.price !== 'number') {
758
+ params.valid = false;
759
+ params.reason += 'Invalid price parameter; ';
760
+ }
761
+
762
+ if (params.cancelParams.side && !['buy', 'sell'].includes(params.cancelParams.side)) {
763
+ params.valid = false;
764
+ params.reason += 'Invalid side parameter; ';
765
+ }
766
+
767
+ if (params.cancelParams.txid) {
768
+ params.valid = false;
769
+ params.reason += 'TxId parameter deprecated for now. ; ';
770
+ }
771
+ }
772
+
773
+ return params;
774
+ },
775
+
776
+ // 7: Create Whitelist
777
+ validateCreateWhitelist: async (sender, params, txid) => {
778
+ params.reason = '';
779
+ params.valid = true;
780
+
781
+ const isAlreadyActivated = await activationInstance.isTxTypeActive(7);
782
+ if(isAlreadyActivated==false){
783
+ params.valid=false
784
+ params.reason += 'Tx type not yet activated '
785
+ }
786
+
787
+ const is = await Validity.isActivated(params.block,txid,7)
788
+ console.log(is)
789
+ if (!is) {
790
+ params.valid = false;
791
+ params.reason = 'Transaction type activated after tx';
792
+ }
793
+
794
+ if (!(params.backupAddress && typeof params.backupAddress === 'string')) {
795
+ params.valid = false;
796
+ params.reason += 'Invalid backup address; ';
797
+ }
798
+
799
+ if(!validateAddress(params.backupAddress)){
800
+ const valid = await TxUtils.validateAddressWrapper(params.backupAddress)
801
+ if(!valid.isValid){
802
+ params.valid= false
803
+ params.reason = 'Destination address is not validly formed.'
804
+ }
805
+ }
806
+
807
+ if (!(typeof params.name === 'string')) {
808
+ params.valid = false;
809
+ params.reason += 'Invalid name; ';
810
+ }
811
+
812
+ return params;
813
+ },
814
+
815
+ validateUpdateAdmin: async (sender, params, txid) => {
816
+ params.reason = '';
817
+ params.valid = true;
818
+
819
+ const isAlreadyActivated = await activationInstance.isTxTypeActive(8);
820
+ if (!isAlreadyActivated) {
821
+ params.valid = false;
822
+ params.reason += 'Tx type not yet activated; ';
823
+ }
824
+
825
+ const is = await Validity.isActivated(params.block,txid,8)
826
+ console.log(is)
827
+ if (!is) {
828
+ params.valid = false;
829
+ params.reason = 'Transaction type activated after tx';
830
+ }
831
+
832
+ if (!(typeof params.newAddress === 'string')) {
833
+ params.valid = false;
834
+ params.reason += 'Invalid new address; ';
835
+ }
836
+
837
+ // Validate admin based on the type
838
+ if (params.whitelist) {
839
+ const whitelistInfo = await ClearList.getList(params.id);
840
+ if (whitelistInfo.adminAddress !== sender||whitelistInfo.backupAddress!==sender) {
841
+ params.valid = false;
842
+ params.reason += 'Sender is not the admin of the whitelist; ';
843
+ }
844
+ }
845
+
846
+ if(params.oracle) {
847
+ const admin = await OracleList.isAdmin(sender, params.id);
848
+ if (!oracleInfo || oracleInfo.adminAddress !== sender||oracleInfo.backupAddress!==sender) {
849
+ params.valid = false;
850
+ params.reason += 'Sender is not the admin of the oracle; ';
851
+ }
852
+ }
853
+
854
+ if(params.token) {
855
+ const tokenInfo = await PropertyList.getPropertyData(params.id)
856
+ if (tokenInfo.issuer !== sender||tokenInfo.backupAddress!==sender){
857
+ params.valid = false;
858
+ params.reason += 'Sender is not the admin of the token;'
859
+ }
860
+
861
+ if(tokenInfo.type!==2){
862
+ params.valid = false
863
+ params.reason += "Not a managed token with a usable admin address"
864
+ }
865
+ }
866
+
867
+ if(!validateAddress(params.newAddress)){
868
+ const valid = await TxUtils.validateAddressWrapper(params.newAddress)
869
+ if(!valid.isvalid){
870
+ params.valid= false
871
+ params.reason = 'Destination address is not validly formed.'
872
+ }
873
+ }
874
+
875
+ return params;
876
+ },
877
+
878
+ // 9: Issue Attestation
879
+ validateIssueOrRevokeAttestation: async (sender, params, txid) => {
880
+ params.reason = '';
881
+ params.valid = true;
882
+
883
+ const isAlreadyActivated = await activationInstance.isTxTypeActive(9);
884
+ if (!isAlreadyActivated) {
885
+ params.valid = false;
886
+ params.reason += 'Tx type not yet activated; ';
887
+ }
888
+
889
+ const is = await Validity.isActivated(params.block,txid,9)
890
+ console.log(is)
891
+ if (!is) {
892
+ params.valid = false;
893
+ params.reason = 'Transaction type activated after tx';
894
+ }
895
+
896
+ if (typeof params.targetAddress !== 'string') {
897
+ params.valid = false;
898
+ params.reason += 'Invalid target address; ';
899
+ }
900
+
901
+ // Fetch the clearlistId from params or wherever it's stored
902
+ const clearlistId = params.id;
903
+
904
+ // Assuming ClearList or an equivalent instance is available
905
+ console.log('this clearlist id' +clearlistId)
906
+ const clearlist = await ClearList.getClearlistById(clearlistId); // Implement this method as per your clearlist management logic
907
+ console.log('testing logic in attest validity '+Boolean(!clearlist)+Boolean(clearlistId!=0))
908
+ if (!clearlist&&clearlistId!=0) {
909
+ params.valid = false;
910
+ params.reason += `Clearlist with ID ${clearlistId} not found; `;
911
+ } else if(clearlistId!=0){
912
+ // Check if the sender matches the admin address of the clearlist
913
+ if (sender !== clearlist.adminAddress) {
914
+ params.valid = false;
915
+ params.reason += `Sender ${sender} is not authorized to issue or revoke attestations for clearlist ${clearlistId}; `;
916
+ }
917
+ }
918
+ console.log('params in validate attestation '+sender+' '+params.targetAddress)
919
+ if(sender!=params.targetAddress&&clearlistId==0){
920
+ params.valid = false;
921
+ params.reason += `Sender and target address must be the same for self-cert (clearlist id 0) `;
922
+ }
923
+
924
+ if(params.revoke==true&&!ClearList.isAddressInClearlist(params.targetAddress)){
925
+ params.valid = false;
926
+ params.reason += `Target Address has no attestation to revoke `;
927
+ }
928
+
929
+ // Additional validation logic can be added here
930
+
931
+ return params;
932
+ },
933
+
934
+
935
+ // 10: AMM Pool Attestation
936
+
937
+ async validateAMMPool(sender, params, txid) {
938
+ params.reason = '';
939
+ params.valid = true;
940
+
941
+ const isAlreadyActivated = await activationInstance.isTxTypeActive(10);
942
+ if (!isAlreadyActivated) {
943
+ params.valid = false;
944
+ params.reason += 'Tx type not yet activated ';
945
+ }
946
+
947
+ if (!Validity.isValidNumber(params.amount) || !Validity.isValidNumber(params.amount2)) {
948
+ params.valid = false;
949
+ params.reason += 'Invalid or missing amount; ';
950
+ return params;
951
+ }
952
+
953
+ const isActive = await Validity.isActivated(params.block, txid, 10);
954
+ if (!isActive) {
955
+ params.valid = false;
956
+ params.reason = 'Transaction type activated after tx';
957
+ }
958
+
959
+ if (typeof params.targetAddress !== 'string') {
960
+ params.valid = false;
961
+ params.reason += 'Invalid target address; ';
962
+ }
963
+
964
+ const propertyData1 = await PropertyList.getPropertyData(params.id);
965
+ const propertyData2 = await PropertyList.getPropertyData(params.id2);
966
+ if (propertyData1 === 2 || propertyData1 === 3 || propertyData2 === 2 || propertyData2 === 3) {
967
+ params.valid = false;
968
+ params.reason = "Cannot trade vesting tokens";
969
+ }
970
+
971
+ // Whitelist validation
972
+ const senderWhitelists = [].concat(propertyData1.whitelistId || []);
973
+ const desiredLists = [].concat(propertyData2.whitelistId || []);
974
+
975
+ for (const whitelistId of senderWhitelists) {
976
+ const whitelisted = await ClearList.isAddressInClearlist(whitelistId, sender);
977
+ if (!whitelisted) {
978
+ params.valid = false;
979
+ params.reason += `Sender address not in clearlist for offered token ${whitelistId}; `;
980
+ }
981
+ }
982
+ for (const whitelistId of desiredLists) {
983
+ const whitelisted = await ClearList.isAddressInClearlist(whitelistId, sender);
984
+ if (!whitelisted) {
985
+ params.valid = false;
986
+ params.reason += `Trader address not in clearlist ${whitelistId}; `;
987
+ }
988
+ }
989
+
990
+ // Optional new params
991
+ if (params.strategyBlob) {
992
+ try {
993
+ JSON.parse(params.strategyBlob);
994
+ } catch {
995
+ params.valid = false;
996
+ params.reason += 'Invalid strategyBlob JSON; ';
997
+ }
998
+ }
999
+ if (params.optionsMaker && !Number.isInteger(params.optionsMaker)) {
1000
+ params.valid = false;
1001
+ params.reason += 'optionsMaker must be integer; ';
1002
+ }
1003
+ if (params.optionsTaker && !Number.isInteger(params.optionsTaker)) {
1004
+ params.valid = false;
1005
+ params.reason += 'optionsTaker must be integer; ';
1006
+ }
1007
+
1008
+ return params;
1009
+ },
1010
+
1011
+
1012
+ // 11: Grant Managed Token
1013
+ validateGrantManagedToken: async (sender, params, txid) => {
1014
+ params.reason = '';
1015
+ params.valid = true;
1016
+
1017
+ const isAlreadyActivated = await activationInstance.isTxTypeActive(11);
1018
+ if(isAlreadyActivated==false){
1019
+ params.valid=false
1020
+ params.reason += 'Tx type not yet activated '
1021
+ }
1022
+
1023
+ if (!Validity.isValidNumber(params.amountGranted)) {
1024
+ params.valid = false;
1025
+ params.reason += 'Invalid or missing amount; ';
1026
+ return params;
1027
+ }
1028
+
1029
+ if(!validateAddress(params.addressToGrantTo)){
1030
+ const valid = await TxUtils.validateAddressWrapper(params.addressToGrantTo)
1031
+ if(!valid.isvalid){
1032
+ params.valid= false
1033
+ params.reason = 'Destination address is not validly formed.'
1034
+ }
1035
+ }
1036
+
1037
+ const is = await Validity.isActivated(params.block,txid,11)
1038
+ console.log(is)
1039
+ if (!is) {
1040
+ params.valid = false;
1041
+ params.reason = 'Transaction type activated after tx';
1042
+ }
1043
+
1044
+ const isManagedProperty = PropertyList.isManagedAndAdmin(params.propertyId);
1045
+ if (!isManagedProperty) {
1046
+ params.valid = false;
1047
+ params.reason += 'Property is not of managed type or admin does not match';
1048
+ }
1049
+
1050
+ return params;
1051
+ },
1052
+
1053
+ // 12: Redeem Managed Token
1054
+ validateRedeemManagedToken: async (sender, params, txid) => {
1055
+ params.reason = '';
1056
+ params.valid = true;
1057
+
1058
+ const isAlreadyActivated = await activationInstance.isTxTypeActive(12);
1059
+ if(isAlreadyActivated==false){
1060
+ params.valid=false
1061
+ params.reason += 'Tx type not yet activated '
1062
+ }
1063
+
1064
+ if (!Validity.isValidNumber(params.amountDestroyed)) {
1065
+ params.valid = false;
1066
+ params.reason += 'Invalid or missing amount; ';
1067
+ return params;
1068
+ }
1069
+
1070
+ const is = await Validity.isActivated(params.block,txid,12)
1071
+ console.log(is)
1072
+ if (!is) {
1073
+ params.valid = false;
1074
+ params.reason = 'Transaction type activated after tx';
1075
+ }
1076
+
1077
+ const isPropertyAdmin = PropertyList.isAdmin(params.senderAddress, params.propertyId);
1078
+ if (!isPropertyAdmin) {
1079
+ params.valid = false;
1080
+ params.reason += 'Sender is not admin of the property; ';
1081
+ }
1082
+
1083
+ const isManagedProperty = PropertyList.isManagedProperty(params.propertyId);
1084
+ if (!isManagedProperty) {
1085
+ params.valid = false;
1086
+ params.reason += 'Property is not of managed type; ';
1087
+ }
1088
+
1089
+ const canRedeemTokens = TallyMap.canRedeemTokens(params.senderAddress, params.propertyId, params.amount);
1090
+ if (!canRedeemTokens) {
1091
+ params.valid = false;
1092
+ params.reason += 'Cannot redeem tokens; insufficient balance or other criteria not met; ';
1093
+ }
1094
+
1095
+ return params;
1096
+ },
1097
+
1098
+ // 13: Create Oracle
1099
+ validateCreateOracle: async (sender, params, txid) => {
1100
+ params.reason = '';
1101
+ params.valid = true
1102
+
1103
+ const isAlreadyActivated = await activationInstance.isTxTypeActive(13);
1104
+ if(isAlreadyActivated==false){
1105
+ params.valid=false
1106
+ params.reason += 'Tx type not yet activated '
1107
+ }
1108
+
1109
+ const is = await Validity.isActivated(params.block,txid,13)
1110
+ console.log(is)
1111
+ if (!is) {
1112
+ params.valid = false;
1113
+ params.reason = 'Transaction type activated after tx';
1114
+ }
1115
+
1116
+ return params;
1117
+ },
1118
+
1119
+ // 14: Publish Oracle Data
1120
+ validatePublishOracleData: async (sender, params, txid) => {
1121
+ params.reason = '';
1122
+ params.valid = await OracleList.isAdmin(sender, params.oracleId);
1123
+ console.log('is oracle admin '+params.valid + ' ' + params.oracleId)
1124
+ if (params.valid==false) {
1125
+ params.reason = 'Sender is not admin of the specified oracle; ';
1126
+ }
1127
+
1128
+ if (!Validity.isValidNumber(params.price)) {
1129
+ params.valid = false;
1130
+ params.reason += 'Price is not a valid number; ';
1131
+ }
1132
+ if ('high' in params && !Validity.isValidNumber(params.high)) {
1133
+ params.valid = false;
1134
+ params.reason += 'High is not a valid number; ';
1135
+ }
1136
+ if ('low' in params && !Validity.isValidNumber(params.low)) {
1137
+ params.valid = false;
1138
+ params.reason += 'Low is not a valid number; ';
1139
+ }
1140
+ if ('close' in params && !Validity.isValidNumber(params.close)) {
1141
+ params.valid = false;
1142
+ params.reason += 'Close is not a valid number; ';
1143
+ }
1144
+
1145
+
1146
+ const isAlreadyActivated = await activationInstance.isTxTypeActive(14);
1147
+ if(isAlreadyActivated==false){
1148
+ params.valid=false
1149
+ params.reason += 'Tx type not yet activated '
1150
+ }
1151
+
1152
+ const is = await Validity.isActivated(params.block,txid,14)
1153
+ console.log(is)
1154
+ if (!is) {
1155
+ params.valid = false;
1156
+ params.reason = 'Transaction type activated after tx';
1157
+ }
1158
+ // Retrieve the oracle instance using its ID
1159
+ const oracle = await OracleList.getOracleInfo(params.oracleId);
1160
+ if (!oracle) {
1161
+ params.valid = false
1162
+ params.reason += 'Oracle not found; ';
1163
+ }
1164
+
1165
+ return params;
1166
+ },
1167
+
1168
+ // 15: Close Oracle
1169
+ validateCloseOracle: async (sender, params, txid) => {
1170
+ params.reason = '';
1171
+ params.valid = OracleList.isAdmin(sender, params.oracleId);
1172
+ if (!params.valid) {
1173
+ params.reason = 'Sender is not admin of the specified oracle; ';
1174
+ }
1175
+ const isAlreadyActivated = await activationInstance.isTxTypeActive(15);
1176
+ if(isAlreadyActivated==false){
1177
+ params.valid=false
1178
+ params.reason += 'Tx type not yet activated '
1179
+ }
1180
+
1181
+ const is = await Validity.isActivated(params.block,txid,14)
1182
+ console.log(is)
1183
+ if (!is) {
1184
+ params.valid = false;
1185
+ params.reason = 'Transaction type activated after tx';
1186
+ }
1187
+
1188
+ return params;
1189
+ },
1190
+
1191
+ //16: Create Contracts
1192
+ validateCreateContractSeries: async (sender, params, txid) => {
1193
+ params.valid = true;
1194
+ params.reason = '';
1195
+
1196
+ // Check if the underlyingOracleId exists or is null
1197
+ if (params.native === false) {
1198
+ const validOracle = await OracleList.getOracleInfo(params.underlyingOracleId) !== null;
1199
+ if (!validOracle) {
1200
+ params.valid = false;
1201
+ params.reason += "Invalid or missing underlying oracle ID. ";
1202
+ }
1203
+ }
1204
+
1205
+ const isAlreadyActivated = await activationInstance.isTxTypeActive(16);
1206
+ if(isAlreadyActivated==false){
1207
+ params.valid=false
1208
+ params.reason += 'Tx type not yet activated '
1209
+ }
1210
+
1211
+ const is = await Validity.isActivated(params.block,txid,16)
1212
+ console.log(is)
1213
+ if (!is) {
1214
+ params.valid = false;
1215
+ params.reason = 'Transaction type activated after tx';
1216
+ }
1217
+
1218
+ // Check if collateralPropertyId is a valid existing propertyId
1219
+ const validCollateralProperty = await PropertyList.getPropertyData(params.collateralPropertyId) !== null;
1220
+ if (!validCollateralProperty) {
1221
+ params.valid = false;
1222
+ params.reason += "Invalid collateral property ID. ";
1223
+ }
1224
+
1225
+ // On-Chain Data Validation
1226
+ if (params.native === true && params.onChainData) {
1227
+ let validNatives = true;
1228
+
1229
+ let isDuplicate = await ContractRegistry.isDuplicateNativeContract(params.collateralPropertyId,params.onChainData, params.notionalPropertyId)
1230
+ console.log('is dupe ' +isDuplicate)
1231
+ if(isDuplicate){
1232
+ params.valid = false;
1233
+ params.reason += "Collateral or on-chain pair is redundant.";
1234
+ }
1235
+
1236
+ for (const pid of params.onChainData) {
1237
+ let propertyData1 = PropertyList.getPropertyData(pid[0])
1238
+ let propertyData2 = PropertyList.getPropertyData(pid[1])
1239
+ console.log('validating propertyids '+pid)
1240
+ if (pid[0] !== null && propertyData1==null) {
1241
+ validNatives = false;
1242
+ break;
1243
+ }
1244
+ if (pid[1] !== null && propertyData2==null) {
1245
+ validNatives = false;
1246
+ break;
1247
+ }
1248
+ }
1249
+ if (!validNatives) {
1250
+ params.valid = false;
1251
+ params.reason += "Invalid on-chain data format or property IDs. ";
1252
+ }
1253
+ if(params.onChainData.length==0){
1254
+ params.valid = false;
1255
+ params.reason += "Array of on-chain pairs for native settlement data is empty.";
1256
+ }
1257
+ }
1258
+
1259
+ const isVEST= (parseInt(params.collateralPropertyId)==2&&parseInt(params.notionalPropertyId)==2||parseInt(params.collateralPropertyId)==3)
1260
+ if(isVEST){
1261
+ params.valid =false
1262
+ params.reason += "Vesting tokens cannot be used as collateral or hedged"
1263
+ }
1264
+
1265
+ // Check if notionalPropertyId exists or is null (for oracle contracts)
1266
+ if (params.notionalPropertyId !== null&&params.native==true) {
1267
+ const validNotionalProperty = await PropertyList.getPropertyData(params.notionalPropertyId) !== null;
1268
+ if (!validNotionalProperty) {
1269
+ params.valid = false;
1270
+ params.reason += "Invalid notional property ID. ";
1271
+ }
1272
+ }
1273
+
1274
+ // Check if notionalValue is a number
1275
+ if (typeof params.notionalValue !== 'number'||params.notionalValue ==0) {
1276
+ params.valid = false;
1277
+ params.reason += "Notional value must be a non-zero number. ";
1278
+ }
1279
+
1280
+ // Check if expiryPeriod is an integer
1281
+ if (!Number.isInteger(params.expiryPeriod)) {
1282
+ params.valid = false;
1283
+ params.reason += "Expiry period must be an integer. ";
1284
+ }
1285
+
1286
+ // Check if series is a valid integer
1287
+ if (!Number.isInteger(params.series)) {
1288
+ params.valid = false;
1289
+ params.reason += "Series must be an integer. ";
1290
+ }
1291
+
1292
+ // Validate inverse and fee as booleans
1293
+ if (typeof params.inverse !== 'boolean') {
1294
+ params.valid = false;
1295
+ params.reason += "Inverse must be a boolean. ";
1296
+ }
1297
+
1298
+ if (typeof params.fee !== 'boolean') {
1299
+ params.valid = false;
1300
+ params.reason += "Fee must be a boolean. ";
1301
+ }
1302
+
1303
+ if (!params.valid) {
1304
+ console.log(`Contract series validation failed: ${params.reason}`);
1305
+ }
1306
+
1307
+ return params;
1308
+ },
1309
+
1310
+
1311
+ // 17: Exercise Derivative
1312
+ validateExerciseDerivative: async (sender, params, txid) => {
1313
+ params.reason = '';
1314
+ params.valid = true;
1315
+
1316
+ const isAlreadyActivated = await activationInstance.isTxTypeActive(17);
1317
+ if(isAlreadyActivated==false){
1318
+ params.valid=false
1319
+ params.reason += 'Tx type not yet activated '
1320
+ }
1321
+
1322
+ const is = await Validity.isActivated(params.block,txid,17)
1323
+ console.log(is)
1324
+ if (!is) {
1325
+ params.valid = false;
1326
+ params.reason = 'Transaction type activated after tx';
1327
+ }
1328
+
1329
+ const isValidDerivative = derivativeRegistry.isValidDerivative(params.contractId);
1330
+ if (!isValidDerivative) {
1331
+ params.valid = false;
1332
+ params.reason += 'Invalid derivative contract; ';
1333
+ }
1334
+
1335
+ const canExercise = marginMap.canExercise(params.senderAddress, params.contractId, params.amount);
1336
+ if (!canExercise) {
1337
+ params.valid = false;
1338
+ params.reason += 'Cannot exercise derivative; insufficient contracts or margin; ';
1339
+ }
1340
+
1341
+ return params;
1342
+ },
1343
+
1344
+ isTradePriceWithinLeverageBounds({
1345
+ tradePrice,
1346
+ markPrice,
1347
+ leverage,
1348
+ bufferBps = 65
1349
+ }) {
1350
+ const BigNumber = require('bignumber.js');
1351
+ console.log('trade price, mark, lev '+tradePrice+' '+markPrice+' '+leverage)
1352
+ if (
1353
+ tradePrice == null ||
1354
+ markPrice == null ||
1355
+ !leverage ||
1356
+ leverage <= 0
1357
+ ) {
1358
+ return {
1359
+ valid: false,
1360
+ reason: 'Missing tradePrice, markPrice, or leverage'
1361
+ };
1362
+ }
1363
+
1364
+ const priceBN = new BigNumber(tradePrice);
1365
+ const markBN = new BigNumber(markPrice);
1366
+
1367
+ if (!priceBN.isFinite() || !markBN.isFinite() || markBN.lte(0)) {
1368
+ return {
1369
+ valid: false,
1370
+ reason: 'Invalid numeric values for price or mark'
1371
+ };
1372
+ }
1373
+
1374
+ // |P - M| / M
1375
+ const deviation = priceBN.minus(markBN).abs().div(markBN);
1376
+
1377
+ // 1 / leverage
1378
+ const maxMove = new BigNumber(1).div(leverage).div(2);
1379
+
1380
+ // buffer in decimal (65 bps = 0.0065)
1381
+ const buffer = new BigNumber(bufferBps).div(10_000);
1382
+
1383
+ const allowed = maxMove.minus(buffer);
1384
+
1385
+ if (allowed.lte(0)) {
1386
+ return {
1387
+ valid: false,
1388
+ reason: 'Leverage buffer exceeds allowable price movement'
1389
+ };
1390
+ }
1391
+
1392
+ if (deviation.gte(allowed)) {
1393
+ return {
1394
+ valid: false,
1395
+ reason:
1396
+ `Trade price deviates ${(deviation.times(100)).toFixed(2)}% ` +
1397
+ `from mark; max allowed ${(allowed.times(100)).toFixed(2)}%`
1398
+ };
1399
+ }
1400
+
1401
+ return { valid: true };
1402
+ },
1403
+
1404
+ // 18: Trade Contract On-chain
1405
+ async validateTradeContractOnchain(sender, params, txid){
1406
+ params.reason = '';
1407
+ params.valid = true;
1408
+ console.log('validating contract trade '+JSON.stringify(params))
1409
+ console.log('calling get contract Info in validate trade'+params.contractId)
1410
+ const contractDetails = await ContractRegistry.getContractInfo(params.contractId);
1411
+ console.log('checking contract details validity ' + JSON.stringify(contractDetails))
1412
+
1413
+ const isAlreadyActivated = await activationInstance.isTxTypeActive(18);
1414
+ if(isAlreadyActivated==false){
1415
+ params.valid=false
1416
+ params.reason += 'Tx type not yet activated '
1417
+ }
1418
+
1419
+ if (!Validity.isValidNumber(params.price)) {
1420
+ params.valid = false;
1421
+ params.reason += 'Price is not a valid number; ';
1422
+ }
1423
+
1424
+ const hasRef = await Validity.hasReferencePrice(
1425
+ params.contractId,
1426
+ params.block
1427
+ );
1428
+ console.log('hasRef '+hasRef)
1429
+
1430
+ if (!hasRef) {
1431
+ params.valid = false;
1432
+ params.reason += 'No reference price exists for contract at this block; ';
1433
+ return params;
1434
+ }
1435
+
1436
+ const priceCheck = Validity.isTradePriceWithinLeverageBounds({
1437
+ tradePrice: params.price,
1438
+ markPrice:hasRef,
1439
+ leverage: contractDetails.leverage,
1440
+ bufferBps: 65
1441
+ });
1442
+
1443
+ if (!priceCheck.valid) {
1444
+ params.valid = false;
1445
+ params.reason += priceCheck.reason + '; ';
1446
+ return params;
1447
+ }
1448
+
1449
+
1450
+
1451
+ if(sender==null){
1452
+ params.valid=false
1453
+ params.reason += "Sender is null"
1454
+ }
1455
+
1456
+ const is = await Validity.isActivated(params.block,txid,18)
1457
+ console.log(is)
1458
+ if (!is) {
1459
+ params.valid = false;
1460
+ params.reason = 'Transaction type activated after tx';
1461
+ }
1462
+
1463
+ if(contractDetails==null||contractDetails=={}){
1464
+ params.valid=false
1465
+ params.reason+= "contractId not found"
1466
+ return params
1467
+ }
1468
+ const admin = activationInstance.getAdmin()
1469
+ console.log('admin '+admin)
1470
+ if(sender!=admin){
1471
+ let bans = await ClearList.getBanlist()
1472
+ if(bans==null){bans = bannedCountries}
1473
+ const senderCountryInfo = await ClearList.getCountryCodeByAddress(sender);
1474
+ if (!senderCountryInfo || bans.includes(senderCountryInfo.countryCode)) {
1475
+ params.valid = false;
1476
+ params.reason += 'Sender cannot trade contracts from a banned country or lacking country code attestation';
1477
+ }
1478
+ }
1479
+
1480
+ const MarginMap = require('./marginMap.js')
1481
+ const marginMap = await MarginMap.loadMarginMap(params.contractId);
1482
+ console.log(params.contractId, params.price)
1483
+ const initialMarginPerContract = await ContractRegistry.getInitialMargin(params.contractId, params.price);
1484
+ console.log('init margin '+initialMarginPerContract)
1485
+ const amountBN = new BigNumber(params.amount);
1486
+ let totalInitialMargin = BigNumber(initialMarginPerContract).times(amountBN).toNumber();
1487
+
1488
+ const existingPosition = await marginMap.getPositionForAddress(sender, params.contractId);
1489
+ console.log('existing position to see if we reduce '+JSON.stringify(existingPosition))
1490
+ // Determine if the trade reduces the position size for buyer or seller
1491
+ console.log('flag values '+existingPosition.contracts+' '+params.sell)
1492
+ const isBuyerReducingPosition = Boolean(existingPosition.contracts < 0 &&params.sell==false);
1493
+ const isSellerReducingPosition = Boolean(existingPosition.contracts > 0 && params.sell==true);
1494
+ console.log(' reduce flags buyer/seller '+isBuyerReducingPosition+' '+isSellerReducingPosition)
1495
+
1496
+ const isBuyerFlippingPosition = Boolean(params.amount>Math.abs(existingPosition.contracts)&&existingPosition.contracts<0&&params.sell==false)
1497
+ const isSellerFlippingPosition = Boolean(params.amount>existingPosition.contracts&&existingPosition.contracts>0&&params.sell==true)
1498
+
1499
+ let flipLong = 0
1500
+ let flipShort = 0
1501
+
1502
+ console.log('flips? buy/sell'+isBuyerFlippingPosition+' '+isSellerFlippingPosition)
1503
+ console.log('params for flip flags '+params.amount+' '+existingPosition.contracts+' '+Math.abs(existingPosition.contracts))
1504
+ if(isBuyerFlippingPosition){
1505
+ flipLong=params.amount-Math.abs(existingPosition.contracts)
1506
+ totalInitialMargin = BigNumber(initialMarginPerContract).times(flipLong).toNumber();
1507
+ }else if(isSellerFlippingPosition){
1508
+ flipShort=params.amount-existingPosition.contracts
1509
+ totalInitialMargin = BigNumber(initialMarginPerContract).times(flipShort).toNumber();
1510
+ }
1511
+
1512
+ if((isBuyerReducingPosition==false&&isSellerReducingPosition==false)||isBuyerFlippingPosition||isSellerFlippingPosition){
1513
+
1514
+ // Check if the sender has enough balance for the initial margin
1515
+ console.log('about to call hasSufficientBalance in validateTradeContractOnchain '+sender, contractDetails.collateralPropertyId, totalInitialMargin)
1516
+ const hasSufficientBalance = await TallyMap.hasSufficientBalance(sender, contractDetails.collateralPropertyId, totalInitialMargin);
1517
+ console.log('need to adjust trade? '+JSON.stringify(hasSufficientBalance))
1518
+ if (hasSufficientBalance.hasSufficient==false) {
1519
+ let contractUndo = BigNumber(hasSufficientBalance.shortfall)
1520
+ .dividedBy(initialMarginPerContract)
1521
+ .decimalPlaces(0, BigNumber.ROUND_CEIL)
1522
+ .toNumber();
1523
+
1524
+ params.amount -= contractUndo;
1525
+
1526
+ if(params.amount==0){
1527
+ console.log('Insufficient balance for initial margin');
1528
+ params.valid=false
1529
+ params.reason+= "Insufficient balance for initial margin"
1530
+ }
1531
+ }
1532
+ }
1533
+
1534
+ hasSufficientBalance = await TallyMap.hasSufficientBalance(params.senderAddress, contractDetails.collateralPropertyId, totalInitialMargin)
1535
+ console.log('dbl tap has sufficient '+JSON.stringify(hasSufficientBalance))
1536
+ if(hasSufficientBalance.hasSufficient==false){
1537
+
1538
+ }
1539
+
1540
+ const collateralPropertyId = contractDetails.collateralPropertyId;
1541
+ console.log('clearlist id '+contractDetails.whitelist)
1542
+ if(collateralPropertyId!=1&&contractDetails.whitelist!=undefined&&contractDetails.whitelist!=0&&contractDetails.whitelist!=null){
1543
+ console.log(collateralPropertyId)
1544
+ // Get property data for the collateralPropertyId
1545
+ const collateralPropertyData = await PropertyList.getPropertyData(collateralPropertyId);
1546
+ if (collateralPropertyData == null || collateralPropertyData == undefined) {
1547
+ params.valid = false;
1548
+ params.reason += 'Collateral propertyId not found in Property List; ';
1549
+ }
1550
+
1551
+ // Extract whitelist IDs from the collateral property data
1552
+ const senderWhitelists = Array.isArray(collateralPropertyData.whitelistId) ? collateralPropertyData.whitelistId : [collateralPropertyData.whitelistId];
1553
+ // Check if the sender address is in the whitelists
1554
+ var listed = false
1555
+ for (const whitelistId of senderWhitelists) {
1556
+ const senderWhitelisted = await ClearList.isAddressInClearlist(whitelistId, sender);
1557
+ if (senderWhitelisted) {
1558
+ listed=true
1559
+ break; // No need to check further if one fails
1560
+ }
1561
+ }
1562
+ if(!listed){
1563
+ params.valid = false;
1564
+ params.reason += `Sender address not in clearlist; `;
1565
+ }
1566
+ }
1567
+ console.log('finished contract trade params '+params.valid+' '+params.reason)
1568
+ return params;
1569
+
1570
+ },
1571
+
1572
+ // 19: Trade Contract Channel
1573
+ async validateTradeContractChannel(sender, params,txid){
1574
+ params.reason = '';
1575
+ params.valid = true;
1576
+
1577
+ console.log('calling get contract Info in validate channel trade'+params.block)
1578
+ const contractDetails = await ContractRegistry.getContractInfo(params.contractId);
1579
+
1580
+ if(params.amount < 1){
1581
+ params.valid=false
1582
+ params.reason += 'Contract amount must be a whole integer'
1583
+ }
1584
+
1585
+ if (!Validity.isValidNumber(params.price)) {
1586
+ params.valid = false;
1587
+ params.reason += 'Price is not a valid number; ';
1588
+ }
1589
+
1590
+ params.amount=Math.floor(params.amount)
1591
+ const isAlreadyActivated = await activationInstance.isTxTypeActive(19);
1592
+ if(isAlreadyActivated==false){
1593
+ params.valid=false
1594
+ params.reason += 'Tx type not yet activated '
1595
+ }
1596
+
1597
+ const hasRef = await Validity.hasReferencePrice(
1598
+ params.contractId,
1599
+ params.block
1600
+ );
1601
+
1602
+ if (!hasRef) {
1603
+ params.valid = false;
1604
+ params.reason += 'No reference price exists for contract at this block; ';
1605
+ return params;
1606
+ }
1607
+
1608
+ const priceCheck = Validity.isTradePriceWithinLeverageBounds({
1609
+ tradePrice: params.price,
1610
+ markPrice: hasRef,
1611
+ leverage: contractDetails.leverage,
1612
+ bufferBps: 65
1613
+ });
1614
+
1615
+ if (!priceCheck.valid) {
1616
+ params.valid = false;
1617
+ params.reason += priceCheck.reason + '; ';
1618
+ return params;
1619
+ }
1620
+
1621
+
1622
+ const is = await Validity.isActivated(params.block,txid,19)
1623
+ console.log(is)
1624
+ if (!is) {
1625
+ params.valid = false;
1626
+ params.reason = 'Transaction type activated after tx';
1627
+ }
1628
+
1629
+ if(params.expiryBlock<params.block||params.expiryBlock==undefined){
1630
+ params.valid=false
1631
+ params.reason = "Tx confirmed in block later than expiration block"
1632
+ return params
1633
+ }
1634
+
1635
+ if(sender==null){
1636
+ params.valid=false
1637
+ params.reason += "Sender is null"
1638
+ }
1639
+
1640
+ const channel = await Channels.getChannel(sender)
1641
+ console.log('checking inside validate validateTradeContractChannel '+JSON.stringify(params))
1642
+ console.log('checking channel' +JSON.stringify(channel))
1643
+ const { commitAddressA, commitAddressB } = await Channels.getCommitAddresses(sender);
1644
+ if(commitAddressA==null&&commitAddressB==null){
1645
+ params.valid=false
1646
+ params.reason = "Tx sender is not found to be a channel address"
1647
+ console.log('exiting contract channel validity for lack of commit addr '+JSON.stringify(params))
1648
+ return params
1649
+ }
1650
+ const admin = activationInstance.getAdmin()
1651
+ if(sender!=admin){
1652
+ let bans = await ClearList.getBanlist()
1653
+ if(bans==null){bans = bannedCountries}
1654
+ const senderCountryInfo = await ClearList.getCountryCodeByAddress(commitAddressA);
1655
+ if (!senderCountryInfo || bans.includes(senderCountryInfo.countryCode)) {
1656
+ params.valid = false;
1657
+ params.reason += 'Commiter A cannot handle TL or TLI from a banned country or lacking country code attestation';
1658
+ }
1659
+ const BCountryInfo = await ClearList.getCountryCodeByAddress(commitAddressB);
1660
+ if (!BCountryInfo || bans.includes(BCountryInfo.countryCode)) {
1661
+ params.valid = false;
1662
+ params.reason += 'Commiter B cannot handle TL or TLI from a banned country or lacking country code attestation';
1663
+ }
1664
+ }
1665
+
1666
+ // --- Channel clearList enforcement ---
1667
+ if (channel.clearLists) {
1668
+ const clearListsA = channel.clearLists.A || [];
1669
+ const clearListsB = channel.clearLists.B || [];
1670
+
1671
+ // If A committed with clearLists, B must be attested in at least one
1672
+ if (clearListsA.length > 0 && commitAddressB) {
1673
+ let bApproved = false;
1674
+ for (const listId of clearListsA) {
1675
+ if (await ClearList.isAddressInClearlistOrDerived(listId, commitAddressB)) {
1676
+ bApproved = true;
1677
+ break;
1678
+ }
1679
+ }
1680
+ if (!bApproved) {
1681
+ params.valid = false;
1682
+ params.reason += `Committer B (${commitAddressB}) not attested in channel clearLists A [${clearListsA}]; `;
1683
+ }
1684
+ }
1685
+
1686
+ // If B committed with clearLists, A must be attested in at least one
1687
+ if (clearListsB.length > 0 && commitAddressA) {
1688
+ let aApproved = false;
1689
+ for (const listId of clearListsB) {
1690
+ if (await ClearList.isAddressInClearlistOrDerived(listId, commitAddressA)) {
1691
+ aApproved = true;
1692
+ break;
1693
+ }
1694
+ }
1695
+ if (!aApproved) {
1696
+ params.valid = false;
1697
+ params.reason += `Committer A (${commitAddressA}) not attested in channel clearLists B [${clearListsB}]; `;
1698
+ }
1699
+ }
1700
+ }
1701
+
1702
+ if (!params.valid) return params;
1703
+
1704
+
1705
+ const collateralPropertyId = contractDetails.collateralPropertyId
1706
+ const initialMarginPerContract = await ContractRegistry.getInitialMargin(params.contractId, params.price);
1707
+ let totalInitialMargin = BigNumber(initialMarginPerContract).times(params.amount).toNumber();
1708
+
1709
+ if(!contractDetails){
1710
+ params.valid=false
1711
+ params.reason = "ContractId not found"
1712
+ console.log('exiting contract channel validity for lack of contractId '+JSON.stringify(params))
1713
+ return params
1714
+ }
1715
+ console.log(JSON.stringify(contractDetails))
1716
+ const collateralIdString = contractDetails.collateralPropertyId.toString()
1717
+ const balanceA = channel.A[collateralIdString]
1718
+ const balanceB = channel.B[collateralIdString]
1719
+ console.log('checking our channel info is correct: A'+balanceA+' B '+balanceB+' commitAddrA '+commitAddressA+' commitAddrB '+commitAddressB)
1720
+
1721
+ const tallyA = await TallyMap.getTally(commitAddressA, collateralPropertyId);
1722
+ const tallyB = await TallyMap.getTally(commitAddressB, collateralPropertyId);
1723
+ const effectiveA = (balanceA || 0) + (tallyA?.available || 0);
1724
+ const effectiveB = (balanceB || 0) + (tallyB?.available || 0);
1725
+
1726
+ const marginMap = await MarginMap.getInstance(params.contractId)
1727
+ const existingPositionA = await marginMap.getPositionForAddress(commitAddressA, params.contractId);
1728
+ const existingPositionB = await marginMap.getPositionForAddress(commitAddressB, params.contractId);
1729
+ // Determine if the trade reduces the position size for buyer or seller
1730
+ let AIsSeller
1731
+ let isBuyerReducingPosition
1732
+ let isSellerReducingPosition
1733
+ console.log('columnA is what now?' +params.columnAIsSeller)
1734
+ console.log('positions '+JSON.stringify(existingPositionA))
1735
+ console.log('positions cont. '+JSON.stringify(existingPositionB))
1736
+ if(params.columnAIsSeller===true||params.columnAIsSeller===1||params.columnAIsSeller==="1"){
1737
+ AIsSeller=true
1738
+ isBuyerReducingPosition= Boolean(existingPositionB.contracts < 0);
1739
+ isSellerReducingPosition = Boolean(existingPositionA.contracts>0)
1740
+ console.log('setting reduce flags '+AIsSeller+' '+isBuyerReducingPosition+' '+isSellerReducingPosition)
1741
+ }else{
1742
+ AIsSeller=false
1743
+ isBuyerReducingPosition= Boolean(existingPositionA.contracts < 0);
1744
+ isSellerReducingPosition = Boolean(existingPositionB.contracts>0)
1745
+ console.log('setting reduce flags '+AIsSeller+' '+isBuyerReducingPosition+' '+isSellerReducingPosition)
1746
+ }
1747
+
1748
+ // --- INITIAL MARGIN CHECKS FOR NON-REDUCING SIDES ---
1749
+ console.log('isBuyerReducingPosition '+isBuyerReducingPosition+' '+balanceA, balanceB, totalInitialMargin)
1750
+ if (!isBuyerReducingPosition&&params.columnAIsSeller) {
1751
+ const res = Validity.hasSufficientChannelMargin(balanceA, totalInitialMargin, "B");
1752
+ if (!res.valid) { params.valid = false; params.reason += res.reason; return params; }
1753
+ }else if(!isBuyerReducingPosition&&!params.columnAIsSeller){
1754
+ const res = Validity.hasSufficientChannelMargin(balanceA, totalInitialMargin, "A");
1755
+ if (!res.valid) { params.valid = false; params.reason += res.reason; return params; }
1756
+ }
1757
+ console.log('isSellerReducingPosition '+isSellerReducingPosition+' '+balanceA, balanceB, totalInitialMargin)
1758
+
1759
+ if (!isSellerReducingPosition&&params.columnAIsSeller) {
1760
+ const res = Validity.hasSufficientChannelMargin(balanceB, totalInitialMargin, "A");
1761
+ if (!res.valid) { params.valid = false; params.reason += res.reason; return params; }
1762
+ }else if(!isSellerReducingPosition&&!params.columnAIsSeller){
1763
+ const res = Validity.hasSufficientChannelMargin(balanceB, totalInitialMargin, "B");
1764
+ if (!res.valid) { params.valid = false; params.reason += res.reason; return params; }
1765
+ }
1766
+
1767
+
1768
+ let enoughMargin
1769
+ if (isBuyerReducingPosition == false && isSellerReducingPosition == false) {
1770
+ // Check if the sender has enough balance for the initial margin
1771
+ enoughMargin = Boolean((effectiveA >= totalInitialMargin) && (effectiveB >= totalInitialMargin))
1772
+ console.log('enough margin both not reducing '+enoughMargin+' '+effectiveA+' '+effectiveB+' '+totalInitialMargin)
1773
+ if (enoughMargin == false) {
1774
+ console.log('Insufficient balance for initial margin');
1775
+ params.valid = false;
1776
+ params.reason += "Insufficient balance for initial margin on both sides";
1777
+ }
1778
+ } else if (isBuyerReducingPosition == true && isSellerReducingPosition == false) {
1779
+ if (AIsSeller == true) {
1780
+ enoughMargin = effectiveA >= totalInitialMargin;
1781
+ } else {
1782
+ enoughMargin = effectiveB >= totalInitialMargin;
1783
+ }
1784
+ if (enoughMargin == false) {
1785
+ console.log('Insufficient balance for initial margin');
1786
+ params.valid = false;
1787
+ params.reason += "Insufficient balance for initial margin on sellSide";
1788
+ }
1789
+ } else if (isBuyerReducingPosition == false && isSellerReducingPosition == true) {
1790
+ if (AIsSeller == true) {
1791
+ enoughMargin = effectiveB >= totalInitialMargin;
1792
+ } else {
1793
+ enoughMargin = effectiveA >= totalInitialMargin;
1794
+ }
1795
+ if (enoughMargin == false) {
1796
+ console.log('Insufficient balance for initial margin');
1797
+ params.valid = false;
1798
+ params.reason += "Insufficient balance for initial margin on buySide";
1799
+ }
1800
+ }
1801
+
1802
+ let isBuyerFlippingPosition
1803
+ let isSellerFlippingPosition
1804
+
1805
+ if(AIsSeller==true){
1806
+ isBuyerFlippingPosition = Boolean(params.amount>Math.abs(existingPositionB.contracts)&&existingPositionB.contracts<0)
1807
+ isSellerFlippingPosition = Boolean(params.amount>existingPositionA.contracts&&existingPositionA.contracts>0)
1808
+ }else{
1809
+ isBuyerFlippingPosition = Boolean(params.amount>Math.abs(existingPositionA.contracts)&&existingPositionA.contracts<0)
1810
+ isSellerFlippingPosition = Boolean(params.amount>existingPositionB.contracts&&existingPositionB.contracts>0)
1811
+ }
1812
+
1813
+ let flipLong = 0
1814
+ let flipShort = 0
1815
+ let AFlipLong
1816
+ let BFlipLong
1817
+ let AFlipShort
1818
+ let BFlipShort
1819
+ let totalInitialMarginFlip = 0
1820
+ if(isBuyerFlippingPosition&&AIsSeller){
1821
+ flipLong=params.amount-Math.abs(existingPositionB.contracts)
1822
+ totalInitialMarginFlip = BigNumber(initialMarginPerContract).times(flipLong).toNumber();
1823
+ BFlipLong = true
1824
+ }else if(isSellerFlippingPosition&&AIsSeller){
1825
+ flipShort=params.amount-existingPositionA.contracts
1826
+ totalInitialMarginFlip = BigNumber(initialMarginPerContract).times(flipShort).toNumber();
1827
+ AFlipShort = true
1828
+ }else if(isBuyerFlippingPosition&&!AIsSeller){
1829
+ flipLong=params.amount-Math.abs(existingPositionA.contracts)
1830
+ totalInitialMargin = BigNumber(initialMarginPerContract).times(flipLong).toNumber();
1831
+ AFlipLong= true
1832
+ }else if(isSellerFlippingPosition&&!AIsSeller){
1833
+ flipShort=params.amount-existingPositionB.contracts
1834
+ totalInitialMargin = BigNumber(initialMarginPerContract).times(flipShort).toNumber();
1835
+ BFlipShort=true
1836
+ }
1837
+
1838
+ // --- FLIP (CROSS/REVERSAL) MARGIN CHECKS ---
1839
+ if (AFlipLong) {
1840
+ const res = Validity.checkFlipMargin(balanceA, flipLong * initialMarginPerContract, "A", "flipLong");
1841
+ if (!res.valid) { params.valid = false; params.reason += res.reason; return params; }
1842
+ }
1843
+ if (AFlipShort) {
1844
+ const res = Validity.checkFlipMargin(balanceA, flipShort * initialMarginPerContract, "A", "flipShort");
1845
+ if (!res.valid) { params.valid = false; params.reason += res.reason; return params; }
1846
+ }
1847
+ if (BFlipLong) {
1848
+ const res = Validity.checkFlipMargin(balanceB, flipLong * initialMarginPerContract, "B", "flipLong");
1849
+ if (!res.valid) { params.valid = false; params.reason += res.reason; return params; }
1850
+ }
1851
+ if (BFlipShort) {
1852
+ const res = Validity.checkFlipMargin(balanceB, flipShort * initialMarginPerContract, "B", "flipShort");
1853
+ if (!res.valid) { params.valid = false; params.reason += res.reason; return params; }
1854
+ }
1855
+
1856
+ if((effectiveA<(flipLong*initialMarginPerContract)&&AFlipLong==true)
1857
+ ||(effectiveA<(flipShort*initialMarginPerContract)&&AFlipShort==true)
1858
+ ||(effectiveB<(flipLong*initialMarginPerContract)&&BFlipLong==true)
1859
+ ||(effectiveB<(flipShort*initialMarginPerContract)&&BFlipShort==true)){
1860
+ let shortfall
1861
+ let doubleFlip = Boolean((AFlipLong&&BFlipShort)||(BFlipLong&&AFlipShort))
1862
+ let shortfall2
1863
+ if(AFlipLong){
1864
+ shortfall=flipLong*initialMarginPerContract-(effectiveA+tallyA.available)
1865
+ }
1866
+ if(AFlipShort){
1867
+ shortfall=flipShort*initialMarginPerContract-(effectiveA+tallyA.available)
1868
+ }
1869
+ if(BFlipShort){
1870
+ if(doubleFlip){
1871
+ shortfall2=flipLong*initialMarginPerContract-(effectiveA+tallyA.available)
1872
+ }
1873
+ shortfall=flipShort*initialMarginPerContract-(effectiveB+tallyB.available)
1874
+ }
1875
+ if(BFlipLong){
1876
+ if(doubleFlip){
1877
+ shortfall2=flipShort*initialMarginPerContract-(effectiveA+tallyA.available)
1878
+ }
1879
+ shortfall=flipLong
1880
+ }
1881
+ if(doubleFlip){
1882
+ shortfall=Math.max(shortfall,shortfall2)
1883
+ }
1884
+ let contractUndo = BigNumber(shortfall)
1885
+ .dividedBy(initialMarginPerContract)
1886
+ .decimalPlaces(0, BigNumber.ROUND_CEIL)
1887
+ .toNumber();
1888
+
1889
+ params.amount -= contractUndo;
1890
+ }
1891
+
1892
+ /*const isAddressAWhitelisted = contractDetails.type === 'oracle' ? await whitelistRegistry.isAddressWhitelisted(commitAddressA, contractDetails.oracleId) : true;
1893
+ if (!isAddressAWhitelisted) {
1894
+ params.valid = false;
1895
+ params.reason += 'Commit address A not whitelisted; ';
1896
+ }
1897
+
1898
+ const isAddressBWhitelisted = contractDetails.type === 'oracle' ? await whitelistRegistry.isAddressWhitelisted(commitAddressB, contractDetails.oracleId) : true;
1899
+ if (!isAddressBWhitelisted) {
1900
+ params.valid = false;
1901
+ params.reason += 'Commit address B not whitelisted; ';
1902
+ }*/
1903
+ console.log('finishing validated channel contract '+JSON.stringify(params))
1904
+
1905
+ return params;
1906
+ },
1907
+
1908
+ async hasReferencePrice(contractId, blockHeight) {
1909
+ console.log('contractId in hasRef '+contractId)
1910
+ const contract = await ContractRegistry.getContractInfo(contractId);
1911
+ if (!contract) return false;
1912
+ console.log('contract info '+JSON.stringify(contract))
1913
+ // 1) Oracle-backed
1914
+ if (contract.underlyingOracleId){
1915
+ return OracleList.getOraclePrice(contract.underlyingOracleId)
1916
+
1917
+ }
1918
+
1919
+ // 2) Index / VWAP-backed
1920
+ if (contract.onChainData.length>0) {
1921
+ const price = await VolumeIndex.getLastPrice(contract.indexPair, blockHeight);
1922
+ if (price != null && price > 0) return price;
1923
+ }
1924
+
1925
+ // 3) Explicit bootstrap mark
1926
+ if (
1927
+ contract.firstMark &&
1928
+ contract.firstMark.blockHeight <= blockHeight &&
1929
+ Number.isFinite(contract.firstMark.price) &&
1930
+ contract.firstMark.price > 0
1931
+ ) {
1932
+ return true;
1933
+ }
1934
+
1935
+ return false;
1936
+ },
1937
+
1938
+
1939
+ hasSufficientChannelMargin(balance, required, label) {
1940
+ if (balance < required) {
1941
+ return {
1942
+ valid: false,
1943
+ reason: `Insufficient channel balance in ${label} (need ${required}, found ${balance}); `
1944
+ };
1945
+ }
1946
+ return { valid: true, reason: '' };
1947
+ },
1948
+
1949
+ checkFlipMargin(balance, required, label, flipType) {
1950
+ if (required > 0 && balance < required) {
1951
+ return {
1952
+ valid: false,
1953
+ reason: `Insufficient channel balance in ${label} for ${flipType} (need ${required}, found ${balance}); `
1954
+ };
1955
+ }
1956
+ return { valid: true, reason: '' };
1957
+ },
1958
+
1959
+ // 20: Trade Tokens Channel
1960
+ validateTradeTokensChannel: async (sender, params, txid) => {
1961
+ params.reason = '';
1962
+ params.valid = true;
1963
+ console.log('inside validateTradeTokensChannel '+JSON.stringify(params))
1964
+ const isAlreadyActivated = await activationInstance.isTxTypeActive(20);
1965
+ if(isAlreadyActivated==false){
1966
+ params.valid=false
1967
+ params.reason += 'Tx type not yet activated '
1968
+ return params
1969
+ }
1970
+
1971
+ if (!Validity.isValidNumber(params.amountDesired)) {
1972
+ params.valid = false;
1973
+ params.reason += 'Price is not a valid number; ';
1974
+ }
1975
+
1976
+ if (!Validity.isValidNumber(params.amountOffered)) {
1977
+ params.valid = false;
1978
+ params.reason += 'Price is not a valid number; ';
1979
+ }
1980
+
1981
+ const is = await Validity.isActivated(params.block,txid,20)
1982
+ console.log(is)
1983
+ if (!is) {
1984
+ params.valid = false;
1985
+ params.reason = 'Transaction type activated after tx';
1986
+ }
1987
+
1988
+ console.log(params.expiryBlock,params.block)
1989
+ if(params.expiryBlock<params.block||params.expiryBlock==undefined){
1990
+ params.valid=false
1991
+ params.reason = "Tx confirmed in block later than expiration block"
1992
+ return params
1993
+ }
1994
+
1995
+ const isVEST= (parseInt(params.propertyIdDesired)==2&&parseInt(params.propertyIdOffered)==2||parseInt(params.propertyIdDesired)==3&&parseInt(params.propertyIdOffered)==4)
1996
+ if(isVEST){
1997
+ params.valid =false
1998
+ params.reason += "Vesting tokens cannot be traded"
1999
+ }
2000
+
2001
+ if(params.propertyIdOffered==params.propertyIdDesired&&(!params.Id1ColoredOutput&&!params.Id2ColoredOutput)){
2002
+ params.valid =false
2003
+ params.reason += "Cannot trade token against its own type"
2004
+ }
2005
+
2006
+ if(params.propertyIdOffered==0||params.propertyIdDesired==0){
2007
+ params.valid=false
2008
+ params.reason += "Should be a UTXO trade"
2009
+ }
2010
+
2011
+ const { commitAddressA, commitAddressB } = await Channels.getCommitAddresses(params.senderAddress);
2012
+ if(commitAddressA==null&&commitAddressB==null){
2013
+ params.valid=false
2014
+ params.reason += "Tx sender is not found to be a channel address"
2015
+ return params
2016
+ }
2017
+ const admin = activationInstance.getAdmin()
2018
+ console.log('admin in channel token '+admin)
2019
+ if(sender!=admin){
2020
+ let bans = await ClearList.getBanlist()
2021
+ if(bans==null){bans = bannedCountries}
2022
+ const senderCountryInfo = await ClearList.getCountryCodeByAddress(commitAddressA);
2023
+ if(params.propertyIdDesired == 1||params.propertyIdDesired == 2||params.propertyIdDesired == 3||params.propertyIdDesired == 4||params.propertyIdOffered == 1||params.propertyIdOffered == 2||params.propertyIdOffered == 3||params.propertyIdOffered == 4){
2024
+ if(!activationInstance.areActivationsAboveThreshold()){
2025
+ const isAcc = await ClearList.isAddressInClearlist(3,sender)
2026
+ console.log('sender country info '+JSON.stringify(senderCountryInfo))
2027
+ if ((!senderCountryInfo || bans.includes(senderCountryInfo.countryCode))&&!isAcc){
2028
+ params.valid = false;
2029
+ params.reason += 'Commiter A cannot handle TL or TLI from a banned country or lacking country code attestation';
2030
+ }
2031
+ }
2032
+ }
2033
+ const BCountryInfo = await ClearList.getCountryCodeByAddress(commitAddressB);
2034
+ if(params.propertyIdDesired == 1||params.propertyIdDesired == 2||params.propertyIdDesired == 3||params.propertyIdDesired == 4||params.propertyIdOffered == 1||params.propertyIdOffered == 2||params.propertyIdOffered == 3||params.propertyIdOffered == 4){
2035
+ if(!activationInstance.areActivationsAboveThreshold()){
2036
+ const isAcc = await ClearList.isAddressInClearlist(3,sender)
2037
+ console.log('sender country info '+JSON.stringify(senderCountryInfo))
2038
+ if ((!BCountryInfo || bans.includes(BCountryInfo.countryCode))&&!isAcc){
2039
+ params.valid = false;
2040
+ params.reason += 'Commiter B cannot handle TL or TLI from a banned country or lacking country code attestation';
2041
+ }
2042
+ }
2043
+ }
2044
+ }
2045
+
2046
+
2047
+ const channel = await Channels.getChannel(sender)
2048
+ console.log('channel returned ' +JSON.stringify(channel))
2049
+
2050
+ // --- Channel clearList enforcement ---
2051
+ if (channel && channel.clearLists) {
2052
+ const clearListsA = channel.clearLists.A || [];
2053
+ const clearListsB = channel.clearLists.B || [];
2054
+
2055
+ // If A committed with clearLists, B must be attested in at least one
2056
+ if (clearListsA.length > 0 && commitAddressB) {
2057
+ let bApproved = false;
2058
+ for (const listId of clearListsA) {
2059
+ if (await ClearList.isAddressInClearlistOrDerived(listId, commitAddressB)) {
2060
+ bApproved = true;
2061
+ break;
2062
+ }
2063
+ }
2064
+ if (!bApproved) {
2065
+ params.valid = false;
2066
+ params.reason += `Committer B (${commitAddressB}) not attested in channel clearLists A [${clearListsA}]; `;
2067
+ }
2068
+ }
2069
+
2070
+ // If B committed with clearLists, A must be attested in at least one
2071
+ if (clearListsB.length > 0 && commitAddressA) {
2072
+ let aApproved = false;
2073
+ for (const listId of clearListsB) {
2074
+ if (await ClearList.isAddressInClearlistOrDerived(listId, commitAddressA)) {
2075
+ aApproved = true;
2076
+ break;
2077
+ }
2078
+ }
2079
+ if (!aApproved) {
2080
+ params.valid = false;
2081
+ params.reason += `Committer A (${commitAddressA}) not attested in channel clearLists B [${clearListsB}]; `;
2082
+ }
2083
+ }
2084
+
2085
+ if (!params.valid) return params;
2086
+ }
2087
+
2088
+ let balanceA
2089
+ let balanceB
2090
+ let propertyIdOfferedString = params.propertyIdOffered.toString()
2091
+ let propertyIdDesiredString = params.propertyIdDesired.toString()
2092
+ let sufficientOffered
2093
+ let sufficientDesired
2094
+
2095
+ if(params.columnAIsOfferer==true){
2096
+ balanceA = channel.A[propertyIdOfferedString]
2097
+ balanceB = channel.B[propertyIdDesiredString]
2098
+
2099
+ if (balanceA < params.amountOffered) {
2100
+ params.valid = false;
2101
+ params.reason += `Insufficient channel balance in A (need ${params.amountOffered}, found ${balanceA}); `;
2102
+ }
2103
+ if (balanceB < params.amountDesired) {
2104
+ params.valid = false;
2105
+ params.reason += `Insufficient channel balance in B (need ${params.amountDesired}, found ${balanceB}); `;
2106
+ }
2107
+ if (!params.valid) {
2108
+ console.log('Rejecting for insufficient channel column balance: ' + JSON.stringify(params));
2109
+ return params;
2110
+ }
2111
+
2112
+ const hasSufficientA = TallyMap.hasSufficientChannel(sender,params.propertyIdOffered,params.amountOffered)
2113
+ const hasSufficientB = TallyMap.hasSufficientChannel(sender,params.propertyIdDesired,params.amountDesired)
2114
+
2115
+ console.log('validating token channel trade columnA is offerer'+balanceA+' '+balanceB+' '+channel.A+' '+channel.B)
2116
+ console.log('sufficient channel? '+JSON.stringify(hasSufficientA)+' '+JSON.stringify(hasSufficientB))
2117
+ if(balanceA<params.amountOffered||!hasSufficientA.hasSufficient){
2118
+ params.valid=false
2119
+ params.reason += "Column A has insufficient balance for amountOffered"
2120
+ }
2121
+ if(balanceB<params.amountDesired||!hasSufficientB.hasSufficient){
2122
+ params.valid=false
2123
+ params.reason += "Column B has insufficient balance for amountDesired"
2124
+ }
2125
+ }else if(params.columnAIsOfferer==false){
2126
+ balanceA = channel.A[propertyIdDesiredString]
2127
+ balanceB = channel.B[propertyIdOfferedString]
2128
+
2129
+ if (balanceA < params.amountDesired) {
2130
+ params.valid = false;
2131
+ params.reason += `Insufficient channel balance in A (need ${params.amountDesired}, found ${balanceA}); `;
2132
+ }
2133
+ if (balanceB < params.amountOffered) {
2134
+ params.valid = false;
2135
+ params.reason += `Insufficient channel balance in B (need ${params.amountOffered}, found ${balanceB}); `;
2136
+ }
2137
+ if (!params.valid) {
2138
+ console.log('Rejecting for insufficient channel column balance: ' + JSON.stringify(params));
2139
+ return params;
2140
+ }
2141
+
2142
+ const hasSufficientA = TallyMap.hasSufficientChannel(sender,params.propertyIdDesired,params.amountDesired)
2143
+ const hasSufficientB = TallyMap.hasSufficientChannel(sender,params.propertyIdOffered,params.amountOffered)
2144
+
2145
+ console.log('validating token channel trade columnB is offerer'+balanceA+' '+balanceB+' '+channel.A+' '+channel.B)
2146
+ console.log('sufficient channel? '+JSON.stringify(hasSufficientA)+' '+JSON.stringify(hasSufficientB))
2147
+
2148
+ if(balanceA<params.amountOffered||!hasSufficientA.hasSufficient){
2149
+ params.valid=false
2150
+ params.reason += "Column A has insufficient balance for amountOffered"
2151
+ }
2152
+ if(balanceB<params.amountDesired||!hasSufficientB.hasSufficient){
2153
+ params.valid=false
2154
+ params.reason += "Column B has insufficient balance for amountDesired"
2155
+ }
2156
+ }
2157
+
2158
+ // Get property data for both propertyIdOffered and propertyIdDesired
2159
+ const propertyDataOffered = await PropertyList.getPropertyData(params.propertyIdOffered);
2160
+ const propertyDataDesired = await PropertyList.getPropertyData(params.propertyIdDesired);
2161
+
2162
+ if (propertyDataOffered == null || propertyDataDesired == null) {
2163
+ params.valid = false;
2164
+ params.reason += 'Property data not found; ';
2165
+ return params;
2166
+ }
2167
+
2168
+ const whitelistsOffered = Array.isArray(propertyDataOffered.whitelistId) ? propertyDataOffered.whitelistId : [propertyDataOffered.whitelistId];
2169
+ const whitelistsDesired = Array.isArray(propertyDataDesired.whitelistId) ? propertyDataDesired.whitelistId : [propertyDataDesired.whitelistId];
2170
+
2171
+ // Skip whitelist checks if both properties have whitelistId of 0
2172
+ if (whitelistsOffered.includes(0) && whitelistsDesired.includes(0)) {
2173
+ console.log('Both properties have whitelistId 0, skipping whitelist checks.');
2174
+ } else {
2175
+ // Check whitelist for commitAddressA and commitAddressB
2176
+ let listed1 = true, listed2 = true, listed3 = true, listed4 = true;
2177
+
2178
+ // Skip whitelist checks for offered if whitelistId is 0
2179
+ if (!whitelistsOffered.includes(0)) {
2180
+ listed1 = await isListed(whitelistsOffered, commitAddressA);
2181
+ listed3 = await isListed(whitelistsOffered, commitAddressB);
2182
+ }
2183
+
2184
+ // Skip whitelist checks for desired if whitelistId is 0
2185
+ if (!whitelistsDesired.includes(0)) {
2186
+ listed2 = await isListed(whitelistsDesired, commitAddressA);
2187
+ listed4 = await isListed(whitelistsDesired, commitAddressB);
2188
+ }
2189
+
2190
+ // If any of the checks fail, invalidate the params
2191
+ if (!listed1 || !listed2 || !listed3 || !listed4) {
2192
+ params.valid = false;
2193
+ if (!listed1) params.reason += 'Commit address A not whitelisted in clearlist for property offered; ';
2194
+ if (!listed2) params.reason += 'Commit address A not whitelisted in clearlist for property desired; ';
2195
+ if (!listed3) params.reason += 'Commit address B not whitelisted in clearlist for property offered; ';
2196
+ if (!listed4) params.reason += 'Commit address B not whitelisted in clearlist for property desired; ';
2197
+ }
2198
+ }
2199
+ return params;
2200
+ },
2201
+
2202
+ // Helper function to check if an address is in the whitelist
2203
+ async isListed(whitelistIds, commitAddress) {
2204
+ for (const whitelistId of whitelistIds) {
2205
+ const isWhitelisted = await ClearList.isAddressInClearlist(whitelistId, commitAddress);
2206
+ if (isWhitelisted) return true;
2207
+ }
2208
+ return false;
2209
+ },
2210
+
2211
+ // Function to perform whitelist checks
2212
+ async checkWhitelist(whitelists, commitAddress, propertyRole, params) {
2213
+ const listed = await isListed(whitelists, commitAddress);
2214
+ if (!listed) {
2215
+ params.valid = false;
2216
+ params.reason += `Commit address ${propertyRole} not whitelisted in clearlist; `;
2217
+ }
2218
+ },
2219
+
2220
+ // 21: Withdrawal
2221
+ validateWithdrawal: async (sender, params, txid) => {
2222
+ params.reason = '';
2223
+ params.valid = true;
2224
+
2225
+ if(params.ref){
2226
+ //console.log(params.ref)
2227
+ const outputs = await TxUtils.getTransactionOutputs(txid)
2228
+
2229
+ let matchingOutput = null;
2230
+ //console.log(JSON.stringify(outputs))
2231
+ // Loop through the outputs array to find the one with the matching vout
2232
+ for (let i = 0; i < outputs.length; i++) {
2233
+ //console.log('in the for '+i+' '+outputs[i].vout+' '+params.ref)
2234
+ if (outputs[i].vout === Number(params.ref)) {
2235
+ matchingOutput = outputs[i];
2236
+ //console.log('match output '+matchingOutput)
2237
+ break; // Exit loop once the matching output is found
2238
+ }
2239
+ }
2240
+
2241
+ if (matchingOutput) {
2242
+ // Access the matching output's address and satoshis
2243
+ params.channelAddress = matchingOutput.address;
2244
+ console.log('params.channelAddress '+params.channelAddress)
2245
+ }else{
2246
+ params.valid = false
2247
+ params.reason += "No channel address detectable in payload or ref: output"
2248
+ }
2249
+ }
2250
+
2251
+ const isAlreadyActivated = await activationInstance.isTxTypeActive(21);
2252
+ if(isAlreadyActivated==false){
2253
+ params.valid=false
2254
+ params.reason += 'Tx type not yet activated '
2255
+ }
2256
+
2257
+ const is = await Validity.isActivated(params.block,txid,21)
2258
+ console.log(is)
2259
+ if (!is) {
2260
+ params.valid = false;
2261
+ params.reason = 'Transaction type activated after tx';
2262
+ }
2263
+
2264
+ if(params.withdrawAll!=true&&(params.propertyId==null||params.amount==null)){
2265
+ params.valid=false
2266
+ params.reason += 'propertyId or amount not specified while withdrawAll is false'
2267
+
2268
+ }
2269
+
2270
+ const { commitAddressA, commitAddressB } = await Channels.getCommitAddresses(params.channelAddress);
2271
+
2272
+ if (!commitAddressA&&!commitAddressB) {
2273
+ params.valid = false;
2274
+ params.reason += 'Channel not instantiated; ';
2275
+ return params
2276
+ }
2277
+
2278
+ const channel = await Channels.getChannel(params.channelAddress)
2279
+ let isColumnA = params.column
2280
+ let balance
2281
+ console.log('inside validate withdrawal '+sender+' '+Boolean(sender==channel.participants.A)+Boolean(sender==channel.participants.B))
2282
+ if (sender!=channel.participants.A&&sender!=channel.participants.B) {
2283
+ params.valid = false;
2284
+ params.reason += 'Sender not authorized for the channel';
2285
+ }else{
2286
+ if(sender==channel.participants.A){
2287
+ isColumnA=true
2288
+ balance=channel.A[params.propertyId]
2289
+ console.log('column ' +params.column)
2290
+ if(params.column==false){
2291
+ params.valid = false;
2292
+ params.reason += 'Sender does not match with column';
2293
+ }
2294
+ }else if(sender==channel.participants.B){
2295
+ console.log('checking this column disqualification logic works '+params.column)
2296
+ isColumnA=false
2297
+ balance=channel.B[params.propertyId]
2298
+ if(params.column==true){
2299
+ params.valid = false;
2300
+ params.reason += 'Sender does not match with column';
2301
+ }
2302
+ }
2303
+ }
2304
+ //if column is true then it's column B because 0 comes before 1 and A before B
2305
+ console.log('inside validate withdrawal '+params.column +'isColumnA '+isColumnA+' balance '+balance+' withdraw amount '+params.amount)
2306
+ if(params.column==undefined){
2307
+ params.valid = false
2308
+ params.reason+='column parameter not specified'
2309
+ }
2310
+
2311
+ if (balance < params.amount) {
2312
+ params.valid = false;
2313
+ params.reason += 'Insufficient balance for withdrawal; ';
2314
+ }
2315
+
2316
+ return params;
2317
+ },
2318
+
2319
+ // 22: Transfer
2320
+ validateTransfer: async (sender, params, txid) => {
2321
+ params.reason = '';
2322
+ params.valid = true;
2323
+
2324
+ if (!Validity.isValidNumber(params.amount)) {
2325
+ params.valid = false;
2326
+ params.reason += 'Amount is not a valid number; ';
2327
+ }
2328
+
2329
+ if(params.ref){
2330
+ const outputs = await TxUtils.getTransactionOutputs(txid)
2331
+
2332
+ let matchingOutput = null;
2333
+
2334
+ // Loop through the outputs array to find the one with the matching vout
2335
+ for (let i = 0; i < outputs.length; i++) {
2336
+ if (outputs[i].vout === params.ref) {
2337
+ matchingOutput = outputs[i];
2338
+ break; // Exit loop once the matching output is found
2339
+ }
2340
+ }
2341
+
2342
+ if (matchingOutput) {
2343
+ // Access the matching output's address and satoshis
2344
+ params.toChannelAddress = matchingOutput.address;
2345
+ }else{
2346
+ params.valid = false
2347
+ params.reason += "No channel address detectable in payload or ref: output"
2348
+ }
2349
+ }
2350
+
2351
+ const isAlreadyActivated = await activationInstance.isTxTypeActive(22);
2352
+ if(isAlreadyActivated==false){
2353
+ params.valid=false
2354
+ params.reason += 'Tx type not yet activated '
2355
+ }
2356
+
2357
+ if(!validateAddress(params.toChannelAddress)){
2358
+ const valid = await TxUtils.validateAddressWrapper(params.toChannelAddress)
2359
+ if(!valid.isvalid){
2360
+ params.valid= false
2361
+ params.reason = 'Destination address is not validly formed.'
2362
+ }
2363
+ }
2364
+
2365
+ const is = await Validity.isActivated(params.block,txid,22)
2366
+ console.log(is)
2367
+ if (!is) {
2368
+ params.valid = false;
2369
+ params.reason = 'Transaction type activated after tx';
2370
+ }
2371
+
2372
+ const isValidSourceChannel = Channels.isValidChannel(sender);
2373
+ if (!isValidSourceChannel) {
2374
+ params.valid = false;
2375
+ params.reason += 'Invalid source channel; ';
2376
+ }
2377
+
2378
+ //const { commitAddressA, commitAddressB } = await Channels.getCommitAddresses(sender)
2379
+ console.log('calling channel in validity for '+sender)
2380
+ const channel = await Channels.getChannel(sender)
2381
+ console.log(JSON.stringify(channel) +' '+Boolean(!channel))
2382
+ let balance =0
2383
+ if(!channel){
2384
+ params.valid = false;
2385
+ params.reason += 'Sender is not a channel.';
2386
+ }else{
2387
+ const balanceA = channel.A[params.propertyId] || 0;
2388
+ const balanceB = channel.B[params.propertyId] || 0;
2389
+
2390
+ let commiter = ''
2391
+
2392
+ if(params.isColumnA){
2393
+ balance= balanceA
2394
+ commiter = channel.participants.A
2395
+ }else if(!params.isColumnA){
2396
+ balance= balanceB
2397
+ commiter = channel.participants.B
2398
+ }
2399
+ console.log(JSON.stringify(channel))
2400
+ console.log(balanceA,balanceB, params.amount, params.isColumnA, balance)
2401
+ }
2402
+
2403
+
2404
+ const hasSufficientBalance = Boolean(balance>=params.amount);
2405
+ console.log('suf balance in transfer val ' +JSON.stringify(hasSufficientBalance))
2406
+ if (!hasSufficientBalance) {
2407
+ params.valid = false;
2408
+ params.reason += 'Insufficient balance for transfer; ';
2409
+ }
2410
+
2411
+
2412
+
2413
+ const otherChannel = await Channels.getChannel(params.toChannelAddress)
2414
+ if(otherChannel!=undefined||otherChannel!=null){
2415
+ let commitedA = otherChannel.participants.A
2416
+ let commitedB = otherChannel.participants.B
2417
+ if(commitedA!=''&&commitedA!=sender&&commitedB!=''&&commitedB!=sender){
2418
+ params.valid = false
2419
+ params.reason += 'Both columns of the desired transferee channel are occupied by commiters other than the commiter owning the transfered tokens.'
2420
+ }
2421
+ }
2422
+
2423
+ // Ensure pay is a boolean; default to false if not provided
2424
+ if (typeof params.pay === 'undefined' || params.pay === '') {
2425
+ params.pay = false;
2426
+ } else if (typeof params.pay !== 'boolean') {
2427
+ params.valid = false;
2428
+ params.reason += 'pay is not a boolean; ';
2429
+ }
2430
+
2431
+ // Validate payRef if pay is enabled
2432
+ if (params.pay && params.payRef) {
2433
+ if (!Number.isInteger(Number(params.payRef)) || Number(params.payRef) < 0) {
2434
+ params.valid = false;
2435
+ params.reason += 'payRef is not a valid integer; ';
2436
+ } else {
2437
+ // Retrieve channel information
2438
+
2439
+ if (channel) {
2440
+ // Determine the relevant column (A or B) based on params.isColumnA
2441
+ const column = params.isColumnA ? 'A' : 'B';
2442
+
2443
+ // Check if pay is enabled for the column
2444
+ if (!channel.payEnabled || !channel.payEnabled[column]) {
2445
+ params.valid = false;
2446
+ params.reason += `Pay not enabled for column ${column}; `;
2447
+ }
2448
+
2449
+ // Check clearLists for the column
2450
+ const clearLists = channel.clearLists ? channel.clearLists[column] : undefined;
2451
+ if (clearLists) {
2452
+ // Retrieve the payRef address details from transaction outputs
2453
+ const outputs = await TxUtils.getTransactionOutputs(txid);
2454
+ const payRefOutput = outputs.find(output => output.vout === Number(params.payRef));
2455
+
2456
+ if (!payRefOutput) {
2457
+ params.valid = false;
2458
+ params.reason += 'Invalid payRef output; ';
2459
+ } else {
2460
+ const payRefAddress = payRefOutput.scriptPubKey.addresses[0]; // Assuming single address per output // Check if payRefAddress matches any entry in the clearLists array
2461
+ params.payRefAddress = payRefAddress
2462
+ const isValidAttestation = await Promise.all(
2463
+ clearLists.map(async (listId) => {
2464
+ return await ClearList.isAddressInClearlist(listId, payRefAddress);
2465
+ })
2466
+ );
2467
+
2468
+ // If none of the clearLists contain a match, invalidate
2469
+ if (!isValidAttestation.includes(true)) {
2470
+ params.valid = false;
2471
+ params.reason += `No valid attestation for payRef address ${payRefAddress}; `;
2472
+ }
2473
+ }
2474
+ }
2475
+ } else {
2476
+ params.valid = false;
2477
+ params.reason += 'Sender channel not found; ';
2478
+ }
2479
+ }
2480
+ }
2481
+
2482
+
2483
+ return params;
2484
+ },
2485
+
2486
+ // 23: Settle Channel PNL
2487
+ validateSettleChannelPNL: async (sender, params, txid) => {
2488
+ params.reason = '';
2489
+ params.valid = true;
2490
+
2491
+ const isAlreadyActivated = await activationInstance.isTxTypeActive(23);
2492
+ if(isAlreadyActivated==false){
2493
+ params.valid=false
2494
+ params.reason += 'Tx type not yet activated '
2495
+ }
2496
+
2497
+ const is = await Validity.isActivated(params.block,txid,23)
2498
+ console.log(is)
2499
+ if (!is) {
2500
+ params.valid = false;
2501
+ params.reason = 'Transaction type activated after tx';
2502
+ }
2503
+
2504
+ const isValidChannel = channelRegistry.isValidChannel(params.channelAddress);
2505
+ if (!isValidChannel) {
2506
+ params.valid = false;
2507
+ params.reason += 'Invalid channel; ';
2508
+ }
2509
+
2510
+ const isValidContract = marginMap.isValidContract(params.contractId);
2511
+ if (!isValidContract) {
2512
+ params.valid = false;
2513
+ params.reason += 'Invalid contract for settlement; ';
2514
+ }
2515
+
2516
+ const canSettle = marginMap.canSettlePNL(params.channelAddress, params.contractId, params.amountSettled);
2517
+ if (!canSettle) {
2518
+ params.valid = false;
2519
+ params.reason += 'Cannot settle PNL; terms not met; ';
2520
+ }
2521
+
2522
+ const isNuetralized = await Scaling.isThisSettlementAlreadyNuetralized(sender, txid)
2523
+ if(isNuetralized){
2524
+ params.valid = false
2525
+ params.reason += "Settlement already invalidated by later settlement that updates it. "
2526
+ }
2527
+ return params;
2528
+ },
2529
+
2530
+ // 24: Mint Synthetic
2531
+ validateMintSynthetic: async (sender, params, txid) => {
2532
+ params.reason = '';
2533
+ params.valid = true;
2534
+ console.log(JSON.stringify(params));
2535
+
2536
+ // User input → round down
2537
+ const roundedAmount = BigNumber(params.amount)
2538
+ .decimalPlaces(0, BigNumber.ROUND_DOWN)
2539
+ .toNumber();
2540
+
2541
+ const isAlreadyActivated = await activationInstance.isTxTypeActive(24);
2542
+ if (!isAlreadyActivated) {
2543
+ params.valid = false;
2544
+ params.reason += 'Tx type not yet activated ';
2545
+ }
2546
+
2547
+ const is = await Validity.isActivated(params.block, txid, 24);
2548
+ console.log(is);
2549
+ if (!is) {
2550
+ params.valid = false;
2551
+ params.reason = 'Transaction type activated after tx';
2552
+ }
2553
+
2554
+ if (roundedAmount < 1) {
2555
+ params.valid = false;
2556
+ params.reason += 'Amount less than one';
2557
+ }
2558
+
2559
+ params.amount = roundedAmount;
2560
+
2561
+ const contractInfo = await ContractRegistry.getContractInfo(params.contractId);
2562
+ if (!contractInfo) {
2563
+ params.valid = false;
2564
+ params.reason += "hedge contract not found";
2565
+ return params;
2566
+ }
2567
+
2568
+ const tokenPair = contractInfo.onChainData[0][0] + '-' + contractInfo.onChainData[0][1];
2569
+ const collateralPropertyId = contractInfo.collateralPropertyId;
2570
+ const notionalValue = contractInfo.notionalValue;
2571
+
2572
+ if (!contractInfo.inverse) {
2573
+ params.valid = false;
2574
+ params.reason += 'Cannot mint synthetics with linear contracts';
2575
+ }
2576
+ if (!contractInfo.native) {
2577
+ params.valid = false;
2578
+ params.reason += 'Cannot mint synthetics with oracle contracts... no one man should have all that power';
2579
+ }
2580
+
2581
+ const marginMap = await MarginMap.getInstance(params.contractId);
2582
+ const position = await marginMap.getPositionForAddress(sender, params.contractId);
2583
+ if (!position.contracts) {
2584
+ params.valid = false;
2585
+ params.reason += 'Null contracts cannot hedge a mint';
2586
+ }
2587
+
2588
+ let grossNotional = BigNumber(position.contracts)
2589
+ .times(notionalValue)
2590
+ .decimalPlaces(8, BigNumber.ROUND_DOWN) // hedge cap conservative
2591
+ .toNumber();
2592
+
2593
+ console.log(
2594
+ 'validating mint ' +
2595
+ grossNotional +
2596
+ ' ' +
2597
+ params.amount +
2598
+ ' ' +
2599
+ position.contracts +
2600
+ ' ' +
2601
+ notionalValue
2602
+ );
2603
+
2604
+ if (params.amount > Math.abs(grossNotional)) {
2605
+ if (grossNotional <= 0) {
2606
+ params.amount = BigNumber(Math.abs(grossNotional))
2607
+ .decimalPlaces(8, BigNumber.ROUND_DOWN) // cap down
2608
+ .toNumber();
2609
+ params.reason +=
2610
+ 'insufficient contracts to hedge total, minting based on available contracts';
2611
+ } else {
2612
+ params.valid = false;
2613
+ params.reason += 'insufficient contracts to hedge the amount requested';
2614
+ }
2615
+ }
2616
+
2617
+ const markPrice = await VolumeIndex.getLastPrice(tokenPair, params.block);
2618
+ if (!markPrice) {
2619
+ params.valid = false;
2620
+ params.reason += 'cannot identify price to value the mint';
2621
+ }
2622
+
2623
+ // 1. Figure the target synthetic value at avgPrice
2624
+ const targetValue = BigNumber(params.amount)
2625
+ .times(notionalValue)
2626
+ .div(position.avgPrice) // avg entry, not mark
2627
+ .decimalPlaces(8, BigNumber.ROUND_UP);
2628
+
2629
+ // 2. Work out the pro-rata margin slice
2630
+ const posContracts = Math.abs(position.contracts);
2631
+ let marginSlice = new BigNumber(0);
2632
+ if (posContracts > 0) {
2633
+ marginSlice = BigNumber(position.margin)
2634
+ .times(params.amount)
2635
+ .div(posContracts)
2636
+ .decimalPlaces(8, BigNumber.ROUND_DOWN);
2637
+ }
2638
+
2639
+ // 3. Whatever margin covers, reduce avail leg accordingly
2640
+ let grossRequired = targetValue.minus(marginSlice);
2641
+ if (grossRequired.isNegative()) grossRequired = new BigNumber(0);
2642
+
2643
+ // 4. Store rounded numbers
2644
+ params.margin = marginSlice.toNumber();
2645
+ params.grossRequired = grossRequired.toNumber();
2646
+
2647
+
2648
+ const hasSufficientBalance = await TallyMap.hasSufficientBalance(
2649
+ sender,
2650
+ collateralPropertyId,
2651
+ grossRequired
2652
+ );
2653
+ console.log(hasSufficientBalance.hasSufficient + ' ' + grossRequired);
2654
+
2655
+ if (hasSufficientBalance.hasSufficient == false) {
2656
+ if (hasSufficientBalance.available >= initMargin) {
2657
+ let newAmount = BigNumber(hasSufficientBalance.available)
2658
+ .dividedBy(initMargin)
2659
+ .decimalPlaces(0, BigNumber.ROUND_DOWN) // safe cap
2660
+ .toNumber();
2661
+
2662
+ if (newAmount <= params.amount) {
2663
+ params.amount = newAmount;
2664
+ totalMargin = BigNumber(initMargin)
2665
+ .times(params.amount)
2666
+ .decimalPlaces(8, BigNumber.ROUND_UP)
2667
+ .toNumber();
2668
+ grossRequired = BigNumber(params.amount)
2669
+ .times(notionalValue)
2670
+ .dividedBy(markPrice)
2671
+ .minus(totalMargin)
2672
+ .decimalPlaces(8, BigNumber.ROUND_UP)
2673
+ .toNumber();
2674
+ params.reason +=
2675
+ 'insufficient collateral to mint total, minting based on available collateral';
2676
+ } else {
2677
+ params.reason +=
2678
+ 'insufficient collateral to mint total, minting based on available collateral capped at contracts';
2679
+ }
2680
+ }
2681
+ params.valid = false;
2682
+ params.reason += 'insufficient collateral to create a 1x hedge position';
2683
+ }
2684
+
2685
+ console.log(
2686
+ 'about to calculate contracts ' +
2687
+ params.amount +
2688
+ ' ' +
2689
+ notionalValue +
2690
+ ' ' +
2691
+ BigNumber(params.amount).dividedBy(notionalValue).decimalPlaces(0, BigNumber.ROUND_DOWN).toNumber()
2692
+ );
2693
+
2694
+ params.contracts = BigNumber(params.amount)
2695
+ .dividedBy(notionalValue)
2696
+ .decimalPlaces(0, BigNumber.ROUND_DOWN) // conservative
2697
+ .toNumber();
2698
+
2699
+ return params;
2700
+ },
2701
+
2702
+ // 25: Redeem Synthetic
2703
+ validateRedeemSynthetic: async (sender, params,txid) => {
2704
+ params.reason = '';
2705
+ params.valid = true;
2706
+ console.log('validating redeem '+JSON.stringify(params))
2707
+ const roundedAmount = Math.floor(params.amount);
2708
+ params.propertyId='s-'+params.propertyId+'-'+params.contractId
2709
+ // Check if the rounded amount is still >= 1
2710
+
2711
+ const isAlreadyActivated = await activationInstance.isTxTypeActive(25);
2712
+ if(isAlreadyActivated==false){
2713
+ params.valid=false
2714
+ params.reason += 'Tx type not yet activated '
2715
+ }
2716
+
2717
+ const is = await Validity.isActivated(params.block,txid,25)
2718
+ console.log(is)
2719
+ if (!is) {
2720
+ params.valid = false;
2721
+ params.reason = 'Transaction type activated after tx';
2722
+ }
2723
+
2724
+ if (roundedAmount < 1) {
2725
+ params.valid=false
2726
+ params.reason += 'Amount less than one'
2727
+ }
2728
+
2729
+ params.amount = roundedAmount
2730
+ // Check if the synthetic token can be redeemed (existence, sufficient amount, etc.)
2731
+
2732
+ let marginMap= await MarginMap.getInstance(params.contractId)
2733
+ let position = await marginMap.getPositionForAddress(sender, params.contractId)
2734
+ if(position.contracts>0){
2735
+ params.valid=false
2736
+ params.reason += 'Redemption will close existing longs, move synths to a new address to redeem'
2737
+ }
2738
+
2739
+ const canRedeem = await SyntheticRegistry.exists(params.propertyId);
2740
+ if(canRedeem==false){
2741
+ params.valid=false
2742
+ params.reason += 'Token is not of a synthetic nature'
2743
+ }
2744
+ // Ensure the sender has sufficient balance of the synthetic property
2745
+ const hasSufficientBalance = await TallyMap.hasSufficientBalance(params.senderAddress, params.propertyId, params.amount);
2746
+ if(hasSufficientBalance.hasSufficient==false){
2747
+ if(hasSufficientBalance.available>=1){
2748
+ params.amount = Math.floor(hasSufficientBalance.available)
2749
+ }else{
2750
+ params.valid=false
2751
+ params.reason += 'insufficient tokens to redeem in this amount'
2752
+ }
2753
+ }
2754
+ return params;
2755
+ },
2756
+
2757
+ // 26: Pay to Tokens
2758
+ validatePayToTokens: (params, tallyMap) => {
2759
+ // Ensure the sender has sufficient balance of the property used for payment
2760
+ const hasSufficientBalance = tallyMap.hasSufficientBalance(params.senderAddress, params.propertyIdUsed, params.amount);
2761
+ // Additional checks can be implemented based on the specific rules of Pay to Tokens transactions
2762
+
2763
+ return hasSufficientBalance.hasSufficient;
2764
+ },
2765
+
2766
+ validateOptionTrade: async (sender, params, txid) => {
2767
+ params.reason = '';
2768
+ params.valid = true;
2769
+
2770
+ // --- Parse primary & combo ---
2771
+ const tA = OptionsEngine.parseTicker(params.contractId);
2772
+ if (!tA) { params.valid=false; params.reason+='Invalid primary ticker; '; return params; }
2773
+ let tB = null;
2774
+ if (params.comboTicker) {
2775
+ tB = OptionsEngine.parseTicker(params.comboTicker);
2776
+ if (!tB) { params.valid=false; params.reason+='Invalid combo ticker; '; return params; }
2777
+ }
2778
+
2779
+ // --- Series existence ---
2780
+ const seriesInfo = await ContractRegistry.getContractInfo(tA.seriesId);
2781
+ if (!seriesInfo) { params.valid=false; params.reason+='Option series not found; '; return params; }
2782
+ const collateralPropertyId = seriesInfo.collateralPropertyId;
2783
+ const inverse = !!seriesInfo.inverse;
2784
+
2785
+ // --- Channel & commit addrs ---
2786
+ const { commitAddressA, commitAddressB } = await Channels.getCommitAddresses(sender);
2787
+ if (!commitAddressA && !commitAddressB) {
2788
+ params.valid=false; params.reason+='Tx sender is not a channel address; '; return params;
2789
+ }
2790
+
2791
+ // --- Balances for margin checks ---
2792
+ const channel = await Channels.getChannel(sender);
2793
+ const balanceA = channel.A[collateralPropertyId] || 0;
2794
+ const balanceB = channel.B[collateralPropertyId] || 0;
2795
+ const tallyA = await TallyMap.getTally(commitAddressA, collateralPropertyId);
2796
+ const tallyB = await TallyMap.getTally(commitAddressB, collateralPropertyId);
2797
+ const effectiveA = balanceA + (tallyA?.available || 0);
2798
+ const effectiveB = balanceB + (tallyB?.available || 0);
2799
+
2800
+ // --- Margin requirement (spreads w/ premium adj; naked otherwise) ---
2801
+ let requiredMargin = 0;
2802
+
2803
+ if (params.comboTicker && params.comboAmount) {
2804
+ const qty = Math.min(Number(params.amount||0), Number(params.comboAmount||0));
2805
+
2806
+ // width via strike difference; inverse-safe using your estimatePNL
2807
+ const loss = estimatePNL(qty, tA.strike, tB.strike, inverse, seriesInfo.notionalValue);
2808
+ requiredMargin = Math.abs(loss);
2809
+
2810
+ // premium adjustment (credit reduces margin; debit = margin)
2811
+ const leg1Premium = Number(params.price || 0) * Number(params.amount || 0);
2812
+ const leg2Premium = Number(params.comboPrice || 0) * Number(params.comboAmount || 0);
2813
+ const netPremium = leg1Premium - leg2Premium;
2814
+
2815
+ if (netPremium > 0) {
2816
+ requiredMargin = Math.max(0, requiredMargin - netPremium);
2817
+ } else {
2818
+ requiredMargin = Math.abs(netPremium);
2819
+ }
2820
+
2821
+ const leftover = Math.abs(Number(params.amount||0) - Number(params.comboAmount||0));
2822
+ if (leftover > 0) {
2823
+ requiredMargin += (Number(tA.strike||0) / 10) * leftover; // naked leftover rule
2824
+ }
2825
+ } else {
2826
+ requiredMargin = (Number(tA.strike||0) / 10) * Number(params.amount||0);
2827
+ }
2828
+
2829
+ // --- Reduce/Flip & rPNL (per side) for the OPTION ticker itself ---
2830
+ const mm = await MarginMap.getInstance(tA.seriesId);
2831
+
2832
+ // Find who is seller/buyer in *this* tx by your columnA flag
2833
+ const AIsSeller = (params.columnAIsSeller===true || params.columnAIsSeller===1 || params.columnAIsSeller==="1");
2834
+ const sellerAddr = AIsSeller ? commitAddressA : commitAddressB;
2835
+ const buyerAddr = AIsSeller ? commitAddressB : commitAddressA;
2836
+
2837
+ // Load existing option positions under the specific ticker string
2838
+ const sellerBlob = mm.margins.get(sellerAddr) || {};
2839
+ const buyerBlob = mm.margins.get(buyerAddr) || {};
2840
+ const sellerOpt = (sellerBlob.options && sellerBlob.options[params.contractId]) || { contracts: 0, avgPrice: 0 };
2841
+ const buyerOpt = (buyerBlob.options && buyerBlob.options[params.contractId]) || { contracts: 0, avgPrice: 0 };
2842
+
2843
+ // Signed trade deltas: SELL => negative, BUY => positive
2844
+ const primaryDelta = (params.side === 'BUY') ? Number(params.amount||0) : -Number(params.amount||0);
2845
+
2846
+ // Seller side reduce/flip on seller's book (delta is negative if seller is selling)
2847
+ const sellerDelta = -Math.abs(Number(params.amount||0));
2848
+ const sellerRF = OptionsEngine.computeReduceFlip(Number(sellerOpt.contracts||0), sellerDelta);
2849
+
2850
+ // Buyer side reduce/flip on buyer's book (delta is positive if buyer is buying)
2851
+ const buyerDelta = +Math.abs(Number(params.amount||0));
2852
+ const buyerRF = OptionsEngine.computeReduceFlip(Number(buyerOpt.contracts||0), buyerDelta);
2853
+
2854
+ params.sellerReducing = sellerRF.closedQty > 0;
2855
+ params.buyerReducing = buyerRF.closedQty > 0;
2856
+ params.closedContractsSeller = sellerRF.closedQty || 0;
2857
+ params.closedContractsBuyer = buyerRF.closedQty || 0;
2858
+
2859
+ // rPNL = (tradePx - avgPx)*qty for long reduce; (avgPx - tradePx)*qty for short reduce
2860
+ const tradePx = Number(params.price || 0);
2861
+ params.rpnlSeller = OptionsEngine.rpnlForClose(sellerRF.exSide, sellerRF.closedQty, tradePx, sellerOpt.avgPrice);
2862
+ params.rpnlBuyer = OptionsEngine.rpnlForClose(buyerRF.exSide, buyerRF.closedQty, tradePx, buyerOpt.avgPrice);
2863
+
2864
+ // --- Balance check with requiredMargin (credit) ---
2865
+ params.creditMargin = requiredMargin; // expose to logic.js
2866
+
2867
+ if (effectiveA < requiredMargin && effectiveB < requiredMargin) {
2868
+ params.valid = false;
2869
+ params.reason += 'Insufficient collateral for option margin; ';
2870
+ }
2871
+
2872
+ // --- Expiry guard ---
2873
+ if (params.blockHeight && params.blockHeight >= tA.expiryBlock) {
2874
+ params.valid = false;
2875
+ params.reason += 'Option already expired; ';
2876
+ }
2877
+
2878
+ return params;
2879
+ },
2880
+
2881
+ parseTicker(tickerStr) {
2882
+ if (!tickerStr || typeof tickerStr !== 'string') return null;
2883
+ const parts = tickerStr.split('-');
2884
+ if (parts.length < 3) return null;
2885
+
2886
+ const seriesId = parts[0];
2887
+ const expiryBlock = parseInt(parts[1], 10);
2888
+ const type = parts[2] === 'C' ? 'Call' : parts[2] === 'P' ? 'Put' : null;
2889
+ const strike = parts[3] ? parseFloat(parts[3]) : null;
2890
+
2891
+ return {
2892
+ seriesId,
2893
+ expiryBlock,
2894
+ type,
2895
+ strike,
2896
+ raw: tickerStr
2897
+ };
2898
+ },
2899
+
2900
+ // 28: Trade Bai Urbun
2901
+ validateTradeBaiUrbun: (params, channelRegistry, baiUrbunRegistry) => {
2902
+ // Verify that the trade channel exists and is valid
2903
+ const isValidChannel = channelRegistry.isValidChannel(params.channelAddress);
2904
+ // Check if Bai Urbun contract terms are valid (price, amount, expiry block, etc.)
2905
+ const isValidContractTerms = baiUrbunRegistry.isValidBaiUrbunTerms(params.propertyIdDownPayment, params.propertyIdToBeSold, params.price, params.amount, params.expiryBlock);
2906
+
2907
+ return isValidChannel && isValidContractTerms;
2908
+ },
2909
+
2910
+ // 29: Trade Murabaha
2911
+ validateTradeMurabaha: (params, channelRegistry, murabahaRegistry) => {
2912
+ // Verify that the trade channel exists and is valid
2913
+ const isValidChannel = channelRegistry.isValidChannel(params.channelAddress);
2914
+ // Check if Murabaha contract terms are valid (down payment, price, amount, expiryparams.block, etc.)
2915
+ const isValidContractTerms = murabahaRegistry.isValidMurabahaTerms(params.propertyIdDownPayment, params.downPaymentPercent, params.propertyIdToBeSold, params.price, params.amount, params.expiryBlock, params.installmentInterval);
2916
+
2917
+ return isValidChannel && isValidContractTerms;
2918
+ },
2919
+
2920
+ // 30: Issue Invoice
2921
+ validateStakeFraudProof: (params, invoiceRegistry, tallyMap) => {
2922
+ // Check if the issuer has sufficient balance of the property to receive payment
2923
+ const hasSufficientBalance = tallyMap.hasSufficientBalance(params.issuerAddress, params.propertyIdToReceivePayment, params.amount);
2924
+ // Validate invoice terms (due date, collateral, etc.)
2925
+ const isValidInvoiceTerms = invoiceRegistry.isValidInvoiceTerms(params.dueDateBlock, params.propertyIdCollateral);
2926
+
2927
+ return hasSufficientBalance.hasSufficient && isValidInvoiceTerms;
2928
+ },
2929
+
2930
+ //31: BatchSettlement
2931
+ validateBatchSettlement: (sender, params, txid, block) =>{
2932
+
2933
+ },
2934
+
2935
+ // 32: Batch Move Zk Rollup
2936
+ validateBatchMoveZkRollup: (params, zkVerifier, tallyMap) => {
2937
+ // Verify the zk proof with the zkVerifier
2938
+ const isZkProofValid = zkVerifier.verifyProof(params.zkProof);
2939
+ // Check the validity of the payment and data logistics within the ordinals
2940
+ const arePaymentsValid = tallyMap.arePaymentsValid(params.payments);
2941
+
2942
+ return isZkProofValid && arePaymentsValid;
2943
+ }
2944
+ };
2945
+
2946
+ const crypto = require("crypto");
2947
+
2948
+ // Network parameters
2949
+ const networks = {
2950
+ bitcoin: {
2951
+ P2PKH: 0x00,
2952
+ P2SH: 0x05,
2953
+ bech32: "bc",
2954
+ },
2955
+ litecoin: {
2956
+ P2PKH: 0x30,
2957
+ P2SH: 0x32,
2958
+ bech32: "ltc",
2959
+ },
2960
+ dogecoin: {
2961
+ P2PKH: 0x1E,
2962
+ P2SH: 0x16,
2963
+ },
2964
+ testnet: {
2965
+ bitcoin: {
2966
+ P2PKH: 0x6F,
2967
+ P2SH: 0xC4,
2968
+ bech32: "tb",
2969
+ },
2970
+ litecoin: {
2971
+ P2PKH: 0x6F,
2972
+ P2SH: 0x3A,
2973
+ bech32: "tltc",
2974
+ },
2975
+ dogecoin: {
2976
+ P2PKH: 0x71,
2977
+ P2SH: 0xC4,
2978
+ },
2979
+ },
2980
+ };
2981
+
2982
+ // Base58 alphabet
2983
+ const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
2984
+
2985
+ // Base58 decoding
2986
+ function decodeBase58(address) {
2987
+ let decoded = BigInt(0);
2988
+ for (const char of address) {
2989
+ const index = BASE58_ALPHABET.indexOf(char);
2990
+ if (index === -1) {
2991
+ throw new Error("Invalid Base58 character");
2992
+ }
2993
+ decoded = decoded * BigInt(58) + BigInt(index);
2994
+ }
2995
+ const hex = decoded.toString(16);
2996
+ const padding = address.match(/^1+/) ? address.match(/^1+/)[0].length : 0;
2997
+ return Buffer.from("00".repeat(padding) + hex.padStart(50, "0"), "hex");
2998
+ }
2999
+
3000
+ // Validate checksum for Base58 addresses
3001
+ function validateBase58Checksum(address, versionByte) {
3002
+ try {
3003
+ const decoded = decodeBase58(address);
3004
+ const version = decoded[0];
3005
+ const checksum = decoded.slice(-4);
3006
+ const body = decoded.slice(0, -4);
3007
+ const validChecksum = crypto
3008
+ .createHash("sha256")
3009
+ .update(crypto.createHash("sha256").update(body).digest())
3010
+ .digest()
3011
+ .slice(0, 4);
3012
+ return version === versionByte && checksum.equals(validChecksum);
3013
+ } catch (error) {
3014
+ return false;
3015
+ }
3016
+ }
3017
+
3018
+ const {bech32} = require("bech32");
3019
+
3020
+ // Validate Bech32 addresses with checksum
3021
+ function validateBech32(address, hrp) {
3022
+ try {
3023
+ const { prefix } = bech32.decode(address); // Decode Bech32 address
3024
+ return prefix === hrp; // Check if the prefix matches
3025
+ } catch (error) {
3026
+ return false; // Invalid Bech32 address
3027
+ }
3028
+ }
3029
+
3030
+ function validateAddress(address) {
3031
+ if (!address || typeof address !== "string") {
3032
+ console.log("Invalid address provided "+address);
3033
+ }
3034
+
3035
+ // Select the network based on the address prefix
3036
+ let network = null;
3037
+
3038
+ for (const [networkName, netConfig] of Object.entries(networks)) {
3039
+ const { P2PKH, P2SH, bech32 } = netConfig;
3040
+ //console.log('validating address form '+JSON.stringify(netConfig))
3041
+ // Match based on the prefix
3042
+ if (
3043
+ (address.startsWith("1") && P2PKH === 0x00) || // Bitcoin P2PKH
3044
+ (address.startsWith("L") && P2PKH === 0x30) || // Litecoin P2PKH
3045
+ (address.startsWith("D") && P2PKH === 0x1E) || // Dogecoin P2PKH
3046
+ (address.startsWith("m") || address.startsWith("n") || address.startsWith("2")) || // Testnets
3047
+ (address.startsWith("3") && P2SH === 0x05) || // Bitcoin P2SH
3048
+ (address.startsWith("M") && P2SH === 0x32) || // Litecoin P2SH
3049
+ (bech32 && address.toLowerCase().startsWith(bech32))
3050
+ ) {
3051
+ network = netConfig;
3052
+ break;
3053
+ }
3054
+ }
3055
+
3056
+ if (!network) {
3057
+ return false; // Address prefix doesn't match any network
3058
+ }
3059
+
3060
+ const { P2PKH, P2SH, bech32 } = network;
3061
+
3062
+ if (address.startsWith("1") || address.startsWith("L") || address.startsWith("D") || address.startsWith("m") || address.startsWith("n")) {
3063
+ // Validate P2PKH
3064
+ return validateBase58Checksum(address, P2PKH);
3065
+ } else if (address.startsWith("3") || address.startsWith("M") || address.startsWith("2")) {
3066
+ // Validate P2SH
3067
+ return validateBase58Checksum(address, P2SH);
3068
+ } else if (bech32 && address.toLowerCase().startsWith(bech32)) {
3069
+ // Validate bech32
3070
+ console.log('validating bech32 '+bech32 +' '+address)
3071
+ return validateBech32(address, bech32);
3072
+ }
3073
+
3074
+ return false; // Invalid address format
3075
+ }
3076
+
3077
+ module.exports = Validity;