Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sdk/src/gateway/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ export class GatewayApiClient extends BaseClient {
* });
* ```
*/
// TODO: remove constructor, set the config from `getQuote`
constructor(chainId: number, options?: { rpcUrl?: string }) {
super();
switch (chainId) {
Expand Down
112 changes: 74 additions & 38 deletions sdk/src/gateway/layerzero.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
ContractFunctionExecutionError,
encodeAbiParameters,
encodePacked,
extractChain,

Check warning on line 9 in sdk/src/gateway/layerzero.ts

View workflow job for this annotation

GitHub Actions / Tests

'extractChain' is defined but never used
Hex,
InsufficientFundsError,
isAddress,
Expand All @@ -18,7 +19,21 @@
toHex,
zeroAddress,
} from 'viem';
import { bob, bobSepolia, mainnet } from 'viem/chains';
import {
bob,
mainnet,
base,

Check warning on line 25 in sdk/src/gateway/layerzero.ts

View workflow job for this annotation

GitHub Actions / Tests

'base' is defined but never used
berachain,
bsc,
unichain,

Check warning on line 28 in sdk/src/gateway/layerzero.ts

View workflow job for this annotation

GitHub Actions / Tests

'unichain' is defined but never used
avalanche,

Check warning on line 29 in sdk/src/gateway/layerzero.ts

View workflow job for this annotation

GitHub Actions / Tests

'avalanche' is defined but never used
sonic,

Check warning on line 30 in sdk/src/gateway/layerzero.ts

View workflow job for this annotation

GitHub Actions / Tests

'sonic' is defined but never used
soneium,
telos,

Check warning on line 32 in sdk/src/gateway/layerzero.ts

View workflow job for this annotation

GitHub Actions / Tests

'telos' is defined but never used
swellchain,

Check warning on line 33 in sdk/src/gateway/layerzero.ts

View workflow job for this annotation

GitHub Actions / Tests

'swellchain' is defined but never used
optimism,
sei,
} from 'viem/chains';
import { layerZeroOftAbi, quoterV2Abi } from './abi';
import { AllWalletClientParams, GatewayApiClient } from './client';
import { getTokenAddress, getTokenSlots } from './tokens';
Expand All @@ -43,6 +58,7 @@
toHexScriptPubKey,
viemClient,
} from './utils';
import { supportedChainsMapping } from './utils/common';

bitcoin.initEccLib(ecc);

Expand Down Expand Up @@ -70,9 +86,28 @@
return this.getChainDeploymentsPromiseCache;
}

// Resolve viem and layerzero chain names
resolveViemChainName(chainKey: string): string {
switch (chainKey.toLowerCase()) {
case bsc.name.toLowerCase():
return 'bsc';
case optimism.name.toLowerCase():
return 'optimism';
case sei.name.toLowerCase():
return 'sei';
case soneium.name.toLowerCase():
return 'soneium';
case berachain.name.toLowerCase():
return 'bera';
default:
return chainKey;
}
}

async getEidForChain(chainKey: string) {
const data = await this.getChainDeployments();
const eid = data[chainKey]?.deployments?.find((item) => item.version === 2)?.eid;
const resolvedChainName = this.resolveViemChainName(chainKey);
const eid = data[resolvedChainName]?.deployments?.find((item) => item.version === 2)?.eid;
return eid !== undefined && eid !== null ? Number(eid) : null;
}

Expand Down Expand Up @@ -105,31 +140,38 @@

async getOftAddressForChain(chainKey: string): Promise<string | null> {
const deployments = await this.getWbtcDeployments();
return deployments[chainKey]?.address || null;
const resolvedChainName = this.resolveViemChainName(chainKey);
return deployments[resolvedChainName]?.address || null;
}

async getSupportedChainsInfo(): Promise<Array<LayerZeroChainInfo>> {
const chains = await this.getChainDeployments();
const chainLookup = Object.fromEntries(
Object.entries(chains).map(([, chainData]) => [
chainData.chainKey,
{
eid: chainData.deployments?.find((item) => item.version === 2)?.eid,
nativeChainId: chainData.chainDetails?.nativeChainId,
},
])
);

const deployments = await this.getWbtcDeployments();
return Object.entries(deployments).map(([chainKey, deployment]) => {
const chainInfo = chainLookup[chainKey];
return {
name: chainKey,
eid: chainInfo?.eid,
oftAddress: deployment.address,
nativeChainId: chainInfo?.nativeChainId,
};

const supportedLayerZeroChainKeys = new Set<string>();
const layerZeroKeyToViemName: Record<string, string> = {};

Object.values(supportedChainsMapping).forEach((chainConfig) => {
const viemChainName = chainConfig.name.toLowerCase();
const layerZeroChainKey = this.resolveViemChainName(viemChainName);
supportedLayerZeroChainKeys.add(layerZeroChainKey);
layerZeroKeyToViemName[layerZeroChainKey] = viemChainName;
});

// Filter layerzero chains that are in supportedChainsMapping
return Object.entries(chains)
.filter(([layerZeroChainKey]) => supportedLayerZeroChainKeys.has(layerZeroChainKey))
.map(([layerZeroChainKey, chainData]) => {
const viemChainName = layerZeroKeyToViemName[layerZeroChainKey];
const deployment = deployments[layerZeroChainKey];

return {
name: viemChainName,
eid: chainData.deployments?.find((item) => item.version === 2)?.eid,
oftAddress: deployment.address,
nativeChainId: chainData.chainDetails?.nativeChainId,
};
});
}

async getChainId(eid: number): Promise<number | null> {
Expand All @@ -155,18 +197,14 @@
}
}

// Viem chain names are used to identify chains
function resolveChainId(chain: number): string {
return getChainConfig(chain).name.toLowerCase();
}

function resolveChainName(chain: number | string): string {
if (typeof chain === 'number') {
switch (chain) {
case bob.id:
return bob.name.toLowerCase();
case bobSepolia.id:
return bobSepolia.name.toLowerCase();
case mainnet.id:
return 'mainnet';
default:
throw new Error(`Unsupported chain ID: ${chain}`);
}
return resolveChainId(chain);
}
return chain.toLowerCase();
}
Expand All @@ -175,11 +213,9 @@
export class LayerZeroGatewayClient extends GatewayApiClient {
private l0Client: LayerZeroClient;

constructor(chainId: number, options?: { rpcUrl?: string }) {
if (chainId !== bob.id) {
throw new Error('LayerZeroGatewayClient only supports BOB mainnet');
}
super(chainId, options);
// TODO: remove constructor, set the config from `getQuote`
constructor(options?: { rpcUrl?: string }) {
super(bob.id, options);
this.l0Client = new LayerZeroClient();
}

Expand Down Expand Up @@ -218,8 +254,8 @@
}

async getQuote(params: GetQuoteParams<LayerZeroQuoteParamsExt>): Promise<ExecuteQuoteParams> {
const fromChain = resolveChainName(params.fromChain);
const toChain = resolveChainName(params.toChain);
const fromChain = typeof params.fromChain === 'number' ? resolveChainId(params.fromChain) : params.fromChain;
const toChain = typeof params.toChain === 'number' ? resolveChainId(params.toChain) : params.toChain;

if (fromChain === 'bitcoin' && toChain === bob.name.toLowerCase()) {
// Handle bitcoin -> bob: use normal flow
Expand Down
2 changes: 1 addition & 1 deletion sdk/src/gateway/types/layerzero.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export type LayerZeroSendParam = {
};

export type LayerZeroChainInfo = {
name: string;
name: string; // viem chain name
eid?: string;
oftAddress: string;
nativeChainId?: number;
Expand Down
11 changes: 8 additions & 3 deletions sdk/src/gateway/utils/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export function formatBtc(btc: bigint) {
return formatUnits(btc, 8);
}

const supportedChainsMapping = {
export const supportedChainsMapping = {
bob,
ethereum: mainnet,
sonic,
Expand All @@ -161,9 +161,14 @@ const chainIdToChainConfigMapping = Object.values(supportedChainsMapping).reduce
);

function getChainIdByName(chainName: string) {
const chain = supportedChainsMapping[chainName.toLowerCase()];
const chain = Object.values(supportedChainsMapping).find(
(chain) => chain.name.toLowerCase() === chainName.toLowerCase()
);

if (!chain) {
throw new Error(`Chain id for "${chainName}" not found. Allowed values ${Object.keys(supportedChainsMapping)}`);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The key is not always the same as the chain name, so this was misleading

throw new Error(
`Chain id for "${chainName}" not found. Allowed values ${Object.values(supportedChainsMapping).map((chain) => chain.name)}`
);
}
return chain.id;
}
Expand Down
63 changes: 52 additions & 11 deletions sdk/test/layerzero.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
import { assert, describe, expect, it, vi } from 'vitest';
import { LayerZeroClient, LayerZeroGatewayClient } from '../src/gateway/layerzero';
import { createPublicClient, createWalletClient, http, PublicClient, Transport, zeroAddress } from 'viem';
import { base, bob, optimism } from 'viem/chains';
import {
bob,
mainnet,
base,
berachain,
bsc,
unichain,
avalanche,
sonic,
soneium,
telos,
swellchain,
optimism,
sei,
} from 'viem/chains';
import { BitcoinSigner, LayerZeroMessageWallet } from '../src/gateway/types';
import * as btc from '@scure/btc-signer';
import { base64 } from '@scure/base';
Expand All @@ -10,6 +24,7 @@ import { HDKey } from '@scure/bip32';
import { Hex } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { getCrossChainStatus } from '../src/gateway/utils/layerzero';
import { supportedChainsMapping } from '../src/gateway/utils/common';

describe('LayerZero Tests', () => {
it.skip('should get chains', async () => {
Expand Down Expand Up @@ -71,7 +86,7 @@ describe('LayerZero Tests', () => {
}, 120000);

it.skip('should get an onramp quote and execute it', async () => {
const client = new LayerZeroGatewayClient(bob.id);
const client = new LayerZeroGatewayClient();

const quote = await client.getQuote({
fromChain: 'bitcoin',
Expand Down Expand Up @@ -111,7 +126,7 @@ describe('LayerZero Tests', () => {
}, 120000);

it.skip('should get an offramp quote and execute it', async () => {
const client = new LayerZeroGatewayClient(bob.id);
const client = new LayerZeroGatewayClient();
const layerZeroClient = new LayerZeroClient();

const quote = await client.getQuote({
Expand Down Expand Up @@ -154,28 +169,52 @@ describe('LayerZero Tests', () => {
console.log(txHash);
}, 120000);

it.skip('should be able to get a layerzero send quote for every chain', async () => {
const client = new LayerZeroGatewayClient();

const l0ChainsInfo = await client.getSupportedChainsInfo();
const toChain = l0ChainsInfo.find((chain) => chain.name === 'bob')!;

for (const fromChain of l0ChainsInfo) {
if (fromChain.name !== toChain.name) {
const quote = await client.getQuote({
fromChain: fromChain.nativeChainId as number,
toChain: toChain.nativeChainId as number,
fromToken: fromChain.oftAddress as string,
toToken: toChain.oftAddress as string,
fromUserAddress: '0xEf7Ff7Fb24797656DF41616e807AB4016AE9dCD5',
toUserAddress: '0xEf7Ff7Fb24797656DF41616e807AB4016AE9dCD5',
amount: 100,
});
console.log(fromChain.name, toChain.name);
}
}
}, 120000);

it.skip('should get a layerzero send quote and execute it', async () => {
const client = new LayerZeroGatewayClient(base.id);
const client = new LayerZeroGatewayClient();

const quote = await client.getQuote({
fromChain: 'base',
fromToken: (await client.getSupportedChainsInfo()).find((chain) => chain.name === 'base')
fromChain: 'BNB Smart Chain',
fromToken: (await client.getSupportedChainsInfo()).find((chain) => chain.name === 'BNB Smart Chain')
?.oftAddress as string,
toChain: 'optimism',
toToken: (await client.getSupportedChainsInfo()).find((chain) => chain.name === 'optimism')
toChain: 'base',
toToken: (await client.getSupportedChainsInfo()).find((chain) => chain.name === 'base')
?.oftAddress as string,
fromUserAddress: '0xEf7Ff7Fb24797656DF41616e807AB4016AE9dCD5',
toUserAddress: '0xEf7Ff7Fb24797656DF41616e807AB4016AE9dCD5',
amount: 100,
});

console.log('quote', quote);

const publicClient = createPublicClient({
chain: base,
chain: bsc,
transport: http(),
});

const walletClient = createWalletClient({
chain: base,
chain: bsc,
transport: http(),
account: privateKeyToAccount(process.env.PRIVATE_KEY as Hex),
});
Expand All @@ -185,6 +224,8 @@ describe('LayerZero Tests', () => {
walletClient,
publicClient: publicClient as PublicClient<Transport>,
});

console.log(txHash);
}, 120000);

it('should get chain id for eid', async () => {
Expand All @@ -196,7 +237,7 @@ describe('LayerZero Tests', () => {
}, 120000);

it('should return onramp, offramp and cross-chain orders', async () => {
const gatewaySDK = new LayerZeroGatewayClient(bob.id);
const gatewaySDK = new LayerZeroGatewayClient();

const getOrdersSpy = vi.spyOn(gatewaySDK, 'getOrders').mockImplementationOnce(() => Promise.resolve([]));
const getCrossChainOrdersSpy = vi
Expand Down
Loading