From c16b0d56bacfb4ef410f58021c02e2d6b1818951 Mon Sep 17 00:00:00 2001 From: Adam Egyed <5456061+adamegyed@users.noreply.github.com> Date: Fri, 14 Feb 2025 10:59:27 -0500 Subject: [PATCH] feat: eip-7702 ui demo (#1302) * feat: 7702 ui demo feat: update 7702 mint flow to actually use 7702 feat: partial recurring transfer support feat: ui progress feat: react state management feat: add permission hooks to session key fix: add templates to demo build fix: declare env var in turbo feat: ui demo user card reflects 7702 delegation status (#1294) * feat: have user avatar reflect if account has been delegated * feat: reflects delegation status in user details card * fix: forces delegation status query to refetch until available * chore: updates comment * refactor: uses viem public client and refetches delegation addr when nftTransferred status changes * refactor: uses defined url constant * chore: removes unnecessary check fix: coalesce undefined code result fix: forces delegation query to refetch until available feat: updated 7702 info blurb chore: cleanup * fix: 7702 styling - lofi * fix: move 7702 out of shared * feat: responsive - 7702 - ui demo branch (#1318) * feat: responsive transaction card * feat: responsive cards * fix: 7702 client loading state (#1319) * feat: update copy * feat: use the new unified mav2 client * fix: switch type to mode in sma7702 client hook * feat: mav2 with small cards in ui demo (#1346) * feat: mav2 in ui demo * chore: move files back * chore: rename MAv2 client hook * feat: update link and copy --------- Co-authored-by: rob chang Co-authored-by: Rob <30500864+RobChangCA@users.noreply.github.com> Co-authored-by: jakehobbs --- .eslintignore | 1 + .gitmodules | 6 + .../ma-v2/modules/allowlist-module/module.ts | 29 ++ .../ma-v2/modules/time-range-module/module.ts | 35 +- examples/ui-demo/contracts/.gitignore | 23 + examples/ui-demo/contracts/Deployments.md | 11 + examples/ui-demo/contracts/README.md | 66 +++ .../911867/run-1736807829.json | 57 ++ .../911867/run-1736537366.json | 97 ++++ .../contracts/config/slither.config.json | 3 + .../contracts/config/solhint-script.json | 21 + .../ui-demo/contracts/config/solhint-src.json | 17 + .../contracts/config/solhint-test.json | 20 + examples/ui-demo/contracts/foundry.toml | 58 +++ examples/ui-demo/contracts/lib/forge-std | 1 + .../contracts/lib/openzeppelin-contracts | 1 + examples/ui-demo/contracts/package.json | 12 + examples/ui-demo/contracts/pnpm-lock.yaml | 489 ++++++++++++++++++ examples/ui-demo/contracts/remappings.txt | 3 + .../ui-demo/contracts/script/Counter.s.sol | 19 + .../ui-demo/contracts/script/DeployNFT.s.sol | 24 + .../contracts/script/DeploySwapVenue.s.sol | 18 + examples/ui-demo/contracts/src/Counter.sol | 14 + .../ui-demo/contracts/src/ERC20Mintable.sol | 16 + examples/ui-demo/contracts/src/NFT.sol | 41 ++ examples/ui-demo/contracts/src/Swap.sol | 34 ++ examples/ui-demo/contracts/test/Counter.t.sol | 24 + examples/ui-demo/env.mjs | 2 + examples/ui-demo/package.json | 2 +- .../src/app/api/bundler-odyssey/route.ts | 30 ++ examples/ui-demo/src/app/config.tsx | 7 + .../configuration/Configuration.tsx | 52 ++ .../ConfigurationSidebarWrapper.tsx | 2 + examples/ui-demo/src/components/icons/key.tsx | 22 + .../ui-demo/src/components/icons/loading.tsx | 3 +- .../ui-demo/src/components/icons/settings.tsx | 21 + .../src/components/mint-card/MintCard.tsx | 148 ------ .../mint-card/MintCardActionButtons.tsx | 52 -- .../ui-demo/src/components/mint-card/NFT.tsx | 147 ------ .../src/components/mint-card/ValueProps.tsx | 86 --- .../components/preview/MobileSplashPage.tsx | 2 +- .../src/components/preview/PreviewWrapper.tsx | 8 +- .../components/shared/WalletTypeSwitch.tsx | 53 ++ .../src/components/small-cards/Button.tsx | 18 + .../src/components/small-cards/MintCard.tsx | 108 ++++ .../src/components/small-cards/MintStages.tsx | 68 +++ .../components/small-cards/Transactions.tsx | 76 +++ .../small-cards/TransactionsCard.tsx | 82 +++ .../src/components/small-cards/Wrapper.tsx | 27 + .../RenderUserConnectionAvatar.tsx | 92 +++- .../UserConnectionAvatar.tsx | 2 +- .../UserConnectionDetails.tsx | 113 ++-- examples/ui-demo/src/hooks/7702/constants.ts | 3 + .../src/hooks/7702/dca/abi/erc20Mintable.ts | 344 ++++++++++++ .../ui-demo/src/hooks/7702/dca/abi/swap.ts | 87 ++++ .../ui-demo/src/hooks/7702/dca/constants.ts | 10 + .../src/hooks/7702/dca/sessionKeyInit.ts | 0 .../ui-demo/src/hooks/7702/genEntityId.ts | 8 + .../ui-demo/src/hooks/7702/transportSetup.ts | 79 +++ examples/ui-demo/src/hooks/useMint7702.tsx | 141 +++++ examples/ui-demo/src/hooks/useMintDefault.tsx | 149 ++++++ .../src/hooks/useModularAccountV2Client.ts | 126 +++++ .../src/hooks/useRecurringTransactions.ts | 284 ++++++++++ examples/ui-demo/src/state/store.tsx | 9 +- examples/ui-demo/src/utils/config.ts | 2 + examples/ui-demo/tailwind.config.ts | 4 + examples/ui-demo/turbo.json | 7 +- package.json | 2 +- yarn.lock | 2 +- 69 files changed, 3126 insertions(+), 494 deletions(-) create mode 100644 examples/ui-demo/contracts/.gitignore create mode 100644 examples/ui-demo/contracts/Deployments.md create mode 100644 examples/ui-demo/contracts/README.md create mode 100644 examples/ui-demo/contracts/broadcast/DeployNFT.s.sol/911867/run-1736807829.json create mode 100644 examples/ui-demo/contracts/broadcast/DeploySwapVenue.s.sol/911867/run-1736537366.json create mode 100644 examples/ui-demo/contracts/config/slither.config.json create mode 100644 examples/ui-demo/contracts/config/solhint-script.json create mode 100644 examples/ui-demo/contracts/config/solhint-src.json create mode 100644 examples/ui-demo/contracts/config/solhint-test.json create mode 100644 examples/ui-demo/contracts/foundry.toml create mode 160000 examples/ui-demo/contracts/lib/forge-std create mode 160000 examples/ui-demo/contracts/lib/openzeppelin-contracts create mode 100644 examples/ui-demo/contracts/package.json create mode 100644 examples/ui-demo/contracts/pnpm-lock.yaml create mode 100644 examples/ui-demo/contracts/remappings.txt create mode 100644 examples/ui-demo/contracts/script/Counter.s.sol create mode 100644 examples/ui-demo/contracts/script/DeployNFT.s.sol create mode 100644 examples/ui-demo/contracts/script/DeploySwapVenue.s.sol create mode 100644 examples/ui-demo/contracts/src/Counter.sol create mode 100644 examples/ui-demo/contracts/src/ERC20Mintable.sol create mode 100644 examples/ui-demo/contracts/src/NFT.sol create mode 100644 examples/ui-demo/contracts/src/Swap.sol create mode 100644 examples/ui-demo/contracts/test/Counter.t.sol create mode 100644 examples/ui-demo/src/app/api/bundler-odyssey/route.ts create mode 100644 examples/ui-demo/src/components/configuration/Configuration.tsx create mode 100644 examples/ui-demo/src/components/icons/key.tsx create mode 100644 examples/ui-demo/src/components/icons/settings.tsx delete mode 100644 examples/ui-demo/src/components/mint-card/MintCard.tsx delete mode 100644 examples/ui-demo/src/components/mint-card/MintCardActionButtons.tsx delete mode 100644 examples/ui-demo/src/components/mint-card/NFT.tsx delete mode 100644 examples/ui-demo/src/components/mint-card/ValueProps.tsx create mode 100644 examples/ui-demo/src/components/shared/WalletTypeSwitch.tsx create mode 100644 examples/ui-demo/src/components/small-cards/Button.tsx create mode 100644 examples/ui-demo/src/components/small-cards/MintCard.tsx create mode 100644 examples/ui-demo/src/components/small-cards/MintStages.tsx create mode 100644 examples/ui-demo/src/components/small-cards/Transactions.tsx create mode 100644 examples/ui-demo/src/components/small-cards/TransactionsCard.tsx create mode 100644 examples/ui-demo/src/components/small-cards/Wrapper.tsx create mode 100644 examples/ui-demo/src/hooks/7702/constants.ts create mode 100644 examples/ui-demo/src/hooks/7702/dca/abi/erc20Mintable.ts create mode 100644 examples/ui-demo/src/hooks/7702/dca/abi/swap.ts create mode 100644 examples/ui-demo/src/hooks/7702/dca/constants.ts create mode 100644 examples/ui-demo/src/hooks/7702/dca/sessionKeyInit.ts create mode 100644 examples/ui-demo/src/hooks/7702/genEntityId.ts create mode 100644 examples/ui-demo/src/hooks/7702/transportSetup.ts create mode 100644 examples/ui-demo/src/hooks/useMint7702.tsx create mode 100644 examples/ui-demo/src/hooks/useMintDefault.tsx create mode 100644 examples/ui-demo/src/hooks/useModularAccountV2Client.ts create mode 100644 examples/ui-demo/src/hooks/useRecurringTransactions.ts diff --git a/.eslintignore b/.eslintignore index bb2c49e534..fd6eec95a6 100644 --- a/.eslintignore +++ b/.eslintignore @@ -6,6 +6,7 @@ site/.vitepress/cache/**/* /examples/* !/examples/ui-demo +/examples/ui-demo/contracts /examples/ui-demo/.next/* **/.turbo/* diff --git a/.gitmodules b/.gitmodules index 336230713d..e66ab99492 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,9 @@ [submodule "examples/embedded-accounts-quickstart"] path = examples/embedded-accounts-quickstart url = https://github.com/alchemyplatform/embedded-accounts-quickstart +[submodule "examples/ui-demo/contracts/lib/openzeppelin-contracts"] + path = examples/ui-demo/contracts/lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts +[submodule "examples/ui-demo/contracts/lib/forge-std"] + path = examples/ui-demo/contracts/lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/account-kit/smart-contracts/src/ma-v2/modules/allowlist-module/module.ts b/account-kit/smart-contracts/src/ma-v2/modules/allowlist-module/module.ts index 911f53b1fd..9a8186381c 100644 --- a/account-kit/smart-contracts/src/ma-v2/modules/allowlist-module/module.ts +++ b/account-kit/smart-contracts/src/ma-v2/modules/allowlist-module/module.ts @@ -1,8 +1,37 @@ import { encodeAbiParameters, type Address, type Hex } from "viem"; import { allowlistModuleAbi } from "./abis/allowlistModuleAbi.js"; +import { HookType, type HookConfig } from "../../actions/common/types.js"; export const AllowlistModule = { abi: allowlistModuleAbi, + buildHook: ( + installArgs: { + entityId: number; + inputs: Array<{ + target: Address; + hasSelectorAllowlist: boolean; + hasERC20SpendLimit: boolean; + erc20SpendLimit: bigint; + selectors: Array; + }>; + }, + address: Address + ): { + hookConfig: HookConfig; + initData: Hex; + } => { + const installData = AllowlistModule.encodeOnInstallData(installArgs); + return { + hookConfig: { + address: address, + entityId: installArgs.entityId, + hookType: HookType.VALIDATION, + hasPreHooks: true, + hasPostHooks: false, + }, + initData: installData, + }; + }, encodeOnInstallData: (args: { entityId: number; inputs: Array<{ diff --git a/account-kit/smart-contracts/src/ma-v2/modules/time-range-module/module.ts b/account-kit/smart-contracts/src/ma-v2/modules/time-range-module/module.ts index a6b3680278..2c2e131138 100644 --- a/account-kit/smart-contracts/src/ma-v2/modules/time-range-module/module.ts +++ b/account-kit/smart-contracts/src/ma-v2/modules/time-range-module/module.ts @@ -1,9 +1,42 @@ -import { encodeAbiParameters, type Hex } from "viem"; +import { encodeAbiParameters, type Address, type Hex } from "viem"; import { timeRangeModuleAbi } from "./abis/timeRangeModuleAbi.js"; +import { HookType, type HookConfig } from "../../actions/common/types.js"; export const TimeRangeModule = { abi: timeRangeModuleAbi, + buildHook: ( + installArgs: { + entityId: number; + validUntil: number; + validAfter: number; + }, + address: Address + ): { hookConfig: HookConfig; initData: Hex } => { + if (installArgs.validUntil > 2 ** 48 - 1) { + throw new Error( + "TimeRangeModule.buildHook: validUntil > type(uint48).max" + ); + } + + if (installArgs.validAfter > 2 ** 48 - 1) { + throw new Error( + "TimeRangeModule.buildHook: validAfter > type(uint48).max" + ); + } + + const installData = TimeRangeModule.encodeOnInstallData(installArgs); + return { + hookConfig: { + address: address, + entityId: installArgs.entityId, + hookType: HookType.VALIDATION, + hasPreHooks: false, + hasPostHooks: false, + }, + initData: installData, + }; + }, encodeOnInstallData: (args: { entityId: number; validUntil: number; diff --git a/examples/ui-demo/contracts/.gitignore b/examples/ui-demo/contracts/.gitignore new file mode 100644 index 0000000000..920b50ddf0 --- /dev/null +++ b/examples/ui-demo/contracts/.gitignore @@ -0,0 +1,23 @@ +# Foundry build and cache directories +out/ +out-optimized/ +cache/ +node_modules/ + +# Coverage +report/ +lcov.info + +# env vars +.env + +# deployments +broadcast/**/run-latest.json +broadcast/**/dry-run/**/* + +# misc +.DS_Store +**/.DS_Store + +# Monorepo support: unhide lib/ +!lib/ \ No newline at end of file diff --git a/examples/ui-demo/contracts/Deployments.md b/examples/ui-demo/contracts/Deployments.md new file mode 100644 index 0000000000..c0d6b9a12c --- /dev/null +++ b/examples/ui-demo/contracts/Deployments.md @@ -0,0 +1,11 @@ +## Mock Swap Venue + +Salt: 0 + +Swap: 0xB0AEC4c25E8332256A91bBaf169E3C32dfC3C33C +DemoUSDC: 0xCFf7C6dA719408113DFcb5e36182c6d5aa491443 +DemoWETH: 0x0766798566D1f6e2f0b126f7783aaB2CBb81c66f + +## AccountKit Demo NFT + +0x7E06a337929B1Cb92363e15414e37959a36E5338 \ No newline at end of file diff --git a/examples/ui-demo/contracts/README.md b/examples/ui-demo/contracts/README.md new file mode 100644 index 0000000000..9265b45584 --- /dev/null +++ b/examples/ui-demo/contracts/README.md @@ -0,0 +1,66 @@ +## Foundry + +**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** + +Foundry consists of: + +- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). +- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. +- **Chisel**: Fast, utilitarian, and verbose solidity REPL. + +## Documentation + +https://book.getfoundry.sh/ + +## Usage + +### Build + +```shell +$ forge build +``` + +### Test + +```shell +$ forge test +``` + +### Format + +```shell +$ forge fmt +``` + +### Gas Snapshots + +```shell +$ forge snapshot +``` + +### Anvil + +```shell +$ anvil +``` + +### Deploy + +```shell +$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key +``` + +### Cast + +```shell +$ cast +``` + +### Help + +```shell +$ forge --help +$ anvil --help +$ cast --help +``` diff --git a/examples/ui-demo/contracts/broadcast/DeployNFT.s.sol/911867/run-1736807829.json b/examples/ui-demo/contracts/broadcast/DeployNFT.s.sol/911867/run-1736807829.json new file mode 100644 index 0000000000..60b056b486 --- /dev/null +++ b/examples/ui-demo/contracts/broadcast/DeployNFT.s.sol/911867/run-1736807829.json @@ -0,0 +1,57 @@ +{ + "transactions": [ + { + "hash": "0x02ae4a484c89989ca992c8024646256c2e37a21bb1d9aab9235fd9a4ae0b92d6", + "transactionType": "CREATE2", + "contractName": "NFT", + "contractAddress": "0x7e06a337929b1cb92363e15414e37959a36e5338", + "function": null, + "arguments": [ + "Account Kit Demo NFT", + "AKD", + "https://static.alchemyapi.io/assets/accountkit/accountkit.jpg" + ], + "transaction": { + "from": "0xddf32240b4ca3184de7ec8f0d5aba27dec8b7a5c", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x15dc14", + "value": "0x0", + "input": "0x0000000000000000000000000000000000000000000000000000000000000000608060405234801561000f575f80fd5b5060405161124b38038061124b83398101604081905261002e916100ff565b82825f61003b8382610212565b5060016100488282610212565b506006915061005990508282610212565b505050506102cc565b634e487b7160e01b5f52604160045260245ffd5b5f82601f830112610085575f80fd5b81516001600160401b0381111561009e5761009e610062565b604051601f8201601f19908116603f011681016001600160401b03811182821017156100cc576100cc610062565b6040528181528382016020018510156100e3575f80fd5b8160208501602083015e5f918101602001919091529392505050565b5f805f60608486031215610111575f80fd5b83516001600160401b03811115610126575f80fd5b61013286828701610076565b602086015190945090506001600160401b0381111561014f575f80fd5b61015b86828701610076565b604086015190935090506001600160401b03811115610178575f80fd5b61018486828701610076565b9150509250925092565b600181811c908216806101a257607f821691505b6020821081036101c057634e487b7160e01b5f52602260045260245ffd5b50919050565b601f82111561020d57805f5260205f20601f840160051c810160208510156101eb5750805b601f840160051c820191505b8181101561020a575f81556001016101f7565b50505b505050565b81516001600160401b0381111561022b5761022b610062565b61023f81610239845461018e565b846101c6565b6020601f821160018114610271575f831561025a5750848201515b5f19600385901b1c1916600184901b17845561020a565b5f84815260208120601f198516915b828110156102a05787850151825560209485019460019092019101610280565b50848210156102bd57868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b610f72806102d95f395ff3fe6080604052600436106100ee575f3560e01c80636c0360eb11610087578063a22cb46511610057578063a22cb46514610279578063b88d4fde14610298578063c87b56dd146102b7578063e985e9c5146102d6575f80fd5b80636c0360eb1461021f57806370a0823114610233578063755edd171461025257806395d89b4114610265575f80fd5b8063095ea7b3116100c2578063095ea7b3146101a157806323b872dd146101c257806342842e0e146101e15780636352211e14610200575f80fd5b80629a9b7b146100f257806301ffc9a71461011a57806306fdde0314610149578063081812fc1461016a575b5f80fd5b3480156100fd575f80fd5b5061010760075481565b6040519081526020015b60405180910390f35b348015610125575f80fd5b50610139610134366004610c48565b6102f5565b6040519015158152602001610111565b348015610154575f80fd5b5061015d610346565b6040516101119190610c98565b348015610175575f80fd5b50610189610184366004610caa565b6103d5565b6040516001600160a01b039091168152602001610111565b3480156101ac575f80fd5b506101c06101bb366004610cdc565b6103fc565b005b3480156101cd575f80fd5b506101c06101dc366004610d04565b61040b565b3480156101ec575f80fd5b506101c06101fb366004610d04565b610499565b34801561020b575f80fd5b5061018961021a366004610caa565b6104b8565b34801561022a575f80fd5b5061015d6104c2565b34801561023e575f80fd5b5061010761024d366004610d3e565b61054e565b610107610260366004610d3e565b610593565b348015610270575f80fd5b5061015d6105b4565b348015610284575f80fd5b506101c0610293366004610d57565b6105c3565b3480156102a3575f80fd5b506101c06102b2366004610da4565b6105ce565b3480156102c2575f80fd5b5061015d6102d1366004610caa565b6105e6565b3480156102e1575f80fd5b506101396102f0366004610e81565b6106a9565b5f6001600160e01b031982166380ac58cd60e01b148061032557506001600160e01b03198216635b5e139f60e01b145b8061034057506301ffc9a760e01b6001600160e01b03198316145b92915050565b60605f805461035490610eb2565b80601f016020809104026020016040519081016040528092919081815260200182805461038090610eb2565b80156103cb5780601f106103a2576101008083540402835291602001916103cb565b820191905f5260205f20905b8154815290600101906020018083116103ae57829003601f168201915b5050505050905090565b5f6103df826106d6565b505f828152600460205260409020546001600160a01b0316610340565b61040782823361070e565b5050565b6001600160a01b03821661043957604051633250574960e11b81525f60048201526024015b60405180910390fd5b5f61044583833361071b565b9050836001600160a01b0316816001600160a01b031614610493576040516364283d7b60e01b81526001600160a01b0380861660048301526024820184905282166044820152606401610430565b50505050565b6104b383838360405180602001604052805f8152506105ce565b505050565b5f610340826106d6565b600680546104cf90610eb2565b80601f01602080910402602001604051908101604052809291908181526020018280546104fb90610eb2565b80156105465780601f1061051d57610100808354040283529160200191610546565b820191905f5260205f20905b81548152906001019060200180831161052957829003601f168201915b505050505081565b5f6001600160a01b038216610578576040516322718ad960e21b81525f6004820152602401610430565b506001600160a01b03165f9081526003602052604090205490565b5f8060075f81546105a390610eea565b91829055509050610340838261080d565b60606001805461035490610eb2565b610407338383610826565b6105d984848461040b565b61049333858585856108c4565b60605f6105f2836104b8565b6001600160a01b0316036106195760405163d872946b60e01b815260040160405180910390fd5b6006805461062690610eb2565b80601f016020809104026020016040519081016040528092919081815260200182805461065290610eb2565b801561069d5780601f106106745761010080835404028352916020019161069d565b820191905f5260205f20905b81548152906001019060200180831161068057829003601f168201915b50505050509050919050565b6001600160a01b039182165f90815260056020908152604080832093909416825291909152205460ff1690565b5f818152600260205260408120546001600160a01b03168061034057604051637e27328960e01b815260048101849052602401610430565b6104b383838360016109ec565b5f828152600260205260408120546001600160a01b039081169083161561074757610747818486610af0565b6001600160a01b03811615610781576107625f855f806109ec565b6001600160a01b0381165f90815260036020526040902080545f190190555b6001600160a01b038516156107af576001600160a01b0385165f908152600360205260409020805460010190555b5f8481526002602052604080822080546001600160a01b0319166001600160a01b0389811691821790925591518793918516917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91a4949350505050565b610407828260405180602001604052805f815250610b54565b6001600160a01b03821661085857604051630b61174360e31b81526001600160a01b0383166004820152602401610430565b6001600160a01b038381165f81815260056020908152604080832094871680845294825291829020805460ff191686151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a3505050565b6001600160a01b0383163b156109e557604051630a85bd0160e11b81526001600160a01b0384169063150b7a0290610906908890889087908790600401610f0e565b6020604051808303815f875af1925050508015610940575060408051601f3d908101601f1916820190925261093d91810190610f4a565b60015b6109a7573d80801561096d576040519150601f19603f3d011682016040523d82523d5f602084013e610972565b606091505b5080515f0361099f57604051633250574960e11b81526001600160a01b0385166004820152602401610430565b805181602001fd5b6001600160e01b03198116630a85bd0160e11b146109e357604051633250574960e11b81526001600160a01b0385166004820152602401610430565b505b5050505050565b8080610a0057506001600160a01b03821615155b15610ac1575f610a0f846106d6565b90506001600160a01b03831615801590610a3b5750826001600160a01b0316816001600160a01b031614155b8015610a4e5750610a4c81846106a9565b155b15610a775760405163a9fbf51f60e01b81526001600160a01b0384166004820152602401610430565b8115610abf5783856001600160a01b0316826001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45b505b50505f90815260046020526040902080546001600160a01b0319166001600160a01b0392909216919091179055565b610afb838383610b6b565b6104b3576001600160a01b038316610b2957604051637e27328960e01b815260048101829052602401610430565b60405163177e802f60e01b81526001600160a01b038316600482015260248101829052604401610430565b610b5e8383610bcf565b6104b3335f8585856108c4565b5f6001600160a01b03831615801590610bc75750826001600160a01b0316846001600160a01b03161480610ba45750610ba484846106a9565b80610bc757505f828152600460205260409020546001600160a01b038481169116145b949350505050565b6001600160a01b038216610bf857604051633250574960e11b81525f6004820152602401610430565b5f610c0483835f61071b565b90506001600160a01b038116156104b3576040516339e3563760e11b81525f6004820152602401610430565b6001600160e01b031981168114610c45575f80fd5b50565b5f60208284031215610c58575f80fd5b8135610c6381610c30565b9392505050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f610c636020830184610c6a565b5f60208284031215610cba575f80fd5b5035919050565b80356001600160a01b0381168114610cd7575f80fd5b919050565b5f8060408385031215610ced575f80fd5b610cf683610cc1565b946020939093013593505050565b5f805f60608486031215610d16575f80fd5b610d1f84610cc1565b9250610d2d60208501610cc1565b929592945050506040919091013590565b5f60208284031215610d4e575f80fd5b610c6382610cc1565b5f8060408385031215610d68575f80fd5b610d7183610cc1565b915060208301358015158114610d85575f80fd5b809150509250929050565b634e487b7160e01b5f52604160045260245ffd5b5f805f8060808587031215610db7575f80fd5b610dc085610cc1565b9350610dce60208601610cc1565b925060408501359150606085013567ffffffffffffffff811115610df0575f80fd5b8501601f81018713610e00575f80fd5b803567ffffffffffffffff811115610e1a57610e1a610d90565b604051601f8201601f19908116603f0116810167ffffffffffffffff81118282101715610e4957610e49610d90565b604052818152828201602001891015610e60575f80fd5b816020840160208301375f6020838301015280935050505092959194509250565b5f8060408385031215610e92575f80fd5b610e9b83610cc1565b9150610ea960208401610cc1565b90509250929050565b600181811c90821680610ec657607f821691505b602082108103610ee457634e487b7160e01b5f52602260045260245ffd5b50919050565b5f60018201610f0757634e487b7160e01b5f52601160045260245ffd5b5060010190565b6001600160a01b03858116825284166020820152604081018390526080606082018190525f90610f4090830184610c6a565b9695505050505050565b5f60208284031215610f5a575f80fd5b8151610c6381610c3056fea164736f6c634300081a000a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000144163636f756e74204b69742044656d6f204e46540000000000000000000000000000000000000000000000000000000000000000000000000000000000000003414b440000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003d68747470733a2f2f7374617469632e616c6368656d796170692e696f2f6173736574732f6163636f756e746b69742f6163636f756e746b69742e6a7067000000", + "nonce": "0x24", + "chainId": "0xde9fb" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0x107eb8", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x02ae4a484c89989ca992c8024646256c2e37a21bb1d9aab9235fd9a4ae0b92d6", + "transactionIndex": "0x1", + "blockHash": "0x9e4144012ac3be4cda0949deacd9f4e7ec834e7aa7f1b6353e72ae36054aeff4", + "blockNumber": "0x7d5534", + "gasUsed": "0xfd376", + "effectiveGasPrice": "0xfd", + "from": "0xddf32240b4ca3184de7ec8f0d5aba27dec8b7a5c", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": "0x7e06a337929b1cb92363e15414e37959a36e5338", + "l1BaseFeeScalar": "0xa6fe0", + "l1BlobBaseFee": "0x337", + "l1BlobBaseFeeScalar": "0x0", + "l1Fee": "0x27ae655b4d4", + "l1GasPrice": "0x5366709", + "l1GasUsed": "0xb213" + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1736807829, + "chain": 911867, + "commit": "42e6bae6" +} \ No newline at end of file diff --git a/examples/ui-demo/contracts/broadcast/DeploySwapVenue.s.sol/911867/run-1736537366.json b/examples/ui-demo/contracts/broadcast/DeploySwapVenue.s.sol/911867/run-1736537366.json new file mode 100644 index 0000000000..815725bde8 --- /dev/null +++ b/examples/ui-demo/contracts/broadcast/DeploySwapVenue.s.sol/911867/run-1736537366.json @@ -0,0 +1,97 @@ +{ + "transactions": [ + { + "hash": "0xff3fd4a479bc16ce127103e5b292c041e10b7fbb9791b5a1420016d04c89ec23", + "transactionType": "CREATE2", + "contractName": "Swap", + "contractAddress": "0xb0aec4c25e8332256a91bbaf169e3c32dfc3c33c", + "function": null, + "arguments": null, + "transaction": { + "from": "0xddf32240b4ca3184de7ec8f0d5aba27dec8b7a5c", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x25334e", + "value": "0x0", + "input": "0x00000000000000000000000000000000000000000000000000000000000000006080806040523461020c57610c80818101906001600160401b0382118383101761021057610882928184823960c0815f9460408152600860408201526744656d6f5553444360c01b60608201526080602082015260046080820152635553444360e01b60a08201520301905ff08015610201575f80546001600160a01b0319166001600160a01b03929092169182179055803b1561020c576040516340c10f1960e01b81523060048201526001600160a01b036024820152905f908290604490829084905af18015610201576101ec575b50604051818101939091906001600160401b038511838610176101d857839460c09284928339604081526008604082015267088cadadeae8aa8960c31b60608201526080602082015260046080820152630ae8aa8960e31b60a082015203019082f080156101cb57600180546001600160a01b0319166001600160a01b03929092169182179055803b156101c8576040516340c10f1960e01b81523060048201526001600160a01b03602482015291908290604490829084905af180156101bd576101a5575b60405161063a90816102488239f35b6101b0828092610224565b6101ba5780610196565b80fd5b6040513d84823e3d90fd5b50fd5b50604051903d90823e3d90fd5b634e487b7160e01b84526041600452602484fd5b6101f99192505f90610224565b5f905f6100d0565b6040513d5f823e3d90fd5b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b601f909101601f19168101906001600160401b038211908210176102105760405256fe6080806040526004361015610012575f80fd5b5f905f3560e01c9081631b2ef1ca14610454575080633e413bee146104035780633fc8cef3146103b1578063ccdc7d761461022d5763eebba33314610055575f80fd5b3461022a5761006336610573565b90678ac7230489e8000082116101cc5782546040517f23b872dd000000000000000000000000000000000000000000000000000000008152336004820152306024820152604481019290925283929060209083906064908290879073ffffffffffffffffffffffffffffffffffffffff165af19081156101c15760209261015d926101a6575b5073ffffffffffffffffffffffffffffffffffffffff60015416906040519485809481937fa9059cbb00000000000000000000000000000000000000000000000000000000835233600484016020909392919373ffffffffffffffffffffffffffffffffffffffff60408201951681520152565b03925af1801561019b5761016f575080f35b6101909060203d602011610194575b61018881836105a7565b810190610615565b5080f35b503d61017e565b6040513d84823e3d90fd5b6101bc90843d86116101945761018881836105a7565b6100e9565b6040513d85823e3d90fd5b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f4d617820313020657468207377617020617420612074696d65000000000000006044820152fd5b80fd5b503461022a5761023c36610573565b9069152d02c7e14af68000008211610353576001546040517f23b872dd000000000000000000000000000000000000000000000000000000008152336004820152306024820152604481019290925283929060209083906064908290879073ffffffffffffffffffffffffffffffffffffffff165af19081156101c15760209261015d92610338575b5073ffffffffffffffffffffffffffffffffffffffff845416906040519485809481937fa9059cbb00000000000000000000000000000000000000000000000000000000835233600484016020909392919373ffffffffffffffffffffffffffffffffffffffff60408201951681520152565b61034e90843d86116101945761018881836105a7565b6102c5565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f4d6178203130306b2055534443207377617020617420612074696d65000000006044820152fd5b503461022a57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261022a57602073ffffffffffffffffffffffffffffffffffffffff60015416604051908152f35b503461022a57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261022a5773ffffffffffffffffffffffffffffffffffffffff6020915416604051908152f35b823461056f5761046336610573565b929073ffffffffffffffffffffffffffffffffffffffff5f541690813b1561056f577f40c10f1900000000000000000000000000000000000000000000000000000000835233600484015260248301525f908290604490829084905af1801561056457610551575b50809173ffffffffffffffffffffffffffffffffffffffff60015416803b1561054d576040517f40c10f19000000000000000000000000000000000000000000000000000000008152336004820152602481019290925282908290604490829084905af1801561019b5761053c5750f35b81610546916105a7565b61022a5780f35b5050fd5b61055d91505f906105a7565b5f826104cb565b6040513d5f823e3d90fd5b5f80fd5b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc604091011261056f576004359060243590565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176105e857604052565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b9081602091031261056f5751801515810361056f579056fea164736f6c634300081a000a60806040523461031057610c808038038061001981610314565b9283398101906040818303126103105780516001600160401b0381116103105782610045918301610339565b60208201519092906001600160401b038111610310576100659201610339565b81516001600160401b03811161022357600354600181811c91168015610306575b602082101461020557601f81116102a3575b50602092601f821160011461024257928192935f92610237575b50508160011b915f199060031b1c1916176003555b80516001600160401b03811161022357600454600181811c91168015610219575b602082101461020557601f81116101a2575b50602091601f8211600114610142579181925f92610137575b50508160011b915f199060031b1c1916176004555b6040516108f5908161038b8239f35b015190505f80610113565b601f1982169260045f52805f20915f5b85811061018a57508360019510610172575b505050811b01600455610128565b01515f1960f88460031b161c191690555f8080610164565b91926020600181928685015181550194019201610152565b60045f527f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b601f830160051c810191602084106101fb575b601f0160051c01905b8181106101f057506100fa565b5f81556001016101e3565b90915081906101da565b634e487b7160e01b5f52602260045260245ffd5b90607f16906100e8565b634e487b7160e01b5f52604160045260245ffd5b015190505f806100b2565b601f1982169360035f52805f20915f5b86811061028b5750836001959610610273575b505050811b016003556100c7565b01515f1960f88460031b161c191690555f8080610265565b91926020600181928685015181550194019201610252565b60035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b601f830160051c810191602084106102fc575b601f0160051c01905b8181106102f15750610098565b5f81556001016102e4565b90915081906102db565b90607f1690610086565b5f80fd5b6040519190601f01601f191682016001600160401b0381118382101761022357604052565b81601f82011215610310578051906001600160401b03821161022357610368601f8301601f1916602001610314565b928284526020838301011161031057815f9260208093018386015e830101529056fe6080806040526004361015610012575f80fd5b5f3560e01c90816306fdde03146106ab57508063095ea7b31461061c57806318160ddd146105ff57806323b872dd14610489578063313ce5671461046e57806340c10f19146102ec57806370a08231146102a857806395d89b411461012d578063a9059cbb146100fc5763dd62ed3e1461008a575f80fd5b346100f85760406003193601126100f8576100a36107ac565b73ffffffffffffffffffffffffffffffffffffffff6100c06107cf565b91165f52600160205273ffffffffffffffffffffffffffffffffffffffff60405f2091165f52602052602060405f2054604051908152f35b5f80fd5b346100f85760406003193601126100f8576101226101186107ac565b60243590336107f2565b602060405160018152f35b346100f8575f6003193601126100f8576040515f600454908160011c6001831692831561029e575b60208210841461027157818552849390811561022f57506001146101d3575b5003601f01601f191681019067ffffffffffffffff8211818310176101a6576101a282918260405282610782565b0390f35b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b60045f90815291507f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b5b8183106102135750508101602001601f19610174565b60209193508060019154838588010152019101909183926101fd565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660208581019190915291151560051b84019091019150601f199050610174565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b90607f1690610155565b346100f85760206003193601126100f85773ffffffffffffffffffffffffffffffffffffffff6102d66107ac565b165f525f602052602060405f2054604051908152f35b346100f85760406003193601126100f8576103056107ac565b6024359073ffffffffffffffffffffffffffffffffffffffff82116103ea5773ffffffffffffffffffffffffffffffffffffffff169081156103be57600254908082018092116103915760207fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef915f9360025584845283825260408420818154019055604051908152a3005b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b7fec442f05000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f4572726f723a206d6178206d696e7420616d6f756e742069732075696e74313660448201527f30206d61780000000000000000000000000000000000000000000000000000006064820152fd5b346100f8575f6003193601126100f857602060405160128152f35b346100f85760606003193601126100f8576104a26107ac565b6104aa6107cf565b6044359073ffffffffffffffffffffffffffffffffffffffff831692835f52600160205260405f2073ffffffffffffffffffffffffffffffffffffffff33165f5260205260405f20547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8110610526575b5061012293506107f2565b8381106105cb57841561059f57331561057357610122945f52600160205260405f2073ffffffffffffffffffffffffffffffffffffffff33165f526020528360405f20910390558461051b565b7f94280d62000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b7fe602df05000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b83907ffb8f41b2000000000000000000000000000000000000000000000000000000005f523360045260245260445260645ffd5b346100f8575f6003193601126100f8576020600254604051908152f35b346100f85760406003193601126100f8576106356107ac565b60243590331561059f5773ffffffffffffffffffffffffffffffffffffffff1690811561057357335f52600160205260405f20825f526020528060405f20556040519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560203392a3602060405160018152f35b346100f8575f6003193601126100f8575f600354908160011c60018316928315610778575b60208210841461027157818552849390811561022f575060011461071c575003601f01601f191681019067ffffffffffffffff8211818310176101a6576101a282918260405282610782565b60035f90815291507fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5b81831061075c5750508101602001601f19610174565b6020919350806001915483858801015201910190918392610746565b90607f16906106d0565b601f19601f602060409481855280519182918282880152018686015e5f8582860101520116010190565b6004359073ffffffffffffffffffffffffffffffffffffffff821682036100f857565b6024359073ffffffffffffffffffffffffffffffffffffffff821682036100f857565b73ffffffffffffffffffffffffffffffffffffffff169081156108bc5773ffffffffffffffffffffffffffffffffffffffff169182156103be57815f525f60205260405f205481811061088a57817fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92602092855f525f84520360405f2055845f525f825260405f20818154019055604051908152a3565b827fe450d38c000000000000000000000000000000000000000000000000000000005f5260045260245260445260645ffd5b7f96c6fd1e000000000000000000000000000000000000000000000000000000005f525f60045260245ffdfea164736f6c634300081a000a", + "nonce": "0x1f", + "chainId": "0xde9fb" + }, + "additionalContracts": [ + { + "transactionType": "CREATE", + "address": "0xcff7c6da719408113dfcb5e36182c6d5aa491443", + "initCode": "0x60806040523461031057610c808038038061001981610314565b9283398101906040818303126103105780516001600160401b0381116103105782610045918301610339565b60208201519092906001600160401b038111610310576100659201610339565b81516001600160401b03811161022357600354600181811c91168015610306575b602082101461020557601f81116102a3575b50602092601f821160011461024257928192935f92610237575b50508160011b915f199060031b1c1916176003555b80516001600160401b03811161022357600454600181811c91168015610219575b602082101461020557601f81116101a2575b50602091601f8211600114610142579181925f92610137575b50508160011b915f199060031b1c1916176004555b6040516108f5908161038b8239f35b015190505f80610113565b601f1982169260045f52805f20915f5b85811061018a57508360019510610172575b505050811b01600455610128565b01515f1960f88460031b161c191690555f8080610164565b91926020600181928685015181550194019201610152565b60045f527f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b601f830160051c810191602084106101fb575b601f0160051c01905b8181106101f057506100fa565b5f81556001016101e3565b90915081906101da565b634e487b7160e01b5f52602260045260245ffd5b90607f16906100e8565b634e487b7160e01b5f52604160045260245ffd5b015190505f806100b2565b601f1982169360035f52805f20915f5b86811061028b5750836001959610610273575b505050811b016003556100c7565b01515f1960f88460031b161c191690555f8080610265565b91926020600181928685015181550194019201610252565b60035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b601f830160051c810191602084106102fc575b601f0160051c01905b8181106102f15750610098565b5f81556001016102e4565b90915081906102db565b90607f1690610086565b5f80fd5b6040519190601f01601f191682016001600160401b0381118382101761022357604052565b81601f82011215610310578051906001600160401b03821161022357610368601f8301601f1916602001610314565b928284526020838301011161031057815f9260208093018386015e830101529056fe6080806040526004361015610012575f80fd5b5f3560e01c90816306fdde03146106ab57508063095ea7b31461061c57806318160ddd146105ff57806323b872dd14610489578063313ce5671461046e57806340c10f19146102ec57806370a08231146102a857806395d89b411461012d578063a9059cbb146100fc5763dd62ed3e1461008a575f80fd5b346100f85760406003193601126100f8576100a36107ac565b73ffffffffffffffffffffffffffffffffffffffff6100c06107cf565b91165f52600160205273ffffffffffffffffffffffffffffffffffffffff60405f2091165f52602052602060405f2054604051908152f35b5f80fd5b346100f85760406003193601126100f8576101226101186107ac565b60243590336107f2565b602060405160018152f35b346100f8575f6003193601126100f8576040515f600454908160011c6001831692831561029e575b60208210841461027157818552849390811561022f57506001146101d3575b5003601f01601f191681019067ffffffffffffffff8211818310176101a6576101a282918260405282610782565b0390f35b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b60045f90815291507f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b5b8183106102135750508101602001601f19610174565b60209193508060019154838588010152019101909183926101fd565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660208581019190915291151560051b84019091019150601f199050610174565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b90607f1690610155565b346100f85760206003193601126100f85773ffffffffffffffffffffffffffffffffffffffff6102d66107ac565b165f525f602052602060405f2054604051908152f35b346100f85760406003193601126100f8576103056107ac565b6024359073ffffffffffffffffffffffffffffffffffffffff82116103ea5773ffffffffffffffffffffffffffffffffffffffff169081156103be57600254908082018092116103915760207fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef915f9360025584845283825260408420818154019055604051908152a3005b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b7fec442f05000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f4572726f723a206d6178206d696e7420616d6f756e742069732075696e74313660448201527f30206d61780000000000000000000000000000000000000000000000000000006064820152fd5b346100f8575f6003193601126100f857602060405160128152f35b346100f85760606003193601126100f8576104a26107ac565b6104aa6107cf565b6044359073ffffffffffffffffffffffffffffffffffffffff831692835f52600160205260405f2073ffffffffffffffffffffffffffffffffffffffff33165f5260205260405f20547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8110610526575b5061012293506107f2565b8381106105cb57841561059f57331561057357610122945f52600160205260405f2073ffffffffffffffffffffffffffffffffffffffff33165f526020528360405f20910390558461051b565b7f94280d62000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b7fe602df05000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b83907ffb8f41b2000000000000000000000000000000000000000000000000000000005f523360045260245260445260645ffd5b346100f8575f6003193601126100f8576020600254604051908152f35b346100f85760406003193601126100f8576106356107ac565b60243590331561059f5773ffffffffffffffffffffffffffffffffffffffff1690811561057357335f52600160205260405f20825f526020528060405f20556040519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560203392a3602060405160018152f35b346100f8575f6003193601126100f8575f600354908160011c60018316928315610778575b60208210841461027157818552849390811561022f575060011461071c575003601f01601f191681019067ffffffffffffffff8211818310176101a6576101a282918260405282610782565b60035f90815291507fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5b81831061075c5750508101602001601f19610174565b6020919350806001915483858801015201910190918392610746565b90607f16906106d0565b601f19601f602060409481855280519182918282880152018686015e5f8582860101520116010190565b6004359073ffffffffffffffffffffffffffffffffffffffff821682036100f857565b6024359073ffffffffffffffffffffffffffffffffffffffff821682036100f857565b73ffffffffffffffffffffffffffffffffffffffff169081156108bc5773ffffffffffffffffffffffffffffffffffffffff169182156103be57815f525f60205260405f205481811061088a57817fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92602092855f525f84520360405f2055845f525f825260405f20818154019055604051908152a3565b827fe450d38c000000000000000000000000000000000000000000000000000000005f5260045260245260445260645ffd5b7f96c6fd1e000000000000000000000000000000000000000000000000000000005f525f60045260245ffdfea164736f6c634300081a000a00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000844656d6f5553444300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045553444300000000000000000000000000000000000000000000000000000000" + }, + { + "transactionType": "CREATE", + "address": "0x0766798566d1f6e2f0b126f7783aab2cbb81c66f", + "initCode": "0x60806040523461031057610c808038038061001981610314565b9283398101906040818303126103105780516001600160401b0381116103105782610045918301610339565b60208201519092906001600160401b038111610310576100659201610339565b81516001600160401b03811161022357600354600181811c91168015610306575b602082101461020557601f81116102a3575b50602092601f821160011461024257928192935f92610237575b50508160011b915f199060031b1c1916176003555b80516001600160401b03811161022357600454600181811c91168015610219575b602082101461020557601f81116101a2575b50602091601f8211600114610142579181925f92610137575b50508160011b915f199060031b1c1916176004555b6040516108f5908161038b8239f35b015190505f80610113565b601f1982169260045f52805f20915f5b85811061018a57508360019510610172575b505050811b01600455610128565b01515f1960f88460031b161c191690555f8080610164565b91926020600181928685015181550194019201610152565b60045f527f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b601f830160051c810191602084106101fb575b601f0160051c01905b8181106101f057506100fa565b5f81556001016101e3565b90915081906101da565b634e487b7160e01b5f52602260045260245ffd5b90607f16906100e8565b634e487b7160e01b5f52604160045260245ffd5b015190505f806100b2565b601f1982169360035f52805f20915f5b86811061028b5750836001959610610273575b505050811b016003556100c7565b01515f1960f88460031b161c191690555f8080610265565b91926020600181928685015181550194019201610252565b60035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b601f830160051c810191602084106102fc575b601f0160051c01905b8181106102f15750610098565b5f81556001016102e4565b90915081906102db565b90607f1690610086565b5f80fd5b6040519190601f01601f191682016001600160401b0381118382101761022357604052565b81601f82011215610310578051906001600160401b03821161022357610368601f8301601f1916602001610314565b928284526020838301011161031057815f9260208093018386015e830101529056fe6080806040526004361015610012575f80fd5b5f3560e01c90816306fdde03146106ab57508063095ea7b31461061c57806318160ddd146105ff57806323b872dd14610489578063313ce5671461046e57806340c10f19146102ec57806370a08231146102a857806395d89b411461012d578063a9059cbb146100fc5763dd62ed3e1461008a575f80fd5b346100f85760406003193601126100f8576100a36107ac565b73ffffffffffffffffffffffffffffffffffffffff6100c06107cf565b91165f52600160205273ffffffffffffffffffffffffffffffffffffffff60405f2091165f52602052602060405f2054604051908152f35b5f80fd5b346100f85760406003193601126100f8576101226101186107ac565b60243590336107f2565b602060405160018152f35b346100f8575f6003193601126100f8576040515f600454908160011c6001831692831561029e575b60208210841461027157818552849390811561022f57506001146101d3575b5003601f01601f191681019067ffffffffffffffff8211818310176101a6576101a282918260405282610782565b0390f35b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b60045f90815291507f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b5b8183106102135750508101602001601f19610174565b60209193508060019154838588010152019101909183926101fd565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660208581019190915291151560051b84019091019150601f199050610174565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b90607f1690610155565b346100f85760206003193601126100f85773ffffffffffffffffffffffffffffffffffffffff6102d66107ac565b165f525f602052602060405f2054604051908152f35b346100f85760406003193601126100f8576103056107ac565b6024359073ffffffffffffffffffffffffffffffffffffffff82116103ea5773ffffffffffffffffffffffffffffffffffffffff169081156103be57600254908082018092116103915760207fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef915f9360025584845283825260408420818154019055604051908152a3005b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b7fec442f05000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f4572726f723a206d6178206d696e7420616d6f756e742069732075696e74313660448201527f30206d61780000000000000000000000000000000000000000000000000000006064820152fd5b346100f8575f6003193601126100f857602060405160128152f35b346100f85760606003193601126100f8576104a26107ac565b6104aa6107cf565b6044359073ffffffffffffffffffffffffffffffffffffffff831692835f52600160205260405f2073ffffffffffffffffffffffffffffffffffffffff33165f5260205260405f20547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8110610526575b5061012293506107f2565b8381106105cb57841561059f57331561057357610122945f52600160205260405f2073ffffffffffffffffffffffffffffffffffffffff33165f526020528360405f20910390558461051b565b7f94280d62000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b7fe602df05000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b83907ffb8f41b2000000000000000000000000000000000000000000000000000000005f523360045260245260445260645ffd5b346100f8575f6003193601126100f8576020600254604051908152f35b346100f85760406003193601126100f8576106356107ac565b60243590331561059f5773ffffffffffffffffffffffffffffffffffffffff1690811561057357335f52600160205260405f20825f526020528060405f20556040519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560203392a3602060405160018152f35b346100f8575f6003193601126100f8575f600354908160011c60018316928315610778575b60208210841461027157818552849390811561022f575060011461071c575003601f01601f191681019067ffffffffffffffff8211818310176101a6576101a282918260405282610782565b60035f90815291507fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5b81831061075c5750508101602001601f19610174565b6020919350806001915483858801015201910190918392610746565b90607f16906106d0565b601f19601f602060409481855280519182918282880152018686015e5f8582860101520116010190565b6004359073ffffffffffffffffffffffffffffffffffffffff821682036100f857565b6024359073ffffffffffffffffffffffffffffffffffffffff821682036100f857565b73ffffffffffffffffffffffffffffffffffffffff169081156108bc5773ffffffffffffffffffffffffffffffffffffffff169182156103be57815f525f60205260405f205481811061088a57817fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92602092855f525f84520360405f2055845f525f825260405f20818154019055604051908152a3565b827fe450d38c000000000000000000000000000000000000000000000000000000005f5260045260245260445260645ffd5b7f96c6fd1e000000000000000000000000000000000000000000000000000000005f525f60045260245ffdfea164736f6c634300081a000a00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000844656d6f5745544800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045745544800000000000000000000000000000000000000000000000000000000" + } + ], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0x1a1af8", + "logs": [ + { + "address": "0xcff7c6da719408113dfcb5e36182c6d5aa491443", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000b0aec4c25e8332256a91bbaf169e3c32dfc3c33c" + ], + "data": "0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff", + "blockHash": "0xc33eaf631249438f553459832fb6d174cc5eaca0fceff7d63dc67019651dae57", + "blockNumber": "0x793495", + "blockTimestamp": "0x678174e5", + "transactionHash": "0xff3fd4a479bc16ce127103e5b292c041e10b7fbb9791b5a1420016d04c89ec23", + "transactionIndex": "0x1", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x0766798566d1f6e2f0b126f7783aab2cbb81c66f", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000b0aec4c25e8332256a91bbaf169e3c32dfc3c33c" + ], + "data": "0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff", + "blockHash": "0xc33eaf631249438f553459832fb6d174cc5eaca0fceff7d63dc67019651dae57", + "blockNumber": "0x793495", + "blockTimestamp": "0x678174e5", + "transactionHash": "0xff3fd4a479bc16ce127103e5b292c041e10b7fbb9791b5a1420016d04c89ec23", + "transactionIndex": "0x1", + "logIndex": "0x1", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000020000000000000000000200000000000000000000000000000000000000000000000001000000000000000000000000000000000000008000000000000000000008000000000000000000000000000020001000000000000000800000000000000000000000010000000000000001000000000000000000000000000400000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0xff3fd4a479bc16ce127103e5b292c041e10b7fbb9791b5a1420016d04c89ec23", + "transactionIndex": "0x1", + "blockHash": "0xc33eaf631249438f553459832fb6d174cc5eaca0fceff7d63dc67019651dae57", + "blockNumber": "0x793495", + "gasUsed": "0x196faa", + "effectiveGasPrice": "0xfd", + "from": "0xddf32240b4ca3184de7ec8f0d5aba27dec8b7a5c", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": "0xb0aec4c25e8332256a91bbaf169e3c32dfc3c33c", + "l1BaseFeeScalar": "0xa6fe0", + "l1BlobBaseFee": "0x63f363e9", + "l1BlobBaseFeeScalar": "0x0", + "l1Fee": "0x33cde391b2e5", + "l1GasPrice": "0x7bbcf3e6", + "l1GasUsed": "0x9cb1" + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1736537366, + "chain": 911867, + "commit": "ae9c0028" +} \ No newline at end of file diff --git a/examples/ui-demo/contracts/config/slither.config.json b/examples/ui-demo/contracts/config/slither.config.json new file mode 100644 index 0000000000..06744d9093 --- /dev/null +++ b/examples/ui-demo/contracts/config/slither.config.json @@ -0,0 +1,3 @@ +{ + "filter_paths": "(lib/|test/)" +} \ No newline at end of file diff --git a/examples/ui-demo/contracts/config/solhint-script.json b/examples/ui-demo/contracts/config/solhint-script.json new file mode 100644 index 0000000000..fecaf705b3 --- /dev/null +++ b/examples/ui-demo/contracts/config/solhint-script.json @@ -0,0 +1,21 @@ +{ + "extends": "solhint:recommended", + "rules": { + "func-name-mixedcase": "off", + "immutable-vars-naming": ["error"], + "no-unused-import": ["error"], + "compiler-version": ["error", ">=0.8.19"], + "custom-errors": "off", + "no-console": "off", + "func-visibility": ["error", { "ignoreConstructors": true }], + "max-line-length": ["error", 120], + "max-states-count": ["warn", 30], + "modifier-name-mixedcase": ["error"], + "private-vars-leading-underscore": ["error"], + "no-inline-assembly": "warn", + "avoid-low-level-calls": "off", + "one-contract-per-file": "off", + "no-empty-blocks": "off", + "reason-string": "off" + } + } \ No newline at end of file diff --git a/examples/ui-demo/contracts/config/solhint-src.json b/examples/ui-demo/contracts/config/solhint-src.json new file mode 100644 index 0000000000..b1ba809590 --- /dev/null +++ b/examples/ui-demo/contracts/config/solhint-src.json @@ -0,0 +1,17 @@ +{ + "extends": "solhint:recommended", + "rules": { + "immutable-vars-naming": ["error"], + "no-unused-import": ["error"], + "compiler-version": ["error", ">=0.8.19"], + "func-visibility": ["error", { "ignoreConstructors": true }], + "max-line-length": ["error", 120], + "func-param-name-mixedcase": ["error"], + "modifier-name-mixedcase": ["error"], + "private-vars-leading-underscore": ["error"], + "ordering": ["warn"], + "no-inline-assembly": "off", + "avoid-low-level-calls": "off", + "no-complex-fallback": "off" + } + } \ No newline at end of file diff --git a/examples/ui-demo/contracts/config/solhint-test.json b/examples/ui-demo/contracts/config/solhint-test.json new file mode 100644 index 0000000000..20ee72c88d --- /dev/null +++ b/examples/ui-demo/contracts/config/solhint-test.json @@ -0,0 +1,20 @@ +{ + "extends": "solhint:recommended", + "rules": { + "func-name-mixedcase": "off", + "immutable-vars-naming": ["error"], + "no-unused-import": ["error"], + "compiler-version": ["error", ">=0.8.19"], + "custom-errors": "off", + "func-visibility": ["error", { "ignoreConstructors": true }], + "max-line-length": ["error", 120], + "max-states-count": ["warn", 30], + "modifier-name-mixedcase": ["error"], + "private-vars-leading-underscore": ["error"], + "no-inline-assembly": "off", + "avoid-low-level-calls": "off", + "one-contract-per-file": "off", + "no-empty-blocks": "off", + "reason-string": ["warn", { "maxLength": 64 }] + } +} \ No newline at end of file diff --git a/examples/ui-demo/contracts/foundry.toml b/examples/ui-demo/contracts/foundry.toml new file mode 100644 index 0000000000..de2246cc5d --- /dev/null +++ b/examples/ui-demo/contracts/foundry.toml @@ -0,0 +1,58 @@ +[profile.default] +solc = '0.8.26' +via_ir = false +src = 'src' +test = 'test' +libs = ['lib'] +out = 'out' +optimizer = true +optimizer_runs = 200 +auto_detect_solc = false +bytecode_hash = "none" +auto_detect_remappings = false +fs_permissions = [ + { access = "read", path = "./out-optimized" } +] + +[fuzz] +runs = 500 + +[invariant] +runs=500 +fail_on_revert = true +depth = 10 + +[profile.optimized-build] +via_ir = true +test = 'src' +optimizer_runs = 10000 +out = 'out-optimized' + +[profile.optimized-test] +src = 'test' + +[profile.optimized-test-deep] +src = 'test' + +[profile.optimized-test-deep.fuzz] +runs = 10000 + +[profile.optimized-test-deep.invariant] +runs = 5000 +depth = 32 + +[profile.deep.fuzz] +runs = 100000 + +[profile.deep.invariant] +runs = 5000 +depth = 32 + +[fmt] +line_length = 115 +wrap_comments = true +sort_imports = true +number_underscore = "thousands" +int_types = "long" + +# See more config options https://github.com/foundry-rs/foundry/tree/master/config \ No newline at end of file diff --git a/examples/ui-demo/contracts/lib/forge-std b/examples/ui-demo/contracts/lib/forge-std new file mode 160000 index 0000000000..b93cf4bc34 --- /dev/null +++ b/examples/ui-demo/contracts/lib/forge-std @@ -0,0 +1 @@ +Subproject commit b93cf4bc34ff214c099dc970b153f85ade8c9f66 diff --git a/examples/ui-demo/contracts/lib/openzeppelin-contracts b/examples/ui-demo/contracts/lib/openzeppelin-contracts new file mode 160000 index 0000000000..acd4ff74de --- /dev/null +++ b/examples/ui-demo/contracts/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit acd4ff74de833399287ed6b31b4debf6b2b35527 diff --git a/examples/ui-demo/contracts/package.json b/examples/ui-demo/contracts/package.json new file mode 100644 index 0000000000..eff455a523 --- /dev/null +++ b/examples/ui-demo/contracts/package.json @@ -0,0 +1,12 @@ +{ + "devDependencies": { + "pnpm": "^8.7.5", + "solhint": "^3.6.2" + }, + "scripts": { + "lint": "pnpm lint:src && pnpm lint:test && pnpm lint:script", + "lint:src": "solhint --max-warnings 0 -c .solhint-src.json './src/**/*.sol'", + "lint:test": "solhint --max-warnings 0 -c .solhint-test.json './test/**/*.sol'", + "lint:script": "solhint --max-warnings 0 -c .solhint-script.json './script/**/*.sol'" + } + } \ No newline at end of file diff --git a/examples/ui-demo/contracts/pnpm-lock.yaml b/examples/ui-demo/contracts/pnpm-lock.yaml new file mode 100644 index 0000000000..c011cb5a17 --- /dev/null +++ b/examples/ui-demo/contracts/pnpm-lock.yaml @@ -0,0 +1,489 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + pnpm: + specifier: ^8.7.5 + version: 8.15.9 + solhint: + specifier: ^3.6.2 + version: 3.6.2 + +packages: + + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + + '@solidity-parser/parser@0.16.2': + resolution: {integrity: sha512-PI9NfoA3P8XK2VBkK5oIfRgKDsicwDZfkVq9ZTBCQYGOP1N2owgY2dyLGyU5/J/hQs8KRk55kdmvTLjy3Mu3vg==} + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + antlr4@4.13.2: + resolution: {integrity: sha512-QiVbZhyy4xAZ17UPEuG3YTOt8ZaoeOR1CvEAqrEsDBsOqINslaB147i9xqljZqoyf5S+EUlGStaj+t22LT9MOg==} + engines: {node: '>=16'} + + antlr4ts@0.5.0-alpha.4: + resolution: {integrity: sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + ast-parents@0.0.1: + resolution: {integrity: sha512-XHusKxKz3zoYk1ic8Un640joHbFMhbqneyoZfoKnEGtf2ey9Uh/IdpcQplODdO/kENaMIWsD0nJm4+wX3UNLHA==} + + astral-regex@2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + + cosmiconfig@8.3.6: + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-uri@3.0.5: + resolution: {integrity: sha512-5JnBCWpFlMo0a3ciDy/JckMzzv1U9coZrIhedq+HXxxUfDTAiS0LA8OKVao4G9BxmCVck/jtA5r3KAtRWEyD8Q==} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + lodash.truncate@4.4.2: + resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + + pnpm@8.15.9: + resolution: {integrity: sha512-SZQ0ydj90aJ5Tr9FUrOyXApjOrzuW7Fee13pDzL0e1E6ypjNXP0AHDHw20VLw4BO3M1XhQHkyik6aBYWa72fgQ==} + engines: {node: '>=16.14'} + hasBin: true + + prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + + slice-ansi@4.0.0: + resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} + engines: {node: '>=10'} + + solhint@3.6.2: + resolution: {integrity: sha512-85EeLbmkcPwD+3JR7aEMKsVC9YrRSxd4qkXuMzrlf7+z2Eqdfm1wHWq1ffTuo5aDhoZxp2I9yF3QkxZOxOL7aQ==} + hasBin: true + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + table@6.9.0: + resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} + engines: {node: '>=10.0.0'} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + +snapshots: + + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-validator-identifier@7.25.9': {} + + '@solidity-parser/parser@0.16.2': + dependencies: + antlr4ts: 0.5.0-alpha.4 + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.0.5 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + antlr4@4.13.2: {} + + antlr4ts@0.5.0-alpha.4: {} + + argparse@2.0.1: {} + + ast-parents@0.0.1: {} + + astral-regex@2.0.0: {} + + balanced-match@1.0.2: {} + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + callsites@3.1.0: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@10.0.1: {} + + cosmiconfig@8.3.6: + dependencies: + import-fresh: 3.3.0 + js-yaml: 4.1.0 + parse-json: 5.2.0 + path-type: 4.0.0 + + emoji-regex@8.0.0: {} + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-uri@3.0.5: {} + + fs.realpath@1.0.0: {} + + glob@8.1.0: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + + has-flag@4.0.0: {} + + ignore@5.3.2: {} + + import-fresh@3.3.0: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + is-arrayish@0.2.1: {} + + is-fullwidth-code-point@3.0.0: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + lines-and-columns@1.2.4: {} + + lodash.truncate@4.4.2: {} + + lodash@4.17.21: {} + + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.26.2 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + path-type@4.0.0: {} + + picocolors@1.1.1: {} + + pluralize@8.0.0: {} + + pnpm@8.15.9: {} + + prettier@2.8.8: + optional: true + + punycode@2.3.1: {} + + require-from-string@2.0.2: {} + + resolve-from@4.0.0: {} + + semver@7.6.3: {} + + slice-ansi@4.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + + solhint@3.6.2: + dependencies: + '@solidity-parser/parser': 0.16.2 + ajv: 6.12.6 + antlr4: 4.13.2 + ast-parents: 0.0.1 + chalk: 4.1.2 + commander: 10.0.1 + cosmiconfig: 8.3.6 + fast-diff: 1.3.0 + glob: 8.1.0 + ignore: 5.3.2 + js-yaml: 4.1.0 + lodash: 4.17.21 + pluralize: 8.0.0 + semver: 7.6.3 + strip-ansi: 6.0.1 + table: 6.9.0 + text-table: 0.2.0 + optionalDependencies: + prettier: 2.8.8 + transitivePeerDependencies: + - typescript + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + table@6.9.0: + dependencies: + ajv: 8.17.1 + lodash.truncate: 4.4.2 + slice-ansi: 4.0.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + text-table@0.2.0: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + wrappy@1.0.2: {} diff --git a/examples/ui-demo/contracts/remappings.txt b/examples/ui-demo/contracts/remappings.txt new file mode 100644 index 0000000000..eaaeb4f11d --- /dev/null +++ b/examples/ui-demo/contracts/remappings.txt @@ -0,0 +1,3 @@ +ds-test/=lib/forge-std/lib/ds-test/src/ +forge-std/=lib/forge-std/src/ +@openzeppelin/contracts=lib/openzeppelin-contracts/contracts/ \ No newline at end of file diff --git a/examples/ui-demo/contracts/script/Counter.s.sol b/examples/ui-demo/contracts/script/Counter.s.sol new file mode 100644 index 0000000000..cdc1fe9a1b --- /dev/null +++ b/examples/ui-demo/contracts/script/Counter.s.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console} from "forge-std/Script.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterScript is Script { + Counter public counter; + + function setUp() public {} + + function run() public { + vm.startBroadcast(); + + counter = new Counter(); + + vm.stopBroadcast(); + } +} diff --git a/examples/ui-demo/contracts/script/DeployNFT.s.sol b/examples/ui-demo/contracts/script/DeployNFT.s.sol new file mode 100644 index 0000000000..c1a1411ef8 --- /dev/null +++ b/examples/ui-demo/contracts/script/DeployNFT.s.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.26; + +import {Script, console} from "forge-std/Script.sol"; + +import {NFT} from "../src/NFT.sol"; + +contract DeployNFTScript is Script { + + function run() public { + + string memory name = "Account Kit Demo NFT"; + string memory symbol = "AKD"; + string memory baseURI = "https://static.alchemyapi.io/assets/accountkit/accountkit.jpg"; + + vm.startBroadcast(); + + NFT nft = new NFT{ salt: 0 }(name, symbol, baseURI); + + console.log("NFT deployed at address: ", address(nft)); + + vm.stopBroadcast(); + } +} \ No newline at end of file diff --git a/examples/ui-demo/contracts/script/DeploySwapVenue.s.sol b/examples/ui-demo/contracts/script/DeploySwapVenue.s.sol new file mode 100644 index 0000000000..a821934f75 --- /dev/null +++ b/examples/ui-demo/contracts/script/DeploySwapVenue.s.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console} from "forge-std/Script.sol"; +import {Swap} from "../src/Swap.sol"; + +contract DeploySwapVenueScript is Script { + + function run() public { + vm.startBroadcast(); + + Swap swap = new Swap{ salt: 0 }(); + + console.log("Swap deployed at address: ", address(swap)); + + vm.stopBroadcast(); + } +} diff --git a/examples/ui-demo/contracts/src/Counter.sol b/examples/ui-demo/contracts/src/Counter.sol new file mode 100644 index 0000000000..aded7997b0 --- /dev/null +++ b/examples/ui-demo/contracts/src/Counter.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} diff --git a/examples/ui-demo/contracts/src/ERC20Mintable.sol b/examples/ui-demo/contracts/src/ERC20Mintable.sol new file mode 100644 index 0000000000..92904e2503 --- /dev/null +++ b/examples/ui-demo/contracts/src/ERC20Mintable.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + +contract ERC20Mintable is ERC20 { + constructor(string memory name, string memory symbol) + ERC20(name, symbol) + {} + + function mint(address to, uint256 amount) public { + require(amount <= type(uint160).max, "Error: max mint amount is uint160 max"); + _mint(to, amount); + } +} \ No newline at end of file diff --git a/examples/ui-demo/contracts/src/NFT.sol b/examples/ui-demo/contracts/src/NFT.sol new file mode 100644 index 0000000000..8e8aed488f --- /dev/null +++ b/examples/ui-demo/contracts/src/NFT.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +error NonExistentTokenURI(); + +contract NFT is ERC721 { + + using Strings for uint256; + string public baseURI; + uint256 public currentTokenId; + + constructor( + string memory _name, + string memory _symbol, + string memory _baseURI + ) ERC721(_name, _symbol) { + baseURI = _baseURI; + } + + function mintTo(address recipient) public payable returns (uint256) { + uint256 newTokenId = ++currentTokenId; + _safeMint(recipient, newTokenId); + return newTokenId; + } + + function tokenURI(uint256 tokenId) + public + view + virtual + override + returns (string memory) + { + if (ownerOf(tokenId) == address(0)) { + revert NonExistentTokenURI(); + } + return baseURI; + } +} \ No newline at end of file diff --git a/examples/ui-demo/contracts/src/Swap.sol b/examples/ui-demo/contracts/src/Swap.sol new file mode 100644 index 0000000000..b7579e5aff --- /dev/null +++ b/examples/ui-demo/contracts/src/Swap.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import {ERC20Mintable} from "./ERC20Mintable.sol"; + +contract Swap { + ERC20Mintable public usdc; + ERC20Mintable public weth; + + constructor() { + usdc = new ERC20Mintable("DemoUSDC", "USDC"); + usdc.mint(address(this), type(uint160).max); + + weth = new ERC20Mintable("DemoWETH", "WETH"); + weth.mint(address(this), type(uint160).max); + } + + function mint(uint amount1, uint amount2) external { + usdc.mint(msg.sender, amount1); + weth.mint(msg.sender, amount2); + } + + function swapUSDCtoWETH(uint amountIn, uint amountOut) external { + require(amountOut <= 10 ether, "Max 10 eth swap at a time"); + usdc.transferFrom(msg.sender, address(this), amountIn); + weth.transfer(msg.sender, amountOut); + } + + function swapWETHtoUSDC(uint amountIn, uint amountOut) external { + require(amountOut <= 100000 ether, "Max 100k USDC swap at a time"); + weth.transferFrom(msg.sender, address(this), amountIn); + usdc.transfer(msg.sender, amountOut); + } +} \ No newline at end of file diff --git a/examples/ui-demo/contracts/test/Counter.t.sol b/examples/ui-demo/contracts/test/Counter.t.sol new file mode 100644 index 0000000000..54b724f7ae --- /dev/null +++ b/examples/ui-demo/contracts/test/Counter.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console} from "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + function testFuzz_SetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } +} diff --git a/examples/ui-demo/env.mjs b/examples/ui-demo/env.mjs index c2014c028e..1ece531008 100644 --- a/examples/ui-demo/env.mjs +++ b/examples/ui-demo/env.mjs @@ -11,6 +11,7 @@ export const env = createEnv({ API_KEY: z.string(), ALCHEMY_API_URL: z.string().url(), ALCHEMY_RPC_URL: z.string().url(), + ALCHEMY_RPC_URL_ODYSSEY: z.string().url(), }, /** @@ -29,5 +30,6 @@ export const env = createEnv({ API_KEY: process.env.API_KEY, ALCHEMY_API_URL: process.env.ALCHEMY_API_URL, ALCHEMY_RPC_URL: process.env.ALCHEMY_RPC_URL, + ALCHEMY_RPC_URL_ODYSSEY: process.env.ALCHEMY_RPC_URL_ODYSSEY, }, }); diff --git a/examples/ui-demo/package.json b/examples/ui-demo/package.json index 4013bad594..336770a6cf 100644 --- a/examples/ui-demo/package.json +++ b/examples/ui-demo/package.json @@ -36,7 +36,7 @@ "react-dom": "^18", "react-syntax-highlighter": "^15.5.0", "tailwind-merge": "^2.3.0", - "viem": "^2.20.0", + "viem": "^2.22.6", "wagmi": "^2.12.7", "zod": "^3.0.0", "zustand": "^5.0.0-rc.2" diff --git a/examples/ui-demo/src/app/api/bundler-odyssey/route.ts b/examples/ui-demo/src/app/api/bundler-odyssey/route.ts new file mode 100644 index 0000000000..3e6bb584ff --- /dev/null +++ b/examples/ui-demo/src/app/api/bundler-odyssey/route.ts @@ -0,0 +1,30 @@ +import { NextRequest, NextResponse } from "next/server"; + +import { env } from "../../../../env.mjs"; + +export async function POST(req: NextRequest) { + const body = await req.text(); + const headers: Record = {}; + req.headers.forEach((value, key) => { + // don't pass the cookie because it doesn't get used downstream + if (key === "cookie") return; + + headers[key] = value; + }); + + const res = await fetch(env.ALCHEMY_RPC_URL_ODYSSEY, { + method: "POST", + headers: { + ...headers, + }, + body, + }); + + if (!res.ok) { + return NextResponse.json(await res.json().catch((e) => ({})), { + status: res.status, + }); + } + + return NextResponse.json(await res.json()); +} diff --git a/examples/ui-demo/src/app/config.tsx b/examples/ui-demo/src/app/config.tsx index 92af76366b..f44adf0019 100644 --- a/examples/ui-demo/src/app/config.tsx +++ b/examples/ui-demo/src/app/config.tsx @@ -39,9 +39,15 @@ export type Config = { } | undefined; }; + walletType: WalletTypes; supportUrl?: string; }; +export enum WalletTypes { + smart = "smart", + hybrid7702 = "7702", +} + export const DEFAULT_CONFIG: Config = { auth: { showEmail: true, @@ -70,6 +76,7 @@ export const DEFAULT_CONFIG: Config = { logoLight: undefined, logoDark: undefined, }, + walletType: WalletTypes.smart, }; export const queryClient = new QueryClient(); diff --git a/examples/ui-demo/src/components/configuration/Configuration.tsx b/examples/ui-demo/src/components/configuration/Configuration.tsx new file mode 100644 index 0000000000..1f6cee70c9 --- /dev/null +++ b/examples/ui-demo/src/components/configuration/Configuration.tsx @@ -0,0 +1,52 @@ +// import { useState } from "react"; +import { cn } from "@/lib/utils"; + +import { SettingsIcon } from "../icons/settings"; +// import { HelpTooltip } from "../shared/HelpTooltip"; +import { WalletTypeSwitch } from "../shared/WalletTypeSwitch"; +import ExternalLink from "../shared/ExternalLink"; +import { useConfigStore } from "@/state"; +import { WalletTypes } from "@/app/config"; + +export const Configuration = ({ className }: { className?: string }) => { + const { setWalletType, walletType } = useConfigStore(); + // const [walletType, setWalletType] = useState(WalletTypes.smart); + + const onSwitchWalletType = () => { + setWalletType( + walletType === WalletTypes.smart + ? WalletTypes.hybrid7702 + : WalletTypes.smart + ); + }; + + return ( +
+
+ + Configuration +
+
+

+ Embedded wallet type +

+ {/* */} +
+ +

+ EIP-7702 adds smart account features to an EOA wallet.{" "} + + Learn more. + +

+
+
+ ); +}; diff --git a/examples/ui-demo/src/components/configuration/ConfigurationSidebarWrapper.tsx b/examples/ui-demo/src/components/configuration/ConfigurationSidebarWrapper.tsx index 487f14074e..59f7e9d24e 100644 --- a/examples/ui-demo/src/components/configuration/ConfigurationSidebarWrapper.tsx +++ b/examples/ui-demo/src/components/configuration/ConfigurationSidebarWrapper.tsx @@ -1,10 +1,12 @@ import React from "react"; import { Authentication } from "./Authentication"; import { Styling } from "./Styling"; +import { Configuration } from "./Configuration"; export function ConfigurationSidebarWrapper() { return (
+
diff --git a/examples/ui-demo/src/components/icons/key.tsx b/examples/ui-demo/src/components/icons/key.tsx new file mode 100644 index 0000000000..77382983bb --- /dev/null +++ b/examples/ui-demo/src/components/icons/key.tsx @@ -0,0 +1,22 @@ +export const Key = ({ className }: { className?: string }) => ( + + + + +); diff --git a/examples/ui-demo/src/components/icons/loading.tsx b/examples/ui-demo/src/components/icons/loading.tsx index ef89746f77..eb5041e0d8 100644 --- a/examples/ui-demo/src/components/icons/loading.tsx +++ b/examples/ui-demo/src/components/icons/loading.tsx @@ -1,5 +1,5 @@ import { useTheme } from "@/state/useTheme"; -export const LoadingIcon = () => { +export const LoadingIcon = ({ className }: { className?: string }) => { const theme = useTheme(); const animationClass = theme === "dark" ? "animate-ui-loading-dark" : "animate-ui-loading-light"; @@ -11,6 +11,7 @@ export const LoadingIcon = () => { viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" + className={className} > ) => ( + + + +); diff --git a/examples/ui-demo/src/components/mint-card/MintCard.tsx b/examples/ui-demo/src/components/mint-card/MintCard.tsx deleted file mode 100644 index 5d181627e9..0000000000 --- a/examples/ui-demo/src/components/mint-card/MintCard.tsx +++ /dev/null @@ -1,148 +0,0 @@ -"use client"; - -import { useCallback, useState } from "react"; - -import { useToast } from "@/hooks/useToast"; -import { useConfigStore } from "@/state"; -import { AccountKitNftMinterABI, nftContractAddress } from "@/utils/config"; -import { - useSendUserOperation, - useSmartAccountClient, -} from "@account-kit/react"; -import { encodeFunctionData } from "viem"; -import { MintCardActionButtons } from "./MintCardActionButtons"; -import { NFT } from "./NFT"; -import { ValueProps } from "./ValueProps"; - -type NFTLoadingState = "loading" | "success"; - -const initialValuePropState = { - signing: "signing", - gas: "gas", - batch: "batch", -} satisfies MintStatus; - -export type MintStatus = { - signing: NFTLoadingState | "signing"; - gas: NFTLoadingState | "gas"; - batch: NFTLoadingState | "batch"; -}; - -export const MintCard = () => { - const [status, setStatus] = useState(initialValuePropState); - const { setToast } = useToast(); - const { nftTransferred: nftTransfered, setNFTTransferred: setNFTTransfered } = - useConfigStore((state) => ({ - nftTransferred: state.nftTransferred, - setNFTTransferred: state.setNftTransferred, - })); - - const handleSuccess = () => { - setStatus(() => ({ - batch: "success", - gas: "success", - signing: "success", - })); - setToast({ - type: "success", - text: ( - <> - - {`You've collected your NFT!`} - - - {`You've successfully collected your NFT! Refresh to mint - again.`} - - - ), - open: true, - }); - setNFTTransfered(true); - }; - const handleError = (error: Error) => { - console.error(error); - setStatus(initialValuePropState); - setToast({ - type: "error", - text: "There was a problem with that action", - open: true, - }); - }; - - const { client, isLoadingClient } = useSmartAccountClient({ - type: "LightAccount", - }); - const { sendUserOperationResult, sendUserOperation } = useSendUserOperation({ - client, - waitForTxn: true, - onError: handleError, - onSuccess: handleSuccess, - onMutate: () => { - setTimeout(() => { - setStatus((prev) => ({ ...prev, signing: "success" })); - }, 500); - setTimeout(() => { - setStatus((prev) => ({ ...prev, gas: "success" })); - }, 750); - }, - }); - - const handleCollectNFT = useCallback(async () => { - if (!client) { - console.error("no client"); - return; - } - setStatus({ - signing: "loading", - gas: "loading", - batch: "loading", - }); - sendUserOperation({ - uo: { - target: nftContractAddress, - data: encodeFunctionData({ - abi: AccountKitNftMinterABI, - functionName: "mintTo", - args: [client.getAddress()], - }), - }, - }); - }, [client, sendUserOperation]); - const transactionUrl = `${client?.chain?.blockExplorers?.default.url}/tx/${sendUserOperationResult?.hash}`; - - const isActionButtonsDisabled = - Object.values(status).some((x) => x === "loading") || isLoadingClient; - - return ( -
-
-
-
-

- {!nftTransfered - ? "One-click checkout" - : "You collected your NFT!"} -

- -
-
- - -
-
-
-
- ); -}; diff --git a/examples/ui-demo/src/components/mint-card/MintCardActionButtons.tsx b/examples/ui-demo/src/components/mint-card/MintCardActionButtons.tsx deleted file mode 100644 index ba6b0b3c7e..0000000000 --- a/examples/ui-demo/src/components/mint-card/MintCardActionButtons.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { cn } from "@/lib/utils"; -import { links } from "@/utils/links"; - -type MintCardActionButtonsProps = { - nftTransfered: boolean; - handleCollectNFT: () => void; - disabled?: boolean; -} & React.HTMLAttributes; - -export function MintCardActionButtons({ - nftTransfered, - handleCollectNFT, - className, - disabled, - ...props -}: MintCardActionButtonsProps) { - return ( -
- {!nftTransfered ? ( - - ) : ( - - )} - - View docs - -

- Visit desktop site to customize styles
and auth methods -

-
- ); -} diff --git a/examples/ui-demo/src/components/mint-card/NFT.tsx b/examples/ui-demo/src/components/mint-card/NFT.tsx deleted file mode 100644 index 25429cde39..0000000000 --- a/examples/ui-demo/src/components/mint-card/NFT.tsx +++ /dev/null @@ -1,147 +0,0 @@ -"use client"; - -import { ChevronDown } from "@/components/icons/chevron-down"; -import { LoadingIcon } from "@/components/icons/loading"; -import { cn } from "@/lib/utils"; -import { AccountKitNftMinterABI, nftContractAddress } from "@/utils/config"; -import { Dialog, useSmartAccountClient } from "@account-kit/react"; -import { useQuery } from "@tanstack/react-query"; -import Image from "next/image"; -import { useState } from "react"; -import { ValueProps } from "./ValueProps"; -import { MintStatus } from "./MintCard"; -import { ExternalLinkIcon } from "@/components/icons/external-link"; -import { useBreakpoint } from "@/hooks/useBreakpoint"; - -type NFTProps = { - nftTransfered: boolean; - transactionUrl?: string; - status: MintStatus; -} & React.HTMLAttributes; - -const NFT_MOBILE_SIZE = 288; - -const afterBoarder = - "after:absolute after:bottom-[-16px] after:left-0 after:w-full after:h-[1px] after:bg-border lg:after:hidden"; - -export function NFT({ - nftTransfered, - transactionUrl, - status, - ...props -}: NFTProps) { - const { client } = useSmartAccountClient({ type: "LightAccount" }); - const [mobileTrayOpen, setMobileTrayOpen] = useState(false); - const breakpoint = useBreakpoint(); - const { data: uri } = useQuery({ - queryKey: ["contractURI", nftContractAddress], - queryFn: async () => { - const uri = await client?.readContract({ - address: nftContractAddress, - abi: AccountKitNftMinterABI, - functionName: "baseURI", - }); - return uri; - }, - enabled: !!client && !!client?.readContract, - }); - return ( -
-

- NFT Summary -

-

- {nftTransfered ? "You collected your NFT!" : "One-click checkout"} -

- {uri ? ( -
- {nftTransfered && ( -
- Collected -
- )} - An NFT -
- ) : ( -
- -
- )} -
-

Gas Fee

-

- - $0.02 - - - Free - -

-
- {nftTransfered && transactionUrl && ( - - View transaction -
- -
-
- )} - - {mobileTrayOpen && ( -
- {" "} -
- )} - {breakpoint === "sm" && ( - setMobileTrayOpen(false)} - > -
-

- How is this working? -

- -
-
- )} -
- ); -} diff --git a/examples/ui-demo/src/components/mint-card/ValueProps.tsx b/examples/ui-demo/src/components/mint-card/ValueProps.tsx deleted file mode 100644 index eaccb3c681..0000000000 --- a/examples/ui-demo/src/components/mint-card/ValueProps.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { MintStatus } from "./MintCard"; -import { LoadingIcon } from "../icons/loading"; -import { CheckIcon } from "../icons/check"; -import { GasIcon } from "../icons/gas"; -import { DrawIcon } from "../icons/draw"; -import { ReceiptIcon } from "../icons/receipt"; - -type Props = { - status: MintStatus; -} & React.HTMLAttributes; - -export function ValueProps({ status, ...props }: Props) { - return ( -
- - - - Sponsor gas fees to remove barriers to adoption.{" "} - - Learn how. - - - } - /> - -
- ); -} -const ValueProp = ({ - icon, - title, - description, - className, -}: { - icon: "signing" | "gas" | "batch" | "loading" | "success"; - title: string; - description: string | JSX.Element; - className?: string; -}) => { - return ( -
-
{getMintIcon(icon)}
-
-

{title}

-

{description}

-
-
- ); -}; - -const getMintIcon = ( - icon: "signing" | "gas" | "batch" | "loading" | "success" -) => { - switch (icon) { - case "signing": - return ; - case "gas": - return ; - case "batch": - return ; - case "loading": - return ; - case "success": - return ; - } -}; diff --git a/examples/ui-demo/src/components/preview/MobileSplashPage.tsx b/examples/ui-demo/src/components/preview/MobileSplashPage.tsx index e705ddae2f..9bd67a2762 100644 --- a/examples/ui-demo/src/components/preview/MobileSplashPage.tsx +++ b/examples/ui-demo/src/components/preview/MobileSplashPage.tsx @@ -14,7 +14,7 @@ export function MobileSplashPage() { }, [breakpoint, isAuthenticating, openAuthModal]); return ( -
+
{/* Header Text */}

diff --git a/examples/ui-demo/src/components/preview/PreviewWrapper.tsx b/examples/ui-demo/src/components/preview/PreviewWrapper.tsx index 926e04894d..fd8b6497d3 100644 --- a/examples/ui-demo/src/components/preview/PreviewWrapper.tsx +++ b/examples/ui-demo/src/components/preview/PreviewWrapper.tsx @@ -4,8 +4,8 @@ import { AuthCard, useUser, useAuthContext } from "@account-kit/react"; import { MobileSplashPage } from "./MobileSplashPage"; import { EOAPostLogin } from "../eoa-post-login/EOAPostLogin"; -import { MintCard } from "../mint-card/MintCard"; import { useState, useEffect } from "react"; +import { SmallCardsWrapper } from "../small-cards/Wrapper"; export function PreviewWrapper({ showCode }: { showCode: boolean }) { return ( @@ -71,9 +71,5 @@ const RenderContent = () => { ); } - return ( -
- -
- ); + return ; }; diff --git a/examples/ui-demo/src/components/shared/WalletTypeSwitch.tsx b/examples/ui-demo/src/components/shared/WalletTypeSwitch.tsx new file mode 100644 index 0000000000..0079931359 --- /dev/null +++ b/examples/ui-demo/src/components/shared/WalletTypeSwitch.tsx @@ -0,0 +1,53 @@ +"use client"; + +import { Root, Thumb } from "@radix-ui/react-switch"; +import { forwardRef, ElementRef, ComponentPropsWithoutRef } from "react"; + +import { cn } from "@/lib/utils"; + +const selectedStyles = "text-[#475569]"; +const unselectedStyles = "text-[#020617]"; + +const WalletTypeSwitch = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, checked, ...props }, ref) => ( + + +
+
+

+ Smart Account +

+
+
+

+ Smart EOA (EIP-7702) +

+
+
+
+)); +WalletTypeSwitch.displayName = Root.displayName; + +export { WalletTypeSwitch }; diff --git a/examples/ui-demo/src/components/small-cards/Button.tsx b/examples/ui-demo/src/components/small-cards/Button.tsx new file mode 100644 index 0000000000..6d19275a90 --- /dev/null +++ b/examples/ui-demo/src/components/small-cards/Button.tsx @@ -0,0 +1,18 @@ +import { PropsWithChildren } from "react"; + +export const Button = ({ + children, + className, + ...rest +}: PropsWithChildren< + React.ComponentProps<"button"> & { className?: string } +>) => { + return ( + + ); +}; diff --git a/examples/ui-demo/src/components/small-cards/MintCard.tsx b/examples/ui-demo/src/components/small-cards/MintCard.tsx new file mode 100644 index 0000000000..57fab59326 --- /dev/null +++ b/examples/ui-demo/src/components/small-cards/MintCard.tsx @@ -0,0 +1,108 @@ +import { LoadingIcon } from "@/components/icons/loading"; +import { UseMintReturn } from "@/hooks/useMintDefault"; +import { nftContractAddress, nftContractAddressOdyssey } from "@/utils/config"; +import Image from "next/image"; +import { useMintDefault } from "@/hooks/useMintDefault"; +import { Button } from "./Button"; +import { MintStages } from "./MintStages"; +import { useMint7702 } from "@/hooks/useMint7702"; + +type NFTLoadingState = "initial" | "loading" | "success"; + +export type MintStatus = { + signing: NFTLoadingState; + gas: NFTLoadingState; + batch: NFTLoadingState; +}; + +export const MintCardDefault = () => { + const mint = useMintDefault({ + contractAddress: nftContractAddress, + }); + return ; +}; + +export const MintCard7702 = () => { + const mint = useMint7702({ + contractAddress: nftContractAddressOdyssey, + }); + return ; +}; + +const MintCardInner = ({ + isLoading, + status, + mintStarted, + handleCollectNFT, + uri, + transactionUrl, +}: UseMintReturn) => { + return ( +
+
+
+ {uri ? ( + An NFT + ) : ( + + )} +
+
+
+

+ Gasless transactions +

+
+ {!mintStarted ? ( + <> +

+ Transact with one click using gas sponsorship and background + signing. +

+
+

Gas Fee

+

+ + $0.02 + + + Free + +

+
+ + ) : ( + + )} +
+
+ +
+ ); +}; diff --git a/examples/ui-demo/src/components/small-cards/MintStages.tsx b/examples/ui-demo/src/components/small-cards/MintStages.tsx new file mode 100644 index 0000000000..2f9dcc6384 --- /dev/null +++ b/examples/ui-demo/src/components/small-cards/MintStages.tsx @@ -0,0 +1,68 @@ +import { CheckCircleFilledIcon } from "@/components/icons/check-circle-filled"; +import { LoadingIcon } from "@/components/icons/loading"; +import { loadingState } from "./Transactions"; +import { ExternalLinkIcon } from "@/components/icons/external-link"; +import { MintStatus } from "../small-cards/MintCard"; + +export const MintStages = ({ + status, + transactionUrl, +}: { + status: MintStatus; + transactionUrl?: string; +}) => { + return ( +
+ + + + Deploying your smart account... + {status.batch === "success" && transactionUrl && ( + + + + )} + + } + /> +
+ ); +}; + +const Stage = ({ + icon, + description, + className, +}: { + icon: loadingState; + description: string | JSX.Element; + className?: string; +}) => { + return ( +
+
{getMintIcon(icon)}
+

{description}

+
+ ); +}; + +export const getMintIcon = (icon: loadingState) => { + switch (icon) { + case "loading": + case "initial": + return ; + case "success": + return ( + + ); + } +}; diff --git a/examples/ui-demo/src/components/small-cards/Transactions.tsx b/examples/ui-demo/src/components/small-cards/Transactions.tsx new file mode 100644 index 0000000000..d3dc457410 --- /dev/null +++ b/examples/ui-demo/src/components/small-cards/Transactions.tsx @@ -0,0 +1,76 @@ +import { ExternalLinkIcon } from "@/components/icons/external-link"; +import { CheckCircleFilledIcon } from "@/components/icons/check-circle-filled"; +import { LoadingIcon } from "@/components/icons/loading"; +import { useEffect, useState } from "react"; +import { TransactionType } from "@/hooks/useRecurringTransactions"; + +export type loadingState = "loading" | "success" | "initial"; + +export const Transactions = ({ + transactions, +}: { + transactions: TransactionType[]; +}) => { + return ( +
+ {transactions.map((transaction, i) => ( + + ))} +
+ ); +}; + +const Transaction = ({ + className, + externalLink, + buyAmountUsdc, + state, +}: TransactionType & { className?: string }) => { + const [countdownSeconds, setCountdownSeconds] = useState(10); + + useEffect(() => { + if (state === "next") { + const interval = setInterval(() => { + setCountdownSeconds((prev) => (prev === 0 ? 0 : prev - 1)); + }, 1000); + return () => clearInterval(interval); + } else { + setCountdownSeconds(10); + } + }, [state]); + + const getText = () => { + if (state === "initial") { + return "Waiting..."; + } + if (state === "initiating") { + return "Buying 1 ETH"; + } + if (state === "next") { + return `Next buy in ${countdownSeconds} seconds`; + } + if (state === "complete") { + return `Bought 1 ETH for ${buyAmountUsdc.toLocaleString()} USDC`; + } + }; + + return ( +
+
+
+ {state === "complete" ? ( + + ) : ( + + )} +
+

{getText()}

+
+ {externalLink && state === "complete" && ( + + + + )} +
+ ); +}; diff --git a/examples/ui-demo/src/components/small-cards/TransactionsCard.tsx b/examples/ui-demo/src/components/small-cards/TransactionsCard.tsx new file mode 100644 index 0000000000..cae416083c --- /dev/null +++ b/examples/ui-demo/src/components/small-cards/TransactionsCard.tsx @@ -0,0 +1,82 @@ +import { Key } from "@/components/icons/key"; +import { Button } from "./Button"; +import { LoadingIcon } from "@/components/icons/loading"; +import { Transactions } from "./Transactions"; +import { useMemo } from "react"; +import { + UseRecurringTransactionReturn, + useRecurringTransactions, +} from "@/hooks/useRecurringTransactions"; + +export const TransactionsCardDefault = () => { + const recurringTxns = useRecurringTransactions({ mode: "default" }); + return ; +}; + +export const TransactionsCard7702 = () => { + const recurringTxns = useRecurringTransactions({ mode: "7702" }); + return ; +}; + +const TransactionsCardInner = ({ + cardStatus, + isLoadingClient, + transactions, + handleTransactions, +}: UseRecurringTransactionReturn) => { + const buttonText = useMemo(() => { + switch (cardStatus) { + case "initial": + return "Create session key"; + case "setup": + return "Creating session key..."; + case "active": + return "Transactions in progress..."; + case "done": + return "Restart session key"; + } + }, [cardStatus]); + + return ( +
+
+
+

+ New! +

+ +
+
+

+ Recurring transactions +

+ + {cardStatus === "initial" ? ( +

+ Set up a dollar-cost average order by creating a session key with + permission to buy ETH every 10 seconds. +

+ ) : cardStatus === "setup" ? ( +
+ +

+ Creating session key and minting USDC... +

+
+ ) : ( + + )} +
+
+ +
+ ); +}; diff --git a/examples/ui-demo/src/components/small-cards/Wrapper.tsx b/examples/ui-demo/src/components/small-cards/Wrapper.tsx new file mode 100644 index 0000000000..39310174a8 --- /dev/null +++ b/examples/ui-demo/src/components/small-cards/Wrapper.tsx @@ -0,0 +1,27 @@ +import { useConfigStore } from "@/state"; +import { WalletTypes } from "@/app/config"; +import { MintCard7702, MintCardDefault } from "./MintCard"; +import { + TransactionsCard7702, + TransactionsCardDefault, +} from "./TransactionsCard"; + +export const SmallCardsWrapper = () => { + const { walletType } = useConfigStore(); + + return ( +
+ {walletType === WalletTypes.smart ? ( + <> + + + + ) : ( + <> + + + + )} +
+ ); +}; diff --git a/examples/ui-demo/src/components/user-connection-avatar/RenderUserConnectionAvatar.tsx b/examples/ui-demo/src/components/user-connection-avatar/RenderUserConnectionAvatar.tsx index 7b015f387d..1b7426d9d8 100644 --- a/examples/ui-demo/src/components/user-connection-avatar/RenderUserConnectionAvatar.tsx +++ b/examples/ui-demo/src/components/user-connection-avatar/RenderUserConnectionAvatar.tsx @@ -3,26 +3,58 @@ import DialogMenu from "./UserConnectionMenuDialog"; import { UserConnectionAvatar } from "./UserConnectionAvatar"; import { UserConnectionDetails } from "./UserConnectionDetails"; import React, { useEffect, useState } from "react"; -import { useAccount } from "@account-kit/react"; +import { useAccount, useSigner } from "@account-kit/react"; import { useQuery } from "@tanstack/react-query"; import { useConfigStore } from "@/state"; +import { WalletTypes } from "@/app/config"; +import { createPublicClient, Hex, http } from "viem"; +import { odysseyTestnet } from "viem/chains"; type RenderAvatarMenuProps = { deploymentStatus: boolean; + delegationAddress?: Hex; }; export const RenderUserConnectionAvatar = ( props: React.HTMLAttributes ) => { + const [autoRefresh, setAutoRefresh] = useState(true); const { account } = useAccount({ - type: "LightAccount", + type: "ModularAccountV2", }); + const { walletType } = useConfigStore(); + + const [publicClient] = useState(() => + createPublicClient({ + chain: odysseyTestnet, + transport: http(), + }) + ); + const signer = useSigner(); const { nftTransferred } = useConfigStore(({ nftTransferred }) => ({ nftTransferred, })); - const { data: deploymentStatus = false, refetch } = useQuery({ - queryKey: ["deploymentStatus"], + const { data: hybridAccount } = useQuery({ + queryKey: ["deploymentStatus7702"], + queryFn: async () => { + const delegationAddress = signer + ? (await publicClient.getCode({ + address: await signer?.getAddress(), + })) ?? "0x" + : "0x"; + const delegationStatus = delegationAddress !== "0x"; + if (delegationStatus) setAutoRefresh(false); + return { + delegationAddress, + delegationStatus, + }; + }, + refetchInterval: autoRefresh ? 5000 : false, // refetch every 5 seconds until delegation address becomes available + }); + + const { data: deploymentStatusSCA = false, refetch: refetchSCA } = useQuery({ + queryKey: ["deploymentStatusSCA"], queryFn: async () => { const initCode = await account?.getInitCode(); return initCode && initCode === "0x"; @@ -33,26 +65,51 @@ export const RenderUserConnectionAvatar = ( useEffect(() => { // Refetch the deployment status if the NFT transferred state changes. // Only refetch if this is a user's first NFT Transfer... - if (nftTransferred && !deploymentStatus) { - refetch(); + if (nftTransferred && !deploymentStatusSCA) { + refetchSCA(); } - }, [nftTransferred, deploymentStatus, refetch]); + }, [nftTransferred, deploymentStatusSCA, refetchSCA]); return (
{/* Popover - Visible on desktop screens */}
- +
{/* Dialog - Visible on mobile screens */}
- +
); }; -const RenderPopoverMenu = ({ deploymentStatus }: RenderAvatarMenuProps) => { +const RenderPopoverMenu = ({ + deploymentStatus, + delegationAddress, +}: RenderAvatarMenuProps) => { const [popoverOpen, setPopoverOpen] = useState(false); return ( @@ -64,13 +121,19 @@ const RenderPopoverMenu = ({ deploymentStatus }: RenderAvatarMenuProps) => { /> - + ); }; -const RenderDialogMenu = ({ deploymentStatus }: RenderAvatarMenuProps) => { +const RenderDialogMenu = ({ + deploymentStatus, + delegationAddress, +}: RenderAvatarMenuProps) => { const [dialogOpen, setDialogOpen] = useState(false); return ( @@ -87,7 +150,10 @@ const RenderDialogMenu = ({ deploymentStatus }: RenderAvatarMenuProps) => { setDialogOpen(false)}>

Profile

- +

diff --git a/examples/ui-demo/src/components/user-connection-avatar/UserConnectionAvatar.tsx b/examples/ui-demo/src/components/user-connection-avatar/UserConnectionAvatar.tsx index 51969b75d7..0332238a49 100644 --- a/examples/ui-demo/src/components/user-connection-avatar/UserConnectionAvatar.tsx +++ b/examples/ui-demo/src/components/user-connection-avatar/UserConnectionAvatar.tsx @@ -26,7 +26,7 @@ const UserConnectionAvatar = ({ ); const user = useUser(); const { address: SCAUserAddress } = useAccount({ - type: "LightAccount", + type: "ModularAccountV2", }); const isEOAUser = user?.type === "eoa"; diff --git a/examples/ui-demo/src/components/user-connection-avatar/UserConnectionDetails.tsx b/examples/ui-demo/src/components/user-connection-avatar/UserConnectionDetails.tsx index 771767fec9..d6e5c958d9 100644 --- a/examples/ui-demo/src/components/user-connection-avatar/UserConnectionDetails.tsx +++ b/examples/ui-demo/src/components/user-connection-avatar/UserConnectionDetails.tsx @@ -1,3 +1,4 @@ +import { WalletTypes } from "@/app/config"; import { ExternalLinkIcon } from "@/components/icons/external-link"; import { LogoutIcon } from "@/components/icons/logout"; import { DeploymentStatusIndicator } from "@/components/user-connection-avatar/DeploymentStatusIndicator"; @@ -5,20 +6,28 @@ import { UserAddressLink } from "./UserAddressLink"; import { useConfigStore } from "@/state"; import { useAccount, useLogout, useSigner, useUser } from "@account-kit/react"; import { useQuery } from "@tanstack/react-query"; +import { Hex } from "viem"; +import { ODYSSEY_EXPLORER_URL } from "@/hooks/7702/constants"; type UserConnectionDetailsProps = { deploymentStatus: boolean; + delegationAddress?: Hex; }; export function UserConnectionDetails({ deploymentStatus, + delegationAddress, }: UserConnectionDetailsProps) { const user = useUser(); const signer = useSigner(); const { logout } = useLogout(); - const { theme, primaryColor } = useConfigStore( - ({ ui: { theme, primaryColor } }) => ({ theme, primaryColor }) + const { theme, primaryColor, walletType } = useConfigStore( + ({ ui: { theme, primaryColor }, walletType }) => ({ + theme, + primaryColor, + walletType, + }) ); - const scaAccount = useAccount({ type: "LightAccount" }); + const scaAccount = useAccount({ type: "ModularAccountV2" }); const isEOAUser = user?.type === "eoa"; @@ -69,40 +78,78 @@ export function UserConnectionDetails({ {/* Smart Account */}
- Smart account + {walletType === WalletTypes.smart ? "Smart account" : "Address"} - +
- {/* Status */} -
- Status -
- - - {deploymentStatus ? "Deployed" : "Not deployed"} - -
-
- {/* Signer */} -
- - - Signer - - + + ) : ( +
+ + Delegated to + +
+ + + {deploymentStatus && delegationAddress ? ( + + Modular Account + + ) : ( + "None" + )} + +
+
+ )} {/* Logout */} diff --git a/examples/ui-demo/src/hooks/7702/constants.ts b/examples/ui-demo/src/hooks/7702/constants.ts new file mode 100644 index 0000000000..ab254f75a9 --- /dev/null +++ b/examples/ui-demo/src/hooks/7702/constants.ts @@ -0,0 +1,3 @@ +export const ODYSSEY_EXPLORER_URL = "https://odyssey-explorer.ithaca.xyz"; + +export const SESSION_KEY_VALIDITY_TIME_SECONDS: number = 6000; // 10 minutes diff --git a/examples/ui-demo/src/hooks/7702/dca/abi/erc20Mintable.ts b/examples/ui-demo/src/hooks/7702/dca/abi/erc20Mintable.ts new file mode 100644 index 0000000000..807101ac4c --- /dev/null +++ b/examples/ui-demo/src/hooks/7702/dca/abi/erc20Mintable.ts @@ -0,0 +1,344 @@ +export const erc20MintableAbi = [ + { + type: "constructor", + inputs: [ + { + name: "name", + type: "string", + internalType: "string", + }, + { + name: "symbol", + type: "string", + internalType: "string", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "allowance", + inputs: [ + { + name: "owner", + type: "address", + internalType: "address", + }, + { + name: "spender", + type: "address", + internalType: "address", + }, + ], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "approve", + inputs: [ + { + name: "spender", + type: "address", + internalType: "address", + }, + { + name: "value", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "bool", + internalType: "bool", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "balanceOf", + inputs: [ + { + name: "account", + type: "address", + internalType: "address", + }, + ], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "decimals", + inputs: [], + outputs: [ + { + name: "", + type: "uint8", + internalType: "uint8", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "mint", + inputs: [ + { + name: "to", + type: "address", + internalType: "address", + }, + { + name: "amount", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "name", + inputs: [], + outputs: [ + { + name: "", + type: "string", + internalType: "string", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "symbol", + inputs: [], + outputs: [ + { + name: "", + type: "string", + internalType: "string", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "totalSupply", + inputs: [], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "transfer", + inputs: [ + { + name: "to", + type: "address", + internalType: "address", + }, + { + name: "value", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "bool", + internalType: "bool", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "transferFrom", + inputs: [ + { + name: "from", + type: "address", + internalType: "address", + }, + { + name: "to", + type: "address", + internalType: "address", + }, + { + name: "value", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "bool", + internalType: "bool", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "event", + name: "Approval", + inputs: [ + { + name: "owner", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "spender", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "value", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "Transfer", + inputs: [ + { + name: "from", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "to", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "value", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + ], + anonymous: false, + }, + { + type: "error", + name: "ERC20InsufficientAllowance", + inputs: [ + { + name: "spender", + type: "address", + internalType: "address", + }, + { + name: "allowance", + type: "uint256", + internalType: "uint256", + }, + { + name: "needed", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "ERC20InsufficientBalance", + inputs: [ + { + name: "sender", + type: "address", + internalType: "address", + }, + { + name: "balance", + type: "uint256", + internalType: "uint256", + }, + { + name: "needed", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "ERC20InvalidApprover", + inputs: [ + { + name: "approver", + type: "address", + internalType: "address", + }, + ], + }, + { + type: "error", + name: "ERC20InvalidReceiver", + inputs: [ + { + name: "receiver", + type: "address", + internalType: "address", + }, + ], + }, + { + type: "error", + name: "ERC20InvalidSender", + inputs: [ + { + name: "sender", + type: "address", + internalType: "address", + }, + ], + }, + { + type: "error", + name: "ERC20InvalidSpender", + inputs: [ + { + name: "spender", + type: "address", + internalType: "address", + }, + ], + }, +] as const; diff --git a/examples/ui-demo/src/hooks/7702/dca/abi/swap.ts b/examples/ui-demo/src/hooks/7702/dca/abi/swap.ts new file mode 100644 index 0000000000..365b7d0c17 --- /dev/null +++ b/examples/ui-demo/src/hooks/7702/dca/abi/swap.ts @@ -0,0 +1,87 @@ +export const swapAbi = [ + { + type: "constructor", + inputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "mint", + inputs: [ + { + name: "amount1", + type: "uint256", + internalType: "uint256", + }, + { + name: "amount2", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "swapUSDCtoWETH", + inputs: [ + { + name: "amountIn", + type: "uint256", + internalType: "uint256", + }, + { + name: "amountOut", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "swapWETHtoUSDC", + inputs: [ + { + name: "amountIn", + type: "uint256", + internalType: "uint256", + }, + { + name: "amountOut", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "usdc", + inputs: [], + outputs: [ + { + name: "", + type: "address", + internalType: "contract ERC20Mintable", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "weth", + inputs: [], + outputs: [ + { + name: "", + type: "address", + internalType: "contract ERC20Mintable", + }, + ], + stateMutability: "view", + }, +] as const; diff --git a/examples/ui-demo/src/hooks/7702/dca/constants.ts b/examples/ui-demo/src/hooks/7702/dca/constants.ts new file mode 100644 index 0000000000..56b0a37167 --- /dev/null +++ b/examples/ui-demo/src/hooks/7702/dca/constants.ts @@ -0,0 +1,10 @@ +import type { Address } from "viem"; + +export const SWAP_VENUE_ADDRESS: Address = + "0xB0AEC4c25E8332256A91bBaf169E3C32dfC3C33C"; + +export const DEMO_USDC_ADDRESS: Address = + "0xCFf7C6dA719408113DFcb5e36182c6d5aa491443"; + +export const DEMO_WETH_ADDRESS: Address = + "0x0766798566D1f6e2f0b126f7783aaB2CBb81c66f"; diff --git a/examples/ui-demo/src/hooks/7702/dca/sessionKeyInit.ts b/examples/ui-demo/src/hooks/7702/dca/sessionKeyInit.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/ui-demo/src/hooks/7702/genEntityId.ts b/examples/ui-demo/src/hooks/7702/genEntityId.ts new file mode 100644 index 0000000000..11c65ba010 --- /dev/null +++ b/examples/ui-demo/src/hooks/7702/genEntityId.ts @@ -0,0 +1,8 @@ +import { maxUint32 } from "viem"; + +export function genEntityId(): number { + const min: number = 1; + const max: number = Number(maxUint32); + + return Math.floor(Math.random() * (max - min + 1)) + min; +} diff --git a/examples/ui-demo/src/hooks/7702/transportSetup.ts b/examples/ui-demo/src/hooks/7702/transportSetup.ts new file mode 100644 index 0000000000..62022476f0 --- /dev/null +++ b/examples/ui-demo/src/hooks/7702/transportSetup.ts @@ -0,0 +1,79 @@ +import type { Chain } from "viem"; +import { defineChain } from "viem"; +import { alchemy } from "@account-kit/infra"; + +// export const mekong: Chain = defineChain({ +// id: 7078815900, +// name: "Mekong Pectra Devnet", +// nativeCurrency: { name: "eth", symbol: "eth", decimals: 18 }, +// rpcUrls: { +// default: { +// http: ["https://rpc.mekong.ethpandaops.io"], +// }, +// alchemy: { +// http: ["https://rpc.mekong.ethpandaops.io"], +// }, +// }, +// blockExplorers: { +// default: { +// name: "Block Explorer", +// url: "https://explorer.mekong.ethpandaops.io", +// }, +// }, +// testnet: true, +// }); + +export const odyssey: Chain = defineChain({ + id: 911867, + name: "Odyssey", + nativeCurrency: { name: "eth", symbol: "eth", decimals: 18 }, + rpcUrls: { + default: { + http: ["https://odyssey.ithaca.xyz"], + }, + alchemy: { + http: ["https://odyssey.ithaca.xyz"], + }, + }, + blockExplorers: { + default: { + name: "Block Explorer", + url: "https://odyssey-explorer.ithaca.xyz/", + }, + }, + testnet: true, +}); + +// const bundlerMethods = [ +// "eth_sendUserOperation", +// "eth_estimateUserOperationGas", +// "eth_getUserOperationReceipt", +// "eth_getUserOperationByHash", +// "eth_supportedEntryPoints", +// "rundler_maxPriorityFeePerGas", +// ]; + +// export const splitMekongTransport = split({ +// overrides: [ +// { +// methods: bundlerMethods, +// transport: http("https://eth-mekong.g.alchemypreview.com/v2/HjEy_lTrJ0P2Y8BRFhAHm_1dVc0svdme"), +// }, +// ], +// fallback: http("/api/rpc-mekong"), +// }) + +// export const splitMekongTransport = alchemy({ +// alchemyConnection: { +// rpcUrl: +// "https://eth-mekong.g.alchemypreview.com/v2/HjEy_lTrJ0P2Y8BRFhAHm_1dVc0svdme", +// }, +// nodeRpcUrl: "/api/rpc-mekong", +// }); + +export const splitOdysseyTransport = alchemy({ + alchemyConnection: { + rpcUrl: "/api/bundler-odyssey", + }, + nodeRpcUrl: "https://odyssey.ithaca.xyz", +}); diff --git a/examples/ui-demo/src/hooks/useMint7702.tsx b/examples/ui-demo/src/hooks/useMint7702.tsx new file mode 100644 index 0000000000..7d099b2bbc --- /dev/null +++ b/examples/ui-demo/src/hooks/useMint7702.tsx @@ -0,0 +1,141 @@ +import { useQuery } from "@tanstack/react-query"; +import { AccountKitNftMinterABI } from "@/utils/config"; +import { useCallback, useState, useMemo } from "react"; +import { useToast } from "@/hooks/useToast"; +import { Address, encodeFunctionData, Hash } from "viem"; +import { MintStatus } from "@/components/small-cards/MintCard"; +import { UseMintReturn } from "./useMintDefault"; +import { useModularAccountV2Client } from "./useModularAccountV2Client"; +import { odyssey, splitOdysseyTransport } from "./7702/transportSetup"; + +const initialValuePropState = { + signing: "initial", + gas: "initial", + batch: "initial", +} satisfies MintStatus; + +export const useMint7702 = (props: { + contractAddress: Address; +}): UseMintReturn => { + const { client, isLoadingClient } = useModularAccountV2Client({ + mode: "7702", + chain: odyssey, + transport: splitOdysseyTransport, + }); + + const [status, setStatus] = useState(initialValuePropState); + const [mintStarted, setMintStarted] = useState(false); + const [txnHash, setTxnHash] = useState(undefined); + const isLoading = + Object.values(status).some((x) => x === "loading") || isLoadingClient; + const { setToast } = useToast(); + + const { data: uri } = useQuery({ + queryKey: ["contractURI", props.contractAddress], + queryFn: async () => { + const uri = await client?.readContract({ + address: props.contractAddress, + abi: AccountKitNftMinterABI, + functionName: "baseURI", + }); + return uri; + }, + enabled: !!client && !!client?.readContract, + }); + + const handleCollectNFT = useCallback(async () => { + const handleSuccess = (txnHash: Hash) => { + setStatus(() => ({ + batch: "success", + gas: "success", + signing: "success", + })); + setTxnHash(txnHash); + + setToast({ + type: "success", + text: ( + <> + + {`You've collected your NFT!`} + + + {`You've successfully collected your NFT! Refresh to mint + again.`} + + + ), + open: true, + }); + }; + + const handleError = (error: Error) => { + console.error(error); + setStatus(initialValuePropState); + setMintStarted(false); + setToast({ + type: "error", + text: "There was a problem with that action", + open: true, + }); + }; + + if (!client) { + console.error("no client"); + return; + } + + setTxnHash(undefined); + setMintStarted(true); + + setStatus({ + signing: "loading", + gas: "loading", + batch: "loading", + }); + + setTimeout(() => { + setStatus((prev) => ({ ...prev, signing: "success" })); + }, 500); + setTimeout(() => { + setStatus((prev) => ({ ...prev, gas: "success" })); + }, 750); + + const uoHash = await client.sendUserOperation({ + uo: { + target: props.contractAddress, + data: encodeFunctionData({ + abi: AccountKitNftMinterABI, + functionName: "mintTo", + args: [client.getAddress()], + }), + }, + }); + + const txnHash = await client + .waitForUserOperationTransaction(uoHash) + .catch((e) => { + handleError(e); + }); + + if (txnHash) { + handleSuccess(txnHash); + } + }, [client, props.contractAddress, setToast]); + + const transactionUrl = useMemo(() => { + if (!client?.chain?.blockExplorers || !txnHash) { + return undefined; + } + return `${client.chain.blockExplorers.default.url}/tx/${txnHash}`; + }, [client?.chain.blockExplorers, txnHash]); + + return { + isLoading, + status, + mintStarted, + handleCollectNFT, + uri, + transactionUrl, + }; +}; diff --git a/examples/ui-demo/src/hooks/useMintDefault.tsx b/examples/ui-demo/src/hooks/useMintDefault.tsx new file mode 100644 index 0000000000..2d5f6ccc7e --- /dev/null +++ b/examples/ui-demo/src/hooks/useMintDefault.tsx @@ -0,0 +1,149 @@ +import { useQuery } from "@tanstack/react-query"; +import { useCallback, useMemo, useState } from "react"; +import { useToast } from "./useToast"; +import { + useSmartAccountClient, + useSendUserOperation, +} from "@account-kit/react"; +import { Address, encodeFunctionData } from "viem"; +import { AccountKitNftMinterABI } from "@/utils/config"; +import { MintStatus } from "@/components/small-cards/MintCard"; + +const initialValuePropState = { + signing: "initial", + gas: "initial", + batch: "initial", +} satisfies MintStatus; + +export interface UseMintReturn { + isLoading: boolean; + status: MintStatus; + mintStarted: boolean; + handleCollectNFT: () => unknown; + uri?: string; + transactionUrl?: string; +} + +// TODO(jh): Once the client supports switching b/w default +// & 7702 modes, this hook should support both modes. +export const useMintDefault = (props: { + contractAddress: Address; +}): UseMintReturn => { + const { client, isLoadingClient } = useSmartAccountClient({ + type: "ModularAccountV2", + accountParams: { + mode: "default", + }, + }); + + const [status, setStatus] = useState(initialValuePropState); + const [mintStarted, setMintStarted] = useState(false); + const isLoading = + Object.values(status).some((x) => x === "loading") || isLoadingClient; + const { setToast } = useToast(); + + const handleSuccess = () => { + setStatus(() => ({ + batch: "success", + gas: "success", + signing: "success", + })); + + setToast({ + type: "success", + text: ( + <> + + {`You've collected your NFT!`} + + + {`You've successfully collected your NFT!`} + + + ), + open: true, + }); + }; + + const handleError = (error: Error) => { + console.error(error); + setStatus(initialValuePropState); + setMintStarted(false); + setToast({ + type: "error", + text: "There was a problem with that action", + open: true, + }); + }; + + const { sendUserOperationResult, sendUserOperation } = useSendUserOperation({ + client, + waitForTxn: true, + onError: handleError, + onSuccess: handleSuccess, + onMutate: () => { + setMintStarted(true); + setTimeout(() => { + setStatus((prev) => ({ ...prev, signing: "success" })); + }, 500); + setTimeout(() => { + setStatus((prev) => ({ ...prev, gas: "success" })); + }, 750); + }, + }); + + const { data: uri } = useQuery({ + queryKey: ["contractURI", props.contractAddress], + queryFn: async () => { + if (!client) { + throw new Error("no client"); + } + return client.readContract({ + address: props.contractAddress, + abi: AccountKitNftMinterABI, + functionName: "baseURI", + }); + }, + enabled: !!client && !!client?.readContract, + }); + + const handleCollectNFT = useCallback(async () => { + if (!client) { + console.error("no client"); + return; + } + + setStatus({ + signing: "loading", + gas: "loading", + batch: "loading", + }); + + sendUserOperation({ + uo: { + target: props.contractAddress, + data: encodeFunctionData({ + abi: AccountKitNftMinterABI, + functionName: "mintTo", + args: [client.getAddress()], + }), + }, + }); + }, [client, props.contractAddress, sendUserOperation]); + + const transactionUrl = useMemo(() => { + if (!client?.chain?.blockExplorers || !sendUserOperationResult?.hash) { + return undefined; + } + return `${client.chain.blockExplorers.default.url}/tx/${sendUserOperationResult.hash}`; + }, [client, sendUserOperationResult?.hash]); + + return { + isLoading, + status, + mintStarted, + handleCollectNFT, + uri, + transactionUrl, + }; +}; diff --git a/examples/ui-demo/src/hooks/useModularAccountV2Client.ts b/examples/ui-demo/src/hooks/useModularAccountV2Client.ts new file mode 100644 index 0000000000..ed8b263d4e --- /dev/null +++ b/examples/ui-demo/src/hooks/useModularAccountV2Client.ts @@ -0,0 +1,126 @@ +import type { AlchemyWebSigner } from "@account-kit/signer"; +import { useSigner, useSignerStatus } from "@account-kit/react"; +import { useState, useEffect } from "react"; +import { + createModularAccountV2Client, + ModularAccountV2Client, +} from "@account-kit/smart-contracts"; +import { alchemyFeeEstimator, AlchemyTransport } from "@account-kit/infra"; +import { + installValidationActions, + InstallValidationActions, +} from "@account-kit/smart-contracts/experimental"; +import { Chain, Hex, Address, PrivateKeyAccount } from "viem"; +import { LocalAccountSigner } from "@aa-sdk/core"; +import { privateKeyToAccount } from "viem/accounts"; + +type Client = ModularAccountV2Client< + AlchemyWebSigner | LocalAccountSigner +> & + InstallValidationActions< + AlchemyWebSigner | LocalAccountSigner + >; + +// Hook that creates an MAv2 client that can be used for things that +// @account-kit/react doesn't yet support, such as session keys. +export const useModularAccountV2Client = ({ + mode, + chain, + transport, + localKeyOverride, +}: { + mode: "7702" | "default"; + chain: Chain; + transport: AlchemyTransport; + localKeyOverride?: { + readonly key: Hex; + readonly entityId: number; + readonly accountAddress?: Address; + }; +}): { + client: Client | undefined; + isLoadingClient: boolean; + isError: boolean; +} => { + const [client, setClient] = useState(undefined); + const [isLoadingClient, setIsLoadingClient] = useState(true); + const [isError, setError] = useState(false); + + const signer = useSigner(); + const { isConnected } = useSignerStatus(); + + // Must destructure the inner fields to use as dependencies in the useEffect hook, otherwise the object reference will be compared and cause an infinite render loop + const { key, entityId, accountAddress } = localKeyOverride ?? {}; + + useEffect(() => { + let isMounted = true; + + const init = async () => { + if (!signer || !isConnected) { + return; + } + + if (key != null && accountAddress == null) { + // We have an override present but don't have the account to set it for, so leave the client as undefined until we get the account address override. + return; + } + + try { + const _client = ( + await createModularAccountV2Client({ + mode, + chain, + transport, + accountAddress, + signer: key + ? new LocalAccountSigner(privateKeyToAccount(key)) + : signer, + signerEntity: entityId + ? { + isGlobalValidation: false, + entityId, + } + : undefined, + feeEstimator: alchemyFeeEstimator(transport), + policyId: process.env.NEXT_PUBLIC_PAYMASTER_POLICY_ID!, + }) + ).extend(installValidationActions); + + if (!isMounted) { + return; + } + + setClient(_client); + setError(false); + } catch (e) { + console.error(e); + + if (!isMounted) { + return; + } + + setClient(undefined); + setError(true); + } finally { + setIsLoadingClient(false); + } + }; + + init(); + + return () => { + isMounted = false; + }; + }, [ + accountAddress, + chain, + entityId, + isConnected, + key, + mode, + signer, + transport, + ]); + + return { client, isLoadingClient, isError }; +}; diff --git a/examples/ui-demo/src/hooks/useRecurringTransactions.ts b/examples/ui-demo/src/hooks/useRecurringTransactions.ts new file mode 100644 index 0000000000..a035d9e9ee --- /dev/null +++ b/examples/ui-demo/src/hooks/useRecurringTransactions.ts @@ -0,0 +1,284 @@ +import { useState } from "react"; +import type { BatchUserOperationCallData } from "@aa-sdk/core"; +import { + encodeFunctionData, + toFunctionSelector, + Hex, + parseEther, + getAbiItem, +} from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { + AllowlistModule, + getDefaultAllowlistModuleAddress, + getDefaultSingleSignerValidationModuleAddress, + getDefaultTimeRangeModuleAddress, + semiModularAccountBytecodeAbi, + TimeRangeModule, +} from "@account-kit/smart-contracts/experimental"; +import { SingleSignerValidationModule } from "@account-kit/smart-contracts/experimental"; +import { useModularAccountV2Client } from "./useModularAccountV2Client"; +import { DEMO_USDC_ADDRESS, SWAP_VENUE_ADDRESS } from "./7702/dca/constants"; +import { swapAbi } from "./7702/dca/abi/swap"; +import { erc20MintableAbi } from "./7702/dca/abi/erc20Mintable"; +import { genEntityId } from "./7702/genEntityId"; +import { odyssey, splitOdysseyTransport } from "./7702/transportSetup"; +import { SESSION_KEY_VALIDITY_TIME_SECONDS } from "./7702/constants"; + +export type CardStatus = "initial" | "setup" | "active" | "done"; + +export type TransactionStages = "initial" | "initiating" | "next" | "complete"; +export type TransactionType = { + state: TransactionStages; + buyAmountUsdc: number; + externalLink?: string; +}; + +export const initialTransactions: TransactionType[] = [ + { + state: "initiating", + buyAmountUsdc: 4000, + }, + { + state: "initial", + buyAmountUsdc: 3500, + }, + { + state: "initial", + buyAmountUsdc: 4200, + }, +]; + +export interface UseRecurringTransactionReturn { + isLoadingClient: boolean; + cardStatus: CardStatus; + transactions: TransactionType[]; + handleTransactions: () => void; +} + +export const useRecurringTransactions = ({ + mode, +}: { + mode: "default" | "7702"; +}): UseRecurringTransactionReturn => { + const [transactions, setTransactions] = + useState(initialTransactions); + + const [cardStatus, setCardStatus] = useState("initial"); + + const [localSessionKey] = useState(() => generatePrivateKey()); + const [sessionKeyEntityId] = useState(() => genEntityId()); + const [sessionKeyAdded, setSessionKeyAdded] = useState(false); + + const { client, isLoadingClient } = useModularAccountV2Client({ + mode, + chain: odyssey, + transport: splitOdysseyTransport, + }); + + const { client: sessionKeyClient } = useModularAccountV2Client({ + mode, + chain: odyssey, + transport: splitOdysseyTransport, + localKeyOverride: { + key: localSessionKey, + entityId: sessionKeyEntityId, + accountAddress: client?.getAddress(), + }, + }); + + const handleTransaction = async (transactionIndex: number) => { + setTransactions((prev) => { + const newState = [...prev]; + newState[transactionIndex].state = "initiating"; + if (transactionIndex + 1 < newState.length) { + newState[transactionIndex + 1].state = "next"; + } + return newState; + }); + + if (!sessionKeyClient) { + console.error("no session key client"); + setCardStatus("initial"); + return; + } + + const usdcInAmount = transactions[transactionIndex].buyAmountUsdc; + + const uoHash = await sessionKeyClient.sendUserOperation({ + uo: { + target: SWAP_VENUE_ADDRESS, + data: encodeFunctionData({ + abi: swapAbi, + functionName: "swapUSDCtoWETH", + args: [parseEther(String(usdcInAmount)), parseEther("1")], + }), + }, + }); + + const txnHash = await sessionKeyClient + .waitForUserOperationTransaction(uoHash) + .catch((e) => { + console.log(e); + }); + + if (!txnHash) { + setCardStatus("initial"); + return; + } + + setTransactions((prev) => { + const newState = [...prev]; + newState[transactionIndex].state = "complete"; + newState[transactionIndex].externalLink = odyssey.blockExplorers + ? `${odyssey.blockExplorers?.default.url}/tx/${txnHash}` + : undefined; + return newState; + }); + }; + + // Mock method to fire transactions for 7702 + const handleTransactions = async () => { + if (!client) { + console.error("no client"); + return; + } + + // initial state as referenced by `const initialTransactions` is mutated, so we need to re-create it. + setTransactions([ + { + state: "initiating", + buyAmountUsdc: 4000, + }, + { + state: "initial", + buyAmountUsdc: 3500, + }, + { + state: "initial", + buyAmountUsdc: 4200, + }, + ]); + setCardStatus("setup"); + + // Start by minting the required USDC amount, and installing the session key, if not already installed. + + const currentEpochTimeSeconds = Math.floor(Date.now() / 1000); + + const batchActions: BatchUserOperationCallData = [ + { + target: DEMO_USDC_ADDRESS, + data: encodeFunctionData({ + abi: erc20MintableAbi, + functionName: "mint", + args: [client.getAddress(), parseEther("11700")], // mint 11,700 USDC + }), + }, + { + target: DEMO_USDC_ADDRESS, + data: encodeFunctionData({ + abi: erc20MintableAbi, + functionName: "approve", + args: [SWAP_VENUE_ADDRESS, parseEther("11700")], // approve 11,700 USDC + }), + }, + // Only install the session key if it hasn't been installed yet + ...(sessionKeyAdded + ? [] + : [ + { + target: await client.getAddress(), + data: await client.encodeInstallValidation({ + validationConfig: { + moduleAddress: + await getDefaultSingleSignerValidationModuleAddress( + odyssey + ), + entityId: sessionKeyEntityId, + isGlobal: false, + isSignatureValidation: false, + isUserOpValidation: true, + }, + selectors: [ + toFunctionSelector( + getAbiItem({ + abi: semiModularAccountBytecodeAbi, + name: "execute", + }) + ), + ], + installData: SingleSignerValidationModule.encodeOnInstallData({ + entityId: sessionKeyEntityId, + signer: privateKeyToAccount(localSessionKey).address, + }), + hooks: [ + AllowlistModule.buildHook( + { + entityId: sessionKeyEntityId, + inputs: [ + { + target: SWAP_VENUE_ADDRESS, + hasSelectorAllowlist: true, + hasERC20SpendLimit: false, + erc20SpendLimit: BigInt(0), + selectors: [ + toFunctionSelector( + getAbiItem({ + abi: swapAbi, + name: "swapUSDCtoWETH", + }) + ), + ], + }, + ], + }, + getDefaultAllowlistModuleAddress(odyssey) + ), + TimeRangeModule.buildHook( + { + entityId: sessionKeyEntityId, + validUntil: + currentEpochTimeSeconds + + SESSION_KEY_VALIDITY_TIME_SECONDS, + validAfter: 0, + }, + getDefaultTimeRangeModuleAddress(odyssey) + ), + ], + }), + }, + ]), + ]; + + const uoHash = await client.sendUserOperation({ + uo: batchActions, + }); + + const txnHash = await client + .waitForUserOperationTransaction(uoHash) + .catch((e) => { + console.log(e); + }); + + if (!txnHash) { + setCardStatus("initial"); + return; + } + + setSessionKeyAdded(true); + setCardStatus("active"); + + for (let i = 0; i < transactions.length; i++) { + await handleTransaction(i); + } + + setCardStatus("done"); + }; + + return { + isLoadingClient, + cardStatus, + transactions, + handleTransactions, + }; +}; diff --git a/examples/ui-demo/src/state/store.tsx b/examples/ui-demo/src/state/store.tsx index 6e588ed3cc..e99db24681 100644 --- a/examples/ui-demo/src/state/store.tsx +++ b/examples/ui-demo/src/state/store.tsx @@ -1,4 +1,4 @@ -import { Config, DEFAULT_CONFIG } from "@/app/config"; +import { Config, DEFAULT_CONFIG, WalletTypes } from "@/app/config"; import { getSectionsForConfig } from "@/app/sections"; import { AuthCardHeader } from "@/components/shared/AuthCardHeader"; import { cookieStorage, parseCookie } from "@account-kit/core"; @@ -49,6 +49,7 @@ export type DemoState = Config & { ) => void; setTheme: (theme: Config["ui"]["theme"]) => void; setSupportUrl: (url: string) => void; + setWalletType: (walletType: WalletTypes) => void; }; export const createDemoStore = (initialConfig: Config = DEFAULT_CONFIG) => { @@ -68,6 +69,7 @@ export const createDemoStore = (initialConfig: Config = DEFAULT_CONFIG) => { setTheme, setNftTransferred, nftTransferred, + setWalletType, ...config }) => config, skipHydration: true, @@ -141,6 +143,11 @@ function createInitialState( }, })); }, + setWalletType: (walletType: WalletTypes) => { + set(() => ({ + walletType, + })); + }, }); } diff --git a/examples/ui-demo/src/utils/config.ts b/examples/ui-demo/src/utils/config.ts index b77a462372..ddd641177f 100644 --- a/examples/ui-demo/src/utils/config.ts +++ b/examples/ui-demo/src/utils/config.ts @@ -1,4 +1,6 @@ export const nftContractAddress = "0x92ccF22A61f92d83463b04090A32dA9a6D958f64"; +export const nftContractAddressOdyssey = + "0x7E06a337929B1Cb92363e15414e37959a36E5338"; export const AccountKitNftMinterABI = [ { type: "constructor", diff --git a/examples/ui-demo/tailwind.config.ts b/examples/ui-demo/tailwind.config.ts index c69cbb7552..289f5a047b 100644 --- a/examples/ui-demo/tailwind.config.ts +++ b/examples/ui-demo/tailwind.config.ts @@ -104,6 +104,10 @@ const config = { backgroundImage: { "bg-main": "url('/images/bg-main.webp')", }, + boxShadow: { + smallCard: + "0px 50px 50px 0px rgba(0, 0, 0, 0.09), 0px 12px 27px 0px rgba(0, 0, 0, 0.10)", + }, }, }, plugins: [require("tailwindcss-animate"), require("tailwind-scrollbar")], diff --git a/examples/ui-demo/turbo.json b/examples/ui-demo/turbo.json index cdd35e71ea..c157b4a433 100644 --- a/examples/ui-demo/turbo.json +++ b/examples/ui-demo/turbo.json @@ -4,7 +4,12 @@ "build": { "dependsOn": ["^build"], "outputs": [".next/**", "!.next/cache/**"], - "env": ["API_KEY", "ALCHEMY_API_URL", "ALCHEMY_RPC_URL"] + "env": [ + "API_KEY", + "ALCHEMY_API_URL", + "ALCHEMY_RPC_URL", + "ALCHEMY_RPC_URL_ODYSSEY" + ] }, "dev": { "dependsOn": ["^build"], diff --git a/package.json b/package.json index 9f06ec0908..ae3b9e18ea 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "build": "yarn build:libs --filter='!@account-kit/react-native-signer' --filter='!@account-kit/react-native-signer-example'", "postbuild": "yarn lint:write", "build:libs": "turbo run build --filter='@account-kit/*' --filter='@aa-sdk/*' --filter='./templates/*'", - "build:demo": "turbo run build --filter=ui-demo", + "build:demo": "turbo run build --filter=ui-demo --filter='./templates/*'", "build:site": "turbo run build --filter=docs", "build:examples": "turbo run build", "clean": "yarn clean:turbo && yarn clean:node_modules", diff --git a/yarn.lock b/yarn.lock index c64023c519..3edf3ce20e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -26132,7 +26132,7 @@ vfile@^6.0.0, vfile@^6.0.1: unist-util-stringify-position "^4.0.0" vfile-message "^4.0.0" -viem@2.20.0, viem@2.22.6, viem@^2.1.1, viem@^2.20.0, viem@^2.21.40, viem@^2.21.41: +viem@2.20.0, viem@2.22.6, viem@^2.1.1, viem@^2.21.40, viem@^2.21.41, viem@^2.22.6: version "2.22.6" resolved "https://registry.yarnpkg.com/viem/-/viem-2.22.6.tgz#724c66caed2c8bfd73748f099131e60d392e26ef" integrity sha512-wbru5XP0Aa2QskBrZsv7VOriqRnAKn0tahs957xRPOM00ABN4AGAY9xM16UvIq+giRJU6oahXDhPrR1QaYymoA==