@openocean.finance/widget
Version:
Openocean Widget for cross-chain bridging and swapping. It will drive your multi-chain strategy and attract new users from everywhere.
227 lines (209 loc) • 7.81 kB
text/typescript
interface OpenOceanToken {
address: string
symbol: string
decimals: number
name: string
icon?: string
usd?: string
chainId?: number
}
export class OpenOceanService {
// private static readonly API_V1_URL = 'https://ethapi.openocean.finance/v1'
//private static readonly API_V2_URL = 'https://ethapi.openocean.finance/v2'
private static readonly API_V3_URL = 'https://open-api.openocean.finance/v3'
private static readonly API_V4_URL = 'https://open-api.openocean.finance/v4'
// Chain ID to OpenOcean chain name mapping
private static readonly CHAIN_ID_MAP: Record<string | number, string> = {
1151111081099710: 'solana', // Solana mainnet
}
// Get OpenOcean supported chain name
private static getChainName(chainId: string | number): string {
return this.CHAIN_ID_MAP[chainId] || chainId.toString()
}
// Get API URL based on chain ID
private static getApiUrl(chainId: string | number): string {
// If chainId exists in CHAIN_ID_MAP, use V1 API
const chainName = this.getChainName(chainId)
return Object.keys(this.CHAIN_ID_MAP).includes(chainId.toString())
? `${this.API_V4_URL}/${chainName}`
: `${this.API_V4_URL}/${chainId}`
}
private static getSolanaAddress(chain: string, tokenAddress: string): string {
if (chain === '1151111081099710' && tokenAddress === 'So11111111111111111111111111111111111111112') {
return '11111111111111111111111111111111'
} else if (chain === '1151111081099710' && tokenAddress === '11111111111111111111111111111111') {
return 'So11111111111111111111111111111111111111112'
}
return tokenAddress
}
static async getQuote(params: {
chain: string
inTokenAddress: string
inTokenSymbol: string
outTokenAddress: string
outTokenSymbol: string
amount: string
slippage?: string
gasPrice?: string
disabledDexIds?: string
enabledDexIds?: string
referrer?: string
}) {
const apiUrl = this.getApiUrl(params.chain)
const slippage = (Number(params.slippage || 0.01)).toString();
const queryParams = new URLSearchParams({
quoteType: 'quote',
inTokenSymbol: params.inTokenSymbol,
inTokenAddress: params.inTokenAddress,
outTokenSymbol: params.outTokenSymbol,
outTokenAddress: params.outTokenAddress,
amountDecimals: params.amount,
slippage,
gasPriceDecimals: params.gasPrice || '',
disabledDexIds: params.disabledDexIds || '',
enabledDexIds: params.enabledDexIds || '',
referrer: params.referrer || '0x3487ef9f9b36547e43268b8f0e2349a226c70b53',
})
const response = await fetch(`${apiUrl}/quote?${queryParams.toString()}`)
return response.json()
}
static async getSwapQuote(params: {
chain: string
inTokenAddress: string
inTokenSymbol: string
outTokenAddress: string
outTokenSymbol: string
amount: string
account: string
slippage?: string
gasPrice?: string
disabledDexIds?: string
enabledDexIds?: string
referrer?: string,
referrerFee?: string,
referrerFeeShare?: string
}) {
const apiUrl = this.getApiUrl(params.chain)
const slippage = (Number(params.slippage || 0.01)).toString();
const referrer: any = {
referrer: params.referrer || '0x3487ef9f9b36547e43268b8f0e2349a226c70b53',
}
if (params.referrerFee) {
referrer.referrerFee = Number(params.referrerFee);
// referrer.referrerFeeShare = params.referrerFeeShare || '1500';
}
const queryParams = new URLSearchParams({
quoteType: 'swap',
inTokenSymbol: params.inTokenSymbol,
inTokenAddress: this.getSolanaAddress(params.chain, params.inTokenAddress),
outTokenSymbol: params.outTokenSymbol,
outTokenAddress: this.getSolanaAddress(params.chain, params.outTokenAddress),
amountDecimals: params.amount,
account: params.account,
slippage,
gasPriceDecimals: params.gasPrice || '',
disabledDexIds: params.disabledDexIds || '',
enabledDexIds: params.enabledDexIds || '',
...referrer,
})
// const isV1Api = Object.keys(this.CHAIN_ID_MAP).includes(params.chain.toString())
// const swapEndpoint = isV1Api ? 'swap-quote' : 'swap'
const response = await fetch(`${apiUrl}/swap?${queryParams.toString()}`)
return response.json()
}
static async getTokenList(chain: string) {
const chainName = this.getChainName(chain)
const response = await fetch(`${this.API_V4_URL}/${chainName}/tokenList`)
const data = await response.json()
if (data.code !== 200) {
throw new Error('Failed to fetch token list')
}
return data.data.map((token: OpenOceanToken) => {
let address = token.address
// Convert Solana native token address
if (chain === '1151111081099710' && address === 'So11111111111111111111111111111111111111112') {
address = '11111111111111111111111111111111'
}
// Convert Base chain native token address
if (address === '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE') {
address = '0x0000000000000000000000000000000000000000'
}
return {
address,
symbol: token.symbol,
decimals: token.decimals,
name: token.name,
logoURI: token.icon,
priceUSD: token.usd,
chainId: Number(chain),
amount: 0n,
featured: false,
popular: false
}
})
}
static async getDexList(chain: string) {
const apiUrl = this.getApiUrl(chain)
const response = await fetch(`${apiUrl}/dexList`)
return response.json()
}
static async getGasPrice(chain: string) {
if (!chain || chain === '1151111081099710') {
return {
data: {
gasPrice: '1000000000000000000',
},
}
}
// const apiUrl = this.getApiUrl(chain)
const response = await fetch(`${this.API_V4_URL}/${chain}/gasPrice`)
const { data } = await response.json()
return data
}
static async getTokenInfo(chain: string, tokenAddress: string) {
const chainName = this.getChainName(chain)
// Check if the address is valid
if (!tokenAddress || (!/^0x[a-fA-F0-9]{40}$/.test(tokenAddress) && !/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(tokenAddress))) {
throw new Error('Invalid token address')
}
const response = await fetch(`${this.API_V4_URL}/${chainName}/getTokenInfo?tokenAddress=${tokenAddress}`)
const data = await response.json()
if (!data || !data.address || !data.symbol || !data.decimals) {
throw new Error('Failed to fetch token info')
}
return {
address: data.address,
symbol: data.symbol,
decimals: data.decimals,
name: data.name,
logoURI: data.icon,
priceUSD: data.usd,
chainId: Number(chain),
amount: 0n,
featured: false,
popular: false
}
}
/**
* Get prices for specified tokens
* @param chain chain name, e.g. 'arbitrum'
* @param tokenAddresses array of token addresses
* @returns Promise<Record<string, string>> returns an object where key is token address and value is price
*/
static async getTokensPrice(chain: string, tokenAddresses: string[]): Promise<Record<string, string>> {
const chainName = this.getChainName(chain)
const tokenAddressesStr = tokenAddresses.join(',')
const response = await fetch(`${this.API_V3_URL}/${chainName}/designated_tokenList?tokens=${tokenAddressesStr}`)
const data = await response.json()
if (data.code !== 200) {
throw new Error('Failed to fetch token prices')
}
const prices = data.data.reduce((acc: Record<string, string>, token: OpenOceanToken) => {
if (token.address && token.usd) {
acc[token.address.toLowerCase()] = token.usd
}
return acc
}, {})
return prices
}
}