Skip to content
Closed
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
68 changes: 68 additions & 0 deletions src/definitions/arc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { defineChain } from 'viem'
import { EcoChain } from '../chain.interface'

/**
* Circle Arc Testnet chain configuration
* Custom chain configuration for Circle's Arc testnet with Eco-specific RPC URLs
*
* Arc uses USDC as its native gas token. Native gas accounting is 18 decimals,
* while the USDC ERC-20 interface is 6 decimals.
*
* NOTE: Arc Mainnet (chain ID 5042) is intentionally NOT defined here yet.
* The public mainnet RPC/USDC/CCTP addresses have not been published by Circle
* (private mainnet only, pending details from Circle private docs — see PAR-26).
* Shipping a mainnet definition without working RPCs would break downstream
* consumers that enumerate getMainnetChains() (e.g. balances-service
* auto-tracks every mainnet chain).
* TODO: add `arc` mainnet (id 5042) once Circle publishes mainnet details.
*/

export const arcTestnet = /*#__PURE__*/ defineChain({
id: 5042002,
name: 'Arc Testnet',
nativeCurrency: { name: 'USDC', symbol: 'USDC', decimals: 18 },
rpcUrls: {
alchemy: {
http: ['https://arc-testnet.g.alchemy.com/v2/${ALCHEMY_API_KEY}'],
webSocket: ['wss://arc-testnet.g.alchemy.com/v2/${ALCHEMY_API_KEY}'],
},
default: {
http: ['https://rpc.testnet.arc.network'],
webSocket: ['wss://rpc.testnet.arc.network'],
},
public: {
http: [
'https://rpc.testnet.arc.network',
'https://rpc.quicknode.testnet.arc.network',
'https://rpc.blockdaemon.testnet.arc.network',
'https://rpc.drpc.testnet.arc.network',
],
webSocket: [
'wss://rpc.testnet.arc.network',
'wss://rpc.quicknode.testnet.arc.network',
'wss://rpc.drpc.testnet.arc.network',
],
},
},
blockExplorers: {
default: {
name: 'ArcScan',
url: 'https://testnet.arcscan.app',
apiUrl: 'https://testnet.arcscan.app/api',
},
},
contracts: {
multicall3: {
address: '0xcA11bde05977b3631167028862bE2a173976CA11',
blockCreated: 0,
},
},
testnet: true,
isCalderaChain: false,
stables: {
USDC: {
address: '0x3600000000000000000000000000000000000000',
decimals: 6,
},
},
}) as EcoChain
1 change: 1 addition & 0 deletions src/definitions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * from './alienx'
export * from './ape'
export * from './appchain'
export * from './arbitrum'
export * from './arc'
export * from './b3'
export * from './base'
export * from './bsc'
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
ape,
appchain,
arbitrum,
arcTestnet,
b3,
base,
baseSepolia,
Expand Down Expand Up @@ -52,6 +53,7 @@ export const EcoRoutesChains = [
ape,
appchain,
arbitrum,
arcTestnet,
b3,
base,
baseSepolia,
Expand Down
101 changes: 101 additions & 0 deletions src/tests/arc.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
const mockViemExtract = jest.fn()
import { cloneDeep } from 'lodash'
import { EcoChains } from '../eco.chains'
import { arcTestnet } from '../definitions/arc'

// getChain shallow-copies rpcUrls, so RPC url groups on the shared chain
// definitions are mutated across calls. Mirror eco.chains.spec.ts and return
// a deep clone from extractChain so each test gets a pristine definition.
jest.mock('viem', () => {
return {
...jest.requireActual('viem'),
extractChain: mockViemExtract,
}
})

describe('Arc Testnet chain', () => {
const ARC_TESTNET_CHAIN_ID = 5042002

beforeEach(() => {
mockViemExtract.mockReset()
mockViemExtract.mockImplementation((args) =>
cloneDeep(jest.requireActual('viem').extractChain(args)),
)
})

it('should define the arc testnet chain correctly', () => {
expect(arcTestnet.id).toBe(ARC_TESTNET_CHAIN_ID)
expect(arcTestnet.name).toBe('Arc Testnet')
expect(arcTestnet.testnet).toBe(true)
expect(arcTestnet.isCalderaChain).toBe(false)
// Arc native gas token is USDC with 18-decimal native accounting
expect(arcTestnet.nativeCurrency).toEqual({
name: 'USDC',
symbol: 'USDC',
decimals: 18,
})
expect(arcTestnet.contracts?.multicall3).toEqual({
address: '0xcA11bde05977b3631167028862bE2a173976CA11',
blockCreated: 0,
})
expect(arcTestnet.blockExplorers?.default).toEqual({
name: 'ArcScan',
url: 'https://testnet.arcscan.app',
apiUrl: 'https://testnet.arcscan.app/api',
})
})

it('should be retrievable through EcoChains by chain id', () => {
const ecoChains = new EcoChains({})
const chain = ecoChains.getChain(ARC_TESTNET_CHAIN_ID)
expect(chain).toBeDefined()
expect(chain.id).toBe(ARC_TESTNET_CHAIN_ID)
expect(chain.name).toBe('Arc Testnet')
})

it('should replace the alchemy api key in rpc urls', () => {
const ecoChains = new EcoChains({ alchemyKey: 'test_alchemy_key' })
const chain = ecoChains.getChain(ARC_TESTNET_CHAIN_ID)
expect(chain.rpcUrls.alchemy.http).toEqual([
'https://arc-testnet.g.alchemy.com/v2/test_alchemy_key',
])
expect(chain.rpcUrls.alchemy.webSocket).toEqual([
'wss://arc-testnet.g.alchemy.com/v2/test_alchemy_key',
])
})

it('should filter out alchemy rpc urls when no api key is provided', () => {
const ecoChains = new EcoChains({})
const chain = ecoChains.getChain(ARC_TESTNET_CHAIN_ID)
expect(chain.rpcUrls.alchemy.http).toEqual([])
expect(chain.rpcUrls.alchemy.webSocket).toEqual([])
// Default public RPCs remain untouched
expect(chain.rpcUrls.default.http).toEqual([
'https://rpc.testnet.arc.network',
])
})

it('should return the USDC stable for the arc testnet', () => {
const ecoChains = new EcoChains({})
const stables = ecoChains.getStablesForChain(ARC_TESTNET_CHAIN_ID)
expect(stables.USDC).toEqual({
address: '0x3600000000000000000000000000000000000000',
decimals: 6,
})
})

it('should be classified as a testnet chain', () => {
const ecoChains = new EcoChains({})
const testnetIds = ecoChains.getTestnetChains().map((chain) => chain.id)
const mainnetIds = ecoChains.getMainnetChains().map((chain) => chain.id)
expect(testnetIds).toContain(ARC_TESTNET_CHAIN_ID)
expect(mainnetIds).not.toContain(ARC_TESTNET_CHAIN_ID)
})

it('should not define an arc mainnet chain yet (pending Circle mainnet details)', () => {
const ecoChains = new EcoChains({})
const ARC_MAINNET_CHAIN_ID = 5042
const allIds = ecoChains.getAllChains().map((chain) => chain.id)
expect(allIds).not.toContain(ARC_MAINNET_CHAIN_ID)
})
})
Loading