@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,241 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* bbo_tracker_hf.js
|
|
4
|
+
* Keeps exactly 2 live orders (1 BUY at best bid, 1 SELL at best ask).
|
|
5
|
+
* - Cancels & replaces on >= TICK_TOL ticks of movement.
|
|
6
|
+
* - Per-side sequencing so cancels/places never overlap.
|
|
7
|
+
* - Token-bucket rate limit to avoid flooding server.
|
|
8
|
+
* - Skips updates if Binance BBO is stale.
|
|
9
|
+
* - Logs Binance BBO and every place/cancel.
|
|
10
|
+
*
|
|
11
|
+
* Requires:
|
|
12
|
+
* - ccxt installed (for Binance ticker).
|
|
13
|
+
* - ./algoAPI.js exposing class ApiWrapper with:
|
|
14
|
+
* - constructor(host, port, debug, autoConnect, {address, otherAddrs}, network)
|
|
15
|
+
* - sendOrder(orderDetails) -> Promise<string> (UUID)
|
|
16
|
+
* - cancelOrder(uuid) -> Promise<void>
|
|
17
|
+
*
|
|
18
|
+
* Run: node run_bbo_tracker.js
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
const ccxt = require('ccxt');
|
|
23
|
+
const ApiWrapper = require('./algoAPI.js');
|
|
24
|
+
|
|
25
|
+
// ===== Config =====
|
|
26
|
+
const CFG = {
|
|
27
|
+
// TL / server
|
|
28
|
+
TL_WS_HOST: '172.81.181.19',
|
|
29
|
+
TL_WS_PORT: 3001,
|
|
30
|
+
TL_NETWORK: 'LTCTEST',
|
|
31
|
+
TL_ADDR: 'tltc1qh4se4w23draju8ef82vdvelz3zj8egflrg2gve',
|
|
32
|
+
TL_PUB: '0342ded4128b00d324eee8bba8c716fc84db004adb63adbeb19b5ac06f8f3b2ab9',
|
|
33
|
+
|
|
34
|
+
BASE_ID: 0, // LTC
|
|
35
|
+
QUOTE_ID: 5, // USDTt
|
|
36
|
+
|
|
37
|
+
// Market source
|
|
38
|
+
SYMBOL_CCXT: 'LTC/USDT',
|
|
39
|
+
|
|
40
|
+
// Behavior
|
|
41
|
+
POLL_MS: 50, // Binance poll interval
|
|
42
|
+
SIZE: 0.10, // order size
|
|
43
|
+
TICK: 0.0001, // price tick
|
|
44
|
+
EDGE_BPS: 0.0, // pad off BBO (0 = exact mirror)
|
|
45
|
+
|
|
46
|
+
// HF stability
|
|
47
|
+
STALE_MS: 250, // if BBO older than this, skip
|
|
48
|
+
MIN_REPLACE_MS: 50, // min gap per-side between replaces
|
|
49
|
+
TICK_TOL: 1.0, // require >= this many ticks to replace
|
|
50
|
+
MAX_OPS_PER_SEC: 200, // global cap (place+cancel) across both sides
|
|
51
|
+
CANCEL_PLACE_GAP_MS: 10, // small delay after cancel before place
|
|
52
|
+
|
|
53
|
+
// Timeouts
|
|
54
|
+
PLACE_TIMEOUT_MS: 5000,
|
|
55
|
+
CANCEL_TIMEOUT_MS: 5000,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// ===== External deps =====
|
|
59
|
+
const binance = new ccxt.binance({ enableRateLimit: true });
|
|
60
|
+
const api = new ApiWrapper(
|
|
61
|
+
CFG.TL_WS_HOST,
|
|
62
|
+
CFG.TL_WS_PORT,
|
|
63
|
+
true, // debug (or tlOn) — unchanged
|
|
64
|
+
true, // autoConnect — unchanged
|
|
65
|
+
CFG.TL_ADDR,
|
|
66
|
+
CFG.TL_PUB,
|
|
67
|
+
CFG.TL_NETWORK
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
// ===== Helpers =====
|
|
72
|
+
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
|
73
|
+
const bps = (x) => x / 10000.0;
|
|
74
|
+
const roundDown = (x, tick) => Math.floor(x / tick) * tick;
|
|
75
|
+
const roundUp = (x, tick) => Math.ceil(x / tick) * tick;
|
|
76
|
+
const num = (v, d=8) => Number(Number(v).toFixed(d));
|
|
77
|
+
|
|
78
|
+
async function withTimeout(p, ms, tag) {
|
|
79
|
+
let t; const killer = new Promise((_, rej) => { t = setTimeout(() => rej(new Error(`${tag||'op'} timeout`)), ms); });
|
|
80
|
+
try { return await Promise.race([p, killer]); } finally { clearTimeout(t); }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function pxChanged(oldPx, newPx, tick, tolTicks) {
|
|
84
|
+
if (oldPx == null) return true;
|
|
85
|
+
return Math.abs(oldPx - newPx) >= tick * tolTicks;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ===== TL order builders (canonical TL shape via props) =====
|
|
89
|
+
function toTLBuy(cfg, price, amount) {
|
|
90
|
+
return {
|
|
91
|
+
type: 'SPOT',
|
|
92
|
+
action: 'BUY',
|
|
93
|
+
keypair: {
|
|
94
|
+
address: cfg.TL_ADDR,
|
|
95
|
+
pubkey: cfg.TL_PUB
|
|
96
|
+
},
|
|
97
|
+
props: {
|
|
98
|
+
id_for_sale: cfg.BASE_ID, // selling quote (USDTt)
|
|
99
|
+
id_desired: cfg.QUOTE_ID, // buying base (TLTC)
|
|
100
|
+
price: Number(price),
|
|
101
|
+
amount: Number(amount),
|
|
102
|
+
transfer: false
|
|
103
|
+
},
|
|
104
|
+
isLimitOrder: true
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function toTLSell(cfg, price, amount) {
|
|
109
|
+
return {
|
|
110
|
+
type: 'SPOT',
|
|
111
|
+
action: 'SELL',
|
|
112
|
+
keypair: {
|
|
113
|
+
address: cfg.TL_ADDR,
|
|
114
|
+
pubkey: cfg.TL_PUB
|
|
115
|
+
},
|
|
116
|
+
props: {
|
|
117
|
+
id_for_sale: cfg.QUOTE_ID, // selling base (TLTC)
|
|
118
|
+
id_desired: cfg.BASE_ID, // receiving quote (USDTt)
|
|
119
|
+
price: Number(price),
|
|
120
|
+
amount: Number(amount),
|
|
121
|
+
transfer: false
|
|
122
|
+
},
|
|
123
|
+
isLimitOrder: true
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
// ===== Token-bucket rate limiter =====
|
|
129
|
+
class TokenBucket {
|
|
130
|
+
constructor(rps) { this.capacity = rps; this.tokens = rps; this.last = Date.now(); }
|
|
131
|
+
take(n = 1) {
|
|
132
|
+
const now = Date.now();
|
|
133
|
+
const dt = (now - this.last) / 1000;
|
|
134
|
+
this.tokens = Math.min(this.capacity, this.tokens + dt * this.capacity);
|
|
135
|
+
this.last = now;
|
|
136
|
+
if (this.tokens >= n) { this.tokens -= n; return true; }
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const bucket = new TokenBucket(CFG.MAX_OPS_PER_SEC);
|
|
141
|
+
|
|
142
|
+
// ===== State =====
|
|
143
|
+
let live = { BUY: null, SELL: null }; // { uuid, px }
|
|
144
|
+
let sideBusy = { BUY: false, SELL: false };// per-side sequencing lock
|
|
145
|
+
let sideLast = { BUY: 0, SELL: 0 }; // last replace time per-side
|
|
146
|
+
let lastBBO = { bid: null, ask: null };
|
|
147
|
+
let lastBBOts = 0;
|
|
148
|
+
|
|
149
|
+
// ===== TL ops =====
|
|
150
|
+
async function place(side, px, sz) {
|
|
151
|
+
if (sideBusy[side]) return;
|
|
152
|
+
if (!bucket.take()) return;
|
|
153
|
+
sideBusy[side] = true;
|
|
154
|
+
try {
|
|
155
|
+
const det = side === 'BUY' ? toTLBuy(CFG, px, sz) : toTLSell(CFG, px, sz);
|
|
156
|
+
console.log('[TL] sendOrder request', det);
|
|
157
|
+
const uuid = await withTimeout(api.sendOrder(det), CFG.PLACE_TIMEOUT_MS, 'place');
|
|
158
|
+
const id = uuid?.orderUuid || uuid;
|
|
159
|
+
live[side] = { uuid: id, px };
|
|
160
|
+
sideLast[side] = Date.now();
|
|
161
|
+
console.log('PLACED', side, num(px, 6), 'uuid=', id);
|
|
162
|
+
} catch (e) {
|
|
163
|
+
console.log('PLACE FAIL', side, px, e?.message || e);
|
|
164
|
+
} finally {
|
|
165
|
+
sideBusy[side] = false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function cancel(side, reason) {
|
|
170
|
+
const cur = live[side];
|
|
171
|
+
if (!cur?.uuid) return;
|
|
172
|
+
if (sideBusy[side]) return;
|
|
173
|
+
if (!bucket.take()) return;
|
|
174
|
+
sideBusy[side] = true;
|
|
175
|
+
try {
|
|
176
|
+
await withTimeout(api.cancelOrder(cur.uuid), CFG.CANCEL_TIMEOUT_MS, 'cancel');
|
|
177
|
+
console.log('CANCELED', side, num(cur.px, 6), 'uuid=', cur.uuid, 'reason=', reason);
|
|
178
|
+
live[side] = null;
|
|
179
|
+
} catch (e) {
|
|
180
|
+
console.log('CANCEL FAIL', side, cur.uuid, e.message || e);
|
|
181
|
+
} finally {
|
|
182
|
+
sideBusy[side] = false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function targetsFromBBO(bid, ask) {
|
|
187
|
+
const b = Math.max(0, roundDown(bid * (1 - (CFG.EDGE_BPS / 10000.0)), CFG.TICK));
|
|
188
|
+
const a = Math.max(0, roundUp( ask * (1 + (CFG.EDGE_BPS / 10000.0)), CFG.TICK));
|
|
189
|
+
return { buyPx: b, sellPx: a };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ===== Main loop =====
|
|
193
|
+
async function tickOnce() {
|
|
194
|
+
// 1) Fetch BBO
|
|
195
|
+
const tkr = await binance.fetchTicker(CFG.SYMBOL_CCXT);
|
|
196
|
+
const bid = Number(tkr.bid), ask = Number(tkr.ask);
|
|
197
|
+
if (!isFinite(bid) || !isFinite(ask)) return;
|
|
198
|
+
lastBBO = { bid, ask }; lastBBOts = Date.now();
|
|
199
|
+
console.log(`[BINANCE] ${CFG.SYMBOL_CCXT} bid=${bid} ask=${ask}`);
|
|
200
|
+
|
|
201
|
+
// 2) Freshness check
|
|
202
|
+
if (Date.now() - lastBBOts > CFG.STALE_MS) return;
|
|
203
|
+
|
|
204
|
+
// 3) Compute targets
|
|
205
|
+
const { buyPx, sellPx } = targetsFromBBO(bid, ask);
|
|
206
|
+
|
|
207
|
+
// 4) BUY side replace logic
|
|
208
|
+
if (!sideBusy.BUY) {
|
|
209
|
+
const old = live.BUY?.px ?? null;
|
|
210
|
+
const ageOk = Date.now() - sideLast.BUY >= CFG.MIN_REPLACE_MS;
|
|
211
|
+
if (ageOk && pxChanged(old, buyPx, CFG.TICK, CFG.TICK_TOL)) {
|
|
212
|
+
await cancel('BUY', 'replace');
|
|
213
|
+
if (CFG.CANCEL_PLACE_GAP_MS) await sleep(CFG.CANCEL_PLACE_GAP_MS);
|
|
214
|
+
await place('BUY', buyPx, CFG.SIZE);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// 5) SELL side replace logic
|
|
219
|
+
if (!sideBusy.SELL) {
|
|
220
|
+
const old = live.SELL?.px ?? null;
|
|
221
|
+
const ageOk = Date.now() - sideLast.SELL >= CFG.MIN_REPLACE_MS;
|
|
222
|
+
if (ageOk && pxChanged(old, sellPx, CFG.TICK, CFG.TICK_TOL)) {
|
|
223
|
+
await cancel('SELL', 'replace');
|
|
224
|
+
if (CFG.CANCEL_PLACE_GAP_MS) await sleep(CFG.CANCEL_PLACE_GAP_MS);
|
|
225
|
+
await place('SELL', sellPx, CFG.SIZE);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
(async () => {
|
|
231
|
+
console.log('Starting BBO tracker (2 orders, HF-stable)…');
|
|
232
|
+
await sleep(10000);
|
|
233
|
+
while (true) {
|
|
234
|
+
try {
|
|
235
|
+
await tickOnce();
|
|
236
|
+
} catch (e) {
|
|
237
|
+
console.log('Loop error:', e.message || e);
|
|
238
|
+
}
|
|
239
|
+
await sleep(CFG.POLL_MS);
|
|
240
|
+
}
|
|
241
|
+
})();
|
package/seller.js
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
const litecore = require('bitcore-lib-ltc');
|
|
2
|
+
const Encode = require('./tradelayer.js/src/txEncoder.js');
|
|
3
|
+
const { buildLitecoinTransaction, buildTokenTradeTransaction, buildFuturesTransaction, getUTXOFromCommit,signPsbtRawTx } = require('./litecoreTxBuilder');
|
|
4
|
+
const WalletListener = require('./tradelayer.js/src/walletInterface.js');
|
|
5
|
+
const util = require('util');
|
|
6
|
+
const BigNumber = require('bignumber.js');
|
|
7
|
+
|
|
8
|
+
class SellSwapper {
|
|
9
|
+
constructor(typeTrade, tradeInfo, sellerInfo, buyerInfo, client, socket,test,tradeUUID) {
|
|
10
|
+
this.typeTrade = typeTrade;
|
|
11
|
+
this.tradeInfo = tradeInfo;
|
|
12
|
+
this.sellerInfo = sellerInfo;
|
|
13
|
+
this.buyerInfo = buyerInfo;
|
|
14
|
+
this.myInfo = sellerInfo
|
|
15
|
+
this.cpInfo = buyerInfo
|
|
16
|
+
this.socket = socket;
|
|
17
|
+
this.client = client;
|
|
18
|
+
this.test = test
|
|
19
|
+
this.multySigChannelData = null
|
|
20
|
+
this.tradeStartTime = Date.now();
|
|
21
|
+
this.tradeUUID = tradeUUID
|
|
22
|
+
// Promisify methods for the given client
|
|
23
|
+
this.getRawTransactionAsync = util.promisify(this.client.getRawTransaction.bind(this.client));
|
|
24
|
+
this.getBlockDataAsync = util.promisify(this.client.getBlock.bind(this.client));
|
|
25
|
+
this.createRawTransactionAsync = util.promisify(this.client.createRawTransaction.bind(this.client));
|
|
26
|
+
this.listUnspentAsync = util.promisify(this.client.cmd.bind(this.client, 'listunspent'));
|
|
27
|
+
this.decoderawtransactionAsync = util.promisify(this.client.cmd.bind(this.client, 'decoderawtransaction'));
|
|
28
|
+
this.dumpprivkeyAsync = util.promisify(this.client.cmd.bind(this.client, 'dumpprivkey'));
|
|
29
|
+
this.sendrawtransactionAsync = util.promisify(this.client.cmd.bind(this.client, 'sendrawtransaction'));
|
|
30
|
+
this.validateAddress = util.promisify(this.client.cmd.bind(this.client, 'validateaddress'));
|
|
31
|
+
this.getBlockCountAsync = util.promisify(this.client.cmd.bind(this.client, 'getblockcount'));
|
|
32
|
+
this.addMultisigAddressAsync = util.promisify(this.client.cmd.bind(this.client, 'addmultisigaddress'));
|
|
33
|
+
this.signrawtransactionwithwalletAsync = util.promisify(this.client.cmd.bind(this.client, 'signrawtransactionwithwallet'));
|
|
34
|
+
this.signrawtransactionwithkeyAsync = util.promisify(this.client.cmd.bind(this.client, 'signrawtransactionwithkey'));
|
|
35
|
+
|
|
36
|
+
this.handleOnEvents();
|
|
37
|
+
this.onReady();
|
|
38
|
+
this.initTrade();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
logTime(stage) {
|
|
42
|
+
const currentTime = Date.now();
|
|
43
|
+
console.log(`Time taken for ${stage}: ${currentTime - this.tradeStartTime} ms`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
onReady() {
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
this.readyRes = resolve;
|
|
49
|
+
// If the readyRes is not called within 60 seconds, terminate the trade
|
|
50
|
+
setTimeout(() => this.terminateTrade('Undefined Error code 1'), 60000);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
bip67SortPubKeys(pubKeys) {
|
|
55
|
+
return [...pubKeys].sort((a, b) =>
|
|
56
|
+
Buffer.from(a, 'hex').compare(Buffer.from(b, 'hex'))
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async ensureFuturesMargin(tradeProps) {
|
|
61
|
+
if (this._futuresMargin) return this._futuresMargin;
|
|
62
|
+
|
|
63
|
+
if (!tradeProps || !tradeProps.contract_id || !tradeProps.price || !tradeProps.amount) {
|
|
64
|
+
throw new Error('Invalid futures trade props for margin calculation');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const { contract_id, price, amount } = tradeProps;
|
|
68
|
+
|
|
69
|
+
// 1) Fetch contract info
|
|
70
|
+
console.log('contract id '+contract_id)
|
|
71
|
+
const contractInfo = await WalletListener.getContractInfo(contract_id);
|
|
72
|
+
if (!contractInfo) {
|
|
73
|
+
throw new Error(`No contract info for contract ${contract_id}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 2) Per-contract margin
|
|
77
|
+
const perContractMargin = await WalletListener.getInitialMargin(
|
|
78
|
+
contract_id,
|
|
79
|
+
price
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
if (!perContractMargin || perContractMargin <= 0) {
|
|
83
|
+
throw new Error('Invalid per-contract margin');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 3) Scale by number of contracts
|
|
87
|
+
const initMargin = perContractMargin * amount;
|
|
88
|
+
|
|
89
|
+
// 4) Collateral propertyId
|
|
90
|
+
const collateral = contractInfo.collateralPropertyId;
|
|
91
|
+
|
|
92
|
+
if (!collateral || initMargin <= 0) {
|
|
93
|
+
throw new Error('Computed invalid futures margin parameters');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
this._futuresMargin = {
|
|
97
|
+
collateral,
|
|
98
|
+
initMargin,
|
|
99
|
+
perContractMargin,
|
|
100
|
+
leverage: contractInfo.leverage,
|
|
101
|
+
inverse: contractInfo.inverse
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
console.log('[FUTURES] margin prepared', this._futuresMargin);
|
|
105
|
+
return this._futuresMargin;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
async sendTxWithSpecRetry(rawTx) {
|
|
110
|
+
const _sendTxWithRetry = async (rawTx, retriesLeft, ms) => {
|
|
111
|
+
try {
|
|
112
|
+
// Attempt to send the transaction
|
|
113
|
+
const result = await this.sendrawtransactionAsync(rawTx);
|
|
114
|
+
// If there's an error and retries are left, try again
|
|
115
|
+
if (result.error && result.error.includes('bad-txns-inputs-missingorspent') && retriesLeft > 0) {
|
|
116
|
+
await new Promise(resolve => setTimeout(resolve, ms));
|
|
117
|
+
console.log('Retrying to send the transaction... Remaining retries:', retriesLeft);
|
|
118
|
+
return _sendTxWithRetry(rawTx, retriesLeft - 1, ms);
|
|
119
|
+
}
|
|
120
|
+
// If successful, return the result
|
|
121
|
+
return result;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
// If an error occurs during sendrawtransactionAsync, handle it here
|
|
124
|
+
console.error('Error during transaction send:', error.message);
|
|
125
|
+
if (retriesLeft > 0) {
|
|
126
|
+
console.log('Retrying after error... Remaining retries:', retriesLeft);
|
|
127
|
+
await new Promise(resolve => setTimeout(resolve, ms));
|
|
128
|
+
return _sendTxWithRetry(rawTx, retriesLeft - 1, ms);
|
|
129
|
+
}
|
|
130
|
+
return { error: 'Transaction failed after retries' }; // Return an error after all retries
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Start the retry process with 15 retries and 800ms interval
|
|
135
|
+
return _sendTxWithRetry(rawTx, 15, 1200);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
removePreviousListeners() {
|
|
140
|
+
// Correctly using template literals with backticks
|
|
141
|
+
this.socket.off(`${this.cpInfo.socketId}::swap`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
terminateTrade(reason){
|
|
145
|
+
// Emit the TERMINATE_TRADE event to the socket
|
|
146
|
+
const eventData = {event:'TERMINATE_TRADE', socketId: this.myInfo.socketId, reason: reason};
|
|
147
|
+
const tag = `${this.myInfo.socketId}::swap`; // Correct string concatenation
|
|
148
|
+
this.socket.emit(tag, eventData);
|
|
149
|
+
this.removePreviousListeners();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
handleOnEvents() {
|
|
153
|
+
this.removePreviousListeners()
|
|
154
|
+
const eventName = `${this.buyerInfo.socketId}::swap`;
|
|
155
|
+
this.socket.on(eventName, async (eventData) => {
|
|
156
|
+
const { socketId, data } = eventData;
|
|
157
|
+
if (eventData.data?.tradeUUID && eventData.data.tradeUUID !== this.tradeUUID){
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
switch (eventData.eventName) {
|
|
161
|
+
case 'BUYER:STEP2':
|
|
162
|
+
await this.onStep2(socketId, data);
|
|
163
|
+
break;
|
|
164
|
+
case 'BUYER:STEP4':
|
|
165
|
+
await this.onStep4(socketId, data.psbtHex, data.commitTxId);
|
|
166
|
+
break;
|
|
167
|
+
case 'BUYER:STEP6':
|
|
168
|
+
await this.onStep6(socketId, data);
|
|
169
|
+
break;
|
|
170
|
+
default:
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async initTrade() {
|
|
177
|
+
try {
|
|
178
|
+
const pubKeys = bip67SortPubKeys([
|
|
179
|
+
this.cpInfo.keypair.pubkey,
|
|
180
|
+
this.myInfo.keypair.pubkey,
|
|
181
|
+
]);
|
|
182
|
+
if (this.typeTrade === 'SPOT' && 'propIdDesired' in this.tradeInfo.props){
|
|
183
|
+
let { propIdDesired, propIdForSale } = this.tradeInfo.props;
|
|
184
|
+
if(propIdDesired==0||propIdForSale==0){
|
|
185
|
+
pubKeys = [this.buyerInfo.keypair.pubkey,this.sellerInfo.keypair.pubkey];
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
console.log('pubkeys for multisig '+JSON.stringify(pubKeys))
|
|
189
|
+
const multisigAddress = await this.addMultisigAddressAsync(2, pubKeys);
|
|
190
|
+
this.multySigChannelData = multisigAddress
|
|
191
|
+
|
|
192
|
+
console.log('generating multisig in sell init '+JSON.stringify(multisigAddress))
|
|
193
|
+
const validateMS = await this.validateAddress(multisigAddress.address.toString());
|
|
194
|
+
console.log('validated '+JSON.stringify(validateMS))
|
|
195
|
+
if (validateMS.error || !validateMS.isvalid) throw new Error(`Multisig address validation failed`);
|
|
196
|
+
|
|
197
|
+
this.multySigChannelData = { address: multisigAddress.address.toString(), redeemScript: multisigAddress.redeemScript.toString(), scriptPubKey: validateMS.scriptPubKey };
|
|
198
|
+
console.log('checking this.multisig '+JSON.stringify(this.multySigChannelData))
|
|
199
|
+
console.log('my info socket id '+this.myInfo.socketId+' '+this.sellerInfo.socketId)
|
|
200
|
+
const swapEvent = { eventName: 'SELLER:STEP1', socketId: this.myInfo.socketId, data: this.multySigChannelData };
|
|
201
|
+
console.log('show socket obj '+JSON.stringify(this.socket.emit)+' '+JSON.stringify(this.socket)+' '+this.socket)
|
|
202
|
+
this.socket.emit(`${this.myInfo.socketId}::swap`, swapEvent);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
console.error(`InitTrade Error: ${error.message}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async onStep2(cpId) {
|
|
209
|
+
this.logTime('Step 2 Start');
|
|
210
|
+
|
|
211
|
+
// --- basic guards (same behavior, clearer logs) ---
|
|
212
|
+
if (!this.multySigChannelData?.address) {
|
|
213
|
+
throw new Error(`No Multisig Address`);
|
|
214
|
+
}
|
|
215
|
+
if (cpId !== this.buyerInfo?.socketId) {
|
|
216
|
+
throw new Error(`Connection Error`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// --- extract trade props; support SPOT (propIdDesired/amountDesired) and FUTURES (collateral/initMargin) ---
|
|
220
|
+
const tprops = this.tradeInfo?.props ?? {};
|
|
221
|
+
console.log('props in step 2 '+JSON.stringify(tprops))
|
|
222
|
+
const isFutures = ('contract_id' in tprops)
|
|
223
|
+
|
|
224
|
+
// SPOT defaults (original names)
|
|
225
|
+
const propIdForSale = tprops.propIdForSale ?? tprops.propertyId ?? 0;
|
|
226
|
+
const amountDesired = tprops.amountForSale ?? tprops.amount ?? 0;
|
|
227
|
+
const transfer = !!(tprops.transfer ?? false);
|
|
228
|
+
|
|
229
|
+
// FUTURES defaults (desktop parity)
|
|
230
|
+
let initMargin =0
|
|
231
|
+
let collateral = 0
|
|
232
|
+
|
|
233
|
+
if(isFutures){
|
|
234
|
+
const margin = await this.ensureFuturesMargin(tprops)
|
|
235
|
+
initMargin= margin.initMargin
|
|
236
|
+
collateral= margin.collateral
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// --- Column A/B detection (use RPC if available; otherwise default 'A') ---
|
|
240
|
+
let isColumnA = true;
|
|
241
|
+
try {
|
|
242
|
+
if (typeof WalletListener?.getColumn === 'function') {
|
|
243
|
+
const col = await WalletListener.getColumn(
|
|
244
|
+
this.sellerInfo?.keypair?.address,
|
|
245
|
+
this.buyerInfo?.keypair?.address
|
|
246
|
+
);
|
|
247
|
+
const tag = col?.data ?? col; // some impls return {data:'A'|'B'}
|
|
248
|
+
isColumnA = (tag === 'A');
|
|
249
|
+
} else {
|
|
250
|
+
// your original hardcode
|
|
251
|
+
const columnRes = 'A';
|
|
252
|
+
// NOTE: your old code used columnRes.data; that would always be undefined.
|
|
253
|
+
isColumnA = (columnRes === 'A');
|
|
254
|
+
}
|
|
255
|
+
} catch (_) {
|
|
256
|
+
// fall back to A, no crash
|
|
257
|
+
isColumnA = true;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// --- build TL payload (commit/transfer; spot vs futures) ---
|
|
261
|
+
let payload;
|
|
262
|
+
if (transfer) {
|
|
263
|
+
// transfer path uses desired SPOT fields; if FUTURES provided, prefer futures collateral/initMargin
|
|
264
|
+
const propertyId = isFutures ? collateral : propIdForSale;
|
|
265
|
+
const amount = isFutures ? initMargin : amountDesired;
|
|
266
|
+
|
|
267
|
+
payload = Encode.encodeTransfer({
|
|
268
|
+
propertyId,
|
|
269
|
+
amount,
|
|
270
|
+
isColumnA,
|
|
271
|
+
destinationAddr: this.multySigChannelData.address,
|
|
272
|
+
});
|
|
273
|
+
} else {
|
|
274
|
+
// commit path; keep your encodeCommit API
|
|
275
|
+
const propertyId = isFutures ? collateral : propIdForSale;
|
|
276
|
+
const amount = isFutures ? initMargin : amountDesired;
|
|
277
|
+
|
|
278
|
+
payload = Encode.encodeCommit({
|
|
279
|
+
amount,
|
|
280
|
+
propertyId,
|
|
281
|
+
channelAddress: this.multySigChannelData.address,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// --- UTXO selection (largest-first) ---
|
|
286
|
+
console.log('calling list unspent ' + this.sellerInfo?.keypair?.address);
|
|
287
|
+
const utxos = await this.listUnspentAsync(0, 999999, [this.sellerInfo.keypair.address]) ?? [];
|
|
288
|
+
if (!Array.isArray(utxos) || utxos.length === 0) {
|
|
289
|
+
throw new Error('No UTXOs found for seller');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const sortedUTXOs = utxos.sort((a, b) =>
|
|
293
|
+
new BigNumber(b?.amount ?? 0).comparedTo(a?.amount ?? 0)
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
const largestUtxo = sortedUTXOs[0];console.log('Largest UTXO:', JSON.stringify(largestUtxo));
|
|
297
|
+
const commitUTXOs = [{
|
|
298
|
+
txid: largestUtxo?.txid ?? largestUtxo?.txId,
|
|
299
|
+
vout: largestUtxo?.vout ?? largestUtxo?.n ?? 0,
|
|
300
|
+
scriptPubKey: largestUtxo?.scriptPubKey,
|
|
301
|
+
amount: largestUtxo?.amount
|
|
302
|
+
}];
|
|
303
|
+
|
|
304
|
+
console.log('commitUTXOs:', JSON.stringify(commitUTXOs));
|
|
305
|
+
|
|
306
|
+
// --- OP_RETURN payload hex ---
|
|
307
|
+
const hexPayload = Buffer.from(payload ?? '', 'utf8').toString('hex');
|
|
308
|
+
console.log('payload ' + payload + ' hex ' + hexPayload);
|
|
309
|
+
|
|
310
|
+
// --- inputs/outputs (don’t assume vout 0 for the channel; we’ll decode below) ---
|
|
311
|
+
const _insForRawTx = commitUTXOs.map(({ txid, vout }) => ({ txid, vout }));
|
|
312
|
+
|
|
313
|
+
const dust = 0.000056;
|
|
314
|
+
const feeSats = 0.000030; // tweak to match current fee market
|
|
315
|
+
const change = new BigNumber(largestUtxo?.amount ?? 0).minus(dust).minus(feeSats).toNumber();
|
|
316
|
+
if (!(change > 0)) {
|
|
317
|
+
throw new Error('Insufficient UTXO for dust+fee');
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const _outsForRawTx = [
|
|
321
|
+
{ [this.multySigChannelData.address]: dust },
|
|
322
|
+
{ [this.myInfo.keypair.address]: change },
|
|
323
|
+
{ data: hexPayload }
|
|
324
|
+
];
|
|
325
|
+
|
|
326
|
+
console.log(
|
|
327
|
+
'inputs for create raw tx ' + JSON.stringify(_insForRawTx) +
|
|
328
|
+
' outs ' + JSON.stringify(_outsForRawTx)
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
// --- create / decode / sign / send (same surface as your original) ---
|
|
332
|
+
let crtRes = await this.createRawTransactionAsync(_insForRawTx, _outsForRawTx);
|
|
333
|
+
|
|
334
|
+
const decoded = await this.decoderawtransactionAsync(crtRes);
|
|
335
|
+
console.log('decoded ' + JSON.stringify(decoded));
|
|
336
|
+
console.log('created commit tx ' + crtRes + ' type of ' + typeof(crtRes));
|
|
337
|
+
|
|
338
|
+
const wif = await this.dumpprivkeyAsync(this.myInfo.keypair.address);
|
|
339
|
+
const signResKey = await this.signrawtransactionwithkeyAsync(crtRes, [wif]);
|
|
340
|
+
console.log('signed with key ' + JSON.stringify(signResKey));
|
|
341
|
+
|
|
342
|
+
const sendRes = await this.sendrawtransactionAsync(signResKey?.hex);
|
|
343
|
+
if (!sendRes) return new Error(`Failed to broadcast the transaction`);
|
|
344
|
+
console.log('sent commit ' + JSON.stringify(sendRes));
|
|
345
|
+
|
|
346
|
+
// --- locate the actual channel vout by address (don’t hardcode index 0) ---
|
|
347
|
+
const voutArr = decoded?.vout ?? [];
|
|
348
|
+
const channelOut = voutArr.find(o =>
|
|
349
|
+
o?.scriptPubKey?.addresses?.[0] === this.multySigChannelData?.address
|
|
350
|
+
) || voutArr.find(o => o?.scriptPubKey?.asm?.includes(this.multySigChannelData?.address));
|
|
351
|
+
|
|
352
|
+
if (!channelOut) {
|
|
353
|
+
throw new Error('No matching vout for commit UTXO');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const utxoData = {
|
|
357
|
+
amount: channelOut.value ?? dust,
|
|
358
|
+
vout: channelOut.n ?? 0,
|
|
359
|
+
txid: sendRes,
|
|
360
|
+
scriptPubKey: this.multySigChannelData.scriptPubKey,
|
|
361
|
+
redeemScript: this.multySigChannelData.redeemScript,
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const swapEvent = { eventName: 'SELLER:STEP3', socketId: this.myInfo.socketId, data: utxoData };
|
|
365
|
+
// keep your surface; if you want the NPM style route, flip this to myInfo
|
|
366
|
+
this.socket.emit(`${this.sellerInfo.socketId}::swap`, swapEvent);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async onStep4(cpId, psbtHex, commitTxId /* optional: only provided on token-channel flows */) {
|
|
370
|
+
this.logTime('Step 4 Start');
|
|
371
|
+
if (this._step4InFlight) return;
|
|
372
|
+
this._step4InFlight = true;
|
|
373
|
+
//try {
|
|
374
|
+
console.log('cpiID '+cpId +' buyer socket '+this.buyerInfo.socketId)
|
|
375
|
+
console.log('deets '+psbtHex+' '+commitTxId)
|
|
376
|
+
// 1) basic sanity
|
|
377
|
+
if (cpId !== this.buyerInfo?.socketId) return new Error(`Connection Error`);
|
|
378
|
+
if (!psbtHex) return new Error(`Missing PSBT Hex`);
|
|
379
|
+
|
|
380
|
+
// 2) optional anti-RBF check on the commit tx (if caller supplies it)
|
|
381
|
+
if (commitTxId) {
|
|
382
|
+
try {
|
|
383
|
+
// Prefer an async wrapper if you have it; else fallback to raw RPC
|
|
384
|
+
const res = await this.getRawTransactionAsync(commitTxId, true);
|
|
385
|
+
|
|
386
|
+
console.log('res '+JSON.stringify(res))
|
|
387
|
+
const vins = res.vin;
|
|
388
|
+
if (!Array.isArray(vins)) throw new Error('vin missing');
|
|
389
|
+
|
|
390
|
+
// BIP-125: any sequence < 0xFFFFFFFE signals opt-in RBF
|
|
391
|
+
const isRbf = vins.some(v => {
|
|
392
|
+
const seq = (v?.sequence ?? 0xffffffff) >>> 0;
|
|
393
|
+
return seq < 0xfffffffe;
|
|
394
|
+
});
|
|
395
|
+
console.log('is RBF? '+isRbf)
|
|
396
|
+
if(isRbf) throw new Error('RBF-enabled commit tx detected; aborting.');
|
|
397
|
+
} catch (e) {
|
|
398
|
+
return new Error(`Anti-RBF check failed: ${e?.message || e}`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// 3) pick network + sign PSBT with our WIF (your existing flow)
|
|
403
|
+
let network = this.test ? 'LTCTEST' : 'LTC';
|
|
404
|
+
const wif = await this.dumpprivkeyAsync(this.myInfo.keypair.address);
|
|
405
|
+
const signRes = await signPsbtRawTx({ wif, network, psbtHex }, this.client);
|
|
406
|
+
if (!signRes?.data?.psbtHex) return new Error(`Failed to sign the PSBT`);
|
|
407
|
+
console.log('sign res '+JSON.stringify(signRes))
|
|
408
|
+
if(signRes.data.isFinished){
|
|
409
|
+
const sentTx = await this.sendTxWithSpecRetry(signRes.data.hex);
|
|
410
|
+
const data = { txid: sentTx, seller: true, trade: this.tradeInfo };
|
|
411
|
+
this.logTime('Tx Broadcast');
|
|
412
|
+
this.socket.emit(`${this.sellerInfo.socketId}::complete`, data);
|
|
413
|
+
return
|
|
414
|
+
}
|
|
415
|
+
// 4) hand signed PSBT to seller for finalization/broadcast
|
|
416
|
+
const swapEvent = {
|
|
417
|
+
eventName: 'SELLER:STEP5',
|
|
418
|
+
socketId: this.myInfo.socketId,
|
|
419
|
+
data: signRes.data.psbtHex
|
|
420
|
+
};
|
|
421
|
+
this.socket.emit(`${this.sellerInfo.socketId}::swap`, swapEvent);
|
|
422
|
+
|
|
423
|
+
//} catch (error) {
|
|
424
|
+
console.error(`Step 4 Error: ${error?.message || error}`);
|
|
425
|
+
//} finally {
|
|
426
|
+
// this._step4InFlight = false;
|
|
427
|
+
//}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
async onStep6(cpId, finalTx) {
|
|
431
|
+
this.logTime('Step 6 Start');
|
|
432
|
+
try {
|
|
433
|
+
if (cpId !== this.buyerInfo.socketId){console.log(`Connection Error`)};
|
|
434
|
+
|
|
435
|
+
const data = { txid: finalTx, seller: true, trade: this.tradeInfo };
|
|
436
|
+
this.socket.emit(`${this.sellerInfo.socketId}::complete`, data);
|
|
437
|
+
} catch (error) {
|
|
438
|
+
console.error(`Step 6 Error: ${error.message}`);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
module.exports = SellSwapper;
|