feat: expand non-evm relay and route planning support

This commit is contained in:
defiQUG
2026-04-18 12:05:34 -07:00
parent da78073104
commit 843cdbf71c
113 changed files with 8542 additions and 222 deletions

View File

@@ -0,0 +1,188 @@
const fs = require("fs");
const path = require("path");
const hre = require("hardhat");
const DEFAULTS = {
weth: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
usdt: "0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1",
usdc: "0x71D6687F38b93CCad569Fa6352c876eea967201b",
cusdt: "0x93E66202A11B1772E55407B32B44e5Cd8eda7f22",
cusdc: "0xf22258f57794CC8E06237084b353Ab30fFfa640b",
};
function envAddress(name, fallback) {
const value = String(process.env[name] || fallback || "").trim();
if (!value) {
throw new Error(`Missing required address env ${name}`);
}
return value;
}
function envUnits(name, fallback, decimals) {
return hre.ethers.parseUnits(String(process.env[name] || fallback), decimals);
}
async function ensureAllowance(token, owner, spender, amount) {
const allowance = await token.allowance(owner, spender);
if (allowance >= amount) return;
const tx = await token.approve(spender, hre.ethers.MaxUint256);
await tx.wait();
}
async function transferIfNeeded(token, to, amount) {
if (amount <= 0n) return;
const tx = await token.transfer(to, amount);
await tx.wait();
}
async function main() {
const signers = await hre.ethers.getSigners();
const deployer = signers[0];
const feeTo = signers[5] || deployer;
const currentBlock = await hre.ethers.provider.getBlockNumber();
const gasPrice = BigInt(process.env.CHAIN138_SUSHISWAP_GAS_PRICE || (await hre.ethers.provider.send("eth_gasPrice", [])));
const deployOverrides = {
type: 0,
gasPrice,
gasLimit: BigInt(process.env.CHAIN138_SUSHISWAP_DEPLOY_GAS_LIMIT || "9000000"),
};
const liquidityOverrides = {
type: 0,
gasPrice,
gasLimit: BigInt(process.env.CHAIN138_SUSHISWAP_LIQUIDITY_GAS_LIMIT || "3000000"),
};
const weth = envAddress("CHAIN138_NATIVE_WETH9", process.env.WETH9 || DEFAULTS.weth);
const usdt = envAddress("CHAIN138_NATIVE_USDT", process.env.OFFICIAL_USDT_ADDRESS || DEFAULTS.usdt);
const usdc = envAddress("CHAIN138_NATIVE_USDC", process.env.OFFICIAL_USDC_ADDRESS || DEFAULTS.usdc);
const cusdt = envAddress("CHAIN138_COMPLIANT_USDT", process.env.COMPLIANT_USDT_ADDRESS || DEFAULTS.cusdt);
const cusdc = envAddress("CHAIN138_COMPLIANT_USDC", process.env.COMPLIANT_USDC_ADDRESS || DEFAULTS.cusdc);
const Factory = await hre.ethers.getContractFactory(
"contracts/vendor/sushiswap-v2/UniswapV2Factory.sol:UniswapV2Factory"
);
const Router = await hre.ethers.getContractFactory(
"contracts/vendor/sushiswap-v2/UniswapV2Router02.sol:UniswapV2Router02"
);
const Pair = await hre.ethers.getContractFactory(
"contracts/vendor/sushiswap-v2/UniswapV2Pair.sol:UniswapV2Pair"
);
let factory;
let router;
const existingFactory = process.env.CHAIN138_SUSHISWAP_EXISTING_FACTORY?.trim();
const existingRouter = process.env.CHAIN138_SUSHISWAP_EXISTING_ROUTER?.trim();
if (existingFactory && existingRouter) {
factory = Factory.attach(existingFactory);
router = Router.attach(existingRouter);
console.log(`[reuse] factory=${existingFactory}`);
console.log(`[reuse] router=${existingRouter}`);
} else {
console.log(`[deploy] SushiSwapFactory gasPrice=${gasPrice}`);
factory = await Factory.deploy(feeTo.address, deployOverrides);
await factory.waitForDeployment();
console.log(`[ok] factory=${await factory.getAddress()}`);
console.log(`[deploy] SushiSwapRouter02`);
router = await Router.deploy(await factory.getAddress(), weth, deployOverrides);
await router.waitForDeployment();
console.log(`[ok] router=${await router.getAddress()}`);
}
const erc20Abi = [
"function transfer(address to,uint256 amount) returns (bool)",
"function approve(address spender,uint256 amount) returns (bool)",
"function allowance(address owner,address spender) view returns (uint256)",
"function balanceOf(address account) view returns (uint256)",
];
const tokenByAddress = {
[weth.toLowerCase()]: new hre.ethers.Contract(weth, erc20Abi, deployer),
[usdt.toLowerCase()]: new hre.ethers.Contract(usdt, erc20Abi, deployer),
[usdc.toLowerCase()]: new hre.ethers.Contract(usdc, erc20Abi, deployer),
[cusdt.toLowerCase()]: new hre.ethers.Contract(cusdt, erc20Abi, deployer),
[cusdc.toLowerCase()]: new hre.ethers.Contract(cusdc, erc20Abi, deployer),
};
const seedSpecs = [
{
key: "wethUsdt",
tokenA: weth,
tokenB: usdt,
amountA: envUnits("CHAIN138_SUSHISWAP_SEED_WETH_USDT_WETH", "25", 18),
amountB: envUnits("CHAIN138_SUSHISWAP_SEED_WETH_USDT_STABLE", "52915", 6),
},
{
key: "wethUsdc",
tokenA: weth,
tokenB: usdc,
amountA: envUnits("CHAIN138_SUSHISWAP_SEED_WETH_USDC_WETH", "25", 18),
amountB: envUnits("CHAIN138_SUSHISWAP_SEED_WETH_USDC_STABLE", "52915", 6),
},
{
key: "cusdtCusdc",
tokenA: cusdt,
tokenB: cusdc,
amountA: envUnits("CHAIN138_SUSHISWAP_SEED_CUSDT_CUSDC_CUSDT", "250000", 6),
amountB: envUnits("CHAIN138_SUSHISWAP_SEED_CUSDT_CUSDC_CUSDC", "250000", 6),
},
];
const deployedPairs = {};
for (const spec of seedSpecs) {
console.log(`[seed] ${spec.key}`);
let pairAddress = await factory.getPair(spec.tokenA, spec.tokenB);
if (pairAddress === hre.ethers.ZeroAddress) {
const tx = await factory.createPair(spec.tokenA, spec.tokenB, liquidityOverrides);
await tx.wait();
pairAddress = await factory.getPair(spec.tokenA, spec.tokenB);
}
const pair = Pair.attach(pairAddress);
const [reserve0, reserve1] = await pair.getReserves();
if (reserve0 > 0n || reserve1 > 0n) {
deployedPairs[spec.key] = pairAddress;
console.log(`[skip] ${spec.key} already seeded ${pairAddress}`);
continue;
}
const currentABalance = await tokenByAddress[spec.tokenA.toLowerCase()].balanceOf(pairAddress);
const currentBBalance = await tokenByAddress[spec.tokenB.toLowerCase()].balanceOf(pairAddress);
const topUpA = spec.amountA > currentABalance ? spec.amountA - currentABalance : 0n;
const topUpB = spec.amountB > currentBBalance ? spec.amountB - currentBBalance : 0n;
await transferIfNeeded(tokenByAddress[spec.tokenA.toLowerCase()], pairAddress, topUpA);
await transferIfNeeded(tokenByAddress[spec.tokenB.toLowerCase()], pairAddress, topUpB);
const mintTx = await pair.mint(deployer.address, liquidityOverrides);
await mintTx.wait();
deployedPairs[spec.key] = pairAddress;
console.log(`[ok] ${spec.key}=${deployedPairs[spec.key]}`);
}
const output = {
chainId: 138,
deployer: deployer.address,
feeToSetter: feeTo.address,
deployedAtBlock: currentBlock,
factory: await factory.getAddress(),
router: await router.getAddress(),
weth,
usdt,
usdc,
cusdt,
cusdc,
pairs: deployedPairs,
};
const outputPath = path.resolve(__dirname, "../../deployments/chain138/sushiswap-native.json");
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
fs.writeFileSync(outputPath, JSON.stringify(output, null, 2) + "\n");
console.log(JSON.stringify(output, null, 2));
}
main().catch((error) => {
console.error(error);
process.exit(1);
});

