Address: 0xEbDd59B8cE1993d38e4E081E0d80662BB169d3db
Balance (XRP): 0 XRP
Bytecode: 0x60806040526004361015610011575f80fd5b5f3560e01c80634784226e146102e85780634c4e814c1461025e5780638dbf4cad146102455780639f795aac146101ff578063b8dc491b14610182578063d14f8dd714610169578063d275afd6146100a05763f8b2cb4f14610071575f80fd5b3461009c57602036600319011261009c57602061009461008f610473565b610bc5565b604051908152f35b5f80fd5b6100a9366104ed565b9190307f000000000000000000000000ebdd59b8ce1993d38e4e081e0d80662bb169d3db6001600160a01b03161461015a576100e69030836109f4565b506001600160a01b0381169082908261012a5750505f516020610e615f395f51905f52602047936101178582610da3565b6040519485526001600160a01b031693a3005b5f516020610e615f395f51905f529161011782610148602094610c37565b9681610155898094610c71565b610d59565b6327844c6960e11b5f5260045ffd5b3461009c57602061009461017c366104b3565b91610ae3565b604036600319011261009c57610196610473565b61019e610489565b90307f000000000000000000000000ebdd59b8ce1993d38e4e081e0d80662bb169d3db6001600160a01b03161461015a576001600160a01b0381169082908261012a5750505f516020610e615f395f51905f52602047936101178582610da3565b610208366104ed565b9190307f000000000000000000000000ebdd59b8ce1993d38e4e081e0d80662bb169d3db6001600160a01b03161461015a576100e6903083610ae3565b3461009c576020610094610258366104b3565b916109f4565b3461009c5760c036600319011261009c5760a4356001600160401b03811161009c573660238201121561009c5780600401356001600160401b03811161009c57366024828401011161009c57307f000000000000000000000000ebdd59b8ce1993d38e4e081e0d80662bb169d3db6001600160a01b03161461015a5760246102e69201610579565b005b608036600319011261009c576102fc610473565b610304610489565b6064356001600160a01b03811692604435929084830361009c57307f000000000000000000000000ebdd59b8ce1993d38e4e081e0d80662bb169d3db6001600160a01b03161461015a576001600160a01b038116938491826103e057504790818111156103d85750915b826103a4575b50505047908161038057005b8161039b5f516020610e615f395f51905f5293602093610da3565b604051908152a3005b6020816103bf855f516020610e815f395f51905f5294610da3565b6040519485526001600160a01b031693a3838281610374565b90509161036e565b9291906103ec84610c37565b906103f78286610c71565b8181111561046b5750915b82610436575b50505061041481610c37565b8061041b57005b61039b815f516020610e615f395f51905f5294602094610d59565b602081610452855f516020610e815f395f51905f529488610d59565b6040519485526001600160a01b031693a3848381610408565b905091610402565b600435906001600160a01b038216820361009c57565b602435906001600160a01b038216820361009c57565b35906001600160a01b038216820361009c57565b606090600319011261009c576004356001600160a01b038116810361009c57906024356001600160a01b038116810361009c579060443590565b606090600319011261009c576004356001600160a01b038116810361009c5790602435906044356001600160a01b038116810361009c5790565b9081606091031261009c5761053b8161049f565b9161054d60406020840135930161049f565b90565b9081606091031261009c576105648161049f565b9160406105736020840161049f565b92013590565b9190915f600484101561097b575b6001600160e01b0319169263b8dc491b60e01b84146108ef576323c2113760e11b84146107725763693ad7eb60e11b8414610715576327de56ab60e21b841461066057638dbf4cad60e01b841461062e5763d14f8dd760e01b84146105f9578363fbdc730160e01b5f5260045260245ffd5b809192935060041161009c578160046106189261062b94019101610550565b916001600160a01b039182169116610ae3565b50565b809192935060041161009c5781600461064d9261062b94019101610550565b916001600160a01b0391821691166109f4565b8092935060041161009c57600461067a9282019101610527565b6001600160a01b0390811692811691907f000000000000000000000000ebdd59b8ce1993d38e4e081e0d80662bb169d3db16301461015a576106bd903083610ae3565b50806106e7575f516020610e615f395f51905f526020476106de8186610da3565b604051908152a3565b5f516020610e615f395f51905f52602061070083610c37565b61070a8185610c71565b6106de818686610d59565b8092935060041161009c57600461072f9282019101610527565b6001600160a01b0390811692811691907f000000000000000000000000ebdd59b8ce1993d38e4e081e0d80662bb169d3db16301461015a576106bd9030836109f4565b809192935060041161009c576080908201829003600319011261009c5761079b6004820161049f565b6107a76024830161049f565b906107b960646044850135940161049f565b6001600160a01b039081169391811692811691907f000000000000000000000000ebdd59b8ce1993d38e4e081e0d80662bb169d3db16301461015a578290816108685747908181111561086157505b80610837575b505050478061081c57505050565b6020816106de5f516020610e615f395f51905f529386610da3565b6020816108525f516020610e815f395f51905f529386610da3565b604051908152a35f818161080e565b9050610808565b61087182610c37565b9061087c8284610c71565b818111156108e857505b806108bd575b506108979150610c37565b806108a157505050565b6020816106de5f516020610e615f395f51905f52938686610d59565b6020816108d95f516020610e815f395f51905f52938686610d59565b604051908152a35f818161088c565b9050610886565b809192935060041161009c576040908201829003600319011261009c57610924602461091d6004840161049f565b920161049f565b6001600160a01b03908116918116907f000000000000000000000000ebdd59b8ce1993d38e4e081e0d80662bb169d3db16301461015a57806106e7575f516020610e615f395f51905f526020476106de8186610da3565b508260041161009c5780356001600160e01b031916610587565b90601f801991011681019081106001600160401b038211176109b657604052565b634e487b7160e01b5f52604160045260245ffd5b6001600160a01b039182168152911660208201526040810191909152606081019190915260800190565b909291906001600160a01b03811680610a3a575050823192818410610a17575050565b9091506305176aa160e21b5f5260018060a01b031660045260245260445260645ffd5b6040516370a0823160e01b81526001600160a01b038616600482015292949290602090829060249082905afa908115610ad8575f91610aa6575b508094808210610a845750505050565b90610aa291604051948594631041008560e21b8652600486016109ca565b0390fd5b90506020813d602011610ad0575b81610ac160209383610995565b8101031261009c57515f610a74565b3d9150610ab4565b6040513d5f823e3d90fd5b909291906001600160a01b03811680610b2a57505082319281841015610b07575050565b90915063a3401cbd60e01b5f5260018060a01b031660045260245260445260645ffd5b6040516370a0823160e01b81526001600160a01b038616600482015292949290602090829060249082905afa908115610ad8575f91610b93575b50809480821015610b755750505050565b90610aa291604051948594630c059d1960e41b8652600486016109ca565b90506020813d602011610bbd575b81610bae60209383610995565b8101031261009c57515f610b64565b3d9150610ba1565b6001600160a01b031680610bd95750333190565b6020602491604051928380926370a0823160e01b82523360048301525afa908115610ad8575f91610c08575090565b90506020813d602011610c2f575b81610c2360209383610995565b8101031261009c575190565b3d9150610c16565b6040516370a0823160e01b815230600482015290602090829060249082906001600160a01b03165afa908115610ad8575f91610c08575090565b60405163095ea7b360e01b60208083019182526001600160a01b037f000000000000000000000000ebdd59b8ce1993d38e4e081e0d80662bb169d3db8181166024860152604480860197909752958452929390921691905f90610cd5606486610995565b84519082855af15f513d82610d3d575b505015610cf157505050565b60405163095ea7b360e01b60208201526001600160a01b0390931660248401525f6044808501919091528352610d3b92610d3690610d30606482610995565b82610e08565b610e08565b565b909150610d515750803b15155b5f80610ce5565b600114610d4a565b60405163a9059cbb60e01b60208201526001600160a01b03929092166024830152604480830193909352918152610d3b91610d95606483610995565b6001600160a01b0316610e08565b5f918291829182916001600160a01b03165af13d15610e03573d6001600160401b0381116109b65760405190610de3601f8201601f191660200183610995565b81525f60203d92013e5b15610df457565b633d2cec6f60e21b5f5260045ffd5b610ded565b905f602091828151910182855af115610ad8575f513d610e5757506001600160a01b0381163b155b610e375750565b635274afe760e01b5f9081526001600160a01b0391909116600452602490fd5b60011415610e3056feed679328aebf74ede77ae09efcf36e90244f83643dadac1c2d9f0b21a46f6ab7f40cc8c1a1d17359049ba500cfc894596a692cffc9d03943cd92ec2e159cf6aea264697066735822122027fb4084318271660ae596c07346a75599ef3270107378e971c5e03d826252c864736f6c634300081e0033
IERC1363.sol
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363.sol) pragma solidity >=0.6.2; import {IERC20} from "./IERC20.sol"; import {IERC165} from "./IERC165.sol"; /** * @title IERC1363 * @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363]. * * Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract * after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction. */ interface IERC1363 is IERC20, IERC165 { /* * Note: the ERC-165 identifier for this interface is 0xb0202a11. * 0xb0202a11 === * bytes4(keccak256('transferAndCall(address,uint256)')) ^ * bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^ * bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^ * bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^ * bytes4(keccak256('approveAndCall(address,uint256)')) ^ * bytes4(keccak256('approveAndCall(address,uint256,bytes)')) */ /** * @dev Moves a `value` amount of tokens from the caller's account to `to` * and then calls {IERC1363Receiver-onTransferReceived} on `to`. * @param to The address which you want to transfer to. * @param value The amount of tokens to be transferred. * @return A boolean value indicating whether the operation succeeded unless throwing. */ function transferAndCall(address to, uint256 value) external returns (bool); /** * @dev Moves a `value` amount of tokens from the caller's account to `to` * and then calls {IERC1363Receiver-onTransferReceived} on `to`. * @param to The address which you want to transfer to. * @param value The amount of tokens to be transferred. * @param data Additional data with no specified format, sent in call to `to`. * @return A boolean value indicating whether the operation succeeded unless throwing. */ function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool); /** * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism * and then calls {IERC1363Receiver-onTransferReceived} on `to`. * @param from The address which you want to send tokens from. * @param to The address which you want to transfer to. * @param value The amount of tokens to be transferred. * @return A boolean value indicating whether the operation succeeded unless throwing. */ function transferFromAndCall(address from, address to, uint256 value) external returns (bool); /** * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism * and then calls {IERC1363Receiver-onTransferReceived} on `to`. * @param from The address which you want to send tokens from. * @param to The address which you want to transfer to. * @param value The amount of tokens to be transferred. * @param data Additional data with no specified format, sent in call to `to`. * @return A boolean value indicating whether the operation succeeded unless throwing. */ function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool); /** * @dev Sets a `value` amount of tokens as the allowance of `spender` over the * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`. * @param spender The address which will spend the funds. * @param value The amount of tokens to be spent. * @return A boolean value indicating whether the operation succeeded unless throwing. */ function approveAndCall(address spender, uint256 value) external returns (bool); /** * @dev Sets a `value` amount of tokens as the allowance of `spender` over the * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`. * @param spender The address which will spend the funds. * @param value The amount of tokens to be spent. * @param data Additional data with no specified format, sent in call to `spender`. * @return A boolean value indicating whether the operation succeeded unless throwing. */ function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool); }
IERC165.sol
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol) pragma solidity >=0.4.16; import {IERC165} from "../utils/introspection/IERC165.sol";
IERC20.sol
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol) pragma solidity >=0.4.16; import {IERC20} from "../token/ERC20/IERC20.sol";
IERC20.sol
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol) pragma solidity >=0.4.16; /** * @dev Interface of the ERC-20 standard as defined in the ERC. */ interface IERC20 { /** * @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); /** * @dev Returns the value of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the value of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves a `value` amount of tokens from the caller's account to `to`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address to, uint256 value) 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 a `value` amount of tokens 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 value) external returns (bool); /** * @dev Moves a `value` amount of tokens from `from` to `to` using the * allowance mechanism. `value` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom(address from, address to, uint256 value) external returns (bool); }
SafeERC20.sol
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol) pragma solidity ^0.8.20; import {IERC20} from "../IERC20.sol"; import {IERC1363} from "../../../interfaces/IERC1363.sol"; /** * @title SafeERC20 * @dev Wrappers around ERC-20 operations that throw on failure (when the token * contract returns false). Tokens that return no value (and instead revert or * throw on failure) are also supported, non-reverting calls are assumed to be * successful. * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. */ library SafeERC20 { /** * @dev An operation with an ERC-20 token failed. */ error SafeERC20FailedOperation(address token); /** * @dev Indicates a failed `decreaseAllowance` request. */ error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); /** * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, * non-reverting calls are assumed to be successful. */ function safeTransfer(IERC20 token, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value))); } /** * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. */ function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value))); } /** * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful. */ function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) { return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value))); } /** * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful. */ function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) { return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value))); } /** * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. * * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client" * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior. */ function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { uint256 oldAllowance = token.allowance(address(this), spender); forceApprove(token, spender, oldAllowance + value); } /** * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no * value, non-reverting calls are assumed to be successful. * * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client" * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior. */ function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal { unchecked { uint256 currentAllowance = token.allowance(address(this), spender); if (currentAllowance < requestedDecrease) { revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease); } forceApprove(token, spender, currentAllowance - requestedDecrease); } } /** * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval * to be set to zero before setting it to a non-zero value, such as USDT. * * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being * set here. */ function forceApprove(IERC20 token, address spender, uint256 value) internal { bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value)); if (!_callOptionalReturnBool(token, approvalCall)) { _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0))); _callOptionalReturn(token, approvalCall); } } /** * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when * targeting contracts. * * Reverts if the returned value is other than `true`. */ function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal { if (to.code.length == 0) { safeTransfer(token, to, value); } else if (!token.transferAndCall(to, value, data)) { revert SafeERC20FailedOperation(address(token)); } } /** * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when * targeting contracts. * * Reverts if the returned value is other than `true`. */ function transferFromAndCallRelaxed( IERC1363 token, address from, address to, uint256 value, bytes memory data ) internal { if (to.code.length == 0) { safeTransferFrom(token, from, to, value); } else if (!token.transferFromAndCall(from, to, value, data)) { revert SafeERC20FailedOperation(address(token)); } } /** * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when * targeting contracts. * * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}. * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall} * once without retrying, and relies on the returned value to be true. * * Reverts if the returned value is other than `true`. */ function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal { if (to.code.length == 0) { forceApprove(token, to, value); } else if (!token.approveAndCall(to, value, data)) { revert SafeERC20FailedOperation(address(token)); } } /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). * @param token The token targeted by the call. * @param data The call data (encoded using abi.encode or one of its variants). * * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements. */ function _callOptionalReturn(IERC20 token, bytes memory data) private { uint256 returnSize; uint256 returnValue; assembly ("memory-safe") { let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20) // bubble errors if iszero(success) { let ptr := mload(0x40) returndatacopy(ptr, 0, returndatasize()) revert(ptr, returndatasize()) } returnSize := returndatasize() returnValue := mload(0) } if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) { revert SafeERC20FailedOperation(address(token)); } } /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). * @param token The token targeted by the call. * @param data The call data (encoded using abi.encode or one of its variants). * * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead. */ function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) { bool success; uint256 returnSize; uint256 returnValue; assembly ("memory-safe") { success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20) returnSize := returndatasize() returnValue := mload(0) } return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1); } }
IERC165.sol
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol) pragma solidity >=0.4.16; /** * @dev Interface of the ERC-165 standard, as defined in the * https://eips.ethereum.org/EIPS/eip-165[ERC]. * * Implementers can declare support of contract interfaces, which can then be * queried by others ({ERC165Checker}). * * For an implementation, see {ERC165}. */ interface IERC165 { /** * @dev Returns true if this contract implements the interface defined by * `interfaceId`. See the corresponding * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section] * to learn more about how these ids are created. * * This function call must use less than 30 000 gas. */ function supportsInterface(bytes4 interfaceId) external view returns (bool); }
IDelegatedExtension.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.27; /// @title IDelegatedExtension /// @author Agustin Aguilar /// @notice Interface for the delegated extension module interface IDelegatedExtension { /// @notice Handle a sequence delegate call /// @param _opHash The operation hash /// @param _startingGas The starting gas /// @param _index The index /// @param _numCalls The number of calls /// @param _space The space /// @param _data The data function handleSequenceDelegateCall( bytes32 _opHash, uint256 _startingGas, uint256 _index, uint256 _numCalls, uint256 _space, bytes calldata _data ) external; }
TrailsTokenSweeper.sol
// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IDelegatedExtension} from "wallet-contracts-v3/modules/interfaces/IDelegatedExtension.sol"; /** * @title TrailsTokenSweeper * @author Shun Kakinoki * @dev This contract can be used to sweep native tokens or ERC20 tokens from this contract to a specified address. */ contract TrailsTokenSweeper is IDelegatedExtension { // ------------------------------------------------------------------------- // Libraries // ------------------------------------------------------------------------- using SafeERC20 for IERC20; // ------------------------------------------------------------------------- // Errors // ------------------------------------------------------------------------- error NativeTransferFailed(); error NotDelegateCall(); error InvalidDelegatedSelector(bytes4 selector); error InsufficientNativeBalance(address account, uint256 required, uint256 available); error InsufficientERC20Balance(address token, address account, uint256 required, uint256 available); error ExcessiveNativeBalance(address account, uint256 maxAllowed, uint256 available); error ExcessiveERC20Balance(address token, address account, uint256 maxAllowed, uint256 available); // ------------------------------------------------------------------------- // Events // ------------------------------------------------------------------------- event Refund(address indexed token, address indexed recipient, uint256 amount); event Sweep(address indexed token, address indexed recipient, uint256 amount); // ------------------------------------------------------------------------- // Constants / Modifiers // ------------------------------------------------------------------------- address private immutable SELF = address(this); modifier onlyDelegatecall() { if (address(this) == SELF) revert NotDelegateCall(); _; } // ------------------------------------------------------------------------- // View Functions // ------------------------------------------------------------------------- /** * @notice Gets the balance of a given token. * @param _token The address of the token. Use address(0) for the native token. * @return The balance of the token. */ function getBalance(address _token) public view returns (uint256) { if (_token == address(0)) { return msg.sender.balance; } else { return IERC20(_token).balanceOf(msg.sender); } } /** * @notice Ensures `account` has at least `minExpected` balance for `token`. * @dev Use `token == address(0)` to validate native balance. Reverts with * specific errors on failure and returns the current balance on success. * @param token The token address to check. Use address(0) for native. * @param account The account whose balance to validate. * @param minExpected The minimum required balance. * @return current The current balance of `account` for the given asset. */ function validateBalance(address token, address account, uint256 minExpected) public view returns (uint256 current) { if (token == address(0)) { current = account.balance; if (current < minExpected) { revert InsufficientNativeBalance(account, minExpected, current); } } else { current = IERC20(token).balanceOf(account); if (current < minExpected) { revert InsufficientERC20Balance(token, account, minExpected, current); } } } /** * @notice Ensures `account` has less than `maxAllowed` balance for `token`. * @dev Use `token == address(0)` to validate native balance. Reverts with * specific errors on failure and returns the current balance on success. * @param token The token address to check. Use address(0) for native. * @param account The account whose balance to validate. * @param maxAllowed The maximum allowed balance (exclusive). * @return current The current balance of `account` for the given asset. */ function validateLesserThanBalance(address token, address account, uint256 maxAllowed) public view returns (uint256 current) { if (token == address(0)) { current = account.balance; if (current >= maxAllowed) { revert ExcessiveNativeBalance(account, maxAllowed, current); } } else { current = IERC20(token).balanceOf(account); if (current >= maxAllowed) { revert ExcessiveERC20Balance(token, account, maxAllowed, current); } } } // ------------------------------------------------------------------------- // Validate and Execute // ------------------------------------------------------------------------- /** * @notice Validates minimum balance then sweeps the entire balance to recipient. * @dev Use address(0) for native token. Runs under delegatecall context. * @param _token The asset to sweep. address(0) for native. * @param _minExpected The minimum required balance before sweeping. * @param _recipient The address to receive the sweep. */ function validateAndSweep(address _token, uint256 _minExpected, address _recipient) public payable onlyDelegatecall { // Validate required minimum balance first; will revert if insufficient. validateBalance(_token, address(this), _minExpected); // Sweep the balance to the recipient and emit events. sweep(_token, _recipient); } /** * @notice Validates maximum balance then sweeps the entire balance to recipient. * @dev Use address(0) for native token. Runs under delegatecall context. * @param _token The asset to sweep. address(0) for native. * @param _maxAllowed The maximum allowed balance before sweeping (exclusive). * @param _recipient The address to receive the sweep. */ function validateLesserThanAndSweep(address _token, uint256 _maxAllowed, address _recipient) public payable onlyDelegatecall { // Validate that balance is less than maximum allowed; will revert if excessive. validateLesserThanBalance(_token, address(this), _maxAllowed); // Sweep the balance to the recipient and emit events. sweep(_token, _recipient); } // ------------------------------------------------------------------------- // Internal Helpers // ------------------------------------------------------------------------- function _ensureERC20Approval(address _token, uint256 _amount) internal { IERC20 erc20 = IERC20(_token); SafeERC20.forceApprove(erc20, SELF, _amount); } function _transferNative(address _to, uint256 _amount) internal { (bool success,) = payable(_to).call{value: _amount}(""); if (!success) revert NativeTransferFailed(); } function _transferERC20(address _token, address _to, uint256 _amount) internal { IERC20 erc20 = IERC20(_token); SafeERC20.safeTransfer(erc20, _to, _amount); } function _nativeBalance() internal view returns (uint256) { return address(this).balance; } function _erc20Balance(address _token) internal view returns (uint256) { return IERC20(_token).balanceOf(address(this)); } // ------------------------------------------------------------------------- // External Functions // ------------------------------------------------------------------------- /** * @notice Approves the sweeper if ERC20, then sweeps the entire balance to recipient. * @dev Approval is set for `SELF` (the sweeper contract) on the wallet (delegatecall context). * For native tokens, approval is skipped and the native balance is swept. * @param _token The address of the token to sweep. Use address(0) for the native token. * @param _recipient The address to send the swept tokens to. */ function sweep(address _token, address _recipient) public payable onlyDelegatecall { if (_token == address(0)) { uint256 amount = _nativeBalance(); _transferNative(_recipient, amount); emit Sweep(_token, _recipient, amount); } else { uint256 amount = _erc20Balance(_token); _ensureERC20Approval(_token, amount); _transferERC20(_token, _recipient, amount); emit Sweep(_token, _recipient, amount); } } /** * @notice Refunds up to `_refundAmount` to `_refundRecipient`, then sweeps any remaining balance to `_sweepRecipient`. * @dev For ERC20 tokens, sets infinite approval to `SELF` in delegatecall context for compatibility, then transfers. * @param _token The token address to operate on. Use address(0) for native. * @param _refundRecipient Address receiving the refund portion. * @param _refundAmount Maximum amount to refund. * @param _sweepRecipient Address receiving the remaining balance. */ function refundAndSweep(address _token, address _refundRecipient, uint256 _refundAmount, address _sweepRecipient) public payable onlyDelegatecall { if (_token == address(0)) { uint256 current = _nativeBalance(); uint256 actualRefund = _refundAmount > current ? current : _refundAmount; if (actualRefund > 0) { _transferNative(_refundRecipient, actualRefund); emit Refund(_token, _refundRecipient, actualRefund); } uint256 remaining = _nativeBalance(); if (remaining > 0) { _transferNative(_sweepRecipient, remaining); emit Sweep(_token, _sweepRecipient, remaining); } } else { uint256 balance = _erc20Balance(_token); _ensureERC20Approval(_token, balance); uint256 actualRefund = _refundAmount > balance ? balance : _refundAmount; if (actualRefund > 0) { _transferERC20(_token, _refundRecipient, actualRefund); emit Refund(_token, _refundRecipient, actualRefund); } uint256 remaining = _erc20Balance(_token); if (remaining > 0) { _transferERC20(_token, _sweepRecipient, remaining); emit Sweep(_token, _sweepRecipient, remaining); } } } // ------------------------------------------------------------------------- // Sequence Delegated Extension Entry Point // ------------------------------------------------------------------------- /** * @notice Entry point for Sequence delegatecall routing. * @dev The wallet module delegatecalls this function with the original call data in `_data`. * We decode the selector and dispatch to the corresponding function in this contract. * Execution context is that of the wallet (delegatecall), which is required for sweeping. */ function handleSequenceDelegateCall( bytes32, /* _opHash */ uint256, /* _startingGas */ uint256, /* _index */ uint256, /* _numCalls */ uint256, /* _space */ bytes calldata _data ) external override onlyDelegatecall { bytes4 selector; if (_data.length >= 4) { selector = bytes4(_data[0:4]); } if (selector == this.sweep.selector) { (address token, address recipient) = abi.decode(_data[4:], (address, address)); sweep(token, recipient); return; } if (selector == this.refundAndSweep.selector) { (address token, address refundRecipient, uint256 refundAmount, address sweepRecipient) = abi.decode(_data[4:], (address, address, uint256, address)); refundAndSweep(token, refundRecipient, refundAmount, sweepRecipient); return; } if (selector == this.validateAndSweep.selector) { (address token, uint256 minExpected, address recipient) = abi.decode(_data[4:], (address, uint256, address)); validateAndSweep(token, minExpected, recipient); return; } if (selector == this.validateLesserThanAndSweep.selector) { (address token, uint256 maxAllowed, address recipient) = abi.decode(_data[4:], (address, uint256, address)); validateLesserThanAndSweep(token, maxAllowed, recipient); return; } if (selector == this.validateBalance.selector) { (address token, address account, uint256 minExpected) = abi.decode(_data[4:], (address, address, uint256)); validateBalance(token, account, minExpected); return; } if (selector == this.validateLesserThanBalance.selector) { (address token, address account, uint256 maxAllowed) = abi.decode(_data[4:], (address, address, uint256)); validateLesserThanBalance(token, account, maxAllowed); return; } revert InvalidDelegatedSelector(selector); } }
Gas Token: