diff --git a/.gitignore b/.gitignore index 149b429a..b33c754b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,8 @@ scripts/node_modules -scripts/.env \ No newline at end of file +scripts/package-lock.json +scripts/.env + +example/node_modules +example/package-lock.json +example/.env +example/infra/.env \ No newline at end of file diff --git a/README.md b/README.md index 291db1f9..5511b0e9 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,10 @@ Constructors parameters can be specified in the `contract_config.json`. The rust repository is strictly used for tests purposes. +## Example + +A fully runnable example lives in the [`example`](example/README.md) folder. + ## 📖 License This project is licensed under the **MIT license**. See [LICENSE](LICENSE) for more information. diff --git a/example/.env.example b/example/.env.example new file mode 100644 index 00000000..c4f622f6 --- /dev/null +++ b/example/.env.example @@ -0,0 +1,13 @@ +# Starknet RPC URL +STARKNET_RPC_URL=https://starknet-sepolia.public.blastapi.io/rpc/v0_8 +# Starknet Sepolia account address +ACCOUNT_ADDRESS= +# fees beneficiary address +BENEFICIARY_ADDRESS= +# Starknet Sepolia network +NETWORK=starknet_sepolia +# Starknet Sepolia private key +PRIVATE_KEY= + +# gnosis chiado private key +GNOSIS_CHIADO_PK= diff --git a/example/README.md b/example/README.md new file mode 100644 index 00000000..fa02baaf --- /dev/null +++ b/example/README.md @@ -0,0 +1,241 @@ +# Hyperlane - Starknet Sepolia <> Gnosis Chiado - Warp Route Deployment Guide + +We use the Hyperlane protocol to deploy a Warp route between Starknet and Gnosis Chiado. This allows us to transfer tokens between these two networks efficiently. + +## Table of Contents + +- [Setup](#setup) +- [Deploy Warp Route on Starknet and Gnosis Chiado](#deploy-warp-route-on-starknet-and-gnosis-chiado) +- [Set Up the Relayer](#set-up-the-relayer) +- [Start the Relayer](#start-the-relayer) +- [Testing the Warp Route](#testing-the-warp-route) +- [🚩 Pitfalls](#🚩-pitfalls) + +## Setup + +To set up the project, follow these steps: + +### Install Dependencies + +1. Make sure dependencies are installed on both the `/example` and `/scripts` directory: + +```bash +npm i +cd ../scripts && npm i && cd ../example +``` + +2. Build the Cairo contracts: + +```bash +cd ../cairo && scarb build && cd ../example +``` + +### Setup Environment Variables + +1. Copy the `.env.example` file from `/example` directory to `.env` and populate the necessary variables + +2. Copy the `.env.example` file from `/scripts` directory to `.env` and populate the necessary variables + +### Gas Fees + +Ensure you have enough gas fees in your Starknet Sepolia account. You can use the [Starknet Faucet](https://starknet-faucet.vercel.app/) to get some test STRK. + +### Hyperlane CLI + +Install the [Hyperlane CLI](https://docs.hyperlane.xyz/docs/reference/developer-tools/cli) globally: + +```bash +npm install -g @hyperlane-xyz/cli +``` + +## Deploy Warp Route on Starknet and Gnosis Chiado + +This guide will help you deploy the Warp route on Starknet and Gnosis Chiado. + +1. **Deploy Starknet Core Contracts** + + ```bash + npm run deploy + ``` + + A file with all deployed contracts will be generated at the following path `./scripts/deployments/starknet_sepolia/deployments.json`. + +2. **Generate `starknetsepolia-deploy.yaml` file:** + + ```bash + hyperlane warp init + ``` + +
+ The prompts should be similar to the following: (click to expand) + + ? Select chains to connect gnosischiadotestnet, starknetsepolia
+ ? Is this chain selection correct?: gnosischiadotestnet, starknetsepolia yes
+ gnosischiadotestnet: Configuring warp route...
+ ? Enter the desired owner address: 0x_your_gnosischiado_owner_address
+ ? Use an existing Proxy Admin contract for the warp route deployment on chain "gnosischiadotestnet"? no
+ ? Do you want to use a trusted ISM for warp route? yes
+ ? Select gnosischiadotestnet's token type collateral
+ ? Enter the existing token address on chain gnosischiadotestnet 0x_gnosischiado_token_you_want_to_bridge_address
+ starknetsepolia: Configuring warp route...
+ ? Enter the desired owner address: 0x_your_starknetsepolia_owner_address
+ ? Use an existing Proxy Admin contract for the warp route deployment on chain "starknetsepolia"? no
+ ? Do you want to use a trusted ISM for warp route? yes
+ ? Select starknetsepolia's token type synthetic
+ Warp Route config is valid, writing to file undefined: + +
+
+ +3. **Build the Warp route contract:** + + ```bash + npm run deploy-route + ``` + + This will deploy the Warp route contract on Starknet and print the deployed contract address. + +
+ Example output: + + Declaring contract HypERC20...
+ Contract HypERC20 declared with class hash: 0x_class_hash
+ HypERC20 contract deployed at address: 0x_deployed_contract_address + +
+
+ +4. **Replace the `starknetsepolia` section in `starknetsepolia-deploy.yaml`** + + ```yaml + starknetsepolia: + foreignDeployment: 0x # deployed contract + remoteDecimals: 18 # Chiado token decimals + mailbox: 0x # mailbox contract + interchainSecurityModule: 0x + owner: 0x # your address + type: synthetic + ``` + + the file is located at `~/.hyperlane/deployments/warp_routes/$COLLATERAL_TICKER` + +
+ Example starknetsepolia-deploy.yaml + + ```yaml + gnosischiadotestnet: + interchainSecurityModule: + modules: + - relayer: '0x_your_relayer_address' + type: trustedRelayerIsm + - domains: {} + owner: '0x_your_gnosischiado_ism_owner_address' + type: defaultFallbackRoutingIsm + threshold: 1 + type: staticAggregationIsm + owner: '0x_your_gnosischiado_owner_address' + token: '0x_gnosischiado_token_you_want_to_bridge_address' + type: collateral + starknetsepolia: + foreignDeployment: '0x_deployed_contract_address_from_step_3' # address from step 3 + remoteDecimals: 18 # decimals of the token on Gnosis Chiado + mailbox: '0x_your_starknetsepolia_mailbox_address' + interchainSecurityModule: '0x_your_starknetsepolia_ism_address' + type: synthetic # the type of contract deployed on Starknet, here it is synthetic of the original token + owner: '0x_your_starknetsepolia_owner_address' + ``` + +
+
+ +5. **Deploy the Warp route to Gnosis Chiado:** + + ```bash + hyperlane warp deploy + ``` + + Select your token route from the list, and confirm the deployment. + Once it's done, you will see something like this: + `Successfully deployed contracts on gnosischiadotestnet` + + Then `Error: address bytes must not be empty`, it's a known issue, you can ignore it as we enroll the router on next step. + +6. **Enroll the remote router on Starknet** + + Go to `enroll-remote-router.ts` and update `REMOTE_TOKEN_ADDRESS`, `REMOTE_DOMAIN_ID`, and `STARKNET_SYNTHETIC_ADDRESS` with the values from the previous steps. + + then run the script: + + ```bash + npm run enroll-remote-router + ``` + +7. **Update the hooks on Starknet** + + You can update the hooks on Starknet using the following command: + + ```bash + npm run update-hooks -- -r $REQUIRED_HOOK -d $DEFAULT_HOOK + ``` + + Replace `$REQUIRED_HOOK` and `$DEFAULT_HOOK` with the desired hook addresses. + + Example: + + ```bash + npm run update-hooks -- -r 0x_required_hook_address -d 0x_default_hook_address + ``` + +
+ +You are now ready to use the Warp route on both Starknet and Gnosis Chiado 🎉 + +
+ +## Set Up the Relayer + +### Configure Environment Variables + +To set up the Hyperlane relayer, configure the environment variables. Copy the example environment file and modify it as needed: + +```bash +cp infra/.env.example infra/.env +``` + +### Update the config.json file + +Update the `infra/config.json` file with the contract addresses from the deployments.json file generated in step 1. + +## Start the Relayer + +At this point, you can start the Hyperlane relayer to handle messages between the two networks: + +```bash +cd infra && docker-compose -f docker-compose.relayer.yml up --force-recreate --remove-orphans +``` + +## Testing the Warp Route + +The Hyperlane commands cannot send messages to non-EVM chains. + +## Validator + +The validator is not implemented in this example + +### Starknet ➡ Gnosis Chiado + +```bash +npm run bridge:to:starknet +``` + +### Gnosis Chiado ➡ Starknet + +```bash +npm run bridge:to:gnosis +``` + +## 🚩 Pitfalls + +- Ensure the ISMs (Interchain Security Modules) are correctly set up on both networks. Or that the NoopIsm is used for testing purposes. +- Approve HypERC20Collateral spender on collateral token (Original ERC20). +- To bridge from Starknet, do not forget to mention the `new CairoOption(CairoOptionVariant.None)` for `hook`and `hook_metadata` diff --git a/example/infra/.env.example b/example/infra/.env.example new file mode 100644 index 00000000..e74d0bbf --- /dev/null +++ b/example/infra/.env.example @@ -0,0 +1,14 @@ +# ---------- Relayer signer (can be same key on both chains) ---------- +HYP_CHAINS_CHIADO_SIGNER_KEY= +HYP_CHAINS_STARKNETSEPOLIA_SIGNER_KEY= +HYP_CHAINS_STARKNETSEPOLIA_SIGNER_ADDRESS= +HYP_CHAINS_STARKNETSEPOLIA_SIGNER_IMPLEMENTATION=argent + +# Checkpoint storage (both agents) +HYP_CHECKPOINTSYNCER_TYPE=localStorage +HYP_CHECKPOINTSYNCER_PATH=/hyperlane_db + + +# VALIDATORS PRIVATE KEYS +VALIDATOR_PK_CHIADO= +VALIDATOR_PK_STARK= \ No newline at end of file diff --git a/example/infra/config.json b/example/infra/config.json new file mode 100644 index 00000000..d50f6b02 --- /dev/null +++ b/example/infra/config.json @@ -0,0 +1,112 @@ +{ + "relayChains": "starknetsepolia,chiado", + "chains": { + "starknetsepolia": { + "signer": { + "type": "starkKey" + }, + "gasCurrencyCoinGeckoId": "starknet", + "name": "starknetsepolia", + "displayName": "Starknet Sepolia", + "protocol": "starknet", + "chainId": "0x534e5f5345504f4c4941", + "domainId": 23448591, + "isTestnet": true, + "blocks": { + "confirmations": 1, + "estimateBlockTime": 30, + "reorgPeriod": 1 + }, + + "rpcUrls": [{ "http": "https://starknet-sepolia.public.blastapi.io/rpc/v0_8" }], + + "blockExplorers": [ + { + "name": "Starknet Sepolia Explorer", + "family": "voyager", + "url": "https://sepolia.voyager.online", + "apiUrl": "https://sepolia.voyager.online/api" + } + ], + + "nativeToken": { + "name": "Stark", + "symbol": "STRK", + "decimals": 18, + "denom": "0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d" + }, + "merkleRootMultisigIsm": "0x048...", + "messageIdMultisigIsm": "0x0499...", + "domainRoutingIsm": "0x05d2...", + "noopIsm": "0x02dc...", + "pausableIsm": "0x07ff...", + "aggregation": "0x031e...", + "protocolFee": "0x0466...", + "hook": "0x0462...", + "mailbox": "0x02e5...", + "merkleTreeHook": "0x06ac...", + "defaultFallbackRoutingIsm": "0x0474...", + "trustedRelayerIsm": "0x027d...", + "domainRoutingHook": "0x025d...", + "validator_announce": "0x025d...", + "interchainGasPaymaster": "0x0000000000000000000000000000000000000000", + "index": { + "from": 1250000 + } + }, + "chiado": { + "blockExplorers": [ + { + "apiUrl": "https://gnosis-chiado.blockscout.com/api", + "family": "blockscout", + "name": "Chiado Explorer", + "url": "https://gnosis-chiado.blockscout.com" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 5, + "reorgPeriod": 5 + }, + "chainId": 10200, + "deployer": { + "name": "k4ctuss", + "url": "https://github.com/k4ctuss" + }, + "displayName": "Gnosis Chiado Testnet", + "domainId": 10200, + "isTestnet": true, + "name": "chiado", + "nativeToken": { + "decimals": 18, + "name": "xDai", + "symbol": "xDai" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://gnosis-chiado-rpc.publicnode.com" + } + ], + "technicalStack": "other", + "domainRoutingIsmFactory": "0x7c09b92a4818B08b8aA47a11698652fC6Aee11F0", + "interchainAccountIsm": "0xCbCDfC039ED11e0ff461dDDDe0825E4fd454c2b7", + "interchainAccountRouter": "0x4e93f12403eA3AA3e467b2e02044b42912e3cAb0", + "mailbox": "0x7323E06E40B0Fb82Dc26D309AaF14e49D266A6a7", + "merkleTreeHook": "0x9Da0eb6667033e01936cD914b46E32d9560D848B", + "proxyAdmin": "0x75A7BFEd3c9de77c5C27205D6Ddf772744ca48fc", + "staticAggregationHookFactory": "0x50fDFa26b9e9cCFf4Af8d6A0F95FBdc0F78F6E99", + "staticAggregationIsmFactory": "0xe95E3EF49b0fB2151301480d1845E760B5575A39", + "staticMerkleRootMultisigIsmFactory": "0xcE41FAc8bfAcb89f6AF3978E655C8839Ee21803a", + "staticMerkleRootWeightedMultisigIsmFactory": "0x32Ada46916c814704378af86163D1861aD8d4f29", + "staticMessageIdMultisigIsmFactory": "0xEB94C84C1aDB6aBA8866EC457d85374642937E2d", + "staticMessageIdWeightedMultisigIsmFactory": "0xEc02d931299Dc41342fD655f62b79345bA646714", + "testRecipient": "0x4e2d35b59FaC85FEFE30416745e743F9c7CaCB02", + "validatorAnnounce": "0xEb079356da4fE505ebA2018EB4D3B2db036B8Da5", + "interchainGasPaymaster": "0x0000000000000000000000000000000000000000", + "index": { + "from": 16990000 + } + } + } +} diff --git a/example/infra/docker-compose.relayer.yml b/example/infra/docker-compose.relayer.yml new file mode 100644 index 00000000..0bb1b3ec --- /dev/null +++ b/example/infra/docker-compose.relayer.yml @@ -0,0 +1,22 @@ +services: + relayer: + platform: linux/amd64 + # b4048d0-20250606-201810 = main branch on the 09 June 2025 + image: gcr.io/abacus-labs-dev/hyperlane-agent:b4048d0-20250606-201810 + container_name: hl-relayer + command: ['./relayer'] + environment: + CONFIG_FILES: /etc/hyperlane/overrides.json + HYP_CHAINS_CHIADO_SIGNER_KEY: ${HYP_CHAINS_CHIADO_SIGNER_KEY} + HYP_CHAINS_STARKNETSEPOLIA_SIGNER_KEY: ${HYP_CHAINS_STARKNETSEPOLIA_SIGNER_KEY} + HYP_CHAINS_STARKNETSEPOLIA_SIGNER_ADDRESS: ${HYP_CHAINS_STARKNETSEPOLIA_SIGNER_ADDRESS} + + volumes: + - relayer_db:/hyperlane_db + # mount the custom domain-list read-only **exactly** at the path above + - ./config.json:/etc/hyperlane/overrides.json:ro + + restart: unless-stopped + +volumes: + relayer_db: {} diff --git a/example/package.json b/example/package.json new file mode 100644 index 00000000..ff1a8a48 --- /dev/null +++ b/example/package.json @@ -0,0 +1,25 @@ +{ + "name": "example", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "deploy": "cd ../scripts && npm run deploy && cd ../example", + "deploy-route": "ts-node scripts/deploy-route.ts", + "update-hooks": "cd ../scripts && npm run update-hooks --", + "enroll-remote-router": "ts-node scripts/enroll-remote-router", + "bridge:to:starknet": "ts-node scripts/bridge.ts chiado", + "bridge:to:gnosis": "ts-node scripts/bridge.ts starknetsepolia" + }, + "keywords": [], + "author": "", + "license": "MIT", + "dependencies": { + "@types/node": "^22.0.2", + "ethers": "^6.14.3", + "dotenv": "^16.4.5", + "fs": "^0.0.1-security", + "starknet": "^7.6.4", + "ts-node": "^10.9.2" + } +} diff --git a/example/scripts/bridge.ts b/example/scripts/bridge.ts new file mode 100644 index 00000000..bed7d853 --- /dev/null +++ b/example/scripts/bridge.ts @@ -0,0 +1,150 @@ +/**************************************************************** + * bridge.ts – Hyperlane Warp-Route helper + * + * Usage: + * ts-node bridge.ts chiado # lock on Chiado, mint on Starknet + * ts-node bridge.ts starknet # burn on Starknet, unlock on Chiado + * + ****************************************************************/ + +import 'dotenv/config'; +import { ethers } from 'ethers'; +import { Account, BigNumberish, cairo, CairoOption, CairoOptionVariant, Contract, json } from 'starknet'; + +import dotenv from 'dotenv'; +import { buildAccount } from '../../scripts/utils'; // ← your helper + +import fs from 'fs'; + +type Order = { + p1: BigNumberish; + p2: BigNumberish; +}; + +dotenv.config(); + +const TOKEN_PATH = '../cairo/target/dev/token'; + +function findContractFile(name: string, suffix: string): string { + const tokenPath = `${TOKEN_PATH}_${name}${suffix}`; + + if (fs.existsSync(tokenPath)) { + return tokenPath; + } + + throw new Error(`Contract file not found for ${name} with suffix ${suffix} at ${tokenPath}`); +} + +export function getCompiledContract(name: string): any { + const contractPath = findContractFile(name, '.contract_class.json'); + return json.parse(fs.readFileSync(contractPath).toString('ascii')); +} + +/**************************************************************** + * ENV + ****************************************************************/ + +// ---------- Chiado ---------- +const CHIADO_RPC = 'https://gnosis-chiado-rpc.publicnode.com'; +const GNOSIS_CHIADO_PK = process.env.GNOSIS_CHIADO_PK as string; // EOA that holds the token +const CHIADO_DOMAIN_ID = '10200'; // Chiado domain-id +const CHIADO_RECIPIENT = '0x_your_gnosischiado_recipient_address'; // recipient on Gnosis Chiado + +// the token address on Gnosis Chiado we want to bridge +const collateralAddress = '0x_gnosischiado_token_you_want_to_bridge_address'; +// the HypERC20Collateral contract on Gnosis Chiado chain +const hypERC20CollateralAddress = '0x_deployed_hyper20_collateral_address'; // HypERC20Collateral on Chiado + +// ---------- Starknet ---------- +const STARKNET_HYPERC20_ADDRESS = '0x_synthetic_contract_address'; // synthetic HypERC20 on Starknet +const STARKNET_RECIPIENT = '0x_starknet_recipient'; // felt recipient on Starknet +const STARKNET_DOMAIN_ID = '23448591'; // Starknet domain-id (fixed in the protocol) + +if (!hypERC20CollateralAddress || !STARKNET_HYPERC20_ADDRESS) { + throw new Error('Missing HypERC20Collateral or STARKNET_HYPERC20_ADDRESS'); +} + +/**************************************************************** + * MAIN + ****************************************************************/ +async function bridge(origin: 'chiado' | 'starknetsepolia') { + if (origin === 'chiado') { + /*---------------------------------------------------------- + * Chiado ➜ Starknet (lock & mint) + *---------------------------------------------------------*/ + if (!GNOSIS_CHIADO_PK || !STARKNET_RECIPIENT) { + throw new Error('Set GNOSIS_CHIADO_PK and STARKNET_RECIPIENT in .env'); + } + const provider = new ethers.JsonRpcProvider(CHIADO_RPC); + const wallet = new ethers.Wallet(GNOSIS_CHIADO_PK, provider); + + const collateralAbi = ['function approve(address spender, uint256 amount) public returns (bool)']; + + // Minimal ABI: approve + transferRemote + const hypERC20Abi = [ + 'function transferRemote(uint32 domain, bytes32 recipient, uint256 amount) public returns (bool)' + ]; + + const hypERC20CollateralContract = new ethers.Contract(hypERC20CollateralAddress, hypERC20Abi, wallet); + const collateralContract = new ethers.Contract(collateralAddress, collateralAbi, wallet); + + const amount = ethers.parseUnits('0.1', 18); // 0.1 token + + console.log('🔐 approving collateral contract…'); + const tx1 = await collateralContract.approve(hypERC20CollateralAddress, amount); + await tx1.wait(); + console.log(`✔️ approve() → ${tx1.hash}`); + + console.log('🚚 sending transferRemote…'); + const tx2 = await hypERC20CollateralContract.transferRemote( + Number(STARKNET_DOMAIN_ID), + ethers.zeroPadValue(STARKNET_RECIPIENT, 32), // bytes32 recipient + amount + ); + await tx2.wait(); + console.log(`✅ transferRemote() → ${tx2.hash}`); + console.log('⏳ Relayer will mint on Starknet once signatures arrive.'); + } else if (origin === 'starknetsepolia') { + /*------------------------------------------------------------ + * Starknet ➜ Chiado (burn & unlock) + *-----------------------------------------------------------*/ + const account: Account = await buildAccount(); + + const compiledContract = getCompiledContract('HypErc20'); + const tokenContract = new Contract(compiledContract.abi, STARKNET_HYPERC20_ADDRESS, account); + + const amount = 1; + const formattedAmount = cairo.uint256(amount * 10 ** 18); + const recipient = '0x' + CHIADO_RECIPIENT!.slice(2).padStart(64, '0'); + + console.log('🔥 burning synthetic token on Starknet…'); + const txRes = await tokenContract.invoke('transfer_remote', [ + // gnosis chain domain id + Number(CHIADO_DOMAIN_ID), + recipient, // gnosis chiado recipient + formattedAmount, // amount + 0, // value + new CairoOption(CairoOptionVariant.None), // hook_metadata + new CairoOption(CairoOptionVariant.None) // hook + ]); + + await account.waitForTransaction(txRes.transaction_hash); + + console.log(`✅ burn sent → ${txRes.transaction_hash}`); + console.log('⏳ Relayer will unlock on Chiado once signatures arrive.'); + } +} + +/**************************************************************** + * Run + ****************************************************************/ +const arg = process.argv[2] as 'chiado' | 'starknetsepolia'; +if (!arg || !['chiado', 'starknetsepolia'].includes(arg)) { + console.error('Usage: ts-node bridge.ts '); + process.exit(1); +} + +bridge(arg).catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/example/scripts/deploy-route.ts b/example/scripts/deploy-route.ts new file mode 100644 index 00000000..e7004b5c --- /dev/null +++ b/example/scripts/deploy-route.ts @@ -0,0 +1,93 @@ +import dotenv from 'dotenv'; + +import fs from 'fs'; +import { Account, byteArray, cairo, CallData, ContractFactory, ContractFactoryParams, json } from 'starknet'; +import TESTNET_DEPLOYMENTS from '../../scripts/deployments/STARKNET_SEPOLIA/deployments.json'; +import { buildAccount } from '../../scripts/utils'; + +dotenv.config(); + +const TOKEN_PATH = '../cairo/target/dev/token'; + +function findContractFile(name: string, suffix: string): string { + const tokenPath = `${TOKEN_PATH}_${name}${suffix}`; + + if (fs.existsSync(tokenPath)) { + return tokenPath; + } + + throw new Error(`Contract file not found for ${name} with suffix ${suffix} at ${tokenPath}`); +} + +export function getCompiledContract(name: string): any { + const contractPath = findContractFile(name, '.contract_class.json'); + return json.parse(fs.readFileSync(contractPath).toString('ascii')); +} + +export function getCompiledContractCasm(name: string): any { + const contractPath = findContractFile(name, '.compiled_contract_class.json'); + return json.parse(fs.readFileSync(contractPath).toString('ascii')); +} + +export async function declareContract(account: Account, name: string): Promise { + console.log(`Declaring contract ${name}...`); + const compiledContract = getCompiledContract(name); + const casm = getCompiledContractCasm(name); + + const declareResponse = await account.declareIfNot({ + contract: compiledContract, + casm + }); + + console.log(`Contract ${name} declared with class hash:`, declareResponse.class_hash); + + return declareResponse.class_hash; +} + +/** + * We deploy an HypERC20 contract to the Starknet network. + */ +async function main() { + const account = await buildAccount(); + + if (!TESTNET_DEPLOYMENTS.mailbox || !TESTNET_DEPLOYMENTS.hook || !TESTNET_DEPLOYMENTS.aggregation) { + throw new Error( + 'Missing required deployment addresses in TESTNET_DEPLOYMENTS. \n> Please run make deploy-starknet-contracts first' + ); + } + + // to update as you need + const constructor = { + decimals: 18, + mailbox: TESTNET_DEPLOYMENTS.mailbox as string, + total_supply: cairo.uint256('0'), + name: byteArray.byteArrayFromString('yourDesiredTokenName'), + symbol: byteArray.byteArrayFromString('yourDesiredTokenSymbol'), + hook: TESTNET_DEPLOYMENTS.hook as string, + // please not that we use noop_ism for testing purposes + // in production you should use the actual ISM contract address + interchain_security_module: TESTNET_DEPLOYMENTS.noop_ism as string, + owner: account.address as string + }; + + const contractName = 'HypERC20'; + + const classHash = await declareContract(account, contractName); + + const compiledContract = getCompiledContract(contractName); + const casm = getCompiledContractCasm(contractName); + const constructorCalldata = CallData.compile(constructor); + const params: ContractFactoryParams = { + compiledContract, + account, + casm, + classHash + }; + + const contractFactory = new ContractFactory(params); + const contract = await contractFactory.deploy(constructorCalldata); + + console.log(`HypERC20 contract deployed at address: ${contract.address}`); +} + +main(); diff --git a/example/scripts/enroll-remote-router.ts b/example/scripts/enroll-remote-router.ts new file mode 100644 index 00000000..6b9d8a37 --- /dev/null +++ b/example/scripts/enroll-remote-router.ts @@ -0,0 +1,67 @@ +import dotenv from 'dotenv'; + +import fs from 'fs'; +import { CallData, Contract, json, num, uint256 } from 'starknet'; +import TESTNET_DEPLOYMENTS from '../../scripts/deployments/STARKNET_SEPOLIA/deployments.json'; +import { buildAccount } from '../../scripts/utils'; + +dotenv.config(); + +// the hypERC20Collateral contract on Gnosis Chiado chain +const REMOTE_TOKEN_ADDRESS = '0x_your_gnosischiado_token_you_want_to_bridge_address'; +// the domain ID of the Gnosis chain +const REMOTE_DOMAIN_ID = '10200'; // Gnosis Chiado chain domain ID +// synthetic on starknet +const STARKNET_SYNTHETIC_ADDRESS = '0x_your_synthetic_hyper20_address'; // synthetic HypERC20 on Starknet + +function findContractFile(name: string, suffix: string): string { + const TOKEN_PATH = '../cairo/target/dev/token'; + const tokenPath = `${TOKEN_PATH}_${name}${suffix}`; + console.log('tokenPath:', tokenPath); + if (fs.existsSync(tokenPath)) { + return tokenPath; + } + + throw new Error(`Contract file not found for ${name} with suffix ${suffix} at ${tokenPath}`); +} + +function getCompiledContract(name: string): any { + const contractPath = findContractFile(name, '.contract_class.json'); + return json.parse(fs.readFileSync(contractPath).toString('ascii')); +} +/** + * We deploy an HypERC20 contract to the Starknet network. + */ +async function main() { + const account = await buildAccount(); + + if (!TESTNET_DEPLOYMENTS.mailbox || !TESTNET_DEPLOYMENTS.hook || !TESTNET_DEPLOYMENTS.aggregation) { + throw new Error('Missing required deployment addresses in TESTNET_DEPLOYMENTS'); + } + + const constructor = { + // gnosis chain domain id + domain: REMOTE_DOMAIN_ID, + router: uint256.bnToUint256(num.toBigInt(REMOTE_TOKEN_ADDRESS)) + }; + + const constructorCalldata = CallData.compile(constructor); + + const compiledContract = getCompiledContract('HypErc20'); + + const contract = new Contract(compiledContract.abi, STARKNET_SYNTHETIC_ADDRESS); + + console.log('Enrolling remote router for HypERC20 contract...'); + + const tx = await account.execute({ + contractAddress: contract.address, + entrypoint: 'enroll_remote_router', + calldata: constructorCalldata + }); + + await account.waitForTransaction(tx.transaction_hash); + + console.log(`✅ Remote router enrolled successfully for HypERC20 contract at address ${STARKNET_SYNTHETIC_ADDRESS}`); +} + +main(); diff --git a/example/tsconfig.json b/example/tsconfig.json new file mode 100644 index 00000000..b6ae8e73 --- /dev/null +++ b/example/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "outDir": "./dist" + }, + "include": ["./**/*.ts"], + "exclude": ["node_modules"] + } \ No newline at end of file diff --git a/scripts/deployments/STARKNET_SEPOLIA/deployments.json b/scripts/deployments/STARKNET_SEPOLIA/deployments.json index b558d4cc..9911ae3c 100644 --- a/scripts/deployments/STARKNET_SEPOLIA/deployments.json +++ b/scripts/deployments/STARKNET_SEPOLIA/deployments.json @@ -1,16 +1,16 @@ { - "merkleroot_multisig_ism": "0x07041a9408d8ee588a48e858fbc59dbb6232b0fe5b4c315d0cab192d151b81ff", - "messageid_multisig_ism": "0x05273d583af7d0721a8ba770206f86ecac7c61fcbaf937e7412d7d04c59b7b3c", - "domain_routing_ism": "0x079956e5090451e3ba0aae43ac1fedd2ed378ad234f4c95b3a4bafe708357603", - "noop_ism": "0x0328e944edb4b1c1e625b9cbb94d6188078e61db33363fb937a2e06ff38b963f", - "pausable_ism": "0x02337f6a5aac407fd2d5ed8e1acd5df2be12633e79c38aacfc8ded680e05dfe1", - "aggregation": "0x075c85b618e5c0469596759578db275a526dce21a6c252b48437f65da799e3af", - "protocol_fee": "0x001e485245b14127de40c9c35281e919acae751e8136bcefcc8033468d6c966d", - "hook": "0x072471e42a5368856ecd5aad27ec0259e3d34c63495c04124e6a3ce55c46cbd5", - "mailbox": "0x07df35d3796603c930747bfc8409173aefc2c4c2c9cae4c0ea4908ea9a3baef2", - "merkle_tree_hook": "0x03577d5fcfa888ee618cfab4563029f2f22db76f8a03e7ed96afa9410e2d0af5", - "default_fallback_routing_ism": "0x045a69c3f44acd7fffb81a64bf00de9d0b1c19ee2fa333ea4b3887fbea44de47", - "trusted_relayer_ism": "0x0168a0e02bba6a10f7fedd211884d2a1d57e28729380161e720d6aa7b1e5ce3e", - "validator_announce": "0x07ae6742b4cd35ee92d6399c046ca2594e8374bb466d8e25aa7f88e2f68f8d9d", - "domain_routing_hook": "0x06b60a487e0cd8419fee8e1cd06091aeddb55e358aedf96ea77ef49296d3ddbe" + "merkleroot_multisig_ism": "0x055e9b55c578eb298550a3d383d4bed3e7f1ea7d0388b572f7cd853ff2f224cb", + "messageid_multisig_ism": "0x03e15ccc5e88083aa0489c151660ab3d393bb11dcd57e2a0515ca51b71e459ba", + "domain_routing_ism": "0x06e916f9a76a45388895740549ef8e183ece59aa7956a102f8d250daebdc076e", + "noop_ism": "0x05e5406d8deaf2968b45bf6146f2f150833842add925a100d1fe3a08a85889bb", + "pausable_ism": "0x02c81e4c5d0eb3c19f37be03ea8c60664dfd6a5477e6be94fcff3f78435e5cb4", + "aggregation": "0x034e92b39e161461f0de92111261c864f748a661c5f16a87d5187a4d496531e8", + "protocol_fee": "0x005162b1d0da6e8b3c051d90fdca92f7b128407b9b53f656a066176204b284bd", + "hook": "0x034bbfd915737767338aac7e87b01b26e0a1323bc0015a8f69746ade378be661", + "mailbox": "0x022dd896eb97a118a4d0198a534bd13924e748a5447077beee1ef4746f4cc0c5", + "merkle_tree_hook": "0x01b91e252ec2f94833d5d74a34474fdf4b0d8912cf4931b0710e0dc7e1a204e3", + "default_fallback_routing_ism": "0x05d278142e85b2556a339f130844acfabc44f07ef3a8571bb78a17023337518c", + "trusted_relayer_ism": "0x05128cfd9a6a26dd7f02e22ff2ead3719d818a57f92e93a94411ecd7b1089f15", + "validator_announce": "0x033a88e24d37d397d017ef2302add746d823f6e2ef875aea96f5ccc0ecc118ed", + "domain_routing_hook": "0x05fec9deb9eee6fe6466951022666e860bd9a45906fcb802f85889091630e96e" } \ No newline at end of file diff --git a/scripts/package.json b/scripts/package.json index f33951f5..d64777f1 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -15,6 +15,6 @@ "commander": "^12.1.0", "dotenv": "^16.4.5", "fs": "^0.0.1-security", - "starknet": "^6.11.0" + "starknet": "^7.6.4" } } diff --git a/scripts/update-hooks.ts b/scripts/update-hooks.ts index f13094db..e3ca0fbd 100644 --- a/scripts/update-hooks.ts +++ b/scripts/update-hooks.ts @@ -6,6 +6,9 @@ import { Command } from "commander"; dotenv.config(); +// example usage: +// npm run update-hooks -- -r 0x_required_hook_address -d 0x_default_hook_address + // Initialize commander const program = new Command();