Cryo Explorer Ethereum Mainnet

Address Contract Verified

Address 0xB3915B992322c60fe090E4666D17eACc01Fb2C4d
Balance 0.009981 ETH
Nonce 1
Code Size 5638 bytes
Indexed Transactions 0
External Etherscan · Sourcify

Contract Bytecode

5638 bytes
0x60806040526004361015610018575b36610352576113c7565b610023600035610062565b80633a5381b51461005d578063636daeda14610058578063f376ebbb146100535763f45346dc0361000e57610328565b6102b8565b610200565b6100c2565b60e01c90565b60405190565b600080fd5b600080fd5b600091031261008357565b610073565b60018060a01b031690565b61009c90610088565b90565b6100a890610093565b9052565b91906100c09060006020850194019061009f565b565b346100f2576100d2366004610078565b6100ee6100dd6103ea565b6100e5610068565b918291826100ac565b0390f35b61006e565b600080fd5b90565b610108816100fc565b0361010f57565b600080fd5b90503590610121826100ff565b565b90565b61012f81610123565b0361013657565b600080fd5b9050359061014882610126565b565b61015381610093565b0361015a57565b600080fd5b9050359061016c8261014a565b565b90565b61017a8161016e565b0361018157565b600080fd5b9050359061019382610171565b565b909160c0828403126101f5576101ae8360008401610114565b926101bc816020850161013b565b926101ca826040830161015f565b926101f26101db8460608501610186565b936101e9816080860161015f565b9360a00161015f565b90565b610073565b60000190565b346102355761021f610213366004610195565b94939093929192610f4c565b610227610068565b80610231816101fa565b0390f35b61006e565b7f000000000000000000000000729bb8896796ad646b1769470d0bbcc0365a9ea990565b90565b61027561027061027a92610088565b61025e565b610088565b90565b61028690610261565b90565b6102929061027d565b90565b61029e90610289565b9052565b91906102b690600060208501940190610295565b565b346102e8576102c8366004610078565b6102e46102d361023a565b6102db610068565b918291826102a2565b0390f35b61006e565b909160608284031261032357610320610309846000850161015f565b93610317816020860161013b565b9360400161015f565b90565b610073565b61033c6103363660046102ed565b91611396565b610344610068565b8061034e816101fa565b0390f35b600080fd5b600090565b601f801991011690565b634e487b7160e01b600052604160045260246000fd5b906103869061035c565b810190811067ffffffffffffffff8211176103a057604052565b610366565b60e01b90565b905051906103b88261014a565b565b906020828203126103d4576103d1916000016103ab565b90565b610073565b6103e1610068565b3d6000823e3d90fd5b6103f2610357565b5061043760206104217f000000000000000000000000729bb8896796ad646b1769470d0bbcc0365a9ea9610289565b633a5381b59061042f610068565b9384926103a5565b82528180610447600482016101fa565b03915afa90811561048c5760009161045e575b5090565b61047f915060203d8111610485575b610477818361037c565b8101906103ba565b3861045a565b503d61046d565b6103d9565b151590565b61049f81610491565b036104a657565b600080fd5b905051906104b882610496565b565b906020828203126104d4576104d1916000016104ab565b90565b610073565b9493929190610536602061050c7f000000000000000000000000729bb8896796ad646b1769470d0bbcc0365a9ea9610289565b63facd743b9061052b339261051f610068565b958694859384936103a5565b8352600483016100ac565b03915afa80156105b65761055391600091610588575b5015610491565b6105625761056095610ac1565b565b6105843361056e610068565b918291635b4576ed60e11b8352600483016100ac565b0390fd5b6105a9915060203d81116105af575b6105a1818361037c565b8101906104ba565b3861054c565b503d610597565b6103d9565b90565b6105d26105cd6105d7926105bb565b61025e565b61016e565b90565b6105ee6105e96105f3926105bb565b61025e565b610088565b90565b6105ff906105da565b90565b61060c60006105f6565b90565b60001c90565b60018060a01b031690565b61062c6106319161060f565b610615565b90565b61063e9054610620565b90565b61064a9061027d565b90565b9050519061065a826100ff565b565b90602082820312610676576106739160000161064d565b90565b610073565b60001b90565b61069561069061069a926105bb565b61067b565b6100fc565b90565b634e487b7160e01b600052601160045260246000fd5b6106bc9061016e565b600160ff1b81146106cd5760000390565b61069d565b6106e66106e16106eb9261016e565b61025e565b610123565b90565b6106f790610261565b90565b610703906106ee565b90565b61070f9061027d565b90565b61071b9061027d565b90565b9050519061072b82610126565b565b90602082820312610747576107449160000161071e565b90565b610073565b61075590610123565b9052565b60409061078361078a94969593966107796060840198600085019061009f565b602083019061074c565b019061074c565b565b600080fd5b600080fd5b906107a96107a2610068565b928361037c565b565b67ffffffffffffffff81116107c9576107c560209161035c565b0190565b610366565b60005b8381106107e2575050906000910152565b8060209183015181850152016107d1565b90929192610808610803826107ab565b610796565b9381855260208501908284011161082457610822926107ce565b565b610791565b9080601f8301121561084757816020610844935191016107f3565b90565b61078c565b9060208282031261087d57600082015167ffffffffffffffff8111610878576108759201610829565b90565b6100f7565b610073565b9061089461088f836107ab565b610796565b918252565b60007f4554480000000000000000000000000000000000000000000000000000000000910152565b6108cb6003610882565b906108d860208301610899565b565b6108e26108c1565b90565b6108ee9061027d565b90565b6108fa906100fc565b9052565b60209181520190565b60007f657468657265756d000000000000000000000000000000000000000000000000910152565b61093c60086020926108fe565b61094581610907565b0190565b5190565b61096c61097560209361097a9361096381610949565b938480936108fe565b958691016107ce565b61035c565b0190565b6109879061016e565b9052565b906109da6109ec959796946109cc6109e5936109bf60a0976109b560c089019360008a01906108f1565b602088019061074c565b858103604087015261092f565b90848203606086015261094d565b96608083019061009f565b019061097e565b565b6109f790610261565b90565b610a03906109ee565b90565b610a0f9061027d565b90565b905090565b610a2360008092610a12565b0190565b610a3090610a17565b90565b67ffffffffffffffff8111610a5157610a4d60209161035c565b0190565b610366565b90610a68610a6383610a33565b610796565b918252565b606090565b3d600014610a8f57610a833d610a56565b903d6000602084013e5b565b610a97610a6d565b90610a8d565b916020610abf929493610ab86040820196600083019061009f565b019061074c565b565b949290919281610ada610ad460006105be565b9161016e565b14610f295780610afb610af5610af060006105f6565b610093565b91610093565b14610f065784610b1a610b14610b0f610602565b610093565b91610093565b14610ee357610b606020610b36610b316000610634565b610641565b633ae6187090610b558892610b49610068565b958694859384936103a5565b8352600483016100ac565b03915afa908115610ede57600091610eb0575b50610b8f610b89610b846000610681565b6100fc565b916100fc565b14610e8d5781610bab610ba5600160ff1b61016e565b9161016e565b14610e6a57610bc1610bbc836106b3565b6106d2565b9584610bdc610bd6610bd1610602565b610093565b91610093565b14600014610dd657610bed30610712565b315b87610c02610bfc83610123565b91610123565b11610dac57509284610c23610c1d610c18610602565b610093565b91610093565b14600014610d2e57610c336108da565b935b610c828694889495610c70610c6a7fde7df4ba62ded64a994288078c38e029db37f1a03f70bcd15e8adee34714a7e3976108e5565b976108e5565b97610c79610068565b9586958661098b565b0390a380610c9f610c99610c94610602565b610093565b91610093565b14600014610d145750610ce6600080610cbf610cba856109fa565b610a06565b85610cc8610068565b9081610cd381610a27565b03925af1610cdf610a72565b5015610491565b610cef5750505b565b610d10610cfa610068565b928392636f54afdd60e11b845260048401610a9d565b0390fd5b91610d21610d29936106fa565b919091611478565b610ced565b610d5a6000610d44610d3f886106fa565b610706565b6395d89b4190610d52610068565b9384926103a5565b82528180610d6a600482016101fa565b03915afa908115610da757600091610d84575b5093610c35565b610da191503d806000833e610d99818361037c565b81019061084c565b38610d7d565b6103d9565b8790610dd2879192610dbc610068565b938493634390211d60e11b855260048501610759565b0390fd5b610e1e6020610dec610de7886106fa565b610706565b6370a0823190610e13610dfe30610712565b92610e07610068565b958694859384936103a5565b8352600483016100ac565b03915afa908115610e6557600091610e37575b50610bef565b610e58915060203d8111610e5e575b610e50818361037c565b81019061072d565b38610e31565b503d610e46565b6103d9565b610e72610068565b630590fb9f60e01b815280610e89600482016101fa565b0390fd5b610e95610068565b630ccd248560e21b815280610eac600482016101fa565b0390fd5b610ed1915060203d8111610ed7575b610ec9818361037c565b81019061065c565b38610b73565b503d610ebf565b6103d9565b610eeb610068565b63a86b1e5360e01b815280610f02600482016101fa565b0390fd5b610f0e610068565b63dae7132560e01b815280610f25600482016101fa565b0390fd5b610f31610068565b631f2a200560e01b815280610f48600482016101fa565b0390fd5b90610f5a95949392916104d9565b565b9190610fb66020610f8c7f000000000000000000000000729bb8896796ad646b1769470d0bbcc0365a9ea9610289565b63facd743b90610fab3392610f9f610068565b958694859384936103a5565b8352600483016100ac565b03915afa90811561102d57600091610fff575b50610fd957610fd7926110c6565b565b610ffb33610fe5610068565b91829163ea04e16f60e01b8352600483016100ac565b0390fd5b611020915060203d8111611026575b611018818361037c565b8101906104ba565b38610fc9565b503d61100e565b6103d9565b61104661104161104b926105bb565b61025e565b610123565b90565b9160206110709294936110696040820196600083019061074c565b019061074c565b565b6110bd6110b26110c4959796946110a460809561109760a0870191600088019061074c565b858103602087015261092f565b90848203604086015261094d565b96606083019061009f565b019061074c565b565b9091806110e46110de6110d960006105f6565b610093565b91610093565b1461137357826110fd6110f76000611032565b91610123565b146113505761114360206111196111146000610634565b610641565b633ae6187090611138869261112c610068565b958694859384936103a5565b8352600483016100ac565b03915afa90811561134b5760009161131d575b5061117261116c6111676000610681565b6100fc565b916100fc565b146112fa578161119161118b611186610602565b610093565b91610093565b146000146112d757346111ac6111a685610123565b91610123565b036112b0575b4291806111ce6111c86111c3610602565b610093565b91610093565b14600014611232576111de6108da565b905b939261122d33939461121b6112157f55ac0a4644bac3bb99b4c6fa68cd18bbc2e8dfa070f65854381c77047f67d594966108e5565b966108e5565b96611224610068565b94859485611072565b0390a3565b61125e6000611248611243846106fa565b610706565b6395d89b4190611256610068565b9384926103a5565b8252818061126e600482016101fa565b03915afa9081156112ab57600091611288575b50906111e0565b6112a591503d806000833e61129d818361037c565b81019061084c565b38611281565b6103d9565b82346112d36112bd610068565b928392631614156d60e21b84526004840161104e565b0390fd5b6112f56112e3836106fa565b336112ed30610712565b90869261155f565b6111b2565b611302610068565b630ccd248560e21b815280611319600482016101fa565b0390fd5b61133e915060203d8111611344575b611336818361037c565b81019061065c565b38611156565b503d61132c565b6103d9565b611358610068565b631f2a200560e01b81528061136f600482016101fa565b0390fd5b61137b610068565b63dae7132560e01b815280611392600482016101fa565b0390fd5b906113a19291610f5c565b565b9160206113c59294936113be6040820196600083019061074c565b019061009f565b565b3433906113eb6113d5610068565b9283926333f7f06160e21b8452600484016113a3565b0390fd5b600090565b60007f5452414e534645525f4641494c45440000000000000000000000000000000000910152565b611429600f6020926108fe565b611432816113f4565b0190565b61144c906020810190600081830391015261141c565b90565b1561145657565b61145e610068565b62461bcd60e51b81528061147460048201611436565b0390fd5b90600060446020926114cb9561148c6113ef565b506040519163a9059cbb60e01b835260018060a01b03166004830152602482015282855af19081601f3d116001600051141615166114cd575b5061144f565b565b90503d903b151715386114c5565b60007f5452414e534645525f46524f4d5f4641494c4544000000000000000000000000910152565b61151060146020926108fe565b611519816114db565b0190565b6115339060208101906000818303910152611503565b90565b1561153d57565b611545610068565b62461bcd60e51b81528061155b6004820161151d565b0390fd5b9160646020926115c0956000936115746113ef565b50604051926323b872dd60e01b845260018060a01b0316600484015260018060a01b03166024830152604482015282855af19081601f3d116001600051141615166115c2575b50611536565b565b90503d903b151715386115ba56fea264697066735822122022f0b40dd8a9305c68a577fa93d9e29ae9d626a8cadaffaf5645e8c64e734adf64736f6c63430008190033

