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>
38 lines
1.9 KiB
JavaScript
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)');
|