Skip to content
Merged
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
7 changes: 7 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,10 @@ jobs:
run: pnpm run build
shell: bash

- name: Lint
run: pnpm run lint
shell: bash

- name: Run unit tests
run: pnpm run test:unit
shell: bash
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
"build-components": "pnpm --filter ccip-react-components run build",
"dev-example": "pnpm --filter example-nextjs run dev",
"clean": "rm -rf node_modules packages/*/node_modules packages/*/dist examples/nextjs/node_modules examples/nextjs/.next",
"test:unit": "pnpm --filter ccip-js run t:unit",
"test:int": "pnpm --filter ccip-js run t:int",
"test:int:hedera": "pnpm --filter ccip-js run t:int:hedera",
"test-ccip-js": "pnpm --filter ccip-js run t:int",
"lint": "pnpm --filter ccip-js run lint && pnpm --filter ccip-react-components run lint",
"test-components": "pnpm --filter ccip-react-components run test"
},
"devDependencies": {
Expand Down
2 changes: 0 additions & 2 deletions packages/ccip-js/.eslintignore

This file was deleted.

51 changes: 51 additions & 0 deletions packages/ccip-js/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Flat config for ESLint v9+
import tseslint from '@typescript-eslint/eslint-plugin'
import tsParser from '@typescript-eslint/parser'
import prettier from 'eslint-plugin-prettier'

export default [
{
ignores: ['dist/**', 'node_modules/**', 'coverage/**', 'artifacts/**', 'artifacts-compile/**', 'cache/**'],
},
{
files: ['src/**/*.{ts,js}', 'test/**/*.{ts,js}'],
languageOptions: {
parser: tsParser,
ecmaVersion: 2022,
sourceType: 'module',
globals: {
node: true,
es2022: true,
browser: true,
},
},
plugins: {
'@typescript-eslint': tseslint,
prettier,
},
rules: {
...tseslint.configs.recommended.rules,
'prettier/prettier': 'error',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
ignoreRestSiblings: true,
},
],
'@typescript-eslint/explicit-module-boundary-types': 'off',
},
},
{
files: ['test/**/*.{ts,js}'],
rules: {
'@typescript-eslint/no-unused-vars': 'off',
},
},
]


5 changes: 3 additions & 2 deletions packages/ccip-js/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
export default {
testEnvironment: 'node',
transform: {
'^.+.tsx?$': ['ts-jest', {}],
'^.+\\.tsx?$': ['ts-jest', { useESM: true }],
},
workerThreads: true,
extensionsToTreatAsEsm: ['.ts'],
testTimeout: 180000,
setupFilesAfterEnv: ['<rootDir>/test/jest.setup.ts'],
}
11 changes: 6 additions & 5 deletions packages/ccip-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@
"check": "tsc --noEmit",
"build:watch": "tsc -w",
"build": "tsc && hardhat compile",
"lint": "eslint 'src/**/*.{ts,js}'",
"lint": "eslint 'src/**/*.{ts,js}' 'test/**/*.{ts,js}'",
"format": "prettier --write 'src/**/*.{ts,js,json,md}'",
"t:int": "jest --coverage -u --testMatch=\"**/integration-testnet**.test.ts\" --detectOpenHandles",
"t:int:viem": "jest --coverage -u --testMatch=\"**/integration-testnet.test.ts\" --detectOpenHandles",
"t:int:ethers": "jest --coverage -u --testMatch=\"**/integration-testnet-ethers.test.ts\" --detectOpenHandles",
"t:unit": "jest --coverage -u --testMatch=\"**/unit.test.ts\" ",
"t:int": "jest --coverage -u --testMatch=\"**/integration-testnet**.test.ts\" --runInBand --detectOpenHandles --forceExit",
"t:int:viem": "jest --coverage -u --testMatch=\"**/integration-testnet.test.ts\" --runInBand --detectOpenHandles --forceExit",
Copy link
Contributor

Choose a reason for hiding this comment

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

--forceExit is generally not recommended. If its the best way for now then perhaps update the comment I left in integration test file, line 22 or so that is marked with a TODO? just to explain the context?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated the comment to explain. I gave it a quick shot keep the tests from hanging without forceExit and wasn't successful. So probably best for that to be a chore for later.