Verified Source Code Full Match

Compiler: v0.8.25+commit.b61c2a91 EVM: paris Optimization: No
NativeAssetManager.sol 77 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.25;
pragma abicoder v2;

import './Shared.sol';
import { IInvoiceManager } from './interfaces/IInvoiceManager.sol';
import { ValidatorManager } from './ValidatorManager.sol';
import { ERC20 } from '../lib/solmate/src/tokens/ERC20.sol';
import { SafeTransferLib } from '../lib/solmate/src/utils/SafeTransferLib.sol';

/**
 * @title NativeAssetManager
 * @notice Manages assets on native chain within the portal ecosystem.
 * @custom:security-contact [email protected]
 */
contract NativeAssetManager is IInvoiceManager, ValidatorManager {
  /**
   * Mapping of all registered assets by their ERC-20 contract address to
   * their respective unique identifiers on the portal chain.
   */
  mapping(address => bytes32) public _assetIdsByAddress;

  /**
   * Mapping of all registered assets by their unique identifiers on the
   * portal chain to their respective ERC-20 contract addresses.
   */
  mapping(bytes32 => address) internal _assetAddressesById;

  bytes32 constant ETHEREUM_HASH = keccak256(bytes('ethereum'));
  bytes32 constant ETH_SYMBOL_HASH = keccak256(bytes('ETH'));
  bytes32 constant ETH_NAME_HASH = keccak256(bytes('Ether'));

  /**
   * Initializes NativeSwapManager on the Ethereum chain.
   * @param _validatorRegistry The address of the validator registry contract
   */
  constructor(
    address _validatorRegistry
  ) ValidatorManager(_validatorRegistry) {}

  ////////////////////////////////////////////////////////////////////////////
  // Liquidity Operations
  ////////////////////////////////////////////////////////////////////////////

  /**
   * Registers an asset in the portal ecosystem.
   * @param asset The asset being registered in the portal ecosystem
   */
  function registerAsset(Asset calldata asset) external onlyFoundation {
    address assetAddress = toAddress(asset.contractAddress);
    if (_assetIdsByAddress[assetAddress] != bytes32(0))
      revert AssetAlreadyRegistered(asset.id);
    if (keccak256(bytes(asset.chain)) != ETHEREUM_HASH)
      revert InvalidChain(asset.chain);

    bytes32 assetSymbolHash = keccak256(bytes(asset.symbol));
    bytes32 assetNameHash = keccak256(bytes(asset.name));
    if (assetAddress == NATIVE_ADDRESS) {
      if (
        assetSymbolHash != ETH_SYMBOL_HASH ||
        assetNameHash != ETH_NAME_HASH ||
        asset.decimals != 18
      ) revert InvalidEthAsset();
    } else {
      ERC20 token = ERC20(assetAddress);
      if (
        keccak256(bytes(token.symbol())) != assetSymbolHash ||
        keccak256(bytes(token.name())) != assetNameHash ||
        token.decimals() != asset.decimals
      ) revert MismatchedTokenMetadata();
    }

    _assetIdsByAddress[assetAddress] = asset.id;
    _assetAddressesById[asset.id] = assetAddress;
    emit AssetRegistered(asset);
  }
}
NativeLiquidityManager.sol 152 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.25;
pragma abicoder v2;

import './Shared.sol';
import { ValidatorManager } from './ValidatorManager.sol';
import { NativeAssetManager } from './NativeAssetManager.sol';
import { IInvoiceManager } from './interfaces/IInvoiceManager.sol';
import { ERC20 } from '../lib/solmate/src/tokens/ERC20.sol';
import { SafeTransferLib } from '../lib/solmate/src/utils/SafeTransferLib.sol';

/**
 * @title NativeLiquidityManager
 * @notice Manages liquidity for native assets within the portal ecosystem.
 * @custom:security-contact [email protected]
 */
contract NativeLiquidityManager is IInvoiceManager, ValidatorManager {
  using SafeTransferLib for ERC20;

  /**
   * @dev The address of the native asset manager contract.
   */
  NativeAssetManager internal assetManager;

  /**
   * Initializes NativeLiquidityManager on the Ethereum chain.
   * @param _validator The address of the validator contract
   * @param _assetManager The address of the asset manager contract
   */
  constructor(
    address _validator,
    address _assetManager
  ) ValidatorManager(_validator) {
    if (_assetManager == address(0)) revert InvalidAssetManager();
    assetManager = NativeAssetManager(_assetManager);
  }

  /**
   * Fallback function to receive ETH deposits.
   */
  receive() external payable {
    revert MustCallDepositFunction(msg.value, msg.sender);
  }

  ////////////////////////////////////////////////////////////////////////////
  // Liquidity Operations
  ////////////////////////////////////////////////////////////////////////////

  /**
   * Deposits an amount of an asset into the contract.
   * @param assetAddress The address of the asset contract; 0x0 for ETH
   * @param nativeAmount The amount of the deposit
   * @param portalAddress The address of the depositor on the portal chain
   */
  function deposit(
    address assetAddress,
    uint256 nativeAmount,
    address portalAddress
  ) external payable notValidator {
    if (portalAddress == address(0)) revert InvalidPortalAddress();
    if (nativeAmount == 0) revert ZeroAmount();
    if (assetManager._assetIdsByAddress(assetAddress) == bytes32(0))
      revert InvalidAssetAddress();

    if (assetAddress == NATIVE_ADDRESS) {
      if (msg.value != nativeAmount)
        revert InvalidEthDeposit(msg.value, nativeAmount);
    } else {
      ERC20(assetAddress).safeTransferFrom(
        msg.sender,
        address(this),
        nativeAmount
      );
    }

    emit Deposit({
      ts: block.timestamp,
      chain: 'ethereum',
      symbol: assetAddress == NATIVE_ADDRESS
        ? 'ETH'
        : ERC20(assetAddress).symbol(),
      contractAddress: assetAddress,
      nativeAmount: nativeAmount,
      nativeAddress: msg.sender,
      portalAddress: portalAddress
    });
  }

  /**
   * Withdraws an amount of an asset from the contract
   * @param id The unique identifier of the withdrawal (liquidity)
   * @param ts The timestamp of the asset burning on the portal chain
   * @param assetAddress The address of the asset contract; 0x0 for ETH
   * @param nativeAmount The amount of the withdrawal
   * @param nativeAddress The address of the recipient on the native chain
   * @param portalAddress The address of the recipient on the portal chain
   */
  function withdraw(
    bytes32 id,
    uint256 ts,
    address assetAddress,
    int256 nativeAmount,
    address nativeAddress,
    address portalAddress
  ) external onlyValidator {
    if (nativeAmount == 0) revert ZeroAmount();
    if (portalAddress == address(0)) revert InvalidPortalAddress();
    if (nativeAddress == NATIVE_ADDRESS) revert InvalidNativeAddress();
    if (assetManager._assetIdsByAddress(assetAddress) == bytes32(0))
      revert InvalidAssetAddress();

    if (nativeAmount == type(int256).min) {
      revert AmountOverflow();
    }
    uint256 withdrawAmount = uint256(-nativeAmount);

    // Check contract solvency: ensure contract has enough balance to fulfill withdrawal
    // Portal chain is the authoritative source of truth for user balances
    uint256 contractBalance = assetAddress == NATIVE_ADDRESS
      ? address(this).balance
      : ERC20(assetAddress).balanceOf(address(this));

    if (withdrawAmount > contractBalance)
      revert InsufficientContractBalance(
        assetAddress,
        contractBalance,
        withdrawAmount
      );

    emit Withdraw({
      id: id,
      ts: ts,
      chain: 'ethereum',
      symbol: assetAddress == NATIVE_ADDRESS
        ? 'ETH'
        : ERC20(assetAddress).symbol(),
      contractAddress: assetAddress,
      nativeAmount: nativeAmount,
      nativeAddress: nativeAddress,
      portalAddress: portalAddress
    });

    if (assetAddress == NATIVE_ADDRESS) {
      (bool success, ) = payable(nativeAddress).call{ value: withdrawAmount }(
        ''
      );
      if (!success) revert EthTransferFailed(nativeAddress, withdrawAmount);
    } else {
      ERC20(assetAddress).safeTransfer(nativeAddress, withdrawAmount);
    }
  }
}
Shared.sol 190 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.25;
pragma abicoder v2;

