Address: 0x0000000000213697bCA95E7373787a40858a51C7
Balance (XRP): 0 XRP
Bytecode: 0x6080604052600436101561001257600080fd5b60003560e01c80630e159f8014610077578063898bd92114610072578063b00c84841461006d578063b05f87db14610068578063d834bcbf146100635763ec08af331461005e57600080fd5b610656565b6105a2565b6102e6565b61027b565b6101b6565b3461011c5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261011c576100ae610121565b6101056100b9610144565b73ffffffffffffffffffffffffffffffffffffffff6044359316600052600060205260406000209073ffffffffffffffffffffffffffffffffffffffff16600052602052604060002090565b906000526020526020604060002054604051908152f35b600080fd5b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361011c57565b6024359073ffffffffffffffffffffffffffffffffffffffff8216820361011c57565b359073ffffffffffffffffffffffffffffffffffffffff8216820361011c57565b9181601f8401121561011c5782359167ffffffffffffffff831161011c576020838186019501011161011c57565b3461011c5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261011c5760043560243567ffffffffffffffff811161011c5761020b610213913690600401610188565b90833361102f565b901561022457602090604051908152f35b507f904689fc0000000000000000000000000000000000000000000000000000000060005273ffffffffffffffffffffffffffffffffffffffff331660045260245260446000fd5b908161012091031261011c5790565b3461011c5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261011c576102b2610121565b60243567ffffffffffffffff811161011c576020916102d86102de92369060040161026c565b90610a7f565b604051908152f35b3461011c5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261011c5761031d610121565b610325610144565b60443567ffffffffffffffff811161011c5761034590369060040161026c565b60643567ffffffffffffffff811161011c57610365903690600401610188565b90610373828285878961146c565b1561051b5750508261038961038e9236906109b3565b6115e6565b906103f5826103e6836103c18773ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b9073ffffffffffffffffffffffffffffffffffffffff16600052602052604060002090565b90600052602052604060002090565b546104cc576104c77faeb5575092e25ccd826d5de3515c096028bb338c1f304db40dc831c3746ee0ae9342610452856103e6866103c18673ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b5561048a84610485856103c18573ffffffffffffffffffffffffffffffffffffffff166000526001602052604060002090565b610f72565b6040519384934292859094939273ffffffffffffffffffffffffffffffffffffffff60609381608085019816845216602083015260408201520152565b0390a1005b7f654c8bbf0000000000000000000000000000000000000000000000000000000060005273ffffffffffffffffffffffffffffffffffffffff9283166004529190911660245260445260646000fd5b6040517f44b7a405000000000000000000000000000000000000000000000000000000008152948594610552949160048701610e31565b0390fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b805482101561059d5760005260206000200190600090565b610556565b3461011c5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261011c576105d9610121565b6106306105e4610144565b73ffffffffffffffffffffffffffffffffffffffff6044359316600052600160205260406000209073ffffffffffffffffffffffffffffffffffffffff16600052602052604060002090565b90815481101561011c5761064391610585565b905460405160039290921b1c8152602090f35b3461011c5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261011c5760206106e2610692610121565b73ffffffffffffffffffffffffffffffffffffffff6106af610144565b91166000526001835260406000209073ffffffffffffffffffffffffffffffffffffffff16600052602052604060002090565b54604051908152f35b8015150361011c57565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761076557604052565b6106f5565b6040519061077960e083610724565b565b6040519061077961012083610724565b359060ff8216820361011c57565b3590610779826106eb565b67ffffffffffffffff81116107655760051b60200190565b81601f8201121561011c5780359067ffffffffffffffff8211610765576040519261080f60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8601160185610724565b8284526020838301011161011c57816000926020809301838601378301015290565b81601f8201121561011c57803590610848826107a4565b926108566040519485610724565b82845260208085019360051b8301019181831161011c5760208101935b83851061088257505050505090565b843567ffffffffffffffff811161011c57820160e07fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0828603011261011c576108c961076a565b916108d660208301610167565b83526040820135602084015260608201359267ffffffffffffffff841161011c5760e08361090b8860208098819801016107bc565b60408401526080810135606084015261092660a08201610799565b608084015261093760c08201610799565b60a0840152013560c0820152815201940193610873565b9080601f8301121561011c578135610965816107a4565b926109736040519485610724565b81845260208085019260051b82010192831161011c57602001905b82821061099b5750505090565b602080916109a884610167565b81520191019061098e565b9190916101208184031261011c576109c961077b565b926109d38261078b565b84526109e160208301610799565b6020850152604082013567ffffffffffffffff811161011c5781610a06918401610831565b6040850152606082013560608501526080820135608085015260a082013567ffffffffffffffff811161011c5781610a3f9184016107bc565b60a085015260c082013560c085015260e082013560e085015261010082013567ffffffffffffffff811161011c57610a77920161094e565b610100830152565b610bc8610b5a610b55610b9c936020860135610a9a816106eb565b15610bce576000905b73ffffffffffffffffffffffffffffffffffffffff6040519160208301937f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f85527fd50a220b5983c5d6e86926072ffa8a3197ae49602ffc9dd0e60d62d561a2e1d560408501527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6606085015260808401521660a082015260a08152610b4a60c082610724565b5190209436906109b3565b611236565b60405192839160208301958690916042927f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201520190565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282610724565b51902090565b4690610aa3565b90357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18236030181121561011c57016020813591019167ffffffffffffffff821161011c578160051b3603831361011c57565b90357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18236030181121561011c57016020813591019167ffffffffffffffff821161011c57813603831361011c57565b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0938186528686013760008582860101520116010190565b90602083828152019160208260051b8501019381936000915b848310610ce05750505050505090565b9091929394957fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08282030183528635907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff218636030182121561011c576020809187600194019073ffffffffffffffffffffffffffffffffffffffff610d6483610167565b168152828201358382015260c080610d93610d826040860186610c28565b60e0604087015260e0860191610c78565b93606081013560608501526080810135610dac816106eb565b15156080850152610dcd60a0820135610dc4816106eb565b151560a0860152565b01359101529801930193019194939290610cd0565b9160209082815201919060005b818110610dfc5750505090565b90919260208060019273ffffffffffffffffffffffffffffffffffffffff610e2388610167565b168152019401929101610def565b93919273ffffffffffffffffffffffffffffffffffffffff610f6f969481610f619416875216602086015260806040860152610e7a60808601610e738361078b565b60ff169052565b610e92610e8960208301610799565b151560a0870152565b610f30610f0e610ebb610ea86040850185610bd5565b61012060c08b01526101a08a0191610cb7565b606084013560e08901526080840135610100890152610edd60a0850185610c28565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff808a8403016101208b0152610c78565b9160c081013561014088015260e0810135610160880152610100810190610bd5565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8087840301610180880152610de2565b926060818503910152610c78565b90565b80546801000000000000000081101561076557610f9491600182018155610585565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff829392549160031b92831b921b1916179055565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b9190820391821161100557565b610fc9565b9190820180921161100557565b9093929384831161011c57841161011c578101920390565b600094938593929091845b83811061104a5750505050509190565b8481013560f81c97959060019081019081908a146111285750600389146110fc57600489146110a2577fb2505f7c00000000000000000000000000000000000000000000000000000000600052600489905260246000fd5b6003810198506110d1906110c9906110c09088013560e81c8b61100a565b809a8789611017565b90858561102f565b906110ee929081156110f4575b5096600052602052604060002090565b9661103a565b9050386110de565b95975094808501359060200197801561112257906110ee91600052602052604060002090565b506110ee565b67ffffffffffffffff9950919691868101803560601c9250601481013560e81c9160179091013560c01c90601f019a169161118b866103e6836103c18973ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b548281151591826111e7575b826111d2575b50506111c9575b906111af92916116ba565b90801561112257906110ee91600052602052604060002090565b600198506111a4565b6111de91925042610ff8565b1015823861119d565b858110159250611197565b805160209091019060005b81811061120a5750505090565b825173ffffffffffffffffffffffffffffffffffffffff168452602093840193909201916001016111fd565b61010081015160405161125181610b9c6020820180956111f2565b51902090611260815160ff1690565b60ff8116806112d957505090610bc861127c604084015161176c565b92610b9c60806060830151920151936040519485936020850197889094939260809260a08301967f11e1e4079a79a66e4ade50033cfe2678cdd5341d2dfe5ef9513edb1a0be147a284526020840152604083015260608201520152565b6001810361133757505060a001518051602091820120604080517fe19a3b94fc3c7ece3f890d98a99bc422615537a08dea0603fa8425867d87d46693810193845290810191909152606081019290925290610bc88160808101610b9c565b6002810361138d57505060c00151604080517f11fdeb7e8373a1aa96bfac8d0ea91526b2c5d15e5cee20e0543e780258f3e8e460208201908152918101929092526060820192909252610bc88160808101610b9c565b6003036113e1575060e00151604080517fe19a3b94fc3c7ece3f890d98a99bc422615537a08dea0603fa8425867d87d46660208201908152918101929092526060820192909252610bc88160808101610b9c565b7f048183200000000000000000000000000000000000000000000000000000000060005260ff1660045260246000fd5b6040513d6000823e3d90fd5b9081602091031261011c57517fffffffff000000000000000000000000000000000000000000000000000000008116810361011c5790565b604090610f6f949281528160208201520191610c78565b9161147991939492610a7f565b9160408214611576575b803b6114925750505050600090565b6114e49373ffffffffffffffffffffffffffffffffffffffff602094604051968795869485937f1626ba7e00000000000000000000000000000000000000000000000000000000855260048501611455565b0392165afa908115611571577f1626ba7e00000000000000000000000000000000000000000000000000000000917fffffffff0000000000000000000000000000000000000000000000000000000091600091611542575b50161490565b611564915060203d60201161156a575b61155c8183610724565b81019061141d565b3861153c565b503d611552565b611411565b602060006115ad611586876118a7565b50604080518a815260ff909216602083015281019290925260608201529081906080820190565b838052039060015afa156115715760005173ffffffffffffffffffffffffffffffffffffffff8083169116036114835750505050600190565b90610bc8610b5a610b9c92602085015115156000146116b3576000905b73ffffffffffffffffffffffffffffffffffffffff6040519160208301937f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f85527f4aa45ca7ad825ceb1bf35643f0a58c295239df563b1b565c2485f96477c5631860408501527f2a80e1ef1d7842f27f2e6be0972bb708b9a135c38860dbe73c27c3486c34f4de606085015260808401521660a082015260a081526116aa60c082610724565b51902093611236565b4690611603565b91604051917fffffffffffffffffffffffffffffffffffffffff00000000000000000000000060208401947f53657175656e6365207265636f76657279206c6561663a0a0000000000000000865260601b166038840152604c830152606c820152606c8152610bc8608c82610724565b805182101561059d5760209160051b010190565b805160209091019060005b8181106117565750505090565b8251845260209384019390920191600101611749565b9081517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06117b261179c836107a4565b926117aa6040519485610724565b8084526107a4565b0136602083013760005b835181101561188e57806117d26001928661172a565b5173ffffffffffffffffffffffffffffffffffffffff81511690602081015190604081015160208151910120906060810151608082015115159060c060a08401511515930151936040519560208701977f0603985259a953da1f65a522f589c17bd1d0117ec1d3abb7c0788aef251ef437895260408801526060870152608086015260a085015260c084015260e0830152610100820152610100815261187a61012082610724565b519020611887828561172a565b52016117bc565b50909150604051610bc881610b9c60208201809561173e565b9060208235920135906040601b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84169360ff1c0160ff8111611005579156fea26469706673582212208a0965e30b11dbd17fc766bf7a6742beab6b3f414dbafa48033ffaba23b4af2464736f6c634300081c0033
Recovery.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.27; import { Payload } from "../../modules/Payload.sol"; import { IERC1271, IERC1271_MAGIC_VALUE_HASH } from "../../modules/interfaces/IERC1271.sol"; import { ISapientCompact } from "../../modules/interfaces/ISapient.sol"; import { LibBytes } from "../../utils/LibBytes.sol"; import { LibOptim } from "../../utils/LibOptim.sol"; using LibBytes for bytes; /// @title Recovery /// @author Agustin Aguilar, William Hua, Michael Standen /// @notice A recovery mode sapient signer contract Recovery is ISapientCompact { bytes32 private constant EIP712_DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); bytes32 private constant EIP712_DOMAIN_NAME_SEQUENCE = keccak256("Sequence Wallet - Recovery Mode"); bytes32 private constant EIP712_DOMAIN_VERSION_SEQUENCE = keccak256("1"); // Make them similar to the flags in BaseSig.sol uint256 internal constant FLAG_RECOVERY_LEAF = 1; uint256 internal constant FLAG_NODE = 3; uint256 internal constant FLAG_BRANCH = 4; /// @notice Emitted when a new payload is queued event NewQueuedPayload(address _wallet, address _signer, bytes32 _payloadHash, uint256 _timestamp); /// @notice Error thrown when the signature is invalid error InvalidSignature(address _wallet, address _signer, Payload.Decoded _payload, bytes _signature); /// @notice Error thrown when the payload is already queued error AlreadyQueued(address _wallet, address _signer, bytes32 _payloadHash); /// @notice Error thrown when the queue is not ready error QueueNotReady(address _wallet, bytes32 _payloadHash); /// @notice Error thrown when the signature flag is invalid error InvalidSignatureFlag(uint256 _flag); function domainSeparator(bool _noChainId, address _wallet) internal view returns (bytes32 _domainSeparator) { return keccak256( abi.encode( EIP712_DOMAIN_TYPEHASH, EIP712_DOMAIN_NAME_SEQUENCE, EIP712_DOMAIN_VERSION_SEQUENCE, _noChainId ? uint256(0) : uint256(block.chainid), _wallet ) ); } /// @notice Mapping of queued timestamps /// @dev wallet -> signer -> payloadHash -> timestamp mapping(address => mapping(address => mapping(bytes32 => uint256))) public timestampForQueuedPayload; /// @notice Mapping of queued payload hashes /// @dev wallet -> signer -> payloadHash[] mapping(address => mapping(address => bytes32[])) public queuedPayloadHashes; /// @notice Get the total number of queued payloads /// @param _wallet The wallet to get the total number of queued payloads for /// @param _signer The signer to get the total number of queued payloads for /// @return The total number of queued payloads function totalQueuedPayloads(address _wallet, address _signer) public view returns (uint256) { return queuedPayloadHashes[_wallet][_signer].length; } function _leafForRecoveryLeaf( address _signer, uint256 _requiredDeltaTime, uint256 _minTimestamp ) internal pure returns (bytes32) { return keccak256(abi.encodePacked("Sequence recovery leaf:\n", _signer, _requiredDeltaTime, _minTimestamp)); } function _recoverBranch( address _wallet, bytes32 _payloadHash, bytes calldata _signature ) internal view returns (bool verified, bytes32 root) { uint256 rindex; while (rindex < _signature.length) { // The first byte is the flag, it determines if we are reading uint256 flag; (flag, rindex) = _signature.readUint8(rindex); if (flag == FLAG_RECOVERY_LEAF) { // Read the signer and requiredDeltaTime address signer; uint256 requiredDeltaTime; uint256 minTimestamp; (signer, rindex) = _signature.readAddress(rindex); (requiredDeltaTime, rindex) = _signature.readUint24(rindex); (minTimestamp, rindex) = _signature.readUint64(rindex); // Check if we have a queued payload for this signer uint256 queuedAt = timestampForQueuedPayload[_wallet][signer][_payloadHash]; if (queuedAt != 0 && queuedAt >= minTimestamp && block.timestamp - queuedAt >= requiredDeltaTime) { verified = true; } bytes32 node = _leafForRecoveryLeaf(signer, requiredDeltaTime, minTimestamp); root = root != bytes32(0) ? LibOptim.fkeccak256(root, node) : node; continue; } if (flag == FLAG_NODE) { // Read node hash bytes32 node; (node, rindex) = _signature.readBytes32(rindex); root = root != bytes32(0) ? LibOptim.fkeccak256(root, node) : node; continue; } if (flag == FLAG_BRANCH) { // Read size uint256 size; (size, rindex) = _signature.readUint24(rindex); // Enter a branch of the signature merkle tree uint256 nrindex = rindex + size; (bool nverified, bytes32 nroot) = _recoverBranch(_wallet, _payloadHash, _signature[rindex:nrindex]); rindex = nrindex; verified = verified || nverified; root = LibOptim.fkeccak256(root, nroot); continue; } revert InvalidSignatureFlag(flag); } return (verified, root); } /// @notice Get the recovery payload hash /// @param _wallet The wallet to get the recovery payload hash for /// @param _payload The payload to get the recovery payload hash for /// @return The recovery payload hash function recoveryPayloadHash(address _wallet, Payload.Decoded calldata _payload) public view returns (bytes32) { bytes32 domain = domainSeparator(_payload.noChainId, _wallet); bytes32 structHash = Payload.toEIP712(_payload); return keccak256(abi.encodePacked("\x19\x01", domain, structHash)); } /// @inheritdoc ISapientCompact function recoverSapientSignatureCompact( bytes32 _payloadHash, bytes calldata _signature ) external view returns (bytes32) { (bool verified, bytes32 root) = _recoverBranch(msg.sender, _payloadHash, _signature); if (!verified) { revert QueueNotReady(msg.sender, _payloadHash); } return root; } /// @notice Queue a payload for recovery /// @param _wallet The wallet to queue the payload for /// @param _signer The signer to queue the payload for /// @param _payload The payload to queue /// @param _signature The signature to queue the payload for function queuePayload( address _wallet, address _signer, Payload.Decoded calldata _payload, bytes calldata _signature ) external { if (!isValidSignature(_wallet, _signer, _payload, _signature)) { revert InvalidSignature(_wallet, _signer, _payload, _signature); } bytes32 payloadHash = Payload.hashFor(_payload, _wallet); if (timestampForQueuedPayload[_wallet][_signer][payloadHash] != 0) { revert AlreadyQueued(_wallet, _signer, payloadHash); } timestampForQueuedPayload[_wallet][_signer][payloadHash] = block.timestamp; queuedPayloadHashes[_wallet][_signer].push(payloadHash); emit NewQueuedPayload(_wallet, _signer, payloadHash, block.timestamp); } function isValidSignature( address _wallet, address _signer, Payload.Decoded calldata _payload, bytes calldata _signature ) internal view returns (bool) { bytes32 rPayloadHash = recoveryPayloadHash(_wallet, _payload); if (_signature.length == 64) { // Try an ECDSA signature bytes32 r; bytes32 s; uint8 v; (r, s, v,) = _signature.readRSVCompact(0); address addr = ecrecover(rPayloadHash, v, r, s); if (addr == _signer) { return true; } } if (_signer.code.length != 0) { // ERC1271 return IERC1271(_signer).isValidSignature(rPayloadHash, _signature) == IERC1271_MAGIC_VALUE_HASH; } return false; } }
Payload.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.27; import { LibBytes } from "../utils/LibBytes.sol"; using LibBytes for bytes; /// @title Payload /// @author Agustin Aguilar, Michael Standen, William Hua /// @notice Library for encoding and decoding payloads library Payload { /// @notice Error thrown when the kind is invalid error InvalidKind(uint8 kind); /// @dev keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") bytes32 private constant EIP712_DOMAIN_TYPEHASH = 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; /// @dev keccak256("Sequence Wallet") bytes32 private constant EIP712_DOMAIN_NAME_SEQUENCE = 0x4aa45ca7ad825ceb1bf35643f0a58c295239df563b1b565c2485f96477c56318; /// @dev keccak256("3") bytes32 private constant EIP712_DOMAIN_VERSION_SEQUENCE = 0x2a80e1ef1d7842f27f2e6be0972bb708b9a135c38860dbe73c27c3486c34f4de; function domainSeparator(bool _noChainId, address _wallet) internal view returns (bytes32 _domainSeparator) { return keccak256( abi.encode( EIP712_DOMAIN_TYPEHASH, EIP712_DOMAIN_NAME_SEQUENCE, EIP712_DOMAIN_VERSION_SEQUENCE, _noChainId ? uint256(0) : uint256(block.chainid), _wallet ) ); } /// @dev keccak256("Call(address to,uint256 value,bytes data,uint256 gasLimit,bool delegateCall,bool onlyFallback,uint256 behaviorOnError)") bytes32 private constant CALL_TYPEHASH = 0x0603985259a953da1f65a522f589c17bd1d0117ec1d3abb7c0788aef251ef437; /// @dev keccak256("Calls(Call[] calls,uint256 space,uint256 nonce,address[] wallets)Call(address to,uint256 value,bytes data,uint256 gasLimit,bool delegateCall,bool onlyFallback,uint256 behaviorOnError)") bytes32 private constant CALLS_TYPEHASH = 0x11e1e4079a79a66e4ade50033cfe2678cdd5341d2dfe5ef9513edb1a0be147a2; /// @dev keccak256("Message(bytes message,address[] wallets)") bytes32 private constant MESSAGE_TYPEHASH = 0xe19a3b94fc3c7ece3f890d98a99bc422615537a08dea0603fa8425867d87d466; /// @dev keccak256("ConfigUpdate(bytes32 imageHash,address[] wallets)") bytes32 private constant CONFIG_UPDATE_TYPEHASH = 0x11fdeb7e8373a1aa96bfac8d0ea91526b2c5d15e5cee20e0543e780258f3e8e4; /// @notice Kind of transaction uint8 public constant KIND_TRANSACTIONS = 0x00; /// @notice Kind of digest uint8 public constant KIND_MESSAGE = 0x01; /// @notice Kind of config update uint8 public constant KIND_CONFIG_UPDATE = 0x02; /// @notice Kind of message uint8 public constant KIND_DIGEST = 0x03; /// @notice Behavior on error: ignore error uint8 public constant BEHAVIOR_IGNORE_ERROR = 0x00; /// @notice Behavior on error: revert on error uint8 public constant BEHAVIOR_REVERT_ON_ERROR = 0x01; /// @notice Behavior on error: abort on error uint8 public constant BEHAVIOR_ABORT_ON_ERROR = 0x02; /// @notice Payload call information /// @param to Address of the target contract /// @param value Value to send with the call /// @param data Data to send with the call /// @param gasLimit Gas limit for the call /// @param delegateCall If the call is a delegate call /// @param onlyFallback If the call should only be executed in an error scenario /// @param behaviorOnError Behavior on error struct Call { address to; uint256 value; bytes data; uint256 gasLimit; bool delegateCall; bool onlyFallback; uint256 behaviorOnError; } /// @notice Decoded payload /// @param kind Kind of payload /// @param noChainId If the chain ID should be omitted /// @param calls Array of calls (transaction kind) /// @param space Nonce space for the calls (transaction kind) /// @param nonce Nonce value for the calls (transaction kind) /// @param message Message to validate (message kind) /// @param imageHash Image hash to update to (config update kind) /// @param digest Digest to validate (digest kind) /// @param parentWallets Parent wallets struct Decoded { uint8 kind; bool noChainId; // Transaction kind Call[] calls; uint256 space; uint256 nonce; // Message kind bytes message; // Config update kind bytes32 imageHash; // Digest kind for 1271 bytes32 digest; // Parent wallets address[] parentWallets; } function fromMessage( bytes memory message ) internal pure returns (Decoded memory _decoded) { _decoded.kind = KIND_MESSAGE; _decoded.message = message; } function fromConfigUpdate( bytes32 imageHash ) internal pure returns (Decoded memory _decoded) { _decoded.kind = KIND_CONFIG_UPDATE; _decoded.imageHash = imageHash; } function fromDigest( bytes32 digest ) internal pure returns (Decoded memory _decoded) { _decoded.kind = KIND_DIGEST; _decoded.digest = digest; } function fromPackedCalls( bytes calldata packed ) internal view returns (Decoded memory _decoded) { _decoded.kind = KIND_TRANSACTIONS; // Read the global flag (uint256 globalFlag, uint256 pointer) = packed.readFirstUint8(); // First bit determines if space is zero or not if (globalFlag & 0x01 == 0x01) { _decoded.space = 0; } else { (_decoded.space, pointer) = packed.readUint160(pointer); } // Next 3 bits determine the size of the nonce uint256 nonceSize = (globalFlag >> 1) & 0x07; if (nonceSize > 0) { // Read the nonce (_decoded.nonce, pointer) = packed.readUintX(pointer, nonceSize); } uint256 numCalls; // Bit 5 determines if the batch contains a single call if (globalFlag & 0x10 == 0x10) { numCalls = 1; } else { // Bit 6 determines if the number of calls uses 1 byte or 2 bytes if (globalFlag & 0x20 == 0x20) { (numCalls, pointer) = packed.readUint16(pointer); } else { (numCalls, pointer) = packed.readUint8(pointer); } } // Read the calls _decoded.calls = new Call[](numCalls); for (uint256 i = 0; i < numCalls; i++) { uint8 flags; (flags, pointer) = packed.readUint8(pointer); // First bit determines if this is a call to self // or a call to another address if (flags & 0x01 == 0x01) { // Call to self _decoded.calls[i].to = address(this); } else { // Call to another address (_decoded.calls[i].to, pointer) = packed.readAddress(pointer); } // Second bit determines if the call has value or not if (flags & 0x02 == 0x02) { (_decoded.calls[i].value, pointer) = packed.readUint256(pointer); } // Third bit determines if the call has data or not if (flags & 0x04 == 0x04) { // 3 bytes determine the size of the calldata uint256 calldataSize; (calldataSize, pointer) = packed.readUint24(pointer); _decoded.calls[i].data = packed[pointer:pointer + calldataSize]; pointer += calldataSize; } // Fourth bit determines if the call has a gas limit or not if (flags & 0x08 == 0x08) { (_decoded.calls[i].gasLimit, pointer) = packed.readUint256(pointer); } // Fifth bit determines if the call is a delegate call or not _decoded.calls[i].delegateCall = (flags & 0x10 == 0x10); // Sixth bit determines if the call is fallback only _decoded.calls[i].onlyFallback = (flags & 0x20 == 0x20); // Last 2 bits are directly mapped to the behavior on error _decoded.calls[i].behaviorOnError = (flags & 0xC0) >> 6; } } function hashCall( Call memory c ) internal pure returns (bytes32) { return keccak256( abi.encode( CALL_TYPEHASH, c.to, c.value, keccak256(c.data), c.gasLimit, c.delegateCall, c.onlyFallback, c.behaviorOnError ) ); } function hashCalls( Call[] memory calls ) internal pure returns (bytes32) { // In EIP712, an array is often hashed as the keccak256 of the concatenated // hashes of each item. So we hash each Call, pack them, and hash again. bytes32[] memory callHashes = new bytes32[](calls.length); for (uint256 i = 0; i < calls.length; i++) { callHashes[i] = hashCall(calls[i]); } return keccak256(abi.encodePacked(callHashes)); } function toEIP712( Decoded memory _decoded ) internal pure returns (bytes32) { bytes32 walletsHash = keccak256(abi.encodePacked(_decoded.parentWallets)); if (_decoded.kind == KIND_TRANSACTIONS) { bytes32 callsHash = hashCalls(_decoded.calls); // The top-level struct for Calls might be something like: // Calls(bytes32 callsHash,uint256 space,uint256 nonce,bytes32 walletsHash) return keccak256(abi.encode(CALLS_TYPEHASH, callsHash, _decoded.space, _decoded.nonce, walletsHash)); } else if (_decoded.kind == KIND_MESSAGE) { // If you define your top-level as: Message(bytes32 messageHash,bytes32 walletsHash) return keccak256(abi.encode(MESSAGE_TYPEHASH, keccak256(_decoded.message), walletsHash)); } else if (_decoded.kind == KIND_CONFIG_UPDATE) { // Top-level: ConfigUpdate(bytes32 imageHash,bytes32 walletsHash) return keccak256(abi.encode(CONFIG_UPDATE_TYPEHASH, _decoded.imageHash, walletsHash)); } else if (_decoded.kind == KIND_DIGEST) { // Top-level: Use MESSAGE_TYPEHASH but assume the digest is already the hashed message return keccak256(abi.encode(MESSAGE_TYPEHASH, _decoded.digest, walletsHash)); } else { // Unknown kind revert InvalidKind(_decoded.kind); } } function hash( Decoded memory _decoded ) internal view returns (bytes32) { bytes32 domain = domainSeparator(_decoded.noChainId, address(this)); bytes32 structHash = toEIP712(_decoded); return keccak256(abi.encodePacked("\x19\x01", domain, structHash)); } function hashFor(Decoded memory _decoded, address _wallet) internal view returns (bytes32) { bytes32 domain = domainSeparator(_decoded.noChainId, _wallet); bytes32 structHash = toEIP712(_decoded); return keccak256(abi.encodePacked("\x19\x01", domain, structHash)); } }
IERC1271.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.18; bytes4 constant IERC1271_MAGIC_VALUE_HASH = 0x1626ba7e; bytes4 constant IERC1271_MAGIC_VALUE_BYTES = 0x20c13b0b; /// @title IERC1271 /// @notice Interface for ERC1271 interface IERC1271 { /// @notice Verifies whether the provided signature is valid with respect to the provided hash /// @dev MUST return the correct magic value if the signature provided is valid for the provided hash /// > The bytes4 magic value to return when signature is valid is 0x1626ba7e : bytes4(keccak256("isValidSignature(bytes32,bytes)") /// > This function MAY modify Ethereum's state /// @param _hash keccak256 hash that was signed /// @param _signature Signature byte array associated with _data /// @return magicValue Magic value 0x1626ba7e if the signature is valid and 0x0 otherwise function isValidSignature(bytes32 _hash, bytes calldata _signature) external view returns (bytes4 magicValue); } /// @title IERC1271Data /// @notice Deprecated interface for ERC1271 using bytes instead of bytes32 interface IERC1271Data { /// @notice Verifies whether the provided signature is valid with respect to the provided hash /// @dev MUST return the correct magic value if the signature provided is valid for the provided hash /// > The bytes4 magic value to return when signature is valid is 0x20c13b0b : bytes4(keccak256("isValidSignature(bytes,bytes)") /// > This function MAY modify Ethereum's state /// @param _data Data that was signed /// @param _signature Signature byte array associated with _data /// @return magicValue Magic value 0x20c13b0b if the signature is valid and 0x0 otherwise function isValidSignature(bytes calldata _data, bytes calldata _signature) external view returns (bytes4 magicValue); }
ISapient.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.27; import { Payload } from "../Payload.sol"; /// @title ISapient /// @author Agustin Aguilar, Michael Standen /// @notice Sapient signers take an explicit payload and return their own "imageHash" as result /// @dev The consumer of this signer must validate if the imageHash is valid or not, for the desired configuration interface ISapient { /// @notice Recovers the image hash of a given signature /// @param payload The payload to recover the signature from /// @param signature The signature to recover the image hash from /// @return imageHash The recovered image hash function recoverSapientSignature( Payload.Decoded calldata payload, bytes calldata signature ) external view returns (bytes32 imageHash); } /// @title ISapientCompact /// @author Agustin Aguilar, Michael Standen /// @notice Sapient signers take a compacted payload and return their own "imageHash" as result /// @dev The consumer of this signer must validate if the imageHash is valid or not, for the desired configuration interface ISapientCompact { /// @notice Recovers the image hash of a given signature, using a hashed payload /// @param digest The digest of the payload /// @param signature The signature to recover the image hash from /// @return imageHash The recovered image hash function recoverSapientSignatureCompact( bytes32 digest, bytes calldata signature ) external view returns (bytes32 imageHash); }
LibBytes.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.18; /// @title Library for reading data from bytes arrays /// @author Agustin Aguilar (aa@horizon.io), Michael Standen (mstan@horizon.io) /// @notice This library contains functions for reading data from bytes arrays. /// @dev These functions do not check if the input index is within the bounds of the data array. /// @dev Reading out of bounds may return dirty values. library LibBytes { function readFirstUint8( bytes calldata _data ) internal pure returns (uint8 a, uint256 newPointer) { assembly { let word := calldataload(_data.offset) a := shr(248, word) newPointer := 1 } } function readUint8(bytes calldata _data, uint256 _index) internal pure returns (uint8 a, uint256 newPointer) { assembly { let word := calldataload(add(_index, _data.offset)) a := shr(248, word) newPointer := add(_index, 1) } } function readUint16(bytes calldata _data, uint256 _index) internal pure returns (uint16 a, uint256 newPointer) { assembly { let word := calldataload(add(_index, _data.offset)) a := shr(240, word) newPointer := add(_index, 2) } } function readUint24(bytes calldata _data, uint256 _index) internal pure returns (uint24 a, uint256 newPointer) { assembly { let word := calldataload(add(_index, _data.offset)) a := shr(232, word) newPointer := add(_index, 3) } } function readUint64(bytes calldata _data, uint256 _index) internal pure returns (uint64 a, uint256 newPointer) { assembly { let word := calldataload(add(_index, _data.offset)) a := shr(192, word) newPointer := add(_index, 8) } } function readUint160(bytes calldata _data, uint256 _index) internal pure returns (uint160 a, uint256 newPointer) { assembly { let word := calldataload(add(_index, _data.offset)) a := shr(96, word) newPointer := add(_index, 20) } } function readUint256(bytes calldata _data, uint256 _index) internal pure returns (uint256 a, uint256 newPointer) { assembly { a := calldataload(add(_index, _data.offset)) newPointer := add(_index, 32) } } function readUintX( bytes calldata _data, uint256 _index, uint256 _length ) internal pure returns (uint256 a, uint256 newPointer) { assembly { let word := calldataload(add(_index, _data.offset)) let shift := sub(256, mul(_length, 8)) a := and(shr(shift, word), sub(shl(mul(8, _length), 1), 1)) newPointer := add(_index, _length) } } function readBytes4(bytes calldata _data, uint256 _pointer) internal pure returns (bytes4 a, uint256 newPointer) { assembly { let word := calldataload(add(_pointer, _data.offset)) a := and(word, 0xffffffff00000000000000000000000000000000000000000000000000000000) newPointer := add(_pointer, 4) } } function readBytes32(bytes calldata _data, uint256 _pointer) internal pure returns (bytes32 a, uint256 newPointer) { assembly { a := calldataload(add(_pointer, _data.offset)) newPointer := add(_pointer, 32) } } function readAddress(bytes calldata _data, uint256 _index) internal pure returns (address a, uint256 newPointer) { assembly { let word := calldataload(add(_index, _data.offset)) a := and(shr(96, word), 0xffffffffffffffffffffffffffffffffffffffff) newPointer := add(_index, 20) } } /// @dev ERC-2098 Compact Signature function readRSVCompact( bytes calldata _data, uint256 _index ) internal pure returns (bytes32 r, bytes32 s, uint8 v, uint256 newPointer) { uint256 yParityAndS; assembly { r := calldataload(add(_index, _data.offset)) yParityAndS := calldataload(add(_index, add(_data.offset, 32))) newPointer := add(_index, 64) } uint256 yParity = uint256(yParityAndS >> 255); s = bytes32(uint256(yParityAndS) & ((1 << 255) - 1)); v = uint8(yParity) + 27; } }
LibOptim.sol
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.18; /// @title LibOptim /// @author Agustin Aguilar /// @notice Library for optimized EVM operations library LibOptim { /** * @notice Computes the keccak256 hash of two 32-byte inputs. * @dev It uses only scratch memory space. * @param _a The first 32 bytes of the hash. * @param _b The second 32 bytes of the hash. * @return c The keccak256 hash of the two 32-byte inputs. */ function fkeccak256(bytes32 _a, bytes32 _b) internal pure returns (bytes32 c) { assembly { mstore(0, _a) mstore(32, _b) c := keccak256(0, 64) } } /** * @notice Returns the return data from the last call. * @return r The return data from the last call. */ function returnData() internal pure returns (bytes memory r) { assembly { let size := returndatasize() r := mload(0x40) let start := add(r, 32) mstore(0x40, add(start, size)) mstore(r, size) returndatacopy(start, 0, size) } } /** * @notice Calls another contract with the given parameters. * @dev This method doesn't increase the memory pointer. * @param _to The address of the contract to call. * @param _val The value to send to the contract. * @param _gas The amount of gas to provide for the call. * @param _data The data to send to the contract. * @return r The success status of the call. */ function call(address _to, uint256 _val, uint256 _gas, bytes memory _data) internal returns (bool r) { assembly { r := call(_gas, _to, _val, add(_data, 32), mload(_data), 0, 0) } } /** * @notice Calls another contract with the given parameters, using delegatecall. * @dev This method doesn't increase the memory pointer. * @param _to The address of the contract to call. * @param _gas The amount of gas to provide for the call. * @param _data The data to send to the contract. * @return r The success status of the call. */ function delegatecall(address _to, uint256 _gas, bytes memory _data) internal returns (bool r) { assembly { r := delegatecall(_gas, _to, add(_data, 32), mload(_data), 0, 0) } } }
Gas Token: