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
17 changes: 17 additions & 0 deletions deployments/_deployments_log_file.json
Original file line number Diff line number Diff line change
Expand Up @@ -47194,5 +47194,22 @@
]
}
}
},
"ContractBasedNativeWrapperFacet": {
"celo": {
"staging": {
"1.0.0": [
{
"ADDRESS": "0xfAbc0E644ECbfdd2ea3f08cf433ac447895A7EA4",
"OPTIMIZER_RUNS": "1000000",
"TIMESTAMP": "2025-10-15 15:24:11",
"CONSTRUCTOR_ARGS": "0x",
"SALT": "22345091",
"VERIFIED": "true",
"ZK_SOLC_VERSION": ""
}
]
}
}
}
}
5 changes: 3 additions & 2 deletions deployments/celo.staging.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@
"SyncSwapV2Facet": "0x2D097d669Db40A7C64360CAE4e64e50485089170",
"UniV2StyleFacet": "0x0D8332Fa8d5845D68311E106a55fc264e209f9E7",
"UniV3StyleFacet": "0x3a82c34B1D51C79beaC85A1db04873C8a1cc7FeA",
"VelodromeV2Facet": "0x5138DD64a101eaB616aA8B3D6C93A6658F117Cf0"
}
"VelodromeV2Facet": "0x5138DD64a101eaB616aA8B3D6C93A6658F117Cf0",
"ContractBasedNativeWrapperFacet": "0xfAbc0E644ECbfdd2ea3f08cf433ac447895A7EA4"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;

import { ContractBasedNativeWrapperFacet } from "lifi/Periphery/LDA/Facets/ContractBasedNativeWrapperFacet.sol";
import { DeployScriptBase } from "../utils/DeployScriptBase.sol";

contract DeployScript is DeployScriptBase {
constructor() DeployScriptBase("ContractBasedNativeWrapperFacet") {}

function run() public returns (ContractBasedNativeWrapperFacet deployed) {
deployed = ContractBasedNativeWrapperFacet(
deploy(type(ContractBasedNativeWrapperFacet).creationCode)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;

import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol";

contract DeployScript is UpdateLDAScriptBase {
function run()
public
returns (address[] memory facets, bytes memory cutData)
{
return update("ContractBasedNativeWrapperFacet");
}
}
97 changes: 97 additions & 0 deletions src/Periphery/LDA/Facets/ContractBasedNativeWrapperFacet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;

import { LibAsset } from "lifi/Libraries/LibAsset.sol";
import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol";
import { InvalidCallData } from "lifi/Errors/GenericErrors.sol";
import { BaseRouteConstants } from "../BaseRouteConstants.sol";

/// @title ContractBasedNativeWrapperFacet
/// @author LI.FI (https://li.fi)
/// @notice Handles wrapping/unwrapping of dual-purpose native tokens that function as both native and ERC-20
/// @dev Some blockchains implement native tokens with "token duality" - they function both as native currency
/// and as ERC-20 compatible tokens without requiring wrapping/unwrapping. Examples include:
///
/// CELO Token Duality (0x471EcE3750Da237f93B8E339c536989b8978a438):
/// - Functions as both native currency (like ETH) and ERC-20 token simultaneously
/// - Native transfers work like ETH transfers on Ethereum
/// - ERC-20 transfers use standard interface but trigger native transfers via precompile
/// - balanceOf() returns native balance directly (no separate storage)
/// - No deposit()/withdraw() methods needed - transfers are always native
///
/// This facet provides simple transfer-based operations for such tokens,
/// since they don't require actual wrapping/unwrapping operations.
/// @custom:version 1.0.0
contract ContractBasedNativeWrapperFacet is BaseRouteConstants {
using LibPackedStream for uint256;

// ==== External Functions ====

/// @notice Unwraps dual-purpose native token
/// @dev Dual-purpose native tokens (like CELO) don't have withdraw() methods since they're always native.
/// ERC-20 transfers automatically trigger native transfers via precompile, so we just transfer directly.
/// @param swapData Encoded swap parameters [destinationAddress]
/// @param from Token source. If from == msg.sender, pull tokens via transferFrom.
/// Otherwise, assume tokens are already held by this contract.
/// @param tokenIn Dual-purpose native token address
/// @param amountIn Amount of tokens to "unwrap" (actually just transfer natively)
function unwrapContractBasedNative(
bytes memory swapData,
address from,
address tokenIn,
uint256 amountIn
) external payable {
address destinationAddress;
assembly {
// swapData layout: [length (32 bytes)][data...]
// We want the first 20 bytes of data, right-shifted to get address
destinationAddress := shr(96, mload(add(swapData, 32)))
}

if (from == msg.sender) {
LibAsset.transferFromERC20(
tokenIn,
msg.sender,
address(this),
amountIn
);
}

// For dual-purpose native tokens, there's no "withdraw" - ERC-20 transfers automatically
// trigger native transfers via precompile, so we just transfer the tokens directly
if (destinationAddress != address(this)) {
LibAsset.transferERC20(tokenIn, destinationAddress, amountIn);
}
}

/// @notice Wraps native tokens to dual-purpose native token
/// @dev Dual-purpose native tokens (like CELO) don't have deposit() methods since they're always native.
/// ERC-20 transfers automatically trigger native transfers via precompile, so we just transfer directly.
/// @param swapData Encoded swap parameters [dualPurposeNativeToken, destinationAddress]
/// @param amountIn Amount of native tokens to "wrap" (actually just transfer natively)
function wrapContractBasedNative(
bytes memory swapData,
address, // from is not used
address, // tokenIn is not used
uint256 amountIn
) external payable {
uint256 stream = LibPackedStream.createStream(swapData);

address contractBasedNativeToken = stream.readAddress();
address destinationAddress = stream.readAddress();

if (contractBasedNativeToken == address(0)) {
revert InvalidCallData();
}

// For contract-based native tokens, there's no "deposit" - we just transfer the tokens directly
// since these tokens behave like native ETH but are actually ERC20 contracts
if (destinationAddress != address(this)) {
LibAsset.transferERC20(
contractBasedNativeToken,
destinationAddress,
amountIn
);
}
}
}
Loading
Loading