////////////////////////////////////////////////////////////////////////////////
// Constants
////////////////////////////////////////////////////////////////////////////////

address constant NATIVE_ADDRESS = address(0);

////////////////////////////////////////////////////////////////////////////////
// Structs
////////////////////////////////////////////////////////////////////////////////

/**
 * Stores the particulars of an asset.
 * @param id SHA-256 hash of the chain name and the asset symbol
 * @param decimals The number of decimal places the asset supports
 * @param enabled Whether the asset is enabled for trading
 * @param chain The name of the native chain of the asset
 * @param symbol Ticker symbol of the asset
 * @param name The human-readable name of the asset
 * @param contractAddress The address of the asset on the native chain
 */
struct Asset {
  bytes32 id;
  uint8 decimals;
  bool enabled;
  string name;
  string chain;
  string symbol;
  string contractAddress;
}

/**
 * Store the particulars of liquidity entering/leaving the Portal ecosystem.
 * @param id The unique identifier of the liquidity
 * @param ts The timestamp at which the liquidity was created
 * @param chain The name of the native chain of the asset
 * @param symbol The ticker symbol of the asset
 * @param nativeAmount The amount of the asset on the native chain
 * @param portalAmount The amount of the asset on the portal chain
 * @param nativeAddress The address of the owner on the native chain
 * @param portalAddress The address of the owner on the portal chain
 */
