Skip to content
Open
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
11 changes: 11 additions & 0 deletions .changeset/funny-sunshade-bloat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@ledgerhq/types-live": minor
"@ledgerhq/cryptoassets": minor
"@ledgerhq/coin-evm": minor
"@ledgerhq/live-common": minor
"@ledgerhq/live-cli": minor
"ledger-live-desktop": minor
"live-mobile": minor
---

Add Arc and Arc Testnet (Circle's USDC-native EVM L1, chainIds 5042 and 5042002)
2 changes: 2 additions & 0 deletions apps/cli/src/live-common-setup-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ setSupportedCurrencies([
"sei_evm",
"berachain",
"hyperevm",
"arc",
"arc_testnet",
"coreum",
"injective",
"casper",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ setSupportedCurrencies([
"sei_evm",
"berachain",
"hyperevm",
"arc",
"arc_testnet",
"canton_network",
"canton_network_devnet",
"canton_network_testnet",
Expand Down
64 changes: 64 additions & 0 deletions apps/ledger-live-desktop/src/mocks/dada/mockAssets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ export const mockAssets = {
monad: "monad/erc20/usdc_0x754704bc059f8c67012fed69bc8a327a5aafb603",
sei_evm: "sei_evm/erc20/usdc_0xe15fc38f6d8c56af07bbcbe3baf5708a2bf42392",
unichain: "unichain/erc20/usdc_0x078d782b760474a361dda0af3839290b0ef57ad6",
arc: "arc",
arc_testnet: "arc_testnet",
},
},
"ethereum/erc20/steth": {
Expand Down Expand Up @@ -320,6 +322,14 @@ export const mockAssets = {
id: "dogecoin",
name: "Dogecoin",
},
arc: {
id: "arc",
name: "Arc",
},
arc_testnet: {
id: "arc_testnet",
name: "Arc Testnet",
},
},
cryptoOrTokenCurrencies: {
"optimism/erc20/usd_coin": {
Expand Down Expand Up @@ -2357,6 +2367,60 @@ export const mockAssets = {
hrp: null,
disableCountervalue: false,
},
arc: {
type: "crypto_currency",
id: "arc",
name: "Arc",
ticker: "USDC",
units: [
{
code: "USDC",
name: "USDC",
magnitude: 18,
},
{
code: "Gwei",
name: "Gwei",
magnitude: 9,
},
],
chainId: "5042",
confirmationsNeeded: 30,
symbol: "USDC",
coinType: 60,
family: "ethereum",
hasSegwit: false,
hasTokens: true,
hrp: null,
disableCountervalue: false,
},
arc_testnet: {
type: "crypto_currency",
id: "arc_testnet",
name: "Arc Testnet",
ticker: "USDC",
units: [
{
code: "USDC",
name: "USDC",
magnitude: 18,
},
{
code: "Gwei",
name: "Gwei",
magnitude: 9,
},
],
chainId: "5042002",
confirmationsNeeded: 30,
symbol: "USDC",
coinType: 60,
family: "ethereum",
hasSegwit: false,
hasTokens: true,
hrp: null,
disableCountervalue: true,
},
"algorand/asa/312769": {
type: "token_currency",
id: "algorand/asa/312769",
Expand Down
18 changes: 18 additions & 0 deletions apps/ledger-live-desktop/tests/fixtures/wallet-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,24 @@ export const expectedCurrencyList = [
ticker: "HYPE",
type: "CryptoCurrency",
},
{
color: "#1A2B5F",
decimals: 18,
family: "ethereum",
id: "arc",
name: "Arc",
ticker: "USDC",
type: "CryptoCurrency",
},
{
color: "#6B7280",
decimals: 18,
family: "ethereum",
id: "arc_testnet",
name: "Arc Testnet",
ticker: "USDC",
type: "CryptoCurrency",
},
{
type: "CryptoCurrency",
id: "coreum",
Expand Down
2 changes: 2 additions & 0 deletions apps/ledger-live-mobile/src/live-common-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ setSupportedCurrencies([
"sei_evm",
"berachain",
"hyperevm",
"arc",
"arc_testnet",
"coreum",
"injective",
"casper",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ export const mocks: AppManifest[] = [
"hyperevm",
"sei_evm",
"berachain",
"arc",
"arc_testnet",
"coreum",
"injective",
"neon_evm",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import type {
AlpacaApi,
BufferTxData,
MemoNotSupported,
} from "@ledgerhq/coin-module-framework/api/types";
import { setupCalClientStore } from "@ledgerhq/cryptoassets/cal-client/test-helpers";
import type { BridgeApi } from "@ledgerhq/ledger-wallet-framework/api/types";
import { EvmConfig } from "../config";
import { createApi } from "./index";

// Arc testnet exposes its native unit (USDC) as an ERC20 at this fixed precompile
// address. The explorer indexes native transfers as BOTH a native tx AND a synthetic
// ERC20 Transfer event on this contract. We rely on the `nativeContracts` config
// to skip the ERC20 side so the wallet doesn't double-count.
const ARC_USDC_NATIVE_CONTRACT = "0x3600000000000000000000000000000000000000";

// Address with native USDC activity on Arc testnet.
const TEST_ADDRESS = "0x66c4371aE8FFeD2ec1c2EBbbcCfb7E494181E1E3";

describe("EVM Api (Arc Testnet)", () => {
let module: AlpacaApi<MemoNotSupported, BufferTxData> & BridgeApi;

beforeAll(() => {
setupCalClientStore();
module = createApi(
{
node: { type: "external", uri: "https://rpc.testnet.arc.network" },
explorer: {
type: "blockscout",
uri: "https://proxyblockscout.api.live.ledger.com/5042002/api",
},
showNfts: false,
nativeContracts: [ARC_USDC_NATIVE_CONTRACT],
} as EvmConfig,
"arc_testnet",
);
});

describe("listOperations", () => {
it("does not return ERC20 operations on contracts listed in nativeContracts", async () => {
const { items } = await module.listOperations(TEST_ADDRESS, {
minHeight: 0,
order: "desc",
limit: 20,
});

expect(items).toBeInstanceOf(Array);

// The address should have some history (sanity check the integ env is reachable).
expect(items.length).toBeGreaterThan(0);

// None of the operations should be an ERC20 op on the native USDC contract.
const erc20OpsOnNativeContract = items.filter(
op =>
op.asset.type === "erc20" &&
"assetReference" in op.asset &&
typeof op.asset.assetReference === "string" &&
op.asset.assetReference.toLowerCase() === ARC_USDC_NATIVE_CONTRACT.toLowerCase(),
);
expect(erc20OpsOnNativeContract).toEqual([]);
});
});

describe("getBalance", () => {
it("returns the native balance and no ERC20 balance for contracts in nativeContracts", async () => {
const balances = await module.getBalance(TEST_ADDRESS);

// First balance is always the native one (coin-evm contract).
const nativeBalance = balances[0];
expect(nativeBalance.asset.type).toBe("native");
expect(typeof nativeBalance.value).toBe("bigint");

// No subsequent balance should be the native-mirror ERC20 contract.
const tokenBalanceOnNativeContract = balances.find(
b =>
b.asset.type === "erc20" &&
"assetReference" in b.asset &&
typeof b.asset.assetReference === "string" &&
b.asset.assetReference.toLowerCase() === ARC_USDC_NATIVE_CONTRACT.toLowerCase(),
);
expect(tokenBalanceOnNativeContract).toBeUndefined();
});
});
});
30 changes: 30 additions & 0 deletions libs/coin-modules/coin-evm/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,36 @@ export type EvmConfig = {
* Use "safe" or "finalized" on chains where reorg protection is needed.
*/
finalizationLevel?: BlockFinalizationTag;
/**
* ERC20 contract addresses that mirror the native balance and must be skipped when
* computing token balances to avoid double-counting (e.g. Circle's Arc, where USDC
* is the native unit of account exposed at a fixed ERC20 address).
* Addresses are matched case-insensitively.
*/
nativeContracts?: string[];
/**
* Minimum effective price per gas (in wei, decimal string) accepted by this chain's
* mempool. Applied as a floor to both the legacy `gasPrice` and the EIP-1559
* `maxPriorityFeePerGas`. Useful on sparse testnets where the network's effective
* floor sits above what `eth_feeHistory` / `eth_gasPrice` reports, causing
* underpriced transactions to be silently dropped.
* 20 gwei example value: "20000000000"
*/
minGasPrice?: string;
/**
* Number of blocks to request from `eth_feeHistory` when estimating priority fees.
* Defaults to 5. Increase on chains with fast block times or sparse traffic so the
* sample window covers enough transactions to be representative (e.g. a 0.5s-block
* chain with 5 blocks only sees 2.5s of history, often not enough to surface a
* meaningful priority fee). Most nodes cap this around 1024; keep well below.
*/
feeHistoryBlockCount?: number;
/**
* Percentile (0-100) of priority fees actually paid per block to sample from
* `eth_feeHistory`. Defaults to 50 (median). Higher values bias toward faster
* inclusion at the cost of paying more; lower values bias toward minimal cost.
*/
feeHistoryRewardPercentile?: number;
};

export type ExternalNodeConfig = Extract<EvmConfig["node"], { type: "external" }>;
Expand Down
Loading
Loading