diff --git a/.env.example b/.env.example index fc1b313..19a3a94 100644 --- a/.env.example +++ b/.env.example @@ -1,12 +1,10 @@ PRIVATE_KEY=yourprivatekeyhere CONTRACT_KEY=yourkeyhere MAINNET=FALSE -OJO_CONTRACT_ADDRESS=0x0 -AXELAR_GAS_RECEIVER_ADDRESS=0x2d5d7d31F671F86C782533cc367F14109a082712 -CREATE2_DEPLOYER_ADDRESS=0x98b2920d53612483f91f12ed7754e51b4a77919e +EVM_CHAINS=["Ethereum"] OJO_CHAIN=ojo OJO_ADDRESS=ojo1es9mhmnunh208ucwq8rlrl97hqulxrz8k37dcu RESOLVE_WINDOW=7200 ASSET_LIMIT=5 -PRICE_FEED_IMPLEMENTATION_CONTRACT_ADDRESS=0xD1077c12ba7C0ED41d288F5505af2Cb23bBD680a -CLONE_FACTORY_CONTRACT_ADDRESS=0x9AaE2ac2637B9f441d1537bBdCEB712854dd426B +PRICE_FEED_DECIMALS=9 +PRICE_FEED_DESCRIPTIONS=["steakLRT", "Re7LRT", "amphrETH", "rstETH"] diff --git a/mainnet_chains.json b/mainnet_chains.json index 77e730e..0d8e102 100644 --- a/mainnet_chains.json +++ b/mainnet_chains.json @@ -4,20 +4,47 @@ "chainId": 42161, "gateway": "0xe432150cce91c13a887f7D836923d5597adD8E31", "rpc": "https://arbitrum-mainnet.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161", - "tokenSymbol": "ETH" + "tokenSymbol": "ETH", + "gasReceiver": "0x2d5d7d31F671F86C782533cc367F14109a082712", + "ojoContract": "0x5BB3E85f91D08fe92a3D123EE35050b763D6E6A7", + "create2Deployer": "0x98b2920d53612483f91f12ed7754e51b4a77919e", + "priceFeedImplementation": "0xa1aB70C0F3725AcA1D1e85Bd4402Dd2d5F6AFf19", + "cloneFactory": "0xd285A4F0Ad1BB6b1Db8cD3dD839E9f423938ef9E" }, { "name": "Optimism", "chainId": 10, "gateway": "0xe432150cce91c13a887f7D836923d5597adD8E31", "rpc": "https://optimism-mainnet.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161", - "tokenSymbol": "ETH" + "tokenSymbol": "ETH", + "gasReceiver": "0x2d5d7d31F671F86C782533cc367F14109a082712", + "ojoContract": "0x5BB3E85f91D08fe92a3D123EE35050b763D6E6A7", + "create2Deployer": "0x98b2920d53612483f91f12ed7754e51b4a77919e", + "priceFeedImplementation": "0xfaC9d315b9b558e10eBdb0462aA42577aADe6601", + "cloneFactory": "0x02Ed15B70D4dE1209c3Dd5a75195CB3f3dDB8B07" }, { "name": "Base", "chainId": 8453, "gateway": "0xe432150cce91c13a887f7D836923d5597adD8E31", "rpc": "https://developer-access-mainnet.base.org", - "tokenSymbol": "ETH" + "tokenSymbol": "ETH", + "gasReceiver": "0x2d5d7d31F671F86C782533cc367F14109a082712", + "ojoContract": "0x5BB3E85f91D08fe92a3D123EE35050b763D6E6A7", + "create2Deployer": "0x98b2920d53612483f91f12ed7754e51b4a77919e", + "priceFeedImplementation": "0x09d43904C8ABd470df1B793df68904A9714558CF", + "cloneFactory": "0xfaC9d315b9b558e10eBdb0462aA42577aADe6601" + }, + { + "name": "Ethereum", + "chainId": 1, + "gateway": "0x4F4495243837681061C4743b74B3eEdf548D56A5", + "rpc": "https://mainnet.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161", + "tokenSymbol": "ETH", + "gasReceiver": "0x2d5d7d31F671F86C782533cc367F14109a082712", + "ojoContract": "0x5BB3E85f91D08fe92a3D123EE35050b763D6E6A7", + "create2Deployer": "0x98b2920d53612483f91f12ed7754e51b4a77919e", + "priceFeedImplementation": "0xde471274F1B684476d341eB131224F389AD4A270", + "cloneFactory": "0x710C8a3c8CB393cA24748849de3585b5C48D4D0c" } ] diff --git a/relayer/config/config.go b/relayer/config/config.go index 2b870f6..5024520 100644 --- a/relayer/config/config.go +++ b/relayer/config/config.go @@ -21,14 +21,15 @@ var ( type ( // Config defines all necessary configuration parameters. Config struct { - ConfigDir string `mapstructure:"config_dir"` - Account Account `mapstructure:"account" validate:"required,gt=0,dive,required"` - Keyring Keyring `mapstructure:"keyring" validate:"required,gt=0,dive,required"` - RPC RPC `mapstructure:"rpc" validate:"required,gt=0,dive,required"` - Gas uint64 `mapstructure:"gas"` - GasPrices string `mapstructure:"gas_prices"` - Relayer Relayer `mapstructure:"relayer" validate:"required,gt=0,dive,required"` - Assets []Assets `mapstructure:"assets" validate:"required,gt=0,dive,required"` + ConfigDir string `mapstructure:"config_dir"` + Account Account `mapstructure:"account" validate:"required,gt=0,dive,required"` + Keyring Keyring `mapstructure:"keyring" validate:"required,gt=0,dive,required"` + RPC RPC `mapstructure:"rpc" validate:"required,gt=0,dive,required"` + Gas uint64 `mapstructure:"gas"` + GasPrices string `mapstructure:"gas_prices"` + Relayer Relayer `mapstructure:"relayer" validate:"required,gt=0,dive,required"` + Assets []Assets `mapstructure:"assets" validate:"required,gt=0,dive,required"` + AxelarGas AxelarGas `mapstructure:"axelar_gas" validate:"required,gt=0,dive,required"` } // Account defines account related configuration that is related to the Ojo @@ -56,7 +57,12 @@ type ( Deviation float64 `mapstructure:"deviation" validate:"required"` Destination string `mapstructure:"destination" validate:"required"` Contract string `mapstructure:"contract" validate:"required"` - Tokens string `mapstructure:"tokens" validate:"required"` + } + + AxelarGas struct { + Denom string `mapstructure:"denom" validate:"required"` + Multiplier string `mapstructure:"multiplier" validate:"required"` + Default string `mapstructure:"default" validate:"required"` } Assets struct { diff --git a/relayer/relayer.toml b/relayer/relayer.toml index 4a406ec..dfd606e 100644 --- a/relayer/relayer.toml +++ b/relayer/relayer.toml @@ -22,10 +22,15 @@ interval = "24h" deviation = "0.05" destination = "Arbitrum" contract = "0x001" -tokens = "100ibc/xyz" # These are the assets we want to periodically push: [[assets]] denom = "BTC" [[assets]] denom = "ETH" + +# This struct is used to estimate the gas prices to pay axelar +[axelar_gas] +denom = "ibc/xyz" +multiplier = "1.2" +default = "1000000" diff --git a/relayer/relayer/client/client.go b/relayer/relayer/client/client.go index 4603cc1..f659284 100644 --- a/relayer/relayer/client/client.go +++ b/relayer/relayer/client/client.go @@ -274,7 +274,10 @@ func (rc RelayerClient) BroadcastTx(nextBlockHeight, timeoutHeight int64, msgs . resp, err := BroadcastTx(clientCtx, factory, msgs...) if resp != nil && resp.Code != 0 { telemetry.IncrCounter(1, "failure", "tx", "code") - err = fmt.Errorf("invalid response code from tx: %d", resp.Code) + err = fmt.Errorf("invalid response code from tx: %d. msg: %s", + resp.Code, + resp.RawLog, + ) } if err != nil { var ( diff --git a/relayer/relayer/client/gas_estimate.go b/relayer/relayer/client/gas_estimate.go new file mode 100644 index 0000000..5088f88 --- /dev/null +++ b/relayer/relayer/client/gas_estimate.go @@ -0,0 +1,100 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + + math "cosmossdk.io/math" +) + +// Assuming the endpoint URL is something like this +const ( + endpointURL = "https://api.axelarscan.io/gmp/estimateGasFee" + sourceChain = "ojo" +) + +// executeData is an example value used to estimate +// the cost of a GMP relay. In practice, this value is constructed +// by the Ojo validators dynamically. +// Ref: https://github.com/ojo-network/ojo/blob/2965d45976ea63053dc84b910b37ea46a06730b5/x/gmp/keeper/keeper.go#L88 +// nolint: lll +const executeData = "0x00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000b20000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b60000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000205245374c52540000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003a2642918000000000000000000000000000000000000000000000000000000000066960af100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000000000000000000000000000000006a0000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000005eebff00000000000000000000000000000000000000000000000000000000005ee4f700000000000000000000000000000000000000000000000000000000005eddef00000000000000000000000000000000000000000000000000000000005ed6e700000000000000000000000000000000000000000000000000000000005ecfdf00000000000000000000000000000000000000000000000000000000005ec8d700000000000000000000000000000000000000000000000000000000005ec1cf00000000000000000000000000000000000000000000000000000000005ebac700000000000000000000000000000000000000000000000000000000005eb3bf00000000000000000000000000000000000000000000000000000000005eacb700000000000000000000000000000000000000000000000000000000005ea5af00000000000000000000000000000000000000000000000000000000005e9ea700000000000000000000000000000000000000000000000000000000005e979f00000000000000000000000000000000000000000000000000000000005e909700000000000000000000000000000000000000000000000000000000005e898f00000000000000000000000000000000000000000000000000000000005e828700000000000000000000000000000000000000000000000000000000005e7b7f00000000000000000000000000000000000000000000000000000000005e747700000000000000000000000000000000000000000000000000000000005e6d6f00000000000000000000000000000000000000000000000000000000005f0f2700000000000000000000000000000000000000000000000000000000005f081f00000000000000000000000000000000000000000000000000000000005f011700000000000000000000000000000000000000000000000000000000005efa0f00000000000000000000000000000000000000000000000000000000005ef307000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000396fb886e0000000000000000000000000000000000000000000000000000000390b235200000000000000000000000000000000000000000000000000000000392e8739f000000000000000000000000000000000000000000000000000000039341dbce000000000000000000000000000000000000000000000000000000038de6f3a8000000000000000000000000000000000000000000000000000000037fb311860000000000000000000000000000000000000000000000000000000376da178a00000000000000000000000000000000000000000000000000000003677c2f7600000000000000000000000000000000000000000000000000000003674094ac0000000000000000000000000000000000000000000000000000000365daf3f000000000000000000000000000000000000000000000000000000003693b3861000000000000000000000000000000000000000000000000000000036cd7179c0000000000000000000000000000000000000000000000000000000368e1d032000000000000000000000000000000000000000000000000000000036386e80c0000000000000000000000000000000000000000000000000000000364b0edfe00000000000000000000000000000000000000000000000000000003632d7fdd000000000000000000000000000000000000000000000000000000035f383873000000000000000000000000000000000000000000000000000000035d01f9f4000000000000000000000000000000000000000000000000000000035cc65f2a00000000000000000000000000000000000000000000000000000003b1dfde9100000000000000000000000000000000000000000000000000000003b46f853f00000000000000000000000000000000000000000000000000000003b522559d00000000000000000000000000000000000000000000000000000003a61dd5b8000000000000000000000000000000000000000000000000000000039e15797f000000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000029baff41400000000000000000000000000000000000000000000000000000001b79f446d000000000000000000000000000000000000000000000000000000025c7afe17000000000000000000000000000000000000000000000000000000026b4946bc000000000000000000000000000000000000000000000000000000016f821e43000000000000000000000000000000000000000000000000000000062b0deeee00000000000000000000000000000000000000000000000000000002882b577e00000000000000000000000000000000000000000000000000000003cf8ec3f800000000000000000000000000000000000000000000000000000000da9873a400000000000000000000000000000000000000000000000000000000fe66049e000000000000000000000000000000000000000000000000000000014fadc3c300000000000000000000000000000000000000000000000000000001912acb0e00000000000000000000000000000000000000000000000000000000ecec7ca600000000000000000000000000000000000000000000000000000002ffe0015500000000000000000000000000000000000000000000000000000001919cfdd300000000000000000000000000000000000000000000000000000003b249cbd0000000000000000000000000000000000000000000000000000000011c81159200000000000000000000000000000000000000000000000000000000d5a49cb800000000000000000000000000000000000000000000000000000000f1dedade00000000000000000000000000000000000000000000000000000001e84f443f00000000000000000000000000000000000000000000000000000001b9921fc6000000000000000000000000000000000000000000000000000000071e341559000000000000000000000000000000000000000000000000000000043b0ba83e000000000000000000000000000000000000000000000000000000021ec0616700000000000000000000000000000000000000000000000000000000000000015245374c525400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + +// RequestBody defines the structure of the request body. +// Adjust the fields according to the actual API requirements. +type RequestBody struct { + DestinationChain string `json:"destinationChain"` + ExecuteData string `json:"executeData"` + DestinationAddress string `json:"destinationAddress"` + GasLimit string `json:"gasLimit"` + SourceChain string `json:"sourceChain"` + ShowDetailedFees bool `json:"showDetailedFees"` +} +type ResponseBody struct { + TotalFee string `json:"totalFee"` + Message string `json:"message"` + Error bool `json:"error"` +} + +// EstimateGasFee performs a JSON POST request to estimate gas fee on Axelar. +// Its output is a math.Int representing the total fee +// denominated in uaxl. +func EstimateGasFee( + destinationChain, + destinationAddress, + gasLimit, + multiplier string, +) (math.Int, error) { + // construct and perform request + body := RequestBody{ + DestinationChain: destinationChain, + ExecuteData: executeData, + DestinationAddress: destinationAddress, + GasLimit: gasLimit, + SourceChain: sourceChain, + ShowDetailedFees: true, + } + jsonBody, err := json.Marshal(body) + if err != nil { + return math.ZeroInt(), err + } + req, err := http.NewRequest("POST", endpointURL, bytes.NewBuffer(jsonBody)) + if err != nil { + return math.ZeroInt(), err + } + req.Header.Set("Content-Type", "application/json") + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return math.ZeroInt(), err + } + defer resp.Body.Close() + + // parse response + responseBody := &ResponseBody{} + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return math.ZeroInt(), err + } + err = json.Unmarshal(respBody, responseBody) + if err != nil { + return math.ZeroInt(), err + } + if responseBody.Error { + return math.ZeroInt(), fmt.Errorf(responseBody.Message) + } + + // execute multiplier + fee, ok := math.NewIntFromString(responseBody.TotalFee) + if !ok { + return math.ZeroInt(), fmt.Errorf("failed to convert total fee to int") + } + m, ok := math.NewIntFromString(multiplier) + if !ok { + return fee, nil + } + return fee.Mul(m), nil +} diff --git a/relayer/relayer/relayer.go b/relayer/relayer/relayer.go index e04e43a..d6383e8 100644 --- a/relayer/relayer/relayer.go +++ b/relayer/relayer/relayer.go @@ -2,17 +2,18 @@ package relayer import ( "context" - "strings" + "fmt" "time" "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" - ibctransfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" "github.com/ojo-network/ojo-evm/relayer/config" "github.com/ojo-network/ojo-evm/relayer/relayer/client" gmptypes "github.com/ojo-network/ojo/x/gmp/types" pfsync "github.com/ojo-network/price-feeder/pkg/sync" "github.com/rs/zerolog" + + math "cosmossdk.io/math" ) const ( @@ -198,7 +199,7 @@ func (r *Relayer) tick(ctx context.Context) error { } r.updateMemory(ctx, batch) } else { - r.logger.Info().Msg("no relays necessary") + r.logger.Debug().Msg("no relays necessary") } return nil @@ -229,15 +230,26 @@ func deviated(existingPrice float64, newestPrice float64, threshold float64) (fl // relay sends a relay message to the Ojo node. func (r Relayer) relay(denoms []string) error { r.logger.Info().Strs("denoms", denoms).Msg("submitting relay tx") - // normalize the coin denom - coins, err := sdk.ParseCoinNormalized(r.cfg.Relayer.Tokens) + + gasFee, err := client.EstimateGasFee( + r.cfg.Relayer.Destination, + r.cfg.Relayer.Contract, + r.cfg.AxelarGas.Default, + r.cfg.AxelarGas.Multiplier, + ) if err != nil { - return err + r.logger.Err(err).Str("default", r.cfg.AxelarGas.Default).Msg("unable to estimate gas fee") + defaultGasFee, ok := math.NewIntFromString(r.cfg.AxelarGas.Default) + if !ok { + return fmt.Errorf("unable to convert default gas fee to int") + } + gasFee = defaultGasFee } - if !strings.HasPrefix(coins.Denom, "ibc/") { - denomTrace := ibctransfertypes.ParseDenomTrace(coins.Denom) - coins.Denom = denomTrace.IBCDenom() + coins := sdk.Coin{ + Denom: r.cfg.AxelarGas.Denom, + Amount: gasFee, } + r.logger.Info().Strs("gas_fee", []string{coins.String()}).Msg("estimated gas fee") msg := gmptypes.NewMsgRelay( r.cfg.Account.Address, diff --git a/scripts/createPriceFeed.ts b/scripts/createPriceFeed.ts deleted file mode 100644 index 41b0ac5..0000000 --- a/scripts/createPriceFeed.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Wallet, ethers } from "ethers"; -import CloneFactory from '../artifacts/contracts/pricefeed/CloneFactory.sol/CloneFactory.json'; -import testnet_chains from '../testnet_chains.json'; -import mainnet_chains from '../mainnet_chains.json'; - -async function main() { - const cloneFactoryAddress = process.env.CLONE_FACTORY_CONTRACT_ADDRESS as string; - const priceFeedDecimals = 18; - const priceFeedDescription = "ETH"; - - const privateKey = process.env.PRIVATE_KEY; - - if (!privateKey) { - throw new Error('Invalid private key. Make sure the PRIVATE_KEY environment variable is set.'); - } - - const mainnet = process.env.MAINNET as string - let evmChains = testnet_chains.map((chain) => ({ ...chain })); - if (mainnet === "TRUE") { - evmChains = mainnet_chains.map((chain) => ({ ...chain })); - } - - for (const chain of evmChains) { - const provider = new ethers.JsonRpcProvider(chain.rpc) - const wallet = new Wallet(privateKey, provider); - const balance = await provider.getBalance(wallet.address) - console.log(`${chain.name} wallet balance: ${ethers.formatEther(balance.toString())} ${chain.tokenSymbol}`); - - const cloneFactoryContract = new ethers.Contract(cloneFactoryAddress, CloneFactory.abi, wallet) - await cloneFactoryContract.createPriceFeed(priceFeedDecimals, priceFeedDescription) - } -} - -main().catch((error) => { - console.error(error); - process.exitCode = 1; -}); diff --git a/scripts/createPriceFeeds.ts b/scripts/createPriceFeeds.ts new file mode 100644 index 0000000..8630f18 --- /dev/null +++ b/scripts/createPriceFeeds.ts @@ -0,0 +1,50 @@ +import { Wallet, ethers } from "ethers"; +import CloneFactory from '../artifacts/contracts/pricefeed/CloneFactory.sol/CloneFactory.json'; +import testnet_chains from '../testnet_chains.json'; +import mainnet_chains from '../mainnet_chains.json'; + +async function main() { + const evmChains = JSON.parse(process.env.EVM_CHAINS!); + const priceFeedDecimals = process.env.PRICE_FEED_DECIMALS as any; + const priceFeedDescriptions = JSON.parse(process.env.PRICE_FEED_DESCRIPTIONS!); + + const privateKey = process.env.PRIVATE_KEY; + + if (!privateKey) { + throw new Error('Invalid private key. Make sure the PRIVATE_KEY environment variable is set.'); + } + + const mainnet = process.env.MAINNET as string + let chains = testnet_chains.map((chain) => ({ ...chain })); + if (mainnet === "TRUE") { + chains = mainnet_chains.map((chain) => ({ ...chain })); + } + + for (const chain of chains) { + if (evmChains.includes(chain.name)) { + const provider = new ethers.JsonRpcProvider(chain.rpc) + const wallet = new Wallet(privateKey, provider); + const balance = await provider.getBalance(wallet.address) + console.log(`${chain.name} wallet balance: ${ethers.formatEther(balance.toString())} ${chain.tokenSymbol}`); + + const cloneFactoryContract = new ethers.Contract(chain.cloneFactory, CloneFactory.abi, wallet) + for (const priceFeedDescription of priceFeedDescriptions) { + console.log(`Deploying ${priceFeedDescription} price feed on ${chain.name}`); + try { + const tx = await cloneFactoryContract.createPriceFeed(priceFeedDecimals, priceFeedDescription); + console.log(`Transaction sent: ${tx.hash}`); + + const receipt = await tx.wait(); + console.log(`Transaction mined: ${receipt.transactionHash}`); + } catch (error) { + console.error(`Failed to deploy ${priceFeedDescription} on ${chain.name}:`, error); + } + } + } + } +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/scripts/deployCloneFactory.ts b/scripts/deployCloneFactory.ts index e5f73a3..6cc51c8 100644 --- a/scripts/deployCloneFactory.ts +++ b/scripts/deployCloneFactory.ts @@ -4,7 +4,7 @@ import testnet_chains from '../testnet_chains.json'; import mainnet_chains from '../mainnet_chains.json'; async function main () { - const priceFeedImplementation = process.env.PRICE_FEED_IMPLEMENTATION_CONTRACT_ADDRESS; + const evmChains = JSON.parse(process.env.EVM_CHAINS!); const privateKey = process.env.PRIVATE_KEY; @@ -13,20 +13,22 @@ async function main () { } const mainnet = process.env.MAINNET as string - let evmChains = testnet_chains.map((chain) => ({ ...chain })); + let chains = testnet_chains.map((chain) => ({ ...chain })); if (mainnet === "TRUE") { - evmChains = mainnet_chains.map((chain) => ({ ...chain })); + chains = mainnet_chains.map((chain) => ({ ...chain })); } - for (const chain of evmChains) { - const provider = new ethers.JsonRpcProvider(chain.rpc) - const wallet = new Wallet(privateKey, provider); - const balance = await provider.getBalance(wallet.address) - console.log(`${chain.name} wallet balance: ${ethers.formatEther(balance.toString())} ${chain.tokenSymbol}`); + for (const chain of chains) { + if (evmChains.includes(chain.name)) { + const provider = new ethers.JsonRpcProvider(chain.rpc) + const wallet = new Wallet(privateKey, provider); + const balance = await provider.getBalance(wallet.address) + console.log(`${chain.name} wallet balance: ${ethers.formatEther(balance.toString())} ${chain.tokenSymbol}`); - const priceFeedFactory = new ethers.ContractFactory(CloneFactory.abi, CloneFactory.bytecode, wallet) - const priceFeed = await priceFeedFactory.deploy(priceFeedImplementation) - console.log(`${chain.name}, address: ${await priceFeed.getAddress()}`); + const priceFeedFactory = new ethers.ContractFactory(CloneFactory.abi, CloneFactory.bytecode, wallet) + const priceFeed = await priceFeedFactory.deploy(chain.priceFeedImplementation) + console.log(`${chain.name}, address: ${await priceFeed.getAddress()}`); + } } } diff --git a/scripts/deployMockOjo.ts b/scripts/deployMockOjo.ts index 5db1f62..af622c9 100644 --- a/scripts/deployMockOjo.ts +++ b/scripts/deployMockOjo.ts @@ -5,8 +5,7 @@ import testnet_chains from '../testnet_chains.json'; import mainnet_chains from '../mainnet_chains.json'; async function main() { - const ojoContractddress = process.env.OJO_CONTRACT_ADDRESS; - const create2DeployerAddress = process.env.CREATE2_DEPLOYER_ADDRESS as string; + const evmChains = JSON.parse(process.env.EVM_CHAINS!); const privateKey = process.env.PRIVATE_KEY; @@ -15,32 +14,34 @@ async function main() { } const mainnet = process.env.MAINNET as string - let evmChains = testnet_chains.map((chain) => ({ ...chain })); + let chains = testnet_chains.map((chain) => ({ ...chain })); if (mainnet === "TRUE") { - evmChains = mainnet_chains.map((chain) => ({ ...chain })); + chains = mainnet_chains.map((chain) => ({ ...chain })); } - for (const chain of evmChains) { - const provider = new ethers.JsonRpcProvider(chain.rpc) - const wallet = new Wallet(privateKey, provider); - const balance = await provider.getBalance(wallet.address) - console.log(`${chain.name} wallet balance: ${ethers.formatEther(balance.toString())} ${chain.tokenSymbol}`); + for (const chain of chains) { + if (evmChains.includes(chain.name)) { + const provider = new ethers.JsonRpcProvider(chain.rpc) + const wallet = new Wallet(privateKey, provider); + const balance = await provider.getBalance(wallet.address) + console.log(`${chain.name} wallet balance: ${ethers.formatEther(balance.toString())} ${chain.tokenSymbol}`); - const deployerContract = new ethers.Contract(create2DeployerAddress, Create2Deployer.abi, wallet); + const deployerContract = new ethers.Contract(chain.create2Deployer, Create2Deployer.abi, wallet); - const salt = ethers.zeroPadValue(ethers.toUtf8Bytes("MockOjo"), 32); + const salt = ethers.zeroPadValue(ethers.toUtf8Bytes("MockOjo"), 32); - const creationCode = ethers.solidityPacked( + const creationCode = ethers.solidityPacked( ["bytes", "bytes"], - [MockOjo.bytecode, ethers.AbiCoder.defaultAbiCoder().encode(["address"], [ojoContractddress])] - ); + [MockOjo.bytecode, ethers.AbiCoder.defaultAbiCoder().encode(["address"], [chain.ojoContract])] + ); - // perform static call to log address of the contract - const deployedAddress = await deployerContract.deploy.staticCallResult(creationCode, salt); - console.log(`${chain.name}, address: ${deployedAddress}`); + // perform static call to log address of the contract + const deployedAddress = await deployerContract.deploy.staticCallResult(creationCode, salt); + console.log(`${chain.name}, address: ${deployedAddress}`); - // perform actual deploy tx - await deployerContract.deploy(creationCode, salt); + // perform actual deploy tx + await deployerContract.deploy(creationCode, salt); + } } } diff --git a/scripts/deployOjo.ts b/scripts/deployOjo.ts index 27af4c6..bf0e059 100644 --- a/scripts/deployOjo.ts +++ b/scripts/deployOjo.ts @@ -3,13 +3,11 @@ import Ojo from '../artifacts/contracts/Ojo.sol/Ojo.json'; import OjoProxy from '../artifacts/contracts/OjoProxy.sol/OjoProxy.json'; import testnet_chains from '../testnet_chains.json'; import mainnet_chains from '../mainnet_chains.json'; -import { deployCreate2InitUpgradable } from './utils/upgradable'; +import { deployCreate2InitUpgradable, estimateDeploymentGas } from './utils/upgradable'; async function main() { - const axelarGasReceiverAddress = process.env.AXELAR_GAS_RECEIVER_ADDRESS; - const create2DeployerAddress = process.env.CREATE2_DEPLOYER_ADDRESS; + const evmChains = JSON.parse(process.env.EVM_CHAINS!); const ojoChain = process.env.OJO_CHAIN; - const ojoAddress = process.env.OJO_ADDRESS; const resolveWindow = Number(process.env.RESOLVE_WINDOW); const assetLimit = Number(process.env.ASSET_LIMIT); @@ -20,30 +18,44 @@ async function main() { } const mainnet = process.env.MAINNET as string - let evmChains = testnet_chains.map((chain) => ({ ...chain })); + let chains = testnet_chains.map((chain) => ({ ...chain })); if (mainnet === "TRUE") { - evmChains = mainnet_chains.map((chain) => ({ ...chain })); + chains = mainnet_chains.map((chain) => ({ ...chain })); } - const key = Number(process.env.PRIVATE_KEY); - - for (const chain of evmChains) { - const provider = new ethers.JsonRpcProvider(chain.rpc) - const wallet = new Wallet(privateKey, provider); - const balance = await provider.getBalance(wallet.address) - console.log(`${chain.name} wallet balance: ${ethers.formatEther(balance.toString())} ${chain.tokenSymbol}`); - - const deployedContract = await deployCreate2InitUpgradable( - create2DeployerAddress, - wallet, - Ojo, - OjoProxy, - [chain.gateway, axelarGasReceiverAddress], - ethers.AbiCoder.defaultAbiCoder().encode(["string", "string", "uint256", "uint16"],[ojoChain, ojoAddress, resolveWindow, assetLimit]), - key - ); - - console.log(`${chain.name}, address: ${await deployedContract.getAddress()}`); + const key = Number(process.env.CONTRACT_KEY); + + for (const chain of chains) { + if (evmChains.includes(chain.name)) { + const provider = new ethers.JsonRpcProvider(chain.rpc) + const wallet = new Wallet(privateKey, provider); + const balance = await provider.getBalance(wallet.address) + console.log(`${chain.name} wallet balance: ${ethers.formatEther(balance.toString())} ${chain.tokenSymbol}`); + + const feeData = await provider.getFeeData(); + const gasPrice = feeData.maxFeePerGas ?? feeData.gasPrice; + if (!gasPrice) { + throw new Error('Unable to retrieve gas price'); + } + const estimatedGas = await estimateDeploymentGas(wallet, Ojo, [chain.gateway, chain.gasReceiver]); + const deploymentCost = estimatedGas * gasPrice; + + if (balance < deploymentCost) { + throw new Error(`Insufficient funds in wallet for deploying on ${chain.name}, deploymentCost: ${ethers.formatEther(deploymentCost.toString())} ${chain.tokenSymbol}`); + } + + const deployedContract = await deployCreate2InitUpgradable( + chain.create2Deployer, + wallet, + Ojo, + OjoProxy, + [chain.gateway, chain.gasReceiver], + ethers.AbiCoder.defaultAbiCoder().encode(["string", "string", "uint256", "uint16"],[ojoChain, chain.ojoContract, resolveWindow, assetLimit]), + key + ); + + console.log(`${chain.name}, address: ${await deployedContract.getAddress()}`); + } } } diff --git a/scripts/deployPriceFeedImplementation.ts b/scripts/deployPriceFeedImplementation.ts index 491f4a1..0258769 100644 --- a/scripts/deployPriceFeedImplementation.ts +++ b/scripts/deployPriceFeedImplementation.ts @@ -4,7 +4,7 @@ import testnet_chains from '../testnet_chains.json'; import mainnet_chains from '../mainnet_chains.json'; async function main() { - const ojoAddress = process.env.OJO_CONTRACT_ADDRESS; + const evmChains = JSON.parse(process.env.EVM_CHAINS!); const privateKey = process.env.PRIVATE_KEY; @@ -13,20 +13,22 @@ async function main() { } const mainnet = process.env.MAINNET as string - let evmChains = testnet_chains.map((chain) => ({ ...chain })); + let chains = testnet_chains.map((chain) => ({ ...chain })); if (mainnet === "TRUE") { - evmChains = mainnet_chains.map((chain) => ({ ...chain })); + chains = mainnet_chains.map((chain) => ({ ...chain })); } - for (const chain of evmChains) { - const provider = new ethers.JsonRpcProvider(chain.rpc) - const wallet = new Wallet(privateKey, provider); - const balance = await provider.getBalance(wallet.address) - console.log(`${chain.name} wallet balance: ${ethers.formatEther(balance.toString())} ${chain.tokenSymbol}`); + for (const chain of chains) { + if (evmChains.includes(chain.name)) { + const provider = new ethers.JsonRpcProvider(chain.rpc) + const wallet = new Wallet(privateKey, provider); + const balance = await provider.getBalance(wallet.address) + console.log(`${chain.name} wallet balance: ${ethers.formatEther(balance.toString())} ${chain.tokenSymbol}`); - const priceFeedFactory = new ethers.ContractFactory(PriceFeed.abi, PriceFeed.bytecode, wallet) - const priceFeed = await priceFeedFactory.deploy(ojoAddress) - console.log(`${chain.name}, address: ${await priceFeed.getAddress()}`); + const priceFeedFactory = new ethers.ContractFactory(PriceFeed.abi, PriceFeed.bytecode, wallet) + const priceFeed = await priceFeedFactory.deploy(chain.ojoContract) + console.log(`${chain.name}, address: ${await priceFeed.getAddress()}`); + } } } diff --git a/scripts/upgradeOjo.ts b/scripts/upgradeOjo.ts index b6696b1..9902d07 100644 --- a/scripts/upgradeOjo.ts +++ b/scripts/upgradeOjo.ts @@ -5,8 +5,7 @@ import mainnet_chains from '../mainnet_chains.json'; import { upgradeUpgradable } from './utils/upgradable'; async function main() { - const ojoProxyAddress = process.env.OJO_CONTRACT_ADDRESS; - const axelarGasReceiverAddress = process.env.AXELAR_GAS_RECEIVER_ADDRESS; + const evmChains = JSON.parse(process.env.EVM_CHAINS!); const privateKey = process.env.PRIVATE_KEY; @@ -15,25 +14,27 @@ async function main() { } const mainnet = process.env.MAINNET as string - let evmChains = testnet_chains.map((chain) => ({ ...chain })); + let chains = testnet_chains.map((chain) => ({ ...chain })); if (mainnet === "TRUE") { - evmChains = mainnet_chains.map((chain) => ({ ...chain })); + chains = mainnet_chains.map((chain) => ({ ...chain })); } - for (const chain of evmChains) { - const provider = new ethers.JsonRpcProvider(chain.rpc) - const wallet = new Wallet(privateKey, provider); - const balance = await provider.getBalance(wallet.address) - console.log(`${chain.name} wallet balance: ${ethers.formatEther(balance.toString())} ${chain.tokenSymbol}`); - - const upgradeTx = await upgradeUpgradable( - ojoProxyAddress, - wallet, - Ojo, - [chain.gateway, axelarGasReceiverAddress] - ); - - console.log(`${chain.name}, upgrade tx: ${upgradeTx.hash}`); + for (const chain of chains) { + if (evmChains.includes(chain.name)) { + const provider = new ethers.JsonRpcProvider(chain.rpc) + const wallet = new Wallet(privateKey, provider); + const balance = await provider.getBalance(wallet.address) + console.log(`${chain.name} wallet balance: ${ethers.formatEther(balance.toString())} ${chain.tokenSymbol}`); + + const upgradeTx = await upgradeUpgradable( + chain.ojoContract, + wallet, + Ojo, + [chain.gateway, chain.gasReceiver] + ); + + console.log(`${chain.name}, upgrade tx: ${upgradeTx.hash}`); + } } } diff --git a/scripts/utils/upgradable.ts b/scripts/utils/upgradable.ts index a405f95..e5dc063 100644 --- a/scripts/utils/upgradable.ts +++ b/scripts/utils/upgradable.ts @@ -27,6 +27,17 @@ export async function deployCreate2InitUpgradable( return new ethers.Contract(await proxy.getAddress(), implementationJson.abi, wallet); } +export async function estimateDeploymentGas( + wallet: ethers.Wallet, + implementationJson: { abi: ethers.Interface | ethers.InterfaceAbi; bytecode: ethers.BytesLike | { object: string; }; }, + implementationConstructorArgs: any +) { + const implementationFactory = new ethers.ContractFactory(implementationJson.abi, implementationJson.bytecode, wallet); + const deployTransaction = await implementationFactory.getDeployTransaction(...implementationConstructorArgs); + const estimatedGas = await wallet.estimateGas(deployTransaction); + return estimatedGas; +} + export async function upgradeUpgradable( proxyAddress: any, wallet: ethers.Wallet, diff --git a/testnet_chains.json b/testnet_chains.json index 8e3df94..4e2b088 100644 --- a/testnet_chains.json +++ b/testnet_chains.json @@ -4,132 +4,227 @@ "chainId": 5, "gateway": "0xe432150cce91c13a887f7D836923d5597adD8E31", "rpc": "https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161", - "tokenSymbol": "ETH" + "tokenSymbol": "ETH", + "gasReceiver": "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6", + "ojoContract": "0x5BB3E85f91D08fe92a3D123EE35050b763D6E6A7", + "create2Deployer": "0x98b2920d53612483f91f12ed7754e51b4a77919e", + "priceFeedImplementation": "", + "cloneFactory": "" }, { "name": "Ethereum Sepolia", "chainId": 11155111, "rpc": "https://1rpc.io/sepolia", "gateway": "0xe432150cce91c13a887f7D836923d5597adD8E31", - "tokenSymbol": "ETH" + "tokenSymbol": "ETH", + "gasReceiver": "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6", + "ojoContract": "0x5BB3E85f91D08fe92a3D123EE35050b763D6E6A7", + "create2Deployer": "0x98b2920d53612483f91f12ed7754e51b4a77919e", + "priceFeedImplementation": "", + "cloneFactory": "" }, { "name": "BNB Chain", "chainId": 97, "gateway": "0x4D147dCb984e6affEEC47e44293DA442580A3Ec0", "rpc": "https://data-seed-prebsc-1-s1.bnbchain.org:8545", - "tokenSymbol": "tBNB" + "tokenSymbol": "tBNB", + "gasReceiver": "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6", + "ojoContract": "0x5BB3E85f91D08fe92a3D123EE35050b763D6E6A7", + "create2Deployer": "0x98b2920d53612483f91f12ed7754e51b4a77919e", + "priceFeedImplementation": "", + "cloneFactory": "" }, { "name": "Polygon Mumbai", "chainId": 80001, "gateway": "0xBF62ef1486468a6bd26Dd669C06db43dEd5B849B", "rpc": "https://polygon-mumbai.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161", - "tokenSymbol": "MATIC" + "tokenSymbol": "MATIC", + "gasReceiver": "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6", + "ojoContract": "0x5BB3E85f91D08fe92a3D123EE35050b763D6E6A7", + "create2Deployer": "0x98b2920d53612483f91f12ed7754e51b4a77919e", + "priceFeedImplementation": "", + "cloneFactory": "" }, { "name": "Polygon zkEVM", "chainId": 1442, "gateway": "0x999117D44220F33e0441fbAb2A5aDB8FF485c54D", "rpc": "https://rpc.public.zkevm-test.net", - "tokenSymbol": "ETH" + "tokenSymbol": "ETH", + "gasReceiver": "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6", + "ojoContract": "0x5BB3E85f91D08fe92a3D123EE35050b763D6E6A7", + "create2Deployer": "0x98b2920d53612483f91f12ed7754e51b4a77919e", + "priceFeedImplementation": "", + "cloneFactory": "" }, { "name": "Avalanche Fuji C-Chain", "chainId": 43113, "rpc": "https://api.avax-test.network/ext/bc/C/rpc", "gateway": "0xC249632c2D40b9001FE907806902f63038B737Ab", - "tokenSymbol": "AVAX" + "tokenSymbol": "AVAX", + "gasReceiver": "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6", + "ojoContract": "0x5BB3E85f91D08fe92a3D123EE35050b763D6E6A7", + "create2Deployer": "0x98b2920d53612483f91f12ed7754e51b4a77919e", + "priceFeedImplementation": "", + "cloneFactory": "" }, { "name": "Fantom", "chainId": 4002, "gateway": "0x97837985Ec0494E7b9C71f5D3f9250188477ae14", "rpc": "https://rpc.testnet.fantom.network", - "tokenSymbol": "FTM" + "tokenSymbol": "FTM", + "gasReceiver": "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6", + "ojoContract": "0x5BB3E85f91D08fe92a3D123EE35050b763D6E6A7", + "create2Deployer": "0x98b2920d53612483f91f12ed7754e51b4a77919e", + "priceFeedImplementation": "", + "cloneFactory": "" }, { "name": "Moonbase", "chainId": 1287, "rpc": "https://rpc.api.moonbase.moonbeam.network", "gateway": "0x5769D84DD62a6fD969856c75c7D321b84d455929", - "tokenSymbol": "DEV" + "tokenSymbol": "DEV", + "gasReceiver": "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6", + "ojoContract": "0x5BB3E85f91D08fe92a3D123EE35050b763D6E6A7", + "create2Deployer": "0x98b2920d53612483f91f12ed7754e51b4a77919e", + "priceFeedImplementation": "", + "cloneFactory": "" }, { "name": "Arbitrum Goerli", "chainId": 421613, "rpc": "https://arbitrum-goerli.publicnode.com", "gateway": "0xe432150cce91c13a887f7D836923d5597adD8E31", - "tokenSymbol": "AGOR" + "tokenSymbol": "AGOR", + "gasReceiver": "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6", + "ojoContract": "0x5BB3E85f91D08fe92a3D123EE35050b763D6E6A7", + "create2Deployer": "0x98b2920d53612483f91f12ed7754e51b4a77919e", + "priceFeedImplementation": "", + "cloneFactory": "" }, { "name": "Arbitrum Sepolia", "chainId": 421614, "gateway": "0xe432150cce91c13a887f7D836923d5597adD8E31", "rpc": "https://arbitrum-sepolia.blockpi.network/v1/rpc/public", - "tokenSymbol": "ETH" + "tokenSymbol": "ETH", + "gasReceiver": "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6", + "ojoContract": "0x5BB3E85f91D08fe92a3D123EE35050b763D6E6A7", + "create2Deployer": "0x98b2920d53612483f91f12ed7754e51b4a77919e", + "priceFeedImplementation": "0x3DB6DF9EDfDcfE97D574Aa6f106C767051561Be2", + "cloneFactory": "0xab2c7cc090A45836fae04501e0454413ECA96611" }, { "name": "Optimism Goerli", "chainId": 420, "gateway": "0xe432150cce91c13a887f7D836923d5597adD8E31", "rpc": "https://optimism-goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161", - "tokenSymbol": "ETH" + "tokenSymbol": "ETH", + "gasReceiver": "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6", + "ojoContract": "0x5BB3E85f91D08fe92a3D123EE35050b763D6E6A7", + "create2Deployer": "0x98b2920d53612483f91f12ed7754e51b4a77919e", + "priceFeedImplementation": "", + "cloneFactory": "" }, { "name": "Optimism Sepolia", "chainId": 11155420, "gateway": "0xe432150cce91c13a887f7D836923d5597adD8E31", "rpc": "https://sepolia.optimism.io", - "tokenSymbol": "ETH" + "tokenSymbol": "ETH", + "gasReceiver": "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6", + "ojoContract": "0x5BB3E85f91D08fe92a3D123EE35050b763D6E6A7", + "create2Deployer": "0x98b2920d53612483f91f12ed7754e51b4a77919e", + "priceFeedImplementation": "0x48B10B538B7E5af4CbFd93B1C4d36668e8F6F644", + "cloneFactory": "0xe9c4145FCeDdc19bc9B788C5d16cF08AD70d3850" }, { "name": "Base Goerli", "chainId": 84531, "gateway": "0xe432150cce91c13a887f7D836923d5597adD8E31", "rpc": "https://goerli.base.org", - "tokenSymbol": "ETH" + "tokenSymbol": "ETH", + "gasReceiver": "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6", + "ojoContract": "0x5BB3E85f91D08fe92a3D123EE35050b763D6E6A7", + "create2Deployer": "0x98b2920d53612483f91f12ed7754e51b4a77919e", + "priceFeedImplementation": "", + "cloneFactory": "" }, { "name": "Base Sepolia", "chainId": 84532, "gateway": "0xe432150cce91c13a887f7D836923d5597adD8E31", "rpc": "https://base-sepolia-rpc.publicnode.com", - "tokenSymbol": "ETH" + "tokenSymbol": "ETH", + "gasReceiver": "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6", + "ojoContract": "0x5BB3E85f91D08fe92a3D123EE35050b763D6E6A7", + "create2Deployer": "0x98b2920d53612483f91f12ed7754e51b4a77919e", + "priceFeedImplementation": "0x09d43904C8ABd470df1B793df68904A9714558CF", + "cloneFactory": "0x02Ed15B70D4dE1209c3Dd5a75195CB3f3dDB8B07" }, { "name": "Mantle", "chainId": 5001, "rpc": "https://rpc.testnet.mantle.xyz", "gateway": "0xe432150cce91c13a887f7D836923d5597adD8E31", - "tokenSymbol": "MNT" + "tokenSymbol": "MNT", + "gasReceiver": "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6", + "ojoContract": "0x5BB3E85f91D08fe92a3D123EE35050b763D6E6A7", + "create2Deployer": "0x98b2920d53612483f91f12ed7754e51b4a77919e", + "priceFeedImplementation": "", + "cloneFactory": "" }, { "name": "Alfajores", "chainId": 44787, "gateway": "0xe432150cce91c13a887f7D836923d5597adD8E31", "rpc": "https://alfajores-forno.celo-testnet.org", - "tokenSymbol": "CELO" + "tokenSymbol": "CELO", + "gasReceiver": "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6", + "ojoContract": "0x5BB3E85f91D08fe92a3D123EE35050b763D6E6A7", + "create2Deployer": "0x98b2920d53612483f91f12ed7754e51b4a77919e", + "priceFeedImplementation": "", + "cloneFactory": "" }, { "name": "Kava", "chainId": 2221, "rpc": "https://evm.testnet.kava.io", "gateway": "0xC8D18F85cB0Cee5C95eC29c69DeaF6cea972349c", - "tokenSymbol": "KAVA" + "tokenSymbol": "KAVA", + "gasReceiver": "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6", + "ojoContract": "0x5BB3E85f91D08fe92a3D123EE35050b763D6E6A7", + "create2Deployer": "0x98b2920d53612483f91f12ed7754e51b4a77919e", + "priceFeedImplementation": "", + "cloneFactory": "" }, { "name": "Filecoin Calibration", "chainId": 314159, "gateway": "0x999117D44220F33e0441fbAb2A5aDB8FF485c54D", "rpc": "https://rpc.ankr.com/filecoin_testnet", - "tokenSymbol": "FIL" + "tokenSymbol": "FIL", + "gasReceiver": "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6", + "ojoContract": "0x5BB3E85f91D08fe92a3D123EE35050b763D6E6A7", + "create2Deployer": "0x98b2920d53612483f91f12ed7754e51b4a77919e", + "priceFeedImplementation": "", + "cloneFactory": "" }, { "name": "Linea Goerli", "chainId": 59140, "gateway": "0xe432150cce91c13a887f7D836923d5597adD8E31", "rpc": "https://rpc.goerli.linea.build", - "tokenSymbol": "LineaETH" + "tokenSymbol": "LineaETH", + "gasReceiver": "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6", + "ojoContract": "0x5BB3E85f91D08fe92a3D123EE35050b763D6E6A7", + "create2Deployer": "0x98b2920d53612483f91f12ed7754e51b4a77919e", + "priceFeedImplementation": "", + "cloneFactory": "" } ]