BNB Price: $695.27 (-2.07%)
Gas: 1 GWei
 

Overview

BNB Balance

BNB Smart Chain LogoBNB Smart Chain LogoBNB Smart Chain Logo0 BNB

BNB Value

$0.00

Token Holdings

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Execute Trade453253092024-12-30 11:11:1014 secs ago1735557070IN
0x1Dd22350...C409c0820
0 BNB0.000278021
Execute Trade453253092024-12-30 11:11:1014 secs ago1735557070IN
0x1Dd22350...C409c0820
0 BNB0.000382221
Deposit Token By...453252462024-12-30 11:08:013 mins ago1735556881IN
0x1Dd22350...C409c0820
0 BNB0.000390932.02
Execute Trade453252282024-12-30 11:07:074 mins ago1735556827IN
0x1Dd22350...C409c0820
0 BNB0.000278031
Execute Trade453252182024-12-30 11:06:374 mins ago1735556797IN
0x1Dd22350...C409c0820
0 BNB0.000294841
Execute Trade453251982024-12-30 11:05:375 mins ago1735556737IN
0x1Dd22350...C409c0820
0 BNB0.000293631
Withdraw453251882024-12-30 11:05:076 mins ago1735556707IN
0x1Dd22350...C409c0820
0 BNB0.00014861
Withdraw453251172024-12-30 11:01:349 mins ago1735556494IN
0x1Dd22350...C409c0820
0 BNB0.000161941
Deposit Token By...453251012024-12-30 11:00:4610 mins ago1735556446IN
0x1Dd22350...C409c0820
0 BNB0.000385282.02
Execute Trade453250562024-12-30 10:58:3112 mins ago1735556311IN
0x1Dd22350...C409c0820
0 BNB0.000385211
Execute Trade453250562024-12-30 10:58:3112 mins ago1735556311IN
0x1Dd22350...C409c0820
0 BNB0.000466761
Execute Trade453250362024-12-30 10:57:3113 mins ago1735556251IN
0x1Dd22350...C409c0820
0 BNB0.000295751
Withdraw453250172024-12-30 10:56:3414 mins ago1735556194IN
0x1Dd22350...C409c0820
0 BNB0.000144831
Withdraw453249972024-12-30 10:55:3415 mins ago1735556134IN
0x1Dd22350...C409c0820
0 BNB0.000164541
Execute Trade453249252024-12-30 10:51:5819 mins ago1735555918IN
0x1Dd22350...C409c0820
0 BNB0.000293111
Execute Trade453249252024-12-30 10:51:5819 mins ago1735555918IN
0x1Dd22350...C409c0820
0 BNB0.000382241
Deposit Token By...453249222024-12-30 10:51:4919 mins ago1735555909IN
0x1Dd22350...C409c0820
0 BNB0.000192721
Execute Trade453248952024-12-30 10:50:2820 mins ago1735555828IN
0x1Dd22350...C409c0820
0 BNB0.000293681
Execute Trade453248952024-12-30 10:50:2820 mins ago1735555828IN
0x1Dd22350...C409c0820
0 BNB0.000277741
Withdraw453248372024-12-30 10:47:3423 mins ago1735555654IN
0x1Dd22350...C409c0820
0 BNB0.000144861
Execute Trade453248042024-12-30 10:45:5525 mins ago1735555555IN
0x1Dd22350...C409c0820
0 BNB0.000378541
Deposit Token By...453247742024-12-30 10:44:2526 mins ago1735555465IN
0x1Dd22350...C409c0820
0 BNB0.000219892.02
Deposit Token By...453247402024-12-30 10:42:4328 mins ago1735555363IN
0x1Dd22350...C409c0820
0 BNB0.000381462
Execute Trade453247032024-12-30 10:40:5230 mins ago1735555252IN
0x1Dd22350...C409c0820
0 BNB0.000291641
Execute Trade453247032024-12-30 10:40:5230 mins ago1735555252IN
0x1Dd22350...C409c0820
0 BNB0.000380031
View all transactions

Latest 25 internal transactions (View All)

Parent Transaction Hash Block From To
452916662024-12-29 7:08:5228 hrs ago1735456132
0x1Dd22350...C409c0820
0.04 BNB
448224352024-12-13 0:04:0417 days ago1734048244
0x1Dd22350...C409c0820
0.001 BNB
446011832024-12-05 7:39:3625 days ago1733384376
0x1Dd22350...C409c0820
0.02 BNB
445434122024-12-03 7:30:5927 days ago1733211059
0x1Dd22350...C409c0820
0.02 BNB
445433442024-12-03 7:27:3527 days ago1733210855
0x1Dd22350...C409c0820
0.0025261 BNB
444825482024-12-01 4:47:3529 days ago1733028455
0x1Dd22350...C409c0820
0.016 BNB
442179542024-11-22 0:16:5138 days ago1732234611
0x1Dd22350...C409c0820
0.01 BNB
439201092024-11-11 16:04:1048 days ago1731341050
0x1Dd22350...C409c0820
0.002 BNB
437125802024-11-04 10:55:4556 days ago1730717745
0x1Dd22350...C409c0820
0.002 BNB
436756342024-11-03 4:07:5757 days ago1730606877
0x1Dd22350...C409c0820
0.005 BNB
436135462024-11-01 0:23:3259 days ago1730420612
0x1Dd22350...C409c0820
0.22 BNB
433938232024-10-24 9:17:0667 days ago1729761426
0x1Dd22350...C409c0820
0.1 BNB
433892652024-10-24 5:29:1167 days ago1729747751
0x1Dd22350...C409c0820
0.003 BNB
433887842024-10-24 5:05:0867 days ago1729746308
0x1Dd22350...C409c0820
0.0002 BNB
433664462024-10-23 10:28:0968 days ago1729679289
0x1Dd22350...C409c0820
0.002 BNB
432543822024-10-19 13:04:1571 days ago1729343055
0x1Dd22350...C409c0820
0.6 BNB
431233082024-10-14 23:50:0276 days ago1728949802
0x1Dd22350...C409c0820
0.03 BNB
431227842024-10-14 23:23:5076 days ago1728948230
0x1Dd22350...C409c0820
0.8 BNB
429867932024-10-10 6:03:5381 days ago1728540233
0x1Dd22350...C409c0820
0.01 BNB
428819562024-10-06 14:41:5684 days ago1728225716
0x1Dd22350...C409c0820
0.08 BNB
428796702024-10-06 12:47:3884 days ago1728218858
0x1Dd22350...C409c0820
0.003 BNB
428524582024-10-05 14:06:5285 days ago1728137212
0x1Dd22350...C409c0820
0.03 BNB
428030392024-10-03 20:55:4187 days ago1727988941
0x1Dd22350...C409c0820
0.03 BNB
427968142024-10-03 15:44:2687 days ago1727970266
0x1Dd22350...C409c0820
0.009 BNB
427668552024-10-02 14:46:1988 days ago1727880379
0x1Dd22350...C409c0820
0.3 BNB
View All Internal Transactions
Loading...
Loading

Similar Match Source Code
This contract matches the deployed Bytecode of the Source Code for Contract 0x9881513a...2970b42Da
The constructor portion of the code might be different and could alter the actual behaviour of the contract

Contract Name:
Exchange

Compiler Version
v0.8.4+commit.c7e474f2

Optimization Enabled:
Yes with 13 runs

Other Settings:
default evmVersion
File 1 of 13 : Exchange.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity ^0.8.0;

import { Address } from './libraries/Address.sol';
import {
  SafeMath as SafeMath256
} from './libraries/SafeMath.sol';

import { AssetRegistry } from './libraries/AssetRegistry.sol';
import { AssetTransfers } from './libraries/AssetTransfers.sol';
import { AssetUnitConversions } from './libraries/AssetUnitConversions.sol';
import { Owned } from './Owned.sol';
import { SafeMath64 } from './libraries/SafeMath64.sol';
import { Signatures } from './libraries/Signatures.sol';
import {
  Enums,
  ICustodian,
  IBEP20,
  IExchange,
  Structs
} from './libraries/Interfaces.sol';
import { UUID } from './libraries/UUID.sol';
import { IterableMapping } from './libraries/IterableMapping.sol';
// import 'hardhat/console.sol';


/**
 * @notice The Exchange contract. Implements all deposit, trade, and withdrawal logic and associated balance tracking
 *
 * @dev The term `asset` refers collectively to BNB and BEP-20 tokens, the term `token` refers only to the latter
 * @dev Events with indexed string parameters (Deposited and TradeExecuted) only log the hash values for those
 * parameters, from which the original raw string values cannot be retrieved. For convenience these events contain
 * the un-indexed string parameter values in addition to the indexed values
 */
