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]
0xff165ee771956fefd7f4725aa88d5c94b93bac2385dfb3a44170d2b52e2ed415Add Members271161252023-03-25 13:37:503 mins ago0x6dfd61136ec1bf27cff1bbbcc1c0172b3878fba1 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
0x776c5ea55348ec8512a2d43ebf732d61a2da36436e0053e4e9715457d1bb600dAdd Members271161152023-03-25 13:37:004 mins ago0xc448b51cf665210419543c5a53f13b8ce87834b2 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.000247119001
0x349c5e2ced716214dda9c74a5932e5134a9b3edebe55774d36b9b888ecbd6f58Add Members271160352023-03-25 13:29:5011 mins ago0xbb68b447945ffc736c9ba7a6d0458ffacdcebd62 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338375
0x1a0806c0c0bebdd44797647721e91dea59a5374bf0966768a16c0819e93aa0b7Add Members271160252023-03-25 13:29:0012 mins ago0xc448b51cf665210419543c5a53f13b8ce87834b2 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0002002905
0x61063c281df18cd9f35c73ec2342888f4a4f19092778e80eeea615c91c609c39Add Members271160172023-03-25 13:28:2012 mins ago0xbb68b447945ffc736c9ba7a6d0458ffacdcebd62 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.000247119001
0xe8a2e6303a1054d31af7b89c465ddcd8646c0a96a449ce61fb1e8a1c6d68a72cAdd Members271160052023-03-25 13:27:2013 mins ago0xc448b51cf665210419543c5a53f13b8ce87834b2 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
0xb12266101a7b15b3792cc0cae61bef7535a2deb07a48fec73faa38edec006552Add Members271159932023-03-25 13:26:2014 mins ago0xa0e70def4c366e156c43d3fcb589dc2953410109 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
0x6b359b98d720367453bdd09ba45d39a6db2fc5c042598d89455a03b2c3e07171Add Members271159912023-03-25 13:26:0515 mins ago0xa0e70def4c366e156c43d3fcb589dc2953410109 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
0x440c183753c65972ea4730b97bd7a7a69aeb36a980abe5f81381303594246193Add Members271159772023-03-25 13:24:5516 mins ago0xc448b51cf665210419543c5a53f13b8ce87834b2 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
0xd029284ff312137a49a3bde0ce7e69f62684e0c3483b27d27be89c9407ce8b15Add Members271159352023-03-25 13:21:1520 mins ago0xa0e70def4c366e156c43d3fcb589dc2953410109 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.000247119001
0x087021ca7012c2f41b5c04bfdd438ed77fa5a90b7a231a3eea752413ccc9f205Add Members271159002023-03-25 13:18:0523 mins ago0x6dfd61136ec1bf27cff1bbbcc1c0172b3878fba1 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
0x93ea2d960bcabc6a9935b52a9704d9f017cea80449071f057ad5a769c49bd00bAdd Members271158672023-03-25 13:15:1026 mins ago0xbb68b447945ffc736c9ba7a6d0458ffacdcebd62 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.000247119001
0x2a7ded63fe2b9cc0e8f3cb28d95b224015220c2213d61bfa4c7381b17ccee56eAdd Members271158602023-03-25 13:14:3526 mins ago0xbfab5463e77341d1de033f79813e2c9696cfa2d1 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0002002905
0x049673b6c2573f7082006d601da7d5139b9f5415824a35df8dd7b52c17598150Add Members271158452023-03-25 13:13:2027 mins ago0x6dfd61136ec1bf27cff1bbbcc1c0172b3878fba1 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
0x8b0eddf5d0170127fd8ba04b43e2245c36b79050daf76e3bd73af31b7dcf9651Add Members271158392023-03-25 13:12:5028 mins ago0x6dfd61136ec1bf27cff1bbbcc1c0172b3878fba1 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
0xf0381a32b3d328905e7c922328aa6dd340924de55fb656efe467f5d8c68afd25Withdraw To Sign...271157942023-03-25 13:08:4032 mins ago0xbb68b447945ffc736c9ba7a6d0458ffacdcebd62 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001921905
0x0cbea860d6e931cd79b9e9be54e518324d363d03e1183349b604f4bf35be55b3Add Members271157682023-03-25 13:06:1035 mins ago0x6dfd61136ec1bf27cff1bbbcc1c0172b3878fba1 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
0x9e52602be80170166a3b38ee8a6fa351ba7de93946cc6ec34b97b77ccb92eb1fAdd Members271157282023-03-25 13:02:3538 mins ago0xa0e70def4c366e156c43d3fcb589dc2953410109 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
0xe6647c69defea8f3a541bd76619d1fd0eb061c7c590bd30f994a3debfcff67a0Add Members271157242023-03-25 13:02:1539 mins ago0xbb68b447945ffc736c9ba7a6d0458ffacdcebd62 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
0xd3996b46d5380079beb179163ddf7f4072932d0e22fe37d3b7a986d2f566a586Add Members271157242023-03-25 13:02:1539 mins ago0xc448b51cf665210419543c5a53f13b8ce87834b2 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.000247119001
0x0a24cd174cd000ce3f0b5f9bf30c0af89b6d038fa2ae04d46d013f051275e4edAdd Members271156942023-03-25 12:59:2541 mins ago0xbfab5463e77341d1de033f79813e2c9696cfa2d1 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
0x21c930f488d1c08e4dbae6209c6e3475916d7dbe66f9bf3190dcf9ca7da867fbAdd Members271156442023-03-25 12:55:1046 mins ago0xc448b51cf665210419543c5a53f13b8ce87834b2 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
0x97911e4a0a3bd64b3afd6149f0f326b32ab088ed8dc887edcc3c02ae6712986fWithdraw To Sign...271156312023-03-25 12:54:0047 mins ago0xbfab5463e77341d1de033f79813e2c9696cfa2d1 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.000310143001
0x82f4be86991037bc8d751ddbd14073de60d5dc4561b392cf0b08d9162eb6f33bAdd Members271155832023-03-25 12:49:4051 mins ago0xbfab5463e77341d1de033f79813e2c9696cfa2d1 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
0xb77d1c5abf8d9b72372f551e59198bd2360f9c5ea887896874fb1050e79969b0Add Members271155782023-03-25 12:49:1052 mins ago0xa0e70def4c366e156c43d3fcb589dc2953410109 IN 0x15287e573007d5fbd65d87ed46c62cf4c71dd66d0 xDAI0.0001338555
[ Download CSV Export 
Latest 1 internal transaction
Parent Txn Hash Block From To Value
0xecaac7df78f299c75f96bb55cafbe47c031221cabe120bc7d04cac5e292e68f8187616572021-10-26 9:51:40515 days 3 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.