struct Liquidity {
  bytes32 id;
  uint256 ts;
  int256 nativeAmount;
  int256 portalAmount;
  address portalAddress;
  string chain;
  string symbol;
  string contractAddress;
  string nativeAddress;
}

/**
 * Enumerates the states of a swap.
 * @param MATCHED The swap has been matched
 * @param HOLDER_INVOICED The holder has created and registed their invoice
 * @param SEEKER_INVOICED The seeker has created and registed their invoice
 * @param HOLDER_PAID The holder has paid the seeker's invoice
 * @param SEEKER_PAID The seeker has paid the holder's invoice
 * @param HOLDER_SETTLED The holder has revealed the secret to settle the swap
 * @param SEEKER_SETTLED The seeker has used the secret to settle the swap
 * @param REFUNDED The swap has been refunded
 */
enum SwapState {
  MATCHED,
  HOLDER_INVOICED,
  SEEKER_INVOICED,
  HOLDER_PAID,
  SEEKER_PAID,
  HOLDER_SETTLED,
  SEEKER_SETTLED,
  REFUNDED
}

/**
 * Store the particulars of a swap between two parties.
 * @param id The unique identifier of the swap
 * @param state The state of the swap
 * @param secretHash The hash of the secret that will unlock the swap
 * @param secretHolder The secret holder of the swap
 * @param secretSeeker The secret seeker of the swap
 * @param holderTimeout Absolute timestamp when holder's HTLC expires
 * @param seekerTimeout Absolute timestamp when seeker's HTLC expires
 * @param createdAt The timestamp at which the swap was created
 * @param holderTimeoutBlock Block height when holder's HTLC expires on native chain
 * @param seekerTimeoutBlock Block height when seeker's HTLC expires on native chain
 */
struct Swap {
  bytes32 id;
  SwapState state;
  bytes32 secretHash;
  Party secretHolder;
  Party secretSeeker;
  uint256 holderTimeout;
  uint256 seekerTimeout;
  uint256 createdAt;
  uint256 holderTimeoutBlock;
  uint256 seekerTimeoutBlock;
}

/**
 * Stores the particulars of a party to a swap.
 * @param portalAddress The address of the party on the portal chain
 * @param amount The amount of the asset
 * @param chain The name of the native chain of the asset
 * @param symbol The ticker symbol of the asset
 * @param contractAddress The address of the asset on the native chain
 * @param invoice The invoice for the swap
 * @param receipt The receipt for the swap
 * @param isTrader Whether the party is a trader (true) or a user (false)
 */
struct Party {
  address portalAddress;
  uint256 amount;
  string chain;
  string symbol;
  string contractAddress;
  string invoice;
  string receipt;
  bool isTrader;
}

////////////////////////////////////////////////////////////////////////////////
// Functions
////////////////////////////////////////////////////////////////////////////////

function toHexString(bytes32 data) pure returns (string memory) {
  bytes memory hexChars = '0123456789abcdef';
  bytes memory str = new bytes(64); // bytes32 → 64 hex characters

  for (uint256 i = 0; i < 32; i++) {
    uint8 byteVal = uint8(data[i]);
    str[i * 2] = hexChars[byteVal >> 4]; // First hex character
    str[i * 2 + 1] = hexChars[byteVal & 0x0f]; // Second hex character
  }
  return string(abi.encodePacked('0x', str));
}