contract Exchange is IExchange, Owned {
  using SafeMath64 for uint64;
  using SafeMath256 for uint256;
  using AssetRegistry for AssetRegistry.Storage;
  using IterableMapping for IterableMapping.Map;

  // Events //

  /**
   * @notice Emitted when an admin changes the Chain Propagation Period tunable parameter with `setChainPropagationPeriod`
   */
  event ChainPropagationPeriodChanged(uint256 previousValue, uint256 newValue);
  /**
   * @notice Emitted when a user deposits BNB with `depositBNB` or a token with `depositAsset` or `depositAssetBySymbol`
   */
  event Deposited(
    uint64 index,
    address indexed wallet,
    address indexed assetAddress,
    string indexed assetSymbolIndex,
    string assetSymbol,
    uint64 quantityInPips,
    uint64 newExchangeBalanceInPips,
    uint256 newExchangeBalanceInAssetUnits
  );
  /**
   * @notice Emitted when an admin changes the Dispatch Wallet tunable parameter with `setDispatcher`
   */
  event AddDispatcher(address newValue);
  event RemoveDispatcher(address value);

  /**
   * @notice Emitted when an admin changes the Fee Wallet tunable parameter with `setFeeWallet`
   */
  event FeeWalletChanged(address previousValue, address newValue);

  /**
   * @notice Emitted when a user invalidates an order nonce with `invalidateOrderNonce`
   */
  event OrderNonceInvalidated(
    address indexed wallet,
    uint128 nonce,
    uint128 timestampInMs,
    uint256 effectiveBlockNumber
  );
  /**
   * @notice Emitted when an admin initiates the token registration process with `registerToken`
   */
  event TokenRegistered(
    IBEP20 indexed assetAddress,
    string assetSymbol,
    uint8 decimals
  );
  /**
   * @notice Emitted when an admin finalizes the token registration process with `confirmAssetRegistration`, after
   * which it can be deposited, traded, or withdrawn
   */
  event TokenRegistrationConfirmed(
    IBEP20 indexed assetAddress,
    string assetSymbol,
    uint8 decimals
  );
  /**
   * @notice Emitted when an admin adds a symbol to a previously registered and confirmed token
   * via `addTokenSymbol`
   */
  event TokenSymbolAdded(IBEP20 indexed assetAddress, string assetSymbol);
  /**
   * @notice Emitted when the Dispatcher Wallet submits a trade for execution with `executeTrade`
   */
  event TradeExecuted(
    address buyWallet,
    address sellWallet,
    string indexed baseAssetSymbolIndex,
    string indexed quoteAssetSymbolIndex,
    string baseAssetSymbol,
    string quoteAssetSymbol,
    uint64 baseQuantityInPips,
    uint64 quoteQuantityInPips,
    uint64 tradePriceInPips,
    bytes32 buyOrderHash,
    bytes32 sellOrderHash
  );
  /**
   * @notice Emitted when the Dispatcher Wallet submits trades for execution with `executeTrades`
   */
  // event TradesExecuted(Structs.ExecRet[] vals);
  /**
   * @notice Emitted when a user invokes the Exit Wallet mechanism with `exitWallet`
   */
  event WalletExited(address indexed wallet, uint256 effectiveBlockNumber);
  /**
   * @notice Emitted when a user withdraws an asset balance through the Exit Wallet mechanism with `withdrawExit`
   */
  event WalletExitWithdrawn(
    address indexed wallet,
    address indexed assetAddress,
    string assetSymbol,
    uint64 quantityInPips,
    uint64 newExchangeBalanceInPips,
    uint256 newExchangeBalanceInAssetUnits
  );
  /**
   * @notice Emitted when a user clears the exited status of a wallet previously exited with `exitWallet`
   */
  event WalletExitCleared(address indexed wallet);
  /**
   * @notice Emitted when the Dispatcher Wallet submits a withdrawal with `withdraw`
   */
  event Withdrawn(
    address indexed wallet,
    address indexed assetAddress,
    string assetSymbol,
    uint64 quantityInPips,
    uint64 newExchangeBalanceInPips,
    uint256 newExchangeBalanceInAssetUnits
  );

  // Internally used structs //

  struct NonceInvalidation {
    bool exists;
    uint64 timestampInMs;
    uint256 effectiveBlockNumber;
  }

  struct WalletExit {
    bool exists;
    uint256 effectiveBlockNumber;
  }

  // Storage //

  // Asset registry data
  AssetRegistry.Storage _assetRegistry;
  // Mapping of order wallet hash => isComplete
  mapping(bytes32 => bool) _completedOrderHashes;
  // Mapping of withdrawal wallet hash => isComplete
  mapping(bytes32 => bool) _completedWithdrawalHashes;
  address payable _custodian;
  uint64 _depositIndex;
  // Mapping of wallet => IterableMapping.Map
  mapping(address => IterableMapping.Map) _balancesInPips;
  // Mapping of wallet => last invalidated timestampInMs
  mapping(address => NonceInvalidation) _nonceInvalidations;
  // Mapping of order hash => filled quantity in pips
  mapping(bytes32 => uint64) _partiallyFilledOrderQuantitiesInPips;
  mapping(address => WalletExit) _walletExits;
  // Tunable parameters
  uint256 _chainPropagationPeriod;
  mapping (address => bool) _dispatcherWallet;
  address _feeWallet;

  // Constant values //

  uint256 constant _maxChainPropagationPeriod = 201600; //(7 * 24 * 60 * 60) / 3; 1 week at 3s/block
  uint64 constant _maxTradeFeeBasisPoints = 2000; // 20 * 100; 20%;
  uint64 constant _maxWithdrawalFeeBasisPoints = 2000; // 20 * 100; // 20%;

  /**
   * @notice Instantiate a new `Exchange` contract
   *
   * @dev Sets `_owner` and `_admin` to `msg.sender` */
  constructor() Owned() {}

  /**
   * @notice Sets the address of the `Custodian` contract
   *
   * @dev The `Custodian` accepts `Exchange` and `Governance` addresses in its constructor, after
   * which they can only be changed by the `Governance` contract itself. Therefore the `Custodian`
   * must be deployed last and its address set here on an existing `Exchange` contract. This value
   * is immutable once set and cannot be changed again
   *
   * @param newCustodian The address of the `Custodian` contract deployed against this `Exchange`
   * contract's address
   */
  function setCustodian(address payable newCustodian) external onlyAdmin {
    require(_custodian == address(0x0), 'already set');
    require(Address.isContract(newCustodian), 'Invalid address');

    _custodian = newCustodian;
  }

  /*** Tunable parameters ***/

  /**
   * @notice Sets a new Chain Propagation Period - the block delay after which order nonce invalidations
   * are respected by `executeTrade` and wallet exits are respected by `executeTrade` and `withdraw`
   *
   * @param newChainPropagationPeriod The new Chain Propagation Period expressed as a number of blocks. Must
   * be less than `_maxChainPropagationPeriod`
   */
  function setChainPropagationPeriod(uint256 newChainPropagationPeriod)
    external
    onlyAdmin
  {
    require(
      newChainPropagationPeriod < _maxChainPropagationPeriod,
      'Bigger than 1 week'
    );

    uint256 oldChainPropagationPeriod = _chainPropagationPeriod;
    _chainPropagationPeriod = newChainPropagationPeriod;

    emit ChainPropagationPeriodChanged(
      oldChainPropagationPeriod,
      newChainPropagationPeriod
    );
  }

  /**
   * @notice Sets the address of the Fee wallet
   *
   * @dev Trade and Withdraw fees will accrue in the `_balancesInPips` mappings for this wallet
   *
   * @param newFeeWallet The new Fee wallet. Must be different from the current one
   */
  function setFeeWallet(address newFeeWallet) external onlyAdmin {
    require(newFeeWallet != address(0x0), 'Invalid address');
    require(
      newFeeWallet != _feeWallet,
      'Same fee wallet'
    );

    address oldFeeWallet = _feeWallet;
    _feeWallet = newFeeWallet;

    emit FeeWalletChanged(oldFeeWallet, newFeeWallet);
  }

  // Accessors //

  /**
   * @notice Load a wallet's balance by asset address, in asset units
   *
   * @param wallet The wallet address to load the balance for. Can be different from `msg.sender`
   * @param assetAddress The asset address to load the wallet's balance for
   *
   * @return The quantity denominated in asset units of asset at `assetAddress` currently
   * deposited by `wallet`
   */
  function loadBalanceInAssetUnitsByAddress(
    address wallet,
    address assetAddress
  ) external view returns (uint256) {
    require(wallet != address(0x0), 'Invalid address');

    Structs.Asset memory asset = _assetRegistry.loadAssetByAddress(
      assetAddress
    );
    return
      AssetUnitConversions.pipsToAssetUnits(
        _balancesInPips[wallet].get(assetAddress),
        asset.decimals
      );
  }

  /**
   * @notice Load a wallet's balance by asset address, in asset units
   *
   * @param wallet The wallet address to load the balance for. Can be different from `msg.sender`
   * @param assetSymbol The asset symbol to load the wallet's balance for
   *
   * @return The quantity denominated in asset units of asset `assetSymbol` currently deposited
   * by `wallet`
   */
  function loadBalanceInAssetUnitsBySymbol(
    address wallet,
    string calldata assetSymbol
  ) external view returns (uint256) {
    require(wallet != address(0x0), 'Invalid address');

    Structs.Asset memory asset = _assetRegistry.loadAssetBySymbol(
      assetSymbol,
      getCurrentTimestampInMs()
    );
    return
      AssetUnitConversions.pipsToAssetUnits(
        _balancesInPips[wallet].get(asset.assetAddress),
        asset.decimals
      );
  }

  /**
   * @notice Load a wallet's balance by asset address, in pips
   *
   * @param wallet The wallet address to load the balance for. Can be different from `msg.sender`
   * @param assetAddress The asset address to load the wallet's balance for
   *
   * @return The quantity denominated in pips of asset at `assetAddress` currently deposited by `wallet`
   */
  function loadBalanceInPipsByAddress(address wallet, address assetAddress)
    external
    view
    returns (uint64)
  {
    require(wallet != address(0x0), 'Invalid address');

    return _balancesInPips[wallet].get(assetAddress);
  }

  /**
   * @notice Load a wallet's balance by asset symbol, in pips
   *
   * @param wallet The wallet address to load the balance for. Can be different from `msg.sender`
   * @param assetSymbol The asset symbol to load the wallet's balance for
   *
   * @return The quantity denominated in pips of asset with `assetSymbol` currently deposited by `wallet`
   */
  function loadBalanceInPipsBySymbol(
    address wallet,
    string calldata assetSymbol
  ) external view returns (uint64) {
    require(wallet != address(0x0), 'Invalid address');

    address assetAddress = _assetRegistry
      .loadAssetBySymbol(assetSymbol, getCurrentTimestampInMs())
      .assetAddress;
    return _balancesInPips[wallet].get(assetAddress);
  }

  /**
   * @notice Load a wallet's asset address of balance
   *
   * @param wallet The wallet address to load the balance asset address for.
   *
   * @return All assets address of a wallet's balance
   */
  function loadBalanceAssetAddress(address wallet) external view returns (address[] memory) {
    address[] memory assetsAddress = new address[](_balancesInPips[wallet].size());
    for (uint i = 0; i < _balancesInPips[wallet].size(); i++) {
        address key = _balancesInPips[wallet].getKeyAtIndex(i);
        assetsAddress[i] = key;
    }
    return assetsAddress;
  }

  /**
   * @notice Load the address of the Fee wallet
   *
   * @return The address of the Fee wallet
   */
  function loadFeeWallet() external view returns (address) {
    return _feeWallet;
  }

  /**
   * @notice Load the quantity filled so far for a partially filled orders

   * @dev Invalidating an order nonce will not clear partial fill quantities for earlier orders because
   * the gas cost would potentially be unbound
   *
   * @param orderHash The order hash as originally signed by placing wallet that uniquely identifies an order
   *
   * @return For partially filled orders, the amount filled so far in pips. For orders in all other states, 0
   */
  function loadPartiallyFilledOrderQuantityInPips(bytes32 orderHash)
    external
    view
    returns (uint64)
  {
    return _partiallyFilledOrderQuantitiesInPips[orderHash];
  }

  // Depositing //

  /**
   * @notice Deposit BNB
   */
  function depositBNB() external payable {
    require(msg.value%uint256(10000000000)==0, "dust value");
    deposit(msg.sender, address(0x0), msg.value);
  }

  /**
   * @notice Deposit `IBEP20` compliant tokens
   *
   * @param tokenAddress The token contract address
   * @param quantityInAssetUnits The quantity to deposit. The sending wallet must first call the `approve` method on
   * the token contract for at least this quantity first
   */
  function depositTokenByAddress(
    IBEP20 tokenAddress,
    uint256 quantityInAssetUnits
  ) external {
    require(
      address(tokenAddress) != address(0x0),
      'Invalid address'
    );
    deposit(msg.sender, address(tokenAddress), quantityInAssetUnits);
  }

  /**
   * @notice Deposit `IBEP20` compliant tokens
   *
   * @param assetSymbol The case-sensitive symbol string for the token
   * @param quantityInAssetUnits The quantity to deposit. The sending wallet must first call the `approve` method on
   * the token contract for at least this quantity first
   */
  function depositTokenBySymbol(
    string calldata assetSymbol,
    uint256 quantityInAssetUnits
  ) external {
    IBEP20 tokenAddress = IBEP20(
      _assetRegistry
        .loadAssetBySymbol(assetSymbol, getCurrentTimestampInMs())
        .assetAddress
    );
    require(
      address(tokenAddress) != address(0x0),
      'Invalid address'
    );

    deposit(msg.sender, address(tokenAddress), quantityInAssetUnits);
  }

  function deposit(
    address wallet,
    address assetAddress,
    uint256 quantityInAssetUnits
  ) private {
    // Calling exitWallet disables deposits immediately on mining, in contrast to withdrawals and
    // trades which respect the Chain Propagation Period given by `effectiveBlockNumber` via
    // `isWalletExitFinalized`
    require(!_walletExits[wallet].exists, 'Wallet exited');

    Structs.Asset memory asset = _assetRegistry.loadAssetByAddress(
      assetAddress
    );
    uint64 quantityInPips = AssetUnitConversions.assetUnitsToPips(
      quantityInAssetUnits,
      asset.decimals
    );
    require(quantityInPips > 0, 'Quantity is too low');

    // Convert from pips back into asset units to remove any fractional amount that is too small
    // to express in pips. If the asset is BNB, this leftover fractional amount accumulates as dust
    // in the `Exchange` contract. If the asset is a token the `Exchange` will call `transferFrom`
    // without this fractional amount and there will be no dust
    uint256 quantityInAssetUnitsWithoutFractionalPips = AssetUnitConversions
      .pipsToAssetUnits(quantityInPips, asset.decimals);

    // If the asset is BNB then the funds were already assigned to this contract via msg.value. If
    // the asset is a token, additionally call the transferFrom function on the token contract for
    // the pre-approved asset quantity
    if (assetAddress != address(0x0)) {
      AssetTransfers.transferFrom(
        wallet,
        IBEP20(assetAddress),
        quantityInAssetUnitsWithoutFractionalPips
      );
    }
    // Forward the funds to the `Custodian`
    AssetTransfers.transferTo(
      _custodian,
      assetAddress,
      quantityInAssetUnitsWithoutFractionalPips
    );

    uint64 newExchangeBalanceInPips = _balancesInPips[wallet].get(assetAddress).add(
      quantityInPips
    );
    uint256 newExchangeBalanceInAssetUnits = AssetUnitConversions
      .pipsToAssetUnits(newExchangeBalanceInPips, asset.decimals);

    // Update balance with actual transferred quantity
    _balancesInPips[wallet].set(assetAddress, newExchangeBalanceInPips);
    _depositIndex++;

    emit Deposited(
      _depositIndex,
      wallet,
      assetAddress,
      asset.symbol,
      asset.symbol,
      quantityInPips,
      newExchangeBalanceInPips,
      newExchangeBalanceInAssetUnits
    );
  }

  // Invalidation //

  /**
   * @notice Invalidate all order nonces with a timestampInMs lower than the one provided
   *
   * @param nonce A Version 1 UUID. After calling and once the Chain Propagation Period has elapsed,
   * `executeTrade` will reject order nonces from this wallet with a timestampInMs component lower than
   * the one provided
   */
  function invalidateOrderNonce(uint128 nonce) external {
    uint64 timestampInMs = UUID.getTimestampInMsFromUuidV1(nonce);
    // Enforce a maximum skew for invalidating nonce timestamps in the future so the user doesn't
    // lock their wallet from trades indefinitely
    require(
      timestampInMs < getOneDayFromNowInMs(),
      'Nonce too far in future'
    );

    if (_nonceInvalidations[msg.sender].exists) {
      require(
        _nonceInvalidations[msg.sender].timestampInMs < timestampInMs,
        'Invalidated'
      );
      require(
        _nonceInvalidations[msg.sender].effectiveBlockNumber <= block.number,
        'Awaiting propagation'
      );
    }

    // Changing the Chain Propagation Period will not affect the effectiveBlockNumber for this invalidation
    uint256 effectiveBlockNumber = block.number + _chainPropagationPeriod;
    _nonceInvalidations[msg.sender] = NonceInvalidation(
      true,
      timestampInMs,
      effectiveBlockNumber
    );

    emit OrderNonceInvalidated(
      msg.sender,
      nonce,
      timestampInMs,
      effectiveBlockNumber
    );
  }

  // Withdrawing //

  /**
   * @notice Settles a user withdrawal submitted off-chain. Calls restricted to currently whitelisted Dispatcher wallet
   *
   * @param withdrawal A `Structs.Withdrawal` struct encoding the parameters of the withdrawal
   */
  function withdraw(Structs.Withdrawal memory withdrawal)
    public
    override
    onlyDispatcher
  {
    // Validations
    require(!isWalletExitFinalized(withdrawal.walletAddress), 'Wallet exited');
    require(
      getFeeBasisPoints(withdrawal.gasFeeInPips, withdrawal.quantityInPips) <=
        _maxWithdrawalFeeBasisPoints,
      'Excessive fee'
    );
    bytes32 withdrawalHash = validateWithdrawalSignature(withdrawal);
    require(
      !_completedWithdrawalHashes[withdrawalHash],
      'Withdrawn'
    );

    // If withdrawal is by asset symbol (most common) then resolve to asset address
    Structs.Asset memory asset = withdrawal.withdrawalType ==
      Enums.WithdrawalType.BySymbol
      ? _assetRegistry.loadAssetBySymbol(
        withdrawal.assetSymbol,
        UUID.getTimestampInMsFromUuidV1(withdrawal.nonce)
      )
      : _assetRegistry.loadAssetByAddress(withdrawal.assetAddress);

    // SafeMath reverts if balance is overdrawn
    uint64 netAssetQuantityInPips = withdrawal.quantityInPips.sub(
      withdrawal.gasFeeInPips
    );
    uint256 netAssetQuantityInAssetUnits = AssetUnitConversions
      .pipsToAssetUnits(netAssetQuantityInPips, asset.decimals);
    uint64 newExchangeBalanceInPips = _balancesInPips[withdrawal
      .walletAddress].get(asset.assetAddress)
      .sub(withdrawal.quantityInPips);
    uint256 newExchangeBalanceInAssetUnits = AssetUnitConversions
      .pipsToAssetUnits(newExchangeBalanceInPips, asset.decimals);

    if (newExchangeBalanceInPips==0) {
      _balancesInPips[withdrawal.walletAddress].remove(asset
      .assetAddress);
    } else {
      _balancesInPips[withdrawal.walletAddress].set(asset
      .assetAddress, newExchangeBalanceInPips);
    }
    _balancesInPips[_feeWallet].set(asset
      .assetAddress, _balancesInPips[_feeWallet].get(asset.assetAddress).add(
      withdrawal.gasFeeInPips
    ));

    ICustodian(_custodian).withdraw(
      withdrawal.walletAddress,
      asset.assetAddress,
      netAssetQuantityInAssetUnits
    );

    _completedWithdrawalHashes[withdrawalHash] = true;

    emit Withdrawn(
      withdrawal.walletAddress,
      asset.assetAddress,
      asset.symbol,
      withdrawal.quantityInPips,
      newExchangeBalanceInPips,
      newExchangeBalanceInAssetUnits
    );
  }

  // Wallet exits //

  /**
   * @notice Flags the sending wallet as exited, immediately disabling deposits upon mining.
   * After the Chain Propagation Period passes trades and withdrawals are also disabled for the wallet,
   * and assets may then be withdrawn one at a time via `withdrawExit`
   */
  function exitWallet() external {
    require(!_walletExits[msg.sender].exists, 'Wallet exited');

    _walletExits[msg.sender] = WalletExit(
      true,
      block.number + _chainPropagationPeriod
    );

    emit WalletExited(msg.sender, block.number + _chainPropagationPeriod);
  }

  /**
   * @notice Withdraw the entire balance of an asset for an exited wallet. The Chain Propagation Period must
   * have already passed since calling `exitWallet` on `assetAddress`
   *
   * @param assetAddress The address of the asset to withdraw
   */
  function withdrawExit(address assetAddress) external {
    require(isWalletExitFinalized(msg.sender), 'Wallet not finalized');

    Structs.Asset memory asset = _assetRegistry.loadAssetByAddress(
      assetAddress
    );
    uint64 balanceInPips = _balancesInPips[msg.sender].get(assetAddress);
    uint256 balanceInAssetUnits = AssetUnitConversions.pipsToAssetUnits(
      balanceInPips,
      asset.decimals
    );

    require(balanceInAssetUnits > 0, 'Balance is 0');
    _balancesInPips[msg.sender].remove(assetAddress);
    ICustodian(_custodian).withdraw(
      payable(msg.sender),
      assetAddress,
      balanceInAssetUnits
    );

    emit WalletExitWithdrawn(
      msg.sender,
      assetAddress,
      asset.symbol,
      balanceInPips,
      0,
      0
    );
  }

  /**
   * @notice Withdraw the entire balance of all assets for an exited wallet. The Chain Propagation Period must
   * have already passed since calling `exitWallet`
   */
  function withdrawAllExit() external {
    require(isWalletExitFinalized(msg.sender), 'Wallet not finalized');
    
    address[] memory assetsAddress = new address[](_balancesInPips[msg.sender].size());
    for (uint i = 0; i < _balancesInPips[msg.sender].size(); i++) {
      address assetAddress = _balancesInPips[msg.sender].getKeyAtIndex(i);
      assetsAddress[i] = assetAddress;
      Structs.Asset memory asset = _assetRegistry.loadAssetByAddress(
        assetAddress
      );
      uint64 balanceInPips = _balancesInPips[msg.sender].get(assetAddress);
      uint256 balanceInAssetUnits = AssetUnitConversions.pipsToAssetUnits(
        balanceInPips,
        asset.decimals
      );
      if (balanceInAssetUnits == 0) {
        continue;
      }
      ICustodian(_custodian).withdraw(
        payable(msg.sender),
        assetAddress,
        balanceInAssetUnits
      );

      emit WalletExitWithdrawn(
        msg.sender,
        assetAddress,
        asset.symbol,
        balanceInPips,
        0,
        0
      );
    }
    for (uint i = 0; i < assetsAddress.length; i++) {
        _balancesInPips[msg.sender].remove(assetsAddress[i]);
    }
  }

  /**
   * @notice Clears exited status of sending wallet. Upon mining immediately enables
   * deposits, trades, and withdrawals by sending wallet
   */
  function clearWalletExit() external {
    require(_walletExits[msg.sender].exists, 'Wallet not exited');

    delete _walletExits[msg.sender];

    emit WalletExitCleared(msg.sender);
  }

  /**
   * @notice Check if the wallet is exited, regardless of whether it is finalized
   *
   * @param wallet The address of the wallet to check
   *
   * @return The exited status of wallet
   */
  function isWalletExit(address wallet) public view returns (bool) {
    WalletExit storage exit = _walletExits[wallet];
    return exit.exists;
  }

  /**
   * @notice Check if the wallet is exited and finalized
   *
   * @param wallet The address of the wallet to check
   *
   * @return The exited and finalized status of wallet
   */
  function isWalletExitFinalized(address wallet) public view returns (bool) {
    WalletExit storage exit = _walletExits[wallet];
    return exit.exists && exit.effectiveBlockNumber <= block.number;
  }

  // Trades //

  /**
   * @notice Execute multiple trades
   *
   * @param buys A `Structs.Order` struct array encoding the parameters of the buy-side orders (receiving base, giving quote)
   * @param sells A `Structs.Order` struct array encoding the parameters of the sell-side order (giving base, receiving quote)
   * @param trades A `Structs.Trade` struct array encoding the parameters of this trade execution of the counterparty orders
   */
  function executeTrades(
    Structs.Order[] memory buys,
    Structs.Order[] memory sells,
    Structs.Trade[] memory trades
  ) external override onlyDispatcher {
    require(
      buys.length==sells.length && sells.length==trades.length,
      "length mismatch"
    );
    require(
      buys.length!=0,
      "length is zero"
    );
    // Structs.ExecRet[] memory rets = new Structs.ExecRet[](buys.length);
    // for (uint i = 0; i < buys.length; i++) {
    //   (bool success, bytes memory data) = address(this).delegatecall(
    //     abi.encodeWithSignature("executeTrade((uint8,uint128,address,uint8,uint8,uint64,bool,uint64,uint64,string,uint8,uint8,uint64,bytes),(uint8,uint128,address,uint8,uint8,uint64,bool,uint64,uint64,string,uint8,uint8,uint64,bytes),(string,string,address,address,uint64,uint64,uint64,uint64,address,address,uint64,uint64,uint64,uint8))", buys[i], sells[i], trades[i])
    //   );
    //   if (success) {
    //     rets[i] = Structs.ExecRet(true, "");
    //   } else {
    //     rets[i] = Structs.ExecRet(false, extractRevertReason(data));
    //   }
    // }
    // emit TradesExecuted(rets);
    for (uint256 i = 0; i < buys.length; i++) {
      // bytes32 orderHash = Signatures.getOrderWalletHash(
      //   buys[i],
      //   trades[i].baseAssetSymbol,
      //   trades[i].quoteAssetSymbol
      // );
      // console.log("contractHash:");
      // console.logBytes32(orderHash);
      // bytes32 orderHash2 = Signatures.getOrderWalletHash(
      //   sells[i],
      //   trades[i].baseAssetSymbol,
      //   trades[i].quoteAssetSymbol
      // );
      // console.log("contractHash2:");
      // console.logBytes32(orderHash2);
      _executeTrade(buys[i], sells[i], trades[i]);
    }
  }

  // function extractRevertReason (bytes memory revertData) internal pure returns (string memory reason) {
  //     uint l = revertData.length;
  //     if (l < 68) return "Silently reverted";
  //     uint t;
  //     assembly {
  //         revertData := add (revertData, 4)
  //         t := mload (revertData) // Save the content of the length slot
  //         mstore (revertData, sub (l, 4)) // Set proper length
  //     }
  //     reason = abi.decode (revertData, (string));
  //     assembly {
  //         mstore (revertData, t) // Restore the content of the length slot
  //     }
  // }
  /**
   * @notice Settles a trade between two orders submitted and matched off-chain
   *
   * @dev As a gas optimization, base and quote symbols are passed in separately and combined to verify
   * the wallet hash, since this is cheaper than splitting the market symbol into its two constituent asset symbols
   * @dev Stack level too deep if declared external
   *
   * @param buy A `Structs.Order` struct encoding the parameters of the buy-side order (receiving base, giving quote)
   * @param sell A `Structs.Order` struct encoding the parameters of the sell-side order (giving base, receiving quote)
   * @param trade A `Structs.Trade` struct encoding the parameters of this trade execution of the counterparty orders
   */
  function executeTrade(
    Structs.Order memory buy,
    Structs.Order memory sell,
    Structs.Trade memory trade
  ) public override onlyDispatcher {
    _executeTrade(buy, sell, trade);
  }

  // Updates buyer, seller, and fee wallet balances for both assets in trade pair according to trade parameters
  function updateBalancesForTrade(
    Structs.Order memory buy,
    Structs.Order memory sell,
    Structs.Trade memory trade
  ) private {
    // Seller gives base asset including fees
    uint64 sellerBaseQuantity = _balancesInPips[sell.walletAddress].get(trade
      .baseAssetAddress)
      .sub(trade.grossBaseQuantityInPips);
    if (sellerBaseQuantity > 0) {
      _balancesInPips[sell.walletAddress].set(trade
      .baseAssetAddress, sellerBaseQuantity);
    } else {
      _balancesInPips[sell.walletAddress].remove(trade
      .baseAssetAddress);
    }
    // Buyer receives base asset minus fees
    uint64 buyerBaseQuantity = _balancesInPips[buy.walletAddress].get(trade
      .baseAssetAddress)
      .add(trade.netBaseQuantityInPips);
    if (buyerBaseQuantity > 0) {
      _balancesInPips[buy.walletAddress].set(trade
      .baseAssetAddress, buyerBaseQuantity);
    } else {
      _balancesInPips[buy.walletAddress].remove(trade
      .baseAssetAddress);
    }

    // Buyer gives quote asset including fees
    uint64 buyerQuoteQuantity = _balancesInPips[buy.walletAddress].get(trade
      .quoteAssetAddress)
      .sub(trade.grossQuoteQuantityInPips);
    if (buyerQuoteQuantity > 0) {
      _balancesInPips[buy.walletAddress].set(trade
      .quoteAssetAddress, buyerQuoteQuantity);
    } else {
       _balancesInPips[buy.walletAddress].remove(trade
      .quoteAssetAddress);
    }
    // Seller receives quote asset minus fees
    uint64 sellerQuoteQuantity = _balancesInPips[sell.walletAddress].get(trade
      .quoteAssetAddress)
      .add(trade.netQuoteQuantityInPips);
    if (sellerQuoteQuantity > 0) {
      _balancesInPips[sell.walletAddress].set(trade
      .quoteAssetAddress, sellerQuoteQuantity);
    } else {
      _balancesInPips[sell.walletAddress].remove(trade
      .quoteAssetAddress);
    }

    // Maker and taker fees to fee wallet
    uint64 makerFeeQuantity = _balancesInPips[_feeWallet].get(trade
      .makerFeeAssetAddress)
      .add(trade.makerFeeQuantityInPips);
    if (makerFeeQuantity > 0) {
      _balancesInPips[_feeWallet].set(trade
      .makerFeeAssetAddress, makerFeeQuantity);
    } else {
      _balancesInPips[_feeWallet].remove(trade
      .makerFeeAssetAddress);
    }
    uint64 takerFeeQuantity = _balancesInPips[_feeWallet].get(trade
      .takerFeeAssetAddress)
      .add(trade.takerFeeQuantityInPips);
    if (takerFeeQuantity > 0) {
      _balancesInPips[_feeWallet].set(trade
      .takerFeeAssetAddress, takerFeeQuantity);
    } else {
      _balancesInPips[_feeWallet].remove(trade
      .takerFeeAssetAddress);
    }
  }

  function updateOrderFilledQuantities(
    Structs.Order memory buyOrder,
    bytes32 buyOrderHash,
    Structs.Order memory sellOrder,
    bytes32 sellOrderHash,
    Structs.Trade memory trade
  ) private {
    updateOrderFilledQuantity(buyOrder, buyOrderHash, trade);
    updateOrderFilledQuantity(sellOrder, sellOrderHash, trade);
  }

  // Update filled quantities tracking for order to prevent over- or double-filling orders
  function updateOrderFilledQuantity(
    Structs.Order memory order,
    bytes32 orderHash,
    Structs.Trade memory trade
  ) private {
    require(!_completedOrderHashes[orderHash], 'Double filled');

    // Total quantity of above filled as a result of all trade executions, including this one
    uint64 newFilledQuantityInPips;

    // Market orders can express quantity in quote terms, and can be partially filled by multiple
    // limit maker orders necessitating tracking partially filled amounts in quote terms to
    // determine completion
    if (order.isQuantityInQuote) {
      require(
        isMarketOrderType(order.orderType),
        'Not market orders'
      );
      newFilledQuantityInPips = trade.grossQuoteQuantityInPips.add(
        _partiallyFilledOrderQuantitiesInPips[orderHash]
      );
    } else {
      // All other orders track partially filled quantities in base terms
      newFilledQuantityInPips = trade.grossBaseQuantityInPips.add(
        _partiallyFilledOrderQuantitiesInPips[orderHash]
      );
    }

    require(
      newFilledQuantityInPips <= order.quantityInPips,
      'Overfilled'
    );

    if (newFilledQuantityInPips < order.quantityInPips) {
      // If the order was partially filled, track the new filled quantity
      _partiallyFilledOrderQuantitiesInPips[orderHash] = newFilledQuantityInPips;
    } else {
      // If the order was completed, delete any partial fill tracking and instead track its completion
      // to prevent future double fills
      delete _partiallyFilledOrderQuantitiesInPips[orderHash];
      _completedOrderHashes[orderHash] = true;
    }
  }

  // Validations //

  function validateAssetPair(
    Structs.Order memory buy,
    Structs.Order memory sell,
    Structs.Trade memory trade
  ) private view {
    require(
      trade.baseAssetAddress != trade.quoteAssetAddress,
      'Base/quote assets must be different'
    );

    // Buy order market pair
    Structs.Asset memory buyBaseAsset = _assetRegistry.loadAssetBySymbol(
      trade.baseAssetSymbol,
      UUID.getTimestampInMsFromUuidV1(buy.nonce)
    );
    Structs.Asset memory buyQuoteAsset = _assetRegistry.loadAssetBySymbol(
      trade.quoteAssetSymbol,
      UUID.getTimestampInMsFromUuidV1(buy.nonce)
    );
    require(
      buyBaseAsset.assetAddress == trade.baseAssetAddress &&
        buyQuoteAsset.assetAddress == trade.quoteAssetAddress,
      'Buy order symbol mismatch'
    );

    // Sell order market pair
    Structs.Asset memory sellBaseAsset = _assetRegistry.loadAssetBySymbol(
      trade.baseAssetSymbol,
      UUID.getTimestampInMsFromUuidV1(sell.nonce)
    );
    Structs.Asset memory sellQuoteAsset = _assetRegistry.loadAssetBySymbol(
      trade.quoteAssetSymbol,
      UUID.getTimestampInMsFromUuidV1(sell.nonce)
    );
    require(
      sellBaseAsset.assetAddress == trade.baseAssetAddress &&
        sellQuoteAsset.assetAddress == trade.quoteAssetAddress,
      'Sell order symbol mismatch'
    );

    // Fee asset validation
    require(
      trade.makerFeeAssetAddress == trade.baseAssetAddress ||
        trade.makerFeeAssetAddress == trade.quoteAssetAddress,
      'Maker fee asset is not in trade pair'
    );
    require(
      trade.takerFeeAssetAddress == trade.baseAssetAddress ||
        trade.takerFeeAssetAddress == trade.quoteAssetAddress,
      'Taker fee asset is not in trade pair'
    );
    require(
      trade.makerFeeAssetAddress != trade.takerFeeAssetAddress,
      'Maker/taker fee assets must be different'
    );
  }

  function validateLimitPrices(
    Structs.Order memory buy,
    Structs.Order memory sell,
    Structs.Trade memory trade
  ) private pure {
    require(
      trade.grossBaseQuantityInPips > 0,
      'Base quantity is 0'
    );
    require(
      trade.grossQuoteQuantityInPips > 0,
      'Quote quantity is 0'
    );

    if (isLimitOrderType(buy.orderType)) {
      require(
        getImpliedQuoteQuantityInPips(
          trade.grossBaseQuantityInPips,
          buy.limitPriceInPips
        ) >= trade.grossQuoteQuantityInPips,
        'Buy limit price exceeded'
      );
    }

    if (isLimitOrderType(sell.orderType)) {
      require(
        getImpliedQuoteQuantityInPips(
          trade.grossBaseQuantityInPips,
          sell.limitPriceInPips
        ) <= trade.grossQuoteQuantityInPips,
        'Sell limit price exceeded'
      );
    }
  }

  function validateTradeFees(Structs.Trade memory trade) private pure {
    uint64 makerTotalQuantityInPips = trade.makerFeeAssetAddress ==
      trade.baseAssetAddress
      ? trade.grossBaseQuantityInPips
      : trade.grossQuoteQuantityInPips;
    require(
      getFeeBasisPoints(
        trade.makerFeeQuantityInPips,
        makerTotalQuantityInPips
      ) <= _maxTradeFeeBasisPoints,
      'Excessive maker fee'
    );

    uint64 takerTotalQuantityInPips = trade.takerFeeAssetAddress ==
      trade.baseAssetAddress
      ? trade.grossBaseQuantityInPips
      : trade.grossQuoteQuantityInPips;
    require(
      getFeeBasisPoints(
        trade.takerFeeQuantityInPips,
        takerTotalQuantityInPips
      ) <= _maxTradeFeeBasisPoints,
      'Excessive taker fee'
    );

    require(
      trade.netBaseQuantityInPips.add(
        trade.makerFeeAssetAddress == trade.baseAssetAddress
          ? trade.makerFeeQuantityInPips
          : trade.takerFeeQuantityInPips
      ) == trade.grossBaseQuantityInPips,
      'Base plus fee is not equal to gross'
    );
    require(
      trade.netQuoteQuantityInPips.add(
        trade.makerFeeAssetAddress == trade.quoteAssetAddress
          ? trade.makerFeeQuantityInPips
          : trade.takerFeeQuantityInPips
      ) == trade.grossQuoteQuantityInPips,
      'Quote plus fee is not equal to gross'
    );
  }

  function validateOrderSignatures(
    Structs.Order memory buy,
    Structs.Order memory sell,
    Structs.Trade memory trade
  ) private pure returns (bytes32, bytes32) {
    bytes32 buyOrderHash = validateOrderSignature(buy, trade);
    bytes32 sellOrderHash = validateOrderSignature(sell, trade);

    return (buyOrderHash, sellOrderHash);
  }

  function validateOrderSignature(
    Structs.Order memory order,
    Structs.Trade memory trade
  ) private pure returns (bytes32) {
    bytes32 orderHash = Signatures.getOrderWalletHash(
      order,
      trade.baseAssetSymbol,
      trade.quoteAssetSymbol
    );

    require(
      Signatures.isSignatureValid(
        orderHash,
        order.walletSignature,
        order.walletAddress
      ),
      order.side == Enums.OrderSide.Buy
        ? 'Invalid buy order signature'
        : 'Invalid sell order signature'
    );

    return orderHash;
  }

  function validateOrderNonces(
    Structs.Order memory buy,
    Structs.Order memory sell
  ) private view {
    require(
      UUID.getTimestampInMsFromUuidV1(buy.nonce) >
        getLastInvalidatedTimestamp(buy.walletAddress),
      'Buy order nonce too low'
    );
    require(
      UUID.getTimestampInMsFromUuidV1(sell.nonce) >
        getLastInvalidatedTimestamp(sell.walletAddress),
      'Sell order nonce too low'
    );
  }

  function validateWithdrawalSignature(Structs.Withdrawal memory withdrawal)
    private
    pure
    returns (bytes32)
  {
    bytes32 withdrawalHash = Signatures.getWithdrawalWalletHash(withdrawal);

    require(
      Signatures.isSignatureValid(
        withdrawalHash,
        withdrawal.walletSignature,
        withdrawal.walletAddress
      ),
      'Invalid signature'
    );

    return withdrawalHash;
  }

  // Asset registry //

  /**
   * @notice Initiate registration process for a token asset. Only `IBEP20` compliant tokens can be
   * added - BNB is hardcoded in the registry
   *
   * @param tokenAddress The address of the `IBEP20` compliant token contract to add
   * @param symbol The symbol identifying the token asset
   * @param decimals The decimal precision of the token
   */
  function registerToken(
    IBEP20 tokenAddress,
    string calldata symbol,
    uint8 decimals
  ) external onlyAdmin {
    _assetRegistry.registerToken(tokenAddress, symbol, decimals);
    emit TokenRegistered(tokenAddress, symbol, decimals);
  }

  /**
   * @notice Finalize registration process for a token asset. All parameters must exactly match a previous
   * call to `registerToken`
   *
   * @param tokenAddress The address of the `IBEP20` compliant token contract to add
   * @param symbol The symbol identifying the token asset
   * @param decimals The decimal precision of the token
   */
  function confirmTokenRegistration(
    IBEP20 tokenAddress,
    string calldata symbol,
    uint8 decimals
  ) external onlyAdmin {
    _assetRegistry.confirmTokenRegistration(tokenAddress, symbol, decimals);
    emit TokenRegistrationConfirmed(tokenAddress, symbol, decimals);
  }

  /**
   * @notice Add a symbol to a token that has already been registered and confirmed
   *
   * @param tokenAddress The address of the `IBEP20` compliant token contract the symbol will identify
   * @param symbol The symbol identifying the token asset
   */
  function addTokenSymbol(IBEP20 tokenAddress, string calldata symbol)
    external
    onlyAdmin
  {
    _assetRegistry.addTokenSymbol(tokenAddress, symbol);
    emit TokenSymbolAdded(tokenAddress, symbol);
  }

  /**
   * @notice Loads an asset descriptor struct by its symbol and timestamp
   *
   * @dev Since multiple token addresses can potentially share the same symbol (in case of a token
   * swap/contract upgrade) the provided `timestampInMs` is compared against each asset's
   * `confirmedTimestampInMs` to uniquely determine the newest asset for the symbol at that point in time
   *
   * @param assetSymbol The asset's symbol
   * @param timestampInMs Point in time used to disambiguate multiple tokens with same symbol
   *
   * @return A `Structs.Asset` record describing the asset
   */
  function loadAssetBySymbol(string calldata assetSymbol, uint64 timestampInMs)
    external
    view
    returns (Structs.Asset memory)
  {
    return _assetRegistry.loadAssetBySymbol(assetSymbol, timestampInMs);
  }

  // Dispatcher whitelisting //

  /**
   * @notice Sets the wallet whitelisted to dispatch transactions calling the `executeTrade` and `withdraw` functions
   *
   * @param newDispatcherWallet The new whitelisted dispatcher wallet. Must be different from the current one
   */
  function addDispatcher(address newDispatcherWallet) external onlyAdmin {
    require(newDispatcherWallet != address(0x0), 'Invalid address');
    require(
      !_dispatcherWallet[newDispatcherWallet],
      'Same address'
    );
    _dispatcherWallet[newDispatcherWallet] = true;

    emit AddDispatcher(newDispatcherWallet);
  }

  /**
   * @notice Clears the currently set whitelisted dispatcher wallet, effectively disabling calling the
   * `executeTrade` and `withdraw` functions until a new wallet is set with `setDispatcher`
   */
  function removeDispatcher(address account) external onlyAdmin {
    emit RemoveDispatcher(account);
    _dispatcherWallet[account] = false;
  }

  function isDispatcher(address account) external view  returns (bool) {
    return _dispatcherWallet[account];
  }

  modifier onlyDispatcher() {
    require(_dispatcherWallet[msg.sender], 'Must be dispatcher');
    _;
  }

  // Utils //

  function isLimitOrderType(Enums.OrderType orderType)
    private
    pure
    returns (bool)
  {
    return
      orderType == Enums.OrderType.Limit ||
      orderType == Enums.OrderType.LimitMaker ||
      orderType == Enums.OrderType.StopLossLimit ||
      orderType == Enums.OrderType.TakeProfitLimit;
  }

  function isMarketOrderType(Enums.OrderType orderType)
    private
    pure
    returns (bool)
  {
    return
      orderType == Enums.OrderType.Market ||
      orderType == Enums.OrderType.StopLoss ||
      orderType == Enums.OrderType.TakeProfit;
  }

  function getCurrentTimestampInMs() private view returns (uint64) {
    uint64 msInOneSecond = 1000;

    return uint64(block.timestamp) * msInOneSecond;
  }

  function getFeeBasisPoints(uint64 fee, uint64 total)
    private
    pure
    returns (uint64)
  {
    uint64 basisPointsInTotal = 100 * 100; // 100 basis points/percent * 100 percent/total
    return fee.mul(basisPointsInTotal).div(total);
  }

  function getImpliedQuoteQuantityInPips(
    uint64 baseQuantityInPips,
    uint64 limitPriceInPips
  ) private pure returns (uint64) {
    // To convert a fractional price to integer pips, shift right by the pip precision of 8 decimals
    uint256 pipsMultiplier = 10**8;

    uint256 impliedQuoteQuantityInPips = uint256(baseQuantityInPips)
      .mul(uint256(limitPriceInPips))
      .div(pipsMultiplier);
    require(
      impliedQuoteQuantityInPips < 2**64,
      'Implied quantity overflows'
    );

    return uint64(impliedQuoteQuantityInPips);
  }

  function getLastInvalidatedTimestamp(address walletAddress)
    private
    view
    returns (uint64)
  {
    if (
      _nonceInvalidations[walletAddress].exists &&
      _nonceInvalidations[walletAddress].effectiveBlockNumber <= block.number
    ) {
      return _nonceInvalidations[walletAddress].timestampInMs;
    }

    return 0;
  }

  function getOneDayFromNowInMs() private view returns (uint64) {
    uint64 secondsInOneDay = 24 * 60 * 60; // 24 hours/day * 60 min/hour * 60 seconds/min
    uint64 msInOneSecond = 1000;

    return (uint64(block.timestamp) + secondsInOneDay) * msInOneSecond;
  }

  function _executeTrade(
    Structs.Order memory buy,
    Structs.Order memory sell,
    Structs.Trade memory trade
  ) private {
    require(
      !isWalletExitFinalized(buy.walletAddress),
      'Buy wallet finalized'
    );
    require(
      !isWalletExitFinalized(sell.walletAddress),
      'Sell wallet finalized'
    );
    require(
      buy.walletAddress != sell.walletAddress,
      'Self-trading not allowed'
    );

    validateAssetPair(buy, sell, trade);
    validateLimitPrices(buy, sell, trade);
    validateOrderNonces(buy, sell);
    (bytes32 buyHash, bytes32 sellHash) = validateOrderSignatures(
      buy,
      sell,
      trade
    );
    validateTradeFees(trade);

    updateOrderFilledQuantities(buy, buyHash, sell, sellHash, trade);
    updateBalancesForTrade(buy, sell, trade);

    emit TradeExecuted(
      buy.walletAddress,
      sell.walletAddress,
      trade.baseAssetSymbol,
      trade.quoteAssetSymbol,
      trade.baseAssetSymbol,
      trade.quoteAssetSymbol,
      trade.grossBaseQuantityInPips,
      trade.grossQuoteQuantityInPips,
      trade.priceInPips,
      buyHash,
      sellHash
    );
  }
}

File 2 of 13 : Address.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize, which returns 0 for contracts in
        // construction, since the code is only stored at the end of the
        // constructor execution.

        uint256 size;
        assembly {
            size := extcodesize(account)
        }
        return size > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCall(target, data, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        require(isContract(target), "Address: call to non-contract");

        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        require(isContract(target), "Address: static call to non-contract");

        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(isContract(target), "Address: delegate call to non-contract");

        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly

                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}

File 3 of 13 : SafeMath.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

// CAUTION
// This version of SafeMath should only be used with Solidity 0.8 or later,
// because it relies on the compiler's built in overflow checks.

/**
 * @dev Wrappers over Solidity's arithmetic operations.
 *
 * NOTE: `SafeMath` is no longer needed starting with Solidity 0.8. The compiler
 * now has built in overflow checking.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the substraction of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     *
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        return a + b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return a - b;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     *
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        return a * b;
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator.
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return a / b;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return a % b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {trySub}.
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b <= a, errorMessage);
            return a - b;
        }
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a / b;
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting with custom message when dividing by zero.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryMod}.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a % b;
        }
    }
}

File 4 of 13 : AssetRegistry.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity ^0.8.0;

import { Address } from './Address.sol';

import { IBEP20, Structs } from './Interfaces.sol';


/**
 * @notice Library helper functions for managing a registry of asset descriptors indexed by address and symbol
 */
library AssetRegistry {
  struct Storage {
    mapping(address => Structs.Asset) assetsByAddress;
    // Mapping value is array since the same symbol can be re-used for a different address
    // (usually as a result of a token swap or upgrade)
    mapping(string => Structs.Asset[]) assetsBySymbol;
  }

  function registerToken(
    Storage storage self,
    IBEP20 tokenAddress,
    string memory symbol,
    uint8 decimals
  ) external {
    require(decimals <= 32, 'More than 32 decimals');
    require(
      tokenAddress != IBEP20(address(0x0)) && Address.isContract(address(tokenAddress)),
      'Invalid address'
    );
    // The string type does not have a length property so cast to bytes to check for empty string
    require(bytes(symbol).length > 0, 'Invalid symbol');
    require(
      !self.assetsByAddress[address(tokenAddress)].isConfirmed,
      'Aready finalized'
    );

    self.assetsByAddress[address(tokenAddress)] = Structs.Asset({
      exists: true,
      assetAddress: address(tokenAddress),
      symbol: symbol,
      decimals: decimals,
      isConfirmed: false,
      confirmedTimestampInMs: 0
    });
  }

  function confirmTokenRegistration(
    Storage storage self,
    IBEP20 tokenAddress,
    string memory symbol,
    uint8 decimals
  ) external {
    Structs.Asset memory asset = self.assetsByAddress[address(tokenAddress)];
    require(asset.exists, 'Unknown token');
    require(!asset.isConfirmed, 'Already finalized');
    require(isStringEqual(asset.symbol, symbol), 'Symbols do not match');
    require(asset.decimals == decimals, 'Decimals do not match');

    asset.isConfirmed = true;
    asset.confirmedTimestampInMs = uint64(block.timestamp * 1000); // Block timestamp is in seconds, store ms
    self.assetsByAddress[address(tokenAddress)] = asset;
    self.assetsBySymbol[symbol].push(asset);
  }

  function addTokenSymbol(
    Storage storage self,
    IBEP20 tokenAddress,
    string memory symbol
  ) external {
    Structs.Asset memory asset = self.assetsByAddress[address(tokenAddress)];
    require(
      asset.exists && asset.isConfirmed,
      'Registration not finalized'
    );
    require(!isStringEqual(symbol, 'BNB'), 'Reserved symbol');

    // This will prevent swapping assets for previously existing orders
    uint64 msInOneSecond = 1000;
    asset.confirmedTimestampInMs = uint64(block.timestamp * msInOneSecond);

    self.assetsBySymbol[symbol].push(asset);
  }

  /**
   * @dev Resolves an asset address into corresponding Asset struct
   *
   * @param assetAddress BNB address of asset
   */
  function loadAssetByAddress(Storage storage self, address assetAddress)
    external
    view
    returns (Structs.Asset memory)
  {
    if (assetAddress == address(0x0)) {
      return getBnbAsset();
    }

    Structs.Asset memory asset = self.assetsByAddress[assetAddress];
    require(
      asset.exists && asset.isConfirmed,
      'No confirmed asset found'
    );

    return asset;
  }

  /**
   * @dev Resolves a asset symbol into corresponding Asset struct
   *
   * @param symbol Asset symbol, e.g. 'IDEX'
   * @param timestampInMs Milliseconds since Unix epoch, usually parsed from a UUID v1 order nonce.
   * Constrains symbol resolution to the asset most recently confirmed prior to timestampInMs. Reverts
   * if no such asset exists
   */
  function loadAssetBySymbol(
    Storage storage self,
    string memory symbol,
    uint64 timestampInMs
  ) external view returns (Structs.Asset memory) {
    if (isStringEqual('BNB', symbol)) {
      return getBnbAsset();
    }

    Structs.Asset memory asset;
    if (self.assetsBySymbol[symbol].length > 0) {
      for (uint8 i = 0; i < self.assetsBySymbol[symbol].length; i++) {
        if (
          self.assetsBySymbol[symbol][i].confirmedTimestampInMs <= timestampInMs
        ) {
          asset = self.assetsBySymbol[symbol][i];
        }
      }
    }
    require(
      asset.exists && asset.isConfirmed,
      'No confirmed asset found'
    );

    return asset;
  }

  /**
   * @dev BNB is modeled as an always-confirmed Asset struct for programmatic consistency
   */
  function getBnbAsset() private pure returns (Structs.Asset memory) {
    return Structs.Asset(true, address(0x0), 'BNB', 18, true, 0);
  }

  // See https://solidity.readthedocs.io/en/latest/types.html#bytes-and-strings-as-arrays
  function isStringEqual(string memory a, string memory b)
    private
    pure
    returns (bool)
  {
    return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b));
  }
}

File 5 of 13 : AssetTransfers.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity ^0.8.0;

import {
  SafeMath as SafeMath256
} from './SafeMath.sol';

import { IBEP20 } from './Interfaces.sol';


/**
 * @notice This library provides helper utilities for transfering assets in and out of contracts.
 * It further validates ERC-20 compliant balance updates in the case of token assets
 */
library AssetTransfers {
  using SafeMath256 for uint256;

  /**
   * @dev Transfers tokens from a wallet into a contract during deposits. `wallet` must already
   * have called `approve` on the token contract for at least `tokenQuantity`. Note this only
   * applies to tokens since BNB is sent in the deposit transaction via `msg.value`
   */
  function transferFrom(
    address wallet,
    IBEP20 tokenAddress,
    uint256 quantityInAssetUnits
  ) external {
    uint256 balanceBefore = tokenAddress.balanceOf(address(this));

    // Because we check for the expected balance change we can safely ignore the return value of transferFrom
    tokenAddress.transferFrom(wallet, address(this), quantityInAssetUnits);

    uint256 balanceAfter = tokenAddress.balanceOf(address(this));
    require(
      balanceAfter.sub(balanceBefore) == quantityInAssetUnits,
      'balance do not match'
    );
  }

  /**
   * @dev Transfers BNB or token assets from a contract to 1) another contract, when `Exchange`
   * forwards funds to `Custodian` during deposit or 2) a wallet, when withdrawing
   */
  function transferTo(
    address payable walletOrContract,
    address asset,
    uint256 quantityInAssetUnits
  ) external {
    if (asset == address(0x0)) {
      require(
        walletOrContract.send(quantityInAssetUnits),
        'BNB transfer failed'
      );
    } else {
      uint256 balanceBefore = IBEP20(asset).balanceOf(walletOrContract);

      // Because we check for the expected balance change we can safely ignore the return value of transfer
      IBEP20(asset).transfer(walletOrContract, quantityInAssetUnits);

      uint256 balanceAfter = IBEP20(asset).balanceOf(walletOrContract);
      require(
        balanceAfter.sub(balanceBefore) == quantityInAssetUnits,
        'balance do not match'
      );
    }
  }
}

File 6 of 13 : AssetUnitConversions.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity ^0.8.0;

import {
  SafeMath as SafeMath256
} from './SafeMath.sol';


/**
 * @notice Library helpers for converting asset quantities between asset units and pips
 */
library AssetUnitConversions {
  using SafeMath256 for uint256;

  function pipsToAssetUnits(uint64 quantityInPips, uint8 assetDecimals)
    internal
    pure
    returns (uint256)
  {
    require(assetDecimals <= 32, 'More than 32 decimals');

    // Exponents cannot be negative, so divide or multiply based on exponent signedness
    if (assetDecimals > 8) {
      return uint256(quantityInPips).mul(uint256(10)**(assetDecimals - 8));
    }
    return uint256(quantityInPips).div(uint256(10)**(8 - assetDecimals));
  }

  function assetUnitsToPips(uint256 quantityInAssetUnits, uint8 assetDecimals)
    internal
    pure
    returns (uint64)
  {
    require(assetDecimals <= 32, 'More than 32 decimals');

    uint256 quantityInPips;
    // Exponents cannot be negative, so divide or multiply based on exponent signedness
    if (assetDecimals > 8) {
      quantityInPips = quantityInAssetUnits.div(
        uint256(10)**(assetDecimals - 8)
      );
    } else {
      quantityInPips = quantityInAssetUnits.mul(
        uint256(10)**(8 - assetDecimals)
      );
    }
    require(quantityInPips < 2**64, 'Pip quantity overflows');

    return uint64(quantityInPips);
  }
}

File 7 of 13 : Owned.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity ^0.8.0;


/**
 * @notice Mixin that provide separate owner and admin roles for RBAC
 */
abstract contract Owned {
  address immutable _owner;
  address _admin;

  modifier onlyOwner {
    require(msg.sender == _owner, 'Caller must be owner');
    _;
  }
  modifier onlyAdmin {
    require(msg.sender == _admin, 'Caller must be admin');
    _;
  }

  /**
   * @notice Sets both the owner and admin roles to the contract creator
   */
  constructor() {
    _owner = msg.sender;
    _admin = msg.sender;
  }

  /**
   * @notice Sets a new whitelisted admin wallet
   *
   * @param newAdmin The new whitelisted admin wallet. Must be different from the current one
   */
  function setAdmin(address newAdmin) external onlyOwner {
    require(newAdmin != address(0x0), 'Invalid wallet address');
    require(newAdmin != _admin, 'Must be different from current admin');

    _admin = newAdmin;
  }

  /**
   * @notice Clears the currently whitelisted admin wallet, effectively disabling any functions requiring
   * the admin role
   */
  function removeAdmin() external onlyOwner {
    _admin = address(0x0);
  }
}

File 8 of 13 : SafeMath64.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity ^0.8.0;


/**
 * @dev Wrappers over Solidity's arithmetic operations with added overflow
 * checks.
 *
 * Arithmetic operations in Solidity wrap on overflow. This can easily result
 * in bugs, because programmers usually assume that an overflow raises an
 * error, which is the standard behavior in high level programming languages.
 * `SafeMath` restores this intuition by reverting the transaction when an
 * operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeMath64 {
  /**
   * @dev Returns the addition of two unsigned integers, reverting on
   * overflow.
   *
   * Counterpart to Solidity's `+` operator.
   *
   * Requirements:
   * - Addition cannot overflow.
   */
  function add(uint64 a, uint64 b) internal pure returns (uint64) {
    uint64 c = a + b;
    require(c >= a, 'SafeMath: addition overflow');

    return c;
  }

  /**
   * @dev Returns the subtraction of two unsigned integers, reverting on
   * overflow (when the result is negative).
   *
   * Counterpart to Solidity's `-` operator.
   *
   * Requirements:
   * - Subtraction cannot overflow.
   */
  function sub(uint64 a, uint64 b) internal pure returns (uint64) {
    return sub(a, b, 'SafeMath: subtraction overflow');
  }

  /**
   * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
   * overflow (when the result is negative).
   *
   * Counterpart to Solidity's `-` operator.
   *
   * Requirements:
   * - Subtraction cannot overflow.
   *
   * _Available since v2.4.0._
   */
  function sub(
    uint64 a,
    uint64 b,
    string memory errorMessage
  ) internal pure returns (uint64) {
    require(b <= a, errorMessage);
    uint64 c = a - b;

    return c;
  }

  /**
   * @dev Returns the multiplication of two unsigned integers, reverting on
   * overflow.
   *
   * Counterpart to Solidity's `*` operator.
   *
   * Requirements:
   * - Multiplication cannot overflow.
   */
  function mul(uint64 a, uint64 b) internal pure returns (uint64) {
    // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
    // benefit is lost if 'b' is also tested.
    // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
    if (a == 0) {
      return 0;
    }

    uint64 c = a * b;
    require(c / a == b, 'SafeMath: multiplication overflow');

    return c;
  }

  /**
   * @dev Returns the integer division of two unsigned integers. Reverts on
   * division by zero. The result is rounded towards zero.
   *
   * Counterpart to Solidity's `/` operator. Note: this function uses a
   * `revert` opcode (which leaves remaining gas untouched) while Solidity
   * uses an invalid opcode to revert (consuming all remaining gas).
   *
   * Requirements:
   * - The divisor cannot be zero.
   */
  function div(uint64 a, uint64 b) internal pure returns (uint64) {
    return div(a, b, 'SafeMath: division by zero');
  }

  /**
   * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
   * division by zero. The result is rounded towards zero.
   *
   * Counterpart to Solidity's `/` operator. Note: this function uses a
   * `revert` opcode (which leaves remaining gas untouched) while Solidity
   * uses an invalid opcode to revert (consuming all remaining gas).
   *
   * Requirements:
   * - The divisor cannot be zero.
   *
   * _Available since v2.4.0._
   */
  function div(
    uint64 a,
    uint64 b,
    string memory errorMessage
  ) internal pure returns (uint64) {
    // Solidity only automatically asserts when dividing by 0
    require(b > 0, errorMessage);
    uint64 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold

    return c;
  }
}

File 9 of 13 : Signatures.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity ^0.8.0;

import { ECDSA } from './ECDSA.sol';

import { Enums, Structs } from './Interfaces.sol';


/**
 * Library helpers for building hashes and verifying wallet signatures on `Order` and `Withdrawal` structs
 */
library Signatures {
  function isSignatureValid(
    bytes32 hash,
    bytes memory signature,
    address signer
  ) external pure returns (bool) {
    return
      ECDSA.recover(ECDSA.toEthSignedMessageHash(hash), signature) == signer;
  }

  function getOrderWalletHash(
    Structs.Order memory order,
    string memory baseSymbol,
    string memory quoteSymbol
  ) external pure returns (bytes32) {
    require(
      order.signatureHashVersion == 1,
      'Version must be 1'
    );
    return
      keccak256(
        // Placing all the fields in a single `abi.encodePacked` call causes a `stack too deep` error
        abi.encodePacked(
          abi.encodePacked(
            order.signatureHashVersion,
            order.nonce,
            order.walletAddress,
            getMarketSymbol(baseSymbol, quoteSymbol),
            uint8(order.orderType),
            uint8(order.side),
            // Ledger qtys and prices are in pip, but order was signed by wallet owner with decimal values
            pipToDecimal(order.quantityInPips)
          ),
          abi.encodePacked(
            order.isQuantityInQuote,
            order.limitPriceInPips > 0
              ? pipToDecimal(order.limitPriceInPips)
              : '',
            order.stopPriceInPips > 0
              ? pipToDecimal(order.stopPriceInPips)
              : '',
            order.clientOrderId,
            uint8(order.timeInForce),
            uint8(order.selfTradePrevention),
            order.cancelAfter
          )
        )
      );
  }

  function getWithdrawalWalletHash(Structs.Withdrawal memory withdrawal)
    external
    pure
    returns (bytes32)
  {
    return
      keccak256(
        abi.encodePacked(
          withdrawal.nonce,
          withdrawal.walletAddress,
          // Ternary branches must resolve to the same type, so wrap in idempotent encodePacked
          withdrawal.withdrawalType == Enums.WithdrawalType.BySymbol
            ? abi.encodePacked(withdrawal.assetSymbol)
            : abi.encodePacked(withdrawal.assetAddress),
          pipToDecimal(withdrawal.quantityInPips),
          withdrawal.autoDispatchEnabled
        )
      );
  }

  /**
   * @dev Combines base and quote asset symbols into the market symbol originally signed by the
   * wallet. For example if base is 'IDEX' and quote is 'ETH', the resulting market symbol is
   * 'IDEX-ETH'. This approach is used rather than passing in the market symbol and splitting it
   * since the latter incurs a higher gas cost
   */
  function getMarketSymbol(string memory baseSymbol, string memory quoteSymbol)
    private
    pure
    returns (string memory)
  {
    bytes memory baseSymbolBytes = bytes(baseSymbol);
    bytes memory hyphenBytes = bytes('-');
    bytes memory quoteSymbolBytes = bytes(quoteSymbol);

    bytes memory marketSymbolBytes = bytes(
      new string(
        baseSymbolBytes.length + quoteSymbolBytes.length + hyphenBytes.length
      )
    );

    uint256 i;
    uint256 j;

    for (i = 0; i < baseSymbolBytes.length; i++) {
      marketSymbolBytes[j++] = baseSymbolBytes[i];
    }

    // Hyphen is one byte
    marketSymbolBytes[j++] = hyphenBytes[0];

    for (i = 0; i < quoteSymbolBytes.length; i++) {
      marketSymbolBytes[j++] = quoteSymbolBytes[i];
    }

    return string(marketSymbolBytes);
  }

  /**
   * @dev Converts an integer pip quantity back into the fixed-precision decimal pip string
   * originally signed by the wallet. For example, 1234567890 becomes '12.34567890'
   */
  function pipToDecimal(uint256 pips) private pure returns (string memory) {
    // Inspired by https://github.com/provable-things/ethereum-api/blob/831f4123816f7a3e57ebea171a3cdcf3b528e475/oraclizeAPI_0.5.sol#L1045-L1062
    uint256 copy = pips;
    uint256 length;
    while (copy != 0) {
      length++;
      copy /= 10;
    }
    if (length < 9) {
      length = 9; // a zero before the decimal point plus 8 decimals
    }
    length++; // for the decimal point

    bytes memory decimal = new bytes(length);
    for (uint256 i = length; i > 0; i--) {
      if (length - i == 8) {
        decimal[i - 1] = bytes1(uint8(46)); // period
      } else {
        decimal[i - 1] = bytes1(uint8(48 + (pips % 10)));
        pips /= 10;
      }
    }
    return string(decimal);
  }
}

File 10 of 13 : Interfaces.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity ^0.8.0;


/**
 * @notice Enums used in `Order` and `Withdrawal` structs
 */
contract Enums {
  enum OrderSelfTradePrevention {
    // Decrement and cancel
    dc,
    // Cancel oldest
    co,
    // Cancel newest
    cn,
    // Cancel both
    cb
  }
  enum OrderSide { Buy, Sell }
  enum OrderTimeInForce {
    // Good until cancelled
    gtc,
    // Good until time
    gtt,
    // Immediate or cancel
    ioc,
    // Fill or kill
    fok
  }
  enum OrderType {
    Market,
    Limit,
    LimitMaker,
    StopLoss,
    StopLossLimit,
    TakeProfit,
    TakeProfitLimit
  }
  enum WithdrawalType { BySymbol, ByAddress }
}


/**
 * @notice Struct definitions
 */
contract Structs {
  /**
   * @notice Argument type for `Exchange.executeTrade` and `Signatures.getOrderWalletHash`
   */
  struct Order {
    // Not currently used but reserved for future use. Must be 1
    uint8 signatureHashVersion;
    // UUIDv1 unique to wallet
    uint128 nonce;
    // Wallet address that placed order and signed hash
    address walletAddress;
    // Type of order
    Enums.OrderType orderType;
    // Order side wallet is on
    Enums.OrderSide side;
    // Order quantity in base or quote asset terms depending on isQuantityInQuote flag
    uint64 quantityInPips;
    // Is quantityInPips in quote terms
    bool isQuantityInQuote;
    // For limit orders, price in decimal pips * 10^8 in quote terms
    uint64 limitPriceInPips;
    // For stop orders, stop loss or take profit price in decimal pips * 10^8 in quote terms
    uint64 stopPriceInPips;
    // Optional custom client order ID
    string clientOrderId;
    // TIF option specified by wallet for order
    Enums.OrderTimeInForce timeInForce;
    // STP behavior specified by wallet for order
    Enums.OrderSelfTradePrevention selfTradePrevention;
    // Cancellation time specified by wallet for GTT TIF order
    uint64 cancelAfter;
    // The ECDSA signature of the order hash as produced by Signatures.getOrderWalletHash
    bytes walletSignature;
  }

  /**
   * @notice Return type for `Exchange.loadAssetBySymbol`, and `Exchange.loadAssetByAddress`; also
   * used internally by `AssetRegistry`
   */
  struct Asset {
    // Flag to distinguish from empty struct
    bool exists;
    // The asset's address
    address assetAddress;
    // The asset's symbol
    string symbol;
    // The asset's decimal precision
    uint8 decimals;
    // Flag set when asset registration confirmed. Asset deposits, trades, or withdrawals only allowed if true
    bool isConfirmed;
    // Timestamp as ms since Unix epoch when isConfirmed was asserted
    uint64 confirmedTimestampInMs;
  }

  /**
   * @notice Argument type for `Exchange.executeTrade` specifying execution parameters for matching orders
   */
  struct Trade {
    // Base asset symbol
    string baseAssetSymbol;
    // Quote asset symbol
    string quoteAssetSymbol;
    // Base asset address
    address baseAssetAddress;
    // Quote asset address
    address quoteAssetAddress;
    // Gross amount including fees of base asset executed
    uint64 grossBaseQuantityInPips;
    // Gross amount including fees of quote asset executed
    uint64 grossQuoteQuantityInPips;
    // Net amount of base asset received by buy side wallet after fees
    uint64 netBaseQuantityInPips;
    // Net amount of quote asset received by sell side wallet after fees
    uint64 netQuoteQuantityInPips;
    // Asset address for liquidity maker's fee
    address makerFeeAssetAddress;
    // Asset address for liquidity taker's fee
    address takerFeeAssetAddress;
    // Fee paid by liquidity maker
    uint64 makerFeeQuantityInPips;
    // Fee paid by liquidity taker
    uint64 takerFeeQuantityInPips;
    // Execution price of trade in decimal pips * 10^8 in quote terms
    uint64 priceInPips;
    // Which side of the order (buy or sell) the liquidity maker was on
    Enums.OrderSide makerSide;
  }

  /**
   * @notice Argument type for `Exchange.withdraw` and `Signatures.getWithdrawalWalletHash`
   */
  struct Withdrawal {
    // Distinguishes between withdrawals by asset symbol or address
    Enums.WithdrawalType withdrawalType;
    // UUIDv1 unique to wallet
    uint128 nonce;
    // Address of wallet to which funds will be returned
    address payable walletAddress;
    // Asset symbol
    string assetSymbol;
    // Asset address
    address assetAddress; // Used when assetSymbol not specified
    // Withdrawal quantity
    uint64 quantityInPips;
    // Gas fee deducted from withdrawn quantity to cover dispatcher tx costs
    uint64 gasFeeInPips;
    // Not currently used but reserved for future use. Must be true
    bool autoDispatchEnabled;
    // The ECDSA signature of the withdrawal hash as produced by Signatures.getWithdrawalWalletHash
    bytes walletSignature;
  }

  /**
   * @notice return type for `Exchange.executeTrades`
   */
  struct ExecRet {
    bool success;
    string err;
  }
}


/**
 * @notice Interface of the BEP-20, but with no return values for transfer and transferFrom.
 * By asserting expected balance changes when calling these two methods we can safely ignore 
 * their return values. This allows support of non-compliant tokens that do not return a boolean. 
 * See https://github.com/ethereum/solidity/issues/4116
 */
interface IBEP20 {
  /**
   * @notice Returns the amount of tokens in existence.
   */
  function totalSupply() external view returns (uint256);

  /**
   * @notice Returns the amount of tokens owned by `account`.
   */
  function balanceOf(address account) external view returns (uint256);

  /**
   * @notice Moves `amount` tokens from the caller's account to `recipient`.
   *
   * Most implementing contracts return a boolean value indicating whether the operation succeeded, but
   * we ignore this and rely on asserting balance changes instead
   *
   * Emits a {Transfer} event.
   */
  function transfer(address recipient, uint256 amount) external;

  /**
   * @notice Returns the remaining number of tokens that `spender` will be
   * allowed to spend on behalf of `owner` through {transferFrom}. This is
   * zero by default.
   *
   * This value changes when {approve} or {transferFrom} are called.
   */
  function allowance(address owner, address spender)
    external
    view
    returns (uint256);

  /**
   * @notice Sets `amount` as the allowance of `spender` over the caller's tokens.
   *
   * Returns a boolean value indicating whether the operation succeeded.
   *
   * IMPORTANT: Beware that changing an allowance with this method brings the risk
   * that someone may use both the old and the new allowance by unfortunate
   * transaction ordering. One possible solution to mitigate this race
   * condition is to first reduce the spender's allowance to 0 and set the
   * desired value afterwards:
   * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
   *
   * Emits an {Approval} event.
   */
  function approve(address spender, uint256 amount) external returns (bool);

  /**
   * @notice Moves `amount` tokens from `sender` to `recipient` using the
   * allowance mechanism. `amount` is then deducted from the caller's
   * allowance.
   *
   * Most implementing contracts return a boolean value indicating whether the operation succeeded, but
   * we ignore this and rely on asserting balance changes instead
   *
   * Emits a {Transfer} event.
   */
  function transferFrom(
    address sender,
    address recipient,
    uint256 amount
  ) external;

  /**
   * @notice Emitted when `value` tokens are moved from one account (`from`) to
   * another (`to`).
   *
   * Note that `value` may be zero.
   */
  event Transfer(address indexed from, address indexed to, uint256 value);

  /**
   * @notice Emitted when the allowance of a `spender` for an `owner` is set by
   * a call to {approve}. `value` is the new allowance.
   */
  event Approval(address indexed owner, address indexed spender, uint256 value);
}


/**
 * @notice Interface to Custodian contract. Used by Exchange and Governance contracts for internal
 * delegate calls
 */
interface ICustodian {
  /**
   * @notice BNB can only be sent by the Exchange
   */
  receive() external payable;

  /**
   * @notice Withdraw any asset and amount to a target wallet
   *
   * @dev No balance checking performed
   *
   * @param wallet The wallet to which assets will be returned
   * @param asset The address of the asset to withdraw (BNB or BEP20 contract)
   * @param quantityInAssetUnits The quantity in asset units to withdraw
   */
  function withdraw(
    address payable wallet,
    address asset,
    uint256 quantityInAssetUnits
  ) external;

  /**
   * @notice Load address of the currently whitelisted Exchange contract
   *
   * @return The address of the currently whitelisted Exchange contract
   */
  function loadExchange() external view returns (address);

  /**
   * @notice Sets a new Exchange contract address
   *
   * @param newExchange The address of the new whitelisted Exchange contract
   */
  function setExchange(address newExchange) external;
}


/**
 * @notice Interface to Exchange contract. Provided only to document struct usage
 */
interface IExchange {
  /**
   * @notice Settles a trade between two orders submitted and matched off-chain
   *
   * @param buy A `Structs.Order` struct encoding the parameters of the buy-side order (receiving base, giving quote)
   * @param sell A `Structs.Order` struct encoding the parameters of the sell-side order (giving base, receiving quote)
   * @param trade A `Structs.Trade` struct encoding the parameters of this trade execution of the counterparty orders
   */
  function executeTrade(
    Structs.Order calldata buy,
    Structs.Order calldata sell,
    Structs.Trade calldata trade
  ) external;

  /**
   * @notice Execute multiple trades
   *
   * @param buy A `Structs.Order` struct array encoding the parameters of the buy-side orders (receiving base, giving quote)
   * @param sell A `Structs.Order` struct array encoding the parameters of the sell-side order (giving base, receiving quote)
   * @param trade A `Structs.Trade` struct array encoding the parameters of this trade execution of the counterparty orders
   */
  function executeTrades(
    Structs.Order[] memory buy,
    Structs.Order[] memory sell,
    Structs.Trade[] memory trade
  ) external;

  /**
   * @notice Settles a user withdrawal submitted off-chain. Calls restricted to currently whitelisted Dispatcher wallet
   *
   * @param withdrawal A `Structs.Withdrawal` struct encoding the parameters of the withdrawal
   */
  function withdraw(Structs.Withdrawal calldata withdrawal) external;
}

File 11 of 13 : UUID.sol
// SPDX-License-Identifier: LGPL-3.0-only

pragma solidity ^0.8.0;

import { SafeMath64 } from './SafeMath64.sol';


/**
 * Library helper for extracting timestamp component of Version 1 UUIDs
 */
library UUID {
  using SafeMath64 for uint64;

  /**
   * Extracts the timestamp component of a Version 1 UUID. Used to make time-based assertions
   * against a wallet-privided nonce
   */
  function getTimestampInMsFromUuidV1(uint128 uuid)
    internal
    pure
    returns (uint64 msSinceUnixEpoch)
  {
    // https://tools.ietf.org/html/rfc4122#section-4.1.2
    uint128 version = (uuid >> 76) & 0x0000000000000000000000000000000F;
    require(version == 1, 'Must be v1 UUID');

    // Time components are in reverse order so shift+mask each to reassemble
    uint128 timeHigh = (uuid >> 16) & 0x00000000000000000FFF000000000000;
    uint128 timeMid = (uuid >> 48) & 0x00000000000000000000FFFF00000000;
    uint128 timeLow = (uuid >> 96) & 0x000000000000000000000000FFFFFFFF;
    uint128 nsSinceGregorianEpoch = (timeHigh | timeMid | timeLow);
    // Gregorian offset given in seconds by https://www.wolframalpha.com/input/?i=convert+1582-10-15+UTC+to+unix+time
    msSinceUnixEpoch = uint64(nsSinceGregorianEpoch / 10000).sub(
      12219292800000
    );

    return msSinceUnixEpoch;
  }
}

File 12 of 13 : IterableMapping.sol
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.0;

library IterableMapping {
    // Iterable mapping from address to uint64;
    struct Map {
        address[] keys;
        mapping(address => uint64) values;
        mapping(address => uint) indexOf;
        mapping(address => bool) inserted;
    }

    function get(Map storage map, address key) internal view returns (uint64) {
        return map.values[key];
    }

    function getKeyAtIndex(Map storage map, uint index) internal view returns (address) {
        return map.keys[index];
    }

    function size(Map storage map) internal view returns (uint) {
        return map.keys.length;
    }

    function set(
        Map storage map,
        address key,
        uint64 val
    ) internal {
        if (map.inserted[key]) {
            map.values[key] = val;
        } else {
            map.inserted[key] = true;
            map.values[key] = val;
            map.indexOf[key] = map.keys.length;
            map.keys.push(key);
        }
    }

    function remove(Map storage map, address key) internal {
        if (!map.inserted[key]) {
            return;
        }

        delete map.inserted[key];
        delete map.values[key];

        uint index = map.indexOf[key];
        uint lastIndex = map.keys.length - 1;
        address lastKey = map.keys[lastIndex];

        map.indexOf[lastKey] = index;
        delete map.indexOf[key];

        map.keys[index] = lastKey;
        map.keys.pop();
    }
}

File 13 of 13 : ECDSA.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
 *
 * These functions can be used to verify that a message was signed by the holder
 * of the private keys of a given address.
 */
library ECDSA {
    enum RecoverError {
        NoError,
        InvalidSignature,
        InvalidSignatureLength,
        InvalidSignatureS,
        InvalidSignatureV
    }

    function _throwError(RecoverError error) private pure {
        if (error == RecoverError.NoError) {
            return; // no error: do nothing
        } else if (error == RecoverError.InvalidSignature) {
            revert("ECDSA: invalid signature");
        } else if (error == RecoverError.InvalidSignatureLength) {
            revert("ECDSA: invalid signature length");
        } else if (error == RecoverError.InvalidSignatureS) {
            revert("ECDSA: invalid signature 's' value");
        } else if (error == RecoverError.InvalidSignatureV) {
            revert("ECDSA: invalid signature 'v' value");
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature` or error string. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     *
     * Documentation for signature generation:
     * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
     * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
     *
     * _Available since v4.3._
     */
    function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
        // Check the signature length
        // - case 65: r,s,v signature (standard)
        // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._
        if (signature.length == 65) {
            bytes32 r;
            bytes32 s;
            uint8 v;
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            assembly {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }
            return tryRecover(hash, v, r, s);
        } else if (signature.length == 64) {
            bytes32 r;
            bytes32 vs;
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            assembly {
                r := mload(add(signature, 0x20))
                vs := mload(add(signature, 0x40))
            }
            return tryRecover(hash, r, vs);
        } else {
            return (address(0), RecoverError.InvalidSignatureLength);
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature`. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     */
    function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, signature);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
     *
     * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
     *
     * _Available since v4.3._
     */
    function tryRecover(
        bytes32 hash,
        bytes32 r,
        bytes32 vs
    ) internal pure returns (address, RecoverError) {
        bytes32 s;
        uint8 v;
        assembly {
            s := and(vs, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
            v := add(shr(255, vs), 27)
        }
        return tryRecover(hash, v, r, s);
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
     *
     * _Available since v4.2._
     */
    function recover(
        bytes32 hash,
        bytes32 r,
        bytes32 vs
    ) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, r, vs);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
     * `r` and `s` signature fields separately.
     *
     * _Available since v4.3._
     */
    function tryRecover(
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal pure returns (address, RecoverError) {
        // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
        // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
        // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
        // signatures from current libraries generate a unique signature with an s-value in the lower half order.
        //
        // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
        // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
        // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
        // these malleable signatures as well.
        if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
            return (address(0), RecoverError.InvalidSignatureS);
        }
        if (v != 27 && v != 28) {
            return (address(0), RecoverError.InvalidSignatureV);
        }

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(hash, v, r, s);
        if (signer == address(0)) {
            return (address(0), RecoverError.InvalidSignature);
        }

        return (signer, RecoverError.NoError);
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function recover(
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, v, r, s);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from a `hash`. This
     * produces hash corresponding to the one signed with the
     * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
     * JSON-RPC method as part of EIP-191.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
        // 32 is the length in bytes of hash,
        // enforced by the type signature above
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
    }

    /**
     * @dev Returns an Ethereum Signed Typed Data, created from a
     * `domainSeparator` and a `structHash`. This produces hash corresponding
     * to the one signed with the
     * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
     * JSON-RPC method as part of EIP-712.
     *
     * See {recover}.
     */
    function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
    }
}

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 13
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "libraries": {
    "contracts/libraries/AssetRegistry.sol": {
      "AssetRegistry": "0x2ae371fbb3d4a45ce174b5ca559974928133bd4c"
    },
    "contracts/libraries/AssetTransfers.sol": {
      "AssetTransfers": "0x1af7b032e507fc235be7d3d5fe56f39ccb7dceca"
    },
    "contracts/libraries/Signatures.sol": {
      "Signatures": "0xcb85f6b03ce6238786aeebca4581dd167fa6a110"
    }
  }
}

Contract Security Audit

Contract ABI

[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newValue","type":"address"}],"name":"AddDispatcher","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"previousValue","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newValue","type":"uint256"}],"name":"ChainPropagationPeriodChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"index","type":"uint64"},{"indexed":true,"internalType":"address","name":"wallet","type":"address"},{"indexed":true,"internalType":"address","name":"assetAddress","type":"address"},{"indexed":true,"internalType":"string","name":"assetSymbolIndex","type":"string"},{"indexed":false,"internalType":"string","name":"assetSymbol","type":"string"},{"indexed":false,"internalType":"uint64","name":"quantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"newExchangeBalanceInPips","type":"uint64"},{"indexed":false,"internalType":"uint256","name":"newExchangeBalanceInAssetUnits","type":"uint256"}],"name":"Deposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"previousValue","type":"address"},{"indexed":false,"internalType":"address","name":"newValue","type":"address"}],"name":"FeeWalletChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"uint128","name":"nonce","type":"uint128"},{"indexed":false,"internalType":"uint128","name":"timestampInMs","type":"uint128"},{"indexed":false,"internalType":"uint256","name":"effectiveBlockNumber","type":"uint256"}],"name":"OrderNonceInvalidated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"value","type":"address"}],"name":"RemoveDispatcher","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IBEP20","name":"assetAddress","type":"address"},{"indexed":false,"internalType":"string","name":"assetSymbol","type":"string"},{"indexed":false,"internalType":"uint8","name":"decimals","type":"uint8"}],"name":"TokenRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IBEP20","name":"assetAddress","type":"address"},{"indexed":false,"internalType":"string","name":"assetSymbol","type":"string"},{"indexed":false,"internalType":"uint8","name":"decimals","type":"uint8"}],"name":"TokenRegistrationConfirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IBEP20","name":"assetAddress","type":"address"},{"indexed":false,"internalType":"string","name":"assetSymbol","type":"string"}],"name":"TokenSymbolAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"buyWallet","type":"address"},{"indexed":false,"internalType":"address","name":"sellWallet","type":"address"},{"indexed":true,"internalType":"string","name":"baseAssetSymbolIndex","type":"string"},{"indexed":true,"internalType":"string","name":"quoteAssetSymbolIndex","type":"string"},{"indexed":false,"internalType":"string","name":"baseAssetSymbol","type":"string"},{"indexed":false,"internalType":"string","name":"quoteAssetSymbol","type":"string"},{"indexed":false,"internalType":"uint64","name":"baseQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"quoteQuantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"tradePriceInPips","type":"uint64"},{"indexed":false,"internalType":"bytes32","name":"buyOrderHash","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"sellOrderHash","type":"bytes32"}],"name":"TradeExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"wallet","type":"address"}],"name":"WalletExitCleared","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"wallet","type":"address"},{"indexed":true,"internalType":"address","name":"assetAddress","type":"address"},{"indexed":false,"internalType":"string","name":"assetSymbol","type":"string"},{"indexed":false,"internalType":"uint64","name":"quantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"newExchangeBalanceInPips","type":"uint64"},{"indexed":false,"internalType":"uint256","name":"newExchangeBalanceInAssetUnits","type":"uint256"}],"name":"WalletExitWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"uint256","name":"effectiveBlockNumber","type":"uint256"}],"name":"WalletExited","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"wallet","type":"address"},{"indexed":true,"internalType":"address","name":"assetAddress","type":"address"},{"indexed":false,"internalType":"string","name":"assetSymbol","type":"string"},{"indexed":false,"internalType":"uint64","name":"quantityInPips","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"newExchangeBalanceInPips","type":"uint64"},{"indexed":false,"internalType":"uint256","name":"newExchangeBalanceInAssetUnits","type":"uint256"}],"name":"Withdrawn","type":"event"},{"inputs":[{"internalType":"address","name":"newDispatcherWallet","type":"address"}],"name":"addDispatcher","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IBEP20","name":"tokenAddress","type":"address"},{"internalType":"string","name":"symbol","type":"string"}],"name":"addTokenSymbol","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"clearWalletExit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IBEP20","name":"tokenAddress","type":"address"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"uint8","name":"decimals","type":"uint8"}],"name":"confirmTokenRegistration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"depositBNB","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"contract IBEP20","name":"tokenAddress","type":"address"},{"internalType":"uint256","name":"quantityInAssetUnits","type":"uint256"}],"name":"depositTokenByAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"assetSymbol","type":"string"},{"internalType":"uint256","name":"quantityInAssetUnits","type":"uint256"}],"name":"depositTokenBySymbol","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint8","name":"signatureHashVersion","type":"uint8"},{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address","name":"walletAddress","type":"address"},{"internalType":"enum Enums.OrderType","name":"orderType","type":"uint8"},{"internalType":"enum Enums.OrderSide","name":"side","type":"uint8"},{"internalType":"uint64","name":"quantityInPips","type":"uint64"},{"internalType":"bool","name":"isQuantityInQuote","type":"bool"},{"internalType":"uint64","name":"limitPriceInPips","type":"uint64"},{"internalType":"uint64","name":"stopPriceInPips","type":"uint64"},{"internalType":"string","name":"clientOrderId","type":"string"},{"internalType":"enum Enums.OrderTimeInForce","name":"timeInForce","type":"uint8"},{"internalType":"enum Enums.OrderSelfTradePrevention","name":"selfTradePrevention","type":"uint8"},{"internalType":"uint64","name":"cancelAfter","type":"uint64"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct Structs.Order","name":"buy","type":"tuple"},{"components":[{"internalType":"uint8","name":"signatureHashVersion","type":"uint8"},{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address","name":"walletAddress","type":"address"},{"internalType":"enum Enums.OrderType","name":"orderType","type":"uint8"},{"internalType":"enum Enums.OrderSide","name":"side","type":"uint8"},{"internalType":"uint64","name":"quantityInPips","type":"uint64"},{"internalType":"bool","name":"isQuantityInQuote","type":"bool"},{"internalType":"uint64","name":"limitPriceInPips","type":"uint64"},{"internalType":"uint64","name":"stopPriceInPips","type":"uint64"},{"internalType":"string","name":"clientOrderId","type":"string"},{"internalType":"enum Enums.OrderTimeInForce","name":"timeInForce","type":"uint8"},{"internalType":"enum Enums.OrderSelfTradePrevention","name":"selfTradePrevention","type":"uint8"},{"internalType":"uint64","name":"cancelAfter","type":"uint64"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct Structs.Order","name":"sell","type":"tuple"},{"components":[{"internalType":"string","name":"baseAssetSymbol","type":"string"},{"internalType":"string","name":"quoteAssetSymbol","type":"string"},{"internalType":"address","name":"baseAssetAddress","type":"address"},{"internalType":"address","name":"quoteAssetAddress","type":"address"},{"internalType":"uint64","name":"grossBaseQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"grossQuoteQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"netBaseQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"netQuoteQuantityInPips","type":"uint64"},{"internalType":"address","name":"makerFeeAssetAddress","type":"address"},{"internalType":"address","name":"takerFeeAssetAddress","type":"address"},{"internalType":"uint64","name":"makerFeeQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"takerFeeQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"priceInPips","type":"uint64"},{"internalType":"enum Enums.OrderSide","name":"makerSide","type":"uint8"}],"internalType":"struct Structs.Trade","name":"trade","type":"tuple"}],"name":"executeTrade","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint8","name":"signatureHashVersion","type":"uint8"},{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address","name":"walletAddress","type":"address"},{"internalType":"enum Enums.OrderType","name":"orderType","type":"uint8"},{"internalType":"enum Enums.OrderSide","name":"side","type":"uint8"},{"internalType":"uint64","name":"quantityInPips","type":"uint64"},{"internalType":"bool","name":"isQuantityInQuote","type":"bool"},{"internalType":"uint64","name":"limitPriceInPips","type":"uint64"},{"internalType":"uint64","name":"stopPriceInPips","type":"uint64"},{"internalType":"string","name":"clientOrderId","type":"string"},{"internalType":"enum Enums.OrderTimeInForce","name":"timeInForce","type":"uint8"},{"internalType":"enum Enums.OrderSelfTradePrevention","name":"selfTradePrevention","type":"uint8"},{"internalType":"uint64","name":"cancelAfter","type":"uint64"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct Structs.Order[]","name":"buys","type":"tuple[]"},{"components":[{"internalType":"uint8","name":"signatureHashVersion","type":"uint8"},{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address","name":"walletAddress","type":"address"},{"internalType":"enum Enums.OrderType","name":"orderType","type":"uint8"},{"internalType":"enum Enums.OrderSide","name":"side","type":"uint8"},{"internalType":"uint64","name":"quantityInPips","type":"uint64"},{"internalType":"bool","name":"isQuantityInQuote","type":"bool"},{"internalType":"uint64","name":"limitPriceInPips","type":"uint64"},{"internalType":"uint64","name":"stopPriceInPips","type":"uint64"},{"internalType":"string","name":"clientOrderId","type":"string"},{"internalType":"enum Enums.OrderTimeInForce","name":"timeInForce","type":"uint8"},{"internalType":"enum Enums.OrderSelfTradePrevention","name":"selfTradePrevention","type":"uint8"},{"internalType":"uint64","name":"cancelAfter","type":"uint64"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct Structs.Order[]","name":"sells","type":"tuple[]"},{"components":[{"internalType":"string","name":"baseAssetSymbol","type":"string"},{"internalType":"string","name":"quoteAssetSymbol","type":"string"},{"internalType":"address","name":"baseAssetAddress","type":"address"},{"internalType":"address","name":"quoteAssetAddress","type":"address"},{"internalType":"uint64","name":"grossBaseQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"grossQuoteQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"netBaseQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"netQuoteQuantityInPips","type":"uint64"},{"internalType":"address","name":"makerFeeAssetAddress","type":"address"},{"internalType":"address","name":"takerFeeAssetAddress","type":"address"},{"internalType":"uint64","name":"makerFeeQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"takerFeeQuantityInPips","type":"uint64"},{"internalType":"uint64","name":"priceInPips","type":"uint64"},{"internalType":"enum Enums.OrderSide","name":"makerSide","type":"uint8"}],"internalType":"struct Structs.Trade[]","name":"trades","type":"tuple[]"}],"name":"executeTrades","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"exitWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint128","name":"nonce","type":"uint128"}],"name":"invalidateOrderNonce","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"isDispatcher","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"isWalletExit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"isWalletExitFinalized","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"assetSymbol","type":"string"},{"internalType":"uint64","name":"timestampInMs","type":"uint64"}],"name":"loadAssetBySymbol","outputs":[{"components":[{"internalType":"bool","name":"exists","type":"bool"},{"internalType":"address","name":"assetAddress","type":"address"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"bool","name":"isConfirmed","type":"bool"},{"internalType":"uint64","name":"confirmedTimestampInMs","type":"uint64"}],"internalType":"struct Structs.Asset","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"loadBalanceAssetAddress","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"},{"internalType":"address","name":"assetAddress","type":"address"}],"name":"loadBalanceInAssetUnitsByAddress","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"},{"internalType":"string","name":"assetSymbol","type":"string"}],"name":"loadBalanceInAssetUnitsBySymbol","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"},{"internalType":"address","name":"assetAddress","type":"address"}],"name":"loadBalanceInPipsByAddress","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"},{"internalType":"string","name":"assetSymbol","type":"string"}],"name":"loadBalanceInPipsBySymbol","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"loadFeeWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"orderHash","type":"bytes32"}],"name":"loadPartiallyFilledOrderQuantityInPips","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IBEP20","name":"tokenAddress","type":"address"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"uint8","name":"decimals","type":"uint8"}],"name":"registerToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"removeAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"removeDispatcher","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newAdmin","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newChainPropagationPeriod","type":"uint256"}],"name":"setChainPropagationPeriod","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"newCustodian","type":"address"}],"name":"setCustodian","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newFeeWallet","type":"address"}],"name":"setFeeWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"enum Enums.WithdrawalType","name":"withdrawalType","type":"uint8"},{"internalType":"uint128","name":"nonce","type":"uint128"},{"internalType":"address payable","name":"walletAddress","type":"address"},{"internalType":"string","name":"assetSymbol","type":"string"},{"internalType":"address","name":"assetAddress","type":"address"},{"internalType":"uint64","name":"quantityInPips","type":"uint64"},{"internalType":"uint64","name":"gasFeeInPips","type":"uint64"},{"internalType":"bool","name":"autoDispatchEnabled","type":"bool"},{"internalType":"bytes","name":"walletSignature","type":"bytes"}],"internalType":"struct Structs.Withdrawal","name":"withdrawal","type":"tuple"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawAllExit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"assetAddress","type":"address"}],"name":"withdrawExit","outputs":[],"stateMutability":"nonpayable","type":"function"}]

Deployed Bytecode

0x6080604052600436106101735760003560e01c80630226b70e1461017857806302ca60021461019a57806313cfda2c146101c95780631abb5832146101f7578063203624a41461021757806320e6a8e314610237578063403f373114610257578063419681821461027757806342220f3414610297578063457aa3c61461029f5780634a284ef9146102bf57806365fce5b7146102d45780636bb509f2146102f4578063704b6c021461031457806372e8f08d1461033457806375348d351461035457806380d0dee71461037457806384ae6c6c146103a4578063869af212146103c457806390d49b9d146103f157806391ec8c8b14610411578063985c4af51461043e5780639a202d471461045e578063ae0e969e14610473578063d257fee9146104c1578063d7a6aec7146104fa578063dbb365351461051a578063dcc634901461053a578063dfa518701461055a578063eb5068f214610593578063ef3b9d4a146105a8578063f34e2ef1146105c8575b600080fd5b34801561018457600080fd5b50610198610193366004614c66565b6105dd565b005b3480156101a657600080fd5b50600c546001600160a01b03166040516101c091906150b7565b60405180910390f35b3480156101d557600080fd5b506101e96101e4366004614a9c565b6106cb565b6040519081526020016101c0565b34801561020357600080fd5b506101e9610212366004614ad4565b6107c1565b34801561022357600080fd5b50610198610232366004614a80565b6108bc565b34801561024357600080fd5b50610198610252366004614c36565b61093e565b34801561026357600080fd5b50610198610272366004614a80565b6109f5565b34801561028357600080fd5b50610198610292366004614cf6565b610aa6565b610198610b7e565b3480156102ab57600080fd5b506101986102ba366004614c66565b610bd5565b3480156102cb57600080fd5b50610198610cac565b3480156102e057600080fd5b506101986102ef366004614e54565b610d44565b34801561030057600080fd5b5061019861030f366004614ed7565b610d83565b34801561032057600080fd5b5061019861032f366004614a80565b611265565b34801561034057600080fd5b5061019861034f366004614ad4565b611388565b34801561036057600080fd5b5061019861036f366004614a80565b611468565b34801561038057600080fd5b5061039461038f366004614a80565b61156e565b60405190151581526020016101c0565b3480156103b057600080fd5b506101986103bf366004614b26565b6115a2565b3480156103d057600080fd5b506103e46103df366004614d3f565b6116fe565b6040516101c0919061539f565b3480156103fd57600080fd5b5061019861040c366004614a80565b6117c6565b34801561041d57600080fd5b5061043161042c366004614a80565b6118c0565b6040516101c09190615168565b34801561044a57600080fd5b50610198610459366004614a80565b6119c8565b34801561046a57600080fd5b50610198611bb7565b34801561047f57600080fd5b506104a961048e366004614c36565b6000908152600860205260409020546001600160401b031690565b6040516001600160401b0390911681526020016101c0565b3480156104cd57600080fd5b506103946104dc366004614a80565b6001600160a01b03166000908152600b602052604090205460ff1690565b34801561050657600080fd5b50610198610515366004614fde565b611c11565b34801561052657600080fd5b506104a9610535366004614a9c565b611e17565b34801561054657600080fd5b50610198610555366004614ccb565b611e61565b34801561056657600080fd5b50610394610575366004614a80565b6001600160a01b031660009081526009602052604090205460ff1690565b34801561059f57600080fd5b50610198611e96565b3480156105b457600080fd5b506104a96105c3366004614ad4565b611f58565b3480156105d457600080fd5b50610198612047565b6000546001600160a01b031633146106105760405162461bcd60e51b81526004016106079061531b565b60405180910390fd5b60405163f282a4f560e01b8152732ae371fbb3d4a45ce174b5ca559974928133bd4c9063f282a4f5906106509060019088908890889088906004016155c3565b60006040518083038186803b15801561066857600080fd5b505af415801561067c573d6000803e3d6000fd5b50505050836001600160a01b03167fa8968236dc4afb2c189afeb69c91b02445e6bd09f0ffe27256b62838edc99edb8484846040516106bd939291906151fa565b60405180910390a250505050565b60006001600160a01b0383166106f35760405162461bcd60e51b81526004016106079061526a565b604051638c4ae9e560e01b8152600090732ae371fbb3d4a45ce174b5ca559974928133bd4c90638c4ae9e590610730906001908790600401615581565b60006040518083038186803b15801561074857600080fd5b505af415801561075c573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526107849190810190614d93565b6001600160a01b03851660009081526006602052604090209091506107b7906107ad9085612330565b8260600151612358565b9150505b92915050565b60006001600160a01b0384166107e95760405162461bcd60e51b81526004016106079061526a565b6000732ae371fbb3d4a45ce174b5ca559974928133bd4c63e970ba6b600186866108116123e3565b6040518563ffffffff1660e01b81526004016108309493929190615603565b60006040518083038186803b15801561084857600080fd5b505af415801561085c573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526108849190810190614d93565b6020808201516001600160a01b0388166000908152600690925260409091209192506108b3916107ad91612330565b95945050505050565b6000546001600160a01b031633146108e65760405162461bcd60e51b81526004016106079061531b565b7f223ed04a545ed5a1e24b45a98f0eb4677763a92dac1ba71c4ec5f227422c49968160405161091591906150b7565b60405180910390a16001600160a01b03166000908152600b60205260409020805460ff19169055565b6000546001600160a01b031633146109685760405162461bcd60e51b81526004016106079061531b565b6203138081106109af5760405162461bcd60e51b8152602060048201526012602482015271426967676572207468616e2031207765656b60701b6044820152606401610607565b600a80549082905560408051828152602081018490527f9a22227d6c0251a79ef8b846202ddcbe9d682ee5482e84abeec6dda096398a6f91015b60405180910390a15050565b6000546001600160a01b03163314610a1f5760405162461bcd60e51b81526004016106079061531b565b6005546001600160a01b031615610a665760405162461bcd60e51b815260206004820152600b60248201526a185b1c9958591e481cd95d60aa1b6044820152606401610607565b803b610a845760405162461bcd60e51b81526004016106079061526a565b600580546001600160a01b0319166001600160a01b0392909216919091179055565b6000732ae371fbb3d4a45ce174b5ca559974928133bd4c63e970ba6b60018686610ace6123e3565b6040518563ffffffff1660e01b8152600401610aed9493929190615603565b60006040518083038186803b158015610b0557600080fd5b505af4158015610b19573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610b419190810190614d93565b6020015190506001600160a01b038116610b6d5760405162461bcd60e51b81526004016106079061526a565b610b783382846123f8565b50505050565b610b8d6402540be40034615af8565b15610bc75760405162461bcd60e51b815260206004820152600a602482015269647573742076616c756560b01b6044820152606401610607565b610bd3336000346123f8565b565b6000546001600160a01b03163314610bff5760405162461bcd60e51b81526004016106079061531b565b6040516319047a2f60e31b8152732ae371fbb3d4a45ce174b5ca559974928133bd4c9063c823d17890610c3f9060019088908890889088906004016155c3565b60006040518083038186803b158015610c5757600080fd5b505af4158015610c6b573d6000803e3d6000fd5b50505050836001600160a01b03167fcf66acda7673c71cd9458c59067eb30a473eeaf2fa05d008acf47616cbbbe01c8484846040516106bd939291906151fa565b3360009081526009602052604090205460ff16610cff5760405162461bcd60e51b815260206004820152601160248201527015d85b1b195d081b9bdd08195e1a5d1959607a1b6044820152606401610607565b33600081815260096020526040808220805460ff19168155600101829055517fb771d4b2a83beca38f442c8903629e0e8ab1a07cf76e94eb2977153167e209369190a2565b336000908152600b602052604090205460ff16610d735760405162461bcd60e51b815260040161060790615293565b610d7e83838361275d565b505050565b336000908152600b602052604090205460ff16610db25760405162461bcd60e51b815260040161060790615293565b610dbf816040015161156e565b15610ddc5760405162461bcd60e51b815260040161060790615378565b6107d06001600160401b0316610dfa8260c001518360a00151612954565b6001600160401b03161115610e415760405162461bcd60e51b815260206004820152600d60248201526c4578636573736976652066656560981b6044820152606401610607565b6000610e4c8261297f565b60008181526004602052604090205490915060ff1615610e9a5760405162461bcd60e51b81526020600482015260096024820152682bb4ba34323930bbb760b91b6044820152606401610607565b60008083516001811115610ebe57634e487b7160e01b600052602160045260246000fd5b14610f59576080830151604051638c4ae9e560e01b8152732ae371fbb3d4a45ce174b5ca559974928133bd4c91638c4ae9e591610f0091600191600401615581565b60006040518083038186803b158015610f1857600080fd5b505af4158015610f2c573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610f549190810190614d93565b610ffb565b6001732ae371fbb3d4a45ce174b5ca559974928133bd4c63e970ba6b90918560600151610f898760200151612ade565b6040518463ffffffff1660e01b8152600401610fa793929190615636565b60006040518083038186803b158015610fbf57600080fd5b505af4158015610fd3573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610ffb9190810190614d93565b905060006110238460c001518560a001516001600160401b0316612b7390919063ffffffff16565b90506000611035828460600151612358565b9050600061108f8660a001516110808660200151600660008b604001516001600160a01b03166001600160a01b0316815260200190815260200160002061233090919063ffffffff16565b6001600160401b031690612b73565b905060006110a1828660600151612358565b90506001600160401b0382166110e1576020808601516040808a01516001600160a01b0316600090815260069093529091206110dc91612bb5565b61110d565b6020808601516040808a01516001600160a01b03166000908152600690935290912061110d9184612d25565b60208086015160c0890151600c546001600160a01b031660009081526006909352604090922061117592611154916111459084612330565b6001600160401b031690612dfe565b600c546001600160a01b031660009081526006602052604090209190612d25565b60055460408089015160208801519151636ce5768960e11b81526001600160a01b039093169263d9caed12926111b0929188906004016150cb565b600060405180830381600087803b1580156111ca57600080fd5b505af11580156111de573d6000803e3d6000fd5b505050600087815260046020908152604091829020805460ff19166001179055870151898201518883015160a08c015193516001600160a01b03938416955091909216927f6960a1f64ecf9da0d1d1bcbfa3dd27f8c1c60de69b13faa28127dafa36c111e4926112549290919088908890615234565b60405180910390a350505050505050565b336001600160a01b037f0000000000000000000000006206bc17993abd8cd45ff191e20c0842bd92908616146112ad5760405162461bcd60e51b8152600401610607906152ed565b6001600160a01b0381166112fc5760405162461bcd60e51b8152602060048201526016602482015275496e76616c69642077616c6c6574206164647265737360501b6044820152606401610607565b6000546001600160a01b03828116911614156113665760405162461bcd60e51b8152602060048201526024808201527f4d75737420626520646966666572656e742066726f6d2063757272656e7420616044820152633236b4b760e11b6064820152608401610607565b600080546001600160a01b0319166001600160a01b0392909216919091179055565b6000546001600160a01b031633146113b25760405162461bcd60e51b81526004016106079061531b565b604051633ce8f24d60e11b8152732ae371fbb3d4a45ce174b5ca559974928133bd4c906379d1e49a906113f090600190879087908790600401615598565b60006040518083038186803b15801561140857600080fd5b505af415801561141c573d6000803e3d6000fd5b50505050826001600160a01b03167f7f63a63f4a2ea318da0b031794d0b5a3183a1b2de54e053ceb17cabdba031735838360405161145b9291906151e6565b60405180910390a2505050565b6000546001600160a01b031633146114925760405162461bcd60e51b81526004016106079061531b565b6001600160a01b0381166114b85760405162461bcd60e51b81526004016106079061526a565b6001600160a01b0381166000908152600b602052604090205460ff16156115105760405162461bcd60e51b815260206004820152600c60248201526b53616d65206164647265737360a01b6044820152606401610607565b6001600160a01b0381166000908152600b602052604090819020805460ff19166001179055517f10c2db09cf947efb59f8c4e821d8f419f975db6c94ffe5c8145bc6e8c91a0461906115639083906150b7565b60405180910390a150565b6001600160a01b0381166000908152600960205260408120805460ff16801561159b575043816001015411155b9392505050565b336000908152600b602052604090205460ff166115d15760405162461bcd60e51b815260040161060790615293565b815183511480156115e3575080518251145b6116215760405162461bcd60e51b815260206004820152600f60248201526e0d8cadccee8d040dad2e6dac2e8c6d608b1b6044820152606401610607565b82516116605760405162461bcd60e51b815260206004820152600e60248201526d6c656e677468206973207a65726f60901b6044820152606401610607565b60005b8351811015610b78576116ec84828151811061168f57634e487b7160e01b600052603260045260246000fd5b60200260200101518483815181106116b757634e487b7160e01b600052603260045260246000fd5b60200260200101518484815181106116df57634e487b7160e01b600052603260045260246000fd5b602002602001015161275d565b806116f681615ab6565b915050611663565b6040805160c081018252600080825260208201819052606082840181905282018190526080820181905260a0820152905163e970ba6b60e01b8152732ae371fbb3d4a45ce174b5ca559974928133bd4c9063e970ba6b9061176a90600190889088908890600401615603565b60006040518083038186803b15801561178257600080fd5b505af4158015611796573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526117be9190810190614d93565b949350505050565b6000546001600160a01b031633146117f05760405162461bcd60e51b81526004016106079061531b565b6001600160a01b0381166118165760405162461bcd60e51b81526004016106079061526a565b600c546001600160a01b03828116911614156118665760405162461bcd60e51b815260206004820152600f60248201526e14d85b5948199959481dd85b1b195d608a1b6044820152606401610607565b600c80546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527f9f4f5dce3c4d197b5d7496cb96e25f0a89809167195964b0daa3ef5fed63c00a91016109e9565b6001600160a01b038116600090815260066020526040812054606091906001600160401b0381111561190257634e487b7160e01b600052604160045260246000fd5b60405190808252806020026020018201604052801561192b578160200160208202803683370190505b50905060005b6001600160a01b0384166000908152600660205260409020548110156119c1576001600160a01b03841660009081526006602052604081206119739083612e6d565b90508083838151811061199657634e487b7160e01b600052603260045260246000fd5b6001600160a01b039092166020928302919091019091015250806119b981615ab6565b915050611931565b5092915050565b6119d13361156e565b6119ed5760405162461bcd60e51b8152600401610607906152bf565b604051638c4ae9e560e01b8152600090732ae371fbb3d4a45ce174b5ca559974928133bd4c90638c4ae9e590611a2a906001908690600401615581565b60006040518083038186803b158015611a4257600080fd5b505af4158015611a56573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052611a7e9190810190614d93565b33600090815260066020526040812091925090611a9b9084612330565b90506000611aad828460600151612358565b905060008111611aee5760405162461bcd60e51b815260206004820152600c60248201526b042616c616e636520697320360a41b6044820152606401610607565b336000908152600660205260409020611b079085612bb5565b600554604051636ce5768960e11b81526001600160a01b039091169063d9caed1290611b3b903390889086906004016150cb565b600060405180830381600087803b158015611b5557600080fd5b505af1158015611b69573d6000803e3d6000fd5b50505050836001600160a01b0316336001600160a01b0316600080516020615baf833981519152856040015185600080604051611ba99493929190615234565b60405180910390a350505050565b336001600160a01b037f0000000000000000000000006206bc17993abd8cd45ff191e20c0842bd9290861614611bff5760405162461bcd60e51b8152600401610607906152ed565b600080546001600160a01b0319169055565b6000611c1c82612ade565b9050611c26612eae565b6001600160401b0316816001600160401b031610611c805760405162461bcd60e51b81526020600482015260176024820152764e6f6e636520746f6f2066617220696e2066757475726560481b6044820152606401610607565b3360009081526007602052604090205460ff1615611d4e57336000908152600760205260409020546001600160401b038083166101009092041610611cf55760405162461bcd60e51b815260206004820152600b60248201526a125b9d985b1a59185d195960aa1b6044820152606401610607565b33600090815260076020526040902060010154431015611d4e5760405162461bcd60e51b815260206004820152601460248201527320bbb0b4ba34b73390383937b830b3b0ba34b7b760611b6044820152606401610607565b6000600a5443611d5e9190615855565b604080516060808201835260018083526001600160401b038781166020808601828152868801898152336000818152600785528a90209851895493516001600160481b0319909416901515610100600160481b0319161761010093909616929092029490941787559251959093019490945584516001600160801b038a16815291820193909352928301849052929350917f10cf19671b43c88b1f02d4e94932d7ffaa89c7278bc5b8868fa7b7676210809b910161145b565b60006001600160a01b038316611e3f5760405162461bcd60e51b81526004016106079061526a565b6001600160a01b038316600090815260066020526040902061159b9083612330565b6001600160a01b038216611e875760405162461bcd60e51b81526004016106079061526a565b611e923383836123f8565b5050565b3360009081526009602052604090205460ff1615611ec65760405162461bcd60e51b815260040161060790615378565b6040518060400160405280600115158152602001600a5443611ee89190615855565b90523360008181526009602090815260409091208351815460ff1916901515178155920151600190920191909155600a547fd60f9f7b2f1a208268475a927bd727c4e198fc8b40aab3004ebcc2bc78ca848090611f459043615855565b60405190815260200160405180910390a2565b60006001600160a01b038416611f805760405162461bcd60e51b81526004016106079061526a565b6000732ae371fbb3d4a45ce174b5ca559974928133bd4c63e970ba6b60018686611fa86123e3565b6040518563ffffffff1660e01b8152600401611fc79493929190615603565b60006040518083038186803b158015611fdf57600080fd5b505af4158015611ff3573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261201b9190810190614d93565b6020908101516001600160a01b0387166000908152600690925260409091209091506108b39082612330565b6120503361156e565b61206c5760405162461bcd60e51b8152600401610607906152bf565b336000908152600660205260408120546001600160401b038111156120a157634e487b7160e01b600052604160045260246000fd5b6040519080825280602002602001820160405280156120ca578160200160208202803683370190505b50905060005b336000908152600660205260409020548110156122cc573360009081526006602052604081206121009083612e6d565b90508083838151811061212357634e487b7160e01b600052603260045260246000fd5b6001600160a01b0390921660209283029190910190910152604051638c4ae9e560e01b8152600090732ae371fbb3d4a45ce174b5ca559974928133bd4c90638c4ae9e590612178906001908690600401615581565b60006040518083038186803b15801561219057600080fd5b505af41580156121a4573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526121cc9190810190614d93565b336000908152600660205260408120919250906121e99084612330565b905060006121fb828460600151612358565b90508061220b57505050506122ba565b600554604051636ce5768960e11b81526001600160a01b039091169063d9caed129061223f903390889086906004016150cb565b600060405180830381600087803b15801561225957600080fd5b505af115801561226d573d6000803e3d6000fd5b50505050836001600160a01b0316336001600160a01b0316600080516020615baf8339815191528560400151856000806040516122ad9493929190615234565b60405180910390a3505050505b806122c481615ab6565b9150506120d0565b5060005b8151811015611e925761231e8282815181106122fc57634e487b7160e01b600052603260045260246000fd5b6020908102919091018101513360009081526006909252604090912090612bb5565b8061232881615ab6565b9150506122d0565b6001600160a01b0316600090815260019190910160205260409020546001600160401b031690565b600060208260ff16111561237e5760405162461bcd60e51b815260040161060790615349565b60088260ff1611156123ba576123b3612398600884615a67565b6123a390600a61592f565b6001600160401b03851690612ed3565b90506107bb565b61159b6123c8836008615a67565b6123d390600a61592f565b6001600160401b03851690612edf565b60006103e86123f281426159f9565b91505090565b6001600160a01b03831660009081526009602052604090205460ff16156124315760405162461bcd60e51b815260040161060790615378565b604051638c4ae9e560e01b8152600090732ae371fbb3d4a45ce174b5ca559974928133bd4c90638c4ae9e59061246e906001908790600401615581565b60006040518083038186803b15801561248657600080fd5b505af415801561249a573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526124c29190810190614d93565b905060006124d4838360600151612eeb565b90506000816001600160401b0316116125255760405162461bcd60e51b81526020600482015260136024820152725175616e7469747920697320746f6f206c6f7760681b6044820152606401610607565b6000612535828460600151612358565b90506001600160a01b038516156125b25760405163251b43c960e21b8152731af7b032e507fc235be7d3d5fe56f39ccb7dceca9063946d0f2490612581908990899086906004016150cb565b60006040518083038186803b15801561259957600080fd5b505af41580156125ad573d6000803e3d6000fd5b505050505b6005546040516352f950a960e11b8152731af7b032e507fc235be7d3d5fe56f39ccb7dceca9163a5f2a152916125f8916001600160a01b031690899086906004016150cb565b60006040518083038186803b15801561261057600080fd5b505af4158015612624573d6000803e3d6000fd5b505050506001600160a01b03861660009081526006602052604081206126509084906111459089612330565b90506000612662828660600151612358565b6001600160a01b0389166000908152600660205260409020909150612688908884612d25565b60058054600160a01b90046001600160401b03169060146126a883615ad1565b91906101000a8154816001600160401b0302191690836001600160401b031602179055505084604001516040516126df919061509b565b6040518091039020876001600160a01b0316896001600160a01b03167fc2813a84168de59cf710030ba2acb13031b567b8ba76f70e8ab0782aa9469a7a600560149054906101000a90046001600160401b0316896040015189888860405161274b95949392919061572f565b60405180910390a45050505050505050565b61276a836040015161156e565b156127ae5760405162461bcd60e51b8152602060048201526014602482015273109d5e481dd85b1b195d08199a5b985b1a5e995960621b6044820152606401610607565b6127bb826040015161156e565b156128005760405162461bcd60e51b815260206004820152601560248201527414d95b1b081dd85b1b195d08199a5b985b1a5e9959605a1b6044820152606401610607565b81604001516001600160a01b031683604001516001600160a01b031614156128655760405162461bcd60e51b815260206004820152601860248201527714d95b198b5d1c98591a5b99c81b9bdd08185b1b1bddd95960421b6044820152606401610607565b612870838383612fb5565b61287b838383613594565b6128858383613742565b600080612893858585613829565b915091506128a083613854565b6128ad8583868487613b06565b6128b8858585613b23565b82602001516040516128ca919061509b565b604051908190038120845190916128e1919061509b565b60405180910390207fbd75b2e24c3d43ec9f10f3583265606c0a7d8b67d147f9dc48376b81456823a1876040015187604001518760000151886020015189608001518a60a001518b61018001518b8b604051612945999897969594939291906150ef565b60405180910390a35050505050565b60006127106107b7836129706001600160401b03871684613eee565b6001600160401b031690613f88565b60008073cb85f6b03ce6238786aeebca4581dd167fa6a11063f398aaf9846040518263ffffffff1660e01b81526004016129b99190615667565b60206040518083038186803b1580156129d157600080fd5b505af41580156129e5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a099190614c4e565b61010084015160408086015190516365a18acf60e01b815292935073cb85f6b03ce6238786aeebca4581dd167fa6a110926365a18acf92612a4e9286926004016151b5565b60206040518083038186803b158015612a6657600080fd5b505af4158015612a7a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a9e9190614c1a565b6107bb5760405162461bcd60e51b8152602060048201526011602482015270496e76616c6964207369676e617475726560781b6044820152606401610607565b6000600f604c83901c1660018114612b2a5760405162461bcd60e51b815260206004820152600f60248201526e135d5cdd081899481d8c4815555251608a1b6044820152606401610607565b610fff60301b601084901c1661ffff60201b603085901c1663ffffffff606086901c168183178117612b68650b1d069b540061108061271084615898565b979650505050505050565b600061159b83836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250613fc7565b6001600160a01b038116600090815260038301602052604090205460ff16612bdb575050565b6001600160a01b03811660009081526003830160209081526040808320805460ff191690556001808601835281842080546001600160401b0319169055600286019092528220548454909291612c3091615a28565b90506000846000018281548110612c5757634e487b7160e01b600052603260045260246000fd5b60009182526020808320909101546001600160a01b03908116808452600289019092526040808420879055908716835282209190915585549091508190869085908110612cb457634e487b7160e01b600052603260045260246000fd5b600091825260209091200180546001600160a01b0319166001600160a01b03929092169190911790558454859080612cfc57634e487b7160e01b600052603160045260246000fd5b600082815260209020810160001990810180546001600160a01b03191690550190555050505050565b6001600160a01b038216600090815260038401602052604090205460ff1615612d82576001600160a01b0382166000908152600184016020526040902080546001600160401b0383166001600160401b0319909116179055505050565b6001600160a01b03821660008181526003850160209081526040808320805460ff19166001908117909155878101835281842080546001600160401b0388166001600160401b031990911617905587546002890184529184208290558101875586835291200180546001600160a01b0319169091179055505050565b600080612e0b838561586d565b9050836001600160401b0316816001600160401b0316101561159b5760405162461bcd60e51b815260206004820152601b60248201527a536166654d6174683a206164646974696f6e206f766572666c6f7760281b6044820152606401610607565b6000826000018281548110612e9257634e487b7160e01b600052603260045260246000fd5b6000918252602090912001546001600160a01b03169392505050565b6000620151806103e880612ec2834261586d565b612ecc91906159f9565b9250505090565b600061159b82846159da565b600061159b82846158be565b600060208260ff161115612f115760405162461bcd60e51b815260040161060790615349565b600060088360ff161115612f4657612f3f612f2d600885615a67565b612f3890600a61592f565b8590612edf565b9050612f69565b612f66612f54846008615a67565b612f5f90600a61592f565b8590612ed3565b90505b600160401b811061159b5760405162461bcd60e51b8152602060048201526016602482015275506970207175616e74697479206f766572666c6f777360501b6044820152606401610607565b80606001516001600160a01b031681604001516001600160a01b0316141561302b5760405162461bcd60e51b815260206004820152602360248201527f426173652f71756f746520617373657473206d75737420626520646966666572604482015262195b9d60ea1b6064820152608401610607565b60006001732ae371fbb3d4a45ce174b5ca559974928133bd4c63e970ba6b9091846000015161305d8860200151612ade565b6040518463ffffffff1660e01b815260040161307b93929190615636565b60006040518083038186803b15801561309357600080fd5b505af41580156130a7573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526130cf9190810190614d93565b905060006001732ae371fbb3d4a45ce174b5ca559974928133bd4c63e970ba6b909185602001516131038960200151612ade565b6040518463ffffffff1660e01b815260040161312193929190615636565b60006040518083038186803b15801561313957600080fd5b505af415801561314d573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526131759190810190614d93565b905082604001516001600160a01b031682602001516001600160a01b03161480156131b9575082606001516001600160a01b031681602001516001600160a01b0316145b6132015760405162461bcd60e51b8152602060048201526019602482015278084eaf240dee4c8cae440e6f2dac4ded840dad2e6dac2e8c6d603b1b6044820152606401610607565b60006001732ae371fbb3d4a45ce174b5ca559974928133bd4c63e970ba6b909186600001516132338960200151612ade565b6040518463ffffffff1660e01b815260040161325193929190615636565b60006040518083038186803b15801561326957600080fd5b505af415801561327d573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526132a59190810190614d93565b905060006001732ae371fbb3d4a45ce174b5ca559974928133bd4c63e970ba6b909187602001516132d98a60200151612ade565b6040518463ffffffff1660e01b81526004016132f793929190615636565b60006040518083038186803b15801561330f57600080fd5b505af4158015613323573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261334b9190810190614d93565b905084604001516001600160a01b031682602001516001600160a01b031614801561338f575084606001516001600160a01b031681602001516001600160a01b0316145b6133d85760405162461bcd60e51b815260206004820152601a6024820152790a6cad8d840dee4c8cae440e6f2dac4ded840dad2e6dac2e8c6d60331b6044820152606401610607565b84604001516001600160a01b03168561010001516001600160a01b0316148061341b575084606001516001600160a01b03168561010001516001600160a01b0316145b6134735760405162461bcd60e51b8152602060048201526024808201527f4d616b657220666565206173736574206973206e6f7420696e207472616465206044820152633830b4b960e11b6064820152608401610607565b84604001516001600160a01b03168561012001516001600160a01b031614806134b6575084606001516001600160a01b03168561012001516001600160a01b0316145b61350e5760405162461bcd60e51b8152602060048201526024808201527f54616b657220666565206173736574206973206e6f7420696e207472616465206044820152633830b4b960e11b6064820152608401610607565b8461012001516001600160a01b03168561010001516001600160a01b0316141561358b5760405162461bcd60e51b815260206004820152602860248201527f4d616b65722f74616b65722066656520617373657473206d75737420626520646044820152671a5999995c995b9d60c21b6064820152608401610607565b50505050505050565b600081608001516001600160401b0316116135e65760405162461bcd60e51b8152602060048201526012602482015271042617365207175616e7469747920697320360741b6044820152606401610607565b60008160a001516001600160401b0316116136395760405162461bcd60e51b8152602060048201526013602482015272051756f7465207175616e74697479206973203606c1b6044820152606401610607565b613646836060015161400b565b156136bd578060a001516001600160401b031661366b82608001518560e001516140b4565b6001600160401b031610156136bd5760405162461bcd60e51b8152602060048201526018602482015277109d5e481b1a5b5a5d081c1c9a58d948195e18d95959195960421b6044820152606401610607565b6136ca826060015161400b565b15610d7e578060a001516001600160401b03166136ef82608001518460e001516140b4565b6001600160401b03161115610d7e5760405162461bcd60e51b815260206004820152601960248201527814d95b1b081b1a5b5a5d081c1c9a58d948195e18d959591959603a1b6044820152606401610607565b61374f826040015161412e565b6001600160401b03166137658360200151612ade565b6001600160401b0316116137b55760405162461bcd60e51b8152602060048201526017602482015276427579206f72646572206e6f6e636520746f6f206c6f7760481b6044820152606401610607565b6137c2816040015161412e565b6001600160401b03166137d88260200151612ade565b6001600160401b031611611e925760405162461bcd60e51b815260206004820152601860248201527753656c6c206f72646572206e6f6e636520746f6f206c6f7760401b6044820152606401610607565b600080600061383886856141a8565b9050600061384686866141a8565b919791965090945050505050565b600081604001516001600160a01b03168261010001516001600160a01b031614613882578160a00151613888565b81608001515b90506107d06001600160401b03166138a583610140015183612954565b6001600160401b031611156138f25760405162461bcd60e51b8152602060048201526013602482015272457863657373697665206d616b65722066656560681b6044820152606401610607565b600082604001516001600160a01b03168361012001516001600160a01b031614613920578260a00151613926565b82608001515b90506107d06001600160401b031661394384610160015183612954565b6001600160401b031611156139905760405162461bcd60e51b81526020600482015260136024820152724578636573736976652074616b65722066656560681b6044820152606401610607565b82608001516001600160401b03166139e984604001516001600160a01b03168561010001516001600160a01b0316146139ce578461016001516139d5565b8461014001515b60c08601516001600160401b031690612dfe565b6001600160401b031614613a4b5760405162461bcd60e51b815260206004820152602360248201527f4261736520706c757320666565206973206e6f7420657175616c20746f2067726044820152626f737360e81b6064820152608401610607565b8260a001516001600160401b0316613aa484606001516001600160a01b03168561010001516001600160a01b031614613a8957846101600151613a90565b8461014001515b60e08601516001600160401b031690612dfe565b6001600160401b031614610d7e5760405162461bcd60e51b8152602060048201526024808201527f51756f746520706c757320666565206973206e6f7420657175616c20746f2067604482015263726f737360e01b6064820152608401610607565b613b11858583614390565b613b1c838383614390565b5050505050565b6080810151604080830151848201516001600160a01b0316600090815260066020529182209192613b5a9290916110809190612330565b90506001600160401b03811615613b9b57604080830151848201516001600160a01b03166000908152600660205291909120613b969183612d25565b613bc5565b604080830151848201516001600160a01b03166000908152600660205291909120613bc591612bb5565b60c0820151604080840151868201516001600160a01b0316600090815260066020529182209192613bfc9290916111459190612330565b90506001600160401b03811615613c3d57604080840151868201516001600160a01b03166000908152600660205291909120613c389183612d25565b613c67565b604080840151868201516001600160a01b03166000908152600660205291909120613c6791612bb5565b60a083015160608401516040808801516001600160a01b0316600090815260066020529081209092613c9e92909161108091612330565b90506001600160401b03811615613cdd5760608401516040808801516001600160a01b03166000908152600660205220613cd89183612d25565b613d05565b60608401516040808801516001600160a01b03166000908152600660205220613d0591612bb5565b60e084015160608501516040808801516001600160a01b0316600090815260066020529081209092613d3c92909161114591612330565b90506001600160401b03811615613d7b5760608501516040808801516001600160a01b03166000908152600660205220613d769183612d25565b613da3565b60608501516040808801516001600160a01b03166000908152600660205220613da391612bb5565b610140850151610100860151600c546001600160a01b031660009081526006602052604081209092613dda92909161114591612330565b90506001600160401b03811615613e1a57610100860151600c546001600160a01b03166000908152600660205260409020613e159183612d25565b613e43565b610100860151600c546001600160a01b03166000908152600660205260409020613e4391612bb5565b610160860151610120870151600c546001600160a01b031660009081526006602052604081209092613e7a92909161114591612330565b90506001600160401b03811615613eba57610120870151600c546001600160a01b03166000908152600660205260409020613eb59183612d25565b613ee3565b610120870151600c546001600160a01b03166000908152600660205260409020613ee391612bb5565b505050505050505050565b60006001600160401b038316613f06575060006107bb565b6000613f1283856159f9565b90506001600160401b038316613f2885836158d2565b6001600160401b03161461159b5760405162461bcd60e51b815260206004820152602160248201527f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f6044820152607760f81b6064820152608401610607565b600061159b83836040518060400160405280601a815260200179536166654d6174683a206469766973696f6e206279207a65726f60301b815250614569565b6000836001600160401b0316836001600160401b031611158290613ffe5760405162461bcd60e51b81526004016106079190615221565b5060006108b38486615a3f565b6000600182600681111561402f57634e487b7160e01b600052602160045260246000fd5b148061405a5750600282600681111561405857634e487b7160e01b600052602160045260246000fd5b145b806140845750600482600681111561408257634e487b7160e01b600052602160045260246000fd5b145b806107bb575060065b8260068111156140ad57634e487b7160e01b600052602160045260246000fd5b1492915050565b60006305f5e100816140dc826140d66001600160401b03888116908816612ed3565b90612edf565b9050600160401b81106107b75760405162461bcd60e51b815260206004820152601a602482015279496d706c696564207175616e74697479206f766572666c6f777360301b6044820152606401610607565b6001600160a01b03811660009081526007602052604081205460ff16801561417157506001600160a01b0382166000908152600760205260409020600101544310155b156141a057506001600160a01b031660009081526007602052604090205461010090046001600160401b031690565b506000919050565b80516020820151604051632779981560e11b8152600092839273cb85f6b03ce6238786aeebca4581dd167fa6a11092634ef3302a926141eb928992600401615410565b60206040518083038186803b15801561420357600080fd5b505af4158015614217573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061423b9190614c4e565b6101a085015160408087015190516365a18acf60e01b815292935073cb85f6b03ce6238786aeebca4581dd167fa6a110926365a18acf926142809286926004016151b5565b60206040518083038186803b15801561429857600080fd5b505af41580156142ac573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906142d09190614c1a565b6000856080015160018111156142f657634e487b7160e01b600052602160045260246000fd5b14614335576040518060400160405280601c81526020017b496e76616c69642073656c6c206f72646572207369676e617475726560201b81525061436a565b6040518060400160405280601b81526020017a496e76616c696420627579206f72646572207369676e617475726560281b8152505b906143885760405162461bcd60e51b81526004016106079190615221565b509392505050565b60008281526003602052604090205460ff16156143df5760405162461bcd60e51b815260206004820152600d60248201526c111bdd589b1948199a5b1b1959609a1b6044820152606401610607565b60008360c0015115614469576143f884606001516145a0565b6144385760405162461bcd60e51b81526020600482015260116024820152704e6f74206d61726b6574206f726465727360781b6044820152606401610607565b60008381526008602052604090205460a0830151614462916001600160401b039182169116612dfe565b9050614496565b6000838152600860205260409020546080830151614493916001600160401b039182169116612dfe565b90505b8360a001516001600160401b0316816001600160401b031611156144e95760405162461bcd60e51b815260206004820152600a60248201526913dd995c999a5b1b195960b21b6044820152606401610607565b8360a001516001600160401b0316816001600160401b0316101561453357600083815260086020526040902080546001600160401b0319166001600160401b038316179055610b78565b5050600090815260086020908152604080832080546001600160401b031916905560039091529020805460ff1916600117905550565b6000816001600160401b0384166145935760405162461bcd60e51b81526004016106079190615221565b5060006108b384866158d2565b6000808260068111156145c357634e487b7160e01b600052602160045260246000fd5b14806145ee575060038260068111156145ec57634e487b7160e01b600052602160045260246000fd5b145b806107bb5750600561408d565b803561460681615b64565b919050565b600082601f83011261461b578081fd5b8135602061463061462b8361580b565b6157db565b80838252828201915082860187848660051b890101111561464f578586fd5b855b8581101561468f5781356001600160401b0381111561466e578788fd5b61467c8a87838c01016147b4565b8552509284019290840190600101614651565b5090979650505050505050565b803561460681615b7c565b805161460681615b7c565b600082601f8301126146c2578081fd5b81356146d061462b8261582e565b8181528460208386010111156146e4578283fd5b816020850160208301379081016020019190915292915050565b80356004811061460657600080fd5b80356002811061460657600080fd5b80356007811061460657600080fd5b60008083601f84011261473c578182fd5b5081356001600160401b03811115614752578182fd5b60208301915083602082850101111561476a57600080fd5b9250929050565b600082601f830112614781578081fd5b815161478f61462b8261582e565b8181528460208386010111156147a3578283fd5b6107b7826020830160208701615a8a565b60006101c082840312156147c6578081fd5b6147ce61576d565b90506147d982614a6a565b81526147e760208301614a3d565b60208201526147f8604083016145fb565b60408201526148096060830161471c565b606082015261481a6080830161470d565b608082015261482b60a08301614a54565b60a082015261483c60c0830161469c565b60c082015261484d60e08301614a54565b60e0820152610100614860818401614a54565b90820152610120828101356001600160401b038082111561488057600080fd5b61488c868387016146b2565b8385015261014092506148a08386016146fe565b8385015261016092506148b48386016146fe565b8385015261018092506148c8838601614a54565b838501526101a09250828501359150808211156148e457600080fd5b506148f1858286016146b2565b82840152505092915050565b60006101c0828403121561490f578081fd5b61491761576d565b905081356001600160401b038082111561493057600080fd5b61493c858386016146b2565b8352602084013591508082111561495257600080fd5b5061495f848285016146b2565b602083015250614971604083016145fb565b6040820152614982606083016145fb565b606082015261499360808301614a54565b60808201526149a460a08301614a54565b60a08201526149b560c08301614a54565b60c08201526149c660e08301614a54565b60e08201526101006149d98184016145fb565b908201526101206149eb8382016145fb565b908201526101406149fd838201614a54565b90820152610160614a0f838201614a54565b90820152610180614a21838201614a54565b908201526101a0614a3383820161470d565b9082015292915050565b80356001600160801b038116811461460657600080fd5b803561460681615b8a565b805161460681615b8a565b803561460681615b9f565b805161460681615b9f565b600060208284031215614a91578081fd5b813561159b81615b64565b60008060408385031215614aae578081fd5b8235614ab981615b64565b91506020830135614ac981615b64565b809150509250929050565b600080600060408486031215614ae8578081fd5b8335614af381615b64565b925060208401356001600160401b03811115614b0d578182fd5b614b198682870161472b565b9497909650939450505050565b600080600060608486031215614b3a578081fd5b83356001600160401b0380821115614b50578283fd5b614b5c8783880161460b565b9450602091508186013581811115614b72578384fd5b614b7e8882890161460b565b945050604086013581811115614b92578384fd5b8601601f81018813614ba2578384fd5b8035614bb061462b8261580b565b8082825285820191508584018b878560051b8701011115614bcf578788fd5b875b84811015614c0857813587811115614be757898afd5b614bf58e8a838a01016148fd565b8552509287019290870190600101614bd1565b50508096505050505050509250925092565b600060208284031215614c2b578081fd5b815161159b81615b7c565b600060208284031215614c47578081fd5b5035919050565b600060208284031215614c5f578081fd5b5051919050565b60008060008060608587031215614c7b578182fd5b8435614c8681615b64565b935060208501356001600160401b03811115614ca0578283fd5b614cac8782880161472b565b9094509250506040850135614cc081615b9f565b939692955090935050565b60008060408385031215614cdd578182fd5b8235614ce881615b64565b946020939093013593505050565b600080600060408486031215614d0a578081fd5b83356001600160401b03811115614d1f578182fd5b614d2b8682870161472b565b909790965060209590950135949350505050565b600080600060408486031215614d53578081fd5b83356001600160401b03811115614d68578182fd5b614d748682870161472b565b9094509250506020840135614d8881615b8a565b809150509250925092565b600060208284031215614da4578081fd5b81516001600160401b0380821115614dba578283fd5b9083019060c08286031215614dcd578283fd5b614dd5615796565b8251614de081615b7c565b81526020830151614df081615b64565b6020820152604083015182811115614e06578485fd5b614e1287828601614771565b604083015250614e2460608401614a75565b6060820152614e35608084016146a7565b6080820152614e4660a08401614a5f565b60a082015295945050505050565b600080600060608486031215614e68578081fd5b83356001600160401b0380821115614e7e578283fd5b614e8a878388016147b4565b94506020860135915080821115614e9f578283fd5b614eab878388016147b4565b93506040860135915080821115614ec0578283fd5b50614ecd868287016148fd565b9150509250925092565b600060208284031215614ee8578081fd5b81356001600160401b0380821115614efe578283fd5b908301906101208286031215614f12578283fd5b614f1a6157b8565b614f238361470d565b8152614f3160208401614a3d565b6020820152614f42604084016145fb565b6040820152606083013582811115614f58578485fd5b614f64878286016146b2565b606083015250614f76608084016145fb565b6080820152614f8760a08401614a54565b60a0820152614f9860c08401614a54565b60c0820152614fa960e0840161469c565b60e08201526101008084013583811115614fc1578586fd5b614fcd888287016146b2565b918301919091525095945050505050565b600060208284031215614fef578081fd5b61159b82614a3d565b6001600160a01b03169052565b6000815180845261501d816020860160208601615a8a565b601f01601f19169290920160200192915050565b6004811061504157615041615b38565b9052565b6002811061504157615041615b38565b6007811061504157615041615b38565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b6001600160401b03169052565b600082516150ad818460208701615a8a565b9190910192915050565b6001600160a01b0391909116815260200190565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6001600160a01b038a81168252891660208201526101206040820181905260009061511c8382018b615005565b90508281036060840152615130818a615005565b6001600160401b03988916608085015296881660a084015250509290941660c083015260e08201526101000191909152949350505050565b6020808252825182820181905260009190848201906040850190845b818110156151a95783516001600160a01b031683529284019291840191600101615184565b50909695505050505050565b8381526060602082015260006151ce6060830185615005565b905060018060a01b0383166040830152949350505050565b6020815260006117be602083018486615065565b60408152600061520e604083018587615065565b905060ff83166020830152949350505050565b60208152600061159b6020830184615005565b6080815260006152476080830187615005565b6001600160401b0395861660208401529390941660408201526060015292915050565b6020808252600f908201526e496e76616c6964206164647265737360881b604082015260600190565b60208082526012908201527126bab9ba103132903234b9b830ba31b432b960711b604082015260600190565b60208082526014908201527315d85b1b195d081b9bdd08199a5b985b1a5e995960621b604082015260600190565b60208082526014908201527321b0b63632b91036bab9ba1031329037bbb732b960611b604082015260600190565b60208082526014908201527321b0b63632b91036bab9ba1031329030b236b4b760611b604082015260600190565b6020808252601590820152744d6f7265207468616e20333220646563696d616c7360581b604082015260600190565b6020808252600d908201526c15d85b1b195d08195e1a5d1959609a1b604082015260600190565b6020815281511515602082015260018060a01b0360208301511660408201526000604083015160c060608401526153d960e0840182615005565b905060ff60608501511660808401526080840151151560a084015260018060401b0360a08501511660c08401528091505092915050565b6060815261542460608201855160ff169052565b6000602085015161544060808401826001600160801b03169052565b50604085015161545360a0840182614ff8565b50606085015161546660c0840182615055565b50608085015161547960e0840182615045565b5060a085015161010061548e8185018361508e565b60c087015191506101206154a58186018415159052565b60e088015192506101406154bb8187018561508e565b918801519250610160916154d18684018561508e565b8189015193506101c0915061018082818801526154f2610220880186615005565b918a01519194506101a061550888820184615031565b938a01519361551988850186615031565b818b0151945061552d6101e089018661508e565b808b0151945050505050605f198483030161020085015261554e8282615005565b91505082810360208401526155638186615005565b905082810360408401526155778185615005565b9695505050505050565b9182526001600160a01b0316602082015260400190565b8481526001600160a01b03841660208201526060604082018190526000906155779083018486615065565b8581526001600160a01b03851660208201526080604082018190526000906155ee9083018587615065565b905060ff831660608301529695505050505050565b84815260606020820152600061561d606083018587615065565b905060018060401b038316604083015295945050505050565b83815260606020820152600061564f6060830185615005565b905060018060401b0383166040830152949350505050565b60208152615679602082018351615045565b6000602083015161569560408401826001600160801b03169052565b5060408301516156a86060840182614ff8565b5060608301516101208060808501526156c5610140850183615005565b915060808501516156d960a0860182614ff8565b5060a08501516156ec60c086018261508e565b5060c08501516156ff60e086018261508e565b5060e08501516101006157158187018315159052565b860151858403601f19018387015290506155778382615005565b600060018060401b03808816835260a0602084015261575160a0840188615005565b9581166040840152939093166060820152608001525092915050565b6040516101c081016001600160401b038111828210171561579057615790615b4e565b60405290565b60405160c081016001600160401b038111828210171561579057615790615b4e565b60405161012081016001600160401b038111828210171561579057615790615b4e565b604051601f8201601f191681016001600160401b038111828210171561580357615803615b4e565b604052919050565b60006001600160401b0382111561582457615824615b4e565b5060051b60200190565b60006001600160401b0382111561584757615847615b4e565b50601f01601f191660200190565b6000821982111561586857615868615b0c565b500190565b60006001600160401b0382811684821680830382111561588f5761588f615b0c565b01949350505050565b60006001600160801b03838116806158b2576158b2615b22565b92169190910492915050565b6000826158cd576158cd615b22565b500490565b60006001600160401b03838116806158b2576158b2615b22565b600181815b8085111561592757816000190482111561590d5761590d615b0c565b8085161561591a57918102915b93841c93908002906158f1565b509250929050565b600061159b60ff841683600082615948575060016107bb565b81615955575060006107bb565b816001811461596b576002811461597557615991565b60019150506107bb565b60ff84111561598657615986615b0c565b50506001821b6107bb565b5060208310610133831016604e8410600b84101617156159b4575081810a6107bb565b6159be83836158ec565b80600019048211156159d2576159d2615b0c565b029392505050565b60008160001904831182151516156159f4576159f4615b0c565b500290565b60006001600160401b0382811684821681151582840482111615615a1f57615a1f615b0c565b02949350505050565b600082821015615a3a57615a3a615b0c565b500390565b60006001600160401b0383811690831681811015615a5f57615a5f615b0c565b039392505050565b600060ff821660ff841680821015615a8157615a81615b0c565b90039392505050565b60005b83811015615aa5578181015183820152602001615a8d565b83811115610b785750506000910152565b6000600019821415615aca57615aca615b0c565b5060010190565b60006001600160401b0382811680821415615aee57615aee615b0c565b6001019392505050565b600082615b0757615b07615b22565b500690565b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052601260045260246000fd5b634e487b7160e01b600052602160045260246000fd5b634e487b7160e01b600052604160045260246000fd5b6001600160a01b0381168114615b7957600080fd5b50565b8015158114615b7957600080fd5b6001600160401b0381168114615b7957600080fd5b60ff81168114615b7957600080fdfe02e03f232c88c87047c5a9cfbad1213b842503fee6a002ec2eec9f5a64725bf4a26469706673582212204180e530e71c579ad804a4288880ec23e43ec397831567cb923c2140d35a7f6964736f6c63430008040033

Block Transaction Gas Used Reward
view all blocks produced
Age Block Fee Address BC Fee Address Voting Power Jailed Incoming
View All Validatorset

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
[ Download: CSV Export  ]
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.