View File

@@ -0,0 +1,186 @@
const fs = require("fs");
const path = require("path");
const hre = require("hardhat");
const DEFAULTS = {
weth: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
usdt: "0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1",
usdc: "0x71D6687F38b93CCad569Fa6352c876eea967201b",
cusdt: "0x93E66202A11B1772E55407B32B44e5Cd8eda7f22",
cusdc: "0xf22258f57794CC8E06237084b353Ab30fFfa640b",
};
function envAddress(name, fallback) {
const value = String(process.env[name] || fallback || "").trim();
if (!value) {
throw new Error(`Missing required address env ${name}`);
}
return value;
}
function envUnits(name, fallback, decimals) {
return hre.ethers.parseUnits(String(process.env[name] || fallback), decimals);
}
async function ensureAllowance(token, owner, spender, amount) {
const allowance = await token.allowance(owner, spender);
if (allowance >= amount) return;
const tx = await token.approve(spender, hre.ethers.MaxUint256);
await tx.wait();
}
async function transferIfNeeded(token, to, amount) {
if (amount <= 0n) return;
const tx = await token.transfer(to, amount);
await tx.wait();
}
async function main() {
const [deployer] = await hre.ethers.getSigners();
const currentBlock = await hre.ethers.provider.getBlockNumber();
const gasPrice = BigInt(process.env.CHAIN138_NATIVE_V2_GAS_PRICE || (await hre.ethers.provider.send("eth_gasPrice", [])));
const deployOverrides = {
type: 0,
gasPrice,
gasLimit: BigInt(process.env.CHAIN138_NATIVE_V2_DEPLOY_GAS_LIMIT || "9000000"),
};
const liquidityOverrides = {
type: 0,
gasPrice,
gasLimit: BigInt(process.env.CHAIN138_NATIVE_V2_LIQUIDITY_GAS_LIMIT || "3000000"),
};
const weth = envAddress("CHAIN138_NATIVE_WETH9", process.env.WETH9 || DEFAULTS.weth);
const usdt = envAddress("CHAIN138_NATIVE_USDT", process.env.OFFICIAL_USDT_ADDRESS || DEFAULTS.usdt);
const usdc = envAddress("CHAIN138_NATIVE_USDC", process.env.OFFICIAL_USDC_ADDRESS || DEFAULTS.usdc);
const cusdt = envAddress("CHAIN138_COMPLIANT_USDT", process.env.COMPLIANT_USDT_ADDRESS || DEFAULTS.cusdt);
const cusdc = envAddress("CHAIN138_COMPLIANT_USDC", process.env.COMPLIANT_USDC_ADDRESS || DEFAULTS.cusdc);
const Factory = await hre.ethers.getContractFactory(
"contracts/vendor/uniswap-v2-core/UniswapV2Factory.sol:UniswapV2Factory"
);
const Router = await hre.ethers.getContractFactory(
"contracts/vendor/uniswap-v2-periphery/UniswapV2Router02.sol:UniswapV2Router02"
);
const Pair = await hre.ethers.getContractFactory(
"contracts/vendor/uniswap-v2-core/UniswapV2Pair.sol:UniswapV2Pair"
);
let factory;
let router;
const existingFactory = process.env.CHAIN138_UNISWAP_V2_EXISTING_FACTORY?.trim();
const existingRouter = process.env.CHAIN138_UNISWAP_V2_EXISTING_ROUTER?.trim();
if (existingFactory && existingRouter) {
factory = Factory.attach(existingFactory);
router = Router.attach(existingRouter);
console.log(`[reuse] factory=${existingFactory}`);
console.log(`[reuse] router=${existingRouter}`);
} else {
console.log(`[deploy] UniswapV2Factory gasPrice=${gasPrice}`);
factory = await Factory.deploy(deployer.address, deployOverrides);
await factory.waitForDeployment();
console.log(`[ok] factory=${await factory.getAddress()}`);
console.log(`[deploy] UniswapV2Router02`);
router = await Router.deploy(await factory.getAddress(), weth, deployOverrides);
await router.waitForDeployment();
console.log(`[ok] router=${await router.getAddress()}`);
}
const erc20Abi = [
"function transfer(address to,uint256 amount) returns (bool)",
"function approve(address spender,uint256 amount) returns (bool)",
"function allowance(address owner,address spender) view returns (uint256)",
"function balanceOf(address account) view returns (uint256)",
];
const tokenByAddress = {
[weth.toLowerCase()]: new hre.ethers.Contract(weth, erc20Abi, deployer),
[usdt.toLowerCase()]: new hre.ethers.Contract(usdt, erc20Abi, deployer),
[usdc.toLowerCase()]: new hre.ethers.Contract(usdc, erc20Abi, deployer),
[cusdt.toLowerCase()]: new hre.ethers.Contract(cusdt, erc20Abi, deployer),
[cusdc.toLowerCase()]: new hre.ethers.Contract(cusdc, erc20Abi, deployer),
};
const seedSpecs = [
{
key: "wethUsdt",
tokenA: weth,
tokenB: usdt,
amountA: envUnits("CHAIN138_UNISWAP_V2_SEED_WETH_USDT_WETH", "25", 18),
amountB: envUnits("CHAIN138_UNISWAP_V2_SEED_WETH_USDT_STABLE", "52915", 6),
},
{
key: "wethUsdc",
tokenA: weth,
tokenB: usdc,
amountA: envUnits("CHAIN138_UNISWAP_V2_SEED_WETH_USDC_WETH", "25", 18),
amountB: envUnits("CHAIN138_UNISWAP_V2_SEED_WETH_USDC_STABLE", "52915", 6),
},
{
key: "cusdtCusdc",
tokenA: cusdt,
tokenB: cusdc,
amountA: envUnits("CHAIN138_UNISWAP_V2_SEED_CUSDT_CUSDC_CUSDT", "250000", 6),
amountB: envUnits("CHAIN138_UNISWAP_V2_SEED_CUSDT_CUSDC_CUSDC", "250000", 6),
},
];
const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600);
const deployedPairs = {};
for (const spec of seedSpecs) {
console.log(`[seed] ${spec.key}`);
let pairAddress = await factory.getPair(spec.tokenA, spec.tokenB);
if (pairAddress === hre.ethers.ZeroAddress) {
const tx = await factory.createPair(spec.tokenA, spec.tokenB, liquidityOverrides);
await tx.wait();
pairAddress = await factory.getPair(spec.tokenA, spec.tokenB);
}
const pair = Pair.attach(pairAddress);
const [reserve0, reserve1] = await pair.getReserves();
if (reserve0 > 0n || reserve1 > 0n) {
deployedPairs[spec.key] = pairAddress;
console.log(`[skip] ${spec.key} already seeded ${pairAddress}`);
continue;
}
const currentABalance = await tokenByAddress[spec.tokenA.toLowerCase()].balanceOf(pairAddress);
const currentBBalance = await tokenByAddress[spec.tokenB.toLowerCase()].balanceOf(pairAddress);
const topUpA = spec.amountA > currentABalance ? spec.amountA - currentABalance : 0n;
const topUpB = spec.amountB > currentBBalance ? spec.amountB - currentBBalance : 0n;
await transferIfNeeded(tokenByAddress[spec.tokenA.toLowerCase()], pairAddress, topUpA);
await transferIfNeeded(tokenByAddress[spec.tokenB.toLowerCase()], pairAddress, topUpB);
const mintTx = await pair.mint(deployer.address, liquidityOverrides);
await mintTx.wait();
deployedPairs[spec.key] = pairAddress;
console.log(`[ok] ${spec.key}=${deployedPairs[spec.key]}`);
}
const output = {
chainId: 138,
deployer: deployer.address,
deployedAtBlock: currentBlock,
factory: await factory.getAddress(),
router: await router.getAddress(),
weth,
usdt,
usdc,
cusdt,
cusdc,
pairs: deployedPairs,
};
const outputPath = path.resolve(__dirname, "../../deployments/chain138/uniswap-v2-native.json");
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
fs.writeFileSync(outputPath, JSON.stringify(output, null, 2) + "\n");
console.log(JSON.stringify(output, null, 2));
}
main().catch((error) => {
console.error(error);
process.exit(1);
});

