@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.
- package/.claude/settings.local.json +13 -0
- package/.claude/skills/tl-algo/SKILL.md +255 -0
- package/.gitattributes +2 -0
- package/.github/workflows/publish.yaml +26 -0
- package/4mm.js +163 -0
- package/LICENSE +21 -0
- package/NPMSwapRefactor.zip +0 -0
- package/README.md +217 -0
- package/address.sh +26 -0
- package/algoAPI.js +581 -0
- package/analyzepsbt.js +92 -0
- package/apiEx.js +99 -0
- package/bb_hyperscalper.js +290 -0
- package/bbo_demo.js +111 -0
- package/buyer.js +622 -0
- package/client.js +50 -0
- package/createTxTest.js +26 -0
- package/createWallet.js +75 -0
- package/daytrader.js +531 -0
- package/decodeTest.js +69 -0
- package/fundingManager.js +144 -0
- package/index.js +4 -0
- package/listener.js +27 -0
- package/litecoreTxBuilder.js +1128 -0
- package/mmEx.js +356 -0
- package/networks.js +51 -0
- package/orderbook.js +200 -0
- package/package.json +34 -0
- package/perTradeQueue.js +36 -0
- package/projectsTLNPMTLNPM/package-lock.json +162 -0
- package/projectsTLNPMTLNPM/package.json +5 -0
- package/quick.js +32 -0
- package/quickFut.js +37 -0
- package/quickSell.js +37 -0
- package/relayerClient.js +117 -0
- package/run4mm.js +80 -0
- package/run_bbo_tracker.js +241 -0
- package/seller.js +443 -0
- package/session.js +45 -0
- package/setup-lin-ltc.sh +139 -0
- package/setup-lin.sh +203 -0
- package/setup-win-ltc.bat +108 -0
- package/setup-win.bat +167 -0
- package/spam_screamer_futures.js +222 -0
- package/tradelayer.js/.gitattributes +2 -0
- package/tradelayer.js/README.md +2 -0
- package/tradelayer.js/oldTests/activationTest.js +6 -0
- package/tradelayer.js/oldTests/base58.test.js +23 -0
- package/tradelayer.js/oldTests/base64Decode.test.js +16 -0
- package/tradelayer.js/oldTests/blocksRefactor.js +140 -0
- package/tradelayer.js/oldTests/checkVestBalance.js +25 -0
- package/tradelayer.js/oldTests/consensusHashProto.js +151 -0
- package/tradelayer.js/oldTests/contractOrderbook.js +243 -0
- package/tradelayer.js/oldTests/createPayload.js +0 -0
- package/tradelayer.js/oldTests/createTestnetAddr.js +43 -0
- package/tradelayer.js/oldTests/decode.js +205 -0
- package/tradelayer.js/oldTests/decodeTest.js +50 -0
- package/tradelayer.js/oldTests/displayTallyMap.js +19 -0
- package/tradelayer.js/oldTests/encodeDecode.js +340 -0
- package/tradelayer.js/oldTests/expressTest.js +29 -0
- package/tradelayer.js/oldTests/extractBlocksVanilla.js +214 -0
- package/tradelayer.js/oldTests/extractBlocksVanillaa.js +179 -0
- package/tradelayer.js/oldTests/extractPubkeyTest.js +60 -0
- package/tradelayer.js/oldTests/fillInputCacheProto.js +111 -0
- package/tradelayer.js/oldTests/getRawTxTest.js +22 -0
- package/tradelayer.js/oldTests/indexTest.js +26 -0
- package/tradelayer.js/oldTests/initTokensTest.js +32 -0
- package/tradelayer.js/oldTests/interfaceChild.js +129 -0
- package/tradelayer.js/oldTests/listenerChild.js +112 -0
- package/tradelayer.js/oldTests/opdecode.js +26 -0
- package/tradelayer.js/oldTests/options.js +79 -0
- package/tradelayer.js/oldTests/optxtest.js +116 -0
- package/tradelayer.js/oldTests/optxtest1.js +64 -0
- package/tradelayer.js/oldTests/oracle.test.js +32 -0
- package/tradelayer.js/oldTests/orderbook.test.js +36 -0
- package/tradelayer.js/oldTests/parsing.js +93 -0
- package/tradelayer.js/oldTests/payload.js +13 -0
- package/tradelayer.js/oldTests/persistenceUnitTest.js +23 -0
- package/tradelayer.js/oldTests/property.test.js +53 -0
- package/tradelayer.js/oldTests/propertyLevel.js +75 -0
- package/tradelayer.js/oldTests/propertyTest.js +32 -0
- package/tradelayer.js/oldTests/queryAddressTest.js +17 -0
- package/tradelayer.js/oldTests/salter.js +14 -0
- package/tradelayer.js/oldTests/tally.js +81 -0
- package/tradelayer.js/oldTests/tally.test.js +48 -0
- package/tradelayer.js/oldTests/tally2.js +124 -0
- package/tradelayer.js/oldTests/tally3.js +142 -0
- package/tradelayer.js/oldTests/tallyDiag.js +38 -0
- package/tradelayer.js/oldTests/testGetRaw.js +40 -0
- package/tradelayer.js/oldTests/testHexConvert.js +47 -0
- package/tradelayer.js/oldTests/testNewEncoding.js +96 -0
- package/tradelayer.js/oldTests/testNewEncoding2.js +113 -0
- package/tradelayer.js/oldTests/testNewEncoding3 +112 -0
- package/tradelayer.js/oldTests/testNewEncoding3.js +168 -0
- package/tradelayer.js/oldTests/testOPReturn.js +102 -0
- package/tradelayer.js/oldTests/testPayload.js +23 -0
- package/tradelayer.js/oldTests/testRaw.js +50 -0
- package/tradelayer.js/oldTests/testSendTooMuch.js +20 -0
- package/tradelayer.js/oldTests/testTxBuild +28 -0
- package/tradelayer.js/oldTests/testTxBuild.js +42 -0
- package/tradelayer.js/oldTests/tokenOrderbook.js +243 -0
- package/tradelayer.js/oldTests/txUtilsA.js +515 -0
- package/tradelayer.js/oldTests/validityUnitTest.js +53 -0
- package/tradelayer.js/oldTests/vaults.js +72 -0
- package/tradelayer.js/oldTests/volumeIndex.js +117 -0
- package/tradelayer.js/oldTests/volumeIndex2.js +88 -0
- package/tradelayer.js/output_base64.txt +1 -0
- package/tradelayer.js/package-lock.json +9967 -0
- package/tradelayer.js/package.json +61 -0
- package/tradelayer.js/server/index.js +88 -0
- package/tradelayer.js/server/litecoind.exe +0 -0
- package/tradelayer.js/src/activation.js +303 -0
- package/tradelayer.js/src/adjuster.js +77 -0
- package/tradelayer.js/src/amm.js +400 -0
- package/tradelayer.js/src/base256.js +55 -0
- package/tradelayer.js/src/base94.js +79 -0
- package/tradelayer.js/src/channels.js +1163 -0
- package/tradelayer.js/src/clearing.js +3109 -0
- package/tradelayer.js/src/clearlist.js +364 -0
- package/tradelayer.js/src/client.js +295 -0
- package/tradelayer.js/src/consensus.js +613 -0
- package/tradelayer.js/src/contractRegistry.js +964 -0
- package/tradelayer.js/src/db.js +89 -0
- package/tradelayer.js/src/init.js +24 -0
- package/tradelayer.js/src/insurance.js +347 -0
- package/tradelayer.js/src/interface.js +218 -0
- package/tradelayer.js/src/interfaceExpress.js +178 -0
- package/tradelayer.js/src/iou.js +509 -0
- package/tradelayer.js/src/listener.js +226 -0
- package/tradelayer.js/src/logic.js +1702 -0
- package/tradelayer.js/src/main.js +927 -0
- package/tradelayer.js/src/marginMap.js +2165 -0
- package/tradelayer.js/src/options.js +126 -0
- package/tradelayer.js/src/oracle.js +394 -0
- package/tradelayer.js/src/orderbook.js +4123 -0
- package/tradelayer.js/src/persistence.js +554 -0
- package/tradelayer.js/src/property.js +411 -0
- package/tradelayer.js/src/reOrg.js +41 -0
- package/tradelayer.js/src/scaling.js +145 -0
- package/tradelayer.js/src/tally.js +1275 -0
- package/tradelayer.js/src/tradeHistoryManager.js +552 -0
- package/tradelayer.js/src/txDecoder.js +584 -0
- package/tradelayer.js/src/txEncoder.js +610 -0
- package/tradelayer.js/src/txIndex.js +502 -0
- package/tradelayer.js/src/txUtils.js +1392 -0
- package/tradelayer.js/src/types.js +429 -0
- package/tradelayer.js/src/validity.js +3077 -0
- package/tradelayer.js/src/vaults.js +430 -0
- package/tradelayer.js/src/vesting.js +491 -0
- package/tradelayer.js/src/volumeIndex.js +618 -0
- package/tradelayer.js/src/walletInterface.js +220 -0
- package/tradelayer.js/src/walletListener.js +665 -0
- package/tradelayer.js/tests/256decode.js +82 -0
- package/tradelayer.js/tests/UTXOracle.js +205 -0
- package/tradelayer.js/tests/base94test.js +23 -0
- package/tradelayer.js/tests/cancelTxTest.js +62 -0
- package/tradelayer.js/tests/contractInterfaceTest.js +48 -0
- package/tradelayer.js/tests/decimalTest.js +65 -0
- package/tradelayer.js/tests/decoderTest.js +100 -0
- package/tradelayer.js/tests/deltaCount.js +47 -0
- package/tradelayer.js/tests/deltaCount2.js +60 -0
- package/tradelayer.js/tests/interfaceTest.js +37 -0
- package/tradelayer.js/tests/mainTest.js +53 -0
- package/tradelayer.js/tests/makeActivationTest.js +24 -0
- package/tradelayer.js/tests/maxHeightTest.js +49 -0
- package/tradelayer.js/tests/reverseHash.js +72 -0
- package/tradelayer.js/tests/sensitiveConsoleOutput.txt +267 -0
- package/tradelayer.js/tests/tallyTest.js +40 -0
- package/tradelayer.js/tests/testBuybacks.js +46 -0
- package/tradelayer.js/tests/testCodeHash.js +49 -0
- package/tradelayer.js/tests/testConsensusHash.js +91 -0
- package/tradelayer.js/tests/testDecode.js +30 -0
- package/tradelayer.js/tests/testEncodingLengths.js +129 -0
- package/tradelayer.js/tests/testGetTx +32 -0
- package/tradelayer.js/tests/testGetTx.js +32 -0
- package/tradelayer.js/tests/testHexHash.js +32 -0
- package/tradelayer.js/tests/testIndexHash.js +35 -0
- package/tradelayer.js/tests/testInitContracts.js +38 -0
- package/tradelayer.js/tests/testMaxConsensus.js +12 -0
- package/tradelayer.js/tests/testMaxSynth.js +44 -0
- package/tradelayer.js/tests/testMint.js +21 -0
- package/tradelayer.js/tests/testNetwork.js +33 -0
- package/tradelayer.js/tests/testOrderbookLoad.js +62 -0
- package/tradelayer.js/tests/testRebates.js +32 -0
- package/tradelayer.js/tests/testRedeem.js +22 -0
- package/tradelayer.js/tests/testTokenTrade.js +39 -0
- package/tradelayer.js/tests/testTxBuild.js +42 -0
- package/tradelayer.js/tests/testUTXOTrade.js +27 -0
- package/tradelayer.js/tests/tokenTradeHistory.js +27 -0
- package/tradelayer.js/tests/tradeFutures.js +40 -0
- package/tradelayer.js/tests/tradeHistoryExample.js +35 -0
- package/tradelayer.js/tests/tradeHistoryLoad.js +15 -0
- package/tradelayer.js/tests/txScanTest.js +134 -0
- package/tradelayer.js/tests/validateTest.js +136 -0
- package/tradelayer.js/tests/vestingTest.js +37 -0
- package/tradelayer.js/utils/activateMainnet.js +59 -0
- package/tradelayer.js/utils/activateMainnetDoge.js +63 -0
- package/tradelayer.js/utils/autocompactdb.js +23 -0
- package/tradelayer.js/utils/base64toHex.js +32 -0
- package/tradelayer.js/utils/broadcastDoge.js +38 -0
- package/tradelayer.js/utils/calcRedeem.js +19 -0
- package/tradelayer.js/utils/checkNetwork.js +27 -0
- package/tradelayer.js/utils/createAddress.js +48 -0
- package/tradelayer.js/utils/createAttestation.js +133 -0
- package/tradelayer.js/utils/createContract.js +118 -0
- package/tradelayer.js/utils/createOracle.js +94 -0
- package/tradelayer.js/utils/createwallet.js +20 -0
- package/tradelayer.js/utils/crossFuturesTrades.js +57 -0
- package/tradelayer.js/utils/crossTokenTrades.js +62 -0
- package/tradelayer.js/utils/dumpPriv.js +29 -0
- package/tradelayer.js/utils/generateChannel.js +34 -0
- package/tradelayer.js/utils/getInfo.js +21 -0
- package/tradelayer.js/utils/hardWipe.js +20 -0
- package/tradelayer.js/utils/hexTo64.js +16 -0
- package/tradelayer.js/utils/importAddress.js +28 -0
- package/tradelayer.js/utils/importpriv.js +20 -0
- package/tradelayer.js/utils/issueOracleContract.js +67 -0
- package/tradelayer.js/utils/issueTokens.js +41 -0
- package/tradelayer.js/utils/listunspent.js +66 -0
- package/tradelayer.js/utils/litecoinClient.js +30 -0
- package/tradelayer.js/utils/loadwallet.js +20 -0
- package/tradelayer.js/utils/publishOracle.js +113 -0
- package/tradelayer.js/utils/sendActivation.js +21 -0
- package/tradelayer.js/utils/sendChannelContractTrade.js +34 -0
- package/tradelayer.js/utils/sendChannelTokenTrade.js +34 -0
- package/tradelayer.js/utils/sendCommit.js +24 -0
- package/tradelayer.js/utils/sendDoge.js +62 -0
- package/tradelayer.js/utils/sendDogeMain.js +67 -0
- package/tradelayer.js/utils/sendDogeTx.js +46 -0
- package/tradelayer.js/utils/sendLTC.js +63 -0
- package/tradelayer.js/utils/sendMainnet.js +62 -0
- package/tradelayer.js/utils/sendTransfer.js +19 -0
- package/tradelayer.js/utils/sendVestTest.js +88 -0
- package/tradelayer.js/utils/sendWithdrawal.js +26 -0
- package/tradelayer.js/utils/simpleStart.js +8 -0
- package/tradelayer.js/utils/startStop.js +27 -0
- package/tradelayer.js/utils/structuredTrades.js +136 -0
- package/tradelayer.js/utils/verifySignature.js +90 -0
- package/tradelayer.js/utils/verifyWitnessAndScriptPubkey.js +41 -0
- package/tradelayer.js/utils/walletCache.js +172 -0
- package/tradelayer.js/utils/walletContractInterface.js +48 -0
- package/tradelayer.js/utils/walletFetchTxs.js +66 -0
- package/tradelayer.js/utils/walletUtils.js +97 -0
- package/tradelayer.js/utils/wipeDB.js +55 -0
- package/tradelayer.js/utils/wipeDBNotTx.js +50 -0
- package/txEncoder.js +529 -0
- package/utility.js +28 -0
- package/verifymessage.js +38 -0
- 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&¶ms.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 &¶ms.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&¶ms.sell==false)
|
|
1497
|
+
const isSellerFlippingPosition = Boolean(params.amount>existingPosition.contracts&&existingPosition.contracts>0&¶ms.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&¶ms.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&¶ms.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;
|