@layerzerolabs/hyperliquid-composer
Version:
LayerZero Labs reference EVM OmniChain Fungible Token (OFT) implementation for Hyperliquid
196 lines (159 loc) • 6.99 kB
text/typescript
import fs from 'fs'
import path from 'path'
import { importDefault, Logger } from '@layerzerolabs/io-devtools'
import { EndpointId } from '@layerzerolabs/lz-definitions'
import type { OAppOmniGraphHardhat } from '@layerzerolabs/toolbox-hardhat'
import type { HardhatUserConfig, NetworkUserConfig } from 'hardhat/types'
import type { CoreSpotDeployment, TxData } from '@/types'
function getFullPath(index: string, isTestnet: boolean): string {
return path.join(process.cwd(), 'deployments', `hypercore-${isTestnet ? 'testnet' : 'mainnet'}`, `${index}.json`)
}
export function getCoreSpotDeployment(index: string | number, isTestnet: boolean, logger?: Logger): CoreSpotDeployment {
const fullPath = getFullPath(index.toString(), isTestnet)
const nativeSpot = fs.readFileSync(fullPath, 'utf8')
if (!nativeSpot) {
const errMsg = `Native spot ${index} not found - make sure the native spot for the token ${index} is found at ${fullPath}`
logger?.error(errMsg)
throw new Error(errMsg)
}
return JSON.parse(nativeSpot) as CoreSpotDeployment
}
export function writeCoreSpotDeployment(
index: string | number,
isTestnet: boolean,
coreSpotDeployment: CoreSpotDeployment,
logger?: Logger
) {
const fullPath = getFullPath(index.toString(), isTestnet)
const dir = path.dirname(fullPath)
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true })
}
fs.writeFileSync(fullPath, JSON.stringify(coreSpotDeployment, null, 2))
logger?.info(`Wrote deployment ${fullPath}`)
}
export function writeUpdatedCoreSpotDeployment(
index: string | number,
isTestnet: boolean,
tokenFullName: string,
tokenAddress: string,
txData: TxData,
logger?: Logger
) {
const fullPath = getFullPath(index.toString(), isTestnet)
const spot = getCoreSpotDeployment(index, isTestnet, logger)
spot.coreSpot.evmContract = {
address: tokenAddress,
evm_extra_wei_decimals: txData.weiDiff ?? 0,
}
spot.coreSpot.fullName = tokenFullName
spot.txData.txHash = txData.txHash
spot.txData.nonce = txData.nonce
spot.txData.from = txData.from
spot.txData.connected = txData.connected
spot.txData.weiDiff = txData.weiDiff
fs.writeFileSync(fullPath, JSON.stringify(spot, null, 2))
logger?.info(`Updated core spot ${index}`)
}
export function writeNativeSpotConnected(
index: string | number,
isTestnet: boolean,
connected: boolean,
weiDiff: number,
logger?: Logger
) {
const fullPath = getFullPath(index.toString(), isTestnet)
const spot = getCoreSpotDeployment(index, isTestnet, logger)
spot.txData.connected = connected
spot.txData.weiDiff = weiDiff
fs.writeFileSync(fullPath, JSON.stringify(spot, null, 2))
logger?.info(`Updated core spot ${index}`)
}
export function updateFreezePrivilegeStatus(
index: string | number,
isTestnet: boolean,
enabled: boolean,
logger?: Logger
) {
const fullPath = getFullPath(index.toString(), isTestnet)
const spot = getCoreSpotDeployment(index, isTestnet, logger)
spot.coreSpot.freezePrivilegeEnabled = enabled
fs.writeFileSync(fullPath, JSON.stringify(spot, null, 2))
logger?.info(`Updated freeze privilege status for core spot ${index}: ${enabled ? 'enabled' : 'revoked'}`)
}
export function updateQuoteTokenStatus(index: string | number, isTestnet: boolean, enabled: boolean, logger?: Logger) {
const fullPath = getFullPath(index.toString(), isTestnet)
const spot = getCoreSpotDeployment(index, isTestnet, logger)
spot.coreSpot.quoteAssetEnabled = enabled
fs.writeFileSync(fullPath, JSON.stringify(spot, null, 2))
logger?.info(`Updated quote token status for core spot ${index}: ${enabled ? 'enabled' : 'disabled'}`)
}
export function updateUserFreezeStatus(
index: string | number,
isTestnet: boolean,
userAddress: string,
frozen: boolean,
logger?: Logger
) {
const fullPath = getFullPath(index.toString(), isTestnet)
const spot = getCoreSpotDeployment(index, isTestnet, logger)
// Ensure blacklistUsers array exists
if (!spot.userGenesis.blacklistUsers) {
spot.userGenesis.blacklistUsers = []
}
// Normalize user address - hyperliquid indexes based on lowercase addresses
const normalizedAddress = userAddress.toLowerCase()
// Remove existing entry for this user
spot.userGenesis.blacklistUsers = spot.userGenesis.blacklistUsers.filter((addr) => addr !== normalizedAddress)
// Add user to blacklist if frozen
if (frozen) {
spot.userGenesis.blacklistUsers.push(normalizedAddress)
}
fs.writeFileSync(fullPath, JSON.stringify(spot, null, 2))
logger?.info(
`Updated freeze status for user ${normalizedAddress} in core spot ${index}: ${frozen ? 'frozen' : 'unfrozen'}`
)
}
export async function getHyperEVMOAppDeployment(
oapp_config: string,
network: string,
logger?: Logger
): Promise<{ contractName: string; deployment: string }> {
if (oapp_config == undefined) {
logger?.info(`oapp-config not supplied. prompting user for inputs`)
throw new Error(`oapp-config not found for ${network}`)
}
const targetEid =
network === 'testnet'
? EndpointId.HYPERLIQUID_V2_TESTNET.valueOf()
: EndpointId.HYPERLIQUID_V2_MAINNET.valueOf()
const lzConfigPath = path.join(process.cwd(), oapp_config)
logger?.info(`LZ config path: ${lzConfigPath}`)
const lzConfig = await importDefault(lzConfigPath)
const contracts = (lzConfig as OAppOmniGraphHardhat).contracts
const contractName = contracts.find((contract) => contract.contract.eid === targetEid)?.contract.contractName
if (!contractName) {
throw new Error(`HyperEVM deployment not found for ${targetEid}`)
}
const hardhatConfigPath = path.join(process.cwd(), 'hardhat.config.ts')
logger?.info(`Hardhat config path: ${hardhatConfigPath}`)
const hardhatConfig = await importDefault(hardhatConfigPath)
const networks = (hardhatConfig as HardhatUserConfig).networks
const networkKey = Object.keys(networks as NetworkUserConfig).find((key) => networks?.[key]?.eid === targetEid)
if (!networkKey) {
throw new Error(`Network not found for ${targetEid}`)
}
const deploymentFilePath = path.join(process.cwd(), 'deployments', networkKey, `${contractName}.json`)
logger?.info(`HyperEVM-${network} deployment file: ${deploymentFilePath}`)
const deploymentFile = JSON.parse(fs.readFileSync(deploymentFilePath, 'utf8'))
return {
contractName: contractName,
deployment: deploymentFile,
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export async function getERC20abi(): Promise<any> {
const erc20Path = path.join(__dirname, '..', 'src', 'types', 'ERC20.json')
const erc20Abi = JSON.parse(fs.readFileSync(erc20Path, 'utf8'))
return erc20Abi['abi']
}