"t:int:ethers": "jest --coverage -u --testMatch=\"**/integration-testnet-ethers.test.ts\" --runInBand --detectOpenHandles --forceExit",
"t:int:hedera": "jest --coverage -u --testMatch=\"**/integration-hedera.test.ts\" --runInBand --detectOpenHandles --forceExit",
"t:unit": "jest --coverage -u --testMatch=\"**/unit.test.ts\" --runInBand --detectOpenHandles --forceExit",
"test:hh": "hardhat test"
},
"devDependencies": {
Expand Down
7 changes: 5 additions & 2 deletions packages/ccip-js/src/ethers-adapters.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Provider, Signer, TypedDataField } from 'ethers'
import type {Address, Hash, Transport, WalletClient, PublicClient } from 'viem'
import type { Address, Hash, Transport, WalletClient, PublicClient } from 'viem'

import { custom, createPublicClient, createWalletClient } from 'viem'
import { toAccount } from 'viem/accounts'
Expand Down Expand Up @@ -46,7 +46,10 @@ export async function ethersSignerToAccount(signer: Signer) {

/** Create a viem PublicClient from an ethers provider. */
export function ethersProviderToPublicClient(provider: Provider, chain: any): PublicClient {
return createPublicClient({ chain: chain as any, transport: ethersProviderToTransport(provider) }) as unknown as PublicClient
return createPublicClient({
chain: chain as any,
transport: ethersProviderToTransport(provider),
}) as unknown as PublicClient
}

/** Create a viem WalletClient from an ethers signer. */
Expand Down
1 change: 0 additions & 1 deletion packages/ccip-js/test/helpers/accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ export const getBalance = async ({ isFork }: BalanceOptions) => {
return balance
}


// export const setAllowance = async ({ isFork, amount, contract }: AllowanceOptions) => {
// const client = isFork ? forkClient : testClient

Expand Down
6 changes: 3 additions & 3 deletions packages/ccip-js/test/helpers/clients.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { account } from './constants'
import { createTestClient, http, publicActions, walletActions } from 'viem'
import { sepolia, anvil } from 'viem/chains'
import { sepolia, anvil } from 'viem/chains'

export const testClient = createTestClient({
chain: anvil,
transport: http(),
transport: http(undefined, { fetchOptions: { keepalive: false } }),
mode: 'anvil',
account,
})
Expand All @@ -13,7 +13,7 @@ export const testClient = createTestClient({

export const forkClient = createTestClient({
chain: sepolia,
transport: http(),
transport: http(undefined, { fetchOptions: { keepalive: false } }),
mode: 'anvil',
account,
})
Expand Down
5 changes: 2 additions & 3 deletions packages/ccip-js/test/helpers/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,11 @@ import feeQuoterJson from '../../artifacts-compile/FeeQuoter.json'
// replace with your own private key (optional)
dotenv.config()

if (process.env.PRIVATE_KEY?.slice(0, 2) !== '0x') {
if (process.env.PRIVATE_KEY && !process.env.PRIVATE_KEY.startsWith('0x')) {
process.env.PRIVATE_KEY = `0x${process.env.PRIVATE_KEY}`
}

export const DEFAULT_ANVIL_PRIVATE_KEY =
'0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' as Hex
export const DEFAULT_ANVIL_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' as Hex
export const account = privateKeyToAccount(DEFAULT_ANVIL_PRIVATE_KEY)

// bridge token contract
Expand Down
12 changes: 6 additions & 6 deletions packages/ccip-js/test/helpers/utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { forkClient, testClient } from "./clients";
import { forkClient, testClient } from './clients'

export const mineBlock = async (isFork: boolean) => {
const client = isFork? forkClient : testClient;
await client.mine({
blocks: 1
})
};
const client = isFork ? forkClient : testClient
await client.mine({
blocks: 1,
})
}
117 changes: 117 additions & 0 deletions packages/ccip-js/test/integration-hedera.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { expect, it, beforeAll, describe } from '@jest/globals'
import * as CCIP from '../src/api'
import * as Viem from 'viem'
import { hederaTestnet } from 'viem/chains'
import bridgeToken from '../artifacts-compile/BridgeToken.json'

const ccipClient = CCIP.createClient()
const bridgeTokenAbi = bridgeToken.contracts['src/contracts/BridgeToken.sol:BridgeToken'].bridgeTokenAbi

const HEDERA_TESTNET_RPC_URL = process.env.HEDERA_TESTNET_RPC_URL || 'https://testnet.hashio.io/api'
const SEPOLIA_CHAIN_SELECTOR = '16015286601757825753'

describe('Integration (viem): Hedera -> Sepolia', () => {
let hederaTestnetClient: Viem.Client
let bnmToken_hedera: any

const HEDERA_TESTNET_CCIP_ROUTER_ADDRESS = '0x802C5F84eAD128Ff36fD6a3f8a418e339f467Ce4'
const LINK_TOKEN_HEDERA = '0x90a386d59b9A6a4795a011e8f032Fc21ED6FEFb6'
const WRAPPED_HBAR = '0xb1F616b8134F602c3Bb465fB5b5e6565cCAd37Ed'

beforeAll(async () => {
hederaTestnetClient = Viem.createPublicClient({
chain: hederaTestnet,
transport: Viem.http(HEDERA_TESTNET_RPC_URL, { fetchOptions: { keepalive: false } }),
})

bnmToken_hedera = Viem.getContract({
address: '0x01Ac06943d2B8327a7845235Ef034741eC1Da352',
abi: bridgeTokenAbi,
client: hederaTestnetClient,
})

expect(bnmToken_hedera.address).toEqual('0x01Ac06943d2B8327a7845235Ef034741eC1Da352')
})

it('returns on-ramp address', async function () {
const hederaOnRampAddress = await ccipClient.getOnRampAddress({
client: hederaTestnetClient,
routerAddress: HEDERA_TESTNET_CCIP_ROUTER_ADDRESS,
destinationChainSelector: SEPOLIA_CHAIN_SELECTOR,
})
expect(hederaOnRampAddress).toBeDefined()
})

it('lists supported fee tokens', async function () {
const result = await ccipClient.getSupportedFeeTokens({
client: hederaTestnetClient,
routerAddress: HEDERA_TESTNET_CCIP_ROUTER_ADDRESS,
destinationChainSelector: SEPOLIA_CHAIN_SELECTOR,
})
expect(result.length).toBeGreaterThan(0)
expect(result).toEqual(expect.arrayContaining([LINK_TOKEN_HEDERA, WRAPPED_HBAR]))
})

it('fetched lane rate refill limits are defined', async function () {
const { tokens, lastUpdated, isEnabled, capacity, rate } = await ccipClient.getLaneRateRefillLimits({
client: hederaTestnetClient,
routerAddress: HEDERA_TESTNET_CCIP_ROUTER_ADDRESS,
destinationChainSelector: SEPOLIA_CHAIN_SELECTOR,
})

expect(typeof tokens).toBe('bigint')
expect(typeof lastUpdated).toBe('number')
expect(typeof isEnabled).toBe('boolean')
expect(typeof capacity).toBe('bigint')
expect(typeof rate).toBe('bigint')
})

it('returns token rate limit by lane', async function () {
const { tokens, lastUpdated, isEnabled, capacity, rate } = await ccipClient.getTokenRateLimitByLane({
client: hederaTestnetClient,
routerAddress: HEDERA_TESTNET_CCIP_ROUTER_ADDRESS,
supportedTokenAddress: bnmToken_hedera.address,
destinationChainSelector: SEPOLIA_CHAIN_SELECTOR,
})

expect(typeof tokens).toBe('bigint')
expect(typeof lastUpdated).toBe('number')
expect(typeof isEnabled).toBe('boolean')
expect(typeof capacity).toBe('bigint')
expect(typeof rate).toBe('bigint')
})

it('getFee is not supported on Hedera Router (throws)', async function () {
await expect(async () =>
ccipClient.getFee({
client: hederaTestnetClient,
routerAddress: HEDERA_TESTNET_CCIP_ROUTER_ADDRESS,
tokenAddress: bnmToken_hedera.address,
amount: Viem.parseEther('0.00000001'),
destinationChainSelector: SEPOLIA_CHAIN_SELECTOR,
destinationAccount: '0x0000000000000000000000000000000000000001',
}),
).rejects.toThrow()
})

it('returns token admin registry', async function () {
const result = await ccipClient.getTokenAdminRegistry({
client: hederaTestnetClient,
routerAddress: HEDERA_TESTNET_CCIP_ROUTER_ADDRESS,
tokenAddress: bnmToken_hedera.address,
destinationChainSelector: SEPOLIA_CHAIN_SELECTOR,
})

expect(result).toBeDefined()
})

it('checks if BnM token is supported for transfer', async function () {
const result = await ccipClient.isTokenSupported({
client: hederaTestnetClient,
routerAddress: HEDERA_TESTNET_CCIP_ROUTER_ADDRESS,
tokenAddress: bnmToken_hedera.address,
destinationChainSelector: SEPOLIA_CHAIN_SELECTOR,
})
expect(typeof result).toBe('boolean')
})
})
16 changes: 6 additions & 10 deletions packages/ccip-js/test/integration-testnet-ethers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect, it, afterAll, beforeAll, describe } from '@jest/globals'
import * as CCIP from '../src/api'
import * as Viem from 'viem'
import { parseEther } from 'viem'
import { sepolia, avalancheFuji, hederaTestnet } from 'viem/chains'
import { sepolia, avalancheFuji } from 'viem/chains'
import { ethers } from 'ethers'

import { privateKeyToAccount } from 'viem/accounts'
Expand All @@ -15,12 +15,9 @@ const bridgeTokenAbi = bridgeToken.contracts['src/contracts/BridgeToken.sol:Brid

const SEPOLIA_RPC_URL = process.env.SEPOLIA_RPC_URL
const AVALANCHE_FUJI_RPC_URL = process.env.AVALANCHE_FUJI_RPC_URL
const HEDERA_TESTNET_RPC_URL = process.env.HEDERA_TESTNET_RPC_URL || 'https://testnet.hashio.io/api'
const SEPOLIA_CHAIN_SELECTOR = '16015286601757825753'
const WRAPPED_NATIVE_AVAX = '0xd00ae08403B9bbb9124bB305C09058E32C39A48c'
const WRAPPED_HBAR = '0xb1F616b8134F602c3Bb465fB5b5e6565cCAd37Ed'
const LINK_TOKEN_FUJI = '0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846'
const LINK_TOKEN_HEDERA = '0x90a386d59b9A6a4795a011e8f032Fc21ED6FEFb6'

if (!SEPOLIA_RPC_URL) {
throw new Error('SEPOLIA_RPC_URL must be set')
Expand Down Expand Up @@ -334,7 +331,8 @@ describe('Integration (ethers adapter): Fuji -> Sepolia', () => {
})
})

describe.skip('√ (Hedera(custom decimals) -> Sepolia) all critical functionality in CCIP Client', () => {
/* Hedera tests moved to integration-hedera.test.ts and executed via `pnpm t:int:hedera`.
describe('√ (Hedera(custom decimals) -> Sepolia) all critical functionality in CCIP Client', () => {
let hederaTestnetClient: Viem.WalletClient
let sepoliaClient: Viem.WalletClient
let bnmToken_hedera: any
Expand All @@ -349,13 +347,13 @@ describe.skip('√ (Hedera(custom decimals) -> Sepolia) all critical functionali
beforeAll(async () => {
hederaTestnetClient = Viem.createWalletClient({
chain: hederaTestnet,
transport: Viem.http(HEDERA_TESTNET_RPC_URL),
transport: Viem.http(HEDERA_TESTNET_RPC_URL, { fetchOptions: { keepalive: false } }),
account: privateKeyToAccount(privateKey),
})

sepoliaClient = Viem.createWalletClient({
chain: sepolia,
transport: Viem.http(SEPOLIA_RPC_URL),
transport: Viem.http(SEPOLIA_RPC_URL, { fetchOptions: { keepalive: false } }),
account: privateKeyToAccount(privateKey),
})

Expand Down Expand Up @@ -552,6 +550,4 @@ describe.skip('√ (Hedera(custom decimals) -> Sepolia) all critical functionali
expect(ccipSend_txReceipt.from.toLowerCase()).toEqual(hederaTestnetClient.account!.address.toLowerCase())
expect(ccipSend_txReceipt.to!.toLowerCase()).toEqual(HEDERA_TESTNET_CCIP_ROUTER_ADDRESS.toLowerCase())
})
})


})*/
Loading
Loading