@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,364 @@
|
|
|
1
|
+
const dbInstance = require('./db.js');
|
|
2
|
+
|
|
3
|
+
class clearlistManager {
|
|
4
|
+
static clearlists = new Map();
|
|
5
|
+
static banlist = ["US", "KP", "SY", "RU", "IR", "CU"];
|
|
6
|
+
static xpubCache = new Map(); // xpub -> derived addresses array
|
|
7
|
+
|
|
8
|
+
static async createClearlist(adminAddress, name, url, description, backupAddress,id) {
|
|
9
|
+
|
|
10
|
+
if(!id){
|
|
11
|
+
id = await this.getNextId();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const clearlistData = {
|
|
15
|
+
id: id,
|
|
16
|
+
admin: adminAddress,
|
|
17
|
+
name: name,
|
|
18
|
+
description: description,
|
|
19
|
+
backup: backupAddress
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const base = await dbInstance.getDatabase('clearlists')
|
|
23
|
+
await base.updateAsync(
|
|
24
|
+
{ _id: id },
|
|
25
|
+
{ $set: { data: clearlistData } },
|
|
26
|
+
{ upsert: true }
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
return clearlistData;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static async loadClearlists() {
|
|
33
|
+
try {
|
|
34
|
+
const base = await dbInstance.getDatabase('clearlists')
|
|
35
|
+
const clearLists = await base.findAsync({});
|
|
36
|
+
clearLists.forEach(({ _id, data }) => {
|
|
37
|
+
this.clearlists.set(_id, data);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return clearLists
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error('Error loading clearlists from the database:', error);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static async getClearlistById(clearlistId) {
|
|
47
|
+
try {
|
|
48
|
+
// Load all clearlists into memory
|
|
49
|
+
await this.loadClearlists();
|
|
50
|
+
|
|
51
|
+
// Check if the clearlistId exists in the Map
|
|
52
|
+
if (this.clearlists.has(clearlistId)) {
|
|
53
|
+
const clearlistData = this.clearlists.get(clearlistId);
|
|
54
|
+
console.log(`Clearlist found: ID ${clearlistId}`, clearlistData);
|
|
55
|
+
return clearlistData;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.log(`Clearlist ID ${clearlistId} not found.`);
|
|
59
|
+
return false; // Return false if the clearlistId doesn't exist
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error(`Error finding clearlist with ID ${clearlistId}:`, error.message);
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
static async getList(id) {
|
|
68
|
+
try {
|
|
69
|
+
const base = await dbInstance.getDatabase('attestations')
|
|
70
|
+
const clearlist = await base.findOneAsync({ _id: id });
|
|
71
|
+
if (clearlist) {
|
|
72
|
+
return clearlist.data;
|
|
73
|
+
} else {
|
|
74
|
+
console.log(`No clearlist found for ID: ${id}`);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error(`Error loading clearlist with ID ${id}:`, error);
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
static async verifyAdmin(clearlistId, adminAddress) {
|
|
84
|
+
const clearlist = this.clearlists.get(clearlistId);
|
|
85
|
+
|
|
86
|
+
if (!clearlist) {
|
|
87
|
+
throw new Error('Clearlist not found');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return clearlist.adminAddress === adminAddress;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
static async updateAdmin(clearlistId, newAdminAddress, backup) {
|
|
94
|
+
const clearlistKey = `${clearlistId}`;
|
|
95
|
+
const clearlist = this.clearlists.get(clearlistId);
|
|
96
|
+
|
|
97
|
+
if (!clearlist) {
|
|
98
|
+
throw new Error('Clearlist not found');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (backup) {
|
|
102
|
+
clearlist.backupAddress = newAdminAddress;
|
|
103
|
+
} else {
|
|
104
|
+
clearlist.adminAddress = newAdminAddress;
|
|
105
|
+
}
|
|
106
|
+
await this.db.updateAsync({ _id: clearlistKey }, { $set: { data: clearlist } });
|
|
107
|
+
this.clearlists.set(clearlistId, clearlist);
|
|
108
|
+
|
|
109
|
+
console.log(`Clearlist ID ${clearlistId} admin updated to ${newAdminAddress}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
static async getNextId() {
|
|
113
|
+
let maxId = 0;
|
|
114
|
+
await this.loadClearlists();
|
|
115
|
+
for (const key of this.clearlists.keys()) {
|
|
116
|
+
const currentId = parseInt(key);
|
|
117
|
+
if (currentId > maxId) {
|
|
118
|
+
maxId = currentId;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return maxId + 1;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
static async addAttestation(clearlistId, address, metaData, block) {
|
|
125
|
+
const attestationId = address;
|
|
126
|
+
|
|
127
|
+
// Detect xpub prefix in metaData
|
|
128
|
+
let xpub = null;
|
|
129
|
+
if (typeof metaData === 'string' && metaData.startsWith('xpub:')) {
|
|
130
|
+
xpub = metaData.slice(5); // strip 'xpub:' prefix
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const attestationData = {
|
|
134
|
+
listId: clearlistId,
|
|
135
|
+
address: address,
|
|
136
|
+
status: 'active',
|
|
137
|
+
data: metaData,
|
|
138
|
+
xpub: xpub,
|
|
139
|
+
timestamp: block
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const base = await dbInstance.getDatabase('attestations');
|
|
143
|
+
await base.updateAsync(
|
|
144
|
+
{ _id: attestationId },
|
|
145
|
+
{ $set: { data: attestationData } },
|
|
146
|
+
{ upsert: true }
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
return attestationId;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
static async addAttestationWithXpub(clearlistId, address, metaData, xpub, block) {
|
|
153
|
+
const attestationId = address;
|
|
154
|
+
const attestationData = {
|
|
155
|
+
listId: clearlistId,
|
|
156
|
+
address: address,
|
|
157
|
+
status: 'active',
|
|
158
|
+
data: metaData,
|
|
159
|
+
xpub: xpub || null,
|
|
160
|
+
timestamp: block
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const base = await dbInstance.getDatabase('attestations');
|
|
164
|
+
await base.updateAsync(
|
|
165
|
+
{ _id: attestationId },
|
|
166
|
+
{ $set: { data: attestationData } },
|
|
167
|
+
{ upsert: true }
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
return attestationId;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Derive first N addresses from an xpub (receive chain m/0/0..m/0/19 and change chain m/1/0..m/1/19).
|
|
175
|
+
* Uses bitcore-lib-ltc HDPublicKey. Results are cached per xpub.
|
|
176
|
+
*/
|
|
177
|
+
static deriveAddressesFromXpub(xpub, network) {
|
|
178
|
+
if (this.xpubCache.has(xpub)) {
|
|
179
|
+
return this.xpubCache.get(xpub);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const addresses = [];
|
|
183
|
+
const GAP_LIMIT = 20;
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
// Try bitcore-lib-ltc first (LTC network)
|
|
187
|
+
let HDPublicKey, Networks;
|
|
188
|
+
try {
|
|
189
|
+
const litecore = require('bitcore-lib-ltc');
|
|
190
|
+
HDPublicKey = litecore.HDPublicKey;
|
|
191
|
+
Networks = litecore.Networks;
|
|
192
|
+
} catch (e) {
|
|
193
|
+
const bitcore = require('bitcore-lib');
|
|
194
|
+
HDPublicKey = bitcore.HDPublicKey;
|
|
195
|
+
Networks = bitcore.Networks;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const hdPubKey = new HDPublicKey(xpub);
|
|
199
|
+
|
|
200
|
+
// Derive receive chain (m/0/i) and change chain (m/1/i)
|
|
201
|
+
for (const chain of [0, 1]) {
|
|
202
|
+
const chainKey = hdPubKey.deriveChild(chain);
|
|
203
|
+
for (let i = 0; i < GAP_LIMIT; i++) {
|
|
204
|
+
const derived = chainKey.deriveChild(i);
|
|
205
|
+
const addr = derived.publicKey.toAddress().toString();
|
|
206
|
+
addresses.push(addr);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
} catch (e) {
|
|
210
|
+
console.error('Error deriving addresses from xpub:', e.message);
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
this.xpubCache.set(xpub, addresses);
|
|
215
|
+
return addresses;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Check if an address is in a clearlist either directly or via xpub derivation.
|
|
220
|
+
*/
|
|
221
|
+
static async isAddressInClearlistOrDerived(clearlistId, address) {
|
|
222
|
+
// First check direct attestation
|
|
223
|
+
const direct = await this.isAddressInClearlist(clearlistId, address);
|
|
224
|
+
if (direct) return true;
|
|
225
|
+
|
|
226
|
+
// Check xpub-bearing attestations in this clearlist
|
|
227
|
+
const base = await dbInstance.getDatabase('attestations');
|
|
228
|
+
const xpubAttestations = await base.findAsync({
|
|
229
|
+
'data.listId': clearlistId,
|
|
230
|
+
'data.status': 'active',
|
|
231
|
+
'data.xpub': { $exists: true, $ne: null }
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
for (const att of xpubAttestations) {
|
|
235
|
+
const xpub = att.data.xpub;
|
|
236
|
+
if (!xpub) continue;
|
|
237
|
+
const derivedAddresses = this.deriveAddressesFromXpub(xpub);
|
|
238
|
+
if (derivedAddresses.includes(address)) {
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
static async revokeAttestation(attestationId, targetAddress, revokeReason,block) {
|
|
247
|
+
const attestationKey = `attestation:${targetAddress}`;
|
|
248
|
+
const base= await dbInstance.getDatabase('attestations')
|
|
249
|
+
const attestation = await base.findOneAsync({ _id: attestationKey });
|
|
250
|
+
|
|
251
|
+
if (!attestation) {
|
|
252
|
+
throw new Error('Attestation not found');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
attestation.data.status = 'revoked';
|
|
256
|
+
attestation.data.id = attestationId;
|
|
257
|
+
attestation.data.revokeReason = revokeReason;
|
|
258
|
+
attestation.data.timestamp = block
|
|
259
|
+
|
|
260
|
+
await this.attestationsDb.updateAsync(
|
|
261
|
+
{ _id: attestationKey },
|
|
262
|
+
{ $set: { data: attestation.data } }
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
return attestationId;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
static async getCountryCodeByAddress(address) {
|
|
269
|
+
try {
|
|
270
|
+
const base = await dbInstance.getDatabase('attestations');
|
|
271
|
+
|
|
272
|
+
// Fetch all attestations for the given address
|
|
273
|
+
const attestations = await base.findAsync({ 'data.address': address });
|
|
274
|
+
|
|
275
|
+
if (!attestations || attestations.length === 0) {
|
|
276
|
+
console.log(`No attestations found for address: ${address}`);
|
|
277
|
+
return null; // No attestations for this address
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Loop through attestations to find one with clearListId: 0 and a valid countryCode
|
|
281
|
+
for (const attestation of attestations) {
|
|
282
|
+
const { listId, data} = attestation.data;
|
|
283
|
+
|
|
284
|
+
if (listId === 0 && data) {
|
|
285
|
+
return {
|
|
286
|
+
address,
|
|
287
|
+
countryCode: data,
|
|
288
|
+
blockHeight: attestation.data.blockHeight || null, // Optional blockHeight in metadata
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// If no valid attestation with clearListId: 0 and countryCode is found
|
|
294
|
+
console.log(`No valid attestation with clearListId 0 and country code found for address: ${address}`);
|
|
295
|
+
return null;
|
|
296
|
+
} catch (error) {
|
|
297
|
+
console.error(`Error fetching country code for address ${address}:`, error);
|
|
298
|
+
return null; // Gracefully return null on error
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
static async setBanlist(banlistArray,block) {
|
|
303
|
+
try {
|
|
304
|
+
const base = await dbInstance.getDatabase('clearlists');
|
|
305
|
+
await base.updateAsync(
|
|
306
|
+
{ _id: 'globalBanlist' }, // Fixed ID for the banlist entity
|
|
307
|
+
{ $set: { data: banlistArray, timestamp: block } },
|
|
308
|
+
{ upsert: true }
|
|
309
|
+
);
|
|
310
|
+
console.log('Banlist updated successfully.');
|
|
311
|
+
} catch (error) {
|
|
312
|
+
console.error('Error updating Banlist in database:', error);
|
|
313
|
+
throw error;
|
|
314
|
+
}
|
|
315
|
+
this.banlist= banlistArray
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
static async getBanlist() {
|
|
319
|
+
try {
|
|
320
|
+
const base = await dbInstance.getDatabase('clearlists');
|
|
321
|
+
const banlist = await base.findAsync({ _id: 'globalBanlist' });
|
|
322
|
+
if (banlist) {
|
|
323
|
+
return banlist.data; // Return the banlist array
|
|
324
|
+
} else {
|
|
325
|
+
console.log('No Banlist found in the database.');
|
|
326
|
+
return null; // Return null if no Banlist exists
|
|
327
|
+
}
|
|
328
|
+
} catch (error) {
|
|
329
|
+
console.error('Error fetching Banlist from database:', error);
|
|
330
|
+
throw error;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
static async getAttestations(clearlistId) {
|
|
335
|
+
const base = await dbInstance.getDatabase('attestations')
|
|
336
|
+
return await base.findAsync({ 'data.clearlistId': clearlistId });
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
static async getAttestationHistory(address, clearlistId) {
|
|
340
|
+
const base = await dbInstance.getDatabase('attestations');
|
|
341
|
+
|
|
342
|
+
// Fetch all matching records for the address and listId
|
|
343
|
+
const records = await base.findAsync({ 'data.address': address, 'data.listId': clearlistId });
|
|
344
|
+
|
|
345
|
+
// Sort by timestamp (descending)
|
|
346
|
+
const sortedRecords = records.sort((a, b) => b.data.timestamp - a.data.timestamp);
|
|
347
|
+
|
|
348
|
+
return sortedRecords; // Return sorted array
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
static async isAddressInClearlist(clearlistId, address) {
|
|
353
|
+
|
|
354
|
+
const base = await dbInstance.getDatabase('attestations')
|
|
355
|
+
const attestations = await base.findAsync({
|
|
356
|
+
'data.clearlistId': clearlistId,
|
|
357
|
+
'data.address': address,
|
|
358
|
+
'data.status': 'active'
|
|
359
|
+
});
|
|
360
|
+
return attestations.length > 0;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
module.exports = clearlistManager;
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
const util = require('util');
|
|
2
|
+
const Litecoin = require('litecoin');
|
|
3
|
+
const Bitcoin = require('bitcoin');
|
|
4
|
+
const Doge = require('dogecoind-rpc');
|
|
5
|
+
|
|
6
|
+
// --- ENV bootstrap helpers (safe if .env is missing) ---
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
function loadDotenvFromKnownLocations() {
|
|
10
|
+
// lazy-load to avoid hard dependency if dotenv isn't installed yet
|
|
11
|
+
let dotenv;
|
|
12
|
+
try { dotenv = require('dotenv'); } catch { return; }
|
|
13
|
+
|
|
14
|
+
// 1) repo-style: one level up relative to this file (…/ .env)
|
|
15
|
+
const repoEnv = path.join(__dirname, '..', '.env');
|
|
16
|
+
dotenv.config({ path: repoEnv, override: false });
|
|
17
|
+
|
|
18
|
+
// 2) fallback: current working directory (.env)
|
|
19
|
+
dotenv.config({ override: false });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getRpcBootstrapFromEnv(defaultChain = 'LTC') {
|
|
23
|
+
loadDotenvFromKnownLocations();
|
|
24
|
+
|
|
25
|
+
const env = process.env;
|
|
26
|
+
const ENV_CHAIN = (env.CHAIN || '').toUpperCase();
|
|
27
|
+
const AUTODETECT = (env.AUTODETECT || '1') !== '0'; // AUTODETECT=0 → lock env, skip discovery
|
|
28
|
+
|
|
29
|
+
const DEFAULT_PORT = { BTC: 8332, BTCTEST: 18332, LTC: 9332, LTCTEST: 19332, DOGE: 22555, DOGETEST:44555 };
|
|
30
|
+
const chain = ['BTC', 'LTC', 'DOGE'].includes(ENV_CHAIN) ? ENV_CHAIN : defaultChain;
|
|
31
|
+
|
|
32
|
+
const host = env.RPC_HOST || '127.0.0.1';
|
|
33
|
+
const user = env.RPC_USER || 'user';
|
|
34
|
+
const pass = env.RPC_PASS || 'pass';
|
|
35
|
+
const port = Number(env.RPC_PORT || DEFAULT_PORT[chain]);
|
|
36
|
+
const timeout = Number(env.TIMEOUT_MS || 60000);
|
|
37
|
+
|
|
38
|
+
// If any of CHAIN / RPC_PORT is provided OR AUTODETECT=0, we consider this a "locked" bootstrap.
|
|
39
|
+
const locked = (!AUTODETECT) || !!env.CHAIN || !!env.RPC_PORT;
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
// normalized values
|
|
43
|
+
chain, host, port, user, pass, timeout,
|
|
44
|
+
// whether the caller should skip autodetect and return immediately
|
|
45
|
+
locked,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
let clientInstance = null;
|
|
51
|
+
|
|
52
|
+
class ClientWrapper {
|
|
53
|
+
constructor() {
|
|
54
|
+
if (clientInstance) {
|
|
55
|
+
return clientInstance;
|
|
56
|
+
}
|
|
57
|
+
this.chain = null;
|
|
58
|
+
this.client = null;
|
|
59
|
+
this.initializing = false
|
|
60
|
+
clientInstance = this; // Assign the instance to the singleton variable
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async init() {
|
|
64
|
+
|
|
65
|
+
// If already initializing, wait for the process to finish
|
|
66
|
+
if (this.isInitializing) {
|
|
67
|
+
console.log('Client initialization already in progress. Waiting...');
|
|
68
|
+
return this.waitForInitialization(); // Wait for ongoing initialization to complete
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.isInitializing = true; // Set flag to indicate initialization is in progress
|
|
72
|
+
|
|
73
|
+
if(!this.client){
|
|
74
|
+
const boot = this.getEnvBootstrap('LTCTEST'); // default LTC; change to 'BTC' if you prefer
|
|
75
|
+
|
|
76
|
+
this.config = {
|
|
77
|
+
host: boot.host,
|
|
78
|
+
port: boot.port,
|
|
79
|
+
user: boot.user,
|
|
80
|
+
pass: boot.pass,
|
|
81
|
+
timeout: boot.timeout,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Build a client immediately using the env-chosen CHAIN (or default)
|
|
85
|
+
this.chain = boot.chain;
|
|
86
|
+
this.client = this._createClientByChain(this.chain);
|
|
87
|
+
|
|
88
|
+
// If .env told us to lock (AUTODETECT=0 or CHAIN/RPC_PORT provided), stop here.
|
|
89
|
+
// (No probing needed; this lets desktop/server scripts fully control startup.)
|
|
90
|
+
if (boot.locked) {
|
|
91
|
+
return this.chain;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Wait for the blockchain to finish initial block download and indexing
|
|
95
|
+
let isTest = true
|
|
96
|
+
try {
|
|
97
|
+
const blockchainInfo = await this.getBlockchainInfo();
|
|
98
|
+
isTest = blockchainInfo.chain === 'test';
|
|
99
|
+
console.log('is test '+isTest)
|
|
100
|
+
}catch (error) {
|
|
101
|
+
if (error.code === -28) {
|
|
102
|
+
console.log('Getting the err on the second call.');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const networkInfo = await this.getNetworkInfo();
|
|
107
|
+
console.log('determining chain in init '+JSON.stringify(networkInfo))
|
|
108
|
+
this.chain = this.determineChainFromSubversion(networkInfo.subversion);
|
|
109
|
+
|
|
110
|
+
if (!this.chain) throw new Error('Unable to determine blockchain chain.');
|
|
111
|
+
|
|
112
|
+
this.config.port = isTest
|
|
113
|
+
? (this.chain === 'BTC' ? 18332 : this.chain === 'DOGE' ? 44556 : 19332)
|
|
114
|
+
: (this.chain === 'BTC' ? 8332 : this.chain === 'DOGE' ? 22555 : 9332);
|
|
115
|
+
|
|
116
|
+
this.client = this._createClientByChain(this.chain);
|
|
117
|
+
}
|
|
118
|
+
return this.chain
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async waitForInitialization() {
|
|
122
|
+
while (this.isInitializing) {
|
|
123
|
+
console.log('waiting for initialization')
|
|
124
|
+
await new Promise(resolve => setTimeout(resolve, 100)); // Wait 100ms between checks
|
|
125
|
+
}
|
|
126
|
+
return this.chain; // Return the chain after initialization completes
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// inside your existing class (e.g., RpcClient / whatever it’s named)
|
|
130
|
+
getEnvBootstrap(defaultChain = 'LTC') {
|
|
131
|
+
return getRpcBootstrapFromEnv(defaultChain);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
_createClientByChain(chain) {
|
|
137
|
+
switch (chain) {
|
|
138
|
+
case 'BTC':
|
|
139
|
+
return new Bitcoin.Client(this.config);
|
|
140
|
+
case 'DOGE':
|
|
141
|
+
return new Doge.Client(this.config);
|
|
142
|
+
default:
|
|
143
|
+
return new Litecoin.Client(this.config);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
static async getInstance(txIndex) {
|
|
148
|
+
if (!clientInstance) {
|
|
149
|
+
if(txIndex){console.log('initializing client by way of txIndex')}
|
|
150
|
+
if(this.isInitializing){await this.waitForInitialization()}
|
|
151
|
+
const clientWrapper = new ClientWrapper();
|
|
152
|
+
console.log('constructed client wrapper now init')
|
|
153
|
+
await clientWrapper.init();
|
|
154
|
+
}
|
|
155
|
+
return clientInstance;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
determineChainFromSubversion(subversion, flag) {
|
|
159
|
+
console.log('chain subversion '+subversion+' '+flag )
|
|
160
|
+
subversion = subversion.toLowerCase();
|
|
161
|
+
if (subversion.includes('litecoin')) return 'LTC';
|
|
162
|
+
if (subversion.includes('bitcoin')) return 'BTC';
|
|
163
|
+
if (subversion.includes('dogecoin')) return 'DOGE';
|
|
164
|
+
throw new Error(`Unknown chain in subversion: ${subversion}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
getBlockchainInfo() {
|
|
168
|
+
return util.promisify(this.client.cmd.bind(this.client, 'getblockchaininfo'))();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
getRawTransaction(txId, verbose = true, blockHash) {
|
|
172
|
+
return util.promisify(this.client.cmd.bind(this.client, 'getrawtransaction'))(txId, verbose);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
getnewaddress() {
|
|
176
|
+
return util.promisify(this.client.cmd.bind(this.client, 'getnewaddress'))();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
sendtoaddress(addr,amt){
|
|
180
|
+
return util.promisify(this.client.cmd.bind(this.client, 'sendtoaddress'))(addr,amt)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
getNetworkInfo(){
|
|
184
|
+
return util.promisify(this.client.cmd.bind(this.client, 'getnetworkinfo'))()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
getTransaction(txId) {
|
|
188
|
+
return util.promisify(this.client.cmd.bind(this.client, 'gettransaction'))(txId);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
getBlock(blockHash) {
|
|
192
|
+
return util.promisify(this.client.cmd.bind(this.client, 'getblock'))(blockHash);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
getBlockHash(height) {
|
|
196
|
+
return util.promisify(this.client.cmd.bind(this.client, 'getblockhash'))(height);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
createRawTransaction(...params) {
|
|
200
|
+
return util.promisify(this.client.cmd.bind(this.client, 'createrawtransaction'))(...params);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
listUnspent(...params) {
|
|
204
|
+
return util.promisify(this.client.cmd.bind(this.client, 'listunspent'))(...params);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
decoderawtransaction(...params) {
|
|
208
|
+
return util.promisify(this.client.cmd.bind(this.client, 'decoderawtransaction'))(...params);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
signrawtransactionwithwallet(...params) {
|
|
212
|
+
return util.promisify(this.client.cmd.bind(this.client, 'signrawtransactionwithwallet'))(...params);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
dumpprivkey(...params) {
|
|
216
|
+
return util.promisify(this.client.cmd.bind(this.client, 'dumpprivkey'))(...params);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
sendrawtransaction(...params) {
|
|
220
|
+
return util.promisify(this.client.cmd.bind(this.client, 'sendrawtransaction'))(...params);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
validateAddress(...params) {
|
|
224
|
+
return util.promisify(this.client.cmd.bind(this.client, 'validateaddress'))(...params);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
getBlockCount() {
|
|
228
|
+
return util.promisify(this.client.cmd.bind(this.client, 'getblockcount'))();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
loadWallet(...params) {
|
|
232
|
+
return util.promisify(this.client.cmd.bind(this.client, 'loadwallet'))(...params);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
listlabels(...params) {
|
|
236
|
+
return util.promisify(this.client.cmd.bind(this.client, 'listlabels'))(...params);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
getaddressesbylabel(label) {
|
|
240
|
+
return util.promisify(this.client.cmd.bind(this.client, 'getaddressesbylabel'))(label);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
getwalletinfo() {
|
|
244
|
+
return util.promisify(this.client.cmd.bind(this.client, 'getwalletinfo'))();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
listunspent() {
|
|
248
|
+
return util.promisify(this.client.cmd.bind(this.client, 'listunspent'))();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
// Add this method to the ClientWrapper class
|
|
253
|
+
async verifyClientChain() {
|
|
254
|
+
try {
|
|
255
|
+
const networkInfo = await clientInstance.getNetworkInfo();
|
|
256
|
+
const subversion = networkInfo.subversion.toLowerCase();
|
|
257
|
+
let match;
|
|
258
|
+
if (subversion.includes('litecoin')) {
|
|
259
|
+
match = 'LTC';
|
|
260
|
+
} else if (subversion.includes('bitcoin')) {
|
|
261
|
+
match = 'BTC';
|
|
262
|
+
} else if (subversion.includes('dogecoin')) {
|
|
263
|
+
match = 'DOGE';
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (match && match !== this.chain) {
|
|
267
|
+
console.warn(`Mismatch: Configured chain is ${this.chain}, but detected ${match}.`);
|
|
268
|
+
this.chain = match;
|
|
269
|
+
} else {
|
|
270
|
+
console.log(`Verified chain: ${this.chain}`);
|
|
271
|
+
}
|
|
272
|
+
} catch (error) {
|
|
273
|
+
console.error('Error verifying chain:', error);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async getChain() {
|
|
278
|
+
const bleh= await this.getNetworkInfo(); // Double-check chain type
|
|
279
|
+
console.log('determining chain in get chain')
|
|
280
|
+
return this.determineChainFromSubversion(bleh.subversion, true);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async getTest(){
|
|
284
|
+
const blockchainInfo = await this.getBlockchainInfo();
|
|
285
|
+
return blockchainInfo.chain === 'test';
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
clientInstance = this;
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
// Additional RPC methods as needed...
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Export singleton instance
|
|
295
|
+
module.exports = ClientWrapper;
|