Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ const config: HardhatUserConfig = {
networks: {
hardhat: {
forking: {
url: `https://zenchain-testnet.api.onfinality.io/rpc?apikey=${process.env.RPC_API_KEY}`,
url: `https://zenchain-testnet.api.onfinality.io/public`,
},
loggingEnabled: true,
chainId: 31337,
},
zenchainTestnet: {
url: `https://zenchain-testnet.api.onfinality.io/rpc?apikey=${process.env.RPC_API_KEY}`,
url: `https://zenchain-testnet.api.onfinality.io/public`,
chainId: 8408,
accounts: [process.env.DEPLOYER_PRIVATE_KEY!],
minGasPrice: 1000000000,
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"deploy:localhost": "npx hardhat run scripts/deploy.ts --network localhost ---show-stack-traces",
"deploy:testnet": "npx hardhat run scripts/deploy.ts --network zenchainTestnet ---show-stack-traces",
"hardhat": "npx hardhat run --network zenchainTestnet --show-stack-traces",
"mint:testnet": "MINT_TO=0x123 MINT_AMOUNT=1000 npx hardhat run scripts/mintTokens.ts --network zenchainTestnet --show-stack-traces",
"dev": "concurrently \"npm run node\" \"wait-on http://localhost:8545 && npm run deploy:localhost\"",
"reset": "npm run clean && npm run dev"
},
Expand Down
177 changes: 92 additions & 85 deletions scripts/addLiquidity.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import hre, { network } from "hardhat";
import { readDeploymentRecord, addLiquidityToPair } from "./utils";
import { Address, parseUnits } from "viem";
import { readDeploymentRecord, addLiquidityToPair, tokenList, getTokenDecimals, getTokenBalance } from "./utils";
import { Address, formatUnits } from "viem";

async function main() {
const [walletClient] = await hre.viem.getWalletClients();
Expand All @@ -16,89 +16,96 @@ async function main() {

console.log("Deployed Contracts:", deployedContracts);

// 1 ETH = 2750 USDC
console.log("Adding liquidity for ETH/USDC pair...");
await addLiquidityToPair({
walletClient,
publicClient,
routerAddress: deployedContracts.router as Address,
tokenA: deployedContracts.tokens.ETH as Address,
tokenB: deployedContracts.tokens.USDC as Address,
amountADesired: parseUnits("1", 18 + 3).toString(),
amountBDesired: parseUnits("2750", 18 + 3).toString(),
mintTokensA: true, // Mint ETH if it's a mock token
mintTokensB: true, // Mint USDC if it's a mock token
});

// 1 ETH = 2750 USDT
console.log("Adding liquidity for ETH/USDT pair...");
await addLiquidityToPair({
walletClient,
publicClient,
routerAddress: deployedContracts.router as Address,
tokenA: deployedContracts.tokens.ETH as Address,
tokenB: deployedContracts.tokens.USDT as Address,
amountADesired: parseUnits("1", 18 + 6).toString(),
amountBDesired: parseUnits("2750", 18 + 6).toString(),
mintTokensA: true, // Mint ETH if it's a mock token
mintTokensB: true, // Mint USDT if it's a mock token
});

// 1 ETH = 6.875 ZTC
console.log("Adding liquidity for ZTC/ETH pair...");
await addLiquidityToPair({
walletClient,
publicClient,
routerAddress: deployedContracts.router as Address,
tokenA: deployedContracts.tokens.ZTC as Address,
tokenB: deployedContracts.tokens.ETH as Address,
amountADesired: parseUnits("6.875", 18 + 1).toString(),
amountBDesired: parseUnits("1", 18 + 1).toString(),
mintTokensA: false,
mintTokensB: true, // Mint ETH if it's a mock token
});

// 1 ZTC = 400 USDC
console.log("Adding liquidity for ZTC/USDC pair...");
await addLiquidityToPair({
walletClient,
publicClient,
routerAddress: deployedContracts.router as Address,
tokenA: deployedContracts.tokens.ZTC as Address,
tokenB: deployedContracts.tokens.USDC as Address,
amountADesired: parseUnits("1", 18 + 1).toString(),
amountBDesired: parseUnits("400", 18 + 1).toString(),
mintTokensA: false,
mintTokensB: true,
});

// 1 ZTC = 400 USDT
console.log("Adding liquidity for ZTC/USDT pair...");
await addLiquidityToPair({
walletClient,
publicClient,
routerAddress: deployedContracts.router as Address,
tokenA: deployedContracts.tokens.ZTC as Address,
tokenB: deployedContracts.tokens.USDT as Address,
amountADesired: parseUnits("1", 18 + 2).toString(),
amountBDesired: parseUnits("400", 18 + 2).toString(),
mintTokensA: false,
mintTokensB: true, // Mint USDT if it's a mock token
});

//1 USDC = 1 USDT
console.log("Adding liquidity for USDC/USDT pair...");
await addLiquidityToPair({
walletClient,
publicClient,
routerAddress: deployedContracts.router as Address,
tokenA: deployedContracts.tokens.USDC as Address,
tokenB: deployedContracts.tokens.USDT as Address,
amountADesired: parseUnits("1", 18 + 5).toString(),
amountBDesired: parseUnits("1", 18 + 5).toString(),
mintTokensA: true, // Mint USDC if it's a mock token
mintTokensB: true, // Mint USDT if it's a mock token
});
// Define USD price map for consistent ratios
const priceUSD: Record<string, number> = {
ETH: 2750,
USDC: 1,
USDT: 1,
ZTC: 400,
BTC: 55000,
};

// Utility to safely extract token symbols from pair key like "ETHUSDC"
const getPairSymbols = (pairKey: string): [keyof typeof deployedContracts.tokens, keyof typeof deployedContracts.tokens] | null => {
for (const a of tokenList) {
if (pairKey.startsWith(a)) {
const b = pairKey.slice(a.length) as keyof typeof deployedContracts.tokens;
if ((tokenList as readonly string[]).includes(b)) {
return [a as keyof typeof deployedContracts.tokens, b];
}
}
}
return null;
};

// Iterate all configured pairs for the network and add liquidity
for (const [pairKey] of Object.entries(deployedContracts.pairs)) {
const syms = getPairSymbols(pairKey);
if (!syms) {
console.warn(`Skipping unknown pair key ${pairKey}`);
continue;
}
const [symA, symB] = syms;
const tokenA = deployedContracts.tokens[symA] as Address;
const tokenB = deployedContracts.tokens[symB] as Address;
if (!tokenA || !tokenB) {
console.warn(`Token addresses missing for pair ${pairKey}: ${symA}=${tokenA}, ${symB}=${tokenB}`);
continue;
}

// Choose amounts that reflect equal USD value on both sides.
// We target $1000 per side for reasonable sizes, but clamp to available balances for non-mintable tokens (e.g., ZTC).
const usdTargetPerSide = 1000;
const priceA = priceUSD[String(symA)] ?? 1;
const priceB = priceUSD[String(symB)] ?? 1;

const ztcAddress = deployedContracts.tokens.ZTC as Address;
const mintA = tokenA.toLowerCase() !== ztcAddress.toLowerCase();
const mintB = tokenB.toLowerCase() !== ztcAddress.toLowerCase();

const [decA, decB] = await Promise.all([
getTokenDecimals({ publicClient, tokenAddress: tokenA }),
getTokenDecimals({ publicClient, tokenAddress: tokenB }),
]);
const [balAWei, balBWei] = await Promise.all([
getTokenBalance({ publicClient, tokenAddress: tokenA, owner: owner as Address }),
getTokenBalance({ publicClient, tokenAddress: tokenB, owner: owner as Address }),
]);
const balAHuman = parseFloat(formatUnits(balAWei, decA));
const balBHuman = parseFloat(formatUnits(balBWei, decB));

const availUsdA = mintA ? Number.POSITIVE_INFINITY : balAHuman * priceA;
const availUsdB = mintB ? Number.POSITIVE_INFINITY : balBHuman * priceB;
const usdPerSideActual = Math.min(usdTargetPerSide, availUsdA, availUsdB);

if (usdPerSideActual <= 0) {
console.warn(`Skipping ${pairKey}: insufficient non-mintable token balance (mintA=${mintA}, mintB=${mintB}).`);
continue;
}

const amountA = (usdPerSideActual / priceA).toString();
const amountB = (usdPerSideActual / priceB).toString();

console.log(`\nAdding liquidity for ${pairKey} -> ${symA}/${symB}`);
console.log(`Target USD per side: $${usdTargetPerSide}, using $${usdPerSideActual}. Computed amounts: ${symA}=${amountA}, ${symB}=${amountB}`);
console.log(`Mint flags: ${symA}=${mintA}, ${symB}=${mintB}. Balances: ${symA}=${balAHuman}, ${symB}=${balBHuman}`);

try {
await addLiquidityToPair({
walletClient,
publicClient,
routerAddress: deployedContracts.router as Address,
tokenA,
tokenB,
amountA,
amountB,
mintTokensA: mintA,
mintTokensB: mintB,
});
} catch (e) {
console.error(`Failed to add liquidity for ${pairKey}`, e);
}
}

}

Expand Down
67 changes: 67 additions & 0 deletions scripts/mintTokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import hre, { network } from "hardhat";
import { Address, parseUnits } from "viem";
import { readDeploymentRecord, getTokenDecimals, mintMockToken } from "./utils";

async function main() {
const [walletClient] = await hre.viem.getWalletClients();
const publicClient = await hre.viem.getPublicClient();
const owner = walletClient.account.address;
const networkName = network.name;

const toEnv = process.env.MINT_TO;
const amountEnv = process.env.MINT_AMOUNT;

if (!toEnv || !amountEnv) {
console.error("Missing environment variables MINT_TO and/or MINT_AMOUNT.");
console.error("Set them inline in the npm script or your environment, e.g. MINT_TO=0x... MINT_AMOUNT=1000");
process.exit(1);
}

const to = toEnv as Address;
const amountHuman = amountEnv; // human-readable amount to mint for each token (e.g., "1000")

console.log(`Minting ${amountHuman} of each selected token to: ${to}`);
console.log(`Using network: ${networkName}, sender: ${owner}`);

const deployments = await readDeploymentRecord(networkName);

const tokenEntries = Object.entries(deployments.tokens) as [keyof typeof deployments.tokens, string][];
// Skip ZTC (non-mintable by current account)
const mintableTokens = tokenEntries.filter(([sym]) => String(sym).toUpperCase() !== "ZTC");

console.log(`Tokens to mint: ${mintableTokens.map(([sym]) => sym).join(", ")}`);

for (const [sym, tokenAddress] of mintableTokens) {
const tokenAddr = tokenAddress as Address | undefined;
if (!tokenAddr) {
console.warn(`Skipping ${sym}: Address not found in deployments for ${networkName}.`);
continue;
}

try {
const decimals = await getTokenDecimals({ publicClient, tokenAddress: tokenAddr });
const amountWei = parseUnits(amountHuman, decimals).toString();

console.log(`\nMinting ${amountHuman} ${String(sym)} (decimals: ${decimals}) to ${to} @ ${tokenAddr}`);
await mintMockToken({
walletClient,
publicClient,
tokenAddress: tokenAddr,
amount: amountWei,
decimals,
forAddress: to,
});
console.log(`Minted ${amountHuman} ${String(sym)} to ${to}`);
} catch (err) {
console.error(`Failed to mint ${String(sym)} at ${tokenAddr}:`, err);
// Continue with next token
}
}

console.log("\nMint process completed.");
}

main().catch((e) => {
console.error(e);
process.exit(1);
});
Loading