View File

@@ -0,0 +1,274 @@
/**
* Create and seed Uniswap V2 pairs on Chain 138: each canonical asset vs WETH9 (ERC-20) in the pair.
*
* Funding the ETH leg (no explicit wrap in this script):
* - Liquidity is added via UniswapV2Router02.addLiquidityETH: you send native ETH as `msg.value`.
* - The router wraps to WETH9 inside the call and mints LP; this script never calls WETH9.deposit().
* - Pair reserves are still WETH + token (AMM math cannot store raw ETH in the pair contract).
*
* Prerequisites:
* - PRIVATE_KEY; deployer holds native ETH (for `value` + gas) + tokens (or MINTER_ROLE to mint).
* - Factory + router: CHAIN138_UNISWAP_V2_EXISTING_* or deployments/chain138/uniswap-v2-native.json
*
* Usage (from smom-dbis-138):
* source scripts/load-env.sh
* npx hardhat run scripts/chain138/seed-uni-v2-weth-quote-pairs.js --network chain138
*
* Tuning:
* CHAIN138_NATIVE_WETH9 — must match routers WETH (default 0xC02a…)
* CHAIN138_UNI_V2_DEFAULT_WETH — native ETH `value` per new pair (default 0.25)
* CHAIN138_UNI_V2_STABLE_PER_WETH — USD face (6-decimal) per 1 ETH for stables (default 2100)
* CHAIN138_UNI_V2_SLIPPAGE_BPS — min amounts for addLiquidityETH (default 50 = 0.5%)
* CHAIN138_UNI_V2_ETH_GAS_BUFFER_WEI — extra wei required on top of `value` (default ~0.03 ETH for gas)
* Per-pair: CHAIN138_UNI_V2_SEED_<key>_WETH and CHAIN138_UNI_V2_SEED_<key>_TOKEN
*/
const fs = require("fs");
const path = require("path");
const hre = require("hardhat");
const WETH9 = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
/** Canonical Chain 138 tokens (see docs/11-references/EXPLORER_TOKEN_LIST_CROSSCHECK.md). */
const WETH_QUOTE_TARGETS = [
{ key: "wethLink", symbol: "LINK", address: "0xb7721dD53A8c629d9f1Ba31a5819AFe250002b03", decimals: 18, kind: "link" },
{ key: "wethCusdt", symbol: "cUSDT", address: "0x93E66202A11B1772E55407B32B44e5Cd8eda7f22", decimals: 6, kind: "stable6" },
{ key: "wethCusdc", symbol: "cUSDC", address: "0xf22258f57794CC8E06237084b353Ab30fFfa640b", decimals: 6, kind: "stable6" },
{ key: "wethWeth10", symbol: "WETH10", address: "0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f", decimals: 18, kind: "weth10" },
{ key: "wethCeurc", symbol: "cEURC", address: "0x8085961F9cF02b4d800A3c6d386D31da4B34266a", decimals: 6, kind: "stable6" },
{ key: "wethCeurt", symbol: "cEURT", address: "0xdf4b71c61E5912712C1Bdd451416B9aC26949d72", decimals: 6, kind: "stable6" },
{ key: "wethCgbpc", symbol: "cGBPC", address: "0x003960f16D9d34F2e98d62723B6721Fb92074aD2", decimals: 6, kind: "stable6" },
{ key: "wethCgbrt", symbol: "cGBPT", address: "0x350f54e4D23795f86A9c03988c7135357CCaD97c", decimals: 6, kind: "stable6" },
{ key: "wethCaudc", symbol: "cAUDC", address: "0xD51482e567c03899eecE3CAe8a058161FD56069D", decimals: 6, kind: "stable6" },
{ key: "wethCjpyc", symbol: "cJPYC", address: "0xEe269e1226a334182aace90056EE4ee5Cc8A6770", decimals: 6, kind: "stable6" },
{ key: "wethCchfc", symbol: "cCHFC", address: "0x873990849DDa5117d7C644f0aF24370797C03885", decimals: 6, kind: "stable6" },
{ key: "wethCcadc", symbol: "cCADC", address: "0x54dBd40cF05e15906A2C21f600937e96787f5679", decimals: 6, kind: "stable6" },
{ key: "wethCxauc", symbol: "cXAUC", address: "0x290E52a8819A4fbD0714E517225429aA2B70EC6b", decimals: 6, kind: "xau6" },
{ key: "wethCxaut", symbol: "cXAUT", address: "0x94e408E26c6FD8F4ee00b54dF19082FDA07dC96E", decimals: 6, kind: "xau6" },
{ key: "wethUsdt", symbol: "USDT", address: "0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1", decimals: 6, kind: "stable6" },
{ key: "wethUsdc", symbol: "USDC", address: "0x71D6687F38b93CCad569Fa6352c876eea967201b", decimals: 6, kind: "stable6" },
];
const erc20Abi = [
"function transfer(address to,uint256 amount) returns (bool)",
"function approve(address spender,uint256 amount) returns (bool)",
"function balanceOf(address account) view returns (uint256)",
];
const routerAbi = [
"function addLiquidityETH(address token,uint amountTokenDesired,uint amountTokenMin,uint amountETHMin,address to,uint deadline) payable returns (uint amountToken, uint amountETH, uint liquidity)",
];
const mintAbi = ["function mint(address to,uint256 amount)"];
function defaultWethFloat() {
return parseFloat(process.env.CHAIN138_UNI_V2_DEFAULT_WETH || "0.25");
}
function stablePerWethUsd() {
return parseFloat(process.env.CHAIN138_UNI_V2_STABLE_PER_WETH || "2100");
}
async function ensureMinted(tokenAddr, signer, need) {
const c = await hre.ethers.getContractAt(erc20Abi, tokenAddr, signer);
const bal = await c.balanceOf(signer.address);
if (bal >= need) return;
const short = need - bal;
const m = await hre.ethers.getContractAt(mintAbi, tokenAddr, signer);
await (await m.mint(signer.address, short)).wait();
}
async function ensureBalance(tokenAddr, signer, need, meta) {
const c = await hre.ethers.getContractAt(erc20Abi, tokenAddr, signer);
const bal = await c.balanceOf(signer.address);
if (bal >= need) return;
try {
await ensureMinted(tokenAddr, signer, need);
} catch (e) {
throw new Error(
`${meta.symbol}: need ${hre.ethers.formatUnits(need - bal, meta.decimals)} more; mint failed: ${e.message}`
);
}
}
/** Ensure wallet has enough native ETH for addLiquidityETH `value` plus a gas buffer (this script does not wrap). */
async function ensureNativeEth(signer, valueWei) {
const buffer = BigInt(process.env.CHAIN138_UNI_V2_ETH_GAS_BUFFER_WEI || "30000000000000000");
const need = valueWei + buffer;
const bal = await signer.provider.getBalance(signer.address);
if (bal < need) {
throw new Error(
`Native ETH: need at least ${hre.ethers.formatEther(need)} (${hre.ethers.formatEther(valueWei)} for pool + buffer); have ${hre.ethers.formatEther(bal)}`
);
}
}
function computeAmounts(meta) {
const envW = process.env[`CHAIN138_UNI_V2_SEED_${meta.key}_WETH`];
const envT = process.env[`CHAIN138_UNI_V2_SEED_${meta.key}_TOKEN`];
const ethWei =
envW !== undefined && envW !== ""
? hre.ethers.parseEther(String(envW))
: hre.ethers.parseEther(String(defaultWethFloat()));
const wethEth = parseFloat(hre.ethers.formatEther(ethWei));
let tokenStr;
if (envT !== undefined && envT !== "") {
tokenStr = String(envT);
} else {
switch (meta.kind) {
case "stable6":
tokenStr = String(Math.max(1, Math.round(stablePerWethUsd() * wethEth)));
break;
case "link":
tokenStr = String(Math.max(1, Math.round(100 * wethEth)));
break;
case "weth10":
tokenStr = hre.ethers.formatEther(ethWei);
break;
case "xau6":
tokenStr = String(Math.max(1, Math.round(1 * Math.max(wethEth, 0.01))));
break;
default:
tokenStr = "1";
}
}
const tokenAmt = hre.ethers.parseUnits(tokenStr, meta.decimals);
return { ethWei, tokenAmt };
}
function minAmounts(amount, bps) {
return (amount * (10000n - bps)) / 10000n;
}
async function main() {
const signers = await hre.ethers.getSigners();
if (!signers?.length) {
throw new Error("chain138: no signer — set PRIVATE_KEY (32-byte hex) for network chain138 in env");
}
const deployer = signers[0];
const gasPrice = BigInt(process.env.CHAIN138_NATIVE_V2_GAS_PRICE || (await hre.ethers.provider.send("eth_gasPrice", [])));
const txOverrides = {
type: 0,
gasPrice,
gasLimit: BigInt(process.env.CHAIN138_NATIVE_V2_LIQUIDITY_GAS_LIMIT || "3000000"),
};
const deploymentPath = path.resolve(__dirname, "../../deployments/chain138/uniswap-v2-native.json");
let factory = (process.env.CHAIN138_UNISWAP_V2_EXISTING_FACTORY || "").trim();
let routerAddr = (process.env.CHAIN138_UNISWAP_V2_EXISTING_ROUTER || "").trim();
if (fs.existsSync(deploymentPath)) {
const j = JSON.parse(fs.readFileSync(deploymentPath, "utf8"));
if (!factory) factory = j.factory;
if (!routerAddr) routerAddr = j.router;
}
if (!factory) {
throw new Error("Set CHAIN138_UNISWAP_V2_EXISTING_FACTORY or add deployments/chain138/uniswap-v2-native.json");
}
if (!routerAddr) {
throw new Error("Set CHAIN138_UNISWAP_V2_EXISTING_ROUTER or add router to deployments/chain138/uniswap-v2-native.json");
}
const Factory = await hre.ethers.getContractFactory(
"contracts/vendor/uniswap-v2-core/UniswapV2Factory.sol:UniswapV2Factory"
);
const Pair = await hre.ethers.getContractFactory(
"contracts/vendor/uniswap-v2-core/UniswapV2Pair.sol:UniswapV2Pair"
);
const factoryC = Factory.attach(factory);
const wethAddr = (process.env.CHAIN138_NATIVE_WETH9 || process.env.CHAIN138_WETH9_ADDRESS || WETH9).trim();
const router = await hre.ethers.getContractAt(routerAbi, routerAddr, deployer);
console.log(
`[eth] add liquidity via router addLiquidityETH (native ETH \`value\`; no WETH9.deposit() in this script) router=${routerAddr}`
);
console.log(`[weth9] pair reserve token: ${wethAddr} (router wraps ETH inside addLiquidityETH)`);
const slipBps = BigInt(process.env.CHAIN138_UNI_V2_SLIPPAGE_BPS || "50");
const skipKeys = new Set(
(process.env.CHAIN138_UNI_V2_SKIP_KEYS || "")
.split(",")
.map((s) => s.trim())
.filter(Boolean)
);
/** @type {{ key: string, pair: string, status: string }[]} */
const report = [];
for (const meta of WETH_QUOTE_TARGETS) {
if (skipKeys.has(meta.key)) {
report.push({ key: meta.key, pair: "", status: "skip_env" });
console.log(`[skip] ${meta.symbol} (${meta.key}) via CHAIN138_UNI_V2_SKIP_KEYS`);
continue;
}
const tokenAddr = meta.address;
if (tokenAddr.toLowerCase() === wethAddr.toLowerCase()) continue;
const { ethWei, tokenAmt } = computeAmounts(meta);
let pairAddress = await factoryC.getPair(wethAddr, tokenAddr);
if (pairAddress === hre.ethers.ZeroAddress) {
const tx = await factoryC.createPair(wethAddr, tokenAddr, txOverrides);
await tx.wait();
pairAddress = await factoryC.getPair(wethAddr, tokenAddr);
}
const pair = Pair.connect(deployer).attach(pairAddress);
const [r0, r1] = await pair.getReserves();
if (r0 > 0n || r1 > 0n) {
report.push({ key: meta.key, pair: pairAddress, status: "skip_existing_liquidity" });
console.log(`[skip] ${meta.symbol} already funded ${pairAddress}`);
continue;
}
const tokenC = await hre.ethers.getContractAt(erc20Abi, tokenAddr, deployer);
await ensureNativeEth(deployer, ethWei);
await ensureBalance(tokenAddr, deployer, tokenAmt, meta);
const allowance = await tokenC.allowance(deployer.address, routerAddr);
if (allowance < tokenAmt) {
await (await tokenC.approve(routerAddr, hre.ethers.MaxUint256)).wait();
}
const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600);
const tokenMin = minAmounts(tokenAmt, slipBps);
const ethMin = minAmounts(ethWei, slipBps);
console.log(`[eth] addLiquidityETH ${meta.symbol}: value=${hre.ethers.formatEther(ethWei)} ETH, token=${tokenAmt.toString()}`);
const tx = await router.addLiquidityETH(
tokenAddr,
tokenAmt,
tokenMin,
ethMin,
deployer.address,
deadline,
{ ...txOverrides, value: ethWei }
);
await tx.wait();
report.push({ key: meta.key, pair: pairAddress, status: "seeded" });
console.log(`[ok] ${meta.symbol} ${pairAddress}`);
}
const out = {
chainId: 138,
deployer: deployer.address,
factory,
router: routerAddr,
weth9: wethAddr,
results: report,
};
const outPath = path.resolve(__dirname, "../../deployments/chain138/uni-v2-weth-quote-seed.json");
fs.mkdirSync(path.dirname(outPath), { recursive: true });
fs.writeFileSync(outPath, JSON.stringify(out, null, 2) + "\n");
console.log(JSON.stringify(out, null, 2));
}
main().catch((e) => {
console.error(e);
process.exit(1);
});