/**
 * Convert a hex string into an address
 * @param _hexString The hex string to convert to an address
 */
function toAddress(string memory _hexString) pure returns (address) {
  require(bytes(_hexString).length == 42, 'Invalid address length');
  bytes memory hexBytes = bytes(_hexString);
  uint160 addr = 0;
  for (uint i = 2; i < 42; i++) {
    uint8 b = uint8(hexBytes[i]);
    if (b >= 48 && b <= 57) {
      b = b - 48; // 0-9
    } else if (b >= 65 && b <= 70) {
      b = b - 55; // A-F
    } else if (b >= 97 && b <= 102) {
      b = b - 87; // a-f
    } else {
      revert('Invalid character in address');
    }
    addr = (addr << 4) | uint160(b);
  }
  return address(addr);
}

/**
 * Convert a hex string into a 32-byte hash
 * @param _hexString The hex string to convert to a bytes32
 */
function toBytes32(string memory _hexString) pure returns (bytes32) {
  require(bytes(_hexString).length == 66, 'Invalid hex string length');
  bytes memory hexBytes = bytes(_hexString);
  bytes32 result;
  for (uint i = 2; i < 66; i++) {
    uint8 b = uint8(hexBytes[i]);
    if (b >= 48 && b <= 57) {
      b = b - 48; // 0-9
    } else if (b >= 65 && b <= 70) {
      b = b - 55; // A-F
    } else if (b >= 97 && b <= 102) {
      b = b - 87; // a-f
    } else {
      revert('Invalid character in hex string');
    }
    result = (result << 4) | bytes32(uint256(b)); // Correct type conversion
  }
  return result;
}
ValidatorManager.sol 77 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.25;
pragma abicoder v2;

import './interfaces/IValidatorManager.sol';
import './interfaces/IValidatorRegistry.sol';

/**
 * @title ValidatorManager
 * @notice Abstract contract providing validator authentication via registry
 * @dev Provides onlyValidator modifier by querying external ValidatorRegistry
 */
abstract contract ValidatorManager is IValidatorManager {
  /**
   * @notice Reference to the validator registry
   */
  IValidatorRegistry public immutable validatorRegistry;

  /**
   * @notice Initialize with validator registry address
   * @param _validatorRegistry Address of ValidatorRegistry contract
   */
  constructor(address _validatorRegistry) {
    validatorRegistry = IValidatorRegistry(_validatorRegistry);
  }

  /**
   * @notice Modifier to restrict function access to validator only
   */
  modifier onlyValidator() {
    if (!validatorRegistry.isValidator(msg.sender)) {
      revert CallerIsNotValidator(msg.sender);
    }
    _;
  }

  /**
   * @notice Modifier to restrict function access to non  validators
   */
  modifier notValidator() {
    if (validatorRegistry.isValidator(msg.sender)) {
      revert CallerIsValidator(msg.sender);
    }
    _;
  }

  /**
   * @notice Modifier to restrict function access to foundation only
   */
  modifier onlyFoundation() {
    if (msg.sender != validatorRegistry.foundation()) {
      revert CallerIsNotFoundation(msg.sender);
    }
    _;
  }

  /**
   * @notice Modifier to restrict function access to validator or foundation
   */
  modifier onlyValidatorOrFoundation() {
    if (
      !validatorRegistry.isValidator(msg.sender) &&
      msg.sender != validatorRegistry.foundation()
    ) {
      revert NotFoundationOrValidator(msg.sender);
    }
    _;
  }

  /**
   * @notice Get current validator address
   * @return address Current validator from registry
   */
  function validator() public view returns (address) {
    return validatorRegistry.validator();
  }
}
IInvoiceManager.sol 181 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.25;
pragma abicoder v2;
import '../Shared.sol';

/**
 * @title IInvoiceManager
 * Interface for the IInvoiceManager contract that provides the basic deposit and withdraw functionalities
 * @custom:security-contact [email protected]
 */
interface IInvoiceManager {
  //////////////////////////////////////////////////////////////////////////////
  // Types
  //////////////////////////////////////////////////////////////////////////////

  /**
   * Defines an invoice associated with a swap
   * @param
   */
  struct Invoice {
    bool initialized;
    bytes32 secretHash;
    address nativeAddress;
    address assetAddress;
    uint256 amount;
  }

  struct LockedFunds {
    address payer;
    address assetAddress;
    uint256 amount;
    uint256 timeout;
    bool recovered;
  }

  //////////////////////////////////////////////////////////////////////////////
  // Events
  //////////////////////////////////////////////////////////////////////////////

  /**
   * Event emitted when an asset is registered.
   * @param asset The asset being registered
   */
  event AssetRegistered(Asset asset);

  /**
   * Event emitted when a deposit is made.
   * @param ts The time the deposit was created
   * @param chain The name of the native chain of the asset
   * @param symbol Ticker symbol of the asset
   * @param nativeAmount The amount of the deposit
   * @param nativeAddress The depositor's address on the native chain
   * @param portalAddress The depositor's address on the portal chain
   */
  event Deposit(
    uint256 ts,
    string chain,
    string symbol,
    address contractAddress,
    uint256 nativeAmount,
    address indexed nativeAddress,
    address indexed portalAddress
  );

