Contract 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d 1

Contract Overview

Balance:
0.009984431999810304 xDAI

xDAI Value:
Less Than $0.01 (@ $1.00/xDAI)

Token:
Txn Hash Method
Block
From
To
Value [Txn Fee]
0xb9004816ee0ffddfbed51cf48c41773d4dacbe005786973f9dd2eb4bb40cf70dWithdraw To Sign...251874172022-11-27 18:27:102 mins ago0xbfab5463e77341d1de033f79813e2c9696cfa2d1 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001921905
0x12d6cbba44a02f601c63f3b6285ff66dfe9fc8f7096f85983d3a8e764f2e8849Add Members251874152022-11-27 18:27:003 mins ago0xc448b51cf665210419543c5a53f13b8ce87834b2 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
0x507cfc9f12e1da7817aa0791ad025d67df925361077f064a9d91fc1bc5b92aeeAdd Members251873962022-11-27 18:25:154 mins ago0xbb68b447945ffc736c9ba7a6d0458ffacdcebd62 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0002002905
0x48db92f27e92b35e4f1ea02e2254ad2f569c7a443f8ceb8c1f505d27367f8b47Add Members251873872022-11-27 18:24:255 mins ago0xa0e70def4c366e156c43d3fcb589dc2953410109 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0002002905
0x6a690d89fcfeb29f7d3ad23764ce48c757721a1b9d1b2189b8bfcd2ba22ba924Add Members251872802022-11-27 18:14:5015 mins ago0xbb68b447945ffc736c9ba7a6d0458ffacdcebd62 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
0x70b4af1905587fe228c4008c964bf36da23aef192f4f9d05f8fc960f7b01115fAdd Members251872692022-11-27 18:13:5016 mins ago0xbb68b447945ffc736c9ba7a6d0458ffacdcebd62 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
0x6be0c33b074f77bd02a800ef03ee9084a34b4c47276602c2545845cbfe7e7bf8Add Members251872452022-11-27 18:11:4018 mins ago0x6dfd61136ec1bf27cff1bbbcc1c0172b3878fba1 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0002002725
0x1f1506c16b5f4fc4d7e064a9c3b2426bc3c6d936a7dba1f96cc3358a8ab98366Add Members251872182022-11-27 18:09:1520 mins ago0xbfab5463e77341d1de033f79813e2c9696cfa2d1 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
0xd758356eed0dc88ee5e563232ef88b0a0233439df9aa7fb8ded60e5b3deae0b0Add Members251871562022-11-27 18:03:4526 mins ago0xbb68b447945ffc736c9ba7a6d0458ffacdcebd62 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338375
0xbf9bb197509692ea3179ea7c7813408dd2e8dfb103fd9e36de040c411a751bd2Add Members251871542022-11-27 18:03:3026 mins ago0x6dfd61136ec1bf27cff1bbbcc1c0172b3878fba1 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0002002905
0xa7e1f40e0a817bc229906c3e7e03711b97061cdc92e3f21c88cbcb3f444a2cd4Add Members251871162022-11-27 18:00:1029 mins ago0x6dfd61136ec1bf27cff1bbbcc1c0172b3878fba1 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
0x79b9df5ceeaaa0ef458fec09368faefb02f791fde462ce0f2eecf091efd35556Add Members251870482022-11-27 17:54:0036 mins ago0xbfab5463e77341d1de033f79813e2c9696cfa2d1 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
0x393a4dbbbe583a480bb5b774707ed77fb77e351cd89b577fc33da46cf17ee438Add Members251870472022-11-27 17:53:5536 mins ago0x6dfd61136ec1bf27cff1bbbcc1c0172b3878fba1 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.000247119001
0x0bf01d8fce26311417ff8ec14a9a980b8d0506f55475cac876e15d7ee055b04aAdd Members251870362022-11-27 17:52:5537 mins ago0xc448b51cf665210419543c5a53f13b8ce87834b2 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.000247119001
0xb49f43574b1f1040777340fa87824f00fa91d7bafb11029d40515708209c1876Add Members251870182022-11-27 17:51:2038 mins ago0xbb68b447945ffc736c9ba7a6d0458ffacdcebd62 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
0x18c3344cf334773d6df0b1d9860d76c0b27f9a206189c56f4c2878526b5aba79Add Members251869792022-11-27 17:47:5042 mins ago0xbb68b447945ffc736c9ba7a6d0458ffacdcebd62 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.000247119001
0x4578c83154e2730ac8c8b5c1d2d37588b18e841386b79182ea7188bc9ac1dcffAdd Members251869612022-11-27 17:46:1543 mins ago0x6dfd61136ec1bf27cff1bbbcc1c0172b3878fba1 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
0x91159d49e8b6c2899f3b108216041049fb22272e3be32502fd49864882e6ee88Withdraw To Sign...251868572022-11-27 17:36:5553 mins ago0x6dfd61136ec1bf27cff1bbbcc1c0172b3878fba1 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.000310161001
0xd2eaffd191c1bf2ad3258ff3af9210895873a33b59c62724a949f7284b070b25Add Members251868242022-11-27 17:33:5556 mins ago0xc448b51cf665210419543c5a53f13b8ce87834b2 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
0x01577e0967dc6581a16339106af24dad7a5a1b8d1da95df80120807e2c118ea4Add Members251867782022-11-27 17:29:501 hr ago0xbb68b447945ffc736c9ba7a6d0458ffacdcebd62 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
0xa3f3e1b9d1c568f7a2236cf276731eb6296d41fcccb85458c5afad2ea62e0fa7Add Members251867732022-11-27 17:29:201 hr ago0x6dfd61136ec1bf27cff1bbbcc1c0172b3878fba1 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
0x227bafa9d61261149e1d049130b714e8bfe59cb6953ebd8236fa027927b25b74Add Members251867642022-11-27 17:28:301 hr 1 min ago0xc448b51cf665210419543c5a53f13b8ce87834b2 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
0x729de744d2299e259a7912fd643aa307b4b88dc58e74e10a4c53bdfdf7d4e117Add Members251867622022-11-27 17:28:201 hr 1 min ago0xc448b51cf665210419543c5a53f13b8ce87834b2 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
0xf8638e8e0906720469c5d46fba3ffb8c3c71822e65da9337a8fd08fcad10c331Add Members251867332022-11-27 17:25:451 hr 4 mins ago0xc448b51cf665210419543c5a53f13b8ce87834b2 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
0x015e84ca06f4cb9d7c13ca83582cb4aaa688e39b58b09d8213b7a9ffb3e9eb94Add Members251867142022-11-27 17:24:051 hr 5 mins ago0xbb68b447945ffc736c9ba7a6d0458ffacdcebd62 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
[ Download CSV Export 
Latest 1 internal transaction
Parent Txn Hash Block From To Value
0xecaac7df78f299c75f96bb55cafbe47c031221cabe120bc7d04cac5e292e68f8187616572021-10-26 9:51:40397 days 8 hrs ago 0xfce1fbfaae61861b011b379442c8ee1dc868abd0  Contract Creation0 xDAI
[ Download CSV Export 
Loading

Minimal Proxy Contract for 0xacf9e8134047edc671162d9404bf63a587435baa

Contract Name:
DataUnionSidechain

Compiler Version
v0.8.6+commit.11564f7e

Optimization Enabled:
Yes with 200 runs

Other Settings:
istanbul EvmVersion
Decompile ByteCode

Contract Source Code (Solidity)

Similar Contracts
/**
 *Submitted for verification at gnosisscan.io on 2022-08-05
*/

// Sources flattened with hardhat v2.6.6 https://hardhat.org

// File @openzeppelin/contracts/token/ERC20/[email protected]

// SPDX-License-Identifier: MIT

pragma solidity 0.8.6;

/**
 * @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 contracts/IERC677.sol





interface IERC677 is IERC20 {
    function transferAndCall(
        address to,
        uint value,
        bytes calldata data
    ) external returns (bool success);

    event Transfer(
        address indexed from,
        address indexed to,
        uint value,
        bytes data
    );
}


// File contracts/Ownable.sol





/**
 * @title Ownable
 * @dev The Ownable contract has an owner address, and provides basic authorization control
 * functions, this simplifies the implementation of "user permissions".
 *
 * Open Zeppelin's ownable doesn't quite work with factory pattern because _owner has private access.
 * When you create a DU, open-zeppelin _owner would be 0x0 (no state from template). Then no address could change _owner to the DU owner.
 * With this custom Ownable, the first person to call initialiaze() can set owner.
 */
contract Ownable {
    address public owner;
    address public pendingOwner;

    event OwnershipTransferred(
        address indexed previousOwner,
        address indexed newOwner
    );

    /**
     * @dev The Ownable constructor sets the original `owner` of the contract to the sender
     * account.
     */
    constructor(address owner_) {
        owner = owner_;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(msg.sender == owner, "error_onlyOwner");
        _;
    }

    /**
     * @dev Allows the current owner to set the pendingOwner address.
     * @param newOwner The address to transfer ownership to.
     */
    function transferOwnership(address newOwner) public onlyOwner {
        pendingOwner = newOwner;
    }

    /**
     * @dev Allows the pendingOwner address to finalize the transfer.
     */
    function claimOwnership() public {
        require(msg.sender == pendingOwner, "error_onlyPendingOwner");
        emit OwnershipTransferred(owner, pendingOwner);
        owner = pendingOwner;
        pendingOwner = address(0);
    }
}


// File contracts/xdai-mainnet-bridge/IERC20Receiver.sol





/*
tokenbridge callback function for receiving relayTokensAndCall()
*/
interface IERC20Receiver {
    function onTokenBridged(
        address token,
        uint256 value,
        bytes calldata data
    ) external;
}


// File contracts/IERC677Receiver.sol





interface IERC677Receiver {
    function onTokenTransfer(
        address _sender,
        uint256 _value,
        bytes calldata _data
    ) external;
}


// File contracts/IWithdrawModule.sol





interface IWithdrawModule {
    /**
     * When a withdraw happens in the DU, tokens are transferred to the withdrawModule, then onWithdraw function is called.
     * The withdrawModule is then free to manage those tokens as it pleases.
     */
    function onWithdraw(address member, address to, IERC677 token, uint amountWei) external;

    /**
     * WithdrawModule can also set limits to withdraws between 0 and (earnings - previously withdrawn earnings).
     */
    function getWithdrawLimit(address member, uint maxWithdrawable) external view returns (uint256);
}


// File contracts/IJoinListener.sol





interface IJoinListener {
    function onJoin(address newMember) external;
}


// File contracts/LeaveConditionCode.sol





/**
 * Describes how the data union member left
 * For the base DataUnion contract this isn't important, but modules/extensions can find it very helpful
 * See e.g. LimitWithdrawModule
 */
enum LeaveConditionCode {
    SELF,   // self remove using partMember()
    AGENT,  // removed by joinPartAgent using partMember()
    BANNED  // removed by BanModule
}


// File contracts/IPartListener.sol





interface IPartListener {
    function onPart(address leavingMember, LeaveConditionCode leaveConditionCode) external;
}


// File contracts/DataUnionSidechain.sol













contract DataUnionSidechain is Ownable, IERC20Receiver, IERC677Receiver {

    // Used to describe both members and join part agents
    enum ActiveStatus {NONE, ACTIVE, INACTIVE}

    // Members
    event MemberJoined(address indexed member);
    event MemberParted(address indexed member, LeaveConditionCode indexed leaveConditionCode);
    event JoinPartAgentAdded(address indexed agent);
    event JoinPartAgentRemoved(address indexed agent);
    event NewMemberEthSent(uint amountWei);

    // Revenue handling: earnings = revenue - admin fee - du fee
    event RevenueReceived(uint256 amount);
    event FeesCharged(uint256 adminFee, uint256 dataUnionFee);
    event NewEarnings(uint256 earningsPerMember, uint256 activeMemberCount);

    // Withdrawals
    event EarningsWithdrawn(address indexed member, uint256 amount);

    // Modules and hooks
    event WithdrawModuleChanged(IWithdrawModule indexed withdrawModule);
    event JoinListenerAdded(IJoinListener indexed listener);
    event JoinListenerRemoved(IJoinListener indexed listener);
    event PartListenerAdded(IPartListener indexed listener);
    event PartListenerRemoved(IPartListener indexed listener);

    // In-contract transfers
    event TransferWithinContract(address indexed from, address indexed to, uint amount);
    event TransferToAddressInContract(address indexed from, address indexed to, uint amount);

    // Variable properties change events
    event UpdateNewMemberEth(uint value);
    event FeesSet(uint256 adminFee, uint256 dataUnionFee);
    event DataUnionBeneficiaryChanged(address indexed current, address indexed old);

    struct MemberInfo {
        ActiveStatus status;
        uint256 earningsBeforeLastJoin;
        uint256 lmeAtJoin;
        uint256 withdrawnEarnings;
    }

    // Constant properties (only set in initialize)
    IERC677 public token;
    address public tokenMediator;
    address public dataUnionMainnet;

    // Modules
    IWithdrawModule public withdrawModule;
    address[] public joinListeners;
    address[] public partListeners;
    bool public modulesLocked;

    // Variable properties
    uint256 public newMemberEth;
    uint256 public adminFeeFraction;
    uint256 public dataUnionFeeFraction;
    address public dataUnionBeneficiary;

    // Useful stats
    uint256 public totalRevenue;
    uint256 public totalEarnings;
    uint256 public totalAdminFees;
    uint256 public totalDataUnionFees;
    uint256 public totalWithdrawn;
    uint256 public activeMemberCount;
    uint256 public inactiveMemberCount;
    uint256 public lifetimeMemberEarnings;
    uint256 public joinPartAgentCount;

    mapping(address => MemberInfo) public memberData;
    mapping(address => ActiveStatus) public joinPartAgents;

    // owner will be set by initialize()
    constructor() Ownable(address(0)) {}

    receive() external payable {}

    function initialize(
        address initialOwner,
        address tokenAddress,
        address tokenMediatorAddress,
        address[] memory initialJoinPartAgents,
        address mainnetDataUnionAddress,
        uint256 defaultNewMemberEth,
        uint256 initialAdminFeeFraction,
        uint256 initialDataUnionFeeFraction,
        address initialDataUnionBeneficiary
    ) public {
        require(!isInitialized(), "error_alreadyInitialized");
        owner = msg.sender; // set real owner at the end. During initialize, addJoinPartAgents can be called by owner only
        token = IERC677(tokenAddress);
        addJoinPartAgents(initialJoinPartAgents);
        tokenMediator = tokenMediatorAddress;
        dataUnionMainnet = mainnetDataUnionAddress;
        setFees(initialAdminFeeFraction, initialDataUnionFeeFraction);
        setDataUnionBeneficiary(initialDataUnionBeneficiary);
        setNewMemberEth(defaultNewMemberEth);
        owner = initialOwner;
    }

    function isInitialized() public view returns (bool){
        return address(token) != address(0);
    }

    /**
     * Atomic getter to get all Data Union state variables in one call
     * This alleviates the fact that JSON RPC batch requests aren't available in ethers.js
     */
    function getStats() public view returns (uint256[9] memory) {
        uint256 cleanedInactiveMemberCount = inactiveMemberCount;
        if (memberData[owner].status == ActiveStatus.INACTIVE) { cleanedInactiveMemberCount -= 1; }
        if (memberData[dataUnionBeneficiary].status == ActiveStatus.INACTIVE) { cleanedInactiveMemberCount -= 1; }
        return [
            totalRevenue,
            totalEarnings,
            totalAdminFees,
            totalDataUnionFees,
            totalWithdrawn,
            activeMemberCount,
            cleanedInactiveMemberCount,
            lifetimeMemberEarnings,
            joinPartAgentCount
        ];
    }

    /**
     * Admin and DU fees as a fraction of revenue,
     *   using fixed-point decimal in the same way as ether: 50% === 0.5 ether === "500000000000000000"
     * @param newAdminFee fee that goes to the DU owner
     * @param newDataUnionFee fee that goes to the DU beneficiary
     */
    function setFees(uint256 newAdminFee, uint256 newDataUnionFee) public onlyOwner {
        require((newAdminFee + newDataUnionFee) <= 1 ether, "error_fees");
        adminFeeFraction = newAdminFee;
        dataUnionFeeFraction = newDataUnionFee;
        emit FeesSet(adminFeeFraction, dataUnionFeeFraction);
    }

    function setDataUnionBeneficiary(address newDataUnionBeneficiary) public onlyOwner {
        emit DataUnionBeneficiaryChanged(newDataUnionBeneficiary, dataUnionBeneficiary);
        dataUnionBeneficiary = newDataUnionBeneficiary;
    }

    function setNewMemberEth(uint val) public onlyOwner {
        newMemberEth = val;
        emit UpdateNewMemberEth(val);
    }

    //------------------------------------------------------------
    // REVENUE HANDLING FUNCTIONS
    //------------------------------------------------------------

    /**
     * Process unaccounted tokens that have been sent previously
     * Called by AMB (see DataUnionMainnet:sendTokensToBridge)
     */
    function refreshRevenue() public returns (uint256) {
        uint256 balance = token.balanceOf(address(this));
        uint256 newTokens = balance - totalWithdrawable(); // since 0.8.0 version of solidity, a - b errors if b > a
        if (newTokens == 0 || activeMemberCount == 0) { return 0; }
        totalRevenue += newTokens;
        emit RevenueReceived(newTokens);

        // fractions are expressed as multiples of 10^18 just like tokens, so must divide away the extra 10^18 factor
        // overflow in multiplication is not an issue: 256bits ~= 10^77
        uint256 adminFee = (newTokens * adminFeeFraction) / (1 ether);
        uint256 duFee = (newTokens * dataUnionFeeFraction) / (1 ether);
        uint256 newEarnings = newTokens - adminFee - duFee;

        _increaseBalance(owner, adminFee);
        _increaseBalance(dataUnionBeneficiary, duFee);
        totalAdminFees += adminFee;
        totalDataUnionFees += duFee;
        emit FeesCharged(adminFee, duFee);

        uint256 earningsPerMember = newEarnings / activeMemberCount;
        lifetimeMemberEarnings = lifetimeMemberEarnings + earningsPerMember;
        totalEarnings = totalEarnings + newEarnings;
        emit NewEarnings(earningsPerMember, activeMemberCount);

        assert (token.balanceOf(address(this)) == totalWithdrawable()); // calling this function immediately again should just return 0 and do nothing
        return newEarnings;
    }

    /**
     * ERC677 callback function, see https://github.com/ethereum/EIPs/issues/677
     * Receives the tokens arriving through bridge
     * Only the token contract is authorized to call this function
     */
    function onTokenTransfer(address, uint256, bytes calldata) override external {
        // guarding refreshRevenue is pointless, but this prevents DU from receiving unexpected ERC677 tokens
        require(msg.sender == address(token), "error_onlyTokenContract");
        refreshRevenue();
    }

    /**
     * Tokenbridge callback function
     */
    function onTokenBridged(address, uint256, bytes memory) override public {
        refreshRevenue();
    }

    //------------------------------------------------------------
    // EARNINGS VIEW FUNCTIONS
    //------------------------------------------------------------

    function getEarnings(address member) public view returns (uint256) {
        MemberInfo storage info = memberData[member];
        require(info.status != ActiveStatus.NONE, "error_notMember");
        return
            info.earningsBeforeLastJoin +
            (
                info.status == ActiveStatus.ACTIVE
                    ? lifetimeMemberEarnings - info.lmeAtJoin
                    : 0
            );
    }

    function getWithdrawn(address member) public view returns (uint256) {
        MemberInfo storage info = memberData[member];
        require(info.status != ActiveStatus.NONE, "error_notMember");
        return info.withdrawnEarnings;
    }

    function getWithdrawableEarnings(address member) public view returns (uint256) {
        uint maxWithdraw = getEarnings(member) - getWithdrawn(member);
        if (address(withdrawModule) != address(0)) {
            uint moduleLimit = withdrawModule.getWithdrawLimit(member, maxWithdraw);
            if (moduleLimit < maxWithdraw) { maxWithdraw = moduleLimit; }
        }
        return maxWithdraw;
    }

    // this includes the fees paid to admins and the DU beneficiary
    function totalWithdrawable() public view returns (uint256) {
        return totalRevenue - totalWithdrawn;
    }

    //------------------------------------------------------------
    // MEMBER MANAGEMENT / VIEW FUNCTIONS
    //------------------------------------------------------------

    function isMember(address member) public view returns (bool) {
        return memberData[member].status == ActiveStatus.ACTIVE;
    }

    function isJoinPartAgent(address agent) public view returns (bool) {
        return joinPartAgents[agent] == ActiveStatus.ACTIVE;
    }

    modifier onlyJoinPartAgent() {
        require(isJoinPartAgent(msg.sender), "error_onlyJoinPartAgent");
        _;
    }

    function addJoinPartAgents(address[] memory agents) public onlyOwner {
        for (uint256 i = 0; i < agents.length; i++) {
            addJoinPartAgent(agents[i]);
        }
    }

    function addJoinPartAgent(address agent) public onlyOwner {
        require(joinPartAgents[agent] != ActiveStatus.ACTIVE, "error_alreadyActiveAgent");
        joinPartAgents[agent] = ActiveStatus.ACTIVE;
        emit JoinPartAgentAdded(agent);
        joinPartAgentCount += 1;
    }

    function removeJoinPartAgent(address agent) public onlyOwner {
        require(joinPartAgents[agent] == ActiveStatus.ACTIVE, "error_notActiveAgent");
        joinPartAgents[agent] = ActiveStatus.INACTIVE;
        emit JoinPartAgentRemoved(agent);
        joinPartAgentCount -= 1;
    }

    function addMember(address payable newMember) public onlyJoinPartAgent {
        MemberInfo storage info = memberData[newMember];
        require(!isMember(newMember), "error_alreadyMember");
        if (info.status == ActiveStatus.INACTIVE) {
            inactiveMemberCount -= 1;
        }
        bool sendEth = info.status == ActiveStatus.NONE && newMemberEth > 0 && address(this).balance >= newMemberEth;
        info.status = ActiveStatus.ACTIVE;
        info.lmeAtJoin = lifetimeMemberEarnings;
        activeMemberCount += 1;
        emit MemberJoined(newMember);

        // listeners get a chance to reject the new member by reverting
        for (uint i = 0; i < joinListeners.length; i++) {
            address listener = joinListeners[i];
            IJoinListener(listener).onJoin(newMember); // may revert
        }

        // give new members ETH. continue even if transfer fails
        if (sendEth) {
            if (newMember.send(newMemberEth)) {
                emit NewMemberEthSent(newMemberEth);
            }
        }
        refreshRevenue();
    }

    function removeMember(address member, LeaveConditionCode leaveConditionCode) public {
        require(msg.sender == member || joinPartAgents[msg.sender] == ActiveStatus.ACTIVE, "error_notPermitted");
        require(isMember(member), "error_notActiveMember");

        memberData[member].earningsBeforeLastJoin = getEarnings(member);
        memberData[member].status = ActiveStatus.INACTIVE;
        activeMemberCount -= 1;
        inactiveMemberCount += 1;
        emit MemberParted(member, leaveConditionCode);

        // listeners do NOT get a chance to prevent parting by reverting
        for (uint i = 0; i < partListeners.length; i++) {
            address listener = partListeners[i];
            try IPartListener(listener).onPart(member, leaveConditionCode) { } catch { }
        }

        refreshRevenue();
    }

    // access checked in removeMember
    function partMember(address member) public {
        removeMember(member, msg.sender == member ? LeaveConditionCode.SELF : LeaveConditionCode.AGENT);
    }

    // access checked in addMember
    function addMembers(address payable[] calldata members) external {
        for (uint256 i = 0; i < members.length; i++) {
            addMember(members[i]);
        }
    }

    // access checked in removeMember
    function partMembers(address[] calldata members) external {
        for (uint256 i = 0; i < members.length; i++) {
            partMember(members[i]);
        }
    }

    //------------------------------------------------------------
    // IN-CONTRACT TRANSFER FUNCTIONS
    //------------------------------------------------------------

    /**
     * Transfer tokens from outside contract, add to a recipient's in-contract balance. Skip admin and DU fees etc.
     */
    function transferToMemberInContract(address recipient, uint amount) public {
        // this is done first, so that in case token implementation calls the onTokenTransfer in its transferFrom (which by ERC677 it should NOT),
        //   transferred tokens will still not count as earnings (distributed to all) but a simple earnings increase to this particular member
        _increaseBalance(recipient, amount);
        totalRevenue += amount;
        emit TransferToAddressInContract(msg.sender, recipient, amount);

        uint balanceBefore = token.balanceOf(address(this));
        require(token.transferFrom(msg.sender, address(this), amount), "error_transfer");
        uint balanceAfter = token.balanceOf(address(this));
        require((balanceAfter - balanceBefore) >= amount, "error_transfer");

        refreshRevenue();
    }

    /**
     * Transfer tokens from sender's in-contract balance to recipient's in-contract balance
     * This is done by "withdrawing" sender's earnings and crediting them to recipient's unwithdrawn earnings,
     *   so withdrawnEarnings never decreases for anyone (within this function)
     * @param recipient whose withdrawable earnings will increase
     * @param amount how much withdrawable earnings is transferred
     */
    function transferWithinContract(address recipient, uint amount) public {
        require(getWithdrawableEarnings(msg.sender) >= amount, "error_insufficientBalance");    // reverts with "error_notMember" msg.sender not member
        MemberInfo storage info = memberData[msg.sender];
        info.withdrawnEarnings = info.withdrawnEarnings + amount;
        _increaseBalance(recipient, amount);
        emit TransferWithinContract(msg.sender, recipient, amount);
        refreshRevenue();
    }

    /**
     * Hack to add to single member's balance without affecting lmeAtJoin
     */
    function _increaseBalance(address member, uint amount) internal {
        MemberInfo storage info = memberData[member];
        info.earningsBeforeLastJoin = info.earningsBeforeLastJoin + amount;

        // allow seeing and withdrawing earnings
        if (info.status == ActiveStatus.NONE) {
            info.status = ActiveStatus.INACTIVE;
            inactiveMemberCount += 1;
        }
    }

    //------------------------------------------------------------
    // WITHDRAW FUNCTIONS
    //------------------------------------------------------------

    function withdrawMembers(address[] calldata members, bool sendToMainnet)
        external
        returns (uint256)
    {
        uint256 withdrawn = 0;
        for (uint256 i = 0; i < members.length; i++) {
            withdrawn = withdrawn + (withdrawAll(members[i], sendToMainnet));
        }
        return withdrawn;
    }

    function withdrawAll(address member, bool sendToMainnet)
        public
        returns (uint256)
    {
        refreshRevenue();
        return withdraw(member, getWithdrawableEarnings(member), sendToMainnet);
    }

    function withdraw(address member, uint amount, bool sendToMainnet)
        public
        returns (uint256)
    {
        require(msg.sender == member || msg.sender == owner, "error_notPermitted");
        return _withdraw(member, member, amount, sendToMainnet);
    }

    function withdrawAllTo(address to, bool sendToMainnet)
        external
        returns (uint256)
    {
        refreshRevenue();
        return withdrawTo(to, getWithdrawableEarnings(msg.sender), sendToMainnet);
    }

    function withdrawTo(address to, uint amount, bool sendToMainnet)
        public
        returns (uint256)
    {
        return _withdraw(msg.sender, to, amount, sendToMainnet);
    }

    /**
     * Check signature from a member authorizing withdrawing its earnings to another account.
     * Throws if the signature is badly formatted or doesn't match the given signer and amount.
     * Signature has parts the act as replay protection:
     * 1) `address(this)`: signature can't be used for other contracts;
     * 2) `withdrawn[signer]`: signature only works once (for unspecified amount), and can be "cancelled" by sending a withdraw tx.
     * Generated in Javascript with: `web3.eth.accounts.sign(recipientAddress + amount.toString(16, 64) + contractAddress.slice(2) + withdrawnTokens.toString(16, 64), signerPrivateKey)`,
     * or for unlimited amount: `web3.eth.accounts.sign(recipientAddress + "0".repeat(64) + contractAddress.slice(2) + withdrawnTokens.toString(16, 64), signerPrivateKey)`.
     * @param signer whose earnings are being withdrawn
     * @param recipient of the tokens
     * @param amount how much is authorized for withdraw, or zero for unlimited (withdrawAll)
     * @param signature byte array from `web3.eth.accounts.sign`
     * @return isValid true iff signer of the authorization (member whose earnings are going to be withdrawn) matches the signature
     */
    function signatureIsValid(
        address signer,
        address recipient,
        uint amount,
        bytes memory signature
    )
        public view
        returns (bool isValid)
    {
        require(signature.length == 65, "error_badSignatureLength");

        bytes32 r; bytes32 s; uint8 v;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            r := mload(add(signature, 32))
            s := mload(add(signature, 64))
            v := byte(0, mload(add(signature, 96)))
        }
        if (v < 27) {
            v += 27;
        }
        require(v == 27 || v == 28, "error_badSignatureVersion");

        // When changing the message, remember to double-check that message length is correct!
        bytes32 messageHash = keccak256(abi.encodePacked(
            "\x19Ethereum Signed Message:\n104", recipient, amount, address(this), getWithdrawn(signer)));
        address calculatedSigner = ecrecover(messageHash, v, r, s);

        return calculatedSigner == signer;
    }

    /**
     * Do an "unlimited donate withdraw" on behalf of someone else, to an address they've specified.
     * Sponsored withdraw is paid by admin, but target account could be whatever the member specifies.
     * The signature gives a "blank cheque" for admin to withdraw all tokens to `recipient` in the future,
     *   and it's valid until next withdraw (and so can be nullified by withdrawing any amount).
     * A new signature needs to be obtained for each subsequent future withdraw.
     * @param fromSigner whose earnings are being withdrawn
     * @param to the address the tokens will be sent to (instead of `msg.sender`)
     * @param sendToMainnet if the tokens should be sent to mainnet or only withdrawn into sidechain address
     * @param signature from the member, see `signatureIsValid` how signature generated for unlimited amount
     */
    function withdrawAllToSigned(
        address fromSigner,
        address to,
        bool sendToMainnet,
        bytes calldata signature
    )
        external
        returns (uint withdrawn)
    {
        require(signatureIsValid(fromSigner, to, 0, signature), "error_badSignature");
        refreshRevenue();
        return _withdraw(fromSigner, to, getWithdrawableEarnings(fromSigner), sendToMainnet);
    }

    /**
     * Do a "donate withdraw" on behalf of someone else, to an address they've specified.
     * Sponsored withdraw is paid by admin, but target account could be whatever the member specifies.
     * The signature is valid only for given amount of tokens that may be different from maximum withdrawable tokens.
     * @param fromSigner whose earnings are being withdrawn
     * @param to the address the tokens will be sent to (instead of `msg.sender`)
     * @param amount of tokens to withdraw
     * @param sendToMainnet if the tokens should be sent to mainnet or only withdrawn into sidechain address
     * @param signature from the member, see `signatureIsValid` how signature generated for unlimited amount
     */
    function withdrawToSigned(
        address fromSigner,
        address to,
        uint amount,
        bool sendToMainnet,
        bytes calldata signature
    )
        external
        returns (uint withdrawn)
    {
        require(signatureIsValid(fromSigner, to, amount, signature), "error_badSignature");
        return _withdraw(fromSigner, to, amount, sendToMainnet);
    }

    /**
     * Internal function common to all withdraw methods.
     * Does NOT check proper access, so all callers must do that first.
     */
    function _withdraw(address from, address to, uint amount, bool sendToMainnet)
        internal
        returns (uint256)
    {
        if (amount == 0) { return 0; }
        refreshRevenue();
        require(amount <= getWithdrawableEarnings(from), "error_insufficientBalance");
        MemberInfo storage info = memberData[from];
        info.withdrawnEarnings += amount;
        totalWithdrawn += amount;

        if (address(withdrawModule) != address(0)) {
            require(token.transfer(address(withdrawModule), amount), "error_transfer");
            withdrawModule.onWithdraw(from, to, token, amount);
        } else {
            _defaultWithdraw(from, to, amount, sendToMainnet);
        }

        emit EarningsWithdrawn(from, amount);
        return amount;
    }

    /**
     * Default DU 2.1 withdraw functionality, can be overridden with a withdrawModule.
     */
    function _defaultWithdraw(address from, address to, uint amount, bool sendToMainnet)
        internal
    {
        if (sendToMainnet) {
            // tokenMediator sends tokens over the bridge it's assigned to
            require(token.transferAndCall(tokenMediator, amount, abi.encodePacked(to)), "error_transfer");
        } else {
            // transferAndCall also enables transfers over another token bridge
            //   in this case to=another bridge's tokenMediator, and from=recipient on the other chain
            // this follows the tokenMediator API: data will contain the recipient address, which is the same as sender but on the other chain
            // in case transferAndCall recipient is not a tokenMediator, the data can be ignored (it contains the DU member's address)
            require(token.transferAndCall(to, amount, abi.encodePacked(from)), "error_transfer");
        }
    }

    //------------------------------------------------------------
    // MODULE MANAGEMENT
    //------------------------------------------------------------

    /**
     * @param newWithdrawModule set to zero to return to the default withdraw functionality
     */
    function setWithdrawModule(IWithdrawModule newWithdrawModule) external onlyOwner {
        require(!modulesLocked, "error_modulesLocked");
        withdrawModule = newWithdrawModule;
        emit WithdrawModuleChanged(newWithdrawModule);
    }

    function addJoinListener(IJoinListener newListener) external onlyOwner {
        joinListeners.push(address(newListener));
        emit JoinListenerAdded(newListener);
    }

    function addPartListener(IPartListener newListener) external onlyOwner {
        partListeners.push(address(newListener));
        emit PartListenerAdded(newListener);
    }

    function removeJoinListener(IJoinListener listener) external onlyOwner {
        require(removeFromAddressArray(joinListeners, address(listener)), "error_joinListenerNotFound");
        emit JoinListenerRemoved(listener);
    }

    function removePartListener(IPartListener listener) external onlyOwner {
        require(removeFromAddressArray(partListeners, address(listener)), "error_partListenerNotFound");
        emit PartListenerRemoved(listener);
    }

    /**
     * Remove the listener from array by copying the last element into its place so that the arrays stay compact
     */
    function removeFromAddressArray(address[] storage array, address element) internal returns (bool success) {
        uint i = 0;
        while (i < array.length && array[i] != element) { i += 1; }
        if (i == array.length) return false;

        if (i < array.length - 1) {
            array[i] = array[array.length - 1];
        }
        array.pop();
        return true;
    }

    function lockModules() public onlyOwner {
        modulesLocked = true;
    }
}

Contract ABI

[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"current","type":"address"},{"indexed":true,"internalType":"address","name":"old","type":"address"}],"name":"DataUnionBeneficiaryChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"member","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"EarningsWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"adminFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"dataUnionFee","type":"uint256"}],"name":"FeesCharged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"adminFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"dataUnionFee","type":"uint256"}],"name":"FeesSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IJoinListener","name":"listener","type":"address"}],"name":"JoinListenerAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IJoinListener","name":"listener","type":"address"}],"name":"JoinListenerRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"agent","type":"address"}],"name":"JoinPartAgentAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"agent","type":"address"}],"name":"JoinPartAgentRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"member","type":"address"}],"name":"MemberJoined","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"member","type":"address"},{"indexed":true,"internalType":"enum LeaveConditionCode","name":"leaveConditionCode","type":"uint8"}],"name":"MemberParted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"earningsPerMember","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"activeMemberCount","type":"uint256"}],"name":"NewEarnings","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amountWei","type":"uint256"}],"name":"NewMemberEthSent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IPartListener","name":"listener","type":"address"}],"name":"PartListenerAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IPartListener","name":"listener","type":"address"}],"name":"PartListenerRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"RevenueReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"TransferToAddressInContract","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"TransferWithinContract","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"UpdateNewMemberEth","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IWithdrawModule","name":"withdrawModule","type":"address"}],"name":"WithdrawModuleChanged","type":"event"},{"inputs":[],"name":"activeMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IJoinListener","name":"newListener","type":"address"}],"name":"addJoinListener","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"agent","type":"address"}],"name":"addJoinPartAgent","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"agents","type":"address[]"}],"name":"addJoinPartAgents","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"newMember","type":"address"}],"name":"addMember","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable[]","name":"members","type":"address[]"}],"name":"addMembers","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IPartListener","name":"newListener","type":"address"}],"name":"addPartListener","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"adminFeeFraction","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"claimOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"dataUnionBeneficiary","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dataUnionFeeFraction","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dataUnionMainnet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"member","type":"address"}],"name":"getEarnings","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStats","outputs":[{"internalType":"uint256[9]","name":"","type":"uint256[9]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"member","type":"address"}],"name":"getWithdrawableEarnings","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"member","type":"address"}],"name":"getWithdrawn","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"inactiveMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"initialOwner","type":"address"},{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"address","name":"tokenMediatorAddress","type":"address"},{"internalType":"address[]","name":"initialJoinPartAgents","type":"address[]"},{"internalType":"address","name":"mainnetDataUnionAddress","type":"address"},{"internalType":"uint256","name":"defaultNewMemberEth","type":"uint256"},{"internalType":"uint256","name":"initialAdminFeeFraction","type":"uint256"},{"internalType":"uint256","name":"initialDataUnionFeeFraction","type":"uint256"},{"internalType":"address","name":"initialDataUnionBeneficiary","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"isInitialized","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"agent","type":"address"}],"name":"isJoinPartAgent","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"member","type":"address"}],"name":"isMember","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"joinListeners","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"joinPartAgentCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"joinPartAgents","outputs":[{"internalType":"enum DataUnionSidechain.ActiveStatus","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lifetimeMemberEarnings","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lockModules","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"memberData","outputs":[{"internalType":"enum DataUnionSidechain.ActiveStatus","name":"status","type":"uint8"},{"internalType":"uint256","name":"earningsBeforeLastJoin","type":"uint256"},{"internalType":"uint256","name":"lmeAtJoin","type":"uint256"},{"internalType":"uint256","name":"withdrawnEarnings","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"modulesLocked","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"newMemberEth","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onTokenBridged","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onTokenTransfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"partListeners","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"member","type":"address"}],"name":"partMember","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"members","type":"address[]"}],"name":"partMembers","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"refreshRevenue","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IJoinListener","name":"listener","type":"address"}],"name":"removeJoinListener","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"agent","type":"address"}],"name":"removeJoinPartAgent","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"member","type":"address"},{"internalType":"enum LeaveConditionCode","name":"leaveConditionCode","type":"uint8"}],"name":"removeMember","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IPartListener","name":"listener","type":"address"}],"name":"removePartListener","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newDataUnionBeneficiary","type":"address"}],"name":"setDataUnionBeneficiary","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newAdminFee","type":"uint256"},{"internalType":"uint256","name":"newDataUnionFee","type":"uint256"}],"name":"setFees","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"val","type":"uint256"}],"name":"setNewMemberEth","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IWithdrawModule","name":"newWithdrawModule","type":"address"}],"name":"setWithdrawModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"signatureIsValid","outputs":[{"internalType":"bool","name":"isValid","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"token","outputs":[{"internalType":"contract IERC677","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenMediator","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalAdminFees","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalDataUnionFees","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalEarnings","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalRevenue","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalWithdrawable","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalWithdrawn","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferToMemberInContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferWithinContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"member","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bool","name":"sendToMainnet","type":"bool"}],"name":"withdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"member","type":"address"},{"internalType":"bool","name":"sendToMainnet","type":"bool"}],"name":"withdrawAll","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"bool","name":"sendToMainnet","type":"bool"}],"name":"withdrawAllTo","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"fromSigner","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"bool","name":"sendToMainnet","type":"bool"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"withdrawAllToSigned","outputs":[{"internalType":"uint256","name":"withdrawn","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"members","type":"address[]"},{"internalType":"bool","name":"sendToMainnet","type":"bool"}],"name":"withdrawMembers","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawModule","outputs":[{"internalType":"contract IWithdrawModule","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bool","name":"sendToMainnet","type":"bool"}],"name":"withdrawTo","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"fromSigner","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bool","name":"sendToMainnet","type":"bool"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"withdrawToSigned","outputs":[{"internalType":"uint256","name":"withdrawn","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]

Block Transaction Gas Used Reward
Age Block Fee Address BC Fee Address Voting Power Jailed Incoming
Block Uncle Number Difficulty Gas Used Reward
Loading
Make sure to use the "Vote Down" button for any spammy posts, and the "Vote Up" for interesting conversations.