Skip to content

Commit

Permalink
feat: Solana SPL-20 (#80)
Browse files Browse the repository at this point in the history
  • Loading branch information
fadeev authored Feb 21, 2025
1 parent 2c4bcfc commit a0cef9c
Show file tree
Hide file tree
Showing 22 changed files with 1,113 additions and 502 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"@coral-xyz/anchor": "^0.30.1",
"@inquirer/prompts": "^5.5.0",
"@mysten/sui": "^0.0.0-experimental-20250131013137",
"@solana/spl-token": "^0.4.12",
"@solana/web3.js": "^1.95.4",
"@uniswap/v2-core": "^1.0.1",
"@uniswap/v2-periphery": "^1.1.0-beta.0",
Expand All @@ -66,6 +67,7 @@
"elliptic": "6.5.7",
"ethers": "^6.13.2",
"hardhat": "^2.22.8",
"js-sha256": "^0.11.0",
"wait-on": "^7.2.0"
}
}
}
8 changes: 8 additions & 0 deletions packages/localnet/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,11 @@ export const FUNGIBLE_MODULE_ADDRESS =

export const MNEMONIC =
"grape subway rack mean march bubble carry avoid muffin consider thing street";

export const NetworkID = {
BNB: "97",
Ethereum: "5",
Solana: "901",
Sui: "103",
ZetaChain: "7000",
};
221 changes: 180 additions & 41 deletions packages/localnet/src/createToken.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
import { BN } from "@coral-xyz/anchor";
import {
createMint,
getOrCreateAssociatedTokenAccount,
mintTo,
} from "@solana/spl-token";
import { LAMPORTS_PER_SOL, PublicKey, SystemProgram } from "@solana/web3.js";
import * as TestERC20 from "@zetachain/protocol-contracts/abi/TestERC20.sol/TestERC20.json";
import * as ZRC20 from "@zetachain/protocol-contracts/abi/ZRC20.sol/ZRC20.json";
import { ethers } from "ethers";

import { NetworkID } from "./constants";
import { deployOpts } from "./deployOpts";
import { tssKeypair } from "./solanaSetup";

export const createToken = async (
addresses: any,
custody: any,
symbol: string,
isGasToken: boolean,
chainID: string,
decimals: number
decimals: number,
solanaEnv?: any
) => {
let erc20;
if (chainID === NetworkID.Solana && !solanaEnv) {
return;
}

const {
fungibleModuleSigner,
Expand Down Expand Up @@ -44,58 +56,26 @@ export const createToken = async (
gatewayZEVM.target,
deployOpts
);

await zrc20.waitForDeployment();

let asset;

if (isGasToken) {
(systemContract as any)
.connect(fungibleModuleSigner)
.setGasCoinZRC20(chainID, zrc20.target);
(systemContract as any)
.connect(fungibleModuleSigner)
.setGasPrice(chainID, 1);
asset = "";
} else if (chainID === NetworkID.Solana) {
asset = await createSolanaSPL(solanaEnv, symbol);
} else {
const erc20Factory = new ethers.ContractFactory(
TestERC20.abi,
TestERC20.bytecode,
deployer
);
if (custody) {
erc20 = await erc20Factory.deploy(symbol, symbol, deployOpts);
await erc20.waitForDeployment();
const erc20Decimals = await (erc20 as any).connect(deployer).decimals();

await (erc20 as any)
.connect(deployer)
.approve(custody.target, ethers.MaxUint256, deployOpts);

await (erc20 as any)
.connect(deployer)
.mint(
custody.target,
ethers.parseUnits("1000000", erc20Decimals),
deployOpts
);
await (erc20 as any)
.connect(deployer)
.mint(
tss.getAddress(),
ethers.parseUnits("1000000", erc20Decimals),
deployOpts
);
await (erc20 as any)
.connect(deployer)
.mint(
await deployer.getAddress(),
ethers.parseUnits("1000000", erc20Decimals),
deployOpts
);
await (custody as any).connect(tss).whitelist(erc20.target, deployOpts);
}
asset = await createERC20(deployer, custody, symbol, tss);
}

foreignCoins.push({
asset: isGasToken ? "" : (erc20 as any).target,
asset,
coin_type: isGasToken ? "Gas" : "ERC20",
decimals: 18,
foreign_chain_id: chainID,
Expand Down Expand Up @@ -156,3 +136,162 @@ export const createToken = async (
deployOpts
);
};

const createSolanaSPL = async (env: any, symbol: string) => {
const mint = await createMint(
env.gatewayProgram.provider.connection,
tssKeypair,
tssKeypair.publicKey,
null,
9
);
console.log(`Created new SPL token: ${mint.toBase58()}`);

const tssTokenAccount = await getOrCreateAssociatedTokenAccount(
env.gatewayProgram.provider.connection,
tssKeypair,
mint,
tssKeypair.publicKey
);

await mintTo(
env.gatewayProgram.provider.connection,
tssKeypair,
mint,
tssTokenAccount.address,
tssKeypair.publicKey,
100 * LAMPORTS_PER_SOL
);

const userTokenAccount = await getOrCreateAssociatedTokenAccount(
env.gatewayProgram.provider.connection,
env.defaultSolanaUser,
mint,
env.defaultSolanaUser.publicKey
);

await mintTo(
env.gatewayProgram.provider.connection,
tssKeypair,
mint,
userTokenAccount.address,
tssKeypair.publicKey,
100 * LAMPORTS_PER_SOL
);

const GATEWAY_PROGRAM_ID = env.gatewayProgram.programId;
console.log("Gateway Program ID:", GATEWAY_PROGRAM_ID.toBase58());

const [gatewayPDA] = await PublicKey.findProgramAddress(
[Buffer.from("meta")],
GATEWAY_PROGRAM_ID
);

const gatewayTokenAccount = await getOrCreateAssociatedTokenAccount(
env.gatewayProgram.provider.connection,
tssKeypair,
mint,
gatewayPDA,
true // allowOwnerOffCurve = true, because gatewayPDA is a program-derived address
);

await whitelistSPLToken(env.gatewayProgram, mint, env.defaultSolanaUser);

console.log("gatewayTokenAccount", gatewayTokenAccount.address.toBase58());

console.log(`TSS ${symbol} token account: ${tssTokenAccount.address}`);
console.log(
`Default user ${symbol} token account: ${userTokenAccount.address}`
);

return mint.toBase58();
};

const whitelistSPLToken = async (
gatewayProgram: any,
mintPublicKey: PublicKey,
authorityKeypair: any
) => {
const [gatewayPDA] = await PublicKey.findProgramAddress(
[Buffer.from("meta", "utf-8")],
gatewayProgram.programId
);

const pdaAccountData = await gatewayProgram.account.pda.fetch(gatewayPDA);
console.log("🚀 Gateway PDA Authority:", pdaAccountData.authority.toBase58());

if (!pdaAccountData.authority.equals(authorityKeypair.publicKey)) {
console.error(
"❌ Error: The provided signer is NOT the authority of the Gateway PDA."
);
process.exit(1);
}

const [whitelistEntryPDA] = await PublicKey.findProgramAddress(
[Buffer.from("whitelist"), mintPublicKey.toBuffer()],
gatewayProgram.programId
);

console.log(
"Whitelisting SPL Token. Whitelist Entry PDA:",
whitelistEntryPDA.toBase58()
);

await gatewayProgram.methods
.whitelistSplMint(new Uint8Array(64).fill(0), 0, new BN(0))
.accounts({
authority: authorityKeypair.publicKey,
pda: gatewayPDA,
systemProgram: SystemProgram.programId,
whitelistCandidate: mintPublicKey,
whitelistEntry: whitelistEntryPDA,
})
.signers([authorityKeypair])
.rpc();

console.log(`✅ Whitelisted SPL Token: ${mintPublicKey.toBase58()}`);
};

const createERC20 = async (
deployer: any,
custody: any,
symbol: any,
tss: any
) => {
const erc20Factory = new ethers.ContractFactory(
TestERC20.abi,
TestERC20.bytecode,
deployer
);
const erc20 = await erc20Factory.deploy(symbol, symbol, deployOpts);
await erc20.waitForDeployment();
const erc20Decimals = await (erc20 as any).connect(deployer).decimals();

await (erc20 as any)
.connect(deployer)
.approve(custody.target, ethers.MaxUint256, deployOpts);

await (erc20 as any)
.connect(deployer)
.mint(
custody.target,
ethers.parseUnits("1000000", erc20Decimals),
deployOpts
);
await (erc20 as any)
.connect(deployer)
.mint(
tss.getAddress(),
ethers.parseUnits("1000000", erc20Decimals),
deployOpts
);
await (erc20 as any)
.connect(deployer)
.mint(
await deployer.getAddress(),
ethers.parseUnits("1000000", erc20Decimals),
deployOpts
);
await (custody as any).connect(tss).whitelist(erc20.target, deployOpts);
return erc20.target;
};
106 changes: 106 additions & 0 deletions packages/localnet/src/evmSetup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import * as Custody from "@zetachain/protocol-contracts/abi/ERC20Custody.sol/ERC20Custody.json";
import * as ERC1967Proxy from "@zetachain/protocol-contracts/abi/ERC1967Proxy.sol/ERC1967Proxy.json";
import * as GatewayEVM from "@zetachain/protocol-contracts/abi/GatewayEVM.sol/GatewayEVM.json";
import * as TestERC20 from "@zetachain/protocol-contracts/abi/TestERC20.sol/TestERC20.json";
import * as ZetaConnectorNonNative from "@zetachain/protocol-contracts/abi/ZetaConnectorNonNative.sol/ZetaConnectorNonNative.json";
import { ethers, Signer } from "ethers";

import { deployOpts } from "./deployOpts";

export const evmSetup = async (deployer: Signer, tss: Signer) => {
const testERC20Factory = new ethers.ContractFactory(
TestERC20.abi,
TestERC20.bytecode,
deployer
);
const testEVMZeta = await testERC20Factory.deploy("zeta", "ZETA", deployOpts);

const gatewayEVMFactory = new ethers.ContractFactory(
GatewayEVM.abi,
GatewayEVM.bytecode,
deployer
);
const gatewayEVMImpl = await gatewayEVMFactory.deploy(deployOpts);

const gatewayEVMInterface = new ethers.Interface(GatewayEVM.abi);
const gatewayEVMInitFragment = gatewayEVMInterface.getFunction("initialize");
const gatewayEVMInitdata = gatewayEVMInterface.encodeFunctionData(
gatewayEVMInitFragment as ethers.FunctionFragment,
[await tss.getAddress(), testEVMZeta.target, await deployer.getAddress()]
);

const proxyEVMFactory = new ethers.ContractFactory(
ERC1967Proxy.abi,
ERC1967Proxy.bytecode,
deployer
);

const proxyEVM = (await proxyEVMFactory.deploy(
gatewayEVMImpl.target,
gatewayEVMInitdata,
deployOpts
)) as any;

const gatewayEVM = new ethers.Contract(
proxyEVM.target,
GatewayEVM.abi,
deployer
);

const zetaConnectorFactory = new ethers.ContractFactory(
ZetaConnectorNonNative.abi,
ZetaConnectorNonNative.bytecode,
deployer
);
const zetaConnectorImpl = await zetaConnectorFactory.deploy(deployOpts);

const custodyFactory = new ethers.ContractFactory(
Custody.abi,
Custody.bytecode,
deployer
);
const custodyImpl = await custodyFactory.deploy(deployOpts);

const zetaConnectorProxy = new ethers.Contract(
zetaConnectorImpl.target,
ZetaConnectorNonNative.abi,
deployer
);

const custodyProxy = new ethers.Contract(
custodyImpl.target,
Custody.abi,
deployer
);

// Temporarily disable
//
// await zetaConnectorProxy.initialize(
// gatewayEVM.target,
// testEVMZeta.target,
// await tss.getAddress(),
// await deployer.getAddress(),
// deployOpts
// );

await custodyProxy.initialize(
gatewayEVM.target,
await tss.getAddress(),
await deployer.getAddress(),
deployOpts
);

await (gatewayEVM as any)
.connect(deployer)
.setCustody(custodyImpl.target, deployOpts);
await (gatewayEVM as any)
.connect(deployer)
.setConnector(zetaConnectorImpl.target, deployOpts);

return {
custody: custodyProxy,
gatewayEVM,
testEVMZeta,
zetaConnector: zetaConnectorProxy,
};
};
Loading

0 comments on commit a0cef9c

Please sign in to comment.