Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
3bd74f4
feat(Linea): Add CCTP support to Linea
nicholaspai Mar 5, 2025
be61552
deploy scripts
nicholaspai Mar 5, 2025
b5cdf31
fix tests
nicholaspai Mar 5, 2025
833fa10
Update Linea_Adapter.ts
nicholaspai Mar 5, 2025
f5cb584
Fix tests
nicholaspai Mar 5, 2025
fb10db9
Update Linea_SpokePool.sol
nicholaspai Mar 5, 2025
b6f0929
Update CircleCCTPAdapter.sol
nicholaspai Mar 5, 2025
ba40d92
Update Linea_SpokePool.json
nicholaspai Mar 5, 2025
1b5960f
Update PolygonZkEVM_SpokePool.ts
nicholaspai Mar 5, 2025
33955f3
Update contracts/Linea_SpokePool.sol
nicholaspai Mar 5, 2025
d439eaf
Update contracts/chain-adapters/Linea_Adapter.sol
nicholaspai Mar 5, 2025
0d436fd
Use V2 CCTP contracts for Linea
nicholaspai Mar 7, 2025
f683c90
fix tests
nicholaspai Mar 7, 2025
9f7e40c
fix(ZkSync_SpokePool): Add __gap (#907)
nicholaspai Mar 10, 2025
17b6ccf
Switch CCTP V2 in the constructor
nicholaspai Mar 11, 2025
51dde94
Update CircleCCTPAdapter.sol
nicholaspai Mar 11, 2025
b6ff742
revert
nicholaspai Mar 11, 2025
90650df
read feeRecipient to determine CCTP version
nicholaspai Mar 11, 2025
13b4953
remove virtual
nicholaspai Mar 11, 2025
e12ea2c
replace try-catch with low-level call() check
nicholaspai Mar 11, 2025
c518515
Check in constructor
nicholaspai Mar 11, 2025
46824c4
Add `OFTTransportAdapter` to support cross-chain token transfers of `…
grasphoper Mar 12, 2025
71b6b2a
Update CircleCCTPAdapter.sol
nicholaspai Mar 12, 2025
f8729d3
Merge branch 'march-25-evm-audit' into linea-cctp
nicholaspai Mar 12, 2025
9f1064b
Comments about CCTP V2 unknown addresses
nicholaspai Mar 13, 2025
f908e38
Respond to comments
nicholaspai Mar 13, 2025
039870d
Revert "Merge branch 'march-25-evm-audit' into linea-cctp"
nicholaspai Mar 13, 2025
08ee557
Merge branch 'master' into linea-cctp
nicholaspai Mar 13, 2025
7e40e02
Update README.md
nicholaspai Mar 13, 2025
b2e634c
Create AcrossOriginSettler.sol
nicholaspai Mar 13, 2025
30e2754
Delete ERC7683OrderDepositorExternal.sol
nicholaspai Mar 13, 2025
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
51 changes: 22 additions & 29 deletions contracts/Linea_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
pragma solidity ^0.8.19;

import "./SpokePool.sol";
import "./libraries/CircleCCTPAdapter.sol";
import { IMessageService, ITokenBridge, IUSDCBridge } from "./external/interfaces/LineaInterfaces.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
Expand All @@ -13,7 +14,7 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
* @notice Linea specific SpokePool.
* @custom:security-contact bugs@across.to
*/
contract Linea_SpokePool is SpokePool {
contract Linea_SpokePool is SpokePool, CircleCCTPAdapter {
using SafeERC20 for IERC20;

/**
Expand All @@ -29,14 +30,13 @@ contract Linea_SpokePool is SpokePool {
/**
* @notice Address of Linea's USDC Bridge contract on L2.
*/
IUSDCBridge public l2UsdcBridge;
IUSDCBridge private DEPRECATED_l2UsdcBridge;

/**************************************
* EVENTS *
**************************************/
event SetL2TokenBridge(address indexed newTokenBridge, address oldTokenBridge);
event SetL2MessageService(address indexed newMessageService, address oldMessageService);
event SetL2UsdcBridge(address indexed newUsdcBridge, address oldUsdcBridge);

/**
* @notice Construct Linea-specific SpokePool.
Expand All @@ -50,16 +50,20 @@ contract Linea_SpokePool is SpokePool {
constructor(
address _wrappedNativeTokenAddress,
uint32 _depositQuoteTimeBuffer,
uint32 _fillDeadlineBuffer
) SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer) {} // solhint-disable-line no-empty-blocks
uint32 _fillDeadlineBuffer,
IERC20 _l2Usdc,
ITokenMessenger _cctpTokenMessenger
)
SpokePool(_wrappedNativeTokenAddress, _depositQuoteTimeBuffer, _fillDeadlineBuffer)
CircleCCTPAdapter(_l2Usdc, _cctpTokenMessenger, CircleDomainIds.Ethereum)
{} // solhint-disable-line no-empty-blocks

/**
* @notice Initialize Linea-specific SpokePool.
* @param _initialDepositId Starting deposit ID. Set to 0 unless this is a re-deployment in order to mitigate
* relay hash collisions.
* @param _l2MessageService Address of Canonical Message Service. Can be reset by admin.
* @param _l2TokenBridge Address of Canonical Token Bridge. Can be reset by admin.
* @param _l2UsdcBridge Address of USDC Bridge. Can be reset by admin.
* @param _crossDomainAdmin Cross domain admin to set. Can be changed by admin.
* @param _withdrawalRecipient Address which receives token withdrawals. Can be changed by admin. For Spoke Pools on L2, this will
* likely be the hub pool.
Expand All @@ -68,14 +72,12 @@ contract Linea_SpokePool is SpokePool {
uint32 _initialDepositId,
IMessageService _l2MessageService,
ITokenBridge _l2TokenBridge,
IUSDCBridge _l2UsdcBridge,
address _crossDomainAdmin,
address _withdrawalRecipient
) public initializer {
__SpokePool_init(_initialDepositId, _crossDomainAdmin, _withdrawalRecipient);
_setL2TokenBridge(_l2TokenBridge);
_setL2MessageService(_l2MessageService);
_setL2UsdcBridge(_l2UsdcBridge);
}

/**
Expand Down Expand Up @@ -106,14 +108,6 @@ contract Linea_SpokePool is SpokePool {
_setL2MessageService(_l2MessageService);
}

/**
* @notice Change L2 USDC bridge address. Callable only by admin.
* @param _l2UsdcBridge New address of L2 USDC bridge.
*/
function setL2UsdcBridge(IUSDCBridge _l2UsdcBridge) public onlyAdmin nonReentrant {
_setL2UsdcBridge(_l2UsdcBridge);
}

/**************************************
* INTERNAL FUNCTIONS *
**************************************/
Expand All @@ -139,27 +133,32 @@ contract Linea_SpokePool is SpokePool {
function _bridgeTokensToHubPool(uint256 amountToReturn, address l2TokenAddress) internal override {
// Linea's L2 Canonical Message Service, requires a minimum fee to be set.
uint256 minFee = minimumFeeInWei();
// We require that the caller pass in the fees as msg.value instead of pulling ETH out of this contract's balance.
// Using the contract's balance would require a separate accounting system to keep LP funds separated from system funds
// used to pay for L2->L1 messages.
require(msg.value == minFee, "MESSAGE_FEE_MISMATCH");

// SpokePool is expected to receive ETH from the L1 HubPool, then we need to first unwrap it to ETH and then
// send ETH directly via the Canonical Message Service.
if (l2TokenAddress == address(wrappedNativeToken)) {
// We require that the caller pass in the fees as msg.value instead of pulling ETH out of this contract's balance.
// Using the contract's balance would require a separate accounting system to keep LP funds separated from system funds
// used to pay for L2->L1 messages.
require(msg.value == minFee, "MESSAGE_FEE_MISMATCH");

// msg.value is added here because the entire native balance (including msg.value) is auto-wrapped
// before the execution of any wrapped token refund leaf. So it must be unwrapped before being sent as a
// fee to the l2MessageService.
WETH9Interface(l2TokenAddress).withdraw(amountToReturn + msg.value); // Unwrap into ETH.
l2MessageService.sendMessage{ value: amountToReturn + msg.value }(withdrawalRecipient, msg.value, "");
}
// If the l1Token is USDC, then we need sent it via the USDC Bridge.
else if (l2TokenAddress == l2UsdcBridge.usdc()) {
IERC20(l2TokenAddress).safeIncreaseAllowance(address(l2UsdcBridge), amountToReturn);
l2UsdcBridge.depositTo{ value: msg.value }(amountToReturn, withdrawalRecipient);
else if (l2TokenAddress == address(usdcToken) && _isCCTPEnabled()) {
_transferUsdc(withdrawalRecipient, amountToReturn);
}
// For other tokens, we can use the Canonical Token Bridge.
else {
// We require that the caller pass in the fees as msg.value instead of pulling ETH out of this contract's balance.
// Using the contract's balance would require a separate accounting system to keep LP funds separated from system funds
// used to pay for L2->L1 messages.
require(msg.value == minFee, "MESSAGE_FEE_MISMATCH");

IERC20(l2TokenAddress).safeIncreaseAllowance(address(l2TokenBridge), amountToReturn);
l2TokenBridge.bridgeToken{ value: msg.value }(l2TokenAddress, amountToReturn, withdrawalRecipient);
}
Expand All @@ -178,12 +177,6 @@ contract Linea_SpokePool is SpokePool {
emit SetL2TokenBridge(address(_l2TokenBridge), oldTokenBridge);
}

function _setL2UsdcBridge(IUSDCBridge _l2UsdcBridge) internal {
address oldUsdcBridge = address(l2UsdcBridge);
l2UsdcBridge = _l2UsdcBridge;
emit SetL2UsdcBridge(address(_l2UsdcBridge), oldUsdcBridge);
}

function _setL2MessageService(IMessageService _l2MessageService) internal {
address oldMessageService = address(l2MessageService);
l2MessageService = _l2MessageService;
Expand Down
21 changes: 9 additions & 12 deletions contracts/chain-adapters/Linea_Adapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity ^0.8.0;

import "./interfaces/AdapterInterface.sol";
import "../external/interfaces/WETH9Interface.sol";
import "../libraries/CircleCCTPAdapter.sol";

import { IMessageService, ITokenBridge, IUSDCBridge } from "../external/interfaces/LineaInterfaces.sol";

Expand All @@ -14,31 +15,29 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
* @custom:security-contact bugs@across.to
*/
// solhint-disable-next-line contract-name-camelcase
contract Linea_Adapter is AdapterInterface {
contract Linea_Adapter is AdapterInterface, CircleCCTPAdapter {
using SafeERC20 for IERC20;

WETH9Interface public immutable L1_WETH;
IMessageService public immutable L1_MESSAGE_SERVICE;
ITokenBridge public immutable L1_TOKEN_BRIDGE;
IUSDCBridge public immutable L1_USDC_BRIDGE;

/**
* @notice Constructs new Adapter.
* @param _l1Weth WETH address on L1.
* @param _l1MessageService Canonical message service contract on L1.
* @param _l1TokenBridge Canonical token bridge contract on L1.
* @param _l1UsdcBridge L1 USDC Bridge to ConsenSys's L2 Linea.
*/
constructor(
WETH9Interface _l1Weth,
IMessageService _l1MessageService,
ITokenBridge _l1TokenBridge,
IUSDCBridge _l1UsdcBridge
) {
IERC20 _l1Usdc,
ITokenMessenger _cctpTokenMessenger
) CircleCCTPAdapter(_l1Usdc, _cctpTokenMessenger, CircleDomainIds.Linea) {
L1_WETH = _l1Weth;
L1_MESSAGE_SERVICE = _l1MessageService;
L1_TOKEN_BRIDGE = _l1TokenBridge;
L1_USDC_BRIDGE = _l1UsdcBridge;
}

/**
Expand Down Expand Up @@ -67,17 +66,15 @@ contract Linea_Adapter is AdapterInterface {
uint256 amount,
address to
) external payable override {
if (l1Token == address(usdcToken) && _isCCTPEnabled()) {
_transferUsdc(to, amount);
}
// If the l1Token is WETH then unwrap it to ETH then send the ETH directly
// via the Canoncial Message Service.
if (l1Token == address(L1_WETH)) {
else if (l1Token == address(L1_WETH)) {
L1_WETH.withdraw(amount);
L1_MESSAGE_SERVICE.sendMessage{ value: amount }(to, 0, "");
}
// If the l1Token is USDC, then we need sent it via the USDC Bridge.
else if (l1Token == L1_USDC_BRIDGE.usdc()) {
IERC20(l1Token).safeIncreaseAllowance(address(L1_USDC_BRIDGE), amount);
L1_USDC_BRIDGE.depositTo(amount, to);
}
// For other tokens, we can use the Canonical Token Bridge.
else {
IERC20(l1Token).safeIncreaseAllowance(address(L1_TOKEN_BRIDGE), amount);
Expand Down
33 changes: 33 additions & 0 deletions contracts/external/interfaces/CCTPInterfaces.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,39 @@ interface ITokenMessenger {
function localMinter() external view returns (ITokenMinter minter);
}

// Source: https://github.com/circlefin/evm-cctp-contracts/blob/63ab1f0ac06ce0793c0bbfbb8d09816bc211386d/src/v2/TokenMessengerV2.sol#L138C1-L166C15
interface ITokenMessengerV2 {
/**
* @notice Deposits and burns tokens from sender to be minted on destination domain.
* Emits a `DepositForBurn` event.
* @dev reverts if:
* - given burnToken is not supported
* - given destinationDomain has no TokenMessenger registered
* - transferFrom() reverts. For example, if sender's burnToken balance or approved allowance
* to this contract is less than `amount`.
* - burn() reverts. For example, if `amount` is 0.
* - maxFee is greater than or equal to `amount`.
* - MessageTransmitterV2#sendMessage reverts.
* @param amount amount of tokens to burn
* @param destinationDomain destination domain to receive message on
* @param mintRecipient address of mint recipient on destination domain
* @param burnToken token to burn `amount` of, on local domain
* @param destinationCaller authorized caller on the destination domain, as bytes32. If equal to bytes32(0),
* any address can broadcast the message.
* @param maxFee maximum fee to pay on the destination domain, specified in units of burnToken
* @param minFinalityThreshold the minimum finality at which a burn message will be attested to.
*/
function depositForBurn(
uint256 amount,
uint32 destinationDomain,
bytes32 mintRecipient,
address burnToken,
bytes32 destinationCaller,
uint256 maxFee,
uint32 minFinalityThreshold
) external;
}

/**
* A TokenMessenger stores a TokenMinter contract which extends the TokenController contract. The TokenController
* contract has a burnLimitsPerMessage public mapping which can be queried to find the per-message burn limit
Expand Down
39 changes: 38 additions & 1 deletion contracts/libraries/CircleCCTPAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ library CircleDomainIds {
uint32 public constant Base = 6;
uint32 public constant Polygon = 7;
uint32 public constant DoctorWho = 10;
uint32 public constant Linea = 11; // TODO replace with actual domain once Circle publishes it.
// Use this value for placeholder purposes only for adapters that extend this adapter but haven't yet been
// assigned a domain ID by Circle.
uint32 public constant UNINITIALIZED = type(uint32).max;
Expand Down Expand Up @@ -50,6 +51,13 @@ abstract contract CircleCCTPAdapter {
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
ITokenMessenger public immutable cctpTokenMessenger;

/**
* @notice Indicates if the CCTP V2 TokenMessenger is being used.
* @dev This is determined by checking if the feeRecipient() function exists and returns a non-zero address.
*/
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
bool public immutable cctpV2;

/**
* @notice intiailizes the CircleCCTPAdapter contract.
* @param _usdcToken USDC address on the current chain.
Expand All @@ -59,12 +67,23 @@ abstract contract CircleCCTPAdapter {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor(
IERC20 _usdcToken,
/// @dev This should ideally be an address but its kept as an ITokenMessenger to avoid rippling changes to the
/// constructors for every SpokePool/Adapter.
ITokenMessenger _cctpTokenMessenger,
uint32 _recipientCircleDomainId
) {
usdcToken = _usdcToken;
cctpTokenMessenger = _cctpTokenMessenger;
recipientCircleDomainId = _recipientCircleDomainId;

// Only the CCTP V2 TokenMessenger has a feeRecipient() function, so we use it to
// figure out if we are using CCTP V2 or V1. `success` can be true even if the contract doesn't
// implement feeRecipient but it has a fallback function so to be extra safe, we check the return value
// of feeRecipient() as well.
(bool success, bytes memory feeRecipient) = address(cctpTokenMessenger).staticcall(
abi.encodeWithSignature("feeRecipient()")
Copy link
Contributor

Choose a reason for hiding this comment

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

This is something I want to highlight to OZ.

We are doing this to avoid adding an extra constructor argument and, thus, create a larger diff for all the contracts that inherit from this CCTP contract. However, the downside is that this could be broken in the future if circle removes this function.

);
cctpV2 = (success && address(bytes20(feeRecipient)) != address(0));
}

/**
Expand Down Expand Up @@ -101,7 +120,25 @@ abstract contract CircleCCTPAdapter {
uint256 remainingAmount = amount;
while (remainingAmount > 0) {
uint256 partAmount = remainingAmount > burnLimit ? burnLimit : remainingAmount;
cctpTokenMessenger.depositForBurn(partAmount, recipientCircleDomainId, to, address(usdcToken));
if (cctpV2) {
Copy link
Member Author

Choose a reason for hiding this comment

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

This if branch adds some bytecode

// Uses the CCTP V2 "standard transfer" speed and
// therefore pays no additional fee for the transfer to be sped up.
ITokenMessengerV2(address(cctpTokenMessenger)).depositForBurn(
partAmount,
recipientCircleDomainId,
to,
address(usdcToken),
// The following parameters are new in this function from V2 to V1, can read more here:
// https://developers.circle.com/stablecoins/evm-smart-contracts
bytes32(0), // destinationCaller is set to bytes32(0) to indicate that anyone can call
// receiveMessage on the destination to finalize the transfer
0, // maxFee can be set to 0 for a "standard transfer"
2000 // minFinalityThreshold can be set to 20000 for a "standard transfer",
// https://github.com/circlefin/evm-cctp-contracts/blob/63ab1f0ac06ce0793c0bbfbb8d09816bc211386d/src/v2/FinalityThresholds.sol#L21
);
} else {
cctpTokenMessenger.depositForBurn(partAmount, recipientCircleDomainId, to, address(usdcToken));
}
remainingAmount -= partAmount;
}
}
Expand Down
7 changes: 5 additions & 2 deletions deploy/028_deploy_linea_adapter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { L1_ADDRESS_MAP, WETH } from "./consts";
import { L1_ADDRESS_MAP, WETH, USDCe } from "./consts";
import { DeployFunction } from "hardhat-deploy/types";
import { HardhatRuntimeEnvironment } from "hardhat/types";

Expand All @@ -14,7 +14,10 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
WETH[chainId],
L1_ADDRESS_MAP[chainId].lineaMessageService,
L1_ADDRESS_MAP[chainId].lineaTokenBridge,
L1_ADDRESS_MAP[chainId].lineaUsdcBridge,
// TODO: USDC.e on Linea will be upgraded to USDC so eventually we should add a USDC entry for Linea in consts
// and read from there instead of using the L1 USDC.e address.
USDCe[chainId],
L1_ADDRESS_MAP[chainId].cctpV2TokenMessenger,
],
});
};
Expand Down
13 changes: 10 additions & 3 deletions deploy/029_deploy_linea_spokepool.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DeployFunction } from "hardhat-deploy/types";
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { deployNewProxy, getSpokePoolDeploymentInfo } from "../utils/utils.hre";
import { FILL_DEADLINE_BUFFER, L2_ADDRESS_MAP, QUOTE_TIME_BUFFER, WETH } from "./consts";
import { FILL_DEADLINE_BUFFER, L2_ADDRESS_MAP, QUOTE_TIME_BUFFER, WETH, USDCe } from "./consts";

const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { hubPool } = await getSpokePoolDeploymentInfo(hre);
Expand All @@ -14,11 +14,18 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
1_000_000,
L2_ADDRESS_MAP[chainId].lineaMessageService,
L2_ADDRESS_MAP[chainId].lineaTokenBridge,
L2_ADDRESS_MAP[chainId].lineaUsdcBridge,
hubPool.address,
hubPool.address,
];
const constructorArgs = [WETH[chainId], QUOTE_TIME_BUFFER, FILL_DEADLINE_BUFFER];
const constructorArgs = [
WETH[chainId],
QUOTE_TIME_BUFFER,
FILL_DEADLINE_BUFFER,
// TODO: USDC.e on Linea will be upgraded to USDC so eventually we should add a USDC entry for Linea in consts
// and read from there instead of using the L1 USDC.e address.
USDCe[chainId],
L2_ADDRESS_MAP[chainId].cctpV2TokenMessenger,
];

await deployNewProxy("Linea_SpokePool", constructorArgs, initArgs);
};
Expand Down
5 changes: 2 additions & 3 deletions deploy/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ export const L1_ADDRESS_MAP: { [key: number]: { [contractName: string]: string }
polygonRegistry: "0x33a02E6cC863D393d6Bf231B697b82F6e499cA71",
polygonDepositManager: "0x401F6c983eA34274ec46f84D70b31C151321188b",
cctpTokenMessenger: "0xBd3fa81B58Ba92a82136038B25aDec7066af3155",
cctpV2TokenMessenger: "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d",
cctpMessageTransmitter: "0x0a992d191deec32afe36203ad87d7d289a738f81",
lineaMessageService: "0xd19d4B5d358258f05D7B411E21A1460D11B0876F",
lineaTokenBridge: "0x051F1D88f0aF5763fB888eC4378b4D8B29ea3319",
lineaUsdcBridge: "0x504a330327a089d8364c4ab3811ee26976d388ce",
scrollERC20GatewayRouter: "0xF8B1378579659D8F7EE5f3C929c2f3E332E41Fd6",
scrollMessengerRelay: "0x6774Bcbd5ceCeF1336b5300fb5186a12DDD8b367",
scrollGasPriceOracle: "0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B",
Expand All @@ -53,7 +53,6 @@ export const L1_ADDRESS_MAP: { [key: number]: { [contractName: string]: string }
usdc: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
lineaMessageService: "0xd19d4B5d358258f05D7B411E21A1460D11B0876F", // No sepolia deploy address
lineaTokenBridge: "0x051F1D88f0aF5763fB888eC4378b4D8B29ea3319", // No sepolia deploy address
lineaUsdcBridge: "0x504a330327a089d8364c4ab3811ee26976d388ce", // No sepolia deploy address
scrollERC20GatewayRouter: "0x13FBE0D0e5552b8c9c4AE9e2435F38f37355998a",
scrollMessengerRelay: "0x50c7d3e7f7c656493D1D76aaa1a836CedfCBB16A",
scrollGasPriceOracle: "0x247969F4fad93a33d4826046bc3eAE0D36BdE548",
Expand Down Expand Up @@ -217,7 +216,7 @@ export const L2_ADDRESS_MAP: { [key: number]: { [contractName: string]: string }
},
[CHAIN_IDs.LINEA]: {
lineaMessageService: "0x508Ca82Df566dCD1B0DE8296e70a96332cD644ec",
lineaUsdcBridge: "0xA2Ee6Fce4ACB62D95448729cDb781e3BEb62504A",
cctpV2TokenMessenger: "0xunknown", // No official address from Circle yet.
lineaTokenBridge: "0x353012dc4a9A6cF55c941bADC267f82004A8ceB9",
},
[CHAIN_IDs.SCROLL_SEPOLIA]: {
Expand Down
Loading
Loading