diff --git a/package.json b/package.json index 6248020..77d6cd7 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "vite": "^5.0.3" }, "dependencies": { - "@openzeppelin/defender-sdk": "^2.4.0", + "@openzeppelin/defender-sdk": "^2.5.0", "@remixproject/plugin": "^0.3.38", "@remixproject/plugin-api": "^0.3.38", "@remixproject/plugin-iframe": "^0.3.38", @@ -37,4 +37,4 @@ "superchain-registry": "github:ethereum-optimism/superchain-registry" }, "version": "0.0.1" -} +} \ No newline at end of file diff --git a/src/lib/defender/index.ts b/src/lib/defender/index.ts index 653b897..0034dfc 100644 --- a/src/lib/defender/index.ts +++ b/src/lib/defender/index.ts @@ -22,12 +22,19 @@ export const listApiKeyPermissions = async (credentials: Credentials) => { export const listNetworks = async (credentials: Credentials) => { const client = getClient(credentials); - const [networks, forkedNetworks, privateNetworks] = await Promise.all([ - client.network.listSupportedNetworks(), + + const [nativeNetworks, forkedNetworks, privateNetworks] = (await Promise.all([ + client.network.listSupportedNetworks({ networkType: ["deploy"], includeDefinition: true }), client.network.listForkedNetworks(), client.network.listPrivateNetworks(), - ]); - return [...networks, ...forkedNetworks, ...privateNetworks]; + ])) + + return [ + nativeNetworks + .map((network) => ({...network, networkType: "native"} as const)), + forkedNetworks, + privateNetworks + ].flat() } export const listApprovalProcesses = async (credentials: Credentials) => { diff --git a/src/lib/ethereum/index.ts b/src/lib/ethereum/index.ts index ebc346b..694652a 100644 --- a/src/lib/ethereum/index.ts +++ b/src/lib/ethereum/index.ts @@ -1,5 +1,5 @@ import { type Eip1193Provider, BrowserProvider, ContractFactory } from 'ethers'; -import { chainIds, type TenantNetworkResponse } from "$lib/models/network"; +import { type NetworkResponse, type TenantNetworkResponse } from "$lib/models/network"; import type { DeployContractResult } from '$lib/models/ethereum'; import { log } from '$lib/remix/logger'; @@ -13,21 +13,20 @@ function getEthereum(): Eip1193Provider { * * @param network target network to switch to. */ -export async function switchToNetwork(network: string | TenantNetworkResponse) { - const chainId = typeof network === 'string' ? chainIds[network] : network.chainId; - if (!chainId) throw new Error(`Invalid network: ${network}`); +export async function switchToNetwork(network: NetworkResponse | TenantNetworkResponse) { + if (!network.chainId) throw new Error(`Invalid network: ${network}`); const ethereum = getEthereum(); // ignore if user is already connected to target network. const current = await ethereum.request({ method: 'eth_chainId' }); - if (parseInt(current, 16) === chainId) return; + if (parseInt(current, 16) === network.chainId) return; log("[Defender Deploy] Switching network..."); await ethereum.request({ method: 'wallet_switchEthereumChain', - params: [{ chainId: `0x${chainId.toString(16)}` }], + params: [{ chainId: `0x${network.chainId.toString(16)}` }], }); }; diff --git a/src/lib/models/approval-process.ts b/src/lib/models/approval-process.ts index e280701..38269bc 100644 --- a/src/lib/models/approval-process.ts +++ b/src/lib/models/approval-process.ts @@ -1,4 +1,3 @@ -import type { TenantNetworkResponse } from "./network"; import type { GlobalState } from "./ui"; /** @@ -9,7 +8,7 @@ export type ApprovalProcess = { createdAt: string; name: string; component?: ComponentType; - network?: string | TenantNetworkResponse; + network?: string; via?: string; viaType?: 'EOA' | 'Contract' | 'Multisig' | 'Gnosis Safe' | 'Safe' | 'Gnosis Multisig' | 'Relayer' | 'Relayer Group' | 'Unknown' | 'Relayer Group' | 'Timelock Controller' | 'ERC20' | 'Governor' | 'Fireblocks'; multisigSender?: string; diff --git a/src/lib/models/auth.ts b/src/lib/models/auth.ts index 75f6d23..7b1569f 100644 --- a/src/lib/models/auth.ts +++ b/src/lib/models/auth.ts @@ -1,6 +1,6 @@ import type { ApprovalProcess } from "./approval-process"; import type { BlockExplorerKey } from "./block-explorer-key"; -import type { TenantNetworkResponse } from "./network"; +import type { NetworkResponse, TenantNetworkResponse } from "./network"; import type { Relayer } from "./relayer"; /** @@ -13,7 +13,7 @@ export type ApiKeyCapability = 'create-admin-proposals' | 'manage-relayers' | 'm export type AuthenticationResponse = { credentials: Credentials, permissions: ApiKeyCapability[], - networks: (string | TenantNetworkResponse)[], + networks: (NetworkResponse | TenantNetworkResponse)[], approvalProcesses: ApprovalProcess[], relayers: Relayer[], blockExplorerKeys: BlockExplorerKey[] diff --git a/src/lib/models/network.ts b/src/lib/models/network.ts index 005d962..3f4237e 100644 --- a/src/lib/models/network.ts +++ b/src/lib/models/network.ts @@ -11,150 +11,32 @@ export interface TenantNetworkResponse { isProduction?: boolean; } -export const productionNetworks = new Set([ - 'arbitrum', - 'arbitrum-nova', - 'aurora', - 'avalanche', - 'base', - 'bsc', - 'celo', - 'fantom', - 'fuse', - 'hedera', - 'japan', - 'linea', - 'mainnet', - 'mantle', - 'matic', - 'matic-zkevm', - 'meld', - 'moonbeam', - 'moonriver', - 'optimism', - 'scroll', - 'unichain', - 'xdai', - 'zksync' -]); +export interface NetworkResponse { + name: string; + displayName?: string; + symbol: string; + chainId: number; + networkType: 'native'; + isProduction: boolean; +} + -export function isProductionNetwork(network: string | TenantNetworkResponse): boolean { - if (typeof network === 'string') { - return productionNetworks.has(network); - } +export function isProductionNetwork(network: NetworkResponse | TenantNetworkResponse): boolean { return network.isProduction ?? false; } -export function getNetworkLiteral(network: string | TenantNetworkResponse): string { - return typeof network === 'string' ? network : network.name; +export function getNetworkLiteral(network: NetworkResponse | TenantNetworkResponse): string { + return network.name; } -export const chainIds: { [key in string]: number } = { - 'alfajores': 44787, - 'amoy': 80002, - 'arbitrum': 42161, - 'arbitrum-nova': 42170, - 'arbitrum-sepolia': 421614, - 'aurora': 1313161554, - 'auroratest': 1313161555, - 'avalanche': 43114, - 'base': 8453, - 'base-sepolia': 84532, - 'bsc': 56, - 'bsctest': 97, - 'celo': 42220, - 'fantom': 250, - 'fantomtest': 4002, - 'fuji': 43113, - 'fuse': 122, - 'hedera': 295, - 'hederatest': 296, - 'holesky': 17000, - 'japan': 81, - 'japan-testnet': 10081, - 'linea': 59144, - 'linea-goerli': 59140, - 'linea-sepolia': 59141, - 'mainnet': 1, - 'mantle': 5000, - 'mantle-sepolia': 5003, - 'matic': 137, - 'matic-zkevm': 1101, - 'matic-zkevm-testnet': 1442, - 'meld': 333000333, - 'meld-kanazawa': 222000222, - 'moonbase': 1287, - 'moonbeam': 1284, - 'moonriver': 1285, - 'mumbai': 80001, - 'optimism': 10, - 'optimism-sepolia': 11155420, - 'scroll': 534352, - 'scroll-sepolia': 534351, - 'sepolia': 11155111, - 'sokol': 77, - 'unichain': 130, - 'unichain-sepolia': 1301, - 'x-dfk-avax-chain': 53935, - 'x-dfk-avax-chain-test': 335, - 'x-security-alliance': 888, - 'xdai': 100, - 'zksync': 324, - 'zksync-sepolia': 300, -}; - +export function isTenantNetwork(network: NetworkResponse | TenantNetworkResponse): network is TenantNetworkResponse { + return "tenantNetworkId" in network +} +export function isNativeNetwork(network: NetworkResponse | TenantNetworkResponse): network is NetworkResponse { + return network.networkType === "native" +} -export const chainDisplayNames: { [key in string]: string } = { - 'alfajores': 'Celo Alfajores', - 'amoy': 'Polygon Amoy', - 'arbitrum': 'Arbitrum', - 'arbitrum-nova': 'Arbitrum Nova', - 'arbitrum-sepolia': 'Arbitrum Sepolia', - 'aurora': 'Aurora', - 'auroratest': 'Aurora Testnet', - 'avalanche': 'Avalanche', - 'base': 'Base', - 'base-sepolia': 'Base Sepolia', - 'bsc': 'Binance Smart Chain', - 'bsctest': 'Binance Smart Chain Testnet', - 'celo': 'Celo', - 'fantom': 'Fantom', - 'fantomtest': 'Fantom Testnet', - 'fuji': 'Avalanche Fuji', - 'fuse': 'Fuse', - 'hedera': 'Hedera', - 'hederatest': 'Hedera Testnet', - 'holesky': 'Holesky', - 'japan': 'Japan Open Chain', - 'japan-testnet': 'Japan Open Chain Testnet', - 'linea': 'Linea', - 'linea-sepolia': 'Linea Sepolia', - 'mainnet': 'Ethereum Mainnet', - 'mantle': 'Mantle', - 'mantle-sepolia': 'Mantle Sepolia', - 'matic': 'Polygon', - 'matic-zkevm': 'Polygon ZK-EVM', - 'matic-zkevm-testnet': 'Polygon ZK-EVM Testnet', - 'matic-cardona-zkevm-testnet': 'Polygon Cardona ZK-EVM Testnet', - 'meld': 'Meld', - 'meld-kanazawa': 'Meld Kanazawa', - 'moonbase': 'Moonbase', - 'moonbeam': 'Moonbeam', - 'moonriver': 'Moonriver', - 'mumbai': 'Polygon Mumbai', - 'optimism': 'OP Mainnet', - 'optimism-sepolia': 'OP Sepolia', - 'scroll': 'Scroll', - 'scroll-sepolia': 'Scroll Sepolia', - 'sepolia': 'Sepolia', - 'sokol': 'Sokol', - 'unichain': 'Unichain', - 'unichain-sepolia': 'Unichain Sepolia', - 'x-dfk-avax-chain': 'Avalanche X-DFK', - 'x-dfk-avax-chain-test': 'Avalanche X-DFK Testnet', - 'x-security-alliance': 'X Security Alliance', - 'xdai': 'Gnosis Chain', - 'zksync': 'zkSync', - 'zksync-sepolia': 'zkSync Sepolia', -}; \ No newline at end of file +export function getNetworkDisplayName(network: NetworkResponse | TenantNetworkResponse) { + return (isNativeNetwork(network) ? network.displayName : network.name) || network.name +} diff --git a/src/lib/models/ui.ts b/src/lib/models/ui.ts index c815ab7..e769b51 100644 --- a/src/lib/models/ui.ts +++ b/src/lib/models/ui.ts @@ -2,7 +2,7 @@ import type { CompilationFileSources, CompilationResult, SourceWithTarget } from import type { Relayer } from "./relayer"; import type { ApiKeyCapability, Credentials } from "./auth"; import type { ApprovalProcess, ApprovalProcessToCreate } from "./approval-process"; -import type { TenantNetworkResponse } from "./network"; +import type { NetworkResponse, TenantNetworkResponse } from "./network"; import type { BlockExplorerKey } from "./block-explorer-key"; export type DropdownItem = { @@ -20,7 +20,7 @@ export type GlobalState = { successMessage?: string; credentials: Credentials; permissions: ApiKeyCapability[]; - networks: (string | TenantNetworkResponse)[]; + networks: (NetworkResponse | TenantNetworkResponse)[]; approvalProcesses: ApprovalProcess[]; relayers: Relayer[]; blockExplorerKeys: BlockExplorerKey[] @@ -33,7 +33,7 @@ export type GlobalState = { groupNetworksBy?: 'superchain', } form: { - network?: string | TenantNetworkResponse; + network?: NetworkResponse | TenantNetworkResponse; approvalProcessSelected?: ApprovalProcess; approvalProcessToCreate?: ApprovalProcessToCreate; approvalType?: SelectedApprovalProcessType; diff --git a/src/lib/remix/components/ApprovalProcess.svelte b/src/lib/remix/components/ApprovalProcess.svelte index 7edc6fd..8502e85 100644 --- a/src/lib/remix/components/ApprovalProcess.svelte +++ b/src/lib/remix/components/ApprovalProcess.svelte @@ -48,7 +48,7 @@ // Relayer selection logic const relayerByNetwork = (relayer: Relayer) => - relayer.network === globalState.form.network; + relayer.network === globalState.form.network?.name; const relayerToDropdownItem = (relayer: Relayer) => ({ label: `${relayer.name} (${abbreviateAddress(relayer.address)})`, value: relayer, diff --git a/src/lib/remix/components/Deploy.svelte b/src/lib/remix/components/Deploy.svelte index 452e6a7..1fbb116 100644 --- a/src/lib/remix/components/Deploy.svelte +++ b/src/lib/remix/components/Deploy.svelte @@ -2,18 +2,17 @@ import { onDestroy } from "svelte"; // Lib - import { updateSelectedApprovalProcessWithExisting, clearErrorBanner, globalState, setDeploymentCompleted, setErrorBanner } from "$lib/state/state.svelte"; + import { updateSelectedApprovalProcessWithExisting, clearErrorBanner, globalState, setDeploymentCompleted, setErrorBanner, findDeploymentEnvironment } from "$lib/state/state.svelte"; import { log, logError, logSuccess, logWarning } from "$lib/remix/logger"; import { deployContract, switchToNetwork } from "$lib/ethereum"; import { API } from "$lib/api"; // Utils import { attempt } from "$lib/utils/attempt"; - import { isDeploymentEnvironment, isSameNetwork } from "$lib/utils/helpers"; import { getContractFeatures, getConstructorInputs, encodeConstructorArgs, getContractBytecode, createArtifactPayload } from "$lib/utils/contracts"; // Models - import { getNetworkLiteral, isProductionNetwork, type TenantNetworkResponse } from "$lib/models/network"; + import { getNetworkLiteral, isProductionNetwork } from "$lib/models/network"; import type { ApprovalProcess, CreateApprovalProcessRequest} from "$lib/models/approval-process"; import type { DeployContractRequest, UpdateDeploymentRequest } from "$lib/models/deploy"; import type { APIResponse, HTMLInputElementEvent } from "$lib/models/ui"; @@ -95,16 +94,7 @@ ); } }); - - function findDeploymentEnvironment(via?: string, network?: string) { - if (!via || !network) return undefined; - return globalState.approvalProcesses.find((ap) => - ap.network && - isDeploymentEnvironment(ap) && - isSameNetwork(ap.network, network) && - ap.via?.toLocaleLowerCase() === via.toLocaleLowerCase() - ); - } + async function getOrCreateApprovalProcess(): Promise { const ap = globalState.form.approvalProcessToCreate; diff --git a/src/lib/remix/components/Network.svelte b/src/lib/remix/components/Network.svelte index 845eb78..8f3692d 100644 --- a/src/lib/remix/components/Network.svelte +++ b/src/lib/remix/components/Network.svelte @@ -1,7 +1,8 @@