  /**
   * Event emitted when a withdrawal is made.
   * @param chain The name of the native chain of the asset
   * @param symbol The ticker symbol of the asset
   * @param nativeAmount The amount of the withdrawal
   * @param nativeAddress The withdrawer's address on the native chain
   * @param portalAddress The withdrawer's address on the portal chain
   */
  event Withdraw(
    bytes32 id,
    uint256 ts,
    string chain,
    string symbol,
    address contractAddress,
    int256 nativeAmount,
    address indexed nativeAddress,
    address indexed portalAddress
  );

  /**
   * Event emitted when an invoice is created.
   * @param swap The particulars of the swap
   */
  event SwapInvoiceCreated(Swap swap);

  /**
   * Event emitted when the holder pays an invoice.
   * @param id The unique identifier of the swap
   */
  event SwapHolderPaid(bytes32 id);

  /**
   * Event emitted when the seeker pays an invoice.
   * @param id The unique identifier of the swap
   */
  event SwapSeekerPaid(bytes32 id);

  /**
   * Event emitted when the holder settles an invoice.
   * @param id The unique identifier of the swap
   * @param secret The secret corresponding to the secret hash of the swap
   */
  event SwapHolderSettled(bytes32 id, bytes secret);

  /**
   * Event emitted when the seeker settles an invoice.
   * @param id The unique identifier of the swap
   */
  event SwapSeekerSettled(bytes32 id);

  event FundsRecovered(
    bytes32 indexed swapId,
    address indexed recipient,
    address assetAddress,
    uint256 amount
  );

  /**
   * Event emitted when a swap is fully purged from storage
   * @param swapId The ID of the swap
   * @param invoiceId The ID of the invoice
   */
  event SwapStorageCleaned(bytes32 indexed swapId, bytes32 indexed invoiceId);

  // Errors
  error InvalidAssetManager();

  error AssetAlreadyRegistered(bytes32 assetId);
  error InvalidChain(string chain);
  error InvalidEthAsset();
  error MismatchedTokenMetadata();

  error InvalidPortalAddress();
  error ZeroAmount();
  error InvalidAssetAddress();
  error InvalidEthDeposit(uint256 _value, uint256 _amount);

  error InvalidNativeAddress();
  error InsufficientContractBalance(
    address asset,
    uint256 contractBalance,
    uint256 requested
  );

  error SwapNotOnEthereum();
  error SwapNotCrossChain();
  error InvalidChainConfiguration();
  error ZeroInvoiceAmount();

  error InvalidInvoiceId();
  error InvalidSecretHash();
  error InvalidInvoiceAmount(uint256 amountSent, uint256 amountExpected);
  error InvalidSwapState();
  error SwapExpired(bytes32 swapId);

  error NotPayee(address caller);
  error NotPayer(address caller);
  error InvalidSecret();

  error InvoiceDeadlinePassed(uint256 deadline, uint256 currentTime);
  error PaymentDeadlinePassed(uint256 deadline, uint256 currentTime);
  error SettlementDeadlinePassed(uint256 deadline, uint256 currentTime);
  error NotOriginalPayer();
  error NoLockedFunds();
  error FundsAlreadyRecovered();
  error AlreadySettled();
  error NoTimeoutYet(uint256 recoveryTime, uint256 currentTime);

  error SwapNotSettled();
  error LengthMismatch();
  error BatchTooLarge();

  error MustCallDepositFunction(uint256 valueSent, address sender);
  error AmountOverflow();
  error EthTransferFailed(address recipient, uint256 amount);
  error DirectTransferNotAllowed();
}
IValidatorManager.sol 15 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.25;
pragma abicoder v2;

/**
 * @title IValidatorManager
 * @dev Interface for the ValidatorManager contract that defines functions for managing validators.
 * @custom:security-contact [email protected]
 */
interface IValidatorManager {
  error CallerIsNotValidator(address _caller);
  error CallerIsValidator(address _validator);
  error CallerIsNotFoundation(address _caller);
  error NotFoundationOrValidator(address caller);
}
IValidatorRegistry.sol 48 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.25;

/**
 * @title IValidatorRegistry
 * @notice Interface for the ValidatorRegistry contract
 * @dev Provides validator authentication for all system contracts
 */
interface IValidatorRegistry {
  /**
   * @notice Check if an address is the current validator
   * @param account Address to check
   * @return bool True if address is the current validator
   */
  function isValidator(address account) external view returns (bool);

  /**
   * @notice Get the current validator address
   * @return address Current validator
   */
  function validator() external view returns (address);

  /**
   * @notice Get the current foundation address
   * @return address current foundation
   */
  function foundation() external view returns (address);

  /**
   * @notice Emitted when validator is changed
   * @param oldValidator Previous validator address
   * @param newValidator New validator address
   */
  event ValidatorChanged(
    address indexed oldValidator,
    address indexed newValidator
  );

  /**
   * @notice Thrown when zero address is provided
   */
  error ZeroAddress();

  /**
   * @notice Thrown when caller is not authorized
   */
  error Unauthorized();
}
ERC20.sol 206 lines
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event Transfer(address indexed from, address indexed to, uint256 amount);

    event Approval(address indexed owner, address indexed spender, uint256 amount);

    /*//////////////////////////////////////////////////////////////
                            METADATA STORAGE
    //////////////////////////////////////////////////////////////*/

    string public name;

    string public symbol;

    uint8 public immutable decimals;

    /*//////////////////////////////////////////////////////////////
                              ERC20 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 public totalSupply;

    mapping(address => uint256) public balanceOf;

    mapping(address => mapping(address => uint256)) public allowance;

    /*//////////////////////////////////////////////////////////////
                            EIP-2612 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 internal immutable INITIAL_CHAIN_ID;

    bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;

    mapping(address => uint256) public nonces;

    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor(
        string memory _name,
        string memory _symbol,
        uint8 _decimals
    ) {
        name = _name;
        symbol = _symbol;
        decimals = _decimals;

        INITIAL_CHAIN_ID = block.chainid;
        INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
    }

    /*//////////////////////////////////////////////////////////////
                               ERC20 LOGIC
    //////////////////////////////////////////////////////////////*/

    function approve(address spender, uint256 amount) public virtual returns (bool) {
        allowance[msg.sender][spender] = amount;

        emit Approval(msg.sender, spender, amount);

        return true;
    }

    function transfer(address to, uint256 amount) public virtual returns (bool) {
        balanceOf[msg.sender] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(msg.sender, to, amount);

        return true;
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual returns (bool) {
        uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.

        if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;

        balanceOf[from] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(from, to, amount);

        return true;
    }

    /*//////////////////////////////////////////////////////////////
                             EIP-2612 LOGIC
    //////////////////////////////////////////////////////////////*/

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");

        // Unchecked because the only math done is incrementing
        // the owner's nonce which cannot realistically overflow.
        unchecked {
            address recoveredAddress = ecrecover(
                keccak256(
                    abi.encodePacked(
                        "\x19\x01",
                        DOMAIN_SEPARATOR(),
                        keccak256(
                            abi.encode(
                                keccak256(
                                    "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
                                ),
                                owner,
                                spender,
                                value,
                                nonces[owner]++,
                                deadline
                            )
                        )
                    )
                ),
                v,
                r,
                s
            );

            require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");

            allowance[recoveredAddress][spender] = value;
        }

        emit Approval(owner, spender, value);
    }

    function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
        return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
    }

    function computeDomainSeparator() internal view virtual returns (bytes32) {
        return
            keccak256(
                abi.encode(
                    keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                    keccak256(bytes(name)),
                    keccak256("1"),
                    block.chainid,
                    address(this)
                )
            );
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(address to, uint256 amount) internal virtual {
        totalSupply += amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(address(0), to, amount);
    }

    function _burn(address from, uint256 amount) internal virtual {
        balanceOf[from] -= amount;

        // Cannot underflow because a user's balance
        // will never be larger than the total supply.
        unchecked {
            totalSupply -= amount;
        }

        emit Transfer(from, address(0), amount);
    }
}
SafeTransferLib.sol 124 lines
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

import {ERC20} from "../tokens/ERC20.sol";

/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
library SafeTransferLib {
    /*//////////////////////////////////////////////////////////////
                             ETH OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferETH(address to, uint256 amount) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Transfer the ETH and store if it succeeded or not.
            success := call(gas(), to, amount, 0, 0, 0, 0)
        }

        require(success, "ETH_TRANSFER_FAILED");
    }

    /*//////////////////////////////////////////////////////////////
                            ERC20 OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferFrom(
        ERC20 token,
        address from,
        address to,
        uint256 amount
    ) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from" argument.
            mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
            mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.

            // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
            // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
            success := call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)

            // Set success to whether the call reverted, if not we check it either
            // returned exactly 1 (can't just be non-zero data), or had no return data and token has code.
            if and(iszero(and(eq(mload(0), 1), gt(returndatasize(), 31))), success) {
                success := iszero(or(iszero(extcodesize(token)), returndatasize())) 
            }
        }

        require(success, "TRANSFER_FROM_FAILED");
    }

    function safeTransfer(
        ERC20 token,
        address to,
        uint256 amount
    ) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.

            // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
            // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
            success := call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)

            // Set success to whether the call reverted, if not we check it either
            // returned exactly 1 (can't just be non-zero data), or had no return data and token has code.
            if and(iszero(and(eq(mload(0), 1), gt(returndatasize(), 31))), success) {
                success := iszero(or(iszero(extcodesize(token)), returndatasize())) 
            }
        }

        require(success, "TRANSFER_FAILED");
    }

    function safeApprove(
        ERC20 token,
        address to,
        uint256 amount
    ) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.

            // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
            // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
            success := call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)

            // Set success to whether the call reverted, if not we check it either
            // returned exactly 1 (can't just be non-zero data), or had no return data and token has code.
            if and(iszero(and(eq(mload(0), 1), gt(returndatasize(), 31))), success) {
                success := iszero(or(iszero(extcodesize(token)), returndatasize())) 
            }
        }

        require(success, "APPROVE_FAILED");
    }
}

Read Contract

validator 0x3a5381b5 → address
validatorRegistry 0xf376ebbb → address

Write Contract 2 functions

These functions modify contract state and require a wallet transaction to execute.

deposit 0xf45346dc
address assetAddress
uint256 nativeAmount
address portalAddress
withdraw 0x636daeda
bytes32 id
uint256 ts
address assetAddress
int256 nativeAmount
address nativeAddress
address portalAddress

Recent Transactions

No transactions found for this address