deviant/scripts/tron-sim-transfer.mjs
walerikus 34c672b927 feat(build-b): B2 trustless USDT(Tron)<->BCH atomic swap validated on real coins
B2 (Build B milestone 2): the real cross-chain atomic swap, Tron analog of
T4. One sha256 hashlock governs a Nile TronHTLC leg (real test-USDT) and a
chipnet AtomicSwapHTLC leg. Validated end-to-end, all 3 checks PASS:
- Alice locks 5 USDT on Nile, Bob funds the BCH HTLC on chipnet
- Alice claims the BCH leg, revealing S on-chain
- Bob reads S off chipnet and claims the real Nile USDT
No oracle, no wrapped token, no custodian. First interchain rail into the hub.
  BCH claim 2fc1dfb0… · Nile USDT claim f5128666…

Token gotcha (fixed): the Nile TetherToken performs transfer but RETURNS
false (declares returns(bool) without setting it), so require(transfer(...))
and even SafeERC20's bool decode wrongly revert and roll back a good move.
TronHTLC._safeTransfer/_From now ignore the return value and verify by
BALANCE DELTA. Applies to USDT on every chain — B3 vault must do the same.

  scripts/tron-bch-htlc-swap.mjs (B2 harness)
  scripts/tron-trc20-holdings.mjs, tron-usdt-probe.mjs, tron-sim-transfer.mjs,
  tron-b2-claim-retry.mjs (discovery + diagnostics)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 01:24:55 +02:00

38 lines
1.9 KiB
JavaScript

/**
* tron-sim-transfer.mjs — simulate USDT.transfer with caller = the HTLC contract
* (vs an EOA) to surface the real revert reason behind B2's "transfer failed".
*/
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { TronWeb } from 'tronweb';
const ROOT = join(import.meta.dirname, '..');
const NILE = 'https://nile.trongrid.io';
const sw = JSON.parse(readFileSync(join(ROOT, '.tron-b2-lastswap.json'), 'utf8'));
const k = JSON.parse(readFileSync(join(ROOT, '.tron-nile-keys.json'), 'utf8'));
const tronWeb = new TronWeb({ fullHost: NILE, privateKey: k.privateKey });
const USDT_HEX = tronWeb.address.toHex(sw.usdt);
const decodeRevert = (hexStr) => {
if (!hexStr) return '(no data)';
if (hexStr.startsWith('08c379a0')) { const len = parseInt(hexStr.slice(72, 136), 16); return Buffer.from(hexStr.slice(136, 136 + len * 2), 'hex').toString('utf8'); }
return hexStr.slice(0, 80);
};
async function sim(caller, to, value, label) {
const res = await tronWeb.transactionBuilder.triggerConstantContract(
USDT_HEX, 'transfer(address,uint256)', {},
[{ type: 'address', value: tronWeb.address.toHex(to) }, { type: 'uint256', value }],
tronWeb.address.toHex(caller));
const okFlag = res?.result?.result === true;
const cres = res?.constant_result?.[0];
const msgHex = res?.result?.message ? Buffer.from(res.result.message, 'hex').toString('utf8') : '';
console.log(`\n${label} (caller ${caller}):`);
console.log(' result.result:', okFlag, '| code:', res?.result?.code || '-');
console.log(' constant_result:', cres ? cres.slice(0, 80) : '(none)', '->', decodeRevert(cres));
if (msgHex) console.log(' message:', msgHex);
}
console.log('USDT', sw.usdt, '| HTLC', sw.htlc, '| receiver', sw.receiver, '| amount', sw.amount);
await sim(sw.htlc, sw.receiver, sw.amount, '1) HTLC -> receiver (the failing call)');
await sim(k.address, sw.receiver, 100000, '2) Alice(EOA) -> receiver (control)');