@layerzerolabs/hyperliquid-composer
Version:
LayerZero Labs reference EVM OmniChain Fungible Token (OFT) implementation for Hyperliquid
112 lines (97 loc) • 4.29 kB
text/typescript
import axios, { AxiosInstance } from 'axios'
import inquirer from 'inquirer'
import { Logger, createModuleInteractionLogger } from '@layerzerolabs/io-devtools'
import { getTimestampMs, signL1Action } from '../signer'
import type { IHyperliquidSigner } from './interfaces'
import { HYPERLIQUID_URLS, ValueType } from '../types'
export class HyperliquidClient {
private readonly client: AxiosInstance
private readonly baseUrl: string
private readonly isTestnet: boolean
private readonly logger: Logger
private readonly skipPrompt: boolean
constructor(isTestnet: boolean, logLevel: string, skipPrompt?: boolean) {
this.baseUrl = isTestnet ? HYPERLIQUID_URLS.TESTNET : HYPERLIQUID_URLS.MAINNET
this.isTestnet = isTestnet
this.skipPrompt = skipPrompt ?? false
this.logger = createModuleInteractionLogger('hyperliquid-client', logLevel)
this.client = axios.create({
baseURL: this.baseUrl,
headers: {
'Content-Type': 'application/json',
},
})
}
async submitHyperliquidAction(endpoint: string, signer: IHyperliquidSigner | null, action: ValueType) {
let payload = action
if (endpoint === '/exchange') {
if (!signer) {
this.logger.error('Signer is null')
process.exit(1)
}
const nonce = getTimestampMs()
const signature = await signL1Action({
signer,
action,
nonce,
isTestnet: this.isTestnet,
vaultAddress: null,
})
payload = {
action,
nonce,
signature,
vaultAddress: null,
}
const signerAddress = await signer.getAddress()
this.logger.info(
`Transaction is sent from ${signerAddress} on network hypercore-${this.isTestnet ? 'testnet' : 'mainnet'}`
)
if (this.skipPrompt) {
this.logger.info('CI mode enabled - skipping confirmation prompt')
this.logger.debug(`Transaction payload: ${JSON.stringify(payload, null, 2)}`)
} else {
const { executeTx } = await inquirer.prompt([
{
type: 'confirm',
name: 'executeTx',
message: `Do you want to execute the transaction ${JSON.stringify(payload, null, 2)}?`,
default: false,
},
])
if (!executeTx) {
this.logger.info('Transaction bundle cancelled - quitting.')
process.exit(1)
}
}
}
this.logger.debug(`Sending payload to full url: ${this.baseUrl}${endpoint}`)
this.logger.debug(`payload: \n ${JSON.stringify(payload, null, 2)}`)
try {
const response = await this.client.post(endpoint, payload)
const data = response.data
if (data.status === 'err') {
this.logger.error(`Hyperliquid API error: ${data['response']}`)
} else if (endpoint === '/exchange') {
this.logger.info(`Hyperliquid API response: ${JSON.stringify(data['response'], null, 2)}`)
}
return data
} catch (error) {
// These are HTTP errors that we catch
this.logger.error(error)
this.logger.error(`Sending payload to full url: ${this.baseUrl}${endpoint}`)
this.logger.error(`payload: ${JSON.stringify(payload, null, 2)}`)
if (axios.isAxiosError(error) && error.response) {
this.logger.error('API Error:', {
status: error.response.status,
statusText: error.response.statusText,
data: error.response.data,
url: `${this.baseUrl}${endpoint}`,
payload: JSON.stringify(payload),
})
throw new Error(`Hyperliquid API error: ${JSON.stringify(error.response.data)}`)
}
throw new Error(`Failed to submit Hyperliquid action: ${error}`)
}
}
}