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