deviant/scripts/verify-gateway.mjs
walerikus 6ffda4bcea feat(cross-chain): human-named gateway (wallet-legible) + own-wallet Send plan
Make the source wallet's popup legible: rename the vault to DeviantBitcoinCashGateway
with intent functions — depositToBitcoinCash(uint256,bytes32) (was lock), release,
bitcoinCashBacked(), event DepositedToBitcoinCash — so wallets decode the call as
"Deposit To Bitcoin Cash" (4byte registry, and fully once verified). Same audited
logic (balance-delta safe, operator release, public backing). Rewired evm/tron.mjs,
connect/{evm,tron}.ts, both deposit e2e scripts. One self-contained file (paris +
istanbul). Re-validated full round-trip on real Sepolia + Nile + chipnet: deposit 7/7
+ 6/6, redeem 2/2. New gateways 0x9D5C7402… (Sepolia) · TUsXiRemR… (Nile).

scripts/verify-gateway.mjs: Etherscan-v2 standard-JSON verify (needs ETHERSCAN_API_KEY)
+ prints Tronscan settings; the .sol has no imports so it is the flattened source.

Plan doc: Parameters/CrossChainTransactions/multichain-wallet-send-ui_PLAN.md — the
From→To "Send" UI for Deviant's OWN multi-chain wallet (one seed → BCH/EVM/Tron, in-app
signer, no foreign popup), kept distinct from the shipping external-wallet Deposit/
Withdraw model. Both coexist. Snapshot 0201; tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 03:11:44 +02:00

88 lines
5 KiB
JavaScript

#!/usr/bin/env node
/**
* verify-gateway.mjs — verify the DeviantBitcoinCashGateway source on the block
* explorer, so wallets render the deposit as the decoded, human-named method
* "Deposit To Bitcoin Cash" with named parameters (instead of a raw contract blob).
*
* Etherscan (Sepolia) is fully automated here — it just needs an API key:
* ETHERSCAN_API_KEY=xxxxx node scripts/verify-gateway.mjs
* Tron/Nile (Tronscan) uses a different flow; this prints the exact settings to paste
* into the Tronscan "Verify & Publish Contract Source Code" page.
*
* The contract is a single self-contained file (no imports) → the .sol IS the flattened
* source, and we submit it as solc standard-JSON (the most robust verification format),
* with the EXACT compiler settings the correspondent service used.
*/
import { readFileSync, existsSync } from 'node:fs';
import { join } from 'node:path';
import solc from 'solc';
import { AbiCoder } from 'ethers';
const ROOT = join(import.meta.dirname, '..');
const SRC_PATH = join(ROOT, 'contracts/evm/DeviantBitcoinCashGateway.sol');
const STATE = join(ROOT, 'services/correspondent/.correspondent-state.json');
const SRC = readFileSync(SRC_PATH, 'utf8');
const CONTRACT = 'DeviantBitcoinCashGateway.sol:DeviantBitcoinCashGateway';
const USDC = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238';
// solc "0.8.26+commit.8a97fa7a.Emscripten.clang" → Etherscan "v0.8.26+commit.8a97fa7a"
const longVer = solc.version();
const compilerversion = 'v' + longVer.replace(/\.Emscripten.*$/, '').replace(/^soljson-/, '');
// The EXACT standard-JSON input the service compiles with (evm.mjs / paris).
const standardJson = {
language: 'Solidity',
sources: { 'DeviantBitcoinCashGateway.sol': { content: SRC } },
settings: { evmVersion: 'paris', optimizer: { enabled: true, runs: 200 }, outputSelection: { '*': { '*': ['abi', 'evm.bytecode.object'] } } },
};
function deployed() {
const arg = process.argv.find((a) => a.startsWith('0x') && a.length === 42);
if (arg) return arg;
if (existsSync(STATE)) { const s = JSON.parse(readFileSync(STATE, 'utf8')); const a = s.vaults?.['USDC (Ethereum)']?.address; if (a) return a; }
throw new Error('pass the deployed Sepolia gateway address (0x…) or run a deposit first to populate state');
}
async function main() {
const address = deployed();
const operator = '0xCA144dcbB9e0986141242D16F235cAb752c141bF'; // service operator (== .sepolia-keys addr)
const ctorArgs = AbiCoder.defaultAbiCoder().encode(['address', 'address'], [USDC, operator]).slice(2);
console.log(`Gateway (Sepolia): ${address}`);
console.log(`Compiler: ${compilerversion} · evmVersion paris · optimizer 200`);
console.log(`Constructor args (token, operator): ${USDC}, ${operator}`);
console.log('');
console.log('--- Tron/Nile (Tronscan) — paste on https://nile.tronscan.org/#/contract/<addr>/code → Verify:');
const tronAddr = (() => { try { return JSON.parse(readFileSync(STATE, 'utf8')).vaults?.['USDT (Tron)']?.address; } catch { return '<tron gateway>'; } })();
console.log(` contract: ${tronAddr}`);
console.log(` name DeviantBitcoinCashGateway · compiler ${compilerversion.replace('v', '')} · optimizer Yes/200 · evmVersion istanbul · license MIT`);
console.log(` source: contracts/evm/DeviantBitcoinCashGateway.sol (single file, paste whole)`);
console.log('');
const KEY = process.env.ETHERSCAN_API_KEY;
if (!KEY) {
console.log('ETHERSCAN_API_KEY not set — cannot submit Sepolia verification automatically.');
console.log('Get a free key at https://etherscan.io/apis, then:');
console.log(` ETHERSCAN_API_KEY=xxxx node scripts/verify-gateway.mjs ${address}`);
console.log('(Or verify manually with the settings above: standard-JSON input, contract name DeviantBitcoinCashGateway.)');
return;
}
const body = new URLSearchParams({
chainid: '11155111', module: 'contract', action: 'verifysourcecode', apikey: KEY,
contractaddress: address, sourceCode: JSON.stringify(standardJson), codeformat: 'solidity-standard-json-input',
contractname: CONTRACT, compilerversion, constructorArguements: ctorArgs,
});
const sub = await (await fetch('https://api.etherscan.io/v2/api', { method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded' }, body })).json();
console.log('submit:', sub);
if (sub.status !== '1') { console.log('submission rejected (already verified? bad key?)'); return; }
const guid = sub.result;
for (let i = 0; i < 20; i++) {
await new Promise((r) => setTimeout(r, 5000));
const chk = await (await fetch(`https://api.etherscan.io/v2/api?chainid=11155111&module=contract&action=checkverifystatus&guid=${guid}&apikey=${KEY}`)).json();
console.log('status:', chk.result);
if (String(chk.result).includes('Verified') || chk.status === '1') { console.log('✓ VERIFIED on Sepolia Etherscan'); return; }
if (String(chk.result).includes('Fail')) { console.log('verification failed'); return; }
}
}
main().catch((e) => { console.error('FAILED:', e?.message ?? e); process.exitCode = 1; });