├── .env.example ├── .gitattributes ├── audits ├── cantina_audit_231215.pdf ├── cantina_audit_240320.pdf └── cantina_audit_240624.pdf ├── subgraph ├── tsconfig.json ├── .gitignore ├── abis │ ├── SyncStaking.json │ ├── VenusReward.json │ ├── VenusToken.json │ ├── SwapReferralFeePayer.json │ └── AccountFactory.json ├── package.json ├── tests │ ├── new-account-utils.ts │ └── new-account.test.ts ├── networks.json ├── src │ ├── referral.ts │ ├── sync-staking.ts │ ├── sync-clave-staking.ts │ ├── venus-reward.ts │ ├── erc-20-paymaster.ts │ ├── social-recovery.ts │ ├── account.ts │ ├── gasless-paymaster.ts │ ├── account-factory-v2.ts │ ├── odos-router.ts │ ├── account-factory.ts │ ├── zero-usdt-pool.ts │ ├── venus-pool-usdce.ts │ ├── venus-pool-usdt.ts │ ├── clave-appa-stake.ts │ ├── clave-zk-stake.ts │ ├── koi-pair.ts │ └── meow-staking.ts └── docker-compose.yml ├── balances-subgraph ├── tsconfig.json ├── networks.json ├── schema.graphql ├── .gitignore ├── package.json ├── src │ ├── account-factory.ts │ ├── erc20.ts │ └── helpers.ts ├── subgraph.yaml ├── docker-compose.yml └── abis │ └── ERC20.json ├── contracts ├── interfaces │ ├── IResolver.sol │ ├── IEmailRecoveryModule.sol │ ├── IClaveRegistry.sol │ ├── IModule.sol │ ├── IInitable.sol │ ├── IHook.sol │ ├── IEmailRecoverySubjectHandler.sol │ ├── IUpgradeManager.sol │ ├── IClave.sol │ ├── IValidator.sol │ ├── IModuleManager.sol │ ├── IHookManager.sol │ ├── IValidatorManager.sol │ └── IOwnerManager.sol ├── auth │ ├── SelfAuth.sol │ ├── HookAuth.sol │ ├── ModuleAuth.sol │ ├── BootloaderAuth.sol │ └── Auth.sol ├── test │ ├── MockToken.sol │ ├── MockValidator.sol │ ├── TestOracle.sol │ ├── MockModule.sol │ ├── TEEValidatorTest.sol │ ├── MockImplementation.sol │ ├── MockStorage.sol │ └── MockHook.sol ├── cns │ └── IClaveNameService.sol ├── earn │ └── ClaveEarnRouter.sol ├── validators │ ├── EOAValidator.sol │ └── TEEValidator.sol ├── libraries │ ├── SignatureDecoder.sol │ └── ClaveStorage.sol ├── referral │ └── SwapReferralFeePayer.sol ├── ClaveProxy.sol ├── managers │ ├── UpgradeManager.sol │ ├── ModuleManager.sol │ └── OwnerManager.sol ├── helpers │ ├── TokenCallbackHandler.sol │ └── VerifierCaller.sol ├── batch │ └── BatchCaller.sol ├── handlers │ ├── ValidationHandler.sol │ └── ERC1271Handler.sol ├── modules │ └── recovery │ │ └── EmailRecoveryModule.sol └── ClaveRegistry.sol ├── tsconfig.json ├── .eslintrc.js ├── .gas-snapshot ├── deploy ├── deploy.ts ├── interact.ts └── deploy-mvp.ts ├── test ├── utils │ ├── names.ts │ ├── p256.ts │ ├── managers │ │ ├── upgrademanager.ts │ │ ├── modulemanager.ts │ │ ├── hookmanager.ts │ │ ├── validatormanager.ts │ │ └── ownermanager.ts │ ├── paymasters.ts │ ├── passkey.ts │ ├── buffer.ts │ └── fixture.ts ├── deployments │ ├── rip7212.test.ts │ └── deployer.test.ts └── accounts │ └── managers │ └── upgrademanager.test.ts ├── README.md ├── tasks └── deploy.ts ├── package.json ├── CONTRIBUTING.md ├── .gitignore ├── hardhat.config.ts └── logo.svg /.env.example: -------------------------------------------------------------------------------- 1 | PRIVATE_KEY= 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | .gas-snapshot linguist-language=Julia -------------------------------------------------------------------------------- /audits/cantina_audit_231215.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getclave/clave-contracts/HEAD/audits/cantina_audit_231215.pdf -------------------------------------------------------------------------------- /audits/cantina_audit_240320.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getclave/clave-contracts/HEAD/audits/cantina_audit_240320.pdf -------------------------------------------------------------------------------- /audits/cantina_audit_240624.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getclave/clave-contracts/HEAD/audits/cantina_audit_240624.pdf -------------------------------------------------------------------------------- /subgraph/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@graphprotocol/graph-ts/types/tsconfig.base.json", 3 | "include": ["src", "tests"] 4 | } 5 | -------------------------------------------------------------------------------- /balances-subgraph/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@graphprotocol/graph-ts/types/tsconfig.base.json", 3 | "include": ["src", "tests"] 4 | } 5 | -------------------------------------------------------------------------------- /subgraph/.gitignore: -------------------------------------------------------------------------------- 1 | # Matchstick binaries 2 | tests/.* 3 | matchstick 4 | 5 | # Test coverage tools 6 | tests/.tools/ 7 | 8 | # Graph CLI 9 | generated/ 10 | build/ -------------------------------------------------------------------------------- /balances-subgraph/networks.json: -------------------------------------------------------------------------------- 1 | { 2 | "zksync-era": { 3 | "AccountFactory": { 4 | "address": "0x2B196aaB35184aa539E3D8360258CAF8d8309Ebc", 5 | "startBlock": 30099184 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /contracts/interfaces/IResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | interface IResolver { 5 | function resolve(bytes memory name, bytes memory data) external view returns (bytes memory); 6 | } 7 | -------------------------------------------------------------------------------- /contracts/interfaces/IEmailRecoveryModule.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | interface IEmailRecoveryModule { 5 | function canStartRecoveryRequest(address smartAccount) external view returns (bool); 6 | } 7 | -------------------------------------------------------------------------------- /contracts/interfaces/IClaveRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | interface IClaveRegistry { 5 | function register(address account) external; 6 | 7 | function isClave(address account) external view returns (bool); 8 | } 9 | -------------------------------------------------------------------------------- /contracts/interfaces/IModule.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | import {IInitable} from '../interfaces/IInitable.sol'; 5 | import {IERC165} from '@openzeppelin/contracts/utils/introspection/IERC165.sol'; 6 | 7 | interface IModule is IInitable, IERC165 {} 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "resolveJsonModule": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | module.exports = { 7 | parserOptions: { 8 | project: 'tsconfig.json', 9 | tsconfigRootDir: __dirname, 10 | sourceType: 'module', 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /contracts/interfaces/IInitable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | interface IInitable { 5 | event Inited(address indexed account); 6 | event Disabled(address indexed account); 7 | 8 | function init(bytes calldata initData) external; 9 | 10 | function disable() external; 11 | 12 | function isInited(address account) external view returns (bool); 13 | } 14 | -------------------------------------------------------------------------------- /balances-subgraph/schema.graphql: -------------------------------------------------------------------------------- 1 | type ClaveAccount @entity { 2 | "account address" 3 | id: Bytes! 4 | erc20balances: [ERC20Balance!]! @derivedFrom(field: "account") 5 | } 6 | 7 | type ERC20 @entity { 8 | "token address" 9 | id: Bytes! 10 | totalAmount: BigInt! 11 | } 12 | 13 | type ERC20Balance @entity { 14 | "account.id.concat(ERC20.id)" 15 | id: Bytes! 16 | account: ClaveAccount! 17 | token: ERC20! 18 | amount: BigInt! 19 | } 20 | -------------------------------------------------------------------------------- /.gas-snapshot: -------------------------------------------------------------------------------- 1 | _______________________ ________________________________________________ 2 | __ ____/__ |_ ___/ ___ __ \__ ____/__ __ \_ __ \__ __ \__ __/ 3 | _ / __ __ /| |____ \ __ /_/ /_ __/ __ /_/ / / / /_ /_/ /_ / 4 | / /_/ / _ ___ |___/ / _ _, _/_ /___ _ ____// /_/ /_ _, _/_ / 5 | \____/ /_/ |_/____/ /_/ |_| /_____/ /_/ \____/ /_/ |_| /_/ 6 | -------------------------------------------------------------------------------- /contracts/auth/SelfAuth.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | import {Errors} from '../libraries/Errors.sol'; 5 | 6 | /** 7 | * @title SelfAuth 8 | * @notice Abstract contract that allows only calls by the self contract 9 | * @author https://getclave.io 10 | */ 11 | abstract contract SelfAuth { 12 | modifier onlySelf() { 13 | if (msg.sender != address(this)) { 14 | revert Errors.NOT_FROM_SELF(); 15 | } 16 | _; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /balances-subgraph/.gitignore: -------------------------------------------------------------------------------- 1 | # Graph CLI generated artifacts 2 | build/ 3 | generated/ 4 | 5 | # Dependency directories 6 | node_modules/ 7 | jspm_packages/ 8 | 9 | # Logs 10 | logs 11 | *.log 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | 16 | # Optional npm cache directory 17 | .npm 18 | 19 | # Optional eslint cache 20 | .eslintcache 21 | 22 | # dotenv environment variables file 23 | .env 24 | 25 | # Testing 26 | coverage 27 | coverage.json 28 | 29 | # Typechain 30 | typechain 31 | typechain-types 32 | 33 | # Hardhat files 34 | cache 35 | -------------------------------------------------------------------------------- /contracts/auth/HookAuth.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | import {Errors} from '../libraries/Errors.sol'; 5 | 6 | /** 7 | * @title HookAuth 8 | * @notice Abstract contract that allows only calls from hooks 9 | * @author https://getclave.io 10 | */ 11 | abstract contract HookAuth { 12 | function _isHook(address addr) internal view virtual returns (bool); 13 | 14 | modifier onlyHook() { 15 | if (!_isHook(msg.sender)) { 16 | revert Errors.NOT_FROM_HOOK(); 17 | } 18 | _; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /contracts/auth/ModuleAuth.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | import {Errors} from '../libraries/Errors.sol'; 5 | 6 | /** 7 | * @title ModuleAuth 8 | * @notice Abstract contract that allows only calls from modules 9 | * @author https://getclave.io 10 | */ 11 | abstract contract ModuleAuth { 12 | function _isModule(address addr) internal view virtual returns (bool); 13 | 14 | modifier onlyModule() { 15 | if (!_isModule(msg.sender)) { 16 | revert Errors.NOT_FROM_MODULE(); 17 | } 18 | _; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /contracts/test/MockToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | pragma solidity ^0.8.17; 4 | 5 | import {ERC20} from '@openzeppelin/contracts/token/ERC20/ERC20.sol'; 6 | 7 | contract MockStable is ERC20 { 8 | uint8 private _decimals; 9 | 10 | constructor() ERC20('STABLE', 'STBL') { 11 | _decimals = 18; 12 | } 13 | 14 | function mint(address _to, uint256 _amount) public returns (bool) { 15 | _mint(_to, _amount); 16 | return true; 17 | } 18 | 19 | function decimals() public view override returns (uint8) { 20 | return _decimals; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /subgraph/abis/SyncStaking.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": true, 7 | "internalType": "address", 8 | "name": "from", 9 | "type": "address" 10 | }, 11 | { 12 | "indexed": true, 13 | "internalType": "address", 14 | "name": "reward", 15 | "type": "address" 16 | }, 17 | { 18 | "indexed": false, 19 | "internalType": "uint256", 20 | "name": "amount", 21 | "type": "uint256" 22 | } 23 | ], 24 | "name": "ClaimRewards", 25 | "type": "event" 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /contracts/auth/BootloaderAuth.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | import {BOOTLOADER_FORMAL_ADDRESS} from '@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol'; 5 | import {Errors} from '../libraries/Errors.sol'; 6 | 7 | /** 8 | * @title BootloaderAuth 9 | * @notice Abstract contract that allows only calls from bootloader 10 | * @author https://getclave.io 11 | */ 12 | abstract contract BootloaderAuth { 13 | modifier onlyBootloader() { 14 | if (msg.sender != BOOTLOADER_FORMAL_ADDRESS) { 15 | revert Errors.NOT_FROM_BOOTLOADER(); 16 | } 17 | _; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /deploy/deploy.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | import * as hre from 'hardhat'; 7 | 8 | import { deployContract } from './utils'; 9 | 10 | // An example of a basic deploy script 11 | // Do not push modifications to this file 12 | // Just modify, interact then revert changes 13 | export default async function (): Promise { 14 | const contractArtifactName = 'Example'; 15 | const constructorArguments = ['Clave', 324]; 16 | await deployContract(hre, contractArtifactName, constructorArguments); 17 | } 18 | -------------------------------------------------------------------------------- /subgraph/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clave-subgraph", 3 | "license": "UNLICENSED", 4 | "scripts": { 5 | "codegen": "graph codegen", 6 | "build": "graph build", 7 | "deploy": "graph deploy --node https://api.studio.thegraph.com/deploy/ clave", 8 | "create-local": "graph create --node http://localhost:8020/ clave", 9 | "remove-local": "graph remove --node http://localhost:8020/ clave", 10 | "deploy-local": "graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 clave", 11 | "test": "graph test" 12 | }, 13 | "dependencies": { 14 | "@graphprotocol/graph-cli": "0.71.1", 15 | "@graphprotocol/graph-ts": "0.35.1" 16 | }, 17 | "devDependencies": { 18 | "matchstick-as": "0.6.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /contracts/auth/Auth.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | import {BootloaderAuth} from './BootloaderAuth.sol'; 5 | import {ModuleAuth} from './ModuleAuth.sol'; 6 | import {SelfAuth} from './SelfAuth.sol'; 7 | import {HookAuth} from './HookAuth.sol'; 8 | import {Errors} from '../libraries/Errors.sol'; 9 | 10 | /** 11 | * @title Auth 12 | * @notice Abstract contract that organizes authentification logic for the contract 13 | * @author https://getclave.io 14 | */ 15 | abstract contract Auth is BootloaderAuth, SelfAuth, ModuleAuth, HookAuth { 16 | modifier onlySelfOrModule() { 17 | if (msg.sender != address(this) && !_isModule(msg.sender)) { 18 | revert Errors.NOT_FROM_SELF_OR_MODULE(); 19 | } 20 | _; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /contracts/cns/IClaveNameService.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | interface IClaveNameService { 5 | /** 6 | * @notice Resolve name to address from L2 7 | * @param _name string - Subdomain name to resolve 8 | * @return - address - Owner of the name 9 | */ 10 | function resolve(string memory _name) external view returns (address); 11 | 12 | /** 13 | * @notice Register a new name and issue as a ENS subdomain 14 | * @param to address - Owner of the registered address 15 | * @param _name string - Name to be registered 16 | * @dev Only owner of the given address or authorized accounts can register a name 17 | */ 18 | function registerName(address to, string memory _name) external returns (uint256); 19 | } 20 | -------------------------------------------------------------------------------- /balances-subgraph/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clave-balances-subgraph", 3 | "license": "UNLICENSED", 4 | "scripts": { 5 | "codegen": "graph codegen", 6 | "build": "graph build", 7 | "deploy": "graph deploy --node https://api.studio.thegraph.com/deploy/ clave-balances", 8 | "create-local": "graph create --node http://localhost:8020/ clave-balances", 9 | "remove-local": "graph remove --node http://localhost:8020/ clave-balances", 10 | "deploy-local": "graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 clave-balances", 11 | "test": "graph test" 12 | }, 13 | "dependencies": { 14 | "@graphprotocol/graph-cli": "0.71.1", 15 | "@graphprotocol/graph-ts": "0.35.1" 16 | }, 17 | "devDependencies": { 18 | "matchstick-as": "0.6.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /contracts/interfaces/IHook.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | import {Transaction} from '@matterlabs/zksync-contracts/l2/system-contracts/libraries/TransactionHelper.sol'; 5 | import {IInitable} from '../interfaces/IInitable.sol'; 6 | import {IERC165} from '@openzeppelin/contracts/utils/introspection/IERC165.sol'; 7 | 8 | interface IValidationHook is IInitable, IERC165 { 9 | function validationHook( 10 | bytes32 signedHash, 11 | Transaction calldata transaction, 12 | bytes calldata hookData 13 | ) external; 14 | } 15 | 16 | interface IExecutionHook is IInitable, IERC165 { 17 | function preExecutionHook( 18 | Transaction calldata transaction 19 | ) external returns (bytes memory context); 20 | 21 | function postExecutionHook(bytes memory context) external; 22 | } 23 | -------------------------------------------------------------------------------- /test/utils/names.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | 7 | export const CONTRACT_NAMES = { 8 | BATCH_CALLER: 'BatchCaller', 9 | REGISTRY: 'ClaveRegistry', 10 | IMPLEMENTATION: 'ClaveImplementation', 11 | PROXY: 'ClaveProxy', 12 | FACTORY: 'AccountFactory', 13 | MOCK_VALIDATOR: 'MockValidator', 14 | }; 15 | 16 | export enum VALIDATORS { 17 | MOCK = 'MockValidator', 18 | TEE = 'TEEValidator', 19 | EOA = 'EOAValidator', 20 | PASSKEY = 'PasskeyValidator', 21 | } 22 | 23 | export enum HOOKS { 24 | VALIDATION = 1, 25 | EXECUTION = 0, 26 | } 27 | 28 | export enum PAYMASTERS { 29 | GASLESS = 'GaslessPaymaster', 30 | ERC20 = 'ERC20Paymaster', 31 | ERC20_MOCK = 'ERC20PaymasterMock', 32 | } 33 | -------------------------------------------------------------------------------- /contracts/test/MockValidator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | import {IR1Validator, IERC165} from '../interfaces/IValidator.sol'; 5 | import {Transaction} from '@matterlabs/zksync-contracts/l2/system-contracts/libraries/TransactionHelper.sol'; 6 | 7 | /** 8 | * @title Mock validator contract implementing r1validator interface 9 | * @author https://getclave.io 10 | */ 11 | contract MockValidator is IR1Validator { 12 | function validateSignature( 13 | bytes32, 14 | bytes calldata, 15 | bytes32[2] calldata 16 | ) external pure override returns (bool valid) { 17 | valid = true; 18 | } 19 | 20 | /// @inheritdoc IERC165 21 | function supportsInterface(bytes4 interfaceId) external pure override returns (bool) { 22 | return 23 | interfaceId == type(IR1Validator).interfaceId || 24 | interfaceId == type(IERC165).interfaceId; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /contracts/earn/ClaveEarnRouter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | interface IZtake { 5 | function balanceOf(address account) external view returns (uint256); 6 | 7 | function earned(address account) external view returns (uint256); 8 | } 9 | 10 | contract ClaveEarnRouter { 11 | address public stakingAddress; 12 | 13 | constructor(address _stakingAddress) { 14 | stakingAddress = _stakingAddress; 15 | } 16 | 17 | function stakePositions( 18 | address account 19 | ) external view returns (uint256[] memory tokensInPosition, uint256[] memory rewards) { 20 | IZtake staking = IZtake(stakingAddress); 21 | uint256 balance = staking.balanceOf(account); 22 | uint256 earned = staking.earned(account); 23 | tokensInPosition = new uint256[](2); 24 | rewards = new uint256[](2); 25 | tokensInPosition[0] = balance; 26 | rewards[0] = earned; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /subgraph/tests/new-account-utils.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/consistent-type-imports */ 2 | 3 | /** 4 | * Copyright Clave - All Rights Reserved 5 | * Unauthorized copying of this file, via any medium is strictly prohibited 6 | * Proprietary and confidential 7 | */ 8 | import { Address, ethereum } from '@graphprotocol/graph-ts'; 9 | import { newMockEvent } from 'matchstick-as'; 10 | 11 | import { NewClaveAccount } from '../generated/AccountFactory/AccountFactory'; 12 | 13 | export function createNewClaveAccountEvent( 14 | accountAddress: Address, 15 | ): NewClaveAccount { 16 | const newClaveAccountEvent = changetype(newMockEvent()); 17 | newClaveAccountEvent.parameters = []; 18 | 19 | newClaveAccountEvent.parameters.push( 20 | new ethereum.EventParam( 21 | 'accountAddress', 22 | ethereum.Value.fromAddress(accountAddress), 23 | ), 24 | ); 25 | 26 | return newClaveAccountEvent; 27 | } 28 | -------------------------------------------------------------------------------- /subgraph/abis/VenusReward.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": true, 7 | "internalType": "address", 8 | "name": "vToken", 9 | "type": "address" 10 | }, 11 | { 12 | "indexed": true, 13 | "internalType": "address", 14 | "name": "supplier", 15 | "type": "address" 16 | }, 17 | { 18 | "indexed": false, 19 | "internalType": "uint256", 20 | "name": "rewardTokenDelta", 21 | "type": "uint256" 22 | }, 23 | { 24 | "indexed": false, 25 | "internalType": "uint256", 26 | "name": "rewardTokenTotal", 27 | "type": "uint256" 28 | }, 29 | { 30 | "indexed": false, 31 | "internalType": "uint256", 32 | "name": "rewardTokenSupplyIndex", 33 | "type": "uint256" 34 | } 35 | ], 36 | "name": "DistributedSupplierRewardToken", 37 | "type": "event" 38 | } 39 | ] 40 | -------------------------------------------------------------------------------- /test/utils/p256.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | import elliptic from 'elliptic'; 7 | 8 | export function genKey(): elliptic.ec.KeyPair { 9 | const ec = new elliptic.ec('p256'); 10 | return ec.genKeyPair(); 11 | } 12 | 13 | export function encodePublicKey(key: elliptic.ec.KeyPair): string { 14 | const pubKey = key.getPublic(); 15 | const x = pubKey.getX().toString('hex').padStart(64, '0'); 16 | const y = pubKey.getY().toString('hex').padStart(64, '0'); 17 | 18 | return '0x' + x + y; 19 | } 20 | 21 | export function sign(msg: string, key: elliptic.ec.KeyPair): string { 22 | const buffer = Buffer.from(msg.slice(2), 'hex'); 23 | const signature = key.sign(buffer); 24 | const r = signature.r.toString('hex').padStart(64, '0'); 25 | const s = signature.s.toString('hex').padStart(64, '0'); 26 | return '0x' + r + s; 27 | } 28 | -------------------------------------------------------------------------------- /subgraph/networks.json: -------------------------------------------------------------------------------- 1 | { 2 | "zksync-era": { 3 | "AccountFactory": { 4 | "address": "0x3B91C2eCEaAd96f5Ef3B08738B2aA202a4012a79", 5 | "startBlock": 24799912 6 | }, 7 | "AccountFactoryV2": { 8 | "address": "0x2B196aaB35184aa539E3D8360258CAF8d8309Ebc", 9 | "startBlock": 30099184 10 | }, 11 | "OdosRouter": { 12 | "address": "0x4bBa932E9792A2b917D47830C93a9BC79320E4f7", 13 | "startBlock": 24799912 14 | }, 15 | "ClaveImplementation": { 16 | "address": "0xf5bEDd0304ee359844541262aC349a6016A50bc6" 17 | }, 18 | "GaslessPaymaster": { 19 | "address": "0xa05B87198934eCB187157cBb98b25A0B79F33DEd" 20 | }, 21 | "ERC20Paymaster": { 22 | "address": "0x7a862CC27FAD666aB180F1708f8b18F7892FA761", 23 | "startBlock": 24799912 24 | }, 25 | "SocialRecovery": { 26 | "address": "0x9eF467CAA8291c6DAdD08EA458D763a8258B347e", 27 | "startBlock": 24799912 28 | }, 29 | "zeroUsdtPool": { 30 | "address": "0x4d9429246EA989C9CeE203B43F6d1C7D83e3B8F8" 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contracts/validators/EOAValidator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | import {IK1Validator, IERC165} from '../interfaces/IValidator.sol'; 5 | import {ECDSA} from '@openzeppelin/contracts/utils/cryptography/ECDSA.sol'; 6 | 7 | /** 8 | * @title secp256k1 ec keys' signature validator contract implementing its interface 9 | * @author https://getclave.io 10 | */ 11 | contract EOAValidator is IK1Validator { 12 | // ECDSA library to make verifications 13 | using ECDSA for bytes32; 14 | 15 | /// @inheritdoc IK1Validator 16 | function validateSignature( 17 | bytes32 signedHash, 18 | bytes calldata signature 19 | ) external pure override returns (address signer) { 20 | (signer, ) = signedHash.tryRecover(signature); 21 | } 22 | 23 | /// @inheritdoc IERC165 24 | function supportsInterface(bytes4 interfaceId) external pure override returns (bool) { 25 | return 26 | interfaceId == type(IK1Validator).interfaceId || 27 | interfaceId == type(IERC165).interfaceId; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/utils/managers/upgrademanager.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | import type { ec } from 'elliptic'; 7 | import type { Contract, Provider } from 'zksync-ethers'; 8 | import { utils } from 'zksync-ethers'; 9 | 10 | import { prepareTeeTx } from '../transactions'; 11 | 12 | export async function upgradeTx( 13 | provider: Provider, 14 | account: Contract, 15 | validator: Contract, 16 | newImplementation: Contract, 17 | keyPair: ec.KeyPair, 18 | ): Promise { 19 | const upgradeTx = await account.upgradeTo.populateTransaction( 20 | await newImplementation.getAddress(), 21 | ); 22 | const tx = await prepareTeeTx( 23 | provider, 24 | account, 25 | upgradeTx, 26 | await validator.getAddress(), 27 | keyPair, 28 | ); 29 | const txReceipt = await provider.broadcastTransaction( 30 | utils.serializeEip712(tx), 31 | ); 32 | await txReceipt.wait(); 33 | } 34 | -------------------------------------------------------------------------------- /contracts/interfaces/IEmailRecoverySubjectHandler.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | interface IEmailRecoverySubjectHandler { 5 | function acceptanceSubjectTemplates() external pure returns (string[][] memory); 6 | 7 | function recoverySubjectTemplates() external pure returns (string[][] memory); 8 | 9 | function extractRecoveredAccountFromAcceptanceSubject( 10 | bytes[] memory subjectParams, 11 | uint256 templateIdx 12 | ) external view returns (address); 13 | 14 | function extractRecoveredAccountFromRecoverySubject( 15 | bytes[] memory subjectParams, 16 | uint256 templateIdx 17 | ) external view returns (address); 18 | 19 | function validateAcceptanceSubject( 20 | uint256 templateIdx, 21 | bytes[] memory subjectParams 22 | ) external view returns (address); 23 | 24 | function validateRecoverySubject( 25 | uint256 templateIdx, 26 | bytes[] memory subjectParams, 27 | address recoveryManager 28 | ) external view returns (address, bytes32); 29 | } 30 | -------------------------------------------------------------------------------- /contracts/interfaces/IUpgradeManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | /** 5 | * @title Interface of the upgrade manager contract 6 | * @author https://getclave.io 7 | */ 8 | interface IUpgradeManager { 9 | /** 10 | * @notice Event emitted when the contract is upgraded 11 | * @param oldImplementation address - Address of the old implementation contract 12 | * @param newImplementation address - Address of the new implementation contract 13 | */ 14 | event Upgraded(address indexed oldImplementation, address indexed newImplementation); 15 | 16 | /** 17 | * @notice Upgrades the account contract to a new implementation 18 | * @dev Can only be called by self 19 | * @param newImplementation address - Address of the new implementation contract 20 | */ 21 | function upgradeTo(address newImplementation) external; 22 | 23 | /** 24 | * @notice Returns the current implementation address 25 | * @return address - Address of the current implementation contract 26 | */ 27 | function implementation() external view returns (address); 28 | } 29 | -------------------------------------------------------------------------------- /contracts/libraries/SignatureDecoder.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | import {Errors} from '../libraries/Errors.sol'; 5 | 6 | library SignatureDecoder { 7 | // Decode transaction.signature into signature, validator and hook data 8 | function decodeSignature( 9 | bytes calldata txSignature 10 | ) internal pure returns (bytes memory signature, address validator, bytes[] memory hookData) { 11 | (signature, validator, hookData) = abi.decode(txSignature, (bytes, address, bytes[])); 12 | } 13 | 14 | // Decode transaction.signature into hook data 15 | function decodeSignatureOnlyHookData( 16 | bytes calldata txSignature 17 | ) internal pure returns (bytes[] memory hookData) { 18 | (hookData) = abi.decode(txSignature, (bytes[])); 19 | } 20 | 21 | // Decode signature into signature and validator 22 | function decodeSignatureNoHookData( 23 | bytes memory signatureAndValidator 24 | ) internal pure returns (bytes memory signature, address validator) { 25 | (signature, validator) = abi.decode(signatureAndValidator, (bytes, address)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clave ZkSync Contracts 2 | 3 | Clave 4 | 5 | ## Project structure 6 | 7 | - `/contracts`: smart contracts. 8 | - `/deploy`: deployment and contract interaction scripts. 9 | - `/test`: test files 10 | - `hardhat.config.ts`: configuration file. 11 | 12 | ## Commands 13 | 14 | - `npx hardhat compile` will compile the contracts, typescript bindings are generated automatically. 15 | - `npx hardhat deploy {contract name} {constructor arguments}` will deploy and verify the contract. Requires [environment variable setup](#environment-variables). 16 | - `npm run test`: run tests. 17 | 18 | ### Environment variables 19 | 20 | In order to prevent users to leak private keys, this project includes the `dotenv` package which is used to load environment variables. It's used to load the wallet private key, required to run the deploy script. 21 | 22 | To use it, rename `.env.example` to `.env` and enter your private key. 23 | 24 | ``` 25 | PRIVATE_KEY=123cde574ccff.... 26 | ``` 27 | 28 | ## Official Links 29 | 30 | - [Website](https://getclave.io/) 31 | - [GitHub](https://github.com/getclave) 32 | - [Twitter](https://twitter.com/getclave) 33 | -------------------------------------------------------------------------------- /test/utils/paymasters.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | import type { ethers } from 'ethers'; 7 | import { utils } from 'zksync-ethers'; 8 | import type { Contract, types } from 'zksync-ethers'; 9 | 10 | export function getGaslessPaymasterInput( 11 | paymasterAddress: types.Address, 12 | ): types.PaymasterParams { 13 | return utils.getPaymasterParams(paymasterAddress, { 14 | type: 'General', 15 | innerInput: new Uint8Array(), 16 | }); 17 | } 18 | 19 | export function getERC20PaymasterInput( 20 | paymasterAddress: types.Address, 21 | tokenAddress: types.Address, 22 | minimalAllowance: bigint, 23 | oraclePayload: ethers.BytesLike, 24 | ): types.PaymasterParams { 25 | return utils.getPaymasterParams(paymasterAddress, { 26 | type: 'ApprovalBased', 27 | token: tokenAddress, 28 | minimalAllowance, 29 | innerInput: oraclePayload, 30 | }); 31 | } 32 | 33 | export async function getOraclePayload( 34 | paymasterContract: Contract, 35 | ): Promise { 36 | paymasterContract; 37 | return '0x'; 38 | } 39 | -------------------------------------------------------------------------------- /test/utils/passkey.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | import { sha256 } from 'ethers'; 7 | 8 | import { bufferFromBase64url, bufferFromString } from './buffer'; 9 | 10 | // hash of 'https://getclave.io' + (BE, BS, UP, UV) flags set + unincremented sign counter 11 | const authData = bufferFromBase64url( 12 | 'F1-vhQTCzdfAF3iosO_Uh07LOu_X67cHmpQfe-iJfUEdAAAAAA', 13 | ); 14 | const clientDataPrefix = bufferFromString( 15 | '{"type":"webauthn.get","challenge":"', 16 | ); 17 | const clientDataSuffix = bufferFromString('","origin":"https://getclave.io"}'); 18 | 19 | export function getSignedData(challenge: string): string { 20 | const challengeBuffer = Buffer.from(challenge.slice(2), 'hex'); 21 | const challengeBase64 = challengeBuffer.toString('base64url'); 22 | const clientData = Buffer.concat([ 23 | clientDataPrefix, 24 | bufferFromString(challengeBase64), 25 | clientDataSuffix, 26 | ]); 27 | 28 | const clientDataHash = Buffer.from(sha256(clientData).slice(2), 'hex'); 29 | 30 | return sha256(Buffer.concat([authData, clientDataHash])); 31 | } 32 | -------------------------------------------------------------------------------- /test/utils/buffer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | export function bufferFromBase64url(base64url: string): Buffer { 7 | return Buffer.from(toBase64(base64url), 'base64'); 8 | } 9 | 10 | export function bufferFromString(string: string): Buffer { 11 | return Buffer.from(string, 'utf8'); 12 | } 13 | 14 | function toBase64(base64url: string | Buffer): string { 15 | base64url = base64url.toString(); 16 | return padString(base64url).replace(/\-/g, '+').replace(/_/g, '/'); 17 | } 18 | 19 | function padString(input: string): string { 20 | const segmentLength = 4; 21 | const stringLength = input.length; 22 | const diff = stringLength % segmentLength; 23 | 24 | if (!diff) { 25 | return input; 26 | } 27 | 28 | let position = stringLength; 29 | let padLength = segmentLength - diff; 30 | const paddedStringLength = stringLength + padLength; 31 | const buffer = Buffer.alloc(paddedStringLength); 32 | 33 | buffer.write(input); 34 | 35 | while (padLength--) { 36 | buffer.write('=', position++); 37 | } 38 | 39 | return buffer.toString(); 40 | } 41 | -------------------------------------------------------------------------------- /balances-subgraph/src/account-factory.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/consistent-type-imports */ 2 | 3 | /** 4 | * Copyright Clave - All Rights Reserved 5 | * Unauthorized copying of this file, via any medium is strictly prohibited 6 | * Proprietary and confidential 7 | */ 8 | import { Bytes, ethereum, json } from '@graphprotocol/graph-ts'; 9 | 10 | // eslint-disable-next-line @typescript-eslint/consistent-type-imports 11 | import { ClaveAccountCreated as ClaveAccountCreatedEvent } from '../generated/AccountFactory/AccountFactory'; 12 | import { ClaveAccount } from '../generated/schema'; 13 | import { wallets } from '../wallets'; 14 | 15 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 16 | export function handleOnce(_block: ethereum.Block): void { 17 | const walletsJson = json.fromString(wallets).toArray(); 18 | walletsJson.forEach((element) => { 19 | const accountAddress = element.toObject().entries[0].value.toString(); 20 | const account = new ClaveAccount(Bytes.fromHexString(accountAddress)); 21 | 22 | account.save(); 23 | }); 24 | } 25 | 26 | export function handleClaveAccountCreated( 27 | event: ClaveAccountCreatedEvent, 28 | ): void { 29 | const account = new ClaveAccount(event.params.accountAddress); 30 | account.save(); 31 | } 32 | -------------------------------------------------------------------------------- /contracts/referral/SwapReferralFeePayer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | import {IERC20} from '@openzeppelin/contracts/token/ERC20/ERC20.sol'; 5 | import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; 6 | 7 | contract SwapReferralFeePayer { 8 | using SafeERC20 for IERC20; 9 | 10 | event ReferralFee(address indexed referrer, address indexed token, uint256 fee); 11 | event Cashback(address indexed referred, address indexed token, uint256 fee); 12 | 13 | function payFee( 14 | address referrer, 15 | address token, 16 | uint256 fee, 17 | uint256 cashback 18 | ) external payable { 19 | if (referrer != address(0)) { 20 | if (token == address(0)) { 21 | (bool success, ) = payable(referrer).call{value: fee}(''); 22 | require(success, 'ReferralFeePayer: failed to pay referral fee'); 23 | } else { 24 | IERC20 erc20 = IERC20(token); 25 | erc20.safeTransferFrom(msg.sender, referrer, fee); 26 | } 27 | 28 | emit ReferralFee(referrer, token, fee); 29 | } 30 | 31 | if (cashback != 0) { 32 | emit Cashback(msg.sender, token, cashback); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /contracts/validators/TEEValidator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | import {IR1Validator, IERC165} from '../interfaces/IValidator.sol'; 5 | import {Errors} from '../libraries/Errors.sol'; 6 | import {VerifierCaller} from '../helpers/VerifierCaller.sol'; 7 | 8 | /** 9 | * @title secp256r1 ec keys' signature validator contract implementing its interface 10 | * @author https://getclave.io 11 | */ 12 | contract TEEValidator is IR1Validator, VerifierCaller { 13 | // RIP-7212 enabled 14 | address constant P256_VERIFIER = 0x0000000000000000000000000000000000000100; 15 | 16 | /// @inheritdoc IR1Validator 17 | function validateSignature( 18 | bytes32 signedHash, 19 | bytes calldata signature, 20 | bytes32[2] calldata pubKey 21 | ) external view override returns (bool valid) { 22 | bytes32[2] memory rs = abi.decode(signature, (bytes32[2])); 23 | 24 | valid = callVerifier(P256_VERIFIER, sha256(abi.encodePacked(signedHash)), rs, pubKey); 25 | } 26 | 27 | /// @inheritdoc IERC165 28 | function supportsInterface(bytes4 interfaceId) external pure override returns (bool) { 29 | return 30 | interfaceId == type(IR1Validator).interfaceId || 31 | interfaceId == type(IERC165).interfaceId; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /deploy/interact.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | import * as hre from 'hardhat'; 7 | 8 | import { getWallet } from './utils'; 9 | 10 | // Address of the contract to interact with 11 | const CONTRACT_ADDRESS = '0x...'; 12 | // Name of the contract to interact with 13 | const CONTRACT_NAME = 'Example'; 14 | 15 | // An example of a script to interact with the contract 16 | // Do not push modifications to this file 17 | // Just modify, interact then revert changes 18 | export default async function (): Promise { 19 | console.log(`Running script to interact with contract ${CONTRACT_ADDRESS}`); 20 | 21 | const contract = await hre.ethers.getContractAt( 22 | CONTRACT_NAME, 23 | CONTRACT_ADDRESS, 24 | getWallet(hre), 25 | ); 26 | 27 | // Run contract read function 28 | const response = await contract.greet(); 29 | console.log(`Current message is: ${response}`); 30 | 31 | // Run contract write function 32 | const transaction = await contract.setGreeting('Hello people!'); 33 | console.log(`Transaction hash of setting new message: ${transaction.hash}`); 34 | 35 | // Wait until transaction is processed 36 | await transaction.wait(); 37 | } 38 | -------------------------------------------------------------------------------- /contracts/test/TestOracle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | import {PrimaryProdDataServiceConsumerBase} from '@redstone-finance/evm-connector/contracts/data-services/PrimaryProdDataServiceConsumerBase.sol'; 5 | 6 | /** 7 | * @title TestOracle - Test contract to simulate an oracle 8 | */ 9 | contract TestOracle is PrimaryProdDataServiceConsumerBase { 10 | // The nominator used for price calculation 11 | uint256 constant PRICE_PAIR_NOMINATOR = 1e18; 12 | uint256 public rateCheck; 13 | 14 | /** 15 | * @notice This function gets the ETH/PARAM_TOKEN price from the oracle 16 | */ 17 | function getPairPrice(bytes memory oracleCalldata) external { 18 | bytes32 dataFeedIds = bytes32('ETH'); 19 | rateCheck = callOracle(dataFeedIds, oracleCalldata); 20 | } 21 | 22 | /** 23 | * @notice This function calls the oracle and returns the values 24 | * @param dataFeedIds bytes32[] - Array of oracle ids 25 | * @param - bytes - Oracle payload 26 | * @return uint256[] - Oracle return as token prices 27 | */ 28 | function callOracle( 29 | bytes32 dataFeedIds, 30 | bytes memory //* oracleCalldata */ 31 | ) private view returns (uint256) { 32 | return getOracleNumericValueFromTxMsg(dataFeedIds); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /contracts/interfaces/IClave.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | import {IAccount} from '@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IAccount.sol'; 5 | 6 | import {IERC1271Upgradeable} from '@openzeppelin/contracts-upgradeable/interfaces/IERC1271Upgradeable.sol'; 7 | import {IERC777Recipient} from '@openzeppelin/contracts/token/ERC777/IERC777Recipient.sol'; 8 | import {IERC721Receiver} from '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol'; 9 | import {IERC1155Receiver} from '@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol'; 10 | 11 | import {IHookManager} from './IHookManager.sol'; 12 | import {IModuleManager} from './IModuleManager.sol'; 13 | import {IOwnerManager} from './IOwnerManager.sol'; 14 | import {IUpgradeManager} from './IUpgradeManager.sol'; 15 | import {IValidatorManager} from './IValidatorManager.sol'; 16 | 17 | /** 18 | * @title IClave 19 | * @notice Interface for the Clave contract 20 | * @dev Implementations of this interface are contract that can be used as a Clave 21 | */ 22 | interface IClaveAccount is 23 | IERC1271Upgradeable, 24 | IERC721Receiver, 25 | IERC1155Receiver, 26 | IHookManager, 27 | IModuleManager, 28 | IOwnerManager, 29 | IValidatorManager, 30 | IUpgradeManager, 31 | IAccount 32 | { 33 | event FeePaid(); 34 | } 35 | -------------------------------------------------------------------------------- /subgraph/tests/new-account.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | import { Address } from '@graphprotocol/graph-ts'; 7 | import { 8 | afterAll, 9 | assert, 10 | beforeAll, 11 | clearStore, 12 | describe, 13 | test, 14 | } from 'matchstick-as/assembly/index'; 15 | 16 | import { handleNewClaveAccount } from '../src/account-factory'; 17 | import { createNewClaveAccountEvent } from './new-account-utils'; 18 | 19 | describe('handleNewClaveAccount()', () => { 20 | beforeAll(() => { 21 | const accountAddress = Address.fromString( 22 | '0x0000000000000000000000000000000000000001', 23 | ); 24 | const newClaveAccountEvent = createNewClaveAccountEvent(accountAddress); 25 | handleNewClaveAccount(newClaveAccountEvent); 26 | }); 27 | 28 | afterAll(() => { 29 | clearStore(); 30 | }); 31 | 32 | test('ClaveAccount created and stored', () => { 33 | assert.entityCount('ClaveAccount', 1); 34 | assert.fieldEquals( 35 | 'ClaveAccount', 36 | '0x0000000000000000000000000000000000000001', 37 | 'id', 38 | '0x0000000000000000000000000000000000000001', 39 | ); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /tasks/deploy.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | import { task } from 'hardhat/config'; 7 | 8 | import { deployContract } from '../deploy/utils'; 9 | 10 | task('deploy', 'Task for deploying contracts') 11 | .addFlag( 12 | 'silent', 13 | 'If true, the deployment process will not print any logs', 14 | ) 15 | .addFlag( 16 | 'noVerify', 17 | 'If true, the contract will not be verified on Block Explorer', 18 | ) 19 | .addPositionalParam( 20 | 'contractArtifactName', 21 | 'The name of the contract artifact to deploy', 22 | ) 23 | .addOptionalVariadicPositionalParam( 24 | 'constructorArguments', 25 | 'The arguments for the contract constructor', 26 | ) 27 | .setAction(async (taskArgs, hre) => { 28 | const contractArtifactName = taskArgs.contractArtifactName; 29 | const constructorArguments = taskArgs.constructorArguments; 30 | const options = { 31 | silent: taskArgs.silent, 32 | noVerify: taskArgs.noVerify, 33 | }; 34 | 35 | await deployContract( 36 | hre, 37 | contractArtifactName, 38 | constructorArguments, 39 | options, 40 | ); 41 | }); 42 | -------------------------------------------------------------------------------- /test/deployments/rip7212.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | import { expect } from 'chai'; 7 | import * as hre from 'hardhat'; 8 | import { Provider } from 'zksync-ethers'; 9 | 10 | describe('RIP-7212 tests', () => { 11 | let provider: Provider; 12 | 13 | before(async () => { 14 | provider = new Provider(hre.network.config.url, undefined, { 15 | cacheTimeout: -1, 16 | }); 17 | }); 18 | 19 | it('should enable rip-7212', async () => { 20 | const precompileAddress = '0x0000000000000000000000000000000000000100'; 21 | const data = 22 | '0x4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e'; 23 | 24 | const result = await provider.call({ 25 | to: precompileAddress, 26 | data: data, 27 | value: 0, 28 | }); 29 | 30 | expect(result).to.be.eq( 31 | '0x0000000000000000000000000000000000000000000000000000000000000001', 32 | ); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /contracts/test/MockModule.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | import {IERC165} from '@openzeppelin/contracts/utils/introspection/IERC165.sol'; 5 | 6 | import {IModule} from '../interfaces/IModule.sol'; 7 | 8 | interface IClave { 9 | function executeFromModule(address to, uint256 value, bytes memory data) external; 10 | 11 | function k1AddOwner(address addr) external; 12 | } 13 | 14 | contract MockModule is IModule { 15 | mapping(address => uint256) public values; 16 | 17 | function init(bytes calldata initData) external override { 18 | values[msg.sender] = abi.decode(initData, (uint256)); 19 | } 20 | 21 | function disable() external override { 22 | delete values[msg.sender]; 23 | } 24 | 25 | function testExecuteFromModule(address account, address to) external { 26 | uint256 value = values[account]; 27 | IClave(account).executeFromModule(to, value, ''); 28 | } 29 | 30 | function testOnlySelfOrModule(address account) external { 31 | IClave(account).k1AddOwner(address(this)); 32 | } 33 | 34 | function isInited(address account) external view returns (bool) { 35 | return true; 36 | } 37 | 38 | /// @inheritdoc IERC165 39 | function supportsInterface(bytes4 interfaceId) external pure override returns (bool) { 40 | return interfaceId == type(IModule).interfaceId || interfaceId == type(IERC165).interfaceId; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /balances-subgraph/subgraph.yaml: -------------------------------------------------------------------------------- 1 | specVersion: 1.0.0 2 | indexerHints: 3 | prune: auto 4 | schema: 5 | file: ./schema.graphql 6 | dataSources: 7 | - kind: ethereum 8 | name: erc20 9 | network: zksync-era 10 | source: 11 | abi: ERC20 12 | startBlock: 24799912 13 | mapping: 14 | kind: ethereum/events 15 | apiVersion: 0.0.7 16 | language: wasm/assemblyscript 17 | entities: 18 | - ERC20 19 | - ERC20Balance 20 | abis: 21 | - name: ERC20 22 | file: ./abis/ERC20.json 23 | eventHandlers: 24 | - event: Transfer(indexed address,indexed address,uint256) 25 | handler: handleTransfer 26 | file: ./src/erc20.ts 27 | - kind: ethereum 28 | name: AccountFactory 29 | network: zksync-era 30 | source: 31 | address: '0x2B196aaB35184aa539E3D8360258CAF8d8309Ebc' 32 | abi: AccountFactory 33 | startBlock: 24799912 34 | mapping: 35 | kind: ethereum/events 36 | apiVersion: 0.0.7 37 | language: wasm/assemblyscript 38 | entities: 39 | - ClaveAccount 40 | abis: 41 | - name: AccountFactory 42 | file: ./abis/AccountFactory.json 43 | eventHandlers: 44 | - event: ClaveAccountCreated(indexed address) 45 | handler: handleClaveAccountCreated 46 | blockHandlers: 47 | - handler: handleOnce 48 | filter: 49 | kind: once 50 | file: ./src/account-factory.ts 51 | -------------------------------------------------------------------------------- /test/utils/fixture.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | import type { ec } from 'elliptic'; 7 | import type { Contract } from 'zksync-ethers'; 8 | 9 | import type { ClaveDeployer } from './deployer'; 10 | import { VALIDATORS } from './names'; 11 | import { genKey } from './p256'; 12 | 13 | export type fixtureTypes = [ 14 | batchCaller: Contract, 15 | registry: Contract, 16 | implementation: Contract, 17 | factory: Contract, 18 | validator: Contract, 19 | account: Contract, 20 | keyPair: ec.KeyPair, 21 | ]; 22 | 23 | export const fixture = async ( 24 | deployer: ClaveDeployer, 25 | validatorOption: VALIDATORS = VALIDATORS.MOCK, 26 | ): Promise => { 27 | const keyPair = genKey(); 28 | 29 | const batchCaller = await deployer.batchCaller(); 30 | const registry = await deployer.registry(); 31 | const implementation = await deployer.implementation(batchCaller); 32 | const factory = await deployer.factory(implementation, registry); 33 | const validator = await deployer.validator(validatorOption); 34 | const account = await deployer.account(keyPair, factory, validator); 35 | 36 | return [ 37 | batchCaller, 38 | registry, 39 | implementation, 40 | factory, 41 | validator, 42 | account, 43 | keyPair, 44 | ]; 45 | }; 46 | -------------------------------------------------------------------------------- /subgraph/src/referral.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/consistent-type-imports */ 2 | 3 | /** 4 | * Copyright Clave - All Rights Reserved 5 | * Unauthorized copying of this file, via any medium is strictly prohibited 6 | * Proprietary and confidential 7 | */ 8 | import { 9 | Cashback as CashbackEvent, 10 | ReferralFee as ReferralFeeEvent, 11 | } from '../generated/SwapReferralFeePayer/SwapReferralFeePayer'; 12 | import { ClaveAccount } from '../generated/schema'; 13 | import { getOrCreateCashback, getOrCreateReferralFee } from './helpers'; 14 | 15 | export function handleCashback(event: CashbackEvent): void { 16 | const account = ClaveAccount.load(event.params.referred); 17 | if (!account) { 18 | return; 19 | } 20 | 21 | const cashback = getOrCreateCashback(account, event.params.token); 22 | cashback.amount = cashback.amount.plus(event.params.fee); 23 | cashback.save(); 24 | } 25 | 26 | export function handleReferralFee(event: ReferralFeeEvent): void { 27 | const referrer = ClaveAccount.load(event.params.referrer); 28 | if (!referrer) { 29 | return; 30 | } 31 | 32 | const referred = ClaveAccount.load(event.transaction.from); 33 | if (!referred) { 34 | return; 35 | } 36 | 37 | const referralFee = getOrCreateReferralFee( 38 | referrer, 39 | referred, 40 | event.params.token, 41 | ); 42 | referralFee.amount = referralFee.amount.plus(event.params.fee); 43 | referralFee.save(); 44 | } 45 | -------------------------------------------------------------------------------- /contracts/ClaveProxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | import {EfficientCall} from '@matterlabs/zksync-contracts/l2/system-contracts/libraries/EfficientCall.sol'; 5 | 6 | contract ClaveProxy { 7 | //keccak-256 of "eip1967.proxy.implementation" subtracted by 1 8 | bytes32 private constant _IMPLEMENTATION_SLOT = 9 | 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; 10 | 11 | /** 12 | * @notice Sets the initial implementation contract. 13 | * @param implementation address - Address of the implementation contract. 14 | */ 15 | constructor(address implementation) { 16 | assembly { 17 | sstore(_IMPLEMENTATION_SLOT, implementation) 18 | } 19 | } 20 | 21 | /** 22 | * @dev Fallback function that delegates the call to the implementation contract. 23 | */ 24 | fallback() external payable { 25 | address impl; 26 | assembly { 27 | impl := and(sload(_IMPLEMENTATION_SLOT), 0xffffffffffffffffffffffffffffffffffffffff) 28 | } 29 | 30 | bool success = EfficientCall.rawDelegateCall(gasleft(), impl, msg.data); 31 | 32 | assembly { 33 | returndatacopy(0, 0, returndatasize()) 34 | switch success 35 | case 0 { 36 | revert(0, returndatasize()) 37 | } 38 | default { 39 | return(0, returndatasize()) 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /contracts/test/TEEValidatorTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | import {IR1Validator, IERC165} from '../interfaces/IValidator.sol'; 5 | import {Errors} from '../libraries/Errors.sol'; 6 | import {VerifierCaller} from '../helpers/VerifierCaller.sol'; 7 | 8 | /** 9 | * @title secp256r1 ec keys' signature validator contract implementing its interface 10 | * @author https://getclave.io 11 | */ 12 | contract TEEValidatorTest is IR1Validator, VerifierCaller { 13 | //dummy value 14 | address immutable P256_VERIFIER; 15 | 16 | /** 17 | * @notice Constructor function of the validator 18 | * @param p256VerifierAddress address - Address of the p256 verifier contract 19 | */ 20 | constructor(address p256VerifierAddress) { 21 | P256_VERIFIER = p256VerifierAddress; 22 | } 23 | 24 | /// @inheritdoc IR1Validator 25 | function validateSignature( 26 | bytes32 signedHash, 27 | bytes calldata signature, 28 | bytes32[2] calldata pubKey 29 | ) external view override returns (bool valid) { 30 | bytes32[2] memory rs = abi.decode(signature, (bytes32[2])); 31 | 32 | valid = callVerifier(P256_VERIFIER, signedHash, rs, pubKey); 33 | } 34 | 35 | /// @inheritdoc IERC165 36 | function supportsInterface(bytes4 interfaceId) external pure override returns (bool) { 37 | return 38 | interfaceId == type(IR1Validator).interfaceId || 39 | interfaceId == type(IERC165).interfaceId; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /subgraph/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | graph-node: 4 | image: graphprotocol/graph-node 5 | ports: 6 | - "8000:8000" 7 | - "8001:8001" 8 | - "8020:8020" 9 | - "8030:8030" 10 | - "8040:8040" 11 | depends_on: 12 | - ipfs 13 | - postgres 14 | extra_hosts: 15 | - host.docker.internal:host-gateway 16 | environment: 17 | postgres_host: postgres 18 | postgres_user: graph-node 19 | postgres_pass: let-me-in 20 | postgres_db: graph-node 21 | ipfs: "ipfs:5001" 22 | ethereum: "mainnet:http://host.docker.internal:8545" 23 | GRAPH_LOG: info 24 | ipfs: 25 | image: ipfs/kubo:v0.17.0 26 | ports: 27 | - "5001:5001" 28 | volumes: 29 | - ./data/ipfs:/data/ipfs 30 | postgres: 31 | image: postgres:14 32 | ports: 33 | - "5432:5432" 34 | command: 35 | [ 36 | "postgres", 37 | "-cshared_preload_libraries=pg_stat_statements", 38 | "-cmax_connections=200", 39 | ] 40 | environment: 41 | POSTGRES_USER: graph-node 42 | POSTGRES_PASSWORD: let-me-in 43 | POSTGRES_DB: graph-node 44 | # FIXME: remove this env. var. which we shouldn't need. Introduced by 45 | # , maybe as a 46 | # workaround for https://github.com/docker/for-mac/issues/6270? 47 | PGDATA: "/var/lib/postgresql/data" 48 | POSTGRES_INITDB_ARGS: "-E UTF8 --locale=C" 49 | volumes: 50 | - ./data/postgres:/var/lib/postgresql/data 51 | -------------------------------------------------------------------------------- /contracts/interfaces/IValidator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | import {IERC165} from '@openzeppelin/contracts/utils/introspection/IERC165.sol'; 5 | 6 | /** 7 | * @title secp256r1 ec keys' signature validator interface 8 | * @author https://getclave.io 9 | */ 10 | interface IR1Validator is IERC165 { 11 | /** 12 | * @notice Allows to validate secp256r1 ec signatures 13 | * @param signedHash bytes32 - hash of the data that is signed by the key 14 | * @param signature bytes - signature 15 | * @param pubKey bytes32[2] - public key coordinates array for the x and y values 16 | * @return valid bool - validation result 17 | */ 18 | function validateSignature( 19 | bytes32 signedHash, 20 | bytes calldata signature, 21 | bytes32[2] calldata pubKey 22 | ) external view returns (bool valid); 23 | } 24 | 25 | /** 26 | * @title secp256k1 ec keys' signature validator interface 27 | * @author https://getclave.io 28 | */ 29 | interface IK1Validator is IERC165 { 30 | /** 31 | * @notice Allows to validate secp256k1 ec signatures 32 | * @param signedHash bytes32 - hash of the transaction signed by the key 33 | * @param signature bytes - signature 34 | * @return signer address - recovered signer address 35 | */ 36 | function validateSignature( 37 | bytes32 signedHash, 38 | bytes calldata signature 39 | ) external view returns (address signer); 40 | } 41 | -------------------------------------------------------------------------------- /subgraph/abis/VenusToken.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": true, 7 | "internalType": "address", 8 | "name": "minter", 9 | "type": "address" 10 | }, 11 | { 12 | "indexed": false, 13 | "internalType": "uint256", 14 | "name": "mintAmount", 15 | "type": "uint256" 16 | }, 17 | { 18 | "indexed": false, 19 | "internalType": "uint256", 20 | "name": "mintTokens", 21 | "type": "uint256" 22 | }, 23 | { 24 | "indexed": false, 25 | "internalType": "uint256", 26 | "name": "accountBalance", 27 | "type": "uint256" 28 | } 29 | ], 30 | "name": "Mint", 31 | "type": "event" 32 | }, 33 | { 34 | "anonymous": false, 35 | "inputs": [ 36 | { 37 | "indexed": true, 38 | "internalType": "address", 39 | "name": "redeemer", 40 | "type": "address" 41 | }, 42 | { 43 | "indexed": false, 44 | "internalType": "uint256", 45 | "name": "redeemAmount", 46 | "type": "uint256" 47 | }, 48 | { 49 | "indexed": false, 50 | "internalType": "uint256", 51 | "name": "redeemTokens", 52 | "type": "uint256" 53 | }, 54 | { 55 | "indexed": false, 56 | "internalType": "uint256", 57 | "name": "accountBalance", 58 | "type": "uint256" 59 | } 60 | ], 61 | "name": "Redeem", 62 | "type": "event" 63 | } 64 | ] 65 | -------------------------------------------------------------------------------- /balances-subgraph/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | graph-node: 4 | image: graphprotocol/graph-node 5 | ports: 6 | - "8000:8000" 7 | - "8001:8001" 8 | - "8020:8020" 9 | - "8030:8030" 10 | - "8040:8040" 11 | depends_on: 12 | - ipfs 13 | - postgres 14 | extra_hosts: 15 | - host.docker.internal:host-gateway 16 | environment: 17 | postgres_host: postgres 18 | postgres_user: graph-node 19 | postgres_pass: let-me-in 20 | postgres_db: graph-node 21 | ipfs: "ipfs:5001" 22 | ethereum: "mainnet:http://host.docker.internal:8545" 23 | GRAPH_LOG: info 24 | ipfs: 25 | image: ipfs/kubo:v0.17.0 26 | ports: 27 | - "5001:5001" 28 | volumes: 29 | - ./data/ipfs:/data/ipfs 30 | postgres: 31 | image: postgres:14 32 | ports: 33 | - "5432:5432" 34 | command: 35 | [ 36 | "postgres", 37 | "-cshared_preload_libraries=pg_stat_statements", 38 | "-cmax_connections=200", 39 | ] 40 | environment: 41 | POSTGRES_USER: graph-node 42 | POSTGRES_PASSWORD: let-me-in 43 | POSTGRES_DB: graph-node 44 | # FIXME: remove this env. var. which we shouldn't need. Introduced by 45 | # , maybe as a 46 | # workaround for https://github.com/docker/for-mac/issues/6270? 47 | PGDATA: "/var/lib/postgresql/data" 48 | POSTGRES_INITDB_ARGS: "-E UTF8 --locale=C" 49 | volumes: 50 | - ./data/postgres:/var/lib/postgresql/data 51 | -------------------------------------------------------------------------------- /balances-subgraph/src/erc20.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | // eslint-disable-next-line @typescript-eslint/consistent-type-imports 7 | import { Transfer as TransferEvent } from '../generated/erc20/ERC20'; 8 | import { ClaveAccount, ERC20 } from '../generated/schema'; 9 | import { 10 | ZERO, 11 | decreaseAccountBalance, 12 | increaseAccountBalance, 13 | } from './helpers'; 14 | 15 | export function handleTransfer(event: TransferEvent): void { 16 | const from = event.params.from; 17 | const to = event.params.to; 18 | 19 | const fromAccount = ClaveAccount.load(from); 20 | const toAccount = ClaveAccount.load(to); 21 | 22 | //not a Clave related transfer 23 | if (fromAccount === null && toAccount === null) { 24 | return; 25 | } 26 | 27 | const tokenAddress = event.address; 28 | let token = ERC20.load(tokenAddress); 29 | if (!token) { 30 | token = new ERC20(tokenAddress); 31 | token.totalAmount = ZERO; 32 | } 33 | 34 | const amount = event.params.value; 35 | if (fromAccount !== null) { 36 | const tokenBalanceFrom = decreaseAccountBalance( 37 | fromAccount, 38 | token, 39 | amount, 40 | ); 41 | tokenBalanceFrom.save(); 42 | } 43 | if (toAccount !== null) { 44 | const tokenBalanceTo = increaseAccountBalance(toAccount, token, amount); 45 | tokenBalanceTo.save(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /contracts/managers/UpgradeManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | import {Errors} from '../libraries/Errors.sol'; 5 | import {Auth} from '../auth/Auth.sol'; 6 | 7 | import {IUpgradeManager} from '../interfaces/IUpgradeManager.sol'; 8 | 9 | /** 10 | * @title Upgrade Manager 11 | * @notice Abstract contract for managing the upgrade process of the account 12 | * @author https://getclave.io 13 | */ 14 | abstract contract UpgradeManager is IUpgradeManager, Auth { 15 | // keccak-256 of "eip1967.proxy.implementation" subtracted by 1 16 | bytes32 private constant _IMPLEMENTATION_SLOT = 17 | 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; 18 | 19 | /// @inheritdoc IUpgradeManager 20 | function upgradeTo(address newImplementation) external override onlySelf { 21 | address oldImplementation; 22 | assembly { 23 | oldImplementation := and( 24 | sload(_IMPLEMENTATION_SLOT), 25 | 0xffffffffffffffffffffffffffffffffffffffff 26 | ) 27 | } 28 | if (oldImplementation == newImplementation) { 29 | revert Errors.SAME_IMPLEMENTATION(); 30 | } 31 | assembly { 32 | sstore(_IMPLEMENTATION_SLOT, newImplementation) 33 | } 34 | 35 | emit Upgraded(oldImplementation, newImplementation); 36 | } 37 | 38 | /// @inheritdoc IUpgradeManager 39 | function implementation() external view override returns (address) { 40 | address impl; 41 | assembly { 42 | impl := and(sload(_IMPLEMENTATION_SLOT), 0xffffffffffffffffffffffffffffffffffffffff) 43 | } 44 | 45 | return impl; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /contracts/test/MockImplementation.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | import {MockStorage} from './MockStorage.sol'; 5 | import {AddressLinkedList} from '../libraries/LinkedList.sol'; 6 | 7 | contract MockImplementation { 8 | using AddressLinkedList for mapping(address => address); 9 | 10 | bytes32 private constant _IMPLEMENTATION_SLOT = 11 | 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; 12 | 13 | function setTestNumber(uint256 number) external { 14 | MockStorage.layout().testNumber = number; 15 | } 16 | 17 | function getTestNumber() external view returns (uint256) { 18 | return MockStorage.layout().testNumber; 19 | } 20 | 21 | function r1IsValidator(address validator) external view returns (bool) { 22 | return _r1IsValidator(validator); 23 | } 24 | 25 | function r1ListValidators() external view returns (address[] memory validatorList) { 26 | validatorList = _r1ValidatorsLinkedList().list(); 27 | } 28 | 29 | function implementation() external view returns (address) { 30 | address impl; 31 | assembly { 32 | impl := and(sload(_IMPLEMENTATION_SLOT), 0xffffffffffffffffffffffffffffffffffffffff) 33 | } 34 | 35 | return impl; 36 | } 37 | 38 | function _r1IsValidator(address validator) internal view returns (bool) { 39 | return _r1ValidatorsLinkedList().exists(validator); 40 | } 41 | 42 | function _r1ValidatorsLinkedList() 43 | private 44 | view 45 | returns (mapping(address => address) storage r1Validators) 46 | { 47 | r1Validators = MockStorage.layout().r1Validators; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clave-contracts", 3 | "version": "1.0.0", 4 | "author": "getclave.io", 5 | "dependencies": { 6 | "@matterlabs/zksync-contracts": "0.6.1", 7 | "@nomad-xyz/excessively-safe-call": "github:nomad-xyz/ExcessivelySafeCall", 8 | "@openzeppelin/contracts": "4.6.0", 9 | "@openzeppelin/contracts-upgradeable": "4.6.0", 10 | "ts-morph": "^19.0.0" 11 | }, 12 | "devDependencies": { 13 | "@matterlabs/hardhat-zksync": "1.1.0", 14 | "@nomicfoundation/hardhat-chai-matchers": "2.0.7", 15 | "@nomicfoundation/hardhat-ethers": "3.0.6", 16 | "@nomicfoundation/hardhat-verify": "2.0.9", 17 | "@redstone-finance/evm-connector": "0.6.1", 18 | "@typechain/ethers-v6": "0.5.1", 19 | "@typechain/hardhat": "9.1.0", 20 | "@types/chai": "4.3.16", 21 | "@types/elliptic": "6.4.18", 22 | "@types/mocha": "10.0.7", 23 | "chai": "4.5.0", 24 | "dotenv": "16.4.5", 25 | "elliptic": "6.5.4", 26 | "ethers": "6.13.2", 27 | "hardhat": "2.22.7", 28 | "mocha": "10.7.0", 29 | "prettier-plugin-solidity": "1.3.1", 30 | "read-last-lines": "1.8.0", 31 | "ts-node": "10.9.2", 32 | "typechain": "8.3.2", 33 | "typescript": "5.3.3", 34 | "zksync-ethers": "6.11.1" 35 | }, 36 | "license": "MIT", 37 | "main": "index.js", 38 | "scripts": { 39 | "compile": "npx hardhat compile", 40 | "deploy:mvp": "npx hardhat deploy-zksync --script deploy-mvp.ts", 41 | "lint": "npx prettier --write --plugin=prettier-plugin-solidity 'contracts/**/*.sol'", 42 | "lint-check": "npx prettier --list-different --plugin=prettier-plugin-solidity 'contracts/**/*.sol'", 43 | "test-real": "TEST=true npx hardhat test --network hardhat", 44 | "test": "exit 0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /contracts/helpers/TokenCallbackHandler.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | import '@openzeppelin/contracts/utils/introspection/IERC165.sol'; 4 | import '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol'; 5 | import '@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol'; 6 | 7 | /** 8 | * Token callback handler. 9 | * Handles supported tokens' callbacks, allowing account receiving these tokens. 10 | */ 11 | contract TokenCallbackHandler is IERC721Receiver, IERC1155Receiver { 12 | function onERC721Received( 13 | address, 14 | address, 15 | uint256, 16 | bytes calldata 17 | ) external pure override returns (bytes4) { 18 | return IERC721Receiver.onERC721Received.selector; 19 | } 20 | 21 | function onERC1155Received( 22 | address, 23 | address, 24 | uint256, 25 | uint256, 26 | bytes calldata 27 | ) external pure override returns (bytes4) { 28 | return IERC1155Receiver.onERC1155Received.selector; 29 | } 30 | 31 | function onERC1155BatchReceived( 32 | address, 33 | address, 34 | uint256[] calldata, 35 | uint256[] calldata, 36 | bytes calldata 37 | ) external pure override returns (bytes4) { 38 | return IERC1155Receiver.onERC1155BatchReceived.selector; 39 | } 40 | 41 | /// @dev functon visibility changed to public to allow overriding 42 | function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { 43 | return 44 | interfaceId == type(IERC721Receiver).interfaceId || 45 | interfaceId == type(IERC1155Receiver).interfaceId || 46 | interfaceId == type(IERC165).interfaceId; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /contracts/helpers/VerifierCaller.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | abstract contract VerifierCaller { 5 | /** 6 | * @notice Calls the verifier function with given params 7 | * @param verifier address - Address of the verifier contract 8 | * @param hash bytes32 - Signed data hash 9 | * @param rs bytes32[2] - Signature array for the r and s values 10 | * @param pubKey bytes32[2] - Public key coordinates array for the x and y values 11 | * @return - bool - Return the success of the verification 12 | */ 13 | function callVerifier( 14 | address verifier, 15 | bytes32 hash, 16 | bytes32[2] memory rs, 17 | bytes32[2] memory pubKey 18 | ) internal view returns (bool) { 19 | /** 20 | * Prepare the input format 21 | * input[ 0: 32] = signed data hash 22 | * input[ 32: 64] = signature r 23 | * input[ 64: 96] = signature s 24 | * input[ 96:128] = public key x 25 | * input[128:160] = public key y 26 | */ 27 | bytes memory input = abi.encodePacked(hash, rs[0], rs[1], pubKey[0], pubKey[1]); 28 | 29 | // Make a call to verify the signature 30 | (bool success, bytes memory data) = verifier.staticcall(input); 31 | 32 | uint256 returnValue; 33 | // Return true if the call was successful and the return value is 1 34 | if (success && data.length > 0) { 35 | assembly { 36 | returnValue := mload(add(data, 0x20)) 37 | } 38 | return returnValue == 1; 39 | } 40 | 41 | // Otherwise return false for the unsucessful calls and invalid signatures 42 | return false; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /contracts/libraries/ClaveStorage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | library ClaveStorage { 5 | //keccak256('clave.contracts.ClaveStorage') - 1 6 | bytes32 private constant CLAVE_STORAGE_SLOT = 7 | 0x3248da1aeae8bd923cbf26901dc4bfc6bb48bb0fbc5b6102f1151fe7012884f4; 8 | 9 | struct Layout { 10 | // ┌───────────────────┐ 11 | // │ Ownership Data │ 12 | mapping(bytes => bytes) r1Owners; 13 | mapping(address => address) k1Owners; 14 | uint256[50] __gap_0; 15 | // └───────────────────┘ 16 | 17 | // ┌───────────────────┐ 18 | // │ Fallback │ 19 | address defaultFallbackContract; // for next version 20 | uint256[50] __gap_1; 21 | // └───────────────────┘ 22 | 23 | // ┌───────────────────┐ 24 | // │ Validation │ 25 | mapping(address => address) r1Validators; 26 | mapping(address => address) k1Validators; 27 | uint256[50] __gap_2; 28 | // └───────────────────┘ 29 | 30 | // ┌───────────────────┐ 31 | // │ Module │ 32 | mapping(address => address) modules; 33 | uint256[50] __gap_3; 34 | // └───────────────────┘ 35 | 36 | // ┌───────────────────┐ 37 | // │ Hooks │ 38 | mapping(address => address) validationHooks; 39 | mapping(address => address) executionHooks; 40 | mapping(address => mapping(bytes32 => bytes)) hookDataStore; 41 | uint256[50] __gap_4; 42 | // └───────────────────┘ 43 | } 44 | 45 | function layout() internal pure returns (Layout storage l) { 46 | bytes32 slot = CLAVE_STORAGE_SLOT; 47 | assembly { 48 | l.slot := slot 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/utils/managers/modulemanager.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | import type { ec } from 'elliptic'; 7 | import { concat } from 'ethers'; 8 | import type { Contract, Provider } from 'zksync-ethers'; 9 | import { utils } from 'zksync-ethers'; 10 | 11 | import { prepareTeeTx } from '../transactions'; 12 | 13 | export async function addModule( 14 | provider: Provider, 15 | account: Contract, 16 | validator: Contract, 17 | module: Contract, 18 | initData: string, 19 | keyPair: ec.KeyPair, 20 | ): Promise { 21 | const moduleAndData = concat([await module.getAddress(), initData]); 22 | 23 | const addModuleTx = await account.addModule.populateTransaction( 24 | moduleAndData, 25 | ); 26 | const tx = await prepareTeeTx( 27 | provider, 28 | account, 29 | addModuleTx, 30 | await validator.getAddress(), 31 | keyPair, 32 | ); 33 | const txReceipt = await provider.broadcastTransaction( 34 | utils.serializeEip712(tx), 35 | ); 36 | await txReceipt.wait(); 37 | } 38 | 39 | export async function removeModule( 40 | provider: Provider, 41 | account: Contract, 42 | validator: Contract, 43 | module: Contract, 44 | keyPair: ec.KeyPair, 45 | ): Promise { 46 | const removeModuleTx = await account.removeModule.populateTransaction( 47 | await module.getAddress(), 48 | ); 49 | 50 | const tx = await prepareTeeTx( 51 | provider, 52 | account, 53 | removeModuleTx, 54 | await validator.getAddress(), 55 | keyPair, 56 | ); 57 | 58 | const txReceipt = await provider.broadcastTransaction( 59 | utils.serializeEip712(tx), 60 | ); 61 | await txReceipt.wait(); 62 | } 63 | -------------------------------------------------------------------------------- /contracts/batch/BatchCaller.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {SystemContractHelper} from '@matterlabs/zksync-contracts/l2/system-contracts/libraries/SystemContractHelper.sol'; 5 | import {EfficientCall} from '@matterlabs/zksync-contracts/l2/system-contracts/libraries/EfficientCall.sol'; 6 | import {Errors} from '../libraries/Errors.sol'; 7 | 8 | // Each call data for batches 9 | struct Call { 10 | address target; // Target contract address 11 | bool allowFailure; // Whether to revert if the call fails 12 | uint256 value; // Amount of ETH to send with call 13 | bytes callData; // Calldata to send 14 | } 15 | 16 | /// @title BatchCaller 17 | /// @notice Make multiple calls in a single transaction 18 | contract BatchCaller { 19 | /// @notice Make multiple calls, ensure success if required 20 | /// @dev Reverts if not called via delegatecall 21 | /// @param calls Call[] calldata - An array of Call structs 22 | function batchCall(Call[] calldata calls) external { 23 | bool isDelegateCall = SystemContractHelper.getCodeAddress() != address(this); 24 | if (!isDelegateCall) { 25 | revert Errors.ONLY_DELEGATECALL(); 26 | } 27 | 28 | // Execute each call 29 | uint256 len = calls.length; 30 | Call calldata calli; 31 | for (uint256 i = 0; i < len; ) { 32 | calli = calls[i]; 33 | address target = calli.target; 34 | uint256 value = calli.value; 35 | bytes calldata callData = calli.callData; 36 | 37 | bool success = EfficientCall.rawCall(gasleft(), target, value, callData, false); 38 | if (!calls[i].allowFailure && !success) { 39 | revert Errors.CALL_FAILED(); 40 | } 41 | 42 | unchecked { 43 | i++; 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /subgraph/abis/SwapReferralFeePayer.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": true, 7 | "internalType": "address", 8 | "name": "referred", 9 | "type": "address" 10 | }, 11 | { 12 | "indexed": true, 13 | "internalType": "address", 14 | "name": "token", 15 | "type": "address" 16 | }, 17 | { 18 | "indexed": false, 19 | "internalType": "uint256", 20 | "name": "fee", 21 | "type": "uint256" 22 | } 23 | ], 24 | "name": "Cashback", 25 | "type": "event" 26 | }, 27 | { 28 | "anonymous": false, 29 | "inputs": [ 30 | { 31 | "indexed": true, 32 | "internalType": "address", 33 | "name": "referrer", 34 | "type": "address" 35 | }, 36 | { 37 | "indexed": true, 38 | "internalType": "address", 39 | "name": "token", 40 | "type": "address" 41 | }, 42 | { 43 | "indexed": false, 44 | "internalType": "uint256", 45 | "name": "fee", 46 | "type": "uint256" 47 | } 48 | ], 49 | "name": "ReferralFee", 50 | "type": "event" 51 | }, 52 | { 53 | "inputs": [ 54 | { 55 | "internalType": "address", 56 | "name": "referrer", 57 | "type": "address" 58 | }, 59 | { 60 | "internalType": "address", 61 | "name": "token", 62 | "type": "address" 63 | }, 64 | { 65 | "internalType": "uint256", 66 | "name": "fee", 67 | "type": "uint256" 68 | }, 69 | { 70 | "internalType": "uint256", 71 | "name": "cashback", 72 | "type": "uint256" 73 | } 74 | ], 75 | "name": "payFee", 76 | "outputs": [], 77 | "stateMutability": "payable", 78 | "type": "function" 79 | } 80 | ] 81 | -------------------------------------------------------------------------------- /balances-subgraph/src/helpers.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-types */ 2 | 3 | /** 4 | * Copyright Clave - All Rights Reserved 5 | * Unauthorized copying of this file, via any medium is strictly prohibited 6 | * Proprietary and confidential 7 | */ 8 | 9 | /* eslint-disable @typescript-eslint/consistent-type-imports */ 10 | 11 | /* eslint-disable prefer-const */ 12 | import { BigInt } from '@graphprotocol/graph-ts'; 13 | 14 | import { ClaveAccount, ERC20, ERC20Balance } from '../generated/schema'; 15 | 16 | export const ZERO = BigInt.fromI32(0); 17 | export const ONE = BigInt.fromI32(1); 18 | 19 | function getOrCreateAccountBalance( 20 | account: ClaveAccount, 21 | token: ERC20, 22 | ): ERC20Balance { 23 | let balanceId = account.id.concat(token.id); 24 | let previousBalance = ERC20Balance.load(balanceId); 25 | 26 | if (previousBalance !== null) { 27 | return previousBalance; 28 | } 29 | 30 | let newBalance = new ERC20Balance(balanceId); 31 | newBalance.account = account.id; 32 | newBalance.token = token.id; 33 | newBalance.amount = ZERO; 34 | 35 | return newBalance; 36 | } 37 | 38 | export function increaseAccountBalance( 39 | account: ClaveAccount, 40 | token: ERC20, 41 | amount: BigInt, 42 | ): ERC20Balance { 43 | let balance = getOrCreateAccountBalance(account, token); 44 | balance.amount = balance.amount.plus(amount); 45 | token.totalAmount = token.totalAmount.plus(amount); 46 | token.save(); 47 | 48 | return balance; 49 | } 50 | 51 | export function decreaseAccountBalance( 52 | account: ClaveAccount, 53 | token: ERC20, 54 | amount: BigInt, 55 | ): ERC20Balance { 56 | let balance = getOrCreateAccountBalance(account, token); 57 | balance.amount = balance.amount.minus(amount); 58 | token.totalAmount = token.totalAmount.minus(amount); 59 | token.save(); 60 | 61 | return balance; 62 | } 63 | -------------------------------------------------------------------------------- /contracts/test/MockStorage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | library MockStorage { 5 | //keccak256('clave.contracts.ClaveStorage') - 1 6 | bytes32 private constant CLAVE_STORAGE_SLOT = 7 | 0x3248da1aeae8bd923cbf26901dc4bfc6bb48bb0fbc5b6102f1151fe7012884f4; 8 | 9 | struct Layout { 10 | // ┌───────────────────┐ 11 | // │ Ownership Data │ 12 | mapping(bytes => bytes) r1Owners; 13 | mapping(address => address) k1Owners; 14 | uint256[50] __gap_0; 15 | // └───────────────────┘ 16 | 17 | // ┌───────────────────┐ 18 | // │ Fallback │ 19 | address defaultFallbackContract; 20 | uint256[49] __gap_1; 21 | // └───────────────────┘ 22 | 23 | // ┌───────────────────┐ 24 | // │ Test │ 25 | uint256 testNumber; 26 | // └───────────────────┘ 27 | 28 | // ┌───────────────────┐ 29 | // │ Validation │ 30 | mapping(address => address) r1Validators; 31 | mapping(address => address) k1Validators; 32 | uint256[50] __gap_2; 33 | // └───────────────────┘ 34 | 35 | // ┌───────────────────┐ 36 | // │ Module │ 37 | mapping(address => address) modules; 38 | uint256[50] __gap_3; 39 | // └───────────────────┘ 40 | 41 | // ┌───────────────────┐ 42 | // │ Hooks │ 43 | mapping(address => address) validationHooks; 44 | mapping(address => address) executionHooks; 45 | mapping(address => mapping(bytes32 => bytes)) hookDataStore; 46 | uint256[50] __gap_4; 47 | // └───────────────────┘ 48 | } 49 | 50 | function layout() internal pure returns (Layout storage l) { 51 | bytes32 slot = CLAVE_STORAGE_SLOT; 52 | assembly { 53 | l.slot := slot 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Clave 2 | 3 | Welcome to the Clave! We're thrilled you're considering contributing to our project. Every contribution, from code to documentation, helps us create a better product and community. 4 | 5 | ## How to Contribute 6 | 7 | 1. **Fork the Repository** 8 | 9 | - Create your own fork of the repo. This allows you to work freely without affecting the main project. 10 | 11 | 2. **Create a Branch** 12 | 13 | - Create a branch in your forked repo for your contribution. Preferably, use a clear and descriptive name. 14 | 15 | 3. **Make Your Changes** 16 | 17 | - Implement your changes, adhering to our coding standards and guidelines. 18 | - If you're adding functionality, please include tests. 19 | 20 | 4. **Commit Your Changes** 21 | 22 | - Write clear, concise commit messages. This helps us understand and review your contribution. 23 | 24 | 5. **Push to the Branch** 25 | 26 | - Push your changes to your branch in your forked repository. 27 | 28 | 6. **Submit a Pull Request** 29 | 30 | - Open a pull request against our main branch. 31 | - Include a clear description of the changes and any relevant issue numbers. 32 | 33 | 7. **Code Review** 34 | - Wait for a core team member to review your pull request. Be open to feedback and revisions if necessary. 35 | 36 | ## Testing 37 | 38 | - Ensure your code passes all tests. We use Hardhat for automated testing. 39 | - Add new tests for any new features or bug fixes. 40 | 41 | ## Reporting Bugs 42 | 43 | - If you find a bug, please open an issue in our repository. 44 | - Provide a detailed description of the bug and steps to reproduce it. 45 | 46 | ## Documentation 47 | 48 | - Update the documentation if your changes require it. 49 | - We value clear, understandable documentation for all aspects of our project. 50 | 51 | ## Licensing 52 | 53 | - Remember that contributions are subject to the project’s license, typically found in the LICENSE file. 54 | 55 | Thank you for contributing to Clave! 56 | -------------------------------------------------------------------------------- /subgraph/src/sync-staking.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | 7 | /* eslint-disable @typescript-eslint/consistent-type-imports */ 8 | import { ClaimRewards as ClaimRewardsEvent } from '../generated/SyncStaking/SyncStaking'; 9 | import { ClaveAccount } from '../generated/schema'; 10 | import { 11 | getOrCreateDailyEarnFlow, 12 | getOrCreateDay, 13 | getOrCreateEarnPosition, 14 | getOrCreateMonth, 15 | getOrCreateMonthlyEarnFlow, 16 | getOrCreateWeek, 17 | getOrCreateWeeklyEarnFlow, 18 | } from './helpers'; 19 | 20 | const protocol = 'SyncSwap'; 21 | 22 | export function handleClaimRewards(event: ClaimRewardsEvent): void { 23 | const account = ClaveAccount.load(event.params.from); 24 | if (!account) { 25 | return; 26 | } 27 | 28 | const pool = event.address; 29 | const token = event.params.reward; 30 | const amount = event.params.amount; 31 | 32 | const day = getOrCreateDay(event.block.timestamp); 33 | const week = getOrCreateWeek(event.block.timestamp); 34 | const month = getOrCreateMonth(event.block.timestamp); 35 | const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); 36 | const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); 37 | const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); 38 | const earnPosition = getOrCreateEarnPosition( 39 | account, 40 | pool, 41 | token, 42 | protocol, 43 | ); 44 | 45 | dailyEarnFlow.claimedGain = dailyEarnFlow.claimedGain.plus(amount); 46 | weeklyEarnFlow.claimedGain = weeklyEarnFlow.claimedGain.plus(amount); 47 | monthlyEarnFlow.claimedGain = monthlyEarnFlow.claimedGain.plus(amount); 48 | earnPosition.normalGain = earnPosition.normalGain.plus(amount); 49 | 50 | dailyEarnFlow.save(); 51 | weeklyEarnFlow.save(); 52 | monthlyEarnFlow.save(); 53 | earnPosition.save(); 54 | } 55 | -------------------------------------------------------------------------------- /subgraph/src/sync-clave-staking.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | 7 | /* eslint-disable @typescript-eslint/consistent-type-imports */ 8 | import { ClaimRewards as ClaimRewardsEvent } from '../generated/SyncClaveStaking/SyncClaveStaking'; 9 | import { ClaveAccount } from '../generated/schema'; 10 | import { 11 | getOrCreateDailyEarnFlow, 12 | getOrCreateDay, 13 | getOrCreateEarnPosition, 14 | getOrCreateMonth, 15 | getOrCreateMonthlyEarnFlow, 16 | getOrCreateWeek, 17 | getOrCreateWeeklyEarnFlow, 18 | } from './helpers'; 19 | 20 | const protocol = 'SyncSwap'; 21 | 22 | export function handleClaimRewards(event: ClaimRewardsEvent): void { 23 | const account = ClaveAccount.load(event.params.account); 24 | if (!account) { 25 | return; 26 | } 27 | 28 | const pool = event.address; 29 | const token = event.params.token; 30 | const amount = event.params.amount; 31 | 32 | const day = getOrCreateDay(event.block.timestamp); 33 | const week = getOrCreateWeek(event.block.timestamp); 34 | const month = getOrCreateMonth(event.block.timestamp); 35 | const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); 36 | const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); 37 | const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); 38 | const earnPosition = getOrCreateEarnPosition( 39 | account, 40 | pool, 41 | token, 42 | protocol, 43 | ); 44 | 45 | dailyEarnFlow.claimedGain = dailyEarnFlow.claimedGain.plus(amount); 46 | weeklyEarnFlow.claimedGain = weeklyEarnFlow.claimedGain.plus(amount); 47 | monthlyEarnFlow.claimedGain = monthlyEarnFlow.claimedGain.plus(amount); 48 | earnPosition.normalGain = earnPosition.normalGain.plus(amount); 49 | 50 | dailyEarnFlow.save(); 51 | weeklyEarnFlow.save(); 52 | monthlyEarnFlow.save(); 53 | earnPosition.save(); 54 | } 55 | -------------------------------------------------------------------------------- /test/utils/managers/hookmanager.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | import type { ec } from 'elliptic'; 7 | import type { BytesLike } from 'ethers'; 8 | import type { Contract, Provider } from 'zksync-ethers'; 9 | import { utils } from 'zksync-ethers'; 10 | 11 | import type { HOOKS } from '../names'; 12 | import { prepareTeeTx } from '../transactions'; 13 | 14 | export async function addHook( 15 | provider: Provider, 16 | account: Contract, 17 | validator: Contract, 18 | hook: Contract, 19 | isValidation: HOOKS, 20 | keyPair: ec.KeyPair, 21 | hookData: Array = [], 22 | ): Promise { 23 | const addHookTx = await account.addHook.populateTransaction( 24 | await hook.getAddress(), 25 | isValidation == 1 ? true : false, 26 | ); 27 | const tx = await prepareTeeTx( 28 | provider, 29 | account, 30 | addHookTx, 31 | await validator.getAddress(), 32 | keyPair, 33 | hookData, 34 | ); 35 | const txReceipt = await provider.broadcastTransaction( 36 | utils.serializeEip712(tx), 37 | ); 38 | await txReceipt.wait(); 39 | } 40 | 41 | export async function removeHook( 42 | provider: Provider, 43 | account: Contract, 44 | validator: Contract, 45 | hook: Contract, 46 | isValidation: HOOKS, 47 | keyPair: ec.KeyPair, 48 | hookData: Array = [], 49 | ): Promise { 50 | const removeHookTx = await account.removeHook.populateTransaction( 51 | await hook.getAddress(), 52 | isValidation == 1 ? true : false, 53 | ); 54 | const tx = await prepareTeeTx( 55 | provider, 56 | account, 57 | removeHookTx, 58 | await validator.getAddress(), 59 | keyPair, 60 | hookData, 61 | ); 62 | const txReceipt = await provider.broadcastTransaction( 63 | utils.serializeEip712(tx), 64 | ); 65 | await txReceipt.wait(); 66 | } 67 | -------------------------------------------------------------------------------- /contracts/handlers/ValidationHandler.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | import {SignatureDecoder} from '../libraries/SignatureDecoder.sol'; 5 | import {BytesLinkedList} from '../libraries/LinkedList.sol'; 6 | import {OwnerManager} from '../managers/OwnerManager.sol'; 7 | import {ValidatorManager} from '../managers/ValidatorManager.sol'; 8 | 9 | import {IK1Validator, IR1Validator} from '../interfaces/IValidator.sol'; 10 | 11 | /** 12 | * @title ValidationHandler 13 | * @notice Contract which calls validators for signature validation 14 | * @author https://getclave.io 15 | */ 16 | abstract contract ValidationHandler is OwnerManager, ValidatorManager { 17 | function _handleValidation( 18 | address validator, 19 | bytes32 signedHash, 20 | bytes memory signature 21 | ) internal view returns (bool) { 22 | if (_r1IsValidator(validator)) { 23 | mapping(bytes => bytes) storage owners = OwnerManager._r1OwnersLinkedList(); 24 | bytes memory cursor = owners[BytesLinkedList.SENTINEL_BYTES]; 25 | while (cursor.length > BytesLinkedList.SENTINEL_LENGTH) { 26 | bytes32[2] memory pubKey = abi.decode(cursor, (bytes32[2])); 27 | 28 | bool _success = IR1Validator(validator).validateSignature( 29 | signedHash, 30 | signature, 31 | pubKey 32 | ); 33 | 34 | if (_success) { 35 | return true; 36 | } 37 | 38 | cursor = owners[cursor]; 39 | } 40 | } else if (_k1IsValidator(validator)) { 41 | address recoveredAddress = IK1Validator(validator).validateSignature( 42 | signedHash, 43 | signature 44 | ); 45 | 46 | if (recoveredAddress == address(0)) { 47 | return false; 48 | } 49 | 50 | if (OwnerManager._k1IsOwner(recoveredAddress)) { 51 | return true; 52 | } 53 | } 54 | 55 | return false; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /contracts/interfaces/IModuleManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | /** 5 | * @title Interface of the manager contract for modules 6 | * @author https://getclave.io 7 | */ 8 | interface IModuleManager { 9 | /** 10 | * @notice Event emitted when a module is added 11 | * @param module address - Address of the added module 12 | */ 13 | event AddModule(address indexed module); 14 | 15 | /** 16 | * @notice Event emitted when a module is removed 17 | * @param module address - Address of the removed module 18 | */ 19 | event RemoveModule(address indexed module); 20 | 21 | /** 22 | * @notice Add a module to the list of modules and call it's init function 23 | * @dev Can only be called by self or a module 24 | * @param moduleAndData bytes calldata - Address of the module and data to initialize it with 25 | */ 26 | function addModule(bytes calldata moduleAndData) external; 27 | 28 | /** 29 | * @notice Remove a module from the list of modules and call it's disable function 30 | * @dev Can only be called by self or a module 31 | * @param module address - Address of the module to remove 32 | */ 33 | function removeModule(address module) external; 34 | 35 | /** 36 | * @notice Allow modules to execute arbitrary calls on behalf of the account 37 | * @dev Can only be called by a module 38 | * @param to address - Address to call 39 | * @param value uint256 - Eth to send with call 40 | * @param data bytes memory - Data to make the call with 41 | */ 42 | function executeFromModule(address to, uint256 value, bytes memory data) external; 43 | 44 | /** 45 | * @notice Check if an address is in the list of modules 46 | * @param addr address - Address to check 47 | * @return bool - True if the address is a module, false otherwise 48 | */ 49 | function isModule(address addr) external view returns (bool); 50 | 51 | /** 52 | * @notice Get the list of modules 53 | * @return moduleList address[] memory - List of modules 54 | */ 55 | function listModules() external view returns (address[] memory moduleList); 56 | } 57 | -------------------------------------------------------------------------------- /subgraph/src/venus-reward.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | 7 | /* eslint-disable @typescript-eslint/consistent-type-imports */ 8 | import { Address } from '@graphprotocol/graph-ts'; 9 | 10 | import { DistributedSupplierRewardToken as DistributedSupplierRewardTokenEvent } from '../generated/VenusReward/VenusReward'; 11 | import { ClaveAccount } from '../generated/schema'; 12 | import { 13 | getOrCreateDailyEarnFlow, 14 | getOrCreateDay, 15 | getOrCreateEarnPosition, 16 | getOrCreateMonth, 17 | getOrCreateMonthlyEarnFlow, 18 | getOrCreateWeek, 19 | getOrCreateWeeklyEarnFlow, 20 | } from './helpers'; 21 | 22 | const protocol = 'Venus'; 23 | const token = Address.fromHexString( 24 | '0xD78ABD81a3D57712a3af080dc4185b698Fe9ac5A', 25 | ); 26 | 27 | export function handleDistributedSupplierRewardToken( 28 | event: DistributedSupplierRewardTokenEvent, 29 | ): void { 30 | const account = ClaveAccount.load(event.params.supplier); 31 | if (!account) { 32 | return; 33 | } 34 | 35 | const pool = event.address; 36 | const amount = event.params.rewardTokenTotal; 37 | 38 | const day = getOrCreateDay(event.block.timestamp); 39 | const week = getOrCreateWeek(event.block.timestamp); 40 | const month = getOrCreateMonth(event.block.timestamp); 41 | 42 | const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); 43 | const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); 44 | const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); 45 | const earnPosition = getOrCreateEarnPosition( 46 | account, 47 | pool, 48 | token, 49 | protocol, 50 | ); 51 | 52 | dailyEarnFlow.claimedGain = dailyEarnFlow.claimedGain.plus(amount); 53 | weeklyEarnFlow.claimedGain = weeklyEarnFlow.claimedGain.plus(amount); 54 | monthlyEarnFlow.claimedGain = monthlyEarnFlow.claimedGain.plus(amount); 55 | earnPosition.normalGain = earnPosition.normalGain.plus(amount); 56 | 57 | dailyEarnFlow.save(); 58 | weeklyEarnFlow.save(); 59 | monthlyEarnFlow.save(); 60 | earnPosition.save(); 61 | } 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .vscode 9 | 10 | # hardhat artifacts 11 | artifacts 12 | cache 13 | typechain-types 14 | 15 | # zksync artifacts 16 | artifacts-zk 17 | cache-zk 18 | deployments-zk 19 | 20 | # Diagnostic reports (https://nodejs.org/api/report.html) 21 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 22 | 23 | # Runtime data 24 | pids 25 | *.pid 26 | *.seed 27 | *.pid.lock 28 | 29 | # Directory for instrumented libs generated by jscoverage/JSCover 30 | lib-cov 31 | 32 | # Coverage directory used by tools like istanbul 33 | coverage 34 | *.lcov 35 | 36 | # nyc test coverage 37 | .nyc_output 38 | 39 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 40 | .grunt 41 | 42 | # Bower dependency directory (https://bower.io/) 43 | bower_components 44 | 45 | # node-waf configuration 46 | .lock-wscript 47 | 48 | # Compiled binary addons (https://nodejs.org/api/addons.html) 49 | build/Release 50 | 51 | # Dependency directories 52 | node_modules/ 53 | jspm_packages/ 54 | 55 | # TypeScript v1 declaration files 56 | typings/ 57 | 58 | # TypeScript cache 59 | *.tsbuildinfo 60 | 61 | # Optional npm cache directory 62 | .npm 63 | 64 | # Optional eslint cache 65 | .eslintcache 66 | 67 | # Microbundle cache 68 | .rpt2_cache/ 69 | .rts2_cache_cjs/ 70 | .rts2_cache_es/ 71 | .rts2_cache_umd/ 72 | 73 | # Optional REPL history 74 | .node_repl_history 75 | 76 | # Output of 'npm pack' 77 | *.tgz 78 | 79 | # Yarn Integrity file 80 | .yarn-integrity 81 | 82 | # dotenv environment variables file 83 | .env 84 | .env.test 85 | 86 | # parcel-bundler cache (https://parceljs.org/) 87 | .cache 88 | 89 | # Next.js build output 90 | .next 91 | 92 | # Nuxt.js build / generate output 93 | .nuxt 94 | dist 95 | 96 | # Gatsby files 97 | .cache/ 98 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 99 | # https://nextjs.org/blog/next-9-1#public-directory-support 100 | # public 101 | 102 | # vuepress build output 103 | .vuepress/dist 104 | 105 | # Serverless directories 106 | .serverless/ 107 | 108 | # FuseBox cache 109 | .fusebox/ 110 | 111 | # DynamoDB Local files 112 | .dynamodb/ 113 | 114 | # TernJS port file 115 | .tern-port 116 | -------------------------------------------------------------------------------- /subgraph/src/erc-20-paymaster.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | 7 | /* eslint-disable @typescript-eslint/consistent-type-imports */ 8 | import { BigInt } from '@graphprotocol/graph-ts'; 9 | 10 | import { ERC20PaymasterUsed as ERC20PaymasterUsedEvent } from '../generated/ERC20Paymaster/ERC20Paymaster'; 11 | import { ClaveAccount, ClaveTransaction } from '../generated/schema'; 12 | import { 13 | getOrCreateDay, 14 | getOrCreateDayAccount, 15 | getOrCreateMonth, 16 | getOrCreateMonthAccount, 17 | getOrCreateWeek, 18 | getOrCreateWeekAccount, 19 | getTotal, 20 | } from './helpers'; 21 | 22 | export function handleERC20PaymasterUsed(event: ERC20PaymasterUsedEvent): void { 23 | const account = ClaveAccount.load(event.params.user); 24 | if (!account) { 25 | return; 26 | } 27 | 28 | const day = getOrCreateDay(event.block.timestamp); 29 | const week = getOrCreateWeek(event.block.timestamp); 30 | const month = getOrCreateMonth(event.block.timestamp); 31 | const total = getTotal(); 32 | day.transactions = day.transactions + 1; 33 | week.transactions = week.transactions + 1; 34 | month.transactions = month.transactions + 1; 35 | total.transactions = total.transactions + 1; 36 | 37 | const dayAccount = getOrCreateDayAccount(account, day); 38 | dayAccount.save(); 39 | const weekAccount = getOrCreateWeekAccount(account, week); 40 | weekAccount.save(); 41 | const monthAccount = getOrCreateMonthAccount(account, month); 42 | monthAccount.save(); 43 | 44 | const transaction = new ClaveTransaction(event.transaction.hash); 45 | transaction.sender = event.transaction.from; 46 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 47 | transaction.to = event.transaction.to!; 48 | transaction.value = event.transaction.value; 49 | let gasUsed = BigInt.fromI32(0); 50 | const receipt = event.receipt; 51 | if (receipt != null) { 52 | gasUsed = receipt.gasUsed; 53 | } 54 | transaction.gasCost = event.transaction.gasPrice.times(gasUsed); 55 | transaction.paymaster = 'ERC20'; 56 | transaction.date = event.block.timestamp; 57 | 58 | account.txCount = account.txCount + 1; 59 | account.save(); 60 | 61 | day.save(); 62 | week.save(); 63 | month.save(); 64 | total.save(); 65 | transaction.save(); 66 | } 67 | -------------------------------------------------------------------------------- /subgraph/src/social-recovery.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | 7 | /* eslint-disable @typescript-eslint/consistent-type-imports */ 8 | import { 9 | Disabled as DisabledEvent, 10 | Inited as InitedEvent, 11 | RecoveryExecuted as RecoveryExecutedEvent, 12 | RecoveryStarted as RecoveryStartedEvent, 13 | RecoveryStopped as RecoveryStoppedEvent, 14 | } from '../generated/SocialRecovery/SocialRecovery'; 15 | import { ClaveAccount } from '../generated/schema'; 16 | import { getTotal } from './helpers'; 17 | 18 | export function handleDisabled(event: DisabledEvent): void { 19 | const account = ClaveAccount.load(event.params.account); 20 | 21 | if (account === null) { 22 | return; 23 | } 24 | 25 | const total = getTotal(); 26 | total.backedUp = total.backedUp - 1; 27 | 28 | account.hasRecovery = false; 29 | account.isRecovering = false; 30 | 31 | total.save(); 32 | account.save(); 33 | } 34 | 35 | export function handleInited(event: InitedEvent): void { 36 | const account = ClaveAccount.load(event.params.account); 37 | 38 | if (account === null) { 39 | return; 40 | } 41 | 42 | const total = getTotal(); 43 | total.backedUp = total.backedUp + 1; 44 | 45 | account.hasRecovery = true; 46 | account.isRecovering = false; 47 | 48 | total.save(); 49 | account.save(); 50 | } 51 | 52 | export function handleRecoveryExecuted(event: RecoveryExecutedEvent): void { 53 | const account = ClaveAccount.load(event.params.account); 54 | 55 | if (account === null) { 56 | return; 57 | } 58 | 59 | account.isRecovering = false; 60 | // eslint-disable-next-line @typescript-eslint/restrict-plus-operands 61 | account.recoveryCount = account.recoveryCount + 1; 62 | 63 | account.save(); 64 | } 65 | 66 | export function handleRecoveryStarted(event: RecoveryStartedEvent): void { 67 | const account = ClaveAccount.load(event.params.account); 68 | 69 | if (account === null) { 70 | return; 71 | } 72 | 73 | account.isRecovering = true; 74 | 75 | account.save(); 76 | } 77 | 78 | export function handleRecoveryStopped(event: RecoveryStoppedEvent): void { 79 | const account = ClaveAccount.load(event.params.account); 80 | 81 | if (account === null) { 82 | return; 83 | } 84 | 85 | account.isRecovering = false; 86 | 87 | account.save(); 88 | } 89 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | import '@matterlabs/hardhat-zksync'; 7 | import '@nomicfoundation/hardhat-chai-matchers'; 8 | import '@nomicfoundation/hardhat-ethers'; 9 | import '@typechain/hardhat'; 10 | import dotenv from 'dotenv'; 11 | import type { HardhatUserConfig } from 'hardhat/config'; 12 | import type { NetworkUserConfig } from 'hardhat/types'; 13 | 14 | import './tasks/deploy'; 15 | 16 | dotenv.config(); 17 | 18 | const zkSyncMainnet: NetworkUserConfig = { 19 | url: 'https://mainnet.era.zksync.io', 20 | ethNetwork: 'mainnet', 21 | zksync: true, 22 | verifyURL: 23 | 'https://zksync2-mainnet-explorer.zksync.io/contract_verification', 24 | chainId: 324, 25 | enableRip7212: true, 26 | }; 27 | 28 | const zkSyncSepolia: NetworkUserConfig = { 29 | url: 'https://sepolia.era.zksync.dev', 30 | ethNetwork: 'sepolia', 31 | zksync: true, 32 | verifyURL: 'https://explorer.sepolia.era.zksync.dev/contract_verification', 33 | chainId: 300, 34 | enableRip7212: true, 35 | }; 36 | 37 | const inMemoryNode: NetworkUserConfig = { 38 | url: 'http://127.0.0.1:8011', 39 | ethNetwork: '', // in-memory node doesn't support eth node; removing this line will cause an error 40 | zksync: true, 41 | chainId: 260, 42 | enableRip7212: true, 43 | }; 44 | 45 | const dockerizedNode: NetworkUserConfig = { 46 | url: 'http://localhost:3050', 47 | ethNetwork: 'http://localhost:8545', 48 | zksync: true, 49 | chainId: 270, 50 | enableRip7212: true, 51 | }; 52 | 53 | const config: HardhatUserConfig = { 54 | zksolc: { 55 | version: 'latest', 56 | settings: { 57 | enableEraVMExtensions: true, 58 | optimizer: process.env.TEST 59 | ? { 60 | mode: 'z', 61 | } 62 | : undefined, 63 | libraries: { 64 | 'contracts/libraries/StringUtils.sol': { 65 | StringUtils: '0x7e390c46302Fb6D7f6C7b4e36937287eB678FBC0', 66 | }, 67 | }, 68 | }, 69 | }, 70 | defaultNetwork: 'zkSyncSepolia', 71 | networks: { 72 | hardhat: { 73 | zksync: true, 74 | enableRip7212: true, 75 | }, 76 | zkSyncSepolia, 77 | zkSyncMainnet, 78 | inMemoryNode, 79 | dockerizedNode, 80 | }, 81 | solidity: { 82 | version: '0.8.26', 83 | }, 84 | }; 85 | 86 | export default config; 87 | -------------------------------------------------------------------------------- /subgraph/src/account.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | 7 | /* eslint-disable @typescript-eslint/consistent-type-imports */ 8 | import { BigInt } from '@graphprotocol/graph-ts'; 9 | 10 | import { ClaveAccount, ClaveTransaction } from '../generated/schema'; 11 | import { 12 | FeePaid as FeePaidEvent, 13 | Upgraded as UpgradedEvent, 14 | } from '../generated/templates/Account/ClaveImplementation'; 15 | import { 16 | getOrCreateDay, 17 | getOrCreateDayAccount, 18 | getOrCreateMonth, 19 | getOrCreateMonthAccount, 20 | getOrCreateWeek, 21 | getOrCreateWeekAccount, 22 | getTotal, 23 | } from './helpers'; 24 | 25 | export function handleFeePaid(event: FeePaidEvent): void { 26 | const transaction = new ClaveTransaction(event.transaction.hash); 27 | const day = getOrCreateDay(event.block.timestamp); 28 | const week = getOrCreateWeek(event.block.timestamp); 29 | const month = getOrCreateMonth(event.block.timestamp); 30 | const total = getTotal(); 31 | const account = ClaveAccount.load(event.address); 32 | if (account != null) { 33 | const dayAccount = getOrCreateDayAccount(account, day); 34 | dayAccount.save(); 35 | const weekAccount = getOrCreateWeekAccount(account, week); 36 | weekAccount.save(); 37 | const monthAccount = getOrCreateMonthAccount(account, month); 38 | monthAccount.save(); 39 | 40 | account.txCount = account.txCount + 1; 41 | account.save(); 42 | } 43 | day.transactions = day.transactions + 1; 44 | week.transactions = week.transactions + 1; 45 | month.transactions = month.transactions + 1; 46 | total.transactions = total.transactions + 1; 47 | transaction.sender = event.address; 48 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 49 | transaction.to = event.transaction.to!; 50 | transaction.value = event.transaction.value; 51 | let gasUsed = BigInt.fromI32(0); 52 | const receipt = event.receipt; 53 | if (receipt != null) { 54 | gasUsed = receipt.gasUsed; 55 | } 56 | transaction.gasCost = event.transaction.gasPrice.times(gasUsed); 57 | transaction.paymaster = 'None'; 58 | transaction.date = event.block.timestamp; 59 | 60 | day.save(); 61 | week.save(); 62 | month.save(); 63 | total.save(); 64 | transaction.save(); 65 | } 66 | 67 | export function handleUpgraded(event: UpgradedEvent): void { 68 | const account = ClaveAccount.load(event.address); 69 | if (account == null) { 70 | return; 71 | } 72 | account.implementation = event.params.newImplementation; 73 | account.save(); 74 | } 75 | -------------------------------------------------------------------------------- /subgraph/src/gasless-paymaster.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | 7 | /* eslint-disable @typescript-eslint/consistent-type-imports */ 8 | import { BigInt } from '@graphprotocol/graph-ts'; 9 | 10 | import { FeeSponsored as FeeSponsoredEvent } from '../generated/GaslessPaymaster/GaslessPaymaster'; 11 | import { ClaveAccount, ClaveTransaction } from '../generated/schema'; 12 | import { 13 | getOrCreateDay, 14 | getOrCreateDayAccount, 15 | getOrCreateMonth, 16 | getOrCreateMonthAccount, 17 | getOrCreateWeek, 18 | getOrCreateWeekAccount, 19 | getTotal, 20 | } from './helpers'; 21 | 22 | export function handleFeeSponsored(event: FeeSponsoredEvent): void { 23 | const transaction = new ClaveTransaction(event.transaction.hash); 24 | const day = getOrCreateDay(event.block.timestamp); 25 | const week = getOrCreateWeek(event.block.timestamp); 26 | const month = getOrCreateMonth(event.block.timestamp); 27 | const total = getTotal(); 28 | const account = ClaveAccount.load(event.params.user); 29 | if (account != null) { 30 | const dayAccount = getOrCreateDayAccount(account, day); 31 | dayAccount.save(); 32 | const weekAccount = getOrCreateWeekAccount(account, week); 33 | weekAccount.save(); 34 | const monthAccount = getOrCreateMonthAccount(account, month); 35 | monthAccount.save(); 36 | 37 | day.transactions = day.transactions + 1; 38 | week.transactions = week.transactions + 1; 39 | month.transactions = month.transactions + 1; 40 | total.transactions = total.transactions + 1; 41 | 42 | transaction.sender = event.transaction.from; 43 | if (event.transaction.to) { 44 | transaction.to = event.transaction.to; 45 | } 46 | transaction.value = event.transaction.value; 47 | let gasUsed = BigInt.fromI32(0); 48 | const receipt = event.receipt; 49 | if (receipt != null) { 50 | gasUsed = receipt.gasUsed; 51 | } 52 | const gasCost = event.transaction.gasPrice.times(gasUsed); 53 | day.gasSponsored = day.gasSponsored.plus(gasCost); 54 | week.gasSponsored = week.gasSponsored.plus(gasCost); 55 | month.gasSponsored = month.gasSponsored.plus(gasCost); 56 | total.gasSponsored = total.gasSponsored.plus(gasCost); 57 | transaction.gasCost = gasCost; 58 | transaction.paymaster = 'Gasless'; 59 | transaction.date = event.block.timestamp; 60 | 61 | account.txCount = account.txCount + 1; 62 | account.save(); 63 | } 64 | 65 | day.save(); 66 | week.save(); 67 | month.save(); 68 | total.save(); 69 | transaction.save(); 70 | } 71 | -------------------------------------------------------------------------------- /contracts/handlers/ERC1271Handler.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | import {IERC1271Upgradeable} from '@openzeppelin/contracts-upgradeable/interfaces/IERC1271Upgradeable.sol'; 5 | 6 | import {SignatureDecoder} from '../libraries/SignatureDecoder.sol'; 7 | import {ValidationHandler} from './ValidationHandler.sol'; 8 | import {EIP712} from '../helpers/EIP712.sol'; 9 | 10 | /** 11 | * @title ERC1271Handler 12 | * @notice Contract which provides ERC1271 signature validation 13 | * @author https://getclave.io 14 | */ 15 | abstract contract ERC1271Handler is 16 | IERC1271Upgradeable, 17 | EIP712('Clave1271', '1.0.0'), 18 | ValidationHandler 19 | { 20 | struct ClaveMessage { 21 | bytes32 signedHash; 22 | } 23 | 24 | bytes32 constant _CLAVE_MESSAGE_TYPEHASH = keccak256('ClaveMessage(bytes32 signedHash)'); 25 | 26 | bytes4 private constant _ERC1271_MAGIC = 0x1626ba7e; 27 | 28 | /** 29 | * @dev Should return whether the signature provided is valid for the provided data 30 | * @param signedHash bytes32 - Hash of the data that is signed 31 | * @param signatureAndValidator bytes calldata - Validator address concatenated to signature 32 | * @return magicValue bytes4 - Magic value if the signature is valid, 0 otherwise 33 | */ 34 | function isValidSignature( 35 | bytes32 signedHash, 36 | bytes memory signatureAndValidator 37 | ) public view override returns (bytes4 magicValue) { 38 | (bytes memory signature, address validator) = SignatureDecoder.decodeSignatureNoHookData( 39 | signatureAndValidator 40 | ); 41 | 42 | bytes32 eip712Hash = _hashTypedDataV4(_claveMessageHash(ClaveMessage(signedHash))); 43 | 44 | bool valid = _handleValidation(validator, eip712Hash, signature); 45 | 46 | magicValue = valid ? _ERC1271_MAGIC : bytes4(0); 47 | } 48 | 49 | /** 50 | * @notice Returns the EIP-712 hash of the Clave message 51 | * @param claveMessage ClaveMessage calldata - The message containing signedHash 52 | * @return bytes32 - EIP712 hash of the message 53 | */ 54 | function getEip712Hash(ClaveMessage calldata claveMessage) external view returns (bytes32) { 55 | return _hashTypedDataV4(_claveMessageHash(claveMessage)); 56 | } 57 | 58 | /** 59 | * @notice Returns the typehash for the clave message struct 60 | * @return bytes32 - Clave message typehash 61 | */ 62 | function claveMessageTypeHash() external pure returns (bytes32) { 63 | return _CLAVE_MESSAGE_TYPEHASH; 64 | } 65 | 66 | function _claveMessageHash(ClaveMessage memory claveMessage) internal pure returns (bytes32) { 67 | return keccak256(abi.encode(_CLAVE_MESSAGE_TYPEHASH, claveMessage.signedHash)); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /contracts/interfaces/IHookManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | /** 5 | * @title Interface of the manager contract for hooks 6 | * @author https://getclave.io 7 | */ 8 | interface IHookManager { 9 | /** 10 | * @notice Event emitted when a hook is added 11 | * @param hook address - Address of the added hook 12 | */ 13 | event AddHook(address indexed hook); 14 | 15 | /** 16 | * @notice Event emitted when a hook is removed 17 | * @param hook address - Address of the removed hook 18 | */ 19 | event RemoveHook(address indexed hook); 20 | 21 | /** 22 | * @notice Add a hook to the list of hooks and call it's init function 23 | * @dev Can only be called by self or a module 24 | * @param hookAndData bytes calldata - Address of the hook and data to initialize it with 25 | * @param isValidation bool - True if the hook is a validation hook, false otherwise 26 | */ 27 | function addHook(bytes calldata hookAndData, bool isValidation) external; 28 | 29 | /** 30 | * @notice Remove a hook from the list of hooks and call it's disable function 31 | * @dev Can only be called by self or a module 32 | * @param hook address - Address of the hook to remove 33 | * @param isValidation bool - True if the hook is a validation hook, false otherwise 34 | */ 35 | function removeHook(address hook, bool isValidation) external; 36 | 37 | /** 38 | * @notice Allow a hook to store data in the contract 39 | * @dev Can only be called by a hook 40 | * @param key bytes32 - Slot to store data at 41 | * @param data bytes calldata - Data to store 42 | */ 43 | function setHookData(bytes32 key, bytes calldata data) external; 44 | 45 | /** 46 | * @notice Get the data stored by a hook 47 | * @param hook address - Address of the hook to retrieve data for 48 | * @param key bytes32 - Slot to retrieve data from 49 | * @return bytes memory - Data stored at the slot 50 | */ 51 | function getHookData(address hook, bytes32 key) external view returns (bytes memory); 52 | 53 | /** 54 | * @notice Check if an address is in the list of hooks 55 | * @param addr address - Address to check 56 | * @return bool - True if the address is a hook, false otherwise 57 | */ 58 | function isHook(address addr) external view returns (bool); 59 | 60 | /** 61 | * @notice Get the list of validation or execution hooks 62 | * @param isValidation bool - True if the list of validation hooks should be returned, false otherwise 63 | * @return hookList address[] memory - List of validation or exeuction hooks 64 | */ 65 | function listHooks(bool isValidation) external view returns (address[] memory hookList); 66 | } 67 | -------------------------------------------------------------------------------- /subgraph/src/account-factory-v2.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | 7 | /* eslint-disable @typescript-eslint/consistent-type-imports */ 8 | import { Bytes } from '@graphprotocol/graph-ts'; 9 | 10 | import { 11 | ClaveAccountCreated as ClaveAccountCreatedEvent, 12 | ClaveAccountDeployed as ClaveAccountDeployedEvent, 13 | } from '../generated/AccountFactoryV2/AccountFactoryV2'; 14 | import { ClaveAccount } from '../generated/schema'; 15 | import { Account } from '../generated/templates'; 16 | import { 17 | ZERO, 18 | getOrCreateDay, 19 | getOrCreateMonth, 20 | getOrCreateWeek, 21 | getTotal, 22 | } from './helpers'; 23 | 24 | export function handleClaveAccountCreated( 25 | event: ClaveAccountCreatedEvent, 26 | ): void { 27 | let account = ClaveAccount.load(event.params.accountAddress); 28 | if (account) { 29 | return; 30 | } 31 | account = new ClaveAccount(event.params.accountAddress); 32 | const day = getOrCreateDay(event.block.timestamp); 33 | const week = getOrCreateWeek(event.block.timestamp); 34 | const month = getOrCreateMonth(event.block.timestamp); 35 | const total = getTotal(); 36 | 37 | day.createdAccounts = day.createdAccounts + 1; 38 | week.createdAccounts = week.createdAccounts + 1; 39 | month.createdAccounts = month.createdAccounts + 1; 40 | total.createdAccounts = total.createdAccounts + 1; 41 | account.creationDate = event.block.timestamp; 42 | account.hasRecovery = false; 43 | account.isRecovering = false; 44 | account.recoveryCount = 0; 45 | account.txCount = 0; 46 | 47 | day.save(); 48 | week.save(); 49 | month.save(); 50 | total.save(); 51 | account.save(); 52 | Account.create(event.params.accountAddress); 53 | } 54 | 55 | export function handleClaveAccountDeployed( 56 | event: ClaveAccountDeployedEvent, 57 | ): void { 58 | let account = ClaveAccount.load(event.params.accountAddress); 59 | const day = getOrCreateDay(event.block.timestamp); 60 | const week = getOrCreateWeek(event.block.timestamp); 61 | const month = getOrCreateMonth(event.block.timestamp); 62 | const total = getTotal(); 63 | day.deployedAccounts = day.deployedAccounts + 1; 64 | week.deployedAccounts = week.deployedAccounts + 1; 65 | month.deployedAccounts = month.deployedAccounts + 1; 66 | total.deployedAccounts = total.deployedAccounts + 1; 67 | if (!account) { 68 | account = new ClaveAccount(event.params.accountAddress); 69 | 70 | account.hasRecovery = false; 71 | account.isRecovering = false; 72 | account.recoveryCount = 0; 73 | account.txCount = 0; 74 | account.creationDate = ZERO; 75 | } 76 | 77 | account.implementation = Bytes.fromHexString( 78 | '0xf5bEDd0304ee359844541262aC349a6016A50bc6', 79 | ); 80 | account.deployDate = event.block.timestamp; 81 | 82 | day.save(); 83 | week.save(); 84 | month.save(); 85 | total.save(); 86 | account.save(); 87 | } 88 | -------------------------------------------------------------------------------- /subgraph/src/odos-router.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | 7 | /* eslint-disable @typescript-eslint/consistent-type-imports */ 8 | import { Swap as SwapEvent } from '../generated/OdosRouter/OdosRouter'; 9 | import { 10 | ClaveAccount, 11 | DailySwappedTo, 12 | InAppSwap, 13 | MonthlySwappedTo, 14 | WeeklySwappedTo, 15 | } from '../generated/schema'; 16 | import { 17 | ZERO, 18 | getOrCreateDay, 19 | getOrCreateMonth, 20 | getOrCreateWeek, 21 | } from './helpers'; 22 | 23 | export function handleSwap(event: SwapEvent): void { 24 | const account = ClaveAccount.load(event.params.sender); 25 | if (!account) { 26 | return; 27 | } 28 | 29 | const day = getOrCreateDay(event.block.timestamp); 30 | const week = getOrCreateWeek(event.block.timestamp); 31 | const month = getOrCreateMonth(event.block.timestamp); 32 | 33 | const tokenOutAddress = event.params.outputToken; 34 | const dailySwappedToId = day.id.concat(tokenOutAddress); 35 | let dailySwappedTo = DailySwappedTo.load(dailySwappedToId); 36 | if (!dailySwappedTo) { 37 | dailySwappedTo = new DailySwappedTo(dailySwappedToId); 38 | dailySwappedTo.day = day.id; 39 | dailySwappedTo.erc20 = tokenOutAddress; 40 | dailySwappedTo.amount = ZERO; 41 | } 42 | 43 | dailySwappedTo.amount = dailySwappedTo.amount.plus(event.params.amountOut); 44 | 45 | const weeklySwappedToId = week.id.concat(tokenOutAddress); 46 | let weeklySwappedTo = WeeklySwappedTo.load(weeklySwappedToId); 47 | if (!weeklySwappedTo) { 48 | weeklySwappedTo = new WeeklySwappedTo(weeklySwappedToId); 49 | weeklySwappedTo.week = week.id; 50 | weeklySwappedTo.erc20 = tokenOutAddress; 51 | weeklySwappedTo.amount = ZERO; 52 | } 53 | 54 | weeklySwappedTo.amount = weeklySwappedTo.amount.plus( 55 | event.params.amountOut, 56 | ); 57 | 58 | const monthlySwappedToId = month.id.concat(tokenOutAddress); 59 | let monthlySwappedTo = MonthlySwappedTo.load(monthlySwappedToId); 60 | if (!monthlySwappedTo) { 61 | monthlySwappedTo = new MonthlySwappedTo(monthlySwappedToId); 62 | monthlySwappedTo.month = month.id; 63 | monthlySwappedTo.erc20 = tokenOutAddress; 64 | monthlySwappedTo.amount = ZERO; 65 | } 66 | 67 | monthlySwappedTo.amount = monthlySwappedTo.amount.plus( 68 | event.params.amountOut, 69 | ); 70 | 71 | const inAppSwap = new InAppSwap( 72 | event.transaction.hash.concatI32(event.logIndex.toI32()), 73 | ); 74 | 75 | inAppSwap.account = account.id; 76 | inAppSwap.amountIn = event.params.inputAmount; 77 | inAppSwap.tokenIn = event.params.inputToken; 78 | inAppSwap.amountOut = event.params.amountOut; 79 | inAppSwap.tokenOut = event.params.outputToken; 80 | inAppSwap.date = event.block.timestamp; 81 | 82 | dailySwappedTo.save(); 83 | weeklySwappedTo.save(); 84 | monthlySwappedTo.save(); 85 | inAppSwap.save(); 86 | } 87 | -------------------------------------------------------------------------------- /contracts/test/MockHook.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | import {Transaction} from '@matterlabs/zksync-contracts/l2/system-contracts/libraries/TransactionHelper.sol'; 5 | import {IERC165} from '@openzeppelin/contracts/utils/introspection/IERC165.sol'; 6 | 7 | import {IValidationHook, IExecutionHook} from '../interfaces/IHook.sol'; 8 | 9 | interface IClave { 10 | function setHookData(bytes32 key, bytes calldata data) external; 11 | 12 | function getHookData(address hook, bytes32 key) external view returns (bytes memory); 13 | } 14 | 15 | contract MockValidationHook is IValidationHook { 16 | bytes constant context = 'test-context'; 17 | 18 | function init(bytes calldata) external override {} 19 | 20 | function disable() external override {} 21 | 22 | function validationHook( 23 | bytes32, 24 | Transaction calldata, 25 | bytes calldata hookData 26 | ) external pure override { 27 | bool shouldRevert = abi.decode(hookData, (bool)); 28 | require(!shouldRevert); 29 | } 30 | 31 | function setHookData(address account, bytes32 key, bytes calldata data) external { 32 | IClave(account).setHookData(key, data); 33 | } 34 | 35 | function getHookData( 36 | address account, 37 | address hook, 38 | bytes32 key 39 | ) external view returns (bytes memory) { 40 | return IClave(account).getHookData(hook, key); 41 | } 42 | 43 | function isInited(address account) external view returns (bool) { 44 | return true; 45 | } 46 | 47 | function supportsInterface(bytes4 interfaceId) external pure override returns (bool) { 48 | return 49 | interfaceId == type(IValidationHook).interfaceId || 50 | interfaceId == type(IERC165).interfaceId; 51 | } 52 | } 53 | 54 | contract MockExecutionHook is IExecutionHook { 55 | bytes constant context = 'test-context'; 56 | 57 | function init(bytes calldata) external override {} 58 | 59 | function disable() external override {} 60 | 61 | function preExecutionHook( 62 | Transaction calldata transaction 63 | ) external pure override returns (bytes memory context_) { 64 | require(transaction.value != 5); 65 | context_ = context; 66 | } 67 | 68 | function postExecutionHook(bytes memory context_) external pure override { 69 | require(keccak256(context_) == keccak256(context)); 70 | } 71 | 72 | function setHookData(address account, bytes32 key, bytes calldata data) external { 73 | IClave(account).setHookData(key, data); 74 | } 75 | 76 | function getHookData( 77 | address account, 78 | address hook, 79 | bytes32 key 80 | ) external view returns (bytes memory) { 81 | return IClave(account).getHookData(hook, key); 82 | } 83 | 84 | function isInited(address account) external view returns (bool) { 85 | return true; 86 | } 87 | 88 | function supportsInterface(bytes4 interfaceId) external pure override returns (bool) { 89 | return 90 | interfaceId == type(IExecutionHook).interfaceId || 91 | interfaceId == type(IERC165).interfaceId; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /test/utils/managers/validatormanager.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | import type { ec } from 'elliptic'; 7 | import type { Contract, Provider } from 'zksync-ethers'; 8 | import { utils } from 'zksync-ethers'; 9 | 10 | import { prepareTeeTx } from '../transactions'; 11 | 12 | export async function addR1Validator( 13 | provider: Provider, 14 | account: Contract, 15 | validator: Contract, 16 | newR1Validator: Contract, 17 | keyPair: ec.KeyPair, 18 | ): Promise { 19 | const addValidatorTx = await account.r1AddValidator.populateTransaction( 20 | await newR1Validator.getAddress(), 21 | ); 22 | const tx = await prepareTeeTx( 23 | provider, 24 | account, 25 | addValidatorTx, 26 | await validator.getAddress(), 27 | keyPair, 28 | ); 29 | const txReceipt = await provider.broadcastTransaction( 30 | utils.serializeEip712(tx), 31 | ); 32 | await txReceipt.wait(); 33 | } 34 | 35 | export async function removeR1Validator( 36 | provider: Provider, 37 | account: Contract, 38 | validator: Contract, 39 | removingR1Validator: Contract, 40 | keyPair: ec.KeyPair, 41 | ): Promise { 42 | const removeValidatorTx = 43 | await account.r1RemoveValidator.populateTransaction( 44 | removingR1Validator, 45 | ); 46 | const tx = await prepareTeeTx( 47 | provider, 48 | account, 49 | removeValidatorTx, 50 | await validator.getAddress(), 51 | keyPair, 52 | ); 53 | const txReceipt = await provider.broadcastTransaction( 54 | utils.serializeEip712(tx), 55 | ); 56 | await txReceipt.wait(); 57 | } 58 | 59 | export async function addK1Validator( 60 | provider: Provider, 61 | account: Contract, 62 | validator: Contract, 63 | newK1Validator: Contract, 64 | keyPair: ec.KeyPair, 65 | ): Promise { 66 | const addValidatorTx = await account.k1AddValidator.populateTransaction( 67 | await newK1Validator.getAddress(), 68 | ); 69 | const tx = await prepareTeeTx( 70 | provider, 71 | account, 72 | addValidatorTx, 73 | await validator.getAddress(), 74 | keyPair, 75 | ); 76 | const txReceipt = await provider.broadcastTransaction( 77 | utils.serializeEip712(tx), 78 | ); 79 | await txReceipt.wait(); 80 | } 81 | 82 | export async function removeK1Validator( 83 | provider: Provider, 84 | account: Contract, 85 | validator: Contract, 86 | removingK1Validator: Contract, 87 | keyPair: ec.KeyPair, 88 | ): Promise { 89 | const removeValidatorTx = 90 | await account.k1RemoveValidator.populateTransaction( 91 | removingK1Validator, 92 | ); 93 | const tx = await prepareTeeTx( 94 | provider, 95 | account, 96 | removeValidatorTx, 97 | await validator.getAddress(), 98 | keyPair, 99 | ); 100 | const txReceipt = await provider.broadcastTransaction( 101 | utils.serializeEip712(tx), 102 | ); 103 | await txReceipt.wait(); 104 | } 105 | -------------------------------------------------------------------------------- /test/accounts/managers/upgrademanager.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | import { assert, expect } from 'chai'; 7 | import type { ec } from 'elliptic'; 8 | import * as hre from 'hardhat'; 9 | import type { Contract, Wallet } from 'zksync-ethers'; 10 | import { Provider } from 'zksync-ethers'; 11 | 12 | import { LOCAL_RICH_WALLETS, getWallet } from '../../../deploy/utils'; 13 | import { ClaveDeployer } from '../../utils/deployer'; 14 | import { fixture } from '../../utils/fixture'; 15 | import { upgradeTx } from '../../utils/managers/upgrademanager'; 16 | import { VALIDATORS } from '../../utils/names'; 17 | 18 | describe('Clave Contracts - Manager tests', () => { 19 | let deployer: ClaveDeployer; 20 | let provider: Provider; 21 | let richWallet: Wallet; 22 | let teeValidator: Contract; 23 | let account: Contract; 24 | let keyPair: ec.KeyPair; 25 | 26 | before(async () => { 27 | richWallet = getWallet(hre, LOCAL_RICH_WALLETS[0].privateKey); 28 | deployer = new ClaveDeployer(hre, richWallet); 29 | provider = new Provider(hre.network.config.url, undefined, { 30 | cacheTimeout: -1, 31 | }); 32 | 33 | [, , , , teeValidator, account, keyPair] = await fixture( 34 | deployer, 35 | VALIDATORS.TEE, 36 | ); 37 | 38 | const accountAddress = await account.getAddress(); 39 | 40 | await deployer.fund(10000, accountAddress); 41 | }); 42 | 43 | describe('Upgrade Manager', () => { 44 | let mockImplementation: Contract; 45 | 46 | before(async () => { 47 | mockImplementation = await deployer.deployCustomContract( 48 | 'MockImplementation', 49 | [], 50 | ); 51 | }); 52 | 53 | it('should revert to a new implementation with unauthorized msg.sender', async () => { 54 | await expect( 55 | account.upgradeTo(await mockImplementation.getAddress()), 56 | ).to.be.revertedWithCustomError(account, 'NOT_FROM_SELF'); 57 | }); 58 | 59 | it('should upgrade to a new implementation', async () => { 60 | expect(await account.implementation()).not.to.be.eq( 61 | await mockImplementation.getAddress(), 62 | ); 63 | 64 | await upgradeTx( 65 | provider, 66 | account, 67 | teeValidator, 68 | mockImplementation, 69 | keyPair, 70 | ); 71 | 72 | expect(await account.implementation()).to.eq( 73 | await mockImplementation.getAddress(), 74 | ); 75 | }); 76 | 77 | it('should revert upgrading to the same implementation', async () => { 78 | expect(await account.implementation()).to.be.eq( 79 | await mockImplementation.getAddress(), 80 | ); 81 | 82 | try { 83 | await upgradeTx( 84 | provider, 85 | account, 86 | teeValidator, 87 | mockImplementation, 88 | keyPair, 89 | ); 90 | assert(false, 'Should revert'); 91 | } catch (err) {} 92 | }); 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /contracts/modules/recovery/EmailRecoveryModule.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {IModule} from '../../interfaces/IModule.sol'; 5 | import {IEmailRecoveryModule} from '../../interfaces/IEmailRecoveryModule.sol'; 6 | import {IClaveAccount} from '../../interfaces/IClave.sol'; 7 | import {Errors} from '../../libraries/Errors.sol'; 8 | import {IERC165} from '@openzeppelin/contracts/utils/introspection/IERC165.sol'; 9 | import {EmailRecoveryManager, GuardianManager} from '../../EmailRecoveryManager.sol'; 10 | 11 | contract EmailRecoveryModule is EmailRecoveryManager, IModule, IEmailRecoveryModule { 12 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ 13 | /* CONSTANTS & STORAGE */ 14 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ 15 | 16 | /** 17 | * Account address to isInited 18 | */ 19 | mapping(address account => bool) internal inited; 20 | 21 | /** 22 | * @notice Emitted when a recovery is executed 23 | * @param account address - Recovered account 24 | * @param newOwner bytes - New owner of the account 25 | */ 26 | event RecoveryExecuted(address indexed account, bytes newOwner); 27 | 28 | constructor( 29 | address verifier, 30 | address dkimRegistry, 31 | address emailAuthImpl, 32 | address subjectHandler, 33 | bytes32 _proxyBytecodeHash 34 | ) EmailRecoveryManager(verifier, dkimRegistry, emailAuthImpl, subjectHandler) { 35 | proxyBytecodeHash = _proxyBytecodeHash; 36 | } 37 | 38 | function init(bytes calldata initData) external override { 39 | if (isInited(msg.sender)) { 40 | revert Errors.ALREADY_INITED(); 41 | } 42 | 43 | if (!IClaveAccount(msg.sender).isModule(address(this))) { 44 | revert Errors.MODULE_NOT_ADDED_CORRECTLY(); 45 | } 46 | 47 | ( 48 | address[] memory guardians, 49 | uint256[] memory weights, 50 | uint256 threshold, 51 | uint256 delay, 52 | uint256 expiry 53 | ) = abi.decode(initData, (address[], uint256[], uint256, uint256, uint256)); 54 | 55 | inited[msg.sender] = true; 56 | 57 | configureRecovery(guardians, weights, threshold, delay, expiry); 58 | 59 | emit Inited(msg.sender); 60 | } 61 | 62 | function disable() external override { 63 | inited[msg.sender] = false; 64 | 65 | deInitRecoveryModule(); 66 | 67 | emit Disabled(msg.sender); 68 | } 69 | 70 | function isInited(address account) public view override returns (bool) { 71 | return inited[account]; 72 | } 73 | 74 | function canStartRecoveryRequest(address account) external view returns (bool) { 75 | GuardianConfig memory guardianConfig = getGuardianConfig(account); 76 | 77 | return guardianConfig.acceptedWeight >= guardianConfig.threshold; 78 | } 79 | 80 | function recover(address account, bytes calldata newOwner) internal override { 81 | IClaveAccount(account).resetOwners(newOwner); 82 | 83 | emit RecoveryExecuted(account, newOwner); 84 | } 85 | 86 | /// @inheritdoc IERC165 87 | function supportsInterface(bytes4 interfaceId) external pure override returns (bool) { 88 | return interfaceId == type(IModule).interfaceId || interfaceId == type(IERC165).interfaceId; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /contracts/interfaces/IValidatorManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | /** 5 | * @title Manager contract for validators 6 | * @author https://getclave.io 7 | */ 8 | interface IValidatorManager { 9 | /** 10 | * @notice Event emitted when a r1 validator is added 11 | * @param validator address - Address of the added r1 validator 12 | */ 13 | event R1AddValidator(address indexed validator); 14 | 15 | /** 16 | * @notice Event emitted when a k1 validator is added 17 | * @param validator address - Address of the added k1 validator 18 | */ 19 | event K1AddValidator(address indexed validator); 20 | 21 | /** 22 | * @notice Event emitted when a r1 validator is removed 23 | * @param validator address - Address of the removed r1 validator 24 | */ 25 | event R1RemoveValidator(address indexed validator); 26 | 27 | /** 28 | * @notice Event emitted when a k1 validator is removed 29 | * @param validator address - Address of the removed k1 validator 30 | */ 31 | event K1RemoveValidator(address indexed validator); 32 | 33 | /** 34 | * @notice Adds a validator to the list of r1 validators 35 | * @dev Can only be called by self or a whitelisted module 36 | * @param validator address - Address of the r1 validator to add 37 | */ 38 | function r1AddValidator(address validator) external; 39 | 40 | /** 41 | * @notice Adds a validator to the list of k1 validators 42 | * @dev Can only be called by self or a whitelisted module 43 | * @param validator address - Address of the k1 validator to add 44 | */ 45 | function k1AddValidator(address validator) external; 46 | 47 | /** 48 | * @notice Removes a validator from the list of r1 validators 49 | * @dev Can only be called by self or a whitelisted module 50 | * @dev Can not remove the last validator 51 | * @param validator address - Address of the validator to remove 52 | */ 53 | function r1RemoveValidator(address validator) external; 54 | 55 | /** 56 | * @notice Removes a validator from the list of k1 validators 57 | * @dev Can only be called by self or a whitelisted module 58 | * @param validator address - Address of the validator to remove 59 | */ 60 | function k1RemoveValidator(address validator) external; 61 | 62 | /** 63 | * @notice Checks if an address is in the r1 validator list 64 | * @param validator address -Address of the validator to check 65 | * @return True if the address is a validator, false otherwise 66 | */ 67 | function r1IsValidator(address validator) external view returns (bool); 68 | 69 | /** 70 | * @notice Checks if an address is in the k1 validator list 71 | * @param validator address - Address of the validator to check 72 | * @return True if the address is a validator, false otherwise 73 | */ 74 | function k1IsValidator(address validator) external view returns (bool); 75 | 76 | /** 77 | * @notice Returns the list of r1 validators 78 | * @return validatorList address[] memory - Array of r1 validator addresses 79 | */ 80 | function r1ListValidators() external view returns (address[] memory validatorList); 81 | 82 | /** 83 | * @notice Returns the list of k1 validators 84 | * @return validatorList address[] memory - Array of k1 validator addresses 85 | */ 86 | function k1ListValidators() external view returns (address[] memory validatorList); 87 | } 88 | -------------------------------------------------------------------------------- /subgraph/src/account-factory.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/consistent-type-imports */ 2 | 3 | /** 4 | * Copyright Clave - All Rights Reserved 5 | * Unauthorized copying of this file, via any medium is strictly prohibited 6 | * Proprietary and confidential 7 | */ 8 | import { 9 | Address, 10 | BigInt, 11 | Bytes, 12 | ethereum, 13 | json, 14 | } from '@graphprotocol/graph-ts'; 15 | 16 | // eslint-disable-next-line @typescript-eslint/consistent-type-imports 17 | import { NewClaveAccount as NewClaveAccountEvent } from '../generated/AccountFactory/AccountFactory'; 18 | import { ClaveAccount } from '../generated/schema'; 19 | import { Account } from '../generated/templates'; 20 | import { wallets } from '../wallets'; 21 | import { 22 | ZERO, 23 | getOrCreateDay, 24 | getOrCreateMonth, 25 | getOrCreateWeek, 26 | getTotal, 27 | } from './helpers'; 28 | 29 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 30 | export function handleOnce(_block: ethereum.Block): void { 31 | const walletsJson = json.fromString(wallets).toArray(); 32 | walletsJson.forEach((element) => { 33 | const accountAddress = element.toObject().entries[0].value.toString(); 34 | const createdAt = element.toObject().entries[1].value.toString(); 35 | const createdAtDate = BigInt.fromI64( 36 | Date.parse(createdAt).getTime(), 37 | ).div(BigInt.fromU32(1000)); 38 | const account = new ClaveAccount(Bytes.fromHexString(accountAddress)); 39 | const day = getOrCreateDay(createdAtDate); 40 | const week = getOrCreateWeek(createdAtDate); 41 | const month = getOrCreateMonth(createdAtDate); 42 | const total = getTotal(); 43 | 44 | day.createdAccounts = day.createdAccounts + 1; 45 | week.createdAccounts = week.createdAccounts + 1; 46 | month.createdAccounts = month.createdAccounts + 1; 47 | total.createdAccounts = total.createdAccounts + 1; 48 | account.creationDate = createdAtDate; 49 | account.hasRecovery = false; 50 | account.isRecovering = false; 51 | account.recoveryCount = 0; 52 | account.txCount = 0; 53 | 54 | day.save(); 55 | week.save(); 56 | month.save(); 57 | total.save(); 58 | account.save(); 59 | Account.create(Address.fromBytes(Bytes.fromHexString(accountAddress))); 60 | }); 61 | } 62 | 63 | export function handleNewClaveAccount(event: NewClaveAccountEvent): void { 64 | let account = ClaveAccount.load(event.params.accountAddress); 65 | const day = getOrCreateDay(event.block.timestamp); 66 | const week = getOrCreateWeek(event.block.timestamp); 67 | const month = getOrCreateMonth(event.block.timestamp); 68 | const total = getTotal(); 69 | day.deployedAccounts = day.deployedAccounts + 1; 70 | week.deployedAccounts = week.deployedAccounts + 1; 71 | month.deployedAccounts = month.deployedAccounts + 1; 72 | total.deployedAccounts = total.deployedAccounts + 1; 73 | if (!account) { 74 | account = new ClaveAccount(event.params.accountAddress); 75 | 76 | account.hasRecovery = false; 77 | account.isRecovering = false; 78 | account.recoveryCount = 0; 79 | account.txCount = 0; 80 | account.creationDate = ZERO; 81 | } 82 | account.implementation = Bytes.fromHexString( 83 | '0xdd4dD37B22Fc16DBFF3daB6Ecd681798c459f275', 84 | ); 85 | account.deployDate = event.block.timestamp; 86 | 87 | day.save(); 88 | week.save(); 89 | month.save(); 90 | total.save(); 91 | account.save(); 92 | } 93 | -------------------------------------------------------------------------------- /contracts/ClaveRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol'; 5 | 6 | import {Errors} from './libraries/Errors.sol'; 7 | import {IClaveRegistry} from './interfaces/IClaveRegistry.sol'; 8 | 9 | contract ClaveRegistry is Ownable, IClaveRegistry { 10 | mapping(address => bool) public isFactory; 11 | // Mapping of Clave accounts 12 | mapping(address => bool) public isClave; 13 | 14 | /** 15 | * @notice Event emmited when a factory contract is set 16 | * @param factory address - Address of the factory contract 17 | */ 18 | event FactorySet(address indexed factory); 19 | 20 | /** 21 | * @notice Event emmited when a factory contract is unset 22 | * @param factory address - Address of the factory contract 23 | */ 24 | event FactoryUnset(address indexed factory); 25 | 26 | // Constructor function of the contracts 27 | constructor() Ownable() {} 28 | 29 | /** 30 | * @notice Registers an account as a Clave account 31 | * @dev Can only be called by the factory or owner 32 | * @param account address - Address of the account to register 33 | */ 34 | function register(address account) external override { 35 | if (!isFactory[msg.sender] && msg.sender != owner()) { 36 | revert Errors.NOT_FROM_FACTORY(); 37 | } 38 | 39 | isClave[account] = true; 40 | } 41 | 42 | /** 43 | * @notice Registers multiple accounts as Clave accounts 44 | * @dev Can only be called by the factory or owner 45 | * @param accounts address[] - Array of addresses to register 46 | */ 47 | function registerMultiple(address[] calldata accounts) external { 48 | if (!isFactory[msg.sender] && msg.sender != owner()) { 49 | revert Errors.NOT_FROM_FACTORY(); 50 | } 51 | 52 | for (uint256 i = 0; i < accounts.length; i++) { 53 | isClave[accounts[i]] = true; 54 | } 55 | } 56 | 57 | /** 58 | * @notice Unregisters an account as a Clave account 59 | * @dev Can only be called by the factory or owner 60 | * @param account address - Address of the account to unregister 61 | */ 62 | function unregister(address account) external { 63 | if (!isFactory[msg.sender] && msg.sender != owner()) { 64 | revert Errors.NOT_FROM_FACTORY(); 65 | } 66 | 67 | isClave[account] = false; 68 | } 69 | 70 | /** 71 | * @notice Unregisters multiple accounts as Clave accounts 72 | * @dev Can only be called by the factory or owner 73 | * @param accounts address[] - Array of addresses to unregister 74 | */ 75 | function unregisterMultiple(address[] calldata accounts) external { 76 | if (!isFactory[msg.sender] && msg.sender != owner()) { 77 | revert Errors.NOT_FROM_FACTORY(); 78 | } 79 | 80 | for (uint256 i = 0; i < accounts.length; i++) { 81 | isClave[accounts[i]] = false; 82 | } 83 | } 84 | 85 | /** 86 | * @notice Sets a new factory contract 87 | * @dev Can only be called by the owner 88 | * @param factory_ address - Address of the new factory 89 | */ 90 | function setFactory(address factory_) external onlyOwner { 91 | isFactory[factory_] = true; 92 | 93 | emit FactorySet(factory_); 94 | } 95 | 96 | /** 97 | * @notice Unsets a factory contract 98 | * @dev Can only be called by the owner 99 | * @param factory_ address - Address of the factory 100 | */ 101 | function unsetFactory(address factory_) external onlyOwner { 102 | isFactory[factory_] = false; 103 | 104 | emit FactoryUnset(factory_); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /test/utils/managers/ownermanager.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | import type { ec } from 'elliptic'; 7 | import type { Contract, Provider } from 'zksync-ethers'; 8 | import { utils } from 'zksync-ethers'; 9 | 10 | import { prepareTeeTx } from '../transactions'; 11 | 12 | export async function addR1Key( 13 | provider: Provider, 14 | account: Contract, 15 | validator: Contract, 16 | newPublicKey: string, 17 | keyPair: ec.KeyPair, 18 | ): Promise { 19 | const addOwnerTx = await account.r1AddOwner.populateTransaction( 20 | newPublicKey, 21 | ); 22 | const tx = await prepareTeeTx( 23 | provider, 24 | account, 25 | addOwnerTx, 26 | await validator.getAddress(), 27 | keyPair, 28 | ); 29 | 30 | const txReceipt = await provider.broadcastTransaction( 31 | utils.serializeEip712(tx), 32 | ); 33 | await txReceipt.wait(); 34 | } 35 | 36 | export async function addK1Key( 37 | provider: Provider, 38 | account: Contract, 39 | validator: Contract, 40 | newK1Address: string, 41 | keyPair: ec.KeyPair, 42 | ): Promise { 43 | const addOwnerTx = await account.k1AddOwner.populateTransaction( 44 | newK1Address, 45 | ); 46 | const tx = await prepareTeeTx( 47 | provider, 48 | account, 49 | addOwnerTx, 50 | await validator.getAddress(), 51 | keyPair, 52 | ); 53 | const txReceipt = await provider.broadcastTransaction( 54 | utils.serializeEip712(tx), 55 | ); 56 | await txReceipt.wait(); 57 | } 58 | 59 | export async function removeR1Key( 60 | provider: Provider, 61 | account: Contract, 62 | validator: Contract, 63 | removingPublicKey: string, 64 | keyPair: ec.KeyPair, 65 | ): Promise { 66 | const removeOwnerTxData = await account.r1RemoveOwner.populateTransaction( 67 | removingPublicKey, 68 | ); 69 | const tx = await prepareTeeTx( 70 | provider, 71 | account, 72 | removeOwnerTxData, 73 | await validator.getAddress(), 74 | keyPair, 75 | ); 76 | 77 | const txReceipt = await provider.broadcastTransaction( 78 | utils.serializeEip712(tx), 79 | ); 80 | await txReceipt.wait(); 81 | } 82 | 83 | export async function removeK1Key( 84 | provider: Provider, 85 | account: Contract, 86 | validator: Contract, 87 | removingAddress: string, 88 | keyPair: ec.KeyPair, 89 | ): Promise { 90 | const removeOwnerTx = await account.k1RemoveOwner.populateTransaction( 91 | removingAddress, 92 | ); 93 | const tx = await prepareTeeTx( 94 | provider, 95 | account, 96 | removeOwnerTx, 97 | await validator.getAddress(), 98 | keyPair, 99 | ); 100 | const txReceipt = await provider.broadcastTransaction( 101 | utils.serializeEip712(tx), 102 | ); 103 | await txReceipt.wait(); 104 | } 105 | 106 | export async function resetOwners( 107 | provider: Provider, 108 | account: Contract, 109 | validator: Contract, 110 | newPublicKey: string, 111 | keyPair: ec.KeyPair, 112 | ): Promise { 113 | const resetOwnersTx = await account.resetOwners.populateTransaction( 114 | newPublicKey, 115 | ); 116 | const tx = await prepareTeeTx( 117 | provider, 118 | account, 119 | resetOwnersTx, 120 | await validator.getAddress(), 121 | keyPair, 122 | ); 123 | 124 | const txReceipt = await provider.broadcastTransaction( 125 | utils.serializeEip712(tx), 126 | ); 127 | await txReceipt.wait(); 128 | } 129 | -------------------------------------------------------------------------------- /subgraph/abis/AccountFactory.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "internalType": "address", 6 | "name": "implementation", 7 | "type": "address" 8 | }, 9 | { "internalType": "address", "name": "registry", "type": "address" }, 10 | { 11 | "internalType": "bytes32", 12 | "name": "_proxyBytecodeHash", 13 | "type": "bytes32" 14 | }, 15 | { "internalType": "address", "name": "deployer", "type": "address" } 16 | ], 17 | "stateMutability": "nonpayable", 18 | "type": "constructor" 19 | }, 20 | { "inputs": [], "name": "DEPLOYMENT_FAILED", "type": "error" }, 21 | { "inputs": [], "name": "INITIALIZATION_FAILED", "type": "error" }, 22 | { "inputs": [], "name": "NOT_FROM_DEPLOYER", "type": "error" }, 23 | { 24 | "anonymous": false, 25 | "inputs": [ 26 | { 27 | "indexed": true, 28 | "internalType": "address", 29 | "name": "accountAddress", 30 | "type": "address" 31 | } 32 | ], 33 | "name": "NewClaveAccount", 34 | "type": "event" 35 | }, 36 | { 37 | "anonymous": false, 38 | "inputs": [ 39 | { 40 | "indexed": true, 41 | "internalType": "address", 42 | "name": "previousOwner", 43 | "type": "address" 44 | }, 45 | { 46 | "indexed": true, 47 | "internalType": "address", 48 | "name": "newOwner", 49 | "type": "address" 50 | } 51 | ], 52 | "name": "OwnershipTransferred", 53 | "type": "event" 54 | }, 55 | { 56 | "inputs": [], 57 | "name": "VERSION", 58 | "outputs": [{ "internalType": "string", "name": "", "type": "string" }], 59 | "stateMutability": "view", 60 | "type": "function" 61 | }, 62 | { 63 | "inputs": [ 64 | { "internalType": "address", "name": "newDeployer", "type": "address" } 65 | ], 66 | "name": "changeDeployer", 67 | "outputs": [], 68 | "stateMutability": "nonpayable", 69 | "type": "function" 70 | }, 71 | { 72 | "inputs": [ 73 | { "internalType": "bytes32", "name": "salt", "type": "bytes32" }, 74 | { "internalType": "bytes", "name": "initializer", "type": "bytes" } 75 | ], 76 | "name": "deployAccount", 77 | "outputs": [ 78 | { "internalType": "address", "name": "accountAddress", "type": "address" } 79 | ], 80 | "stateMutability": "nonpayable", 81 | "type": "function" 82 | }, 83 | { 84 | "inputs": [ 85 | { "internalType": "bytes32", "name": "salt", "type": "bytes32" } 86 | ], 87 | "name": "getAddressForSalt", 88 | "outputs": [ 89 | { "internalType": "address", "name": "accountAddress", "type": "address" } 90 | ], 91 | "stateMutability": "view", 92 | "type": "function" 93 | }, 94 | { 95 | "inputs": [], 96 | "name": "getImplementation", 97 | "outputs": [{ "internalType": "address", "name": "", "type": "address" }], 98 | "stateMutability": "view", 99 | "type": "function" 100 | }, 101 | { 102 | "inputs": [], 103 | "name": "owner", 104 | "outputs": [{ "internalType": "address", "name": "", "type": "address" }], 105 | "stateMutability": "view", 106 | "type": "function" 107 | }, 108 | { 109 | "inputs": [], 110 | "name": "proxyBytecodeHash", 111 | "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], 112 | "stateMutability": "view", 113 | "type": "function" 114 | }, 115 | { 116 | "inputs": [], 117 | "name": "renounceOwnership", 118 | "outputs": [], 119 | "stateMutability": "nonpayable", 120 | "type": "function" 121 | }, 122 | { 123 | "inputs": [ 124 | { "internalType": "address", "name": "newOwner", "type": "address" } 125 | ], 126 | "name": "transferOwnership", 127 | "outputs": [], 128 | "stateMutability": "nonpayable", 129 | "type": "function" 130 | } 131 | ] 132 | -------------------------------------------------------------------------------- /contracts/interfaces/IOwnerManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | /** 5 | * @title Interface of the manager contract for owners 6 | * @author https://getclave.io 7 | */ 8 | interface IOwnerManager { 9 | /** 10 | * @notice Event emitted when a r1 owner is added 11 | * @param pubKey bytes - r1 owner that has been added 12 | */ 13 | event R1AddOwner(bytes pubKey); 14 | 15 | /** 16 | * @notice Event emitted when a k1 owner is added 17 | * @param addr address - k1 owner that has been added 18 | */ 19 | event K1AddOwner(address indexed addr); 20 | 21 | /** 22 | * @notice Event emitted when a r1 owner is removed 23 | * @param pubKey bytes - r1 owner that has been removed 24 | */ 25 | event R1RemoveOwner(bytes pubKey); 26 | 27 | /** 28 | * @notice Event emitted when a k1 owner is removed 29 | * @param addr address - k1 owner that has been removed 30 | */ 31 | event K1RemoveOwner(address indexed addr); 32 | 33 | /** 34 | * @notice Event emitted when all owners are cleared 35 | */ 36 | event ResetOwners(); 37 | 38 | /** 39 | * @notice Adds a r1 owner to the list of r1 owners 40 | * @dev Can only be called by self or a whitelisted module 41 | * @dev Public Key length must be 64 bytes 42 | * @param pubKey bytes calldata - Public key to add to the list of r1 owners 43 | */ 44 | function r1AddOwner(bytes calldata pubKey) external; 45 | 46 | /** 47 | * @notice Adds a k1 owner to the list of k1 owners 48 | * @dev Can only be called by self or a whitelisted module 49 | * @dev Address can not be the zero address 50 | * @param addr address - Address to add to the list of k1 owners 51 | */ 52 | function k1AddOwner(address addr) external; 53 | 54 | /** 55 | * @notice Removes a r1 owner from the list of r1 owners 56 | * @dev Can only be called by self or a whitelisted module 57 | * @dev Can not remove the last r1 owner 58 | * @param pubKey bytes calldata - Public key to remove from the list of r1 owners 59 | */ 60 | function r1RemoveOwner(bytes calldata pubKey) external; 61 | 62 | /** 63 | * @notice Removes a k1 owner from the list of k1 owners 64 | * @dev Can only be called by self or a whitelisted module 65 | * @param addr address - Address to remove from the list of k1 owners 66 | */ 67 | function k1RemoveOwner(address addr) external; 68 | 69 | /** 70 | * @notice Clears both r1 owners and k1 owners and adds an r1 owner 71 | * @dev Can only be called by self or a whitelisted module 72 | * @dev Public Key length must be 64 bytes 73 | * @param pubKey bytes calldata - new r1 owner to add 74 | */ 75 | function resetOwners(bytes calldata pubKey) external; 76 | 77 | /** 78 | * @notice Checks if a public key is in the list of r1 owners 79 | * @param pubKey bytes calldata - Public key to check 80 | * @return bool - True if the public key is in the list, false otherwise 81 | */ 82 | function r1IsOwner(bytes calldata pubKey) external view returns (bool); 83 | 84 | /** 85 | * @notice Checks if an address is in the list of k1 owners 86 | * @param addr address - Address to check 87 | * @return bool - True if the address is in the list, false otherwise 88 | */ 89 | function k1IsOwner(address addr) external view returns (bool); 90 | 91 | /** 92 | * @notice Returns the list of r1 owners 93 | * @return r1OwnerList bytes[] memory - Array of r1 owner public keys 94 | */ 95 | function r1ListOwners() external view returns (bytes[] memory r1OwnerList); 96 | 97 | /** 98 | * @notice Returns the list of k1 owners 99 | * @return k1OwnerList address[] memory - Array of k1 owner addresses 100 | */ 101 | function k1ListOwners() external view returns (address[] memory k1OwnerList); 102 | } 103 | -------------------------------------------------------------------------------- /subgraph/src/zero-usdt-pool.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | 7 | /* eslint-disable @typescript-eslint/consistent-type-imports */ 8 | import { ClaveAccount } from '../generated/schema'; 9 | import { 10 | Supply as SupplyEvent, 11 | Withdraw as WithdrawEvent, 12 | } from '../generated/zeroUsdtPool/zeroUsdtPool'; 13 | import { 14 | ZERO, 15 | getOrCreateDailyEarnFlow, 16 | getOrCreateDay, 17 | getOrCreateEarnPosition, 18 | getOrCreateMonth, 19 | getOrCreateMonthlyEarnFlow, 20 | getOrCreateWeek, 21 | getOrCreateWeeklyEarnFlow, 22 | } from './helpers'; 23 | 24 | const protocol = 'ZeroLend'; 25 | 26 | export function handleSupply(event: SupplyEvent): void { 27 | const account = ClaveAccount.load(event.params.onBehalfOf); 28 | if (!account) { 29 | return; 30 | } 31 | 32 | const pool = event.address; 33 | const token = event.params.reserve; 34 | const amount = event.params.amount; 35 | 36 | const day = getOrCreateDay(event.block.timestamp); 37 | const week = getOrCreateWeek(event.block.timestamp); 38 | const month = getOrCreateMonth(event.block.timestamp); 39 | 40 | const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); 41 | const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); 42 | const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); 43 | const earnPosition = getOrCreateEarnPosition( 44 | account, 45 | pool, 46 | token, 47 | protocol, 48 | ); 49 | 50 | dailyEarnFlow.amountIn = dailyEarnFlow.amountIn.plus(amount); 51 | weeklyEarnFlow.amountIn = weeklyEarnFlow.amountIn.plus(amount); 52 | monthlyEarnFlow.amountIn = monthlyEarnFlow.amountIn.plus(amount); 53 | earnPosition.invested = earnPosition.invested.plus(amount); 54 | 55 | dailyEarnFlow.save(); 56 | weeklyEarnFlow.save(); 57 | monthlyEarnFlow.save(); 58 | earnPosition.save(); 59 | } 60 | 61 | export function handleWithdraw(event: WithdrawEvent): void { 62 | const account = ClaveAccount.load(event.params.to); 63 | if (!account) { 64 | return; 65 | } 66 | 67 | const pool = event.address; 68 | const token = event.params.reserve; 69 | const amount = event.params.amount; 70 | 71 | const day = getOrCreateDay(event.block.timestamp); 72 | const week = getOrCreateWeek(event.block.timestamp); 73 | const month = getOrCreateMonth(event.block.timestamp); 74 | 75 | const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); 76 | const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); 77 | const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); 78 | const earnPosition = getOrCreateEarnPosition( 79 | account, 80 | pool, 81 | token, 82 | protocol, 83 | ); 84 | 85 | const invested = earnPosition.invested; 86 | 87 | if (amount.gt(invested)) { 88 | const compoundGain = amount.minus(invested); 89 | dailyEarnFlow.amountOut = dailyEarnFlow.amountOut.plus(invested); 90 | dailyEarnFlow.claimedGain = 91 | dailyEarnFlow.claimedGain.plus(compoundGain); 92 | weeklyEarnFlow.amountOut = weeklyEarnFlow.amountOut.plus(invested); 93 | weeklyEarnFlow.claimedGain = 94 | weeklyEarnFlow.claimedGain.plus(compoundGain); 95 | monthlyEarnFlow.amountOut = monthlyEarnFlow.amountOut.plus(invested); 96 | monthlyEarnFlow.claimedGain = 97 | monthlyEarnFlow.claimedGain.plus(compoundGain); 98 | earnPosition.invested = ZERO; 99 | earnPosition.compoundGain = 100 | earnPosition.compoundGain.plus(compoundGain); 101 | } else { 102 | dailyEarnFlow.amountOut = dailyEarnFlow.amountOut.plus(amount); 103 | weeklyEarnFlow.amountOut = weeklyEarnFlow.amountOut.plus(amount); 104 | monthlyEarnFlow.amountOut = monthlyEarnFlow.amountOut.plus(amount); 105 | earnPosition.invested = invested.minus(amount); 106 | } 107 | 108 | dailyEarnFlow.save(); 109 | weeklyEarnFlow.save(); 110 | monthlyEarnFlow.save(); 111 | earnPosition.save(); 112 | } 113 | -------------------------------------------------------------------------------- /contracts/managers/ModuleManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | import {ERC165Checker} from '@openzeppelin/contracts/utils/introspection/ERC165Checker.sol'; 5 | import {ExcessivelySafeCall} from '@nomad-xyz/excessively-safe-call/src/ExcessivelySafeCall.sol'; 6 | 7 | import {ClaveStorage} from '../libraries/ClaveStorage.sol'; 8 | import {Auth} from '../auth/Auth.sol'; 9 | import {AddressLinkedList} from '../libraries/LinkedList.sol'; 10 | import {Errors} from '../libraries/Errors.sol'; 11 | import {IModule} from '../interfaces/IModule.sol'; 12 | import {IInitable} from '../interfaces/IInitable.sol'; 13 | import {IClaveAccount} from '../interfaces/IClave.sol'; 14 | import {IModuleManager} from '../interfaces/IModuleManager.sol'; 15 | 16 | /** 17 | * @title Manager contract for modules 18 | * @notice Abstract contract for managing the enabled modules of the account 19 | * @dev Module addresses are stored in a linked list 20 | * @author https://getclave.io 21 | */ 22 | abstract contract ModuleManager is IModuleManager, Auth { 23 | // Helper library for address to address mappings 24 | using AddressLinkedList for mapping(address => address); 25 | // Interface helper library 26 | using ERC165Checker for address; 27 | // Low level calls helper library 28 | using ExcessivelySafeCall for address; 29 | 30 | /// @inheritdoc IModuleManager 31 | function addModule(bytes calldata moduleAndData) external override onlySelfOrModule { 32 | _addModule(moduleAndData); 33 | } 34 | 35 | /// @inheritdoc IModuleManager 36 | function removeModule(address module) external override onlySelfOrModule { 37 | _removeModule(module); 38 | } 39 | 40 | /// @inheritdoc IModuleManager 41 | function executeFromModule( 42 | address to, 43 | uint256 value, 44 | bytes memory data 45 | ) external override onlyModule { 46 | if (to == address(this)) revert Errors.RECUSIVE_MODULE_CALL(); 47 | 48 | assembly { 49 | let result := call(gas(), to, value, add(data, 0x20), mload(data), 0, 0) 50 | if iszero(result) { 51 | returndatacopy(0, 0, returndatasize()) 52 | revert(0, returndatasize()) 53 | } 54 | } 55 | } 56 | 57 | /// @inheritdoc IModuleManager 58 | function isModule(address addr) external view override returns (bool) { 59 | return _isModule(addr); 60 | } 61 | 62 | /// @inheritdoc IModuleManager 63 | function listModules() external view override returns (address[] memory moduleList) { 64 | moduleList = _modulesLinkedList().list(); 65 | } 66 | 67 | function _addModule(bytes calldata moduleAndData) internal { 68 | if (moduleAndData.length < 20) { 69 | revert Errors.EMPTY_MODULE_ADDRESS(); 70 | } 71 | 72 | address moduleAddress = address(bytes20(moduleAndData[0:20])); 73 | bytes calldata initData = moduleAndData[20:]; 74 | 75 | if (!_supportsModule(moduleAddress)) { 76 | revert Errors.MODULE_ERC165_FAIL(); 77 | } 78 | 79 | _modulesLinkedList().add(moduleAddress); 80 | 81 | IModule(moduleAddress).init(initData); 82 | 83 | emit AddModule(moduleAddress); 84 | } 85 | 86 | function _removeModule(address module) internal { 87 | _modulesLinkedList().remove(module); 88 | 89 | (bool success, ) = module.excessivelySafeCall( 90 | gasleft(), 91 | 0, 92 | 0, 93 | abi.encodeWithSelector(IInitable.disable.selector) 94 | ); 95 | (success); // silence unused local variable warning 96 | 97 | emit RemoveModule(module); 98 | } 99 | 100 | function _isModule(address addr) internal view override returns (bool) { 101 | return _modulesLinkedList().exists(addr); 102 | } 103 | 104 | function _modulesLinkedList() 105 | private 106 | view 107 | returns (mapping(address => address) storage modules) 108 | { 109 | modules = ClaveStorage.layout().modules; 110 | } 111 | 112 | function _supportsModule(address module) internal view returns (bool) { 113 | return module.supportsInterface(type(IModule).interfaceId); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /subgraph/src/venus-pool-usdce.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | 7 | /* eslint-disable @typescript-eslint/consistent-type-imports */ 8 | import { Address } from '@graphprotocol/graph-ts'; 9 | 10 | import { 11 | Mint as MintEvent, 12 | Redeem as RedeemEvent, 13 | } from '../generated/VenusUsdce/VenusToken'; 14 | import { ClaveAccount } from '../generated/schema'; 15 | import { 16 | ZERO, 17 | getOrCreateDailyEarnFlow, 18 | getOrCreateDay, 19 | getOrCreateEarnPosition, 20 | getOrCreateMonth, 21 | getOrCreateMonthlyEarnFlow, 22 | getOrCreateWeek, 23 | getOrCreateWeeklyEarnFlow, 24 | } from './helpers'; 25 | 26 | const protocol = 'Venus'; 27 | 28 | const token = Address.fromHexString( 29 | '0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4', 30 | ); 31 | 32 | export function handleMint(event: MintEvent): void { 33 | const account = ClaveAccount.load(event.params.minter); 34 | if (!account) { 35 | return; 36 | } 37 | 38 | const pool = event.address; 39 | const amount = event.params.mintAmount; 40 | 41 | const day = getOrCreateDay(event.block.timestamp); 42 | const week = getOrCreateWeek(event.block.timestamp); 43 | const month = getOrCreateMonth(event.block.timestamp); 44 | 45 | const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); 46 | const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); 47 | const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); 48 | const earnPosition = getOrCreateEarnPosition( 49 | account, 50 | pool, 51 | token, 52 | protocol, 53 | ); 54 | 55 | dailyEarnFlow.amountIn = dailyEarnFlow.amountIn.plus(amount); 56 | weeklyEarnFlow.amountIn = weeklyEarnFlow.amountIn.plus(amount); 57 | monthlyEarnFlow.amountIn = monthlyEarnFlow.amountIn.plus(amount); 58 | earnPosition.invested = earnPosition.invested.plus(amount); 59 | 60 | dailyEarnFlow.save(); 61 | weeklyEarnFlow.save(); 62 | monthlyEarnFlow.save(); 63 | earnPosition.save(); 64 | } 65 | 66 | export function handleRedeem(event: RedeemEvent): void { 67 | const account = ClaveAccount.load(event.params.redeemer); 68 | if (!account) { 69 | return; 70 | } 71 | 72 | const pool = event.address; 73 | const amount = event.params.redeemAmount; 74 | 75 | const day = getOrCreateDay(event.block.timestamp); 76 | const week = getOrCreateWeek(event.block.timestamp); 77 | const month = getOrCreateMonth(event.block.timestamp); 78 | 79 | const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); 80 | const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); 81 | const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); 82 | const earnPosition = getOrCreateEarnPosition( 83 | account, 84 | pool, 85 | token, 86 | protocol, 87 | ); 88 | 89 | const invested = earnPosition.invested; 90 | 91 | if (amount.gt(invested)) { 92 | const compoundGain = amount.minus(invested); 93 | dailyEarnFlow.amountOut = dailyEarnFlow.amountOut.plus(invested); 94 | dailyEarnFlow.claimedGain = 95 | dailyEarnFlow.claimedGain.plus(compoundGain); 96 | weeklyEarnFlow.amountOut = weeklyEarnFlow.amountOut.plus(invested); 97 | weeklyEarnFlow.claimedGain = 98 | weeklyEarnFlow.claimedGain.plus(compoundGain); 99 | monthlyEarnFlow.amountOut = monthlyEarnFlow.amountOut.plus(invested); 100 | monthlyEarnFlow.claimedGain = 101 | monthlyEarnFlow.claimedGain.plus(compoundGain); 102 | earnPosition.invested = ZERO; 103 | earnPosition.compoundGain = 104 | earnPosition.compoundGain.plus(compoundGain); 105 | } else { 106 | dailyEarnFlow.amountOut = dailyEarnFlow.amountOut.plus(amount); 107 | weeklyEarnFlow.amountOut = weeklyEarnFlow.amountOut.plus(amount); 108 | monthlyEarnFlow.amountOut = monthlyEarnFlow.amountOut.plus(amount); 109 | earnPosition.invested = invested.minus(amount); 110 | } 111 | 112 | dailyEarnFlow.save(); 113 | weeklyEarnFlow.save(); 114 | monthlyEarnFlow.save(); 115 | earnPosition.save(); 116 | } 117 | -------------------------------------------------------------------------------- /subgraph/src/venus-pool-usdt.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | 7 | /* eslint-disable @typescript-eslint/consistent-type-imports */ 8 | import { Address } from '@graphprotocol/graph-ts'; 9 | 10 | import { 11 | Mint as MintEvent, 12 | Redeem as RedeemEvent, 13 | } from '../generated/VenusUsdt/VenusToken'; 14 | import { ClaveAccount } from '../generated/schema'; 15 | import { 16 | ZERO, 17 | getOrCreateDailyEarnFlow, 18 | getOrCreateDay, 19 | getOrCreateEarnPosition, 20 | getOrCreateMonth, 21 | getOrCreateMonthlyEarnFlow, 22 | getOrCreateWeek, 23 | getOrCreateWeeklyEarnFlow, 24 | } from './helpers'; 25 | 26 | const protocol = 'Venus'; 27 | 28 | const token = Address.fromHexString( 29 | '0x493257fD37EDB34451f62EDf8D2a0C418852bA4C', 30 | ); 31 | 32 | export function handleMint(event: MintEvent): void { 33 | const account = ClaveAccount.load(event.params.minter); 34 | if (!account) { 35 | return; 36 | } 37 | 38 | const pool = event.address; 39 | const amount = event.params.mintAmount; 40 | 41 | const day = getOrCreateDay(event.block.timestamp); 42 | const week = getOrCreateWeek(event.block.timestamp); 43 | const month = getOrCreateMonth(event.block.timestamp); 44 | 45 | const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); 46 | const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); 47 | const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); 48 | const earnPosition = getOrCreateEarnPosition( 49 | account, 50 | pool, 51 | token, 52 | protocol, 53 | ); 54 | 55 | dailyEarnFlow.amountIn = dailyEarnFlow.amountIn.plus(amount); 56 | weeklyEarnFlow.amountIn = weeklyEarnFlow.amountIn.plus(amount); 57 | monthlyEarnFlow.amountIn = monthlyEarnFlow.amountIn.plus(amount); 58 | earnPosition.invested = earnPosition.invested.plus(amount); 59 | 60 | dailyEarnFlow.save(); 61 | weeklyEarnFlow.save(); 62 | monthlyEarnFlow.save(); 63 | earnPosition.save(); 64 | } 65 | 66 | export function handleRedeem(event: RedeemEvent): void { 67 | const account = ClaveAccount.load(event.params.redeemer); 68 | if (!account) { 69 | return; 70 | } 71 | 72 | const pool = event.address; 73 | const amount = event.params.redeemAmount; 74 | 75 | const day = getOrCreateDay(event.block.timestamp); 76 | const week = getOrCreateWeek(event.block.timestamp); 77 | const month = getOrCreateMonth(event.block.timestamp); 78 | 79 | const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); 80 | const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); 81 | const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); 82 | const earnPosition = getOrCreateEarnPosition( 83 | account, 84 | pool, 85 | token, 86 | protocol, 87 | ); 88 | 89 | const invested = earnPosition.invested; 90 | 91 | if (amount.gt(invested)) { 92 | const compoundGain = amount.minus(invested); 93 | dailyEarnFlow.amountOut = dailyEarnFlow.amountOut.plus(invested); 94 | dailyEarnFlow.claimedGain = 95 | dailyEarnFlow.claimedGain.plus(compoundGain); 96 | weeklyEarnFlow.amountOut = weeklyEarnFlow.amountOut.plus(invested); 97 | weeklyEarnFlow.claimedGain = 98 | weeklyEarnFlow.claimedGain.plus(compoundGain); 99 | monthlyEarnFlow.amountOut = monthlyEarnFlow.amountOut.plus(invested); 100 | monthlyEarnFlow.claimedGain = 101 | monthlyEarnFlow.claimedGain.plus(compoundGain); 102 | earnPosition.invested = ZERO; 103 | earnPosition.compoundGain = 104 | earnPosition.compoundGain.plus(compoundGain); 105 | } else { 106 | dailyEarnFlow.amountOut = dailyEarnFlow.amountOut.plus(amount); 107 | weeklyEarnFlow.amountOut = weeklyEarnFlow.amountOut.plus(amount); 108 | monthlyEarnFlow.amountOut = monthlyEarnFlow.amountOut.plus(amount); 109 | earnPosition.invested = invested.minus(amount); 110 | } 111 | 112 | dailyEarnFlow.save(); 113 | weeklyEarnFlow.save(); 114 | monthlyEarnFlow.save(); 115 | earnPosition.save(); 116 | } 117 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/deployments/deployer.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | import { expect } from 'chai'; 7 | import type { ec } from 'elliptic'; 8 | import type { BytesLike } from 'ethers'; 9 | import { parseEther } from 'ethers'; 10 | import * as hre from 'hardhat'; 11 | import type { Contract, Wallet } from 'zksync-ethers'; 12 | import { Provider } from 'zksync-ethers'; 13 | 14 | import { LOCAL_RICH_WALLETS, getWallet } from '../../deploy/utils'; 15 | import { ClaveDeployer } from '../utils/deployer'; 16 | import { fixture } from '../utils/fixture'; 17 | import { encodePublicKey } from '../utils/p256'; 18 | 19 | describe('Clave Contracts - Deployer class tests', () => { 20 | let deployer: ClaveDeployer; 21 | let provider: Provider; 22 | let richWallet: Wallet; 23 | let batchCaller: Contract; 24 | let registry: Contract; 25 | let implementation: Contract; 26 | let factory: Contract; 27 | let mockValidator: Contract; 28 | let account: Contract; 29 | let keyPair: ec.KeyPair; 30 | 31 | before(async () => { 32 | richWallet = getWallet(hre, LOCAL_RICH_WALLETS[0].privateKey); 33 | deployer = new ClaveDeployer(hre, richWallet); 34 | provider = new Provider(hre.network.config.url, undefined, { 35 | cacheTimeout: -1, 36 | }); 37 | 38 | [ 39 | batchCaller, 40 | registry, 41 | implementation, 42 | factory, 43 | mockValidator, 44 | account, 45 | keyPair, 46 | ] = await fixture(deployer); 47 | 48 | await deployer.fund(100, await account.getAddress()); 49 | }); 50 | 51 | describe('Contracts', () => { 52 | it('should deploy the contracts', async () => { 53 | expect(await batchCaller.getAddress()).not.to.be.undefined; 54 | expect(await registry.getAddress()).not.to.be.undefined; 55 | expect(await implementation.getAddress()).not.to.be.undefined; 56 | expect(await factory.getAddress()).not.to.be.undefined; 57 | expect(await mockValidator.getAddress()).not.to.be.undefined; 58 | expect(await account.getAddress()).not.to.be.undefined; 59 | }); 60 | }); 61 | 62 | describe('States', () => { 63 | it('should fund the account', async () => { 64 | const balance = await provider.getBalance( 65 | await account.getAddress(), 66 | ); 67 | expect(balance).to.eq(parseEther('100')); 68 | }); 69 | 70 | it('account keeps correct states', async () => { 71 | const validatorAddress = await mockValidator.getAddress(); 72 | const implementationAddress = await implementation.getAddress(); 73 | 74 | const expectedR1Validators = [validatorAddress]; 75 | const expectedK1Validators: Array = []; 76 | const expectedR1Owners = [encodePublicKey(keyPair)]; 77 | const expectedK1Owners: Array = []; 78 | const expectedModules: Array = []; 79 | const expectedHooks: Array = []; 80 | const expectedImplementation = implementationAddress; 81 | 82 | expect(await account.r1ListValidators()).to.deep.eq( 83 | expectedR1Validators, 84 | ); 85 | expect(await account.k1ListValidators()).to.deep.eq( 86 | expectedK1Validators, 87 | ); 88 | expect(await account.r1ListOwners()).to.deep.eq(expectedR1Owners); 89 | expect(await account.k1ListOwners()).to.deep.eq(expectedK1Owners); 90 | expect(await account.listModules()).to.deep.eq(expectedModules); 91 | expect(await account.listHooks(false)).to.deep.eq(expectedHooks); 92 | expect(await account.listHooks(true)).to.deep.eq(expectedHooks); 93 | expect(await account.implementation()).to.eq( 94 | expectedImplementation, 95 | ); 96 | }); 97 | 98 | it('registry is deployed and states are expected', async function () { 99 | const accountAddress = await account.getAddress(); 100 | const factoryAddress = await factory.getAddress(); 101 | 102 | expect(await registry.isClave(accountAddress)).to.be.true; 103 | expect(await registry.isClave(factoryAddress)).not.to.be.true; 104 | }); 105 | }); 106 | }); 107 | -------------------------------------------------------------------------------- /contracts/managers/OwnerManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.17; 3 | 4 | import {ClaveStorage} from '../libraries/ClaveStorage.sol'; 5 | import {BytesLinkedList, AddressLinkedList} from '../libraries/LinkedList.sol'; 6 | import {Errors} from '../libraries/Errors.sol'; 7 | import {Auth} from '../auth/Auth.sol'; 8 | import {IClaveAccount} from '../interfaces/IClave.sol'; 9 | import {IOwnerManager} from '../interfaces/IOwnerManager.sol'; 10 | 11 | /** 12 | * @title Manager contract for owners 13 | * @notice Abstract contract for managing the owners of the account 14 | * @dev R1 Owners are 64 byte secp256r1 public keys 15 | * @dev K1 Owners are secp256k1 addresses 16 | * @dev Owners are stored in a linked list 17 | * @author https://getclave.io 18 | */ 19 | abstract contract OwnerManager is IOwnerManager, Auth { 20 | // Helper library for bytes to bytes mappings 21 | using BytesLinkedList for mapping(bytes => bytes); 22 | // Helper library for address to address mappings 23 | using AddressLinkedList for mapping(address => address); 24 | 25 | /// @inheritdoc IOwnerManager 26 | function r1AddOwner(bytes calldata pubKey) external override onlySelfOrModule { 27 | _r1AddOwner(pubKey); 28 | } 29 | 30 | /// @inheritdoc IOwnerManager 31 | function k1AddOwner(address addr) external override onlySelfOrModule { 32 | _k1AddOwner(addr); 33 | } 34 | 35 | /// @inheritdoc IOwnerManager 36 | function r1RemoveOwner(bytes calldata pubKey) external override onlySelfOrModule { 37 | _r1RemoveOwner(pubKey); 38 | } 39 | 40 | /// @inheritdoc IOwnerManager 41 | function k1RemoveOwner(address addr) external override onlySelfOrModule { 42 | _k1RemoveOwner(addr); 43 | } 44 | 45 | /// @inheritdoc IOwnerManager 46 | function resetOwners(bytes calldata pubKey) external override onlySelfOrModule { 47 | _r1ClearOwners(); 48 | _k1ClearOwners(); 49 | 50 | emit ResetOwners(); 51 | 52 | _r1AddOwner(pubKey); 53 | } 54 | 55 | /// @inheritdoc IOwnerManager 56 | function r1IsOwner(bytes calldata pubKey) external view override returns (bool) { 57 | return _r1IsOwner(pubKey); 58 | } 59 | 60 | /// @inheritdoc IOwnerManager 61 | function k1IsOwner(address addr) external view override returns (bool) { 62 | return _k1IsOwner(addr); 63 | } 64 | 65 | /// @inheritdoc IOwnerManager 66 | function r1ListOwners() external view override returns (bytes[] memory r1OwnerList) { 67 | r1OwnerList = _r1OwnersLinkedList().list(); 68 | } 69 | 70 | /// @inheritdoc IOwnerManager 71 | function k1ListOwners() external view override returns (address[] memory k1OwnerList) { 72 | k1OwnerList = _k1OwnersLinkedList().list(); 73 | } 74 | 75 | function _r1AddOwner(bytes calldata pubKey) internal { 76 | if (pubKey.length != 64) { 77 | revert Errors.INVALID_PUBKEY_LENGTH(); 78 | } 79 | 80 | _r1OwnersLinkedList().add(pubKey); 81 | 82 | emit R1AddOwner(pubKey); 83 | } 84 | 85 | function _k1AddOwner(address addr) internal { 86 | _k1OwnersLinkedList().add(addr); 87 | 88 | emit K1AddOwner(addr); 89 | } 90 | 91 | function _r1RemoveOwner(bytes calldata pubKey) internal { 92 | _r1OwnersLinkedList().remove(pubKey); 93 | 94 | if (_r1OwnersLinkedList().isEmpty()) { 95 | revert Errors.EMPTY_R1_OWNERS(); 96 | } 97 | 98 | emit R1RemoveOwner(pubKey); 99 | } 100 | 101 | function _k1RemoveOwner(address addr) internal { 102 | _k1OwnersLinkedList().remove(addr); 103 | 104 | emit K1RemoveOwner(addr); 105 | } 106 | 107 | function _r1IsOwner(bytes calldata pubKey) internal view returns (bool) { 108 | return _r1OwnersLinkedList().exists(pubKey); 109 | } 110 | 111 | function _k1IsOwner(address addr) internal view returns (bool) { 112 | return _k1OwnersLinkedList().exists(addr); 113 | } 114 | 115 | function _r1OwnersLinkedList() 116 | internal 117 | view 118 | returns (mapping(bytes => bytes) storage r1Owners) 119 | { 120 | r1Owners = ClaveStorage.layout().r1Owners; 121 | } 122 | 123 | function _k1OwnersLinkedList() 124 | internal 125 | view 126 | returns (mapping(address => address) storage k1Owners) 127 | { 128 | k1Owners = ClaveStorage.layout().k1Owners; 129 | } 130 | 131 | function _r1ClearOwners() private { 132 | _r1OwnersLinkedList().clear(); 133 | } 134 | 135 | function _k1ClearOwners() private { 136 | _k1OwnersLinkedList().clear(); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /deploy/deploy-mvp.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | import { Contract } from 'ethers'; 7 | import * as hre from 'hardhat'; 8 | 9 | import { deployContract, getContractBytecodeHash, getWallet } from './utils'; 10 | 11 | /* 12 | * This is an MVP deploy script that handles deplopyment of the following modules: 13 | * 14 | * BatchCaller 15 | * ClaveImplementation 16 | * ClaveRegistry 17 | * GaslessPaymaster 18 | * ClaveProxy 19 | * PasskeyValidator 20 | * AccountFactory 21 | */ 22 | export default async function (): Promise { 23 | const wallet = getWallet(hre); 24 | 25 | const deploymentConfig = { 26 | PAYMASTER_USER_LIMIT: 50, 27 | DEPLOYER_ADDRESS: wallet.address, 28 | }; 29 | 30 | const batchCaller = await deployBatchCaller(); 31 | const implementation = await deployImplementation(batchCaller); 32 | const registry = await deployRegistry(); 33 | const gaslessPaymaster = await deployPaymaster( 34 | registry, 35 | deploymentConfig.PAYMASTER_USER_LIMIT, 36 | ); 37 | const claveProxy = await deployClaveProxy(implementation); 38 | const passkeyValidator = await deployPasskeyValidator(); 39 | const accountFactory = await deployFactory( 40 | implementation, 41 | registry, 42 | deploymentConfig.DEPLOYER_ADDRESS, 43 | ); 44 | 45 | console.log({ 46 | batchCaller, 47 | implementation, 48 | registry, 49 | gaslessPaymaster, 50 | claveProxy, 51 | passkeyValidator, 52 | accountFactory, 53 | }); 54 | } 55 | 56 | const deployBatchCaller = async (): Promise => { 57 | const contractArtifactName = 'BatchCaller'; 58 | const result = await deployContract(hre, contractArtifactName, []); 59 | return await result.getAddress(); 60 | }; 61 | 62 | const deployImplementation = async ( 63 | batchCallerAdddress: string, 64 | ): Promise => { 65 | const contractArtifactName = 'ClaveImplementation'; 66 | const result = await deployContract(hre, contractArtifactName, [ 67 | batchCallerAdddress, 68 | ]); 69 | return await result.getAddress(); 70 | }; 71 | 72 | const deployRegistry = async (): Promise => { 73 | const contractArtifactName = 'ClaveRegistry'; 74 | const result = await deployContract(hre, contractArtifactName); 75 | return await result.getAddress(); 76 | }; 77 | 78 | const deployPaymaster = async ( 79 | registryAddress: string, 80 | limit: number, 81 | ): Promise => { 82 | const contractArtifactName = 'GaslessPaymaster'; 83 | const result = await deployContract(hre, contractArtifactName, [ 84 | registryAddress, 85 | limit, 86 | ]); 87 | return await result.getAddress(); 88 | }; 89 | 90 | const deployClaveProxy = async ( 91 | implementationAddress: string, 92 | ): Promise => { 93 | const contractArtifactName = 'ClaveProxy'; 94 | const result = await deployContract(hre, contractArtifactName, [ 95 | implementationAddress, 96 | ]); 97 | return await result.getAddress(); 98 | }; 99 | 100 | const deployPasskeyValidator = async (): Promise => { 101 | const contractArtifactName = 'PasskeyValidator'; 102 | const result = await deployContract(hre, contractArtifactName, []); 103 | return await result.getAddress(); 104 | }; 105 | 106 | const deployFactory = async ( 107 | implementationAddress: string, 108 | registryAddress: string, 109 | deployer: string, 110 | ): Promise => { 111 | const proxyArtifact = await hre.artifacts.readArtifact('ClaveProxy'); 112 | const bytecode = proxyArtifact.bytecode; 113 | const bytecodeHash = getContractBytecodeHash(bytecode); 114 | 115 | const contractArtifactName = 'AccountFactory'; 116 | const result = await deployContract(hre, contractArtifactName, [ 117 | implementationAddress, 118 | registryAddress, 119 | bytecodeHash, 120 | deployer, 121 | ]); 122 | 123 | const registryArtifact = await hre.artifacts.readArtifact('ClaveRegistry'); 124 | 125 | const registryContract = new Contract( 126 | registryAddress, 127 | registryArtifact.abi, 128 | getWallet(hre), 129 | ); 130 | 131 | const accountFactoryAddress = await result.getAddress(); 132 | 133 | console.log(`Setting factory address to ${accountFactoryAddress}`); 134 | await registryContract.setFactory(accountFactoryAddress); 135 | console.log('Successfully set factory address in registry contract'); 136 | 137 | return accountFactoryAddress; 138 | }; 139 | -------------------------------------------------------------------------------- /subgraph/src/clave-appa-stake.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | 7 | /* eslint-disable @typescript-eslint/consistent-type-imports */ 8 | import { 9 | RewardPaid as RewardPaidEvent, 10 | Staked as StakedEvent, 11 | Withdrawn as WithdrawnEvent, 12 | } from '../generated/ClaveAPPAStake/ZtakeV2'; 13 | import { ClaveAccount } from '../generated/schema'; 14 | import { 15 | getOrCreateDailyEarnFlow, 16 | getOrCreateDay, 17 | getOrCreateEarnPosition, 18 | getOrCreateMonth, 19 | getOrCreateMonthlyEarnFlow, 20 | getOrCreateWeek, 21 | getOrCreateWeeklyEarnFlow, 22 | } from './helpers'; 23 | 24 | const protocol = 'Clave'; 25 | 26 | export function handleStaked(event: StakedEvent): void { 27 | const account = ClaveAccount.load(event.params.user); 28 | if (!account) { 29 | return; 30 | } 31 | 32 | const pool = event.address; 33 | const token = event.params.token; 34 | const amount = event.params.amount; 35 | 36 | const day = getOrCreateDay(event.block.timestamp); 37 | const week = getOrCreateWeek(event.block.timestamp); 38 | const month = getOrCreateMonth(event.block.timestamp); 39 | 40 | const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); 41 | const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); 42 | const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); 43 | const earnPosition = getOrCreateEarnPosition( 44 | account, 45 | pool, 46 | token, 47 | protocol, 48 | ); 49 | 50 | dailyEarnFlow.amountIn = dailyEarnFlow.amountIn.plus(amount); 51 | weeklyEarnFlow.amountIn = weeklyEarnFlow.amountIn.plus(amount); 52 | monthlyEarnFlow.amountIn = monthlyEarnFlow.amountIn.plus(amount); 53 | earnPosition.invested = earnPosition.invested.plus(amount); 54 | 55 | dailyEarnFlow.save(); 56 | weeklyEarnFlow.save(); 57 | monthlyEarnFlow.save(); 58 | earnPosition.save(); 59 | } 60 | 61 | export function handleWithdrawn(event: WithdrawnEvent): void { 62 | const account = ClaveAccount.load(event.transaction.from); 63 | if (!account) { 64 | return; 65 | } 66 | 67 | const pool = event.address; 68 | const token = event.params.token; 69 | const amount = event.params.amount; 70 | 71 | const day = getOrCreateDay(event.block.timestamp); 72 | const week = getOrCreateWeek(event.block.timestamp); 73 | const month = getOrCreateMonth(event.block.timestamp); 74 | 75 | const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); 76 | const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); 77 | const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); 78 | const earnPosition = getOrCreateEarnPosition( 79 | account, 80 | pool, 81 | token, 82 | protocol, 83 | ); 84 | 85 | dailyEarnFlow.amountOut = dailyEarnFlow.amountOut.plus(amount); 86 | weeklyEarnFlow.amountOut = weeklyEarnFlow.amountOut.plus(amount); 87 | monthlyEarnFlow.amountOut = monthlyEarnFlow.amountOut.plus(amount); 88 | earnPosition.invested = earnPosition.invested.minus(amount); 89 | 90 | dailyEarnFlow.save(); 91 | weeklyEarnFlow.save(); 92 | monthlyEarnFlow.save(); 93 | earnPosition.save(); 94 | } 95 | 96 | export function handleRewardPaid(event: RewardPaidEvent): void { 97 | const account = ClaveAccount.load(event.params.user); 98 | if (!account) { 99 | return; 100 | } 101 | 102 | const pool = event.address; 103 | const token = event.params.token; 104 | const amount = event.params.reward; 105 | 106 | const day = getOrCreateDay(event.block.timestamp); 107 | const week = getOrCreateWeek(event.block.timestamp); 108 | const month = getOrCreateMonth(event.block.timestamp); 109 | 110 | const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); 111 | const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); 112 | const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); 113 | const earnPosition = getOrCreateEarnPosition( 114 | account, 115 | pool, 116 | token, 117 | protocol, 118 | ); 119 | 120 | dailyEarnFlow.claimedGain = dailyEarnFlow.claimedGain.plus(amount); 121 | weeklyEarnFlow.claimedGain = weeklyEarnFlow.claimedGain.plus(amount); 122 | monthlyEarnFlow.claimedGain = monthlyEarnFlow.claimedGain.plus(amount); 123 | earnPosition.normalGain = earnPosition.normalGain.plus(amount); 124 | 125 | dailyEarnFlow.save(); 126 | weeklyEarnFlow.save(); 127 | monthlyEarnFlow.save(); 128 | earnPosition.save(); 129 | } 130 | -------------------------------------------------------------------------------- /balances-subgraph/abis/ERC20.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "name", 6 | "outputs": [ 7 | { 8 | "name": "", 9 | "type": "string" 10 | } 11 | ], 12 | "payable": false, 13 | "stateMutability": "view", 14 | "type": "function" 15 | }, 16 | { 17 | "constant": false, 18 | "inputs": [ 19 | { 20 | "name": "_spender", 21 | "type": "address" 22 | }, 23 | { 24 | "name": "_value", 25 | "type": "uint256" 26 | } 27 | ], 28 | "name": "approve", 29 | "outputs": [ 30 | { 31 | "name": "", 32 | "type": "bool" 33 | } 34 | ], 35 | "payable": false, 36 | "stateMutability": "nonpayable", 37 | "type": "function" 38 | }, 39 | { 40 | "constant": true, 41 | "inputs": [], 42 | "name": "totalSupply", 43 | "outputs": [ 44 | { 45 | "name": "", 46 | "type": "uint256" 47 | } 48 | ], 49 | "payable": false, 50 | "stateMutability": "view", 51 | "type": "function" 52 | }, 53 | { 54 | "constant": false, 55 | "inputs": [ 56 | { 57 | "name": "_from", 58 | "type": "address" 59 | }, 60 | { 61 | "name": "_to", 62 | "type": "address" 63 | }, 64 | { 65 | "name": "_value", 66 | "type": "uint256" 67 | } 68 | ], 69 | "name": "transferFrom", 70 | "outputs": [ 71 | { 72 | "name": "", 73 | "type": "bool" 74 | } 75 | ], 76 | "payable": false, 77 | "stateMutability": "nonpayable", 78 | "type": "function" 79 | }, 80 | { 81 | "constant": true, 82 | "inputs": [], 83 | "name": "decimals", 84 | "outputs": [ 85 | { 86 | "name": "", 87 | "type": "uint8" 88 | } 89 | ], 90 | "payable": false, 91 | "stateMutability": "view", 92 | "type": "function" 93 | }, 94 | { 95 | "constant": true, 96 | "inputs": [ 97 | { 98 | "name": "_owner", 99 | "type": "address" 100 | } 101 | ], 102 | "name": "balanceOf", 103 | "outputs": [ 104 | { 105 | "name": "balance", 106 | "type": "uint256" 107 | } 108 | ], 109 | "payable": false, 110 | "stateMutability": "view", 111 | "type": "function" 112 | }, 113 | { 114 | "constant": true, 115 | "inputs": [], 116 | "name": "symbol", 117 | "outputs": [ 118 | { 119 | "name": "", 120 | "type": "string" 121 | } 122 | ], 123 | "payable": false, 124 | "stateMutability": "view", 125 | "type": "function" 126 | }, 127 | { 128 | "constant": false, 129 | "inputs": [ 130 | { 131 | "name": "_to", 132 | "type": "address" 133 | }, 134 | { 135 | "name": "_value", 136 | "type": "uint256" 137 | } 138 | ], 139 | "name": "transfer", 140 | "outputs": [ 141 | { 142 | "name": "", 143 | "type": "bool" 144 | } 145 | ], 146 | "payable": false, 147 | "stateMutability": "nonpayable", 148 | "type": "function" 149 | }, 150 | { 151 | "constant": true, 152 | "inputs": [ 153 | { 154 | "name": "_owner", 155 | "type": "address" 156 | }, 157 | { 158 | "name": "_spender", 159 | "type": "address" 160 | } 161 | ], 162 | "name": "allowance", 163 | "outputs": [ 164 | { 165 | "name": "", 166 | "type": "uint256" 167 | } 168 | ], 169 | "payable": false, 170 | "stateMutability": "view", 171 | "type": "function" 172 | }, 173 | { 174 | "payable": true, 175 | "stateMutability": "payable", 176 | "type": "fallback" 177 | }, 178 | { 179 | "anonymous": false, 180 | "inputs": [ 181 | { 182 | "indexed": true, 183 | "name": "owner", 184 | "type": "address" 185 | }, 186 | { 187 | "indexed": true, 188 | "name": "spender", 189 | "type": "address" 190 | }, 191 | { 192 | "indexed": false, 193 | "name": "value", 194 | "type": "uint256" 195 | } 196 | ], 197 | "name": "Approval", 198 | "type": "event" 199 | }, 200 | { 201 | "anonymous": false, 202 | "inputs": [ 203 | { 204 | "indexed": true, 205 | "name": "from", 206 | "type": "address" 207 | }, 208 | { 209 | "indexed": true, 210 | "name": "to", 211 | "type": "address" 212 | }, 213 | { 214 | "indexed": false, 215 | "name": "value", 216 | "type": "uint256" 217 | } 218 | ], 219 | "name": "Transfer", 220 | "type": "event" 221 | } 222 | ] 223 | -------------------------------------------------------------------------------- /subgraph/src/clave-zk-stake.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | 7 | /* eslint-disable @typescript-eslint/consistent-type-imports */ 8 | import { Address } from '@graphprotocol/graph-ts'; 9 | 10 | import { 11 | RewardPaid as RewardPaidEvent, 12 | Staked as StakedEvent, 13 | Withdrawn as WithdrawnEvent, 14 | } from '../generated/ClaveZKStake/ClaveZKStake'; 15 | import { ClaveAccount } from '../generated/schema'; 16 | import { 17 | getOrCreateDailyEarnFlow, 18 | getOrCreateDay, 19 | getOrCreateEarnPosition, 20 | getOrCreateMonth, 21 | getOrCreateMonthlyEarnFlow, 22 | getOrCreateWeek, 23 | getOrCreateWeeklyEarnFlow, 24 | } from './helpers'; 25 | 26 | const protocol = 'Clave'; 27 | const token = Address.fromHexString( 28 | '0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E', 29 | ); 30 | 31 | export function handleStaked(event: StakedEvent): void { 32 | const account = ClaveAccount.load(event.params.user); 33 | if (!account) { 34 | return; 35 | } 36 | 37 | const pool = event.address; 38 | const amount = event.params.amount; 39 | 40 | const day = getOrCreateDay(event.block.timestamp); 41 | const week = getOrCreateWeek(event.block.timestamp); 42 | const month = getOrCreateMonth(event.block.timestamp); 43 | 44 | const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); 45 | const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); 46 | const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); 47 | const earnPosition = getOrCreateEarnPosition( 48 | account, 49 | pool, 50 | token, 51 | protocol, 52 | ); 53 | 54 | dailyEarnFlow.amountIn = dailyEarnFlow.amountIn.plus(amount); 55 | weeklyEarnFlow.amountIn = weeklyEarnFlow.amountIn.plus(amount); 56 | monthlyEarnFlow.amountIn = monthlyEarnFlow.amountIn.plus(amount); 57 | earnPosition.invested = earnPosition.invested.plus(amount); 58 | 59 | dailyEarnFlow.save(); 60 | weeklyEarnFlow.save(); 61 | monthlyEarnFlow.save(); 62 | earnPosition.save(); 63 | } 64 | 65 | export function handleWithdrawn(event: WithdrawnEvent): void { 66 | const account = ClaveAccount.load(event.transaction.from); 67 | if (!account) { 68 | return; 69 | } 70 | 71 | const pool = event.address; 72 | const amount = event.params.amount; 73 | 74 | const day = getOrCreateDay(event.block.timestamp); 75 | const week = getOrCreateWeek(event.block.timestamp); 76 | const month = getOrCreateMonth(event.block.timestamp); 77 | 78 | const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); 79 | const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); 80 | const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); 81 | const earnPosition = getOrCreateEarnPosition( 82 | account, 83 | pool, 84 | token, 85 | protocol, 86 | ); 87 | 88 | dailyEarnFlow.amountOut = dailyEarnFlow.amountOut.plus(amount); 89 | weeklyEarnFlow.amountOut = weeklyEarnFlow.amountOut.plus(amount); 90 | monthlyEarnFlow.amountOut = monthlyEarnFlow.amountOut.plus(amount); 91 | earnPosition.invested = earnPosition.invested.minus(amount); 92 | 93 | dailyEarnFlow.save(); 94 | weeklyEarnFlow.save(); 95 | monthlyEarnFlow.save(); 96 | earnPosition.save(); 97 | } 98 | 99 | export function handleRewardPaid(event: RewardPaidEvent): void { 100 | const account = ClaveAccount.load(event.params.user); 101 | if (!account) { 102 | return; 103 | } 104 | 105 | const pool = event.address; 106 | const amount = event.params.reward; 107 | 108 | const day = getOrCreateDay(event.block.timestamp); 109 | const week = getOrCreateWeek(event.block.timestamp); 110 | const month = getOrCreateMonth(event.block.timestamp); 111 | 112 | const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); 113 | const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); 114 | const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); 115 | const earnPosition = getOrCreateEarnPosition( 116 | account, 117 | pool, 118 | token, 119 | protocol, 120 | ); 121 | 122 | dailyEarnFlow.claimedGain = dailyEarnFlow.claimedGain.plus(amount); 123 | weeklyEarnFlow.claimedGain = weeklyEarnFlow.claimedGain.plus(amount); 124 | monthlyEarnFlow.claimedGain = monthlyEarnFlow.claimedGain.plus(amount); 125 | earnPosition.normalGain = earnPosition.normalGain.plus(amount); 126 | 127 | dailyEarnFlow.save(); 128 | weeklyEarnFlow.save(); 129 | monthlyEarnFlow.save(); 130 | earnPosition.save(); 131 | } 132 | -------------------------------------------------------------------------------- /subgraph/src/koi-pair.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | 7 | /* eslint-disable @typescript-eslint/consistent-type-imports */ 8 | import { Address } from '@graphprotocol/graph-ts'; 9 | 10 | import { 11 | Burn as BurnEvent, 12 | Claim as ClaimEvent, 13 | Mint as MintEvent, 14 | } from '../generated/KoiUsdceUsdt/KoiPair'; 15 | import { ClaveAccount } from '../generated/schema'; 16 | import { 17 | getOrCreateDailyEarnFlow, 18 | getOrCreateDay, 19 | getOrCreateEarnPosition, 20 | getOrCreateMonth, 21 | getOrCreateMonthlyEarnFlow, 22 | getOrCreateWeek, 23 | getOrCreateWeeklyEarnFlow, 24 | } from './helpers'; 25 | 26 | const protocol = 'Koi'; 27 | const token = Address.fromHexString( 28 | '0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4', 29 | ); 30 | 31 | export function handleMint(event: MintEvent): void { 32 | const account = ClaveAccount.load(event.transaction.from); 33 | if (!account) { 34 | return; 35 | } 36 | 37 | const pool = event.address; 38 | const amount = event.params.amount1.plus(event.params.amount0); 39 | 40 | const day = getOrCreateDay(event.block.timestamp); 41 | const week = getOrCreateWeek(event.block.timestamp); 42 | const month = getOrCreateMonth(event.block.timestamp); 43 | 44 | const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); 45 | const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); 46 | const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); 47 | const earnPosition = getOrCreateEarnPosition( 48 | account, 49 | pool, 50 | token, 51 | protocol, 52 | ); 53 | 54 | dailyEarnFlow.amountIn = dailyEarnFlow.amountIn.plus(amount); 55 | weeklyEarnFlow.amountIn = weeklyEarnFlow.amountIn.plus(amount); 56 | monthlyEarnFlow.amountIn = monthlyEarnFlow.amountIn.plus(amount); 57 | earnPosition.invested = earnPosition.invested.plus(amount); 58 | 59 | dailyEarnFlow.save(); 60 | weeklyEarnFlow.save(); 61 | monthlyEarnFlow.save(); 62 | earnPosition.save(); 63 | } 64 | 65 | export function handleBurn(event: BurnEvent): void { 66 | const account = ClaveAccount.load(event.transaction.from); 67 | if (!account) { 68 | return; 69 | } 70 | 71 | const pool = event.address; 72 | const amount = event.params.amount1.plus(event.params.amount0); 73 | 74 | const day = getOrCreateDay(event.block.timestamp); 75 | const week = getOrCreateWeek(event.block.timestamp); 76 | const month = getOrCreateMonth(event.block.timestamp); 77 | 78 | const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); 79 | const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); 80 | const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); 81 | const earnPosition = getOrCreateEarnPosition( 82 | account, 83 | pool, 84 | token, 85 | protocol, 86 | ); 87 | 88 | dailyEarnFlow.amountOut = dailyEarnFlow.amountOut.plus(amount); 89 | weeklyEarnFlow.amountOut = weeklyEarnFlow.amountOut.plus(amount); 90 | monthlyEarnFlow.amountOut = monthlyEarnFlow.amountOut.plus(amount); 91 | earnPosition.invested = earnPosition.invested.minus(amount); 92 | 93 | dailyEarnFlow.save(); 94 | weeklyEarnFlow.save(); 95 | monthlyEarnFlow.save(); 96 | earnPosition.save(); 97 | } 98 | 99 | export function handleClaim(event: ClaimEvent): void { 100 | const account = ClaveAccount.load(event.params.sender); 101 | if (!account) { 102 | return; 103 | } 104 | 105 | const pool = event.address; 106 | const amount = event.params.amount1.plus(event.params.amount0); 107 | 108 | const day = getOrCreateDay(event.block.timestamp); 109 | const week = getOrCreateWeek(event.block.timestamp); 110 | const month = getOrCreateMonth(event.block.timestamp); 111 | 112 | const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); 113 | const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); 114 | const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); 115 | const earnPosition = getOrCreateEarnPosition( 116 | account, 117 | pool, 118 | token, 119 | protocol, 120 | ); 121 | 122 | dailyEarnFlow.claimedGain = dailyEarnFlow.claimedGain.plus(amount); 123 | weeklyEarnFlow.claimedGain = weeklyEarnFlow.claimedGain.plus(amount); 124 | monthlyEarnFlow.claimedGain = monthlyEarnFlow.claimedGain.plus(amount); 125 | earnPosition.normalGain = earnPosition.normalGain.plus(amount); 126 | 127 | dailyEarnFlow.save(); 128 | weeklyEarnFlow.save(); 129 | monthlyEarnFlow.save(); 130 | earnPosition.save(); 131 | } 132 | -------------------------------------------------------------------------------- /subgraph/src/meow-staking.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Clave - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | */ 6 | 7 | /* eslint-disable @typescript-eslint/consistent-type-imports */ 8 | import { Address } from '@graphprotocol/graph-ts'; 9 | 10 | import { 11 | RewardPaid as RewardPaidEvent, 12 | Staked as StakedEvent, 13 | Withdrawn as WithdrawnEvent, 14 | } from '../generated/ClaveZKStake/ClaveZKStake'; 15 | import { ClaveAccount } from '../generated/schema'; 16 | import { 17 | getOrCreateDailyEarnFlow, 18 | getOrCreateDay, 19 | getOrCreateEarnPosition, 20 | getOrCreateMonth, 21 | getOrCreateMonthlyEarnFlow, 22 | getOrCreateWeek, 23 | getOrCreateWeeklyEarnFlow, 24 | } from './helpers'; 25 | 26 | const protocol = 'Meow'; 27 | const token = Address.fromHexString( 28 | '0x79db8c67d0c33203da4Efb58F7D325E1e0d4d692', 29 | ); 30 | const reward = Address.fromHexString( 31 | '0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E', 32 | ); 33 | 34 | export function handleStaked(event: StakedEvent): void { 35 | const account = ClaveAccount.load(event.params.user); 36 | if (!account) { 37 | return; 38 | } 39 | 40 | const pool = event.address; 41 | const amount = event.params.amount; 42 | 43 | const day = getOrCreateDay(event.block.timestamp); 44 | const week = getOrCreateWeek(event.block.timestamp); 45 | const month = getOrCreateMonth(event.block.timestamp); 46 | 47 | const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); 48 | const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); 49 | const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); 50 | const earnPosition = getOrCreateEarnPosition( 51 | account, 52 | pool, 53 | token, 54 | protocol, 55 | ); 56 | 57 | dailyEarnFlow.amountIn = dailyEarnFlow.amountIn.plus(amount); 58 | weeklyEarnFlow.amountIn = weeklyEarnFlow.amountIn.plus(amount); 59 | monthlyEarnFlow.amountIn = monthlyEarnFlow.amountIn.plus(amount); 60 | earnPosition.invested = earnPosition.invested.plus(amount); 61 | 62 | dailyEarnFlow.save(); 63 | weeklyEarnFlow.save(); 64 | monthlyEarnFlow.save(); 65 | earnPosition.save(); 66 | } 67 | 68 | export function handleWithdrawn(event: WithdrawnEvent): void { 69 | const account = ClaveAccount.load(event.transaction.from); 70 | if (!account) { 71 | return; 72 | } 73 | 74 | const pool = event.address; 75 | const amount = event.params.amount; 76 | 77 | const day = getOrCreateDay(event.block.timestamp); 78 | const week = getOrCreateWeek(event.block.timestamp); 79 | const month = getOrCreateMonth(event.block.timestamp); 80 | 81 | const dailyEarnFlow = getOrCreateDailyEarnFlow(day, token, protocol); 82 | const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, token, protocol); 83 | const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, token, protocol); 84 | const earnPosition = getOrCreateEarnPosition( 85 | account, 86 | pool, 87 | token, 88 | protocol, 89 | ); 90 | 91 | dailyEarnFlow.amountOut = dailyEarnFlow.amountOut.plus(amount); 92 | weeklyEarnFlow.amountOut = weeklyEarnFlow.amountOut.plus(amount); 93 | monthlyEarnFlow.amountOut = monthlyEarnFlow.amountOut.plus(amount); 94 | earnPosition.invested = earnPosition.invested.minus(amount); 95 | 96 | dailyEarnFlow.save(); 97 | weeklyEarnFlow.save(); 98 | monthlyEarnFlow.save(); 99 | earnPosition.save(); 100 | } 101 | 102 | export function handleRewardPaid(event: RewardPaidEvent): void { 103 | const account = ClaveAccount.load(event.params.user); 104 | if (!account) { 105 | return; 106 | } 107 | 108 | const pool = event.address; 109 | const amount = event.params.reward; 110 | 111 | const day = getOrCreateDay(event.block.timestamp); 112 | const week = getOrCreateWeek(event.block.timestamp); 113 | const month = getOrCreateMonth(event.block.timestamp); 114 | 115 | const dailyEarnFlow = getOrCreateDailyEarnFlow(day, reward, protocol); 116 | const weeklyEarnFlow = getOrCreateWeeklyEarnFlow(week, reward, protocol); 117 | const monthlyEarnFlow = getOrCreateMonthlyEarnFlow(month, reward, protocol); 118 | const earnPosition = getOrCreateEarnPosition( 119 | account, 120 | pool, 121 | reward, 122 | protocol, 123 | ); 124 | 125 | dailyEarnFlow.claimedGain = dailyEarnFlow.claimedGain.plus(amount); 126 | weeklyEarnFlow.claimedGain = weeklyEarnFlow.claimedGain.plus(amount); 127 | monthlyEarnFlow.claimedGain = monthlyEarnFlow.claimedGain.plus(amount); 128 | earnPosition.normalGain = earnPosition.normalGain.plus(amount); 129 | 130 | dailyEarnFlow.save(); 131 | weeklyEarnFlow.save(); 132 | monthlyEarnFlow.save(); 133 | earnPosition.save(); 134 | } 135 | --------------------------------------------------------------------------------