Contract Address Details

0x83cA7023c4B1EDB137E1d87B3D05F20fbF6c893B

HoprChannels Last Balance Update: Block #12778658
Created by 0xa187–c09fc5 at 0xd635–b1c2e0

Balance

0 xDAI

(@ /xDAI)

Fetching tokens...

Contract name:
HoprChannels




Optimization enabled
true
Compiler version
v0.6.6+commit.6c089d02




Optimization runs
200
EVM Version
default

Constructor Arguments

0000000000000000000000003cd4b4d97dcad4ee772bc4f0fb0e7605fc86a85b000000000000000000000000000000000000000000000000000000000000003c

Arg [0] (address) : 0x3cd4b4d97dcad4ee772bc4f0fb0e7605fc86a85b
Arg [1] (uint256) : 60

              

Contract source code

/**
* Submitted for verification at blockscout.com on 2020-09-04 10:17:50.862801Z
*/
// File: @openzeppelin/contracts/introspection/IERC1820Registry.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
/**
* @dev Interface of the global ERC1820 Registry, as defined in the
* https://eips.ethereum.org/EIPS/eip-1820[EIP]. Accounts may register
* implementers for interfaces in this registry, as well as query support.
*
* Implementers may be shared by multiple accounts, and can also implement more
* than a single interface for each account. Contracts can implement interfaces
* for themselves, but externally-owned accounts (EOA) must delegate this to a
* contract.
*
* {IERC165} interfaces can also be queried via the registry.
*
* For an in-depth explanation and source code analysis, see the EIP text.
*/
interface IERC1820Registry {
/**
* @dev Sets `newManager` as the manager for `account`. A manager of an
* account is able to set interface implementers for it.
*
* By default, each account is its own manager. Passing a value of `0x0` in
* `newManager` will reset the manager to this initial state.
*
* Emits a {ManagerChanged} event.
*
* Requirements:
*
* - the caller must be the current manager for `account`.
*/
function setManager(address account, address newManager) external;
/**
* @dev Returns the manager for `account`.
*
* See {setManager}.
*/
function getManager(address account) external view returns (address);
/**
* @dev Sets the `implementer` contract as ``account``'s implementer for
* `interfaceHash`.
*
* `account` being the zero address is an alias for the caller's address.
* The zero address can also be used in `implementer` to remove an old one.
*
* See {interfaceHash} to learn how these are created.
*
* Emits an {InterfaceImplementerSet} event.
*
* Requirements:
*
* - the caller must be the current manager for `account`.
* - `interfaceHash` must not be an {IERC165} interface id (i.e. it must not
* end in 28 zeroes).
* - `implementer` must implement {IERC1820Implementer} and return true when
* queried for support, unless `implementer` is the caller. See
* {IERC1820Implementer-canImplementInterfaceForAddress}.
*/
function setInterfaceImplementer(address account, bytes32 interfaceHash, address implementer) external;
/**
* @dev Returns the implementer of `interfaceHash` for `account`. If no such
* implementer is registered, returns the zero address.
*
* If `interfaceHash` is an {IERC165} interface id (i.e. it ends with 28
* zeroes), `account` will be queried for support of it.
*
* `account` being the zero address is an alias for the caller's address.
*/
function getInterfaceImplementer(address account, bytes32 interfaceHash) external view returns (address);
/**
* @dev Returns the interface hash for an `interfaceName`, as defined in the
* corresponding
* https://eips.ethereum.org/EIPS/eip-1820#interface-name[section of the EIP].
*/
function interfaceHash(string calldata interfaceName) external pure returns (bytes32);
/**
* @notice Updates the cache with whether the contract implements an ERC165 interface or not.
* @param account Address of the contract for which to update the cache.
* @param interfaceId ERC165 interface for which to update the cache.
*/
function updateERC165Cache(address account, bytes4 interfaceId) external;
/**
* @notice Checks whether a contract implements an ERC165 interface or not.
* If the result is not cached a direct lookup on the contract address is performed.
* If the result is not cached or the cached value is out-of-date, the cache MUST be updated manually by calling
* {updateERC165Cache} with the contract address.
* @param account Address of the contract to check.
* @param interfaceId ERC165 interface to check.
* @return True if `account` implements `interfaceId`, false otherwise.
*/
function implementsERC165Interface(address account, bytes4 interfaceId) external view returns (bool);
/**
* @notice Checks whether a contract implements an ERC165 interface or not without using nor updating the cache.
* @param account Address of the contract to check.
* @param interfaceId ERC165 interface to check.
* @return True if `account` implements `interfaceId`, false otherwise.
*/
function implementsERC165InterfaceNoCache(address account, bytes4 interfaceId) external view returns (bool);
event InterfaceImplementerSet(address indexed account, bytes32 indexed interfaceHash, address indexed implementer);
event ManagerChanged(address indexed account, address indexed newManager);
}
// File: @openzeppelin/contracts/introspection/IERC1820Implementer.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
/**
* @dev Interface for an ERC1820 implementer, as defined in the
* https://eips.ethereum.org/EIPS/eip-1820#interface-implementation-erc1820implementerinterface[EIP].
* Used by contracts that will be registered as implementers in the
* {IERC1820Registry}.
*/
interface IERC1820Implementer {
/**
* @dev Returns a special value (`ERC1820_ACCEPT_MAGIC`) if this contract
* implements `interfaceHash` for `account`.
*
* See {IERC1820Registry-setInterfaceImplementer}.
*/
function canImplementInterfaceForAddress(bytes32 interfaceHash, address account) external view returns (bytes32);
}
// File: @openzeppelin/contracts/introspection/ERC1820Implementer.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
/**
* @dev Implementation of the {IERC1820Implementer} interface.
*
* Contracts may inherit from this and call {_registerInterfaceForAddress} to
* declare their willingness to be implementers.
* {IERC1820Registry-setInterfaceImplementer} should then be called for the
* registration to be complete.
*/
contract ERC1820Implementer is IERC1820Implementer {
bytes32 constant private _ERC1820_ACCEPT_MAGIC = keccak256(abi.encodePacked("ERC1820_ACCEPT_MAGIC"));
mapping(bytes32 => mapping(address => bool)) private _supportedInterfaces;
/**
* See {IERC1820Implementer-canImplementInterfaceForAddress}.
*/
function canImplementInterfaceForAddress(bytes32 interfaceHash, address account) public view override returns (bytes32) {
return _supportedInterfaces[interfaceHash][account] ? _ERC1820_ACCEPT_MAGIC : bytes32(0x00);
}
/**
* @dev Declares the contract as willing to be an implementer of
* `interfaceHash` for `account`.
*
* See {IERC1820Registry-setInterfaceImplementer} and
* {IERC1820Registry-interfaceHash}.
*/
function _registerInterfaceForAddress(bytes32 interfaceHash, address account) internal virtual {
_supportedInterfaces[interfaceHash][account] = true;
}
}
// File: @openzeppelin/contracts/token/ERC20/IERC20.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev 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);
/**
* @dev 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);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
/**
* @dev 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);
/**
* @dev 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);
}
// File: @openzeppelin/contracts/token/ERC777/IERC777Recipient.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
/**
* @dev Interface of the ERC777TokensRecipient standard as defined in the EIP.
*
* Accounts can be notified of {IERC777} tokens being sent to them by having a
* contract implement this interface (contract holders can be their own
* implementer) and registering it on the
* https://eips.ethereum.org/EIPS/eip-1820[ERC1820 global registry].
*
* See {IERC1820Registry} and {ERC1820Implementer}.
*/
interface IERC777Recipient {
/**
* @dev Called by an {IERC777} token contract whenever tokens are being
* moved or created into a registered account (`to`). The type of operation
* is conveyed by `from` being the zero address or not.
*
* This call occurs _after_ the token contract's state is updated, so
* {IERC777-balanceOf}, etc., can be used to query the post-operation state.
*
* This function may revert to prevent the operation from being executed.
*/
function tokensReceived(
address operator,
address from,
address to,
uint256 amount,
bytes calldata userData,
bytes calldata operatorData
) external;
}
// File: @openzeppelin/contracts/math/SafeMath.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.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 SafeMath {
/**
* @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) {
uint256 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(uint256 a, uint256 b) internal pure returns (uint256) {
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.
*/
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 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(uint256 a, uint256 b) internal pure returns (uint256) {
// 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;
}
uint256 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(uint256 a, uint256 b) internal pure returns (uint256) {
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.
*/
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b > 0, errorMessage);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts 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 mod(a, b, "SafeMath: modulo by zero");
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts with custom message 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, string memory errorMessage) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
}
// File: @openzeppelin/contracts/utils/Address.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.2;
/**
* @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) {
// According to EIP-1052, 0x0 is the value returned for not-yet created accounts
// and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
// for accounts without code, i.e. `keccak256('')`
bytes32 codehash;
bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
// solhint-disable-next-line no-inline-assembly
assembly { codehash := extcodehash(account) }
return (codehash != accountHash && codehash != 0x0);
}
/**
* @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");
// solhint-disable-next-line avoid-low-level-calls, avoid-call-value
(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");
return _functionCallWithValue(target, data, value, errorMessage);
}
function _functionCallWithValue(address target, bytes memory data, uint256 weiValue, string memory errorMessage) private returns (bytes memory) {
require(isContract(target), "Address: call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.call{ value: weiValue }(data);
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
// solhint-disable-next-line no-inline-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
// File: @openzeppelin/contracts/token/ERC20/SafeERC20.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using SafeMath for uint256;
using Address for address;
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(IERC20 token, address spender, uint256 value) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
// solhint-disable-next-line max-line-length
require((value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender).add(value);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) { // Return data is optional
// solhint-disable-next-line max-line-length
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
// File: contracts/utils/ECDSA.sol
pragma solidity ^0.6.0;
// SPDX-License-Identifier: LGPL-3.0-only
/**
* @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 {
// y^2 = x^3 + 7 mod p, where p is FIELD_ORDER
uint256 constant FIELD_ORDER = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f;
uint256 constant CURVE_ORDER = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;
uint256 constant HALF_CURVE_ORDER = (CURVE_ORDER - 1) / 2;
/**
* @dev Computes the Ethereum address from a public key given as an
* uncompressed EC-point.
*/
function pubKeyToEthereumAddress(uint256 x, uint256 y) internal pure returns (address) {
require(validate(x, y), "Point must be on the curve.");
return address(bytes20(bytes32(keccak256(abi.encodePacked(x, y)) << 96)));
}
/**
* @dev Computes the Ethereum address from a public key given as a
* compressed EC-point.
*/
function compressedPubKeyToEthereumAddress(uint256 compressedX, uint8 odd) internal returns (address) {
(uint256 x, uint256 y) = decompress(compressedX, odd);
return pubKeyToEthereumAddress(x, y);
}
function compress(uint256 x, uint256 y) internal pure returns (uint256, uint8) {
return (x, uint8(y % 2));
}
/**
* @dev Decompresses a compressed elliptic curve point and
* returns the uncompressed version.
* @notice secp256k1: y^2 = x^3 + 7 (mod p)
* "Converts from (x, 1 / 0) to (x,y)"
*/
function decompress(uint256 x, uint8 odd) internal returns (uint256, uint256) {
uint256 sqrY = addmod(7, mulmod(mulmod(x, x, FIELD_ORDER), x, FIELD_ORDER), FIELD_ORDER);
uint256 sqrtExponent = (FIELD_ORDER + 1) / 4;
uint256 y;
/* solhint-disable no-inline-assembly */
assembly {
// free memory pointer
let memPtr := mload(0x40)
// length of base, exponent, modulus
mstore(memPtr, 0x20)
mstore(add(memPtr, 0x20), 0x20)
mstore(add(memPtr, 0x40), 0x20)
// assign base, exponent, modulus
mstore(add(memPtr, 0x60), sqrY)
mstore(add(memPtr, 0x80), sqrtExponent)
mstore(add(memPtr, 0xa0), FIELD_ORDER)
// call the precompiled contract BigModExp (0x05)
let success := call(gas(), 0x05, 0x0, memPtr, 0xc0, memPtr, 0x20)
switch success
case 0 {
revert(0x0, 0x0)
}
default {
y := mload(memPtr)
}
}
/* solhint-enable no-inline-assembly */
bool isOdd = y % 2 == 1;
if ((isOdd && odd == 0) || (!isOdd && odd == 1)) {
y = FIELD_ORDER - y;
}
return (x, y);
}
function validate(uint256 x, uint256 y) public pure returns (bool) {
uint256 rightHandSide = addmod(7, mulmod(mulmod(x, x, FIELD_ORDER), x, FIELD_ORDER), FIELD_ORDER);
uint256 leftHandSide = mulmod(y, y, FIELD_ORDER);
return leftHandSide == rightHandSide;
}
/**
* @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,
bytes32 r,
bytes32 s,
uint8 v
) internal pure returns (address) {
// 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 (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): 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) > HALF_CURVE_ORDER) {
revert("ECDSA: invalid signature 's' value");
}
if (v != 27 && v != 28) {
revert("ECDSA: invalid signature 'v' value");
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
require(signer != address(0), "ECDSA: invalid signature");
return signer;
}
/**
* @dev Returns an Ethereum Signed Message, created from a `message`. This
* replicates the behavior of the
* https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign[`eth_sign`]
* JSON-RPC method.
*
* See {recover}.
*/
function toEthSignedMessageHash(string memory length, bytes memory message) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", length, message));
}
}
// File: contracts/HoprChannels.sol
pragma solidity ^0.6.0;
// SPDX-License-Identifier: LGPL-3.0-only
contract HoprChannels is IERC777Recipient, ERC1820Implementer {
using SafeMath for uint256;
// an account has set a new secret hash
event SecretHashSet(address indexed account, bytes27 secretHash, uint32 counter);
// the payment channel has been funded
struct Account {
uint256 accountX; // second part of account's public key
bytes27 hashedSecret; // account's hashedSecret
uint32 counter; // increases everytime 'setHashedSecret' is called by the account
uint8 oddY;
}
enum ChannelStatus {UNINITIALISED, FUNDED, OPEN, PENDING}
struct Channel {
uint96 deposit; // tokens in the deposit
uint96 partyABalance; // tokens that are claimable by party 'A'
uint40 closureTime; // the time when the channel can be closed by either party
uint24 stateCounter;
/* stateCounter mod 10 == 0: uninitialised
* stateCounter mod 10 == 1: funding
* stateCounter mod 10 == 2: open
* stateCounter mod 10 == 3: pending
*/
}
// setup ERC1820
IERC1820Registry internal constant _ERC1820_REGISTRY = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24);
bytes32 public constant TOKENS_RECIPIENT_INTERFACE_HASH = keccak256("ERC777TokensRecipient");
// @TODO: update this whenever adding / removing states.
uint8 constant NUMBER_OF_STATES = 4;
IERC20 public token; // the token that will be used to settle payments
uint256 public secsClosure; // seconds it takes to allow closing of channel after channel's -
// initiated channel closure, in case counter-party does not act -
// within this time period
// store accounts' state
mapping(address => Account) public accounts;
// store channels' state e.g: channels[hash(party_a, party_b)]
mapping(bytes32 => Channel) public channels;
constructor(IERC20 _token, uint256 _secsClosure) public {
token = _token;
require(_secsClosure < (1 << 40), "HoprChannels: Closure timeout must be strictly smaller than 2**40");
secsClosure = _secsClosure;
_ERC1820_REGISTRY.setInterfaceImplementer(address(this), TOKENS_RECIPIENT_INTERFACE_HASH, address(this));
}
/**
* @notice sets caller's hashedSecret
* @param hashedSecret bytes27 hashedSecret to store
*/
function setHashedSecret(bytes27 hashedSecret) external {
require(hashedSecret != bytes27(0), "HoprChannels: hashedSecret is empty");
Account storage account = accounts[msg.sender];
require(account.accountX != uint256(0), "HoprChannels: msg.sender must have called init() before");
require(account.hashedSecret != hashedSecret, "HoprChannels: new and old hashedSecrets are the same");
require(account.counter + 1 < (1 << 32), "HoprChannels: Preventing account counter overflow");
account.hashedSecret = hashedSecret;
account.counter += 1;
emit SecretHashSet(msg.sender, hashedSecret, account.counter);
}
/**
* Initialize the account's on-chain variables.
*
* @param senderX uint256 first component of msg.sender's public key
* @param senderY uint256 second component of msg.sender's public key
* @param hashedSecret initial value for hashedSecret
*/
function init(
uint256 senderX,
uint256 senderY,
bytes27 hashedSecret
) external {
require(senderX != uint256(0), "HoprChannels: first component of public key must not be zero.");
require(hashedSecret != bytes27(0), "HoprChannels: HashedSecret must not be empty.");
require(
ECDSA.pubKeyToEthereumAddress(senderX, senderY) == msg.sender,
"HoprChannels: Given public key must match 'msg.sender'"
);
(, uint8 oddY) = ECDSA.compress(senderX, senderY);
Account storage account = accounts[msg.sender];
require(account.accountX == uint256(0), "HoprChannels: Account must not be set");
accounts[msg.sender] = Account(senderX, hashedSecret, uint32(1), oddY);
emit SecretHashSet(msg.sender, hashedSecret, uint32(1));
}
/**
* Fund a channel between 'initiator' and 'counterParty' using a signature,
* specified tokens must be approved beforehand.
*
* @notice fund a channel
* @param additionalDeposit uint256
* @param partyAAmount uint256
* @param notAfter uint256
* @param r bytes32
* @param s bytes32
* @param v uint8
* @param stateCounter uint128
*/
function fundChannelWithSig(
uint256 additionalDeposit,
uint256 partyAAmount,
uint256 notAfter,
uint256 stateCounter,
bytes32 r,
bytes32 s,
uint8 v
) external {
address initiator = msg.sender;
// verification
require(additionalDeposit > 0, "HoprChannels: 'additionalDeposit' must be strictly greater than zero");
require(additionalDeposit < (1 << 96), "HoprChannels: Invalid amount");
require(
partyAAmount <= additionalDeposit,
"HoprChannels: 'partyAAmount' must be strictly smaller than 'additionalDeposit'"
);
// require(partyAAmount < (1 << 96), "Invalid amount");
require(notAfter >= now, "HoprChannels: signature must not be expired");
address counterparty = ECDSA.recover(
ECDSA.toEthSignedMessageHash(
"160",
abi.encode(stateCounter, initiator, additionalDeposit, partyAAmount, notAfter)
),
r,
s,
uint8(v)
);
require(accounts[msg.sender].accountX != uint256(0), "HoprChannels: initiator must have called init before");
require(
accounts[counterparty].accountX != uint256(0),
"HoprChannels: counterparty must have called init before"
);
require(initiator != counterparty, "HoprChannels: initiator and counterparty must not be the same");
(address partyA, , Channel storage channel, ChannelStatus status) = getChannel(initiator, counterparty);
require(
channel.stateCounter == stateCounter,
"HoprChannels: stored stateCounter and signed stateCounter must be the same"
);
require(
status == ChannelStatus.UNINITIALISED || status == ChannelStatus.FUNDED,
"HoprChannels: channel must be 'UNINITIALISED' or 'FUNDED'"
);
uint256 partyBAmount = additionalDeposit - partyAAmount;
if (initiator == partyA) {
token.transferFrom(initiator, address(this), partyAAmount);
token.transferFrom(counterparty, address(this), partyBAmount);
} else {
token.transferFrom(initiator, address(this), partyBAmount);
token.transferFrom(counterparty, address(this), partyAAmount);
}
channel.deposit = uint96(additionalDeposit);
channel.partyABalance = uint96(partyAAmount);
if (status == ChannelStatus.UNINITIALISED) {
// The state counter indicates the recycling generation and ensures that both parties are using the correct generation.
channel.stateCounter += 1;
}
if (initiator == partyA) {
emitFundedChannel(address(0), initiator, counterparty, partyAAmount, partyBAmount);
} else {
emitFundedChannel(address(0), counterparty, initiator, partyAAmount, partyBAmount);
}
}
/**
* @notice open a channel
* @param counterparty address the counterParty of 'msg.sender'
*/
function openChannel(address counterparty) public {
address opener = msg.sender;
require(opener != counterparty, "HoprChannels: 'opener' and 'counterParty' must not be the same");
require(counterparty != address(0), "HoprChannels: 'counterParty' address is empty");
(, , Channel storage channel, ChannelStatus status) = getChannel(opener, counterparty);
require(status == ChannelStatus.FUNDED, "HoprChannels: channel must be in 'FUNDED' state");
// The state counter indicates the recycling generation and ensures that both parties are using the correct generation.
channel.stateCounter += 1;
emitOpenedChannel(opener, counterparty);
}
/**
* @notice redeem ticket
* @param preImage bytes32 the value that once hashed produces recipients hashedSecret
* @param hashedSecretASecretB bytes32 hash of secretA concatenated with secretB
* @param amount uint256 amount 'msg.sender' will receive
* @param winProb bytes32 win probability
* @param r bytes32
* @param s bytes32
* @param v uint8
*/
function redeemTicket(
bytes32 preImage,
bytes32 channelId,
bytes32 hashedSecretASecretB,
uint256 amount,
bytes32 winProb,
bytes32 r,
bytes32 s,
uint8 v
) public returns (bytes memory) {
Account storage recipientAccount = accounts[msg.sender];
bytes32 challenge = keccak256(abi.encodePacked(hashedSecretASecretB));
bytes32 hashedTicket = ECDSA.toEthSignedMessageHash(
"192",
abi.encodePacked(
channelId,
challenge,
bytes32(recipientAccount.hashedSecret),
uint256(recipientAccount.counter),
amount,
winProb
)
);
bytes32 luck = keccak256(abi.encode(hashedTicket, preImage));
require(uint256(luck) < uint256(winProb), "HoprChannels: ticket must be a win");
(address partyA, , Channel storage channel, ChannelStatus status) = getChannel(
msg.sender,
ECDSA.recover(hashedTicket, r, s, v)
);
require(channel.stateCounter != uint24(0), "HoprChannels: Channel does not exist");
require(
status == ChannelStatus.OPEN || status == ChannelStatus.PENDING,
"HoprChannels: channel must be 'OPEN' or 'PENDING'"
);
require(amount > 0, "HoprChannels: amount must be strictly greater than zero");
require(amount < (1 << 96), "HoprChannels: Invalid amount");
require(
recipientAccount.hashedSecret == bytes27(keccak256(abi.encodePacked(bytes27(preImage)))),
"HoprChannels: given value is not a pre-image of the stored on-chain secret"
);
recipientAccount.hashedSecret = bytes27(preImage);
if (msg.sender == partyA) {
require(channel.partyABalance + amount < (1 << 96), "HoprChannels: Invalid amount");
channel.partyABalance += uint96(amount);
} else {
require(channel.partyABalance >= amount, "HoprChannels: Invalid amount");
channel.partyABalance -= uint96(amount);
}
require(
channel.partyABalance <= channel.deposit,
"HoprChannels: partyABalance must be strictly smaller than deposit balance"
);
}
/**
* A channel's party can initiate channel closure at any time,
* it starts a timeout.
*
* @notice initiate channel's closure
* @param counterparty address counter party of 'msg.sender'
*/
function initiateChannelClosure(address counterparty) external {
address initiator = msg.sender;
(, , Channel storage channel, ChannelStatus status) = getChannel(initiator, counterparty);
require(status == ChannelStatus.OPEN, "HoprChannels: channel must be 'OPEN'");
require(now + secsClosure < (1 << 40), "HoprChannels: Preventing timestamp overflow");
channel.closureTime = uint40(now + secsClosure);
// The state counter indicates the recycling generation and ensures that both parties are using the correct generation.
require(channel.stateCounter + 1 < (1 << 24), "HoprChannels: Preventing stateCounter overflow");
channel.stateCounter += 1;
emitInitiatedChannelClosure(initiator, counterparty, channel.closureTime);
}
/**
* If the timeout is reached without the 'counterParty' reedeming a ticket,
* then the tokens can be claimed by 'msg.sender'.
*
* @notice claim channel's closure
* @param counterparty address counter party of 'msg.sender'
*/
function claimChannelClosure(address counterparty) external {
address initiator = msg.sender;
(address partyA, address partyB, Channel storage channel, ChannelStatus status) = getChannel(
initiator,
counterparty
);
require(channel.stateCounter + 7 < (1 << 24), "Preventing stateCounter overflow");
require(status == ChannelStatus.PENDING, "HoprChannels: channel must be 'PENDING'");
require(now >= uint256(channel.closureTime), "HoprChannels: 'closureTime' has not passed");
// settle balances
if (channel.partyABalance > 0) {
token.transfer(partyA, channel.partyABalance);
channel.deposit -= channel.partyABalance;
}
if (channel.deposit > 0) {
token.transfer(partyB, channel.deposit);
}
emitClosedChannel(initiator, counterparty, channel.partyABalance, channel.deposit);
delete channel.deposit; // channel.deposit = 0
delete channel.partyABalance; // channel.partyABalance = 0
delete channel.closureTime; // channel.closureTime = 0
// The state counter indicates the recycling generation and ensures that both parties are using the correct generation.
// Increase state counter so that we can re-use the same channel after it has been closed.
channel.stateCounter += 7;
}
/**
* A hook triggered when HOPR tokens are send to this contract.
*
* @param operator address operator requesting the transfer
* @param from address token holder address
* @param to address recipient address
* @param amount uint256 amount of tokens to transfer
* @param userData bytes extra information provided by the token holder (if any)
* @param operatorData bytes extra information provided by the operator (if any)
*/
function tokensReceived(
address operator,
address from,
// solhint-disable-next-line no-unused-vars
address to,
uint256 amount,
bytes calldata userData,
// solhint-disable-next-line no-unused-vars
bytes calldata operatorData
) external override {
require(msg.sender == address(token), "HoprChannels: Invalid token");
// only call 'fundChannel' when the operator is not self
if (operator != address(this)) {
(address recipient, address counterParty) = abi.decode(userData, (address, address));
fundChannel(amount, from, recipient, counterParty);
}
}
/**
* Fund a channel between 'accountA' and 'accountB',
* specified tokens must be approved beforehand.
* Called when HOPR tokens are send to this contract.
*
* @notice fund a channel
* @param additionalDeposit uint256 amount to fund the channel
* @param funder address account which the funds are for
* @param recipient address account of first participant of the payment channel
* @param counterparty address account of the second participant of the payment channel
*/
function fundChannel(
uint256 additionalDeposit,
address funder,
address recipient,
address counterparty
) internal {
require(recipient != counterparty, "HoprChannels: 'recipient' and 'counterParty' must not be the same");
require(recipient != address(0), "HoprChannels: 'recipient' address is empty");
require(counterparty != address(0), "HoprChannels: 'counterParty' address is empty");
require(additionalDeposit > 0, "HoprChannels: 'additionalDeposit' must be greater than 0");
require(additionalDeposit < (1 << 96), "HoprChannels: preventing 'amount' overflow");
require(accounts[recipient].accountX != uint256(0), "HoprChannels: initiator must have called init() before");
require(
accounts[counterparty].accountX != uint256(0),
"HoprChannels: counterparty must have called init() before"
);
(address partyA, , Channel storage channel, ChannelStatus status) = getChannel(recipient, counterparty);
require(
status == ChannelStatus.UNINITIALISED || status == ChannelStatus.FUNDED,
"HoprChannels: channel must be 'UNINITIALISED' or 'FUNDED'"
);
require(
recipient != partyA || channel.partyABalance + additionalDeposit < (1 << 96),
"HoprChannels: Invalid amount"
);
require(channel.deposit + additionalDeposit < (1 << 96), "HoprChannels: Invalid amount");
require(channel.stateCounter + 1 < (1 << 24), "HoprChannels: Preventing stateCounter overflow");
channel.deposit += uint96(additionalDeposit);
if (recipient == partyA) {
channel.partyABalance += uint96(additionalDeposit);
}
if (status == ChannelStatus.UNINITIALISED) {
// The state counter indicates the recycling generation and ensures that both parties are using the correct generation.
channel.stateCounter += 1;
}
emitFundedChannel(funder, recipient, counterparty, additionalDeposit, 0);
}
/**
* @notice returns channel data
* @param accountA address of account 'A'
* @param accountB address of account 'B'
*/
function getChannel(address accountA, address accountB)
internal
view
returns (
address,
address,
Channel storage,
ChannelStatus
)
{
(address partyA, address partyB) = getParties(accountA, accountB);
bytes32 channelId = getChannelId(partyA, partyB);
Channel storage channel = channels[channelId];
ChannelStatus status = getChannelStatus(channel);
return (partyA, partyB, channel, status);
}
/**
* @notice return true if accountA is party_a
* @param accountA address of account 'A'
* @param accountB address of account 'B'
*/
function isPartyA(address accountA, address accountB) internal pure returns (bool) {
return uint160(accountA) < uint160(accountB);
}
/**
* @notice return party_a and party_b
* @param accountA address of account 'A'
* @param accountB address of account 'B'
*/
function getParties(address accountA, address accountB) internal pure returns (address, address) {
if (isPartyA(accountA, accountB)) {
return (accountA, accountB);
} else {
return (accountB, accountA);
}
}
/**
* @notice return channel id
* @param party_a address of party 'A'
* @param party_b address of party 'B'
*/
function getChannelId(address party_a, address party_b) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(party_a, party_b));
}
/**
* @notice returns 'ChannelStatus'
* @param channel Channel
*/
function getChannelStatus(Channel memory channel) internal pure returns (ChannelStatus) {
return ChannelStatus(channel.stateCounter % 10);
}
/**
* @dev Emits a FundedChannel event that contains the public keys of recipient
* and counterparty as compressed EC-points.
*/
function emitFundedChannel(
address funder,
address recipient,
address counterparty,
uint256 recipientAmount,
uint256 counterpartyAmount
) private {
/* FundedChannel(
* address funder,
* uint256 indexed recipient,
* uint256 indexed counterParty,
* uint256 recipientAmount,
* uint256 counterPartyAmount
* )
*/
bytes32 FundedChannel = keccak256("FundedChannel(address,uint,uint,uint,uint)");
Account storage recipientAccount = accounts[recipient];
Account storage counterpartyAccount = accounts[counterparty];
uint256 recipientX = recipientAccount.accountX;
uint8 recipientOddY = recipientAccount.oddY;
uint256 counterpartyX = counterpartyAccount.accountX;
uint8 counterpartyOddY = counterpartyAccount.oddY;
assembly {
let topic0 := or(or(shl(2, shr(2, FundedChannel)), shl(1, recipientOddY)), counterpartyOddY)
let memPtr := mload(0x40)
mstore(memPtr, recipientAmount)
mstore(add(memPtr, 0x20), counterpartyAmount)
mstore(add(memPtr, 0x40), funder)
log3(memPtr, 0x60, topic0, recipientX, counterpartyX)
}
}
/**
* @dev Emits a OpenedChannel event that contains the public keys of opener
* and counterparty as compressed EC-points.
*/
function emitOpenedChannel(address opener, address counterparty) private {
/* OpenedChannel(
* uint256 indexed opener,
* uint256 indexed counterParty
* )
*/
bytes32 OpenedChannel = keccak256("OpenedChannel(uint,uint)");
Account storage openerAccount = accounts[opener];
Account storage counterpartyAccount = accounts[counterparty];
uint256 openerX = openerAccount.accountX;
uint8 openerOddY = openerAccount.oddY;
uint256 counterpartyX = counterpartyAccount.accountX;
uint8 counterpartyOddY = counterpartyAccount.oddY;
assembly {
let topic0 := or(or(shl(2, shr(2, OpenedChannel)), shl(1, openerOddY)), counterpartyOddY)
log3(0x00, 0x00, topic0, openerX, counterpartyX)
}
}
/**
* @dev Emits a InitiatedChannelClosure event that contains the public keys of initiator
* and counterparty as compressed EC-points.
*/
function emitInitiatedChannelClosure(
address initiator,
address counterparty,
uint256 closureTime
) private {
/* InitiatedChannelClosure(
* uint256 indexed initiator,
* uint256 indexed counterParty,
* uint256 closureTime
* )
*/
bytes32 InitiatedChannelClosure = keccak256("InitiatedChannelClosure(uint,uint,uint)");
Account storage initiatorAccount = accounts[initiator];
Account storage counterpartyAccount = accounts[counterparty];
uint256 initiatorX = initiatorAccount.accountX;
uint8 initiatorOddY = initiatorAccount.oddY;
uint256 counterpartyX = counterpartyAccount.accountX;
uint8 counterpartyOddY = counterpartyAccount.oddY;
assembly {
let topic0 := or(or(shl(2, shr(2, InitiatedChannelClosure)), shl(1, initiatorOddY)), counterpartyOddY)
let memPtr := mload(0x40)
mstore(memPtr, closureTime)
log3(memPtr, 0x20, topic0, initiatorX, counterpartyX)
}
}
/**
* @dev Emits a ClosedChannel event that contains the public keys of initiator
* and counterparty as compressed EC-points.
*/
function emitClosedChannel(
address initiator,
address counterparty,
uint256 partyAAmount,
uint256 partyBAmount
) private {
/*
* ClosedChannel(
* uint256 indexed initiator,
* uint256 indexed counterParty,
* uint256 partyAAmount,
* uint256 partyBAmount
*/
bytes32 ClosedChannel = keccak256("ClosedChannel(uint,uint,uint,uint)");
Account storage initiatorAccount = accounts[initiator];
Account storage counterpartyAccount = accounts[counterparty];
uint256 initiatorX = initiatorAccount.accountX;
uint8 initiatorOddY = initiatorAccount.oddY;
uint256 counterpartyX = counterpartyAccount.accountX;
uint8 counterpartyOddY = counterpartyAccount.oddY;
assembly {
let topic0 := or(or(shl(2, shr(2, ClosedChannel)), shl(1, initiatorOddY)), counterpartyOddY)
let memPtr := mload(0x40)
mstore(memPtr, partyAAmount)
mstore(add(0x20, memPtr), partyBAmount)
log3(memPtr, 0x40, topic0, initiatorX, counterpartyX)
}
}
}

Contract ABI

[{"type":"constructor","stateMutability":"nonpayable","inputs":[{"type":"address","name":"_token","internalType":"contract IERC20"},{"type":"uint256","name":"_secsClosure","internalType":"uint256"}]},{"type":"event","name":"SecretHashSet","inputs":[{"type":"address","name":"account","internalType":"address","indexed":true},{"type":"bytes27","name":"secretHash","internalType":"bytes27","indexed":false},{"type":"uint32","name":"counter","internalType":"uint32","indexed":false}],"anonymous":false},{"type":"function","stateMutability":"view","outputs":[{"type":"bytes32","name":"","internalType":"bytes32"}],"name":"TOKENS_RECIPIENT_INTERFACE_HASH","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"accountX","internalType":"uint256"},{"type":"bytes27","name":"hashedSecret","internalType":"bytes27"},{"type":"uint32","name":"counter","internalType":"uint32"},{"type":"uint8","name":"oddY","internalType":"uint8"}],"name":"accounts","inputs":[{"type":"address","name":"","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"bytes32","name":"","internalType":"bytes32"}],"name":"canImplementInterfaceForAddress","inputs":[{"type":"bytes32","name":"interfaceHash","internalType":"bytes32"},{"type":"address","name":"account","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint96","name":"deposit","internalType":"uint96"},{"type":"uint96","name":"partyABalance","internalType":"uint96"},{"type":"uint40","name":"closureTime","internalType":"uint40"},{"type":"uint24","name":"stateCounter","internalType":"uint24"}],"name":"channels","inputs":[{"type":"bytes32","name":"","internalType":"bytes32"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"claimChannelClosure","inputs":[{"type":"address","name":"counterparty","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"fundChannelWithSig","inputs":[{"type":"uint256","name":"additionalDeposit","internalType":"uint256"},{"type":"uint256","name":"partyAAmount","internalType":"uint256"},{"type":"uint256","name":"notAfter","internalType":"uint256"},{"type":"uint256","name":"stateCounter","internalType":"uint256"},{"type":"bytes32","name":"r","internalType":"bytes32"},{"type":"bytes32","name":"s","internalType":"bytes32"},{"type":"uint8","name":"v","internalType":"uint8"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"init","inputs":[{"type":"uint256","name":"senderX","internalType":"uint256"},{"type":"uint256","name":"senderY","internalType":"uint256"},{"type":"bytes27","name":"hashedSecret","internalType":"bytes27"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"initiateChannelClosure","inputs":[{"type":"address","name":"counterparty","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"openChannel","inputs":[{"type":"address","name":"counterparty","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"bytes","name":"","internalType":"bytes"}],"name":"redeemTicket","inputs":[{"type":"bytes32","name":"preImage","internalType":"bytes32"},{"type":"bytes32","name":"channelId","internalType":"bytes32"},{"type":"bytes32","name":"hashedSecretASecretB","internalType":"bytes32"},{"type":"uint256","name":"amount","internalType":"uint256"},{"type":"bytes32","name":"winProb","internalType":"bytes32"},{"type":"bytes32","name":"r","internalType":"bytes32"},{"type":"bytes32","name":"s","internalType":"bytes32"},{"type":"uint8","name":"v","internalType":"uint8"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"secsClosure","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setHashedSecret","inputs":[{"type":"bytes27","name":"hashedSecret","internalType":"bytes27"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"contract IERC20"}],"name":"token","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"tokensReceived","inputs":[{"type":"address","name":"operator","internalType":"address"},{"type":"address","name":"from","internalType":"address"},{"type":"address","name":"to","internalType":"address"},{"type":"uint256","name":"amount","internalType":"uint256"},{"type":"bytes","name":"userData","internalType":"bytes"},{"type":"bytes","name":"operatorData","internalType":"bytes"}]}]
            

Contract Byte Code

0x608060405234801561001057600080fd5b50600436106100e95760003560e01c806372581cc01161008c578063b4037e8011610066578063b4037e801461042d578063e108854c14610453578063edc4c6ff14610478578063fc0c546a146104a9576100e9565b806372581cc01461030b5780637a7ebd7b14610313578063a6f4cf291461036d576100e9565b8063275621d1116100c8578063275621d11461023e5780632abb5e9d146102465780633bd7fff41461026c5780635e5c06e2146102b0576100e9565b806223de29146100ee5780630f121113146101da578063249cb3fa14610200575b600080fd5b6101d8600480360360c081101561010457600080fd5b6001600160a01b03823581169260208101358216926040820135909216916060820135919081019060a08101608082013564010000000081111561014757600080fd5b82018360208201111561015957600080fd5b8035906020019184600183028401116401000000008311171561017b57600080fd5b91939092909160208101903564010000000081111561019957600080fd5b8201836020820111156101ab57600080fd5b803590602001918460018302840111640100000000831117156101cd57600080fd5b5090925090506104cd565b005b6101d8600480360360208110156101f057600080fd5b50356001600160a01b0316610580565b61022c6004803603604081101561021657600080fd5b50803590602001356001600160a01b031661085a565b60408051918252519081900360200190f35b61022c6108cf565b6101d86004803603602081101561025c57600080fd5b50356001600160a01b03166108d5565b6101d8600480360360e081101561028257600080fd5b5080359060208101359060408101359060608101359060808101359060a08101359060c0013560ff166109fd565b6102d6600480360360208110156102c657600080fd5b50356001600160a01b03166110bc565b6040805194855264ffffffffff19909316602085015263ffffffff9091168383015260ff166060830152519081900360800190f35b61022c6110f3565b6103306004803603602081101561032957600080fd5b5035611120565b604080516001600160601b03958616815293909416602084015264ffffffffff9091168284015262ffffff16606082015290519081900360800190f35b6103b8600480360361010081101561038457600080fd5b5080359060208101359060408101359060608101359060808101359060a08101359060c08101359060e0013560ff16611164565b6040805160208082528351818301528351919283929083019185019080838360005b838110156103f25781810151838201526020016103da565b50505050905090810190601f16801561041f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101d86004803603602081101561044357600080fd5b50356001600160a01b031661168f565b6101d86004803603602081101561046957600080fd5b503564ffffffffff19166117fb565b6101d86004803603606081101561048e57600080fd5b508035906020810135906040013564ffffffffff19166119c6565b6104b1611bce565b604080516001600160a01b039092168252519081900360200190f35b6001546001600160a01b0316331461052c576040805162461bcd60e51b815260206004820152601b60248201527f486f70724368616e6e656c733a20496e76616c696420746f6b656e0000000000604482015290519081900360640190fd5b6001600160a01b0388163014610576576000808585604081101561054f57600080fd5b506001600160a01b03813581169350602090910135169050610573878a8484611bdd565b50505b5050505050505050565b336000808080610590858761202d565b8154939750919550935091506301000000600160e81b90910462ffffff9081166007011610610606576040805162461bcd60e51b815260206004820181905260248201527f50726576656e74696e67207374617465436f756e746572206f766572666c6f77604482015290519081900360640190fd5b600381600381111561061457fe5b146106505760405162461bcd60e51b8152600401808060200182810382526027815260200180612d896027913960400191505060405180910390fd5b8154600160c01b900464ffffffffff1642101561069e5760405162461bcd60e51b815260040180806020018281038252602a8152602001806129ff602a913960400191505060405180910390fd5b8154600160601b90046001600160601b0316156107705760015482546040805163a9059cbb60e01b81526001600160a01b038881166004830152600160601b9093046001600160601b031660248201529051919092169163a9059cbb9160448083019260209291908290030181600087803b15801561071c57600080fd5b505af1158015610730573d6000803e3d6000fd5b505050506040513d602081101561074657600080fd5b505081546001600160601b03600160601b8204811681831603166001600160601b03199091161782555b81546001600160601b03161561080e5760015482546040805163a9059cbb60e01b81526001600160a01b0387811660048301526001600160601b0390931660248201529051919092169163a9059cbb9160448083019260209291908290030181600087803b1580156107e157600080fd5b505af11580156107f5573d6000803e3d6000fd5b505050506040513d602081101561080b57600080fd5b50505b815461083090869088906001600160601b03600160601b8204811691166120cf565b50805462ffffff600160e81b6001600160e81b031990921682900481166007011602905550505050565b6000828152602081815260408083206001600160a01b038516845290915281205460ff166108895760006108c8565b604051602001808073455243313832305f4143434550545f4d4147494360601b8152506014019050604051602081830303815290604052805190602001205b9392505050565b60025481565b336001600160a01b03821681141561091e5760405162461bcd60e51b815260040180806020018281038252603e815260200180612ecf603e913960400191505060405180910390fd5b6001600160a01b0382166109635760405162461bcd60e51b815260040180806020018281038252602d815260200180612c14602d913960400191505060405180910390fd5b600080610970838561202d565b9094509250600191506109809050565b81600381111561098c57fe5b146109c85760405162461bcd60e51b815260040180806020018281038252602f815260200180612a53602f913960400191505060405180910390fd5b815462ffffff600160e81b8083048216600101909116026001600160e81b039091161782556109f7838561216e565b50505050565b3387610a3a5760405162461bcd60e51b8152600401808060200182810382526044815260200180612f566044913960600191505060405180910390fd5b600160601b8810610a80576040805162461bcd60e51b815260206004820152601c6024820152600080516020612a82833981519152604482015290519081900360640190fd5b87871115610abf5760405162461bcd60e51b815260040180806020018281038252604e815260200180612c8e604e913960600191505060405180910390fd5b42861015610afe5760405162461bcd60e51b815260040180806020018281038252602b815260200180612bb5602b913960400191505060405180910390fd5b604080518082018252600381526203136360ec1b60208083019190915282519081018890526001600160a01b03841681840152606081018b9052608081018a905260a08082018a90528351808303909101815260c0909101909252600091610b7191610b6991612214565b8686866122fe565b33600090815260036020526040902054909150610bbf5760405162461bcd60e51b81526004018080602001828103825260348152602001806128d46034913960400191505060405180910390fd5b6001600160a01b038116600090815260036020526040902054610c135760405162461bcd60e51b8152600401808060200182810382526037815260200180612b7e6037913960400191505060405180910390fd5b806001600160a01b0316826001600160a01b03161415610c645760405162461bcd60e51b815260040180806020018281038252603d8152602001806127d8603d913960400191505060405180910390fd5b6000806000610c73858561202d565b8154939650909450925050600160e81b900462ffffff168914610cc75760405162461bcd60e51b815260040180806020018281038252604a815260200180612ac4604a913960600191505060405180910390fd5b6000816003811115610cd557fe5b1480610cec57506001816003811115610cea57fe5b145b610d275760405162461bcd60e51b815260040180806020018281038252603981526020018061279f6039913960400191505060405180910390fd5b8a8c036001600160a01b038681169085161415610e8757600160009054906101000a90046001600160a01b03166001600160a01b03166323b872dd87308f6040518463ffffffff1660e01b815260040180846001600160a01b03166001600160a01b03168152602001836001600160a01b03166001600160a01b031681526020018281526020019350505050602060405180830381600087803b158015610dcd57600080fd5b505af1158015610de1573d6000803e3d6000fd5b505050506040513d6020811015610df757600080fd5b5050600154604080516323b872dd60e01b81526001600160a01b03888116600483015230602483015260448201859052915191909216916323b872dd9160648083019260209291908290030181600087803b158015610e5557600080fd5b505af1158015610e69573d6000803e3d6000fd5b505050506040513d6020811015610e7f57600080fd5b50610fdb9050565b600154604080516323b872dd60e01b81526001600160a01b03898116600483015230602483015260448201859052915191909216916323b872dd9160648083019260209291908290030181600087803b158015610ee357600080fd5b505af1158015610ef7573d6000803e3d6000fd5b505050506040513d6020811015610f0d57600080fd5b810190808051906020019092919050505050600160009054906101000a90046001600160a01b03166001600160a01b03166323b872dd86308f6040518463ffffffff1660e01b815260040180846001600160a01b03166001600160a01b03168152602001836001600160a01b03166001600160a01b031681526020018281526020019350505050602060405180830381600087803b158015610fae57600080fd5b505af1158015610fc2573d6000803e3d6000fd5b505050506040513d6020811015610fd857600080fd5b50505b8c8360000160006101000a8154816001600160601b0302191690836001600160601b031602179055508b83600001600c6101000a8154816001600160601b0302191690836001600160601b031602179055506000600381111561103a57fe5b82600381111561104657fe5b141561107257825462ffffff600160e81b8083048216600101909116026001600160e81b039091161783555b836001600160a01b0316866001600160a01b0316141561109f5761109a600087878f8561247c565b6110ad565b6110ad600086888f8561247c565b50505050505050505050505050565b60036020526000908152604090208054600190910154602881901b90600160d81b810463ffffffff1690600160f81b900460ff1684565b6040805174115490cdcdcdd51bdad95b9cd49958da5c1a595b9d605a1b8152905190819003601501902081565b6004602052600090815260409020546001600160601b0380821691600160601b810490911690600160c01b810464ffffffffff1690600160e81b900462ffffff1684565b6060600060036000336001600160a01b03166001600160a01b03168152602001908152602001600020905060008860405160200180828152602001915050604051602081830303815290604052805190602001209050600061125b60405180604001604052806003815260200162189c9960e91b8152508c848660010160009054906101000a900460281b64ffffffffff191687600101601b9054906101000a900463ffffffff1663ffffffff168e8e604051602001808781526020018681526020018581526020018481526020018381526020018281526020019650505050505050604051602081830303815290604052612214565b90506000818d60405160200180838152602001828152602001925050506040516020818303038152906040528051906020012090508860001c8160001c106112d45760405162461bcd60e51b8152600401808060200182810382526022815260200180612db06022913960400191505060405180910390fd5b60008060006112ee336112e9878e8e8e6122fe565b61202d565b8154939650909450925050600160e81b900462ffffff166113405760405162461bcd60e51b81526004018080602001828103825260248152602001806129086024913960400191505060405180910390fd5b600281600381111561134e57fe5b14806113655750600381600381111561136357fe5b145b6113a05760405162461bcd60e51b8152600401808060200182810382526031815260200180612dd26031913960400191505060405180910390fd5b60008d116113df5760405162461bcd60e51b815260040180806020018281038252603781526020018061296d6037913960400191505060405180910390fd5b600160601b8d10611425576040805162461bcd60e51b815260206004820152601c6024820152600080516020612a82833981519152604482015290519081900360640190fd5b8f604051602001808264ffffffffff191664ffffffffff19168152601b019150506040516020818303038152906040528051906020012064ffffffffff19168760010160009054906101000a900460281b64ffffffffff1916146114ba5760405162461bcd60e51b815260040180806020018281038252604a81526020018061284d604a913960600191505060405180910390fd5b8f8760010160006101000a8154816001600160d81b03021916908360281c0217905550826001600160a01b0316336001600160a01b03161415611593578154600160601b908190046001600160601b03168e011061154d576040805162461bcd60e51b815260206004820152601c6024820152600080516020612a82833981519152604482015290519081900360640190fd5b8c82600001600c8282829054906101000a90046001600160601b03160192506101000a8154816001600160601b0302191690836001600160601b03160217905550611629565b8154600160601b90046001600160601b03168d11156115e7576040805162461bcd60e51b815260206004820152601c6024820152600080516020612a82833981519152604482015290519081900360640190fd5b8c82600001600c8282829054906101000a90046001600160601b03160392506101000a8154816001600160601b0302191690836001600160601b031602179055505b81546001600160601b03808216600160601b90920416111561167c5760405162461bcd60e51b8152600401808060200182810382526049815260200180612b356049913960600191505060405180910390fd5b5050505050505098975050505050505050565b3360008061169d838561202d565b9094509250600291506116ad9050565b8160038111156116b957fe5b146116f55760405162461bcd60e51b8152600401808060200182810382526024815260200180612f326024913960400191505060405180910390fd5b6501000000000060025442011061173d5760405162461bcd60e51b815260040180806020018281038252602b815260200180612c41602b913960400191505060405180910390fd5b600254825464ffffffffff60c01b1916600160c01b4290920164ffffffffff1691909102178083556301000000600160e81b90910462ffffff90811660010116106117b95760405162461bcd60e51b815260040180806020018281038252602e815260200180612e70602e913960400191505060405180910390fd5b815462ffffff600160e81b8083048216600101909116026001600160e81b03909116178083556109f7908490869064ffffffffff600160c01b90910416612560565b64ffffffffff19811661183f5760405162461bcd60e51b8152600401808060200182810382526023815260200180612cdc6023913960400191505060405180910390fd5b336000908152600360205260409020805461188b5760405162461bcd60e51b8152600401808060200182810382526037815260200180612e036037913960400191505060405180910390fd5b600181015460281b64ffffffffff1990811690831614156118dd5760405162461bcd60e51b8152600401808060200182810382526034815260200180612be06034913960400191505060405180910390fd5b60018082015464010000000063ffffffff600160d81b909204821690920116106119385760405162461bcd60e51b8152600401808060200182810382526031815260200180612e9e6031913960400191505060405180910390fd5b6001818101805463ffffffff60d81b196001600160d81b0319909116602886901c17908116600160d81b9182900463ffffffff9081169094018416820217918290556040805164ffffffffff1987168152919092049092166020830152805133927fe277423b3c010b1e242fb9be2199ad75ffbbc39eea686e8f29edbda512b1935492908290030190a25050565b82611a025760405162461bcd60e51b815260040180806020018281038252603d815260200180612897603d913960400191505060405180910390fd5b64ffffffffff198116611a465760405162461bcd60e51b815260040180806020018281038252602d815260200180612772602d913960400191505060405180910390fd5b33611a5184846125f8565b6001600160a01b031614611a965760405162461bcd60e51b8152600401808060200182810382526036815260200180612d536036913960400191505060405180910390fd5b6000611aa2848461268b565b3360009081526003602052604090208054919350915015611af45760405162461bcd60e51b8152600401808060200182810382526025815260200180612f0d6025913960400191505060405180910390fd5b6040805160808101825286815264ffffffffff1985166020808301828152600184860181815260ff89811660608801908152336000818152600388528a90209851895594519784018054935191516001600160d81b031990941660289990991c9890981763ffffffff60d81b1916600160d81b63ffffffff90921691909102176001600160f81b0316600160f81b929091169190910217909455845192835290820192909252825191927fe277423b3c010b1e242fb9be2199ad75ffbbc39eea686e8f29edbda512b1935492918290030190a25050505050565b6001546001600160a01b031681565b806001600160a01b0316826001600160a01b03161415611c2e5760405162461bcd60e51b815260040180806020018281038252604181526020018061292c6041913960600191505060405180910390fd5b6001600160a01b038216611c735760405162461bcd60e51b815260040180806020018281038252602a815260200180612d29602a913960400191505060405180910390fd5b6001600160a01b038116611cb85760405162461bcd60e51b815260040180806020018281038252602d815260200180612c14602d913960400191505060405180910390fd5b60008411611cf75760405162461bcd60e51b81526004018080602001828103825260388152602001806128156038913960400191505060405180910390fd5b600160601b8410611d395760405162461bcd60e51b815260040180806020018281038252602a815260200180612cff602a913960400191505060405180910390fd5b6001600160a01b038216600090815260036020526040902054611d8d5760405162461bcd60e51b8152600401808060200182810382526036815260200180612e3a6036913960400191505060405180910390fd5b6001600160a01b038116600090815260036020526040902054611de15760405162461bcd60e51b81526004018080602001828103825260398152602001806129c66039913960400191505060405180910390fd5b6000806000611df0858561202d565b929550935090915060009050816003811115611e0857fe5b1480611e1f57506001816003811115611e1d57fe5b145b611e5a5760405162461bcd60e51b815260040180806020018281038252603981526020018061279f6039913960400191505060405180910390fd5b826001600160a01b0316856001600160a01b0316141580611e8e57508154600160601b908190046001600160601b03168801105b611ecd576040805162461bcd60e51b815260206004820152601c6024820152600080516020612a82833981519152604482015290519081900360640190fd5b8154600160601b6001600160601b03909116880110611f21576040805162461bcd60e51b815260206004820152601c6024820152600080516020612a82833981519152604482015290519081900360640190fd5b81546301000000600160e81b90910462ffffff9081166001011610611f775760405162461bcd60e51b815260040180806020018281038252602e815260200180612e70602e913960400191505060405180910390fd5b81546001600160601b038082168901166001600160601b03199091161782556001600160a01b038581169084161415611fdc5781546001600160601b03600160601b80830482168a01909116026bffffffffffffffffffffffff60601b199091161782555b6000816003811115611fea57fe5b141561201657815462ffffff600160e81b8083048216600101909116026001600160e81b039091161782555b6120248686868a600061247c565b50505050505050565b6000806000806000806120408888612698565b91509150600061205083836126be565b6000818152600460209081526040808320815160808101835281546001600160601b038082168352600160601b82041694820194909452600160c01b840464ffffffffff1692810192909252600160e81b90920462ffffff166060820152929350916120bb90612700565b949b939a5090985092965090945050505050565b600060405180806129a460229139604080519182900360220182206001600160a01b03808a16600090815260036020908152848220928b1682529084902082546001808501548354828501548e8b52958a018d9052969a509498509196909560ff600160f81b95869004818116979690950416936003198b166101fe9190941b169290921783179184908790849084a350505050505050505050505050565b604080517f4f70656e65644368616e6e656c2875696e742c75696e74290000000000000000815281516018918190039190910190206001600160a01b0384811660009081526003602052838120918516815292832081546001808401548354828501549697959694959394600160f81b9283900460ff81811696939594909204909116921b6101fe1660031989161782179083908690839080a350505050505050505050565b6000828260405160200180807f19457468657265756d205369676e6564204d6573736167653a0a000000000000815250601a0183805190602001908083835b602083106122725780518252601f199092019160209182019101612253565b51815160209384036101000a600019018019909216911617905285519190930192850191508083835b602083106122ba5780518252601f19909201916020918201910161229b565b6001836020036101000a0380198251168184511680821785525050505050509050019250505060405160208183030381529060405280519060200120905092915050565b60007f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a083111561235f5760405162461bcd60e51b8152600401808060200182810382526022815260200180612aa26022913960400191505060405180910390fd5b8160ff16601b1415801561237757508160ff16601c14155b156123b35760405162461bcd60e51b8152600401808060200182810382526022815260200180612c6c6022913960400191505060405180910390fd5b604080516000808252602080830180855289905260ff86168385015260608301889052608083018790529251909260019260a080820193601f1981019281900390910190855afa15801561240b573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116612473576040805162461bcd60e51b815260206004820152601860248201527f45434453413a20696e76616c6964207369676e61747572650000000000000000604482015290519081900360640190fd5b95945050505050565b60006040518080612a29602a9139602a01905060405180910390209050600060036000876001600160a01b03166001600160a01b031681526020019081526020016000209050600060036000876001600160a01b03166001600160a01b031681526020019081526020016000209050600082600001549050600083600101601f9054906101000a900460ff169050600083600001549050600084600101601f9054906101000a900460ff169050808360011b8860021c60021b17176040518a81528960208201528d6040820152838683606084a35050505050505050505050505050565b60006040518080612b0e60279139604080519182900360270182206001600160a01b03808916600090815260036020908152848220928a16825293902081546001808401548354828501548c8a52969a509498509296919560ff600160f81b94859004818116979590950416936003198b166101fe9190931b16919091178317919084908790849084a3505050505050505050505050565b6000612604838361272c565b612655576040805162461bcd60e51b815260206004820152601b60248201527f506f696e74206d757374206265206f6e207468652063757276652e0000000000604482015290519081900360640190fd5b5060408051602080820194909452808201929092528051808303820181526060909201905280519101206001600160a01b031690565b81600182165b9250929050565b6000806126a58484612760565b156126b4575082905081612691565b5081905082612691565b604080516001600160601b0319606094851b81166020808401919091529390941b90931660348401528051602881850301815260489093019052815191012090565b6000600a826060015162ffffff168161271557fe5b0662ffffff16600381111561272657fe5b92915050565b6000806401000003d01980856401000003d01987880909600708905060006401000003d01984850991909114949350505050565b6001600160a01b039081169116109056fe486f70724368616e6e656c733a20486173686564536563726574206d757374206e6f7420626520656d7074792e486f70724368616e6e656c733a206368616e6e656c206d7573742062652027554e494e495449414c4953454427206f72202746554e44454427486f70724368616e6e656c733a20696e69746961746f7220616e6420636f756e7465727061727479206d757374206e6f74206265207468652073616d65486f70724368616e6e656c733a20276164646974696f6e616c4465706f73697427206d7573742062652067726561746572207468616e2030486f70724368616e6e656c733a20676976656e2076616c7565206973206e6f742061207072652d696d616765206f66207468652073746f726564206f6e2d636861696e20736563726574486f70724368616e6e656c733a20666972737420636f6d706f6e656e74206f66207075626c6963206b6579206d757374206e6f74206265207a65726f2e486f70724368616e6e656c733a20696e69746961746f72206d75737420686176652063616c6c656420696e6974206265666f7265486f70724368616e6e656c733a204368616e6e656c20646f6573206e6f74206578697374486f70724368616e6e656c733a2027726563697069656e742720616e642027636f756e746572506172747927206d757374206e6f74206265207468652073616d65486f70724368616e6e656c733a20616d6f756e74206d757374206265207374726963746c792067726561746572207468616e207a65726f436c6f7365644368616e6e656c2875696e742c75696e742c75696e742c75696e7429486f70724368616e6e656c733a20636f756e7465727061727479206d75737420686176652063616c6c656420696e69742829206265666f7265486f70724368616e6e656c733a2027636c6f7375726554696d652720686173206e6f742070617373656446756e6465644368616e6e656c28616464726573732c75696e742c75696e742c75696e742c75696e7429486f70724368616e6e656c733a206368616e6e656c206d75737420626520696e202746554e44454427207374617465486f70724368616e6e656c733a20496e76616c696420616d6f756e740000000045434453413a20696e76616c6964207369676e6174757265202773272076616c7565486f70724368616e6e656c733a2073746f726564207374617465436f756e74657220616e64207369676e6564207374617465436f756e746572206d757374206265207468652073616d65496e697469617465644368616e6e656c436c6f737572652875696e742c75696e742c75696e7429486f70724368616e6e656c733a2070617274794142616c616e6365206d757374206265207374726963746c7920736d616c6c6572207468616e206465706f7369742062616c616e6365486f70724368616e6e656c733a20636f756e7465727061727479206d75737420686176652063616c6c656420696e6974206265666f7265486f70724368616e6e656c733a207369676e6174757265206d757374206e6f742062652065787069726564486f70724368616e6e656c733a206e657720616e64206f6c64206861736865645365637265747320617265207468652073616d65486f70724368616e6e656c733a2027636f756e746572506172747927206164647265737320697320656d707479486f70724368616e6e656c733a2050726576656e74696e672074696d657374616d70206f766572666c6f7745434453413a20696e76616c6964207369676e6174757265202776272076616c7565486f70724368616e6e656c733a2027706172747941416d6f756e7427206d757374206265207374726963746c7920736d616c6c6572207468616e20276164646974696f6e616c4465706f73697427486f70724368616e6e656c733a2068617368656453656372657420697320656d707479486f70724368616e6e656c733a2070726576656e74696e672027616d6f756e7427206f766572666c6f77486f70724368616e6e656c733a2027726563697069656e7427206164647265737320697320656d707479486f70724368616e6e656c733a20476976656e207075626c6963206b6579206d757374206d6174636820276d73672e73656e64657227486f70724368616e6e656c733a206368616e6e656c206d757374206265202750454e44494e4727486f70724368616e6e656c733a207469636b6574206d75737420626520612077696e486f70724368616e6e656c733a206368616e6e656c206d75737420626520274f50454e27206f72202750454e44494e4727486f70724368616e6e656c733a206d73672e73656e646572206d75737420686176652063616c6c656420696e69742829206265666f7265486f70724368616e6e656c733a20696e69746961746f72206d75737420686176652063616c6c656420696e69742829206265666f7265486f70724368616e6e656c733a2050726576656e74696e67207374617465436f756e746572206f766572666c6f77486f70724368616e6e656c733a2050726576656e74696e67206163636f756e7420636f756e746572206f766572666c6f77486f70724368616e6e656c733a20276f70656e65722720616e642027636f756e746572506172747927206d757374206e6f74206265207468652073616d65486f70724368616e6e656c733a204163636f756e74206d757374206e6f7420626520736574486f70724368616e6e656c733a206368616e6e656c206d75737420626520274f50454e27486f70724368616e6e656c733a20276164646974696f6e616c4465706f73697427206d757374206265207374726963746c792067726561746572207468616e207a65726fa2646970667358221220dcd2345df22efefd886e1109f84179f7084fd25e89f884f009e83b5f990d92a864736f6c63430006060033