diff --git a/packages/contracts/.prettierrc.json b/packages/contracts/.prettierrc.json index 44c8b9a..bbde326 100644 --- a/packages/contracts/.prettierrc.json +++ b/packages/contracts/.prettierrc.json @@ -1,9 +1,22 @@ { + "plugins": ["prettier-plugin-solidity"], "bracketSpacing": true, "printWidth": 120, "proseWrap": "always", "singleQuote": false, "tabWidth": 2, "trailingComma": "all", - "useTabs": false + "useTabs": false, + "overrides": [ + { + "files": "*.sol", + "options": { + "parser": "solidity-parse", + "printWidth": 120, + "tabWidth": 4, + "useTabs": false, + "singleQuote": false + } + } + ] } diff --git a/packages/contracts/.solhint.json b/packages/contracts/.solhint.json index 311f3d1..13854fb 100644 --- a/packages/contracts/.solhint.json +++ b/packages/contracts/.solhint.json @@ -10,7 +10,7 @@ "named-parameters-mapping": "warn", "no-console": "off", "not-rely-on-time": "off", - "one-contract-per-file": true, + "one-contract-per-file": "error", "avoid-low-level-calls": "off", "no-inline-assembly": "off", "no-empty-blocks": "off", diff --git a/packages/contracts/.solhintignore b/packages/contracts/.solhintignore new file mode 100644 index 0000000..815001d --- /dev/null +++ b/packages/contracts/.solhintignore @@ -0,0 +1,6 @@ +# directories +broadcast +cache +coverage +node_modules +out \ No newline at end of file diff --git a/packages/contracts/foundry.toml b/packages/contracts/foundry.toml index 27ed347..b800b5a 100644 --- a/packages/contracts/foundry.toml +++ b/packages/contracts/foundry.toml @@ -31,7 +31,7 @@ test = 'test' multiline_func_header = "all" number_underscore = "thousands" quote_style = "double" - tab_width = 2 + tab_width = 4 wrap_comments = true extra_output_files = [ @@ -43,7 +43,7 @@ fs_permissions = [{ access = "read", path = "./"}] eth_rpc_url = "http://localhost:8545" [profile.sepolia] -eth_rpc_url = "https://arb-mainnet.g.alchemy.com/v2/i2qnBKk5GQ8pVGPLA-G3D9il5o0ULQO3" +eth_rpc_url = "https://eth-sepolia.g.alchemy.com/v2/i2qnBKk5GQ8pVGPLA-G3D9il5o0ULQO3" [profile.arbitrum] eth_rpc_url = "https://arb-mainnet.g.alchemy.com/v2/i2qnBKk5GQ8pVGPLA-G3D9il5o0ULQO3" diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 905c3c0..ce4fd07 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -2,7 +2,7 @@ "name": "contracts", "version": "0.0.8", "private": true, - "description": "Contracts for Protocol", + "descripticon": "Contracts for Protocol", "scripts": { "account": "node script/ListAccount.js", "chain": "anvil --config-out localhost.json", @@ -13,9 +13,11 @@ "lint": "pnpm run prettier && pnpm run solhint", "anvil": "source .env && anvil --fork-url $ANVIL_FORK_URL --chain-id $ANVIL_CHAIN_ID", "build": "forge build", - "test": "pnpm run build && forge test -f https://sepolia-rollup.arbitrum.io/rpc --gas-report", - "deploy:arbitrum": "source .env && FOUNDRY_PROFILE=arbitrum forge script script/Arbitrum.s.sol:ArbitrumScript --private-key $FORGE_PRIVATE_KEY --etherscan-api-key $ETHERSCAN_API_KEY --broadcast", - "deploy:arbitrum-sepolia": "source .env && FOUNDRY_PROFILE=arbitrum-sepolia forge script script/ArbitrumSepolia.s.sol:ArbitrumScript --private-key $FORGE_PRIVATE_KEY --etherscan-api-key $ETHERSCAN_API_KEY --broadcast" + "test": "pnpm run build && forge test -f https://eth-sepolia.g.alchemy.com/v2/i2qnBKk5GQ8pVGPLA-G3D9il5o0ULQO3 --gas-report", + "deploy:counter": "source .env && forge script script/Counter.s.sol:CounterScript --private-key $PRIVATE_KEY --etherscan-api-key $API_KEY_ETHERSCAN", + "deploy:action": "source .env && forge script script/DeployActionRegistry.s.sol:DeployActionRegistry --private-key $PRIVATE_KEY --etherscan-api-key $API_KEY_ETHERSCAN", + "deploy:garden": "source .env && forge script script/DeployGardenToken.s.sol:DeployGardenToken --private-key $PRIVATE_KEY --etherscan-api-key $API_KEY_ETHERSCAN", + "deploy:resolvers": "source .env && forge script script/DeployResolvers.s.sol:DeployResolvers --private-key $PRIVATE_KEY --etherscan-api-key $API_KEY_ETHERSCAN" }, "dependencies": { "@ethereum-attestation-service/eas-contracts": "1.7.1", @@ -29,7 +31,6 @@ "toml": "~3.0.0", "solidity-coverage": "^0.8.12", "solhint": "^5.0.3", - "forge-std": "github:foundry-rs/forge-std#v1.8.1", "prettier": "^3.3.3", "prettier-plugin-solidity": "^1.4.0" } diff --git a/packages/contracts/remappings.txt b/packages/contracts/remappings.txt index 7e83eff..bb01407 100644 --- a/packages/contracts/remappings.txt +++ b/packages/contracts/remappings.txt @@ -1,7 +1,7 @@ -ds-test=./node_modules/ds-test/src/ -forge-std=./node_modules/forge-std/src/ -@openzeppelin/contracts=lib/tokenbound/lib/openzeppelin-contracts/contracts/ -@openzeppelin/contracts-upgradeable=./node_modules/@openzeppelin/contracts-upgradeable/ -@eas=./node_modules/@ethereum-attestation-service/eas-contracts/contracts/ @tokenbound=./lib/tokenbound/src/ erc6551/=lib/tokenbound/lib/erc6551/src/ +forge-std=./lib/tokenbound/lib/forge-std/src/ +ds-test=./lib/tokenbound/lib/forge-std/lib/ds-test/src/ +@eas=./node_modules/@ethereum-attestation-service/eas-contracts/contracts/ +@openzeppelin/contracts=lib/tokenbound/lib/openzeppelin-contracts/contracts/ +@openzeppelin/contracts-upgradeable=./node_modules/@openzeppelin/contracts-upgradeable/ diff --git a/packages/contracts/script/Counter.s.sol b/packages/contracts/script/Counter.s.sol index 58649ba..334fbe7 100644 --- a/packages/contracts/script/Counter.s.sol +++ b/packages/contracts/script/Counter.s.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.25; -import "forge-std/Script.sol"; +import { Script } from "forge-std/Script.sol"; contract CounterScript is Script { function setUp() public {} diff --git a/packages/contracts/script/DeployActionRegistry.s.sol b/packages/contracts/script/DeployActionRegistry.s.sol new file mode 100644 index 0000000..9991520 --- /dev/null +++ b/packages/contracts/script/DeployActionRegistry.s.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import { Script, console } from "forge-std/Script.sol"; +import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol"; +import { FACTORY, SALT } from "../src/Constants.sol"; +import { ActionRegistry, Capital } from "../src/registries/Action.sol"; + +/// @title DeployActionRegistry +/// @notice Script for deploying the ActionRegistry contract using CREATE2. +contract DeployActionRegistry is Script { + function run() public { + // Calculate the CREATE2 address for the ActionRegistry + address predictedRegistryAddress = Create2.computeAddress( + SALT, + keccak256(abi.encodePacked(type(ActionRegistry).creationCode, "")), + FACTORY + ); + + // Check if the contract is already deployed + if (predictedRegistryAddress.code.length == 0) { + vm.startBroadcast(); + ActionRegistry newRegistry = new ActionRegistry{ salt: SALT }(); + + Capital[] memory capitals = new Capital[](1); + capitals[0] = Capital.LIVING; + + string[] memory media = new string[](2); + media[0] = "QmWYQY9vnb9ot7u49UMeH41DdjZghrgr2YoaYaNwYSpeAn"; + media[1] = "QmS9K5EdyakRPW7gV86xivaUNx1AuhPUzUSRD53WnjL4Uz"; + + newRegistry.initialize(address(this)); + newRegistry.registerAction( + block.timestamp, + block.timestamp + 30 days, + "Test Action 1", + "QmTmbcRyKtkMpMFWsm6D8YpgwMUuds3jE4sJdjqhqFGvWe", + capitals, + media + ); + newRegistry.registerAction( + block.timestamp, + block.timestamp + 30 days, + "Test Action 2", + "QmTmbcRyKtkMpMFWsm6D8YpgwMUuds3jE4sJdjqhqFGvWe", + capitals, + media + ); + + vm.stopBroadcast(); + + console.log("ActionRegistry deployed at:", predictedRegistryAddress); + } else { + console.log("ActionRegistry already exists at:", predictedRegistryAddress); + } + + // Print out verification commands + console.log("\nVerification Commands:\n"); + console.log( + "ActionRegistry: forge verify-contract --num-of-optimizations 200 --chain-id", + block.chainid, + predictedRegistryAddress + ); + } +} diff --git a/packages/contracts/script/DeployGardenAccount.s.sol b/packages/contracts/script/DeployGardenAccount.s.sol deleted file mode 100644 index c5ed151..0000000 --- a/packages/contracts/script/DeployGardenAccount.s.sol +++ /dev/null @@ -1,130 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -/* solhint-disable max-line-length */ - -pragma solidity ^0.8.25; - -import { Script, console } from "forge-std/Script.sol"; -import { AccountProxy } from "@tokenbound/AccountProxy.sol"; -import { AccountGuardian } from "@tokenbound/AccountGuardian.sol"; -import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; -import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol"; - -import { GardenToken } from "../src/tokens/Garden.sol"; -import { GardenAccount } from "../src/accounts/Garden.sol"; -import { TOKENBOUND_REGISTRY } from "../src/Constants.sol"; - -contract Deploy is Script { - function run() external { - bytes32 salt = 0x6551655165516551655165516551655165516551655165516551655165516551; - address factory = 0x4e59b44847b379578588920cA78FbF26c0B4956C; - - address tokenboundSafe = 0x1B9Ac97Ea62f69521A14cbe6F45eb24aD6612C19; // ToDo: Deploy with same address on Sepolia - address erc4337EntryPoint = 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789; - address multicallForwarder = 0xcA11bde05977b3631167028862bE2a173976CA11; - - address guardian = Create2.computeAddress( - salt, - keccak256( - abi.encodePacked(type(AccountGuardian).creationCode, abi.encode(tokenboundSafe)) - ), - factory - ); - address implementation = Create2.computeAddress( - salt, - keccak256( - abi.encodePacked( - type(GardenAccount).creationCode, - abi.encode(erc4337EntryPoint, multicallForwarder, TOKENBOUND_REGISTRY, guardian) - ) - ), - factory - ); - address proxy = Create2.computeAddress( - salt, - keccak256( - abi.encodePacked( - type(AccountProxy).creationCode, abi.encode(guardian, implementation) - ) - ), - factory - ); - - // Deploy AccountGuardian - if (guardian.code.length == 0) { - vm.startBroadcast(); - new AccountGuardian{salt: salt}(tokenboundSafe); - vm.stopBroadcast(); - - console.log("AccountGuardian:", guardian, "(deployed)"); - } else { - console.log("AccountGuardian:", guardian, "(exists)"); - } - - // Deploy Account implementation - if (implementation.code.length == 0) { - vm.startBroadcast(); - new GardenAccount{salt: salt}( - erc4337EntryPoint, - multicallForwarder, - TOKENBOUND_REGISTRY, - guardian - ); - vm.stopBroadcast(); - - console.log("GardenAccount:", implementation, "(deployed)"); - } else { - console.log("GardenAccount:", implementation, "(exists)"); - } - - // Deploy AccountProxy - if (proxy.code.length == 0) { - vm.startBroadcast(); - new AccountProxy{salt: salt}(guardian, implementation); - vm.stopBroadcast(); - - console.log("AccountProxy:", proxy, "(deployed)"); - } else { - console.log("AccountProxy:", proxy, "(exists)"); - } - - console.log("\nVerification Commands:\n"); - console.log( - "AccountGuardian: forge verify-contract --num-of-optimizations 200 --chain-id", - block.chainid, - guardian, - string.concat( - "src/AccountGuardian.sol:AccountGuardian --constructor-args $(cast abi-encode \"constructor(address)\" ", - Strings.toHexString(tokenboundSafe), - ")\n" - ) - ); - console.log( - "GardenAccount: forge verify-contract --num-of-optimizations 200 --chain-id", - block.chainid, - implementation, - string.concat( - "src/GardenAccount.sol:GardenAccount --constructor-args $(cast abi-encode \"constructor(address,address,address,address)\" ", - Strings.toHexString(erc4337EntryPoint), - " ", - Strings.toHexString(multicallForwarder), - " ", - Strings.toHexString(TOKENBOUND_REGISTRY), - " ", - Strings.toHexString(guardian), - ")\n" - ) - ); - console.log( - "AccountProxy: forge verify-contract --num-of-optimizations 200 --chain-id", - block.chainid, - proxy, - string.concat( - "src/AccountProxy.sol:AccountProxy --constructor-args $(cast abi-encode \"constructor(address,address)\" ", - Strings.toHexString(guardian), - " ", - Strings.toHexString(implementation), - ")\n" - ) - ); - } -} diff --git a/packages/contracts/script/DeployGardenToken.s.sol b/packages/contracts/script/DeployGardenToken.s.sol new file mode 100644 index 0000000..128218d --- /dev/null +++ b/packages/contracts/script/DeployGardenToken.s.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: UNLICENSED +/* solhint-disable max-line-length */ +/* solhint-disable quotes */ +pragma solidity ^0.8.25; + +import { Script, console } from "forge-std/Script.sol"; +import { AccountProxy } from "@tokenbound/AccountProxy.sol"; +import { AccountGuardian } from "@tokenbound/AccountGuardian.sol"; +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol"; + +import { GardenToken } from "../src/tokens/Garden.sol"; +import { GardenAccount } from "../src/accounts/Garden.sol"; +import { CommunityTokenLib } from "../src/lib/CommunityToken.sol"; + +import { TOKENBOUND_REGISTRY, GREEN_GOODS_SAFE, FACTORY, SALT } from "../src/Constants.sol"; + +/// @title DeployGardenToken +/// @notice Script for deploying the GardenToken contract and minting a garden for Rio Claro, São Paulo. +contract DeployGardenToken is Script { + function run() external { + address erc4337EntryPoint = 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789; + address multicallForwarder = 0xcA11bde05977b3631167028862bE2a173976CA11; + + address gardenAccount; + + // Compute addresses + address guardian = Create2.computeAddress( + SALT, + keccak256(abi.encodePacked(type(AccountGuardian).creationCode, abi.encode(GREEN_GOODS_SAFE))), + FACTORY + ); + address implementation = Create2.computeAddress( + SALT, + keccak256( + abi.encodePacked( + type(GardenAccount).creationCode, + abi.encode(erc4337EntryPoint, multicallForwarder, TOKENBOUND_REGISTRY, guardian) + ) + ), + FACTORY + ); + address proxy = Create2.computeAddress( + SALT, + keccak256(abi.encodePacked(type(AccountProxy).creationCode, abi.encode(guardian, implementation))), + FACTORY + ); + address token = Create2.computeAddress( + SALT, + keccak256(abi.encodePacked(type(GardenToken).creationCode, abi.encode(implementation))), + FACTORY + ); + + // Deploy AccountGuardian + if (guardian.code.length == 0) { + vm.startBroadcast(); + new AccountGuardian{ salt: SALT }(GREEN_GOODS_SAFE); + vm.stopBroadcast(); + console.log("AccountGuardian deployed at:", guardian); + } else { + console.log("AccountGuardian already exists at:", guardian); + } + + // Deploy GardenAccount implementation + if (implementation.code.length == 0) { + vm.startBroadcast(); + new GardenAccount{ salt: SALT }(erc4337EntryPoint, multicallForwarder, TOKENBOUND_REGISTRY, guardian); + vm.stopBroadcast(); + console.log("GardenAccount deployed at:", implementation); + } else { + console.log("GardenAccount already exists at:", implementation); + } + + // Deploy AccountProxy + if (proxy.code.length == 0) { + vm.startBroadcast(); + new AccountProxy{ salt: SALT }(guardian, implementation); + vm.stopBroadcast(); + console.log("AccountProxy deployed at:", proxy); + } else { + console.log("AccountProxy already exists at:", proxy); + } + + // Deploy GardenToken + if (token.code.length == 0) { + vm.startBroadcast(); + GardenToken gardenToken = new GardenToken{ salt: SALT }(implementation); + gardenToken.initialize(address(this)); + + address communityToken = CommunityTokenLib.getCommunityToken(); + console.log("GardenToken deployed at:", token); + + // Mint a garden for Rio Claro, São Paulo + address[] memory gardeners = new address[](1); + address[] memory gardenOperators = new address[](1); + + gardeners[0] = 0x2aa64E6d80390F5C017F0313cB908051BE2FD35e; // afo-wefa.eth + gardenOperators[0] = 0x2aa64E6d80390F5C017F0313cB908051BE2FD35e; // afo-wefa.eth + gardenAccount = gardenToken.mintGarden(communityToken, "Root Planet", gardeners, gardenOperators); + + vm.stopBroadcast(); + console.log("Root Plane Garden for Rio Claro, S\u00e3o Paulo minted."); + } else { + console.log("Garden Token already exists at:", token); + } + + // Print out verification commands + console.log("\nVerification Commands:\n"); + + console.log( + "AccountGuardian: forge verify-contract --num-of-optimizations 200 --chain-id", + block.chainid, + guardian, + string.concat( + 'src/AccountGuardian.sol:AccountGuardian --constructor-args $(cast abi-encode "constructor(address)" ', + Strings.toHexString(GREEN_GOODS_SAFE), + ")\n" + ) + ); + console.log( + "GardenAccount: forge verify-contract --num-of-optimizations 200 --chain-id", + block.chainid, + implementation, + string.concat( + 'src/GardenAccount.sol:GardenAccount --constructor-args $(cast abi-encode "constructor(address,address,address,address)" ', + Strings.toHexString(erc4337EntryPoint), + " ", + Strings.toHexString(multicallForwarder), + " ", + Strings.toHexString(TOKENBOUND_REGISTRY), + " ", + Strings.toHexString(guardian), + ")\n" + ) + ); + console.log( + "AccountProxy: forge verify-contract --num-of-optimizations 200 --chain-id", + block.chainid, + proxy, + string.concat( + 'src/AccountProxy.sol:AccountProxy --constructor-args $(cast abi-encode "constructor(address,address)" ', + Strings.toHexString(guardian), + " ", + Strings.toHexString(implementation), + ")\n" + ) + ); + console.log( + "GardenToken: forge verify-contract --num-of-optimizations 200 --chain-id", + block.chainid, + string.concat( + 'src/GardenToken.sol:GardenToken --constructor-args $(cast abi-encode "constructor(address)" ', + Strings.toHexString(implementation), + "", + Strings.toHexString(gardenAccount), + ")\n" + ) + ); + } +} diff --git a/packages/contracts/script/DeployResolvers.s.sol b/packages/contracts/script/DeployResolvers.s.sol new file mode 100644 index 0000000..645a77e --- /dev/null +++ b/packages/contracts/script/DeployResolvers.s.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: UNLICENSED +/* solhint-disable max-line-length */ +/* solhint-disable quotes */ +pragma solidity ^0.8.25; + +// import { ISchemaRegistry } from "@eas/IEAS.sol"; +import { Script, console } from "forge-std/Script.sol"; +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol"; + +import { EASLib } from "../src/lib/EAS.sol"; +// import { WorkSchema, WorkApprovalSchema } from "../src/Schemas.sol"; +import { ACTION_REGISTRY, FACTORY, SALT } from "../src/Constants.sol"; +import { WorkResolver } from "../src/resolvers/Work.sol"; +import { WorkApprovalResolver } from "../src/resolvers/WorkApproval.sol"; + +/// @title DeployResolvers +/// @notice Script for deploying the WorkResolver and WorkApprovalResolver contracts using CREATE2. +contract DeployResolvers is Script { + function run() public { + address eas = EASLib.getEAS(); + // address schemaRegistry = EASLib.getSchemaRegistry(); + + // Calculate the CREATE2 addresses for the resolvers + address predictedWorkResolverAddress = Create2.computeAddress( + SALT, + keccak256(abi.encodePacked(type(WorkResolver).creationCode, abi.encode(eas, ACTION_REGISTRY))), + FACTORY + ); + + address predictedWorkApprovalResolverAddress = Create2.computeAddress( + SALT, + keccak256(abi.encodePacked(type(WorkApprovalResolver).creationCode, abi.encode(eas, ACTION_REGISTRY))), + FACTORY + ); + + // Deploy WorkResolver + if (predictedWorkResolverAddress.code.length == 0) { + vm.startBroadcast(); + + WorkResolver workResolver = new WorkResolver{ salt: SALT }(eas, ACTION_REGISTRY); + workResolver.initialize(address(this)); + + // bytes32 workSchemaUID = ISchemaRegistry(schemaRegistry).register( + // abi.encode(WorkSchema), + // WorkResolver(predictedWorkResolverAddress), + // true + // ); + + vm.stopBroadcast(); + + console.log("WorkResolver deployed at:", predictedWorkResolverAddress); + // console.log("WorkSchema UID:", workSchemaUID); + } else { + console.log("WorkResolver already exists at:", predictedWorkResolverAddress); + } + + // Deploy WorkApprovalResolvers + if (predictedWorkApprovalResolverAddress.code.length == 0) { + vm.startBroadcast(); + + WorkApprovalResolver workApprovalResolver = new WorkApprovalResolver{ salt: SALT }(eas, ACTION_REGISTRY); + workApprovalResolver.initialize(address(this)); + + // bytes32 workApprovalSchemaUID = ISchemaRegistry(schemaRegistry).register( + // abi.encode(WorkApprovalSchema), + // predictedWorkApprovalResolverAddress, + // true + // ); + + vm.stopBroadcast(); + + console.log("WorkApprovalResolver deployed at:", predictedWorkApprovalResolverAddress); + // console.log("WorkApprovalSchema UID:", workApprovalSchemaUID); + } else { + console.log("WorkApprovalResolver already exists at:", predictedWorkApprovalResolverAddress); + } + + // Print out verification commands + console.log("\nVerification Commands:\n"); + + console.log( + "WorkResolver: forge verify-contract --num-of-optimizations 200 --chain-id", + block.chainid, + predictedWorkResolverAddress, + string.concat( + 'src/resolvers/Work.sol:WorkResolver --constructor-args $(cast abi-encode "constructor(address,address)" ', + Strings.toHexString(eas), + ", ", + Strings.toHexString(ACTION_REGISTRY), + ")" + ) + ); + + console.log( + "WorkApprovalResolver: forge verify-contract --num-of-optimizations 200 --chain-id", + block.chainid, + predictedWorkApprovalResolverAddress, + string.concat( + 'src/resolvers/WorkApproval.sol:WorkApprovalResolver --constructor-args $(cast abi-encode "constructor(address,address)" ', + Strings.toHexString(eas), + ", ", + Strings.toHexString(ACTION_REGISTRY), + ")" + ) + ); + } +} diff --git a/packages/contracts/src/Constants.sol b/packages/contracts/src/Constants.sol index d489403..b5192e2 100644 --- a/packages/contracts/src/Constants.sol +++ b/packages/contracts/src/Constants.sol @@ -1,23 +1,49 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; +// GREEN GOODS +address constant GREEN_GOODS_SAFE = 0x1B9Ac97Ea62f69521A14cbe6F45eb24aD6612C19; +address constant ACTION_REGISTRY = 0x70Df51173B3EF27A245e1a0F129e2BAab39A937E; +address constant WORK_RESOLVER = 0x380217CB03B2AA6838C2B6F615F36C677D7922dB; +address constant WORK_APPROVAL_RESOLVER = 0xECdD5C72D468b2b1d0566102050C42e99A37Ca14; +address constant COMMUNITY_TOKEN_ARBITRUM = 0x633d825006E4c659b061db7FB9378eDEe8bd95f3; +address constant COMMUNITY_TOKEN_SEPOLIA = 0x4cB67033da4FD849a552A4C5553E7F532B93E516; + +address constant GARDEN_TOKEN = 0x5D29C573581270d2bF436382820B3b64904AEeD7; +address constant GARDEN_ACCOUNT_IMPLEMENTATION = 0x5c4FFaAa4aA538D75f531Ebd5b9B08F37d9a65a2; +address constant GARDENER_ACCOUNT_IMPLEMENTATION = 0x0000000000000000000000000000000000000000; + // TOKENBOUND (FUTURE PRIMTIVE) -address constant TOKENBOUND_REGISTRY = 0x002c0c13181038780F552f0eC1B72e8C720147E6; // Same address on all EVM chains -address constant TOKENBOUND_ACCOUNT = 0x9FFDEb36540e1a12b1F27751508715174122C090; // Same address on all EVM chains +bytes32 constant SALT = 0x6551655165516551655165516551655165516551655165516551655165516551; +address constant FACTORY = 0x4e59b44847b379578588920cA78FbF26c0B4956C; +address constant TOKENBOUND_REGISTRY = 0x000000006551c19487814612e58FE06813775758; // Same address on all EVM chains +address constant TOKENBOUND_ACCOUNT = 0x41C8f39463A868d3A88af00cd0fe7102F30E44eC; // Same address on all EVM chains // EAS (ETHEREUM ATTESTATION SERVICE) -address constant EAS_OP = 0x4200000000000000000000000000000000000021; // Any OP Stack deployed - -bytes32 constant actionSchemaUid = 0xfcd67741f543211da27d50ef5b1f3d0a648a9760cc84414b037c1c011ed883a6; -bytes32 constant workSchemaUid = 0xb6e2f5905e3cce83d9713559ae8931940ac487fd4467a5134d8501eb2e339e81; -bytes32 constant workApprovalSchemaUid = 0xb6e2f5905e3cce83d9713559ae8931940ac487fd4467a5134d8501eb2e339e81; - -address constant ActionResolver = 0xa547526412e87fBAD5B483bd17F6540a1dC686fd; -address constant WorkResolver = 0xd76a4D50F1CcaD941B85692Dc6681b35bC6B480c; -address constant WorkApprovalResolver = 0xd76a4D50F1CcaD941B85692Dc6681b35bC6B480c; - -// BASE -address constant BasePaymaster = 0xf5d253B62543C6Ef526309D497f619CeF95aD430; - - - +address constant EAS_ARBITRUM = 0xbD75f629A22Dc1ceD33dDA0b68c546A1c035c458; +address constant EAS_SEPOLIA = 0xC2679fBD37d54388Ce493F1DB75320D236e1815e; + +address constant EAS_SCHEMA_REGISTRY_ARBITRUM = 0xA310da9c5B885E7fb3fbA9D66E9Ba6Df512b78eB; +address constant EAS_SCHEMA_REGISTRY_SEPOLIA = 0x0a7E2Ff54e76B8E6659aedc9103FB21c038050D0; + +// SAFE +address constant SAFE = 0x29fcB43b46531BcA003ddC8FCB67FFE91900C762; +address constant SAFE_FACTORY = 0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67; +address constant SAFE_4337_MODULE = 0xa581c4A4DB7175302464fF3C06380BC3270b4037; + +// ERROR MESSAGES +error NotGardenAccount(); +error NotGardenerAccount(); +error NotInActionRegistry(); + +// ENUMS +enum Capital { + SOCIAL, + MATERIAL, + FINANCIAL, + LIVING, + INTELLECTUAL, + EXPERIENTIAL, + SPIRITUAL, + CULTURAL +} diff --git a/packages/contracts/src/Schemas.sol b/packages/contracts/src/Schemas.sol new file mode 100644 index 0000000..29d013f --- /dev/null +++ b/packages/contracts/src/Schemas.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +struct AsessmentSchema { + string title; + string[] media; +} + +struct WorkSchema { + uint256 actionUID; + string title; + string feedback; + string metadata; + string[] media; +} + +struct WorkApprovalSchema { + uint256 actionUID; + bytes32 workUID; + bool approved; + string feedback; +} diff --git a/packages/contracts/src/accounts/Garden.sol b/packages/contracts/src/accounts/Garden.sol index 7937f77..4f54a18 100644 --- a/packages/contracts/src/accounts/Garden.sol +++ b/packages/contracts/src/accounts/Garden.sol @@ -5,16 +5,54 @@ import { AccountV3Upgradable } from "@tokenbound/AccountV3Upgradable.sol"; import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; error NotGardenOwner(); -error TransferNotStarted(); -error NotGoodTransferResolver(); +/// @title GardenAccount Contract +/// @notice Manages gardeners and operators for a Garden, and supports community token management. +/// @dev Inherits from AccountV3Upgradable and uses OpenZeppelin's Initializable for upgradability. contract GardenAccount is AccountV3Upgradable, Initializable { + /// @notice Emitted when the garden name is updated. + /// @param updater The address of the entity that updated the name. + /// @param newName The new name of the garden. + event NameUpdated(address indexed updater, string newName); + + /// @notice Emitted when a new gardener is added. + /// @param updater The address of the entity that added the gardener. + /// @param gardener The address of the added gardener. + event GardenerAdded(address indexed updater, address indexed gardener); + + /// @notice Emitted when a gardener is removed. + /// @param updater The address of the entity that removed the gardener. + /// @param gardener The address of the removed gardener. + event GardenerRemoved(address indexed updater, address indexed gardener); + + /// @notice Emitted when a new garden operator is added. + /// @param updater The address of the entity that added the operator. + /// @param operator The address of the added garden operator. + event GardenOperatorAdded(address indexed updater, address indexed operator); + + /// @notice Emitted when a garden operator is removed. + /// @param updater The address of the entity that removed the operator. + /// @param operator The address of the removed garden operator. + event GardenOperatorRemoved(address indexed updater, address indexed operator); + + /// @notice The community token associated with this garden. address public communityToken; + + /// @notice The name of the garden. string public name; - mapping(address gardener => bool isGardener) private gardeners; - mapping(address operator => bool isOperator) private gardenOperators; + /// @notice Mapping of gardener addresses to their status. + mapping(address gardener => bool isGardener) public gardeners; + + /// @notice Mapping of garden operator addresses to their status. + mapping(address operator => bool isOperator) public gardenOperators; + /// @notice Initializes the contract with the necessary dependencies. + /// @dev This constructor is for the upgradable pattern and uses Initializable for upgrade safety. + /// @param erc4337EntryPoint The entry point address for ERC-4337 operations. + /// @param multicallForwarder The forwarder address for multicall operations. + /// @param erc6551Registry The registry address for ERC-6551. + /// @param guardian The guardian address for security-related functions. constructor( address erc4337EntryPoint, address multicallForwarder, @@ -22,60 +60,96 @@ contract GardenAccount is AccountV3Upgradable, Initializable { address guardian ) AccountV3Upgradable(erc4337EntryPoint, multicallForwarder, erc6551Registry, guardian) {} + /// @notice Initializes the GardenAccount with initial gardeners and operators. + /// @dev This function must be called after the contract is deployed. + /// @param _communityToken The address of the community token associated with the garden. + /// @param _name The name of the garden. + /// @param _gardeners An array of addresses representing the initial gardeners. + /// @param _gardenOperators An array of addresses representing the initial garden operators. function initialize( address _communityToken, string calldata _name, address[] calldata _gardeners, - address[] calldata _gardenOperators) external initializer { + address[] calldata _gardenOperators + ) external initializer { communityToken = _communityToken; name = _name; for (uint256 i = 0; i < _gardeners.length; i++) { gardeners[_gardeners[i]] = true; + emit GardenerAdded(_msgSender(), _gardeners[i]); } for (uint256 i = 0; i < _gardenOperators.length; i++) { gardenOperators[_gardenOperators[i]] = true; + emit GardenOperatorAdded(_msgSender(), _gardenOperators[i]); } + + emit NameUpdated(_msgSender(), _name); } + /// @notice Updates the name of the garden. + /// @dev Only callable by a valid signer of the contract. + /// @param _name The new name of the garden. function updateName(string memory _name) external { - if (_isValidSigner(msg.sender, "")) { + if (_isValidSigner(_msgSender(), "")) { revert NotGardenOwner(); } name = _name; + + emit NameUpdated(_msgSender(), _name); } + /// @notice Adds a new gardener to the garden. + /// @dev Only callable by a valid signer of the contract. + /// @param gardener The address of the gardener to add. function addGardener(address gardener) external { - if (_isValidSigner(msg.sender, "")) { + if (_isValidSigner(_msgSender(), "")) { revert NotGardenOwner(); } gardeners[gardener] = true; + + emit GardenerAdded(_msgSender(), gardener); } + /// @notice Removes an existing gardener from the garden. + /// @dev Only callable by a valid signer of the contract. + /// @param gardener The address of the gardener to remove. function removeGardener(address gardener) external { - if (_isValidSigner(msg.sender, "")) { + if (_isValidSigner(_msgSender(), "")) { revert NotGardenOwner(); } gardeners[gardener] = false; + + emit GardenerRemoved(_msgSender(), gardener); } + /// @notice Adds a new operator to the garden. + /// @dev Only callable by a valid signer of the contract. + /// @param operator The address of the operator to add. function addGardenOperator(address operator) external { - if (_isValidSigner(msg.sender, "")) { + if (_isValidSigner(_msgSender(), "")) { revert NotGardenOwner(); } gardenOperators[operator] = true; + + emit GardenOperatorAdded(_msgSender(), operator); } + /// @notice Removes an existing operator from the garden. + /// @dev Only callable by a valid signer of the contract. + /// @param operator The address of the operator to remove. function removeGardenOperator(address operator) external { - if (_isValidSigner(msg.sender, "")) { + if (_isValidSigner(_msgSender(), "")) { revert NotGardenOwner(); } gardenOperators[operator] = false; + + emit GardenOperatorRemoved(_msgSender(), operator); } } diff --git a/packages/contracts/src/interfaces/IERC6551Registry.sol b/packages/contracts/src/interfaces/IERC6551Registry.sol index 8067760..e4d6b36 100644 --- a/packages/contracts/src/interfaces/IERC6551Registry.sol +++ b/packages/contracts/src/interfaces/IERC6551Registry.sol @@ -1,30 +1,51 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.25; interface IERC6551Registry { - event AccountCreated( + /** + * @dev The registry MUST emit the ERC6551AccountCreated event upon successful account creation. + */ + event ERC6551AccountCreated( address account, - address implementation, + address indexed implementation, + bytes32 salt, uint256 chainId, - address tokenContract, - uint256 tokenId, - uint256 salt + address indexed tokenContract, + uint256 indexed tokenId ); + /** + * @dev The registry MUST revert with AccountCreationFailed error if the create2 operation fails. + */ + error AccountCreationFailed(); + + /** + * @dev Creates a token bound account for a non-fungible token. + * + * If account has already been created, returns the account address without calling create2. + * + * Emits ERC6551AccountCreated event. + * + * @return account The address of the token bound account + */ function createAccount( address implementation, + bytes32 salt, uint256 chainId, address tokenContract, - uint256 tokenId, - uint256 seed, - bytes calldata initData - ) external returns (address); + uint256 tokenId + ) external returns (address account); + /** + * @dev Returns the computed token bound account address for a non-fungible token. + * + * @return account The address of the token bound account + */ function account( address implementation, + bytes32 salt, uint256 chainId, address tokenContract, - uint256 tokenId, - uint256 salt - ) external view returns (address); + uint256 tokenId + ) external view returns (address account); } diff --git a/packages/contracts/src/lib/CommunityToken.sol b/packages/contracts/src/lib/CommunityToken.sol new file mode 100644 index 0000000..b9e8e81 --- /dev/null +++ b/packages/contracts/src/lib/CommunityToken.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { COMMUNITY_TOKEN_ARBITRUM, COMMUNITY_TOKEN_SEPOLIA } from "../Constants.sol"; + +error InvalidChainId(); + +/// @title CommunityTokenLib +/// @notice A library to retrieve the community token address based on the current chain ID. +/// @dev This library uses the block's chain ID to determine the appropriate community token. +library CommunityTokenLib { + /// @notice Returns the community token address based on the current chain ID. + /// @dev Reverts with `InvalidChainId` if the chain ID is not recognized. + /// @return The address of the community token. + function getCommunityToken() internal view returns (address) { + if (block.chainid == 42161) { + return COMMUNITY_TOKEN_ARBITRUM; + } else if (block.chainid == 11155111) { + return COMMUNITY_TOKEN_SEPOLIA; + } else { + revert InvalidChainId(); + } + } +} diff --git a/packages/contracts/src/lib/EAS.sol b/packages/contracts/src/lib/EAS.sol new file mode 100644 index 0000000..d2dca39 --- /dev/null +++ b/packages/contracts/src/lib/EAS.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { EAS_ARBITRUM, EAS_SEPOLIA, EAS_SCHEMA_REGISTRY_ARBITRUM, EAS_SCHEMA_REGISTRY_SEPOLIA } from "../Constants.sol"; + +error InvalidChainId(); + +/// @title EASLib +/// @notice A library to retrieve the EAS address based on the current chain ID. +/// @dev This library uses the block's chain ID to determine the appropriate EAS address. +library EASLib { + /// @notice Returns the EAS address based on the current chain ID. + /// @dev Reverts with `InvalidChainId` if the chain ID is not recognized. + /// @return The address of the EAS. + function getEAS() internal view returns (address) { + if (block.chainid == 42161) { + return EAS_ARBITRUM; + } else if (block.chainid == 11155111) { + return EAS_SEPOLIA; + } else { + revert InvalidChainId(); + } + } + + function getSchemaRegistry() internal view returns (address) { + if (block.chainid == 42161) { + return EAS_SCHEMA_REGISTRY_ARBITRUM; + } else if (block.chainid == 11155111) { + return EAS_SCHEMA_REGISTRY_SEPOLIA; + } else { + revert InvalidChainId(); + } + } +} diff --git a/packages/contracts/src/lib/TBA.sol b/packages/contracts/src/lib/TBA.sol index a740940..f6774af 100644 --- a/packages/contracts/src/lib/TBA.sol +++ b/packages/contracts/src/lib/TBA.sol @@ -1,65 +1,58 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {TOKENBOUND_REGISTRY} from "../Constants.sol"; -import {IERC6551Registry} from "../interfaces/IERC6551Registry.sol"; +import { TOKENBOUND_REGISTRY, SALT } from "../Constants.sol"; +import { IERC6551Registry } from "../interfaces/IERC6551Registry.sol"; error InvalidChainId(); +/// @title TBALib +/// @notice A library for interacting with Token Bound Accounts (TBA) on different chains. +/// @dev This library handles the creation and retrieval of TBA accounts based on the current chain ID. library TBALib { - function createAccount(address implmentation, address tokenContract, uint256 tokenId) external returns (address) { - address account; - - if (block.chainid == 42161) { - account = IERC6551Registry(TOKENBOUND_REGISTRY).createAccount( - implmentation, - 42161, - tokenContract, - tokenId, - 7, - "" - ); - - } else if (block.chainid == 11155111) { - account = IERC6551Registry(TOKENBOUND_REGISTRY).createAccount( - implmentation, - 11155111, - tokenContract, - tokenId, - 7, - "" - ); + /// @notice Creates a TBA account based on the current chain ID. + /// @dev Reverts with `InvalidChainId` if the chain ID is not recognized. + /// @param implementation The address of the TBA implementation contract. + /// @param tokenContract The address of the token contract associated with the TBA. + /// @param tokenId The ID of the token associated with the TBA. + /// @return The address of the created TBA account. + function createAccount(address implementation, address tokenContract, uint256 tokenId) external returns (address) { + if (block.chainid == 42161 || block.chainid == 11155111) { + return + IERC6551Registry(TOKENBOUND_REGISTRY).createAccount( + implementation, + SALT, + block.chainid, + tokenContract, + tokenId + ); } else { revert InvalidChainId(); } - - return account; } - function getAccount(address implmentation, address tokenContract, uint256 tokenId) external view returns (address) { - address account; - - if (block.chainid == 42161) { - account = IERC6551Registry(TOKENBOUND_REGISTRY).account( - implmentation, - 42161, - tokenContract, - tokenId, - 7 - ); - - } else if (block.chainid == 11155111) { - account = IERC6551Registry(TOKENBOUND_REGISTRY).account( - implmentation, - 11155111, - tokenContract, - tokenId, - 7 - ); + /// @notice Retrieves a TBA account based on the current chain ID. + /// @dev Reverts with `InvalidChainId` if the chain ID is not recognized. + /// @param implementation The address of the TBA implementation contract. + /// @param tokenContract The address of the token contract associated with the TBA. + /// @param tokenId The ID of the token associated with the TBA. + /// @return The address of the TBA account. + function getAccount( + address implementation, + address tokenContract, + uint256 tokenId + ) external view returns (address) { + if (block.chainid == 42161 || block.chainid == 11155111) { + return + IERC6551Registry(TOKENBOUND_REGISTRY).account( + implementation, + SALT, + block.chainid, + tokenContract, + tokenId + ); } else { revert InvalidChainId(); } - - return account; } } diff --git a/packages/contracts/src/mocks/EAS.sol b/packages/contracts/src/mocks/EAS.sol new file mode 100644 index 0000000..733d811 --- /dev/null +++ b/packages/contracts/src/mocks/EAS.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.25; + +import { Attestation } from "@eas/IEAS.sol"; + +contract MockEAS { + mapping(uint256 id => Attestation attestation) private attestations; + + function setAttestation(uint256 id, Attestation memory attestation) public { + attestations[id] = attestation; + } + + function getAttestation(uint256 id) public view returns (Attestation memory) { + return attestations[id]; + } +} diff --git a/packages/contracts/src/registries/Action.sol b/packages/contracts/src/registries/Action.sol new file mode 100644 index 0000000..f6f3f36 --- /dev/null +++ b/packages/contracts/src/registries/Action.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +import { Capital } from "../Constants.sol"; + +error NotActionOwner(); + +/// @title Action Registry Contract +/// @notice This contract allows the owner to register and manage actions. +/// @dev This contract is upgradeable using the UUPS proxy pattern. +contract ActionRegistry is UUPSUpgradeable, OwnableUpgradeable { + /// @dev Represents an action with its metadata. + struct Action { + uint256 startTime; + uint256 endTime; + string title; + string instructions; + Capital[] capitals; + string[] media; + } + + /// @notice Emitted when a new action is registered. + /// @param owner The address of the action owner. + /// @param action The details of the registered action. + event ActionRegistered(address indexed owner, Action indexed action); + + /// @notice Emitted when an action is updated. + /// @param owner The address of the action owner. + /// @param action The updated details of the action. + event ActionUpdated(address indexed owner, Action indexed action); + + uint256 private _nextActionUID; + + mapping(uint256 actionUID => address owner) public actionToOwner; + mapping(uint256 actionUID => Action action) public idToAction; + + modifier onlyActionOwner(uint256 actionUID) { + if (_msgSender() != actionToOwner[actionUID]) { + revert NotActionOwner(); + } + _; + } + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() {} + + /// @notice Initializes the contract and sets the multisig wallet as the owner. + /// @dev This function must be called only once during contract deployment. + /// @param _multisig The address of the multisig wallet to transfer ownership to. + function initialize(address _multisig) external initializer { + __Ownable_init(); + // transferOwnership(_multisig); + // _disableInitializers(); + } + + function getAction(uint256 actionUID) external view returns (Action memory) { + return idToAction[actionUID]; + } + + /// @notice Registers a new action with the specified parameters. + /// @param _startTime The start time of the action. + /// @param _endTime The end time of the action. + /// @param _instructions The CID JSON instructions for the action. + /// @param _capitals An array of Capital structs associated with the action. + /// @param _media An array of media CIDs associated with the action. + function registerAction( + uint256 _startTime, + uint256 _endTime, + string calldata _title, + string calldata _instructions, + Capital[] calldata _capitals, + string[] calldata _media + ) external onlyOwner { + uint256 actionUID = _nextActionUID++; + + actionToOwner[actionUID] = _msgSender(); + idToAction[actionUID] = Action(_startTime, _endTime, _title, _instructions, _capitals, _media); + + emit ActionRegistered(_msgSender(), idToAction[actionUID]); + } + + /// @notice Updates the start time of an existing action. + /// @param actionUID The unique identifier of the action to update. + /// @param _startTime The new start time of the action. + function updateActionStartTime(uint256 actionUID, uint256 _startTime) external onlyActionOwner(actionUID) { + idToAction[actionUID].startTime = _startTime; + + emit ActionUpdated(actionToOwner[actionUID], idToAction[actionUID]); + } + + /// @notice Updates the end time of an existing action. + /// @param actionUID The unique identifier of the action to update. + /// @param _endTime The new end time of the action. + function updateActionEndTime(uint256 actionUID, uint256 _endTime) external onlyActionOwner(actionUID) { + idToAction[actionUID].endTime = _endTime; + + emit ActionUpdated(actionToOwner[actionUID], idToAction[actionUID]); + } + + /// @notice Updates the title of an existing action. + /// @param actionUID The unique identifier of the action to update. + /// @param _title The new title for the action. + function updateActionTitle(uint256 actionUID, string calldata _title) external onlyActionOwner(actionUID) { + idToAction[actionUID].title = _title; + + emit ActionUpdated(actionToOwner[actionUID], idToAction[actionUID]); + } + + /// @notice Updates the instructions for an existing action. + /// @param actionUID The unique identifier of the action to update. + /// @param _instructions The new instructions for the action. + function updateActionInstructions( + uint256 actionUID, + string calldata _instructions + ) external onlyActionOwner(actionUID) { + idToAction[actionUID].instructions = _instructions; + + emit ActionUpdated(actionToOwner[actionUID], idToAction[actionUID]); + } + + /// @notice Updates the media associated with an existing action. + /// @param actionUID The unique identifier of the action to update. + /// @param _media The new array of media URLs to associate with the action. + function updateActionMedia(uint256 actionUID, string[] memory _media) external onlyActionOwner(actionUID) { + idToAction[actionUID].media = _media; + + emit ActionUpdated(actionToOwner[actionUID], idToAction[actionUID]); + } + + /// @dev Authorizes an upgrade to the contract's implementation. + /// @param newImplementation The address of the new implementation contract. + /// @custom:oz-upgrades-unsafe-allow override + function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} +} diff --git a/packages/contracts/src/resolvers/Work.sol b/packages/contracts/src/resolvers/Work.sol new file mode 100644 index 0000000..6b4a5f3 --- /dev/null +++ b/packages/contracts/src/resolvers/Work.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { IEAS, Attestation } from "@eas/IEAS.sol"; +import { SchemaResolver } from "@eas/resolver/SchemaResolver.sol"; +import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +import { WorkSchema } from "../Schemas.sol"; +import { GardenAccount } from "../accounts/Garden.sol"; +import { ActionRegistry } from "../registries/Action.sol"; +import { NotGardenerAccount, NotInActionRegistry } from "../Constants.sol"; + +error NotActiveAction(); + +/// @title WorkResolver +/// @notice A schema resolver for the Actions event schema +/// @dev This contract is upgradable using the UUPS pattern and requires initialization. +contract WorkResolver is SchemaResolver, OwnableUpgradeable, UUPSUpgradeable { + address public actionRegistry; + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor(address easAddrs, address actionAddrs) SchemaResolver(IEAS(easAddrs)) { + actionRegistry = actionAddrs; + // _disableInitializers(); + } + + /// @notice Initializes the contract and sets the multisig wallet as the owner. + /// @dev This function replaces the constructor for upgradable contracts. + /// @param _multisig The address of the multisig wallet to transfer ownership to. + function initialize(address _multisig) external initializer { + __Ownable_init(); + // transferOwnership(_multisig); + } + + /// @notice Indicates whether the resolver is payable. + /// @dev This is a pure function that always returns true. + /// @return A boolean indicating that the resolver is payable. + function isPayable() public pure override returns (bool) { + return true; + } + + /// @notice Handles the logic to be executed when an attestation is made. + /// @dev Verifies the attester and the action's validity and active status. + /// @param attestation The attestation data structure. + /// @return A boolean indicating whether the attestation is valid. + function onAttest(Attestation calldata attestation, uint256 /*value*/) internal view override returns (bool) { + WorkSchema memory schema = abi.decode(attestation.data, (WorkSchema)); + + GardenAccount gardenAccount = GardenAccount(payable(attestation.recipient)); + + if (gardenAccount.gardeners(attestation.attester) == false) { + revert NotGardenerAccount(); + } + + if (ActionRegistry(actionRegistry).getAction(schema.actionUID).startTime == 0) { + revert NotInActionRegistry(); + } + + if (ActionRegistry(actionRegistry).getAction(schema.actionUID).endTime < block.timestamp) { + revert NotActiveAction(); + } + + return (true); + } + + // solhint-disable no-unused-vars + /// @notice Handles the logic to be executed when an attestation is revoked. + /// @dev This function can only be called by the contract owner. + /// @return A boolean indicating whether the revocation is valid. + function onRevoke( + Attestation calldata /*attestation*/, + uint256 /*value*/ + ) internal view override onlyOwner returns (bool) { + return true; + } + + /// @notice Authorizes an upgrade to the contract's implementation. + /// @dev This function can only be called by the contract owner. + /// @param newImplementation The address of the new contract implementation. + function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} +} diff --git a/packages/contracts/src/resolvers/WorkApproval.sol b/packages/contracts/src/resolvers/WorkApproval.sol new file mode 100644 index 0000000..2a299e0 --- /dev/null +++ b/packages/contracts/src/resolvers/WorkApproval.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { IEAS, Attestation } from "@eas/IEAS.sol"; +import { SchemaResolver } from "@eas/resolver/SchemaResolver.sol"; +import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +import { WorkApprovalSchema } from "../Schemas.sol"; +import { GardenAccount } from "../accounts/Garden.sol"; +import { ActionRegistry } from "../registries/Action.sol"; +import { NotInActionRegistry } from "../Constants.sol"; + +error NotInWorkRegistry(); +error NotGardenOperator(); + +/// @title WorkApprovalResolver +/// @notice A schema resolver for the Actions event schema +/// @dev This contract is upgradable using the UUPS pattern and requires initialization. +contract WorkApprovalResolver is SchemaResolver, OwnableUpgradeable, UUPSUpgradeable { + address public actionRegistry; + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor(address easAddrs, address actionAddrs) SchemaResolver(IEAS(easAddrs)) { + actionRegistry = actionAddrs; + // _disableInitializers(); + } + + /// @notice Initializes the contract and sets the multisig wallet as the owner. + /// @dev This function replaces the constructor for upgradable contracts. + /// @param _multisig The address of the multisig wallet to transfer ownership to. + function initialize(address _multisig) external initializer { + __Ownable_init(); + // transferOwnership(_multisig); + } + + /// @notice Indicates whether the resolver is payable. + /// @dev This is a pure function that always returns true. + /// @return A boolean indicating that the resolver is payable + function isPayable() public pure override returns (bool) { + return true; + } + + /// @notice Handles the logic to be executed when an attestation is made. + /// @dev Verifies the attester, work, and action's validity. + /// @param attestation The attestation data structure. + /// @return A boolean indicating whether the attestation is valid. + function onAttest(Attestation calldata attestation, uint256 /*value*/) internal view override returns (bool) { + WorkApprovalSchema memory schema = abi.decode(attestation.data, (WorkApprovalSchema)); + Attestation memory workAttestation = _eas.getAttestation(schema.workUID); + + if (workAttestation.attester != attestation.recipient) { + revert NotInWorkRegistry(); + } + + GardenAccount gardenAccount = GardenAccount(payable(workAttestation.recipient)); + + if (gardenAccount.gardenOperators(attestation.attester) == false) { + revert NotGardenOperator(); + } + + if (ActionRegistry(actionRegistry).getAction(schema.actionUID).startTime == 0) { + revert NotInActionRegistry(); + } + + return (true); + } + + // solhint-disable no-unused-vars + /// @notice Handles the logic to be executed when an attestation is revoked. + /// @dev This function can only be called by the contract owner. + /// @return A boolean indicating whether the revocation is valid. + function onRevoke( + Attestation calldata /*attestation*/, + uint256 /*value*/ + ) internal view override onlyOwner returns (bool) { + return true; + } + + /// @notice Authorizes an upgrade to the contract's implementation. + /// @dev This function can only be called by the contract owner. + /// @param newImplementation The address of the new contract implementation. + function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} +} diff --git a/packages/contracts/src/tokens/Garden.sol b/packages/contracts/src/tokens/Garden.sol index af0f82b..5cd38a1 100644 --- a/packages/contracts/src/tokens/Garden.sol +++ b/packages/contracts/src/tokens/Garden.sol @@ -1,33 +1,68 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.25; -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; -import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import { ERC721Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; import { TBALib } from "../lib/TBA.sol"; import { GardenAccount } from "../accounts/Garden.sol"; -contract GardenToken is Ownable, ERC721 { +/// @title GardenToken Contract +/// @notice This contract manages the minting of Garden tokens and the creation of associated Garden accounts. +/// @dev This contract is upgradable and follows the UUPS pattern. +contract GardenToken is ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable { uint256 private _nextTokenId; address private _gardenAccountImplementation; - constructor( - address gardenAccountImplementation - ) ERC721("Green Goods Garden", "GGG") { + /// @notice Emitted when a new Garden is minted. + /// @param owner The owner of the minted Garden token. + /// @param tokenId The unique identifier of the minted Garden token. + /// @param name The name of the Garden associated with the minted token. + event GardenMinted(address indexed owner, uint256 indexed tokenId, string name); + + /// @custom:oz-upgrades-unsafe-allow constructor + /// @param gardenAccountImplementation The address of the Garden account implementation. + constructor(address gardenAccountImplementation) ERC721Upgradeable() { _gardenAccountImplementation = gardenAccountImplementation; + // _disableInitializers(); // Prevent constructor usage for upgradable contracts + } + + /// @notice Initializes the contract with the given multisig wallet and Garden account implementation. + /// @dev This function replaces the constructor for upgradable contracts. + /// @param _multisig The address of the multisig wallet to set as the owner. + function initialize(address _multisig) external initializer { + __ERC721_init("Green Goods Garden", "GGG"); + __Ownable_init(); + // transferOwnership(_multisig); } + /// @notice Mints a new Garden token and creates the associated Garden account. + /// @dev The Garden account is initialized with the provided parameters. + /// @param communityToken The address of the community token associated with the Garden. + /// @param name The name of the Garden. + /// @param gardeners An array of addresses representing the gardeners of the Garden. + /// @param gardenOperators An array of addresses representing the operators of the Garden. function mintGarden( address communityToken, string calldata name, address[] calldata gardeners, address[] calldata gardenOperators - ) external onlyOwner() { + ) external onlyOwner returns (address) { uint256 tokenId = _nextTokenId++; _safeMint(_msgSender(), tokenId); address gardenAccount = TBALib.createAccount(_gardenAccountImplementation, address(this), tokenId); GardenAccount(payable(gardenAccount)).initialize(communityToken, name, gardeners, gardenOperators); + + emit GardenMinted(_msgSender(), tokenId, name); + + return gardenAccount; } + + /// @notice Authorizes contract upgrades. + /// @dev Restricted to the contract owner. + /// @param newImplementation The address of the new contract implementation. + function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} } diff --git a/packages/contracts/test/ActionRegistry.t.sol b/packages/contracts/test/ActionRegistry.t.sol new file mode 100644 index 0000000..31a84c8 --- /dev/null +++ b/packages/contracts/test/ActionRegistry.t.sol @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.25; + +import { Test } from "forge-std/Test.sol"; + +import { Capital } from "../src/Constants.sol"; +import { ActionRegistry, NotActionOwner } from "../src/registries/Action.sol"; + +contract ActionRegistryTest is Test { + ActionRegistry private actionRegistry; + address private multisig = address(0x123); + address private owner = address(this); + + function setUp() public { + // Deploy the ActionRegistry contract + actionRegistry = new ActionRegistry(); + actionRegistry.initialize(multisig); + } + + function testInitialize() public { + // Test that the contract is properly initialized + assertEq(actionRegistry.owner(), address(this), "Owner should be the multisig address"); + } + + function testRegisterAction() public { + // Test registering a new action + Capital[] memory capitals = new Capital[](1); + capitals[0] = Capital.CULTURAL; + + string[] memory media = new string[](1); + media[0] = "mediaCID1"; + + // vm.prank(multisig); + actionRegistry.registerAction( + block.timestamp, + block.timestamp + 1 days, + "Test Action", + "instructionsCID", + capitals, + media + ); + + ActionRegistry.Action memory action = actionRegistry.getAction(0); + assertEq(action.startTime, block.timestamp, "Start time should be the current time"); + assertEq(action.endTime, block.timestamp + 1 days, "End time should be one day later"); + assertEq(action.instructions, "instructionsCID", "Instructions should match"); + assertEq(action.media[0], "mediaCID1", "Media should match"); + } + + function testUpdateActionStartTime() public { + // Test updating the start time of an action + testRegisterAction(); + + // vm.prank(multisig); + actionRegistry.updateActionStartTime(0, block.timestamp + 1 hours); + + ActionRegistry.Action memory action = actionRegistry.getAction(0); + assertEq(action.startTime, block.timestamp + 1 hours, "Start time should be updated"); + } + + function testUpdateActionEndTime() public { + // Test updating the end time of an action + testRegisterAction(); + + // vm.prank(multisig); + actionRegistry.updateActionEndTime(0, block.timestamp + 2 days); + + ActionRegistry.Action memory action = actionRegistry.getAction(0); + assertEq(action.endTime, block.timestamp + 2 days, "End time should be updated"); + } + + function testUpdateActionInstructions() public { + // Test updating the instructions of an action + testRegisterAction(); + + // vm.prank(multisig); + actionRegistry.updateActionInstructions(0, "newInstructionsCID"); + + ActionRegistry.Action memory action = actionRegistry.getAction(0); + assertEq(action.instructions, "newInstructionsCID", "Instructions should be updated"); + } + + function testUpdateActionMedia() public { + // Test updating the media of an action + testRegisterAction(); + + string[] memory newMedia = new string[](1); + newMedia[0] = "newMediaCID"; + + // vm.prank(multisig); + actionRegistry.updateActionMedia(0, newMedia); + + ActionRegistry.Action memory action = actionRegistry.getAction(0); + assertEq(action.media[0], "newMediaCID", "Media should be updated"); + } + + function testOnlyOwnerCanRegister() public { + // Test that only the owner can register actions + Capital[] memory capitals = new Capital[](1); + capitals[0] = Capital.CULTURAL; + + string[] memory media = new string[](1); + media[0] = "mediaCID1"; + + vm.prank(address(0x999)); + vm.expectRevert("Ownable: caller is not the owner"); + actionRegistry.registerAction( + block.timestamp, + block.timestamp + 1 days, + "Test Action 2", + "instructionsCID", + capitals, + media + ); + } + + function testOnlyOwnerCanUpdate() public { + // Test that only the action owner can update the action + testRegisterAction(); + + vm.prank(address(0x999)); + vm.expectRevert(NotActionOwner.selector); + actionRegistry.updateActionStartTime(0, block.timestamp + 1 hours); + } + + // function testAuthorizeUpgrade() public { + // // Test that only the owner can authorize an upgrade + // address newImplementation = address(0x456); + + // vm.prank(multisig); + // actionRegistry.upgradeTo(newImplementation); + // } + + // function testNonOwnerCannotUpgrade() public { + // // Test that non-owners cannot authorize an upgrade + // address newImplementation = address(0x456); + + // vm.prank(address(0x999)); + // vm.expectRevert("Ownable: caller is not the owner"); + // actionRegistry.upgradeTo(newImplementation); + // } +} diff --git a/packages/contracts/test/GardenAccount.t.sol b/packages/contracts/test/GardenAccount.t.sol index 6389dbc..8189aaa 100644 --- a/packages/contracts/test/GardenAccount.t.sol +++ b/packages/contracts/test/GardenAccount.t.sol @@ -1,11 +1,106 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.25; +pragma solidity >=0.8.25; -import { Test, console2} from "forge-std/Test.sol"; -import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol"; +import { Test } from "forge-std/Test.sol"; +import { GardenAccount } from "../src/accounts/Garden.sol"; + +contract GardenAccountTest is Test { + GardenAccount private gardenAccount; + address private owner = address(this); + address private multisig = address(0x123); -contract MintTest is Test { function setUp() public { + // Deploy the GardenAccount contract + gardenAccount = new GardenAccount( + address(0x001), // erc4337EntryPoint + address(0x002), // multicallForwarder + address(0x003), // erc6551Registry + address(0x004) // guardian + ); + + // Initialize the contract + address[] memory gardeners = new address[](1); + address[] memory gardenOperators = new address[](1); + + gardeners[0] = address(0x100); + gardenOperators[0] = address(0x200); + + gardenAccount.initialize(address(0x555), "Test Garden", gardeners, gardenOperators); + } + + function testInitialize() public { + // Check initial state + assertEq(gardenAccount.communityToken(), address(0x555), "Community token should match"); + assertEq(gardenAccount.name(), "Test Garden", "Name should match"); + assertTrue(gardenAccount.gardeners(address(0x100)), "Gardener should be added"); + assertTrue(gardenAccount.gardenOperators(address(0x200)), "Garden operator should be added"); } + + // function testUpdateName() public { + // vm.prank(owner); + // vm.expectEmit(true, true, true, true); + // emit GardenAccount.NameUpdated(owner, "New Garden Name"); + + // gardenAccount.updateName("New Garden Name"); + // assertEq(gardenAccount.name(), "New Garden Name", "Name should be updated"); + // } + + // function testAddGardener() public { + // vm.prank(owner); + // vm.expectEmit(true, true, true, true); + // emit GardenAccount.GardenerAdded(owner, address(0x300)); + + // gardenAccount.addGardener(address(0x300)); + // assertTrue(gardenAccount.gardeners(address(0x300)), "New gardener should be added"); + // } + + // function testRemoveGardener() public { + // vm.prank(owner); + // vm.expectEmit(true, true, true, true); + // emit GardenAccount.GardenerRemoved(owner, address(0x100)); + + // gardenAccount.removeGardener(address(0x100)); + // assertFalse(gardenAccount.gardeners(address(0x100)), "Gardener should be removed"); + // } + + // function testAddGardenOperator() public { + // vm.prank(owner); + // vm.expectEmit(true, true, true, true); + // emit GardenAccount.GardenOperatorAdded(owner, address(0x400)); + + // gardenAccount.addGardenOperator(address(0x400)); + // assertTrue(gardenAccount.gardenOperators(address(0x400)), "New garden operator should be added"); + // } + + // function testRemoveGardenOperator() public { + // vm.prank(owner); + // vm.expectEmit(true, true, true, true); + // emit GardenAccount.GardenOperatorRemoved(owner, address(0x200)); + + // gardenAccount.removeGardenOperator(address(0x200)); + // assertFalse(gardenAccount.gardenOperators(address(0x200)), "Garden operator should be removed"); + // } + + // function testNotGardenOwnerReverts() public { + // vm.prank(address(0x999)); // Not the owner + // vm.expectRevert(NotGardenOwner.selector); + // gardenAccount.updateName("Invalid Update"); + + // vm.prank(address(0x999)); // Not the owner + // vm.expectRevert(NotGardenOwner.selector); + // gardenAccount.addGardener(address(0x888)); + + // vm.prank(address(0x999)); // Not the owner + // vm.expectRevert(NotGardenOwner.selector); + // gardenAccount.removeGardener(address(0x100)); + + // vm.prank(address(0x999)); // Not the owner + // vm.expectRevert(NotGardenOwner.selector); + // gardenAccount.addGardenOperator(address(0x777)); + + // vm.prank(address(0x999)); // Not the owner + // vm.expectRevert(NotGardenOwner.selector); + // gardenAccount.removeGardenOperator(address(0x200)); + // } } diff --git a/packages/contracts/test/GardenToken.t.sol b/packages/contracts/test/GardenToken.t.sol new file mode 100644 index 0000000..13bcd87 --- /dev/null +++ b/packages/contracts/test/GardenToken.t.sol @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.25; + +import { Test } from "forge-std/Test.sol"; + +import { GardenToken } from "../src/tokens/Garden.sol"; +import { GardenAccount } from "../src/accounts/Garden.sol"; + +contract GardenTokenTest is Test { + GardenToken private gardenToken; + address private multisig = address(0x123); + address private gardenAccountImplementation = + address( + new GardenAccount( + address(0x001), // erc4337EntryPoint + address(0x002), // multicallForwarder + address(0x003), // erc6551Registry + address(0x004) // guardian + ) + ); + address private owner = address(this); + + function setUp() public { + // Deploy the contract and initialize it + gardenToken = new GardenToken(gardenAccountImplementation); + gardenToken.initialize(multisig); + } + + function testInitialize() public { + // Test that the contract is properly initialized + assertEq(gardenToken.owner(), owner, "Owner should be the multisig address"); + } + + // function testMintGarden() public { + // // Test minting a new Garden token + // address[] memory gardeners = new address[](1); + // address[] memory gardenOperators = new address[](1); + + // gardeners[0] = address(0x1); + // gardenOperators[0] = address(0x2); + + // vm.prank(multisig); + + // gardenToken.mintGarden(address(0x3), "Test Garden", gardeners, gardenOperators); + + // assertEq(gardenToken.ownerOf(0), owner, "Owner should be the contract owner"); + // } + + // function testEmitGardenMintedEvent() public { + // // Test that the GardenMinted event is emitted correctly + // address[] memory gardeners = new address[](1); + // address[] memory gardenOperators = new address[](1); + + // gardeners[0] = address(0x1); + // gardenOperators[0] = address(0x2); + + // vm.expectEmit(true, true, true, true); + // emit GardenToken.GardenMinted(owner, 0, "Test Garden"); + + // gardenToken.mintGarden(address(0x3), "Test Garden", gardeners, gardenOperators); + // } + + // function testOnlyOwnerCanMint() public { + // // Test that only the owner can mint new Garden tokens + // address notOwner = address(0x999); + // vm.prank(notOwner); // Change the msg.sender to notOwner + + // address[] memory gardeners = new address[](1); + // address[] memory gardenOperators = new address[](1); + + // gardeners[0] = address(0x1); + // gardenOperators[0] = address(0x2); + + // vm.expectRevert("Ownable: caller is not the owner"); + // gardenToken.mintGarden(address(0x3), "Test Garden", gardeners, gardenOperators); + // } + + // function testAuthorizeUpgrade() public { + // // Test that only the owner can authorize an upgrade + // address newImplementation = address(0x456); + + // gardenToken.upgradeTo(newImplementation); + // // We can't directly check this since the function is internal, + // // but we are verifying that no revert occurs for the owner. + // } + + // function testNonOwnerCannotUpgrade() public { + // // Test that non-owners cannot authorize an upgrade + // address notOwner = address(0x999); + // vm.prank(notOwner); // Change the msg.sender to notOwner + // address newImplementation = address(0x456); + + // vm.expectRevert("Ownable: caller is not the owner"); + // gardenToken.upgradeTo(newImplementation); + // } +} diff --git a/packages/contracts/test/WorkApprovalResolver.t.sol b/packages/contracts/test/WorkApprovalResolver.t.sol new file mode 100644 index 0000000..a53ecd2 --- /dev/null +++ b/packages/contracts/test/WorkApprovalResolver.t.sol @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.25; + +import { Test } from "forge-std/Test.sol"; +// import { Attestation } from "@eas/IEAS.sol"; + +// import { WorkApprovalSchema } from "../src/Schemas.sol"; +// import { NotInActionRegistry } from "../src/Constants.sol"; +import { GardenAccount } from "../src/accounts/Garden.sol"; +import { ActionRegistry } from "../src/registries/Action.sol"; +import { WorkApprovalResolver, NotGardenOperator, NotInWorkRegistry } from "../src/resolvers/WorkApproval.sol"; +import { MockEAS } from "../src/mocks/EAS.sol"; + +contract WorkApprovalResolverTest is Test { + WorkApprovalResolver private workApprovalResolver; + ActionRegistry private mockActionRegistry; + GardenAccount private mockGardenAccount; + MockEAS private mockIEAS; + + address private owner = address(this); + address private multisig = address(0x123); + address private attester = address(0x456); + address private recipient = address(0x789); + + function setUp() public { + // Deploy the mock contracts + mockActionRegistry = new ActionRegistry(); + mockGardenAccount = new GardenAccount(address(mockIEAS), address(0x002), address(0x003), address(0x004)); + mockIEAS = new MockEAS(); + + mockActionRegistry.initialize(multisig); + mockGardenAccount.initialize(address(0x555), "Test Garden", new address[](0), new address[](0)); + + // Deploy the WorkApprovalResolver contract + workApprovalResolver = new WorkApprovalResolver(address(address(0x007)), address(mockActionRegistry)); + workApprovalResolver.initialize(multisig); + } + + function testInitialize() public { + // Test that the contract is properly initialized + assertEq(workApprovalResolver.owner(), owner, "Owner should be the multisig address"); + } + + function testIsPayable() public { + // Test that the resolver is payable + assertTrue(workApprovalResolver.isPayable(), "Resolver should be payable"); + } + + // function testOnAttestValid() public { + // // Mock a valid action and garden account + // mockGardenAccount.addGardenOperator(attester); + // mockActionRegistry.registerAction(1, block.timestamp - 1, block.timestamp + 1000); + + // // Mock a valid work attestation + // Attestation memory workAttestation = Attestation(attester, recipient, ""); + // mockIEAS.setAttestation(1, workAttestation); + + // // Create a valid attestation + // bytes memory data = abi.encode(WorkApprovalSchema(1, 1)); + // Attestation memory attestation = Attestation(attester, recipient, data); + + // bool result = workApprovalResolver.onAttest(attestation, 0); + // assertTrue(result, "Attestation should be valid"); + // } + + // function testOnAttestInvalidOperator() public { + // // Mock an invalid garden operator + // mockGardenAccount.setGardenOperator(attester, false); + // mockActionRegistry.setAction(1, block.timestamp - 1, block.timestamp + 1000); + + // // Mock a valid work attestation + // Attestation memory workAttestation = Attestation(attester, recipient, ""); + // mockIEAS.setAttestation(1, workAttestation); + + // // Create an attestation with an invalid operator + // bytes memory data = abi.encode(WorkApprovalSchema(1, 1)); + // Attestation memory attestation = Attestation(attester, recipient, data); + + // vm.expectRevert(NotGardenOperator.selector); + // workApprovalResolver.onAttest(attestation, 0); + // } + + // function testOnAttestInvalidWork() public { + // // Mock a valid garden operator but an invalid work attestation + // mockGardenAccount.setGardenOperator(attester, true); + // mockActionRegistry.setAction(1, block.timestamp - 1, block.timestamp + 1000); + + // // Mock an invalid work attestation + // Attestation memory workAttestation = Attestation(address(0x999), recipient, ""); + // mockIEAS.setAttestation(1, workAttestation); + + // // Create an attestation with invalid work + // bytes memory data = abi.encode(WorkApprovalSchema(1, 1)); + // Attestation memory attestation = Attestation(attester, recipient, data); + + // vm.expectRevert(NotInWorkRegistry.selector); + // workApprovalResolver.onAttest(attestation, 0); + // } + + // function testOnAttestInvalidAction() public { + // // Mock a valid garden operator but an invalid action + // mockGardenAccount.setGardenOperator(attester, true); + // mockActionRegistry.setAction(1, 0, 0); + + // // Mock a valid work attestation + // Attestation memory workAttestation = Attestation(attester, recipient, ""); + // mockIEAS.setAttestation(1, workAttestation); + + // // Create an attestation with an invalid action + // bytes memory data = abi.encode(WorkApprovalSchema(1, 1)); + // Attestation memory attestation = Attestation(attester, recipient, data); + + // vm.expectRevert(NotInActionRegistry.selector); + // workApprovalResolver.onAttest(attestation, 0); + // } + + // function testOnRevoke() public { + // // Test that the onRevoke function works correctly and can only be called by the owner + // Attestation memory attestation = Attestation(attester, recipient, ""); + + // vm.prank(multisig); + // bool result = workApprovalResolver.onRevoke(attestation, 0); + // assertTrue(result, "Revocation should be valid"); + // } + + // function testOnRevokeNonOwner() public { + // // Test that non-owners cannot revoke + // Attestation memory attestation = Attestation(attester, recipient, ""); + + // vm.prank(address(0x999)); + // vm.expectRevert("Ownable: caller is not the owner"); + // workApprovalResolver.onRevoke(attestation, 0); + // } + + // function testAuthorizeUpgrade() public { + // // Test that only the owner can authorize an upgrade + // address newImplementation = address(0x456); + + // vm.prank(multisig); + // workApprovalResolver._authorizeUpgrade(newImplementation); + // } + + // function testNonOwnerCannotUpgrade() public { + // // Test that non-owners cannot authorize an upgrade + // address newImplementation = address(0x456); + + // vm.prank(address(0x999)); + // vm.expectRevert("Ownable: caller is not the owner"); + // workApprovalResolver._authorizeUpgrade(newImplementation); + // } +} diff --git a/packages/contracts/test/WorkResolver.t.sol b/packages/contracts/test/WorkResolver.t.sol new file mode 100644 index 0000000..3813b22 --- /dev/null +++ b/packages/contracts/test/WorkResolver.t.sol @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.25; + +import { Test } from "forge-std/Test.sol"; +// import { Attestation } from "@eas/IEAS.sol"; + +// import { WorkSchema } from "../src/Schemas.sol"; +// import { NotInActionRegistry, NotGardenerAccount } from "../src/Constants.sol"; +import { WorkResolver, NotActiveAction } from "../src/resolvers/Work.sol"; +import { ActionRegistry } from "../src/registries/Action.sol"; +import { GardenAccount } from "../src/accounts/Garden.sol"; +import { MockEAS } from "../src/mocks/EAS.sol"; + +contract WorkResolverTest is Test { + WorkResolver private workResolver; + ActionRegistry private mockActionRegistry; + GardenAccount private mockGardenAccount; + MockEAS private mockIEAS; + + address private owner = address(this); + address private multisig = address(0x124); + address private attester = address(0x476); + address private recipient = address(0x787); + + function setUp() public { + // Deploy the mock contracts + mockActionRegistry = new ActionRegistry(); + mockGardenAccount = new GardenAccount(address(0x021), address(0x022), address(0x023), address(0x024)); + mockIEAS = new MockEAS(); + + mockActionRegistry.initialize(multisig); + mockGardenAccount.initialize(address(0x545), "Test Garden", new address[](0), new address[](0)); + + // Deploy the WorkResolver contract + workResolver = new WorkResolver(address(mockIEAS), address(mockActionRegistry)); + workResolver.initialize(multisig); + } + + function testInitialize() public { + // Test that the contract is properly initialized + assertEq(workResolver.owner(), owner, "Owner should be the multisig address"); + } + + function testIsPayable() public { + // Test that the resolver is payable + assertTrue(workResolver.isPayable(), "Resolver should be payable"); + } + + // function testOnAttestValid() public { + // // Mock a valid action and garden account + // mockGardenAccount.setGardener(attester, true); + // mockActionRegistry.setAction(1, block.timestamp - 1, block.timestamp + 1000); + + // // Create a valid attestation + // bytes memory data = abi.encode(WorkSchema(1)); + // Attestation memory attestation = Attestation(attester, recipient, data); + + // bool result = workResolver.onAttest(attestation, 0); + // assertTrue(result, "Attestation should be valid"); + // } + + // function testOnAttestInvalidGardener() public { + // // Mock an invalid gardener + // mockGardenAccount.setGardener(attester, false); + // mockActionRegistry.setAction(1, block.timestamp - 1, block.timestamp + 1000); + + // // Create an attestation with an invalid gardener + // bytes memory data = abi.encode(WorkSchema(1)); + // Attestation memory attestation = Attestation(attester, recipient, data); + + // vm.expectRevert(NotGardenerAccount.selector); + // workResolver.onAttest(attestation, 0); + // } + + // function testOnAttestInvalidAction() public { + // // Mock a valid gardener but an invalid action + // mockGardenAccount.setGardener(attester, true); + // mockActionRegistry.setAction(1, 0, 0); + + // // Create an attestation with an invalid action + // bytes memory data = abi.encode(WorkSchema(1)); + // Attestation memory attestation = Attestation(attester, recipient, data); + + // vm.expectRevert(NotInActionRegistry.selector); + // workResolver.onAttest(attestation, 0); + // } + + // function testOnAttestExpiredAction() public { + // // Mock a valid gardener but an expired action + // mockGardenAccount.setGardener(attester, true); + // mockActionRegistry.setAction(1, block.timestamp - 1000, block.timestamp - 500); + + // // Create an attestation with an expired action + // bytes memory data = abi.encode(WorkSchema(1)); + // Attestation memory attestation = Attestation(attester, recipient, data); + + // vm.expectRevert(NotActiveAction.selector); + // workResolver.onAttest(attestation, 0); + // } + + // function testOnRevoke() public { + // // Test that the onRevoke function works correctly and can only be called by the owner + // Attestation memory attestation = Attestation(attester, recipient, ""); + + // vm.prank(multisig); + // bool result = workResolver.onRevoke(attestation, 0); + // assertTrue(result, "Revocation should be valid"); + // } + + // function testOnRevokeNonOwner() public { + // // Test that non-owners cannot revoke + // Attestation memory attestation = Attestation(attester, recipient, ""); + + // vm.prank(address(0x999)); + // vm.expectRevert("Ownable: caller is not the owner"); + // workResolver.onRevoke(attestation, 0); + // } + + // function testAuthorizeUpgrade() public { + // // Test that only the owner can authorize an upgrade + // address newImplementation = address(0x456); + + // vm.prank(multisig); + // workResolver._authorizeUpgrade(newImplementation); + // } + + // function testNonOwnerCannotUpgrade() public { + // // Test that non-owners cannot authorize an upgrade + // address newImplementation = address(0x456); + + // vm.prank(address(0x999)); + // vm.expectRevert("Ownable: caller is not the owner"); + // workResolver._authorizeUpgrade(newImplementation); + // } +} diff --git a/packages/eas/.env.example b/packages/eas/.env.example new file mode 100644 index 0000000..5459a18 --- /dev/null +++ b/packages/eas/.env.example @@ -0,0 +1,8 @@ +## For ethers provider +ALCHEMY_API_KEY= + +## For ethers signer to create schemas +PRIVATE_KEY= + +## For deploying schemas to production on Arbitrum +PROD=false \ No newline at end of file diff --git a/packages/eas/.gitignore b/packages/eas/.gitignore new file mode 100644 index 0000000..82925b5 --- /dev/null +++ b/packages/eas/.gitignore @@ -0,0 +1,45 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +node_modules +node_modules/**/* +/.pnp +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +coverage + +# next.js +.next +/out/ + +# production +build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local +.env + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +.idea/ + +*storybook.log + +**/.idea \ No newline at end of file diff --git a/packages/eas/biome.json b/packages/eas/biome.json new file mode 100644 index 0000000..36e3fcc --- /dev/null +++ b/packages/eas/biome.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.7.3/schema.json", + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + } +} diff --git a/packages/eas/package.json b/packages/eas/package.json new file mode 100644 index 0000000..a7d58ad --- /dev/null +++ b/packages/eas/package.json @@ -0,0 +1,26 @@ +{ + "name": "eas", + "private": true, + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "start": "ts-node ./src/index.ts", + "biome:check": "biome check ./src", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@ethereum-attestation-service/eas-sdk": "^2.5.0", + "commander": "^12.1.0", + "zod": "^3.23.8" + }, + "devDependencies": { + "@biomejs/biome": "1.7.3", + "@swc/core": "^1.5.24", + "@swc/helpers": "^0.5.11", + "ts-node": "^10.9.2" + } +} diff --git a/packages/eas/src/constants.ts b/packages/eas/src/constants.ts new file mode 100644 index 0000000..0b32f3b --- /dev/null +++ b/packages/eas/src/constants.ts @@ -0,0 +1,3 @@ +export const ALCHEMY_API_KEY = process.env.ALCHEMY_API_KEY!; +export const PRIVATE_KEY = process.env.PRIVATE_KEY!; +export const PROD = process.env.PROD === "true"; diff --git a/packages/eas/src/handlers/schemasToEas.ts b/packages/eas/src/handlers/schemasToEas.ts new file mode 100644 index 0000000..71e4a90 --- /dev/null +++ b/packages/eas/src/handlers/schemasToEas.ts @@ -0,0 +1,74 @@ +import { z } from "zod"; +import fs from "node:fs"; +import * as path from "node:path"; + +import { schemaRegistry } from "../services/eas"; + +// EAS schema is an array of [type, name] tuples. E.g. "uint256 eventId, uint8 voteIndex" +const EasSchemaSchema = z.object({ + name: z.string(), + description: z.string(), + values: z + .object({ + type: z.string(), + name: z.string(), + }) + .array(), + resolver: z.string().optional(), + UID: z.string().optional(), + parsed: z.string().optional(), +}); + +export type EasSchema = z.infer; + +export const schemasToEas = async ( + filePath: string, + options: { force?: boolean } +) => { + const { force } = options; + + const _schemaRegistry = schemaRegistry(); + // Read the JSON file + const _path = path.join(process.cwd(), "src", filePath); + const data = fs.readFileSync(_path, "utf-8"); + + // Parse the file content into an array of Metric objects + const schemas: EasSchema[] = JSON.parse(data).map((schema: unknown) => { + return EasSchemaSchema.parse(schema); + }); + + let updated = false; + + // Iterate over the array and store each schema using the EAS service + const updatedMetrics = await Promise.all( + schemas.map(async (schema) => { + // Skip if the metric already has a UID + if (!force && schema?.UID) { + return schema; + } + + const schemaToStore = schema.values + .map(({ type, name }) => `${type} ${name}`) + .join(", "); + + console.log(schemaToStore); + + const tx = await _schemaRegistry.register({ + schema: schemaToStore, + resolverAddress: schema.resolver, + revocable: true, + }); + + const uid = await tx.wait(); + schema.parsed = schemaToStore; + schema.UID = uid; // Attach the UID to the metric object + + console.log(`Stored schema with UID ${uid}`); + updated = true; + return schema; + }) + ); + + // Write the updated metrics back to the JSON file + if (updated) fs.writeFileSync(_path, JSON.stringify(updatedMetrics, null, 2)); +}; diff --git a/packages/eas/src/index.ts b/packages/eas/src/index.ts new file mode 100644 index 0000000..ce1536c --- /dev/null +++ b/packages/eas/src/index.ts @@ -0,0 +1,28 @@ +import "dotenv/config"; + +import { program } from "commander"; + +import { schemasToEas } from "./handlers/schemasToEas"; + +program + .name("eas-upload-util") + .description("Batch upload data to EAS") + .version("0.0.1"); + +console.log(process.cwd()); + +program + .name("eas-upload-util") + .command("schemas-to-eas") + .description("Upload schemas to EAS") + .argument( + "[file]", + "Path to the file containing the metrics", + "/resources/schemas.json" + ) + .option("-f, --force ", "Force upload", false) + .action(async (file: string, opt: { force?: boolean }) => { + await schemasToEas(file, opt); + }); + +program.parse(); diff --git a/packages/eas/src/resources/schemas.json b/packages/eas/src/resources/schemas.json new file mode 100644 index 0000000..7ca4054 --- /dev/null +++ b/packages/eas/src/resources/schemas.json @@ -0,0 +1,69 @@ +[ + { + "name": "Garden Assessment", + "description": "Assess a Green Goods garden sapce biodiversity.", + "values": [ + { + "type": "string[]", + "name": "media" + } + ], + "resolver": "0x8965249828954343", + "UID": "0x0", + "parsed": "" + }, + { + "name": "Work", + "description": "Upload work on a Green Goods sapce.", + "values": [ + { + "type": "uint256", + "name": "actionUID" + }, + { + "type": "string", + "name": "title" + }, + { + "type": "string", + "name": "feedback" + }, + { + "type": "string", + "name": "metadata" + }, + { + "type": "string[]", + "name": "media" + } + ], + "resolver": "0x380217CB03B2AA6838C2B6F615F36C677D7922dB", + "UID": "0x9341009d07b8de3eb72b96ac42246c549f3e32636cb31a75961fbee6db44a0eb", + "parsed": "uint256 actionUID, string title, string feedback, string metadata, string[] media" + }, + { + "name": "Work Approval", + "description": "Approve work on a Green Goods sapce.", + "values": [ + { + "type": "uint256", + "name": "actionUID" + }, + { + "type": "bytes32", + "name": "workUID" + }, + { + "type": "bool", + "name": "approved" + }, + { + "type": "string", + "name": "feedback" + } + ], + "resolver": "0xECdD5C72D468b2b1d0566102050C42e99A37Ca14", + "UID": "0x019249c30ec1d02ae41abb3fbbeeb56b9bbb2261cf94191fac73089308aa662a", + "parsed": "uint256 actionUID bytes32 workUID, bool approved, string feedback" + } +] diff --git a/packages/eas/src/services/eas.ts b/packages/eas/src/services/eas.ts new file mode 100644 index 0000000..9d5018e --- /dev/null +++ b/packages/eas/src/services/eas.ts @@ -0,0 +1,39 @@ +import { ethers } from "ethers"; +import { EAS, SchemaRegistry } from "@ethereum-attestation-service/eas-sdk"; + +import { ALCHEMY_API_KEY, PRIVATE_KEY, PROD } from "../constants"; + +const provider = new ethers.AlchemyProvider( + PROD ? "arbitrum" : "sepolia", + ALCHEMY_API_KEY +); +const signer = new ethers.Wallet(PRIVATE_KEY).connect(provider); + +const easSigner = () => { + const EASContractAddress = + PROD ? + "0xbD75f629A22Dc1ceD33dDA0b68c546A1c035c458" + : "0xC2679fBD37d54388Ce493F1DB75320D236e1815e"; // Sepolia v0.26 + + // Initialize the sdk with the address of the EAS Schema contract address + const eas = new EAS(EASContractAddress); + + // Gets a default provider (in production use something else like infura/alchemy) + + // Connects an ethers style provider/signingProvider to perform read/write functions. + // MUST be a signer to do write operations! + return eas.connect(signer); +}; + +const schemaRegistry = () => { + const schemaRegistryContractAddress = + PROD ? + "0xA310da9c5B885E7fb3fbA9D66E9Ba6Df512b78eB" + : "0x0a7E2Ff54e76B8E6659aedc9103FB21c038050D0"; // Sepolia 0.26 + + const schemaRegistry = new SchemaRegistry(schemaRegistryContractAddress); + + return schemaRegistry.connect(signer); +}; + +export { schemaRegistry, easSigner }; diff --git a/packages/eas/src/utils/assertExists.ts b/packages/eas/src/utils/assertExists.ts new file mode 100644 index 0000000..ea7c8b7 --- /dev/null +++ b/packages/eas/src/utils/assertExists.ts @@ -0,0 +1,7 @@ +export const assertExists = (variable?: string, name?: string) => { + if (!variable) { + throw new Error(`Environment variable ${name} is not set.`); + } + + return variable; +}; \ No newline at end of file diff --git a/packages/eas/tsconfig.json b/packages/eas/tsconfig.json new file mode 100644 index 0000000..cfaff28 --- /dev/null +++ b/packages/eas/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ESNext", + "esModuleInterop": true, + "noImplicitAny": true, + "strictNullChecks": true, + "moduleResolution": "NodeNext", + "module": "NodeNext", + "resolveJsonModule": true + }, + "ts-node": { + "swc": true + }, + "include": ["src/**/*.ts"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index de56ef0..79e223f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,7 +16,7 @@ importers: version: 6.13.2(bufferutil@4.0.7)(utf-8-validate@5.0.10) viem: specifier: ^2.19.6 - version: 2.19.6(bufferutil@4.0.7)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4) + version: 2.19.6(bufferutil@4.0.7)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.23.8) devDependencies: concurrently: specifier: 8.2.2 @@ -35,7 +35,7 @@ importers: dependencies: '@ethereum-attestation-service/eas-sdk': specifier: 2.5.0 - version: 2.5.0(bufferutil@4.0.7)(typescript@5.5.4)(utf-8-validate@5.0.10) + version: 2.5.0(bufferutil@4.0.7)(ts-node@10.9.2(@swc/core@1.7.11(@swc/helpers@0.5.12))(@types/node@22.1.0)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10) '@hookform/resolvers': specifier: ^3.3.4 version: 3.3.4(react-hook-form@7.51.0(react@18.2.0)) @@ -74,7 +74,7 @@ importers: version: 6.22.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0) tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.1) + version: 1.0.7(tailwindcss@3.4.1(ts-node@10.9.2(@types/node@22.1.0)(typescript@5.5.4))) xstate: specifier: 4.38.2 version: 4.38.2 @@ -120,7 +120,7 @@ importers: version: 2.4.1 tailwindcss: specifier: 3.4.1 - version: 3.4.1 + version: 3.4.1(ts-node@10.9.2(@types/node@22.1.0)(typescript@5.5.4)) vite: specifier: ^5.4.0 version: 5.4.0(@types/node@22.1.0)(terser@5.21.0) @@ -138,10 +138,10 @@ importers: dependencies: '@ethereum-attestation-service/eas-contracts': specifier: 1.7.1 - version: 1.7.1(bufferutil@4.0.7)(typescript@5.5.4)(utf-8-validate@5.0.10) + version: 1.7.1(bufferutil@4.0.7)(ts-node@10.9.2(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10) '@openzeppelin/contracts-upgradeable': - specifier: 4.8.3 - version: 4.8.3 + specifier: 4.9.6 + version: 4.9.6 devDependencies: '@types/prettier': specifier: '2' @@ -169,11 +169,36 @@ importers: version: 5.0.3(typescript@5.5.4) solidity-coverage: specifier: ^0.8.12 - version: 0.8.12(hardhat@2.22.4(bufferutil@4.0.7)(typescript@5.5.4)(utf-8-validate@5.0.10)) + version: 0.8.12(hardhat@2.22.4(bufferutil@4.0.7)(ts-node@10.9.2(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) toml: specifier: ~3.0.0 version: 3.0.0 + packages/eas: + dependencies: + '@ethereum-attestation-service/eas-sdk': + specifier: ^2.5.0 + version: 2.5.0(bufferutil@4.0.7)(ts-node@10.9.2(@swc/core@1.7.11(@swc/helpers@0.5.12))(@types/node@22.1.0)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10) + commander: + specifier: ^12.1.0 + version: 12.1.0 + zod: + specifier: ^3.23.8 + version: 3.23.8 + devDependencies: + '@biomejs/biome': + specifier: 1.7.3 + version: 1.7.3 + '@swc/core': + specifier: ^1.5.24 + version: 1.7.11(@swc/helpers@0.5.12) + '@swc/helpers': + specifier: ^0.5.11 + version: 0.5.12 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@swc/core@1.7.11(@swc/helpers@0.5.12))(@types/node@22.1.0)(typescript@5.5.4) + packages: '@aashutoshrathi/word-wrap@1.2.6': @@ -849,9 +874,66 @@ packages: resolution: {integrity: sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==} engines: {node: '>=6.9.0'} + '@biomejs/biome@1.7.3': + resolution: {integrity: sha512-ogFQI+fpXftr+tiahA6bIXwZ7CSikygASdqMtH07J2cUzrpjyTMVc9Y97v23c7/tL1xCZhM+W9k4hYIBm7Q6cQ==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@1.7.3': + resolution: {integrity: sha512-eDvLQWmGRqrPIRY7AIrkPHkQ3visEItJKkPYSHCscSDdGvKzYjmBJwG1Gu8+QC5ed6R7eiU63LEC0APFBobmfQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@1.7.3': + resolution: {integrity: sha512-JXCaIseKRER7dIURsVlAJacnm8SG5I0RpxZ4ya3dudASYUc68WGl4+FEN03ABY3KMIq7hcK1tzsJiWlmXyosZg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@1.7.3': + resolution: {integrity: sha512-c8AlO45PNFZ1BYcwaKzdt46kYbuP6xPGuGQ6h4j3XiEDpyseRRUy/h+6gxj07XovmyxKnSX9GSZ6nVbZvcVUAw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-arm64@1.7.3': + resolution: {integrity: sha512-phNTBpo7joDFastnmZsFjYcDYobLTx4qR4oPvc9tJ486Bd1SfEVPHEvJdNJrMwUQK56T+TRClOQd/8X1nnjA9w==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-x64-musl@1.7.3': + resolution: {integrity: sha512-UdEHKtYGWEX3eDmVWvQeT+z05T9/Sdt2+F/7zmMOFQ7boANeX8pcO6EkJPK3wxMudrApsNEKT26rzqK6sZRTRA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-linux-x64@1.7.3': + resolution: {integrity: sha512-vnedYcd5p4keT3iD48oSKjOIRPYcjSNNbd8MO1bKo9ajg3GwQXZLAH+0Cvlr+eMsO67/HddWmscSQwTFrC/uPA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-win32-arm64@1.7.3': + resolution: {integrity: sha512-unNCDqUKjujYkkSxs7gFIfdasttbDC4+z0kYmcqzRk6yWVoQBL4dNLcCbdnJS+qvVDNdI9rHp2NwpQ0WAdla4Q==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@1.7.3': + resolution: {integrity: sha512-ZmByhbrnmz/UUFYB622CECwhKIPjJLLPr5zr3edhu04LzbfcOrz16VYeNq5dpO1ADG70FORhAJkaIGdaVBG00w==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + '@coinbase/wallet-sdk@4.0.3': resolution: {integrity: sha512-y/OGEjlvosikjfB+wk+4CVb9OxD1ob9cidEBLI5h8Hxaf/Qoob2XoVT1uvhtAzBx34KpGYSd+alKvh/GCRre4Q==} + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + '@emotion/is-prop-valid@1.2.2': resolution: {integrity: sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==} @@ -1201,12 +1283,12 @@ packages: '@jridgewell/sourcemap-codec@1.4.15': resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - '@jridgewell/trace-mapping@0.3.19': - resolution: {integrity: sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==} - '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@lit-labs/ssr-dom-shim@1.2.0': resolution: {integrity: sha512-yWJKmpGE6lUURKAaIltoPIE/wrbY3TEkqQt+X0m+7fQNnAv0keydnYvbiJFP1PnMhizmIWRWOG5KLhYyc/xl+g==} @@ -1481,8 +1563,8 @@ packages: '@octokit/types@12.6.0': resolution: {integrity: sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==} - '@openzeppelin/contracts-upgradeable@4.8.3': - resolution: {integrity: sha512-SXDRl7HKpl2WDoJpn7CK/M9U4Z8gNXDHHChAKh0Iz+Wew3wu6CmFYBeie3je8V0GSXZAIYYwUktSrnW/kwVPtg==} + '@openzeppelin/contracts-upgradeable@4.9.6': + resolution: {integrity: sha512-m4iHazOsOCv1DgM7eD7GupTJ+NFVujRZt1wzddDPSVGpWdKq1SKkla5htKG7+IS4d2XOCtzkUNwRZ7Vq5aEUMA==} '@openzeppelin/merkle-tree@1.0.6': resolution: {integrity: sha512-cGWOb2WBWbJhqvupzxjnKAwGLxxAEYPg51sk76yZ5nVe5D03mw7Vx5yo8llaIEqYhP5O39M8QlrNWclgLfKVrA==} @@ -1909,6 +1991,84 @@ packages: peerDependencies: '@svgr/core': '*' + '@swc/core-darwin-arm64@1.7.11': + resolution: {integrity: sha512-HRQv4qIeMBPThZ6Y/4yYW52rGsS6yrpusvuxLGyoFo45Y0y12/V2yXkOIA/0HIQyrqoUAxn1k4zQXpPaPNCmnw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + + '@swc/core-darwin-x64@1.7.11': + resolution: {integrity: sha512-vtMQj0F3oYwDu5yhO7SKDRg1XekRSi6/TbzHAbBXv+dBhlGGvcZZynT1H90EVFTv+7w7Sh+lOFvRv5Z4ZTcxow==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + + '@swc/core-linux-arm-gnueabihf@1.7.11': + resolution: {integrity: sha512-mHtzWKxhtyreI4CSxs+3+ENv8t/Qo35WFoYG66qHEgJz/Z2Lh6jv1E+MYgHdYwnpQHgHbdvAco7HsBu/Dt6xXw==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + + '@swc/core-linux-arm64-gnu@1.7.11': + resolution: {integrity: sha512-FRwe/x0GfXSQjGP2lIk+NO0pUFS/lI/RorCLBPiK808EVE9JTbh9DKCc/4Bbb4jgScAjNkrFCUVObQYl3YKmpA==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-arm64-musl@1.7.11': + resolution: {integrity: sha512-GY/rs0+GUq14Gbnza90KOrQd/9yHd5qQMii5jcSWcUCT5A8QTa8kiicsM2NxZeTJ69xlKmT7sLod5l99lki/2A==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-x64-gnu@1.7.11': + resolution: {integrity: sha512-QDkGRwSPmp2RBOlSs503IUXlWYlny8DyznTT0QuK0ML2RpDFlXWU94K/EZhS0RBEUkMY/W51OacM8P8aS/dkCg==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-linux-x64-musl@1.7.11': + resolution: {integrity: sha512-SBEfKrXy6zQ6ksnyxw1FaCftrIH4fLfA81xNnKb7x/6iblv7Ko6H0aK3P5C86jyqF/82+ONl9C7ImGkUFQADig==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-win32-arm64-msvc@1.7.11': + resolution: {integrity: sha512-a2Y4xxEsLLYHJN7sMnw9+YQJDi3M1BxEr9hklfopPuGGnYLFNnx5CypH1l9ReijEfWjIAHNi7pq3m023lzW1Hg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@swc/core-win32-ia32-msvc@1.7.11': + resolution: {integrity: sha512-ZbZFMwZO+j8ulhegJ7EhJ/QVZPoQ5qc30ylJQSxizizTJaen71Q7/13lXWc6ksuCKvg6dUKrp/TPgoxOOtSrFA==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + + '@swc/core-win32-x64-msvc@1.7.11': + resolution: {integrity: sha512-IUohZedSJyDu/ReEBG/mqX6uG29uA7zZ9z6dIAF+p6eFxjXmh9MuHryyM+H8ebUyoq/Ad3rL+rUCksnuYNnI0w==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@swc/core@1.7.11': + resolution: {integrity: sha512-AB+qc45UrJrDfbhPKcUXk+9z/NmFfYYwJT6G7/iur0fCse9kXjx45gi40+u/O2zgarG/30/zV6E3ps8fUvjh7g==} + engines: {node: '>=10'} + peerDependencies: + '@swc/helpers': '*' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + + '@swc/helpers@0.5.12': + resolution: {integrity: sha512-KMZNXiGibsW9kvZAO1Pam2JPTDBm+KSHMMHWdsyI/1DbIZjT2A6Gy3hblVXUMEDvUAKq+e0vL0X0o54owWji7g==} + + '@swc/types@0.1.12': + resolution: {integrity: sha512-wBJA+SdtkbFhHjTMYH+dEH1y4VpfGdAc2Kw/LK09i9bXd/K6j6PkDcFCEzb6iVfZMkPRrl/q0e3toqTAJdkIVA==} + '@szmarczak/http-timer@5.0.1': resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} engines: {node: '>=14.16'} @@ -1922,6 +2082,18 @@ packages: '@tanstack/virtual-core@3.1.3': resolution: {integrity: sha512-Y5B4EYyv1j9V8LzeAoOVeTg0LI7Fo5InYKgAjkY1Pu9GjtUwX/EKxNcU7ng3sKr99WEf+bPTcktAeybyMOYo+g==} + '@tsconfig/node10@1.0.11': + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -2181,10 +2353,9 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn@8.11.3: - resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} + acorn-walk@8.3.3: + resolution: {integrity: sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==} engines: {node: '>=0.4.0'} - hasBin: true acorn@8.12.1: resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} @@ -2257,6 +2428,9 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} @@ -2553,6 +2727,10 @@ packages: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -2619,6 +2797,9 @@ packages: create-hmac@1.1.7: resolution: {integrity: sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==} + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + cross-fetch@3.1.8: resolution: {integrity: sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==} @@ -2761,6 +2942,10 @@ packages: didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + diff@5.0.0: resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==} engines: {node: '>=0.3.1'} @@ -3856,6 +4041,9 @@ packages: magic-string@0.25.9: resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + md5.js@1.3.5: resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==} @@ -4966,6 +5154,20 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} @@ -5210,6 +5412,9 @@ packages: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + valtio@1.11.2: resolution: {integrity: sha512-1XfIxnUXzyswPAPXo1P3Pdx2mq/pIqZICkWN60Hby0d9Iqb+MEIpqgYVlbflvHdrp2YR/q3jyKWRPJJ100yxaw==} engines: {node: '>=12.20.0'} @@ -5545,6 +5750,10 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -5552,6 +5761,9 @@ packages: zod@3.22.4: resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} + zod@3.23.8: + resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + snapshots: '@aashutoshrathi/word-wrap@1.2.6': {} @@ -5565,7 +5777,7 @@ snapshots: '@ampproject/remapping@2.2.1': dependencies: '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.19 + '@jridgewell/trace-mapping': 0.3.25 '@apideck/better-ajv-errors@0.3.6(ajv@8.12.0)': dependencies: @@ -5596,7 +5808,7 @@ snapshots: '@babel/traverse': 7.24.0 '@babel/types': 7.24.0 convert-source-map: 2.0.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -5616,7 +5828,7 @@ snapshots: '@babel/traverse': 7.25.3 '@babel/types': 7.25.2 convert-source-map: 2.0.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -5627,7 +5839,7 @@ snapshots: dependencies: '@babel/types': 7.24.0 '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.19 + '@jridgewell/trace-mapping': 0.3.25 jsesc: 2.5.2 '@babel/generator@7.25.0': @@ -5686,7 +5898,7 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-compilation-targets': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) lodash.debounce: 4.0.8 resolve: 1.22.6 transitivePeerDependencies: @@ -6369,7 +6581,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.22.6 '@babel/parser': 7.24.0 '@babel/types': 7.24.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -6396,7 +6608,7 @@ snapshots: '@babel/parser': 7.25.3 '@babel/template': 7.25.0 '@babel/types': 7.25.2 - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -6413,6 +6625,41 @@ snapshots: '@babel/helper-validator-identifier': 7.24.7 to-fast-properties: 2.0.0 + '@biomejs/biome@1.7.3': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 1.7.3 + '@biomejs/cli-darwin-x64': 1.7.3 + '@biomejs/cli-linux-arm64': 1.7.3 + '@biomejs/cli-linux-arm64-musl': 1.7.3 + '@biomejs/cli-linux-x64': 1.7.3 + '@biomejs/cli-linux-x64-musl': 1.7.3 + '@biomejs/cli-win32-arm64': 1.7.3 + '@biomejs/cli-win32-x64': 1.7.3 + + '@biomejs/cli-darwin-arm64@1.7.3': + optional: true + + '@biomejs/cli-darwin-x64@1.7.3': + optional: true + + '@biomejs/cli-linux-arm64-musl@1.7.3': + optional: true + + '@biomejs/cli-linux-arm64@1.7.3': + optional: true + + '@biomejs/cli-linux-x64-musl@1.7.3': + optional: true + + '@biomejs/cli-linux-x64@1.7.3': + optional: true + + '@biomejs/cli-win32-arm64@1.7.3': + optional: true + + '@biomejs/cli-win32-x64@1.7.3': + optional: true + '@coinbase/wallet-sdk@4.0.3': dependencies: buffer: 6.0.3 @@ -6422,6 +6669,10 @@ snapshots: preact: 10.19.6 sha.js: 2.4.11 + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + '@emotion/is-prop-valid@1.2.2': dependencies: '@emotion/memoize': 0.8.1 @@ -6511,7 +6762,7 @@ snapshots: '@eslint/config-array@0.17.1': dependencies: '@eslint/object-schema': 2.1.4 - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -6519,7 +6770,7 @@ snapshots: '@eslint/eslintrc@3.1.0': dependencies: ajv: 6.12.6 - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) espree: 10.1.0 globals: 14.0.0 ignore: 5.2.4 @@ -6534,9 +6785,9 @@ snapshots: '@eslint/object-schema@2.1.4': {} - '@ethereum-attestation-service/eas-contracts@1.4.1(bufferutil@4.0.7)(typescript@5.5.4)(utf-8-validate@5.0.10)': + '@ethereum-attestation-service/eas-contracts@1.4.1(bufferutil@4.0.7)(ts-node@10.9.2(@swc/core@1.7.11(@swc/helpers@0.5.12))(@types/node@22.1.0)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)': dependencies: - hardhat: 2.22.1(bufferutil@4.0.7)(typescript@5.5.4)(utf-8-validate@5.0.10) + hardhat: 2.22.1(bufferutil@4.0.7)(ts-node@10.9.2(@swc/core@1.7.11(@swc/helpers@0.5.12))(@types/node@22.1.0)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - c-kzg @@ -6545,9 +6796,9 @@ snapshots: - typescript - utf-8-validate - '@ethereum-attestation-service/eas-contracts@1.7.1(bufferutil@4.0.7)(typescript@5.5.4)(utf-8-validate@5.0.10)': + '@ethereum-attestation-service/eas-contracts@1.7.1(bufferutil@4.0.7)(ts-node@10.9.2(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)': dependencies: - hardhat: 2.22.4(bufferutil@4.0.7)(typescript@5.5.4)(utf-8-validate@5.0.10) + hardhat: 2.22.4(bufferutil@4.0.7)(ts-node@10.9.2(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - c-kzg @@ -6556,9 +6807,9 @@ snapshots: - typescript - utf-8-validate - '@ethereum-attestation-service/eas-sdk@2.5.0(bufferutil@4.0.7)(typescript@5.5.4)(utf-8-validate@5.0.10)': + '@ethereum-attestation-service/eas-sdk@2.5.0(bufferutil@4.0.7)(ts-node@10.9.2(@swc/core@1.7.11(@swc/helpers@0.5.12))(@types/node@22.1.0)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)': dependencies: - '@ethereum-attestation-service/eas-contracts': 1.4.1(bufferutil@4.0.7)(typescript@5.5.4)(utf-8-validate@5.0.10) + '@ethereum-attestation-service/eas-contracts': 1.4.1(bufferutil@4.0.7)(ts-node@10.9.2(@swc/core@1.7.11(@swc/helpers@0.5.12))(@types/node@22.1.0)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10) '@openzeppelin/merkle-tree': 1.0.6 ethers: 6.13.2(bufferutil@4.0.7)(utf-8-validate@5.0.10) js-base64: 3.7.7 @@ -6876,7 +7127,7 @@ snapshots: dependencies: '@jridgewell/set-array': 1.1.2 '@jridgewell/sourcemap-codec': 1.4.15 - '@jridgewell/trace-mapping': 0.3.19 + '@jridgewell/trace-mapping': 0.3.25 '@jridgewell/gen-mapping@0.3.5': dependencies: @@ -6897,12 +7148,12 @@ snapshots: '@jridgewell/sourcemap-codec@1.4.15': {} - '@jridgewell/trace-mapping@0.3.19': + '@jridgewell/trace-mapping@0.3.25': dependencies: '@jridgewell/resolve-uri': 3.1.1 '@jridgewell/sourcemap-codec': 1.4.15 - '@jridgewell/trace-mapping@0.3.25': + '@jridgewell/trace-mapping@0.3.9': dependencies: '@jridgewell/resolve-uri': 3.1.1 '@jridgewell/sourcemap-codec': 1.4.15 @@ -6948,7 +7199,7 @@ snapshots: '@metamask/utils@3.6.0': dependencies: '@types/debug': 4.1.9 - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) semver: 7.6.0 superstruct: 1.0.3 transitivePeerDependencies: @@ -6958,7 +7209,7 @@ snapshots: dependencies: '@ethereumjs/tx': 4.2.0 '@types/debug': 4.1.9 - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) semver: 7.6.0 superstruct: 1.0.3 transitivePeerDependencies: @@ -7195,7 +7446,7 @@ snapshots: dependencies: '@octokit/openapi-types': 20.0.0 - '@openzeppelin/contracts-upgradeable@4.8.3': {} + '@openzeppelin/contracts-upgradeable@4.9.6': {} '@openzeppelin/merkle-tree@1.0.6': dependencies: @@ -7279,7 +7530,7 @@ snapshots: '@privy-io/api-base@1.2.2': dependencies: - zod: 3.22.4 + zod: 3.23.8 '@privy-io/js-sdk-core@0.26.0(bufferutil@4.0.7)(utf-8-validate@5.0.10)': dependencies: @@ -7306,7 +7557,7 @@ snapshots: '@privy-io/api-base': 1.2.2 ethers: 5.7.2(bufferutil@4.0.7)(utf-8-validate@5.0.10) libphonenumber-js: 1.10.47 - zod: 3.22.4 + zod: 3.23.8 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -7722,6 +7973,63 @@ snapshots: transitivePeerDependencies: - supports-color + '@swc/core-darwin-arm64@1.7.11': + optional: true + + '@swc/core-darwin-x64@1.7.11': + optional: true + + '@swc/core-linux-arm-gnueabihf@1.7.11': + optional: true + + '@swc/core-linux-arm64-gnu@1.7.11': + optional: true + + '@swc/core-linux-arm64-musl@1.7.11': + optional: true + + '@swc/core-linux-x64-gnu@1.7.11': + optional: true + + '@swc/core-linux-x64-musl@1.7.11': + optional: true + + '@swc/core-win32-arm64-msvc@1.7.11': + optional: true + + '@swc/core-win32-ia32-msvc@1.7.11': + optional: true + + '@swc/core-win32-x64-msvc@1.7.11': + optional: true + + '@swc/core@1.7.11(@swc/helpers@0.5.12)': + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.12 + optionalDependencies: + '@swc/core-darwin-arm64': 1.7.11 + '@swc/core-darwin-x64': 1.7.11 + '@swc/core-linux-arm-gnueabihf': 1.7.11 + '@swc/core-linux-arm64-gnu': 1.7.11 + '@swc/core-linux-arm64-musl': 1.7.11 + '@swc/core-linux-x64-gnu': 1.7.11 + '@swc/core-linux-x64-musl': 1.7.11 + '@swc/core-win32-arm64-msvc': 1.7.11 + '@swc/core-win32-ia32-msvc': 1.7.11 + '@swc/core-win32-x64-msvc': 1.7.11 + '@swc/helpers': 0.5.12 + + '@swc/counter@0.1.3': {} + + '@swc/helpers@0.5.12': + dependencies: + tslib: 2.6.2 + + '@swc/types@0.1.12': + dependencies: + '@swc/counter': 0.1.3 + '@szmarczak/http-timer@5.0.1': dependencies: defer-to-connect: 2.0.1 @@ -7734,6 +8042,14 @@ snapshots: '@tanstack/virtual-core@3.1.3': {} + '@tsconfig/node10@1.0.11': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.24.0 @@ -7847,7 +8163,7 @@ snapshots: '@typescript-eslint/types': 8.1.0 '@typescript-eslint/typescript-estree': 8.1.0(typescript@5.5.4) '@typescript-eslint/visitor-keys': 8.1.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) eslint: 9.9.0(jiti@1.21.0) optionalDependencies: typescript: 5.5.4 @@ -7863,7 +8179,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 8.1.0(typescript@5.5.4) '@typescript-eslint/utils': 8.1.0(eslint@9.9.0(jiti@1.21.0))(typescript@5.5.4) - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) ts-api-utils: 1.3.0(typescript@5.5.4) optionalDependencies: typescript: 5.5.4 @@ -7877,7 +8193,7 @@ snapshots: dependencies: '@typescript-eslint/types': 8.1.0 '@typescript-eslint/visitor-keys': 8.1.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.5 @@ -8250,6 +8566,11 @@ snapshots: typescript: 5.5.4 zod: 3.22.4 + abitype@1.0.5(typescript@5.5.4)(zod@3.23.8): + optionalDependencies: + typescript: 5.5.4 + zod: 3.23.8 + abort-controller@3.0.0: dependencies: event-target-shim: 5.0.1 @@ -8260,7 +8581,9 @@ snapshots: dependencies: acorn: 8.12.1 - acorn@8.11.3: {} + acorn-walk@8.3.3: + dependencies: + acorn: 8.12.1 acorn@8.12.1: {} @@ -8272,7 +8595,7 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -8329,6 +8652,8 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 + arg@4.1.3: {} + arg@5.0.2: {} argparse@1.0.10: @@ -8650,6 +8975,8 @@ snapshots: commander@10.0.1: {} + commander@12.1.0: {} + commander@2.20.3: {} commander@3.0.2: {} @@ -8719,6 +9046,8 @@ snapshots: safe-buffer: 5.2.1 sha.js: 2.4.11 + create-require@1.1.1: {} + cross-fetch@3.1.8(encoding@0.1.13): dependencies: node-fetch: 2.7.0(encoding@0.1.13) @@ -8772,10 +9101,6 @@ snapshots: dependencies: ms: 2.0.0 - debug@4.3.4: - dependencies: - ms: 2.1.2 - debug@4.3.4(supports-color@5.5.0): dependencies: ms: 2.1.2 @@ -8836,6 +9161,8 @@ snapshots: didyoumean@1.2.2: {} + diff@4.0.2: {} + diff@5.0.0: {} difflib@0.2.4: @@ -9054,7 +9381,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) escape-string-regexp: 4.0.0 eslint-scope: 8.0.2 eslint-visitor-keys: 4.0.0 @@ -9338,11 +9665,11 @@ snapshots: follow-redirects@1.15.5(debug@4.3.4): optionalDependencies: - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) follow-redirects@1.15.6(debug@4.3.4): optionalDependencies: - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) for-each@0.3.3: dependencies: @@ -9577,7 +9904,7 @@ snapshots: optionalDependencies: uglify-js: 3.17.4 - hardhat@2.22.1(bufferutil@4.0.7)(typescript@5.5.4)(utf-8-validate@5.0.10): + hardhat@2.22.1(bufferutil@4.0.7)(ts-node@10.9.2(@swc/core@1.7.11(@swc/helpers@0.5.12))(@types/node@22.1.0)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10): dependencies: '@ethersproject/abi': 5.7.0 '@metamask/eth-sig-util': 4.0.1 @@ -9596,7 +9923,7 @@ snapshots: chalk: 2.4.2 chokidar: 3.5.3 ci-info: 2.0.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) enquirer: 2.4.1 env-paths: 2.2.1 ethereum-cryptography: 1.2.0 @@ -9623,6 +9950,7 @@ snapshots: uuid: 8.3.2 ws: 7.5.9(bufferutil@4.0.7)(utf-8-validate@5.0.10) optionalDependencies: + ts-node: 10.9.2(@swc/core@1.7.11(@swc/helpers@0.5.12))(@types/node@22.1.0)(typescript@5.5.4) typescript: 5.5.4 transitivePeerDependencies: - bufferutil @@ -9630,7 +9958,7 @@ snapshots: - supports-color - utf-8-validate - hardhat@2.22.4(bufferutil@4.0.7)(typescript@5.5.4)(utf-8-validate@5.0.10): + hardhat@2.22.4(bufferutil@4.0.7)(ts-node@10.9.2(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10): dependencies: '@ethersproject/abi': 5.7.0 '@metamask/eth-sig-util': 4.0.1 @@ -9649,7 +9977,7 @@ snapshots: chalk: 2.4.2 chokidar: 3.5.3 ci-info: 2.0.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) enquirer: 2.4.1 env-paths: 2.2.1 ethereum-cryptography: 1.2.0 @@ -9676,6 +10004,7 @@ snapshots: uuid: 8.3.2 ws: 7.5.9(bufferutil@4.0.7)(utf-8-validate@5.0.10) optionalDependencies: + ts-node: 10.9.2(@swc/core@1.7.11(@swc/helpers@0.5.12))(@types/node@22.1.0)(typescript@5.5.4) typescript: 5.5.4 transitivePeerDependencies: - bufferutil @@ -9758,7 +10087,7 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -9818,7 +10147,7 @@ snapshots: dependencies: '@ioredis/commands': 1.2.0 cluster-key-slot: 1.1.2 - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) denque: 2.1.0 lodash.defaults: 4.2.0 lodash.isarguments: 3.1.0 @@ -10181,6 +10510,8 @@ snapshots: dependencies: sourcemap-codec: 1.4.8 + make-error@1.3.6: {} + md5.js@1.3.5: dependencies: hash-base: 3.1.0 @@ -10252,7 +10583,7 @@ snapshots: mlly@1.6.1: dependencies: - acorn: 8.11.3 + acorn: 8.12.1 pathe: 1.1.2 pkg-types: 1.0.3 ufo: 1.4.0 @@ -10592,12 +10923,13 @@ snapshots: camelcase-css: 2.0.1 postcss: 8.4.41 - postcss-load-config@4.0.1(postcss@8.4.41): + postcss-load-config@4.0.1(postcss@8.4.41)(ts-node@10.9.2(@types/node@22.1.0)(typescript@5.5.4)): dependencies: lilconfig: 2.1.0 yaml: 2.3.2 optionalDependencies: postcss: 8.4.41 + ts-node: 10.9.2(@swc/core@1.7.11(@swc/helpers@0.5.12))(@types/node@22.1.0)(typescript@5.5.4) postcss-nested@6.0.1(postcss@8.4.41): dependencies: @@ -11081,7 +11413,7 @@ snapshots: transitivePeerDependencies: - typescript - solidity-coverage@0.8.12(hardhat@2.22.4(bufferutil@4.0.7)(typescript@5.5.4)(utf-8-validate@5.0.10)): + solidity-coverage@0.8.12(hardhat@2.22.4(bufferutil@4.0.7)(ts-node@10.9.2(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)): dependencies: '@ethersproject/abi': 5.7.0 '@solidity-parser/parser': 0.18.0 @@ -11092,7 +11424,7 @@ snapshots: ghost-testrpc: 0.0.2 global-modules: 2.0.0 globby: 10.0.2 - hardhat: 2.22.4(bufferutil@4.0.7)(typescript@5.5.4)(utf-8-validate@5.0.10) + hardhat: 2.22.4(bufferutil@4.0.7)(ts-node@10.9.2(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10) jsonschema: 1.4.1 lodash: 4.17.21 mocha: 10.2.0 @@ -11276,11 +11608,11 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 - tailwindcss-animate@1.0.7(tailwindcss@3.4.1): + tailwindcss-animate@1.0.7(tailwindcss@3.4.1(ts-node@10.9.2(@types/node@22.1.0)(typescript@5.5.4))): dependencies: - tailwindcss: 3.4.1 + tailwindcss: 3.4.1(ts-node@10.9.2(@types/node@22.1.0)(typescript@5.5.4)) - tailwindcss@3.4.1: + tailwindcss@3.4.1(ts-node@10.9.2(@types/node@22.1.0)(typescript@5.5.4)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -11299,7 +11631,7 @@ snapshots: postcss: 8.4.41 postcss-import: 15.1.0(postcss@8.4.41) postcss-js: 4.0.1(postcss@8.4.41) - postcss-load-config: 4.0.1(postcss@8.4.41) + postcss-load-config: 4.0.1(postcss@8.4.41)(ts-node@10.9.2(@types/node@22.1.0)(typescript@5.5.4)) postcss-nested: 6.0.1(postcss@8.4.41) postcss-selector-parser: 6.0.13 resolve: 1.22.6 @@ -11372,6 +11704,26 @@ snapshots: ts-interface-checker@0.1.13: {} + ts-node@10.9.2(@swc/core@1.7.11(@swc/helpers@0.5.12))(@types/node@22.1.0)(typescript@5.5.4): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 22.1.0 + acorn: 8.12.1 + acorn-walk: 8.3.3 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.5.4 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + optionalDependencies: + '@swc/core': 1.7.11(@swc/helpers@0.5.12) + tslib@1.14.1: {} tslib@2.4.0: {} @@ -11575,6 +11927,8 @@ snapshots: uuid@9.0.1: {} + v8-compile-cache-lib@3.0.1: {} + valtio@1.11.2(@types/react@18.3.3)(react@18.2.0): dependencies: proxy-compare: 2.5.1 @@ -11601,11 +11955,29 @@ snapshots: - utf-8-validate - zod + viem@2.19.6(bufferutil@4.0.7)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.23.8): + dependencies: + '@adraffy/ens-normalize': 1.10.0 + '@noble/curves': 1.4.0 + '@noble/hashes': 1.4.0 + '@scure/bip32': 1.4.0 + '@scure/bip39': 1.3.0 + abitype: 1.0.5(typescript@5.5.4)(zod@3.23.8) + isows: 1.0.4(ws@8.17.1(bufferutil@4.0.7)(utf-8-validate@5.0.10)) + webauthn-p256: 0.0.5 + ws: 8.17.1(bufferutil@4.0.7)(utf-8-validate@5.0.10) + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - zod + vite-plugin-mkcert@1.17.5(vite@5.4.0(@types/node@22.1.0)(terser@5.21.0)): dependencies: '@octokit/rest': 20.0.2 axios: 1.7.3(debug@4.3.4) - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) picocolors: 1.0.0 vite: 5.4.0(@types/node@22.1.0)(terser@5.21.0) transitivePeerDependencies: @@ -11613,7 +11985,7 @@ snapshots: vite-plugin-pwa@0.20.1(vite@5.4.0(@types/node@22.1.0)(terser@5.21.0))(workbox-build@7.0.0(@types/babel__core@7.20.5))(workbox-window@7.0.0): dependencies: - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) pretty-bytes: 6.1.1 tinyglobby: 0.2.2 vite: 5.4.0(@types/node@22.1.0)(terser@5.21.0) @@ -11647,7 +12019,7 @@ snapshots: dependencies: chalk: 4.1.2 commander: 9.5.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -12029,6 +12401,10 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 + yn@3.1.1: {} + yocto-queue@0.1.0: {} zod@3.22.4: {} + + zod@3.23.8: {}