From 5dc44ee41ed955746d64ae0bc9617cc9d54bdf05 Mon Sep 17 00:00:00 2001 From: marc Date: Wed, 27 Mar 2024 11:20:45 +0100 Subject: [PATCH] add web example --- .../.env.example | 6 + .../.eslintrc.cjs | 8 + .../.gitignore | 24 + .../README.md | 36 + .../index.html | 13 + .../package-lock.json | 1296 +++++++++++++++++ .../package.json | 26 + .../public/candide-atelier-logo.svg | 20 + .../public/safe-logo-white.svg | 21 + .../src/App.css | 27 + .../src/App.tsx | 71 + .../src/components/PasskeyCard.tsx | 34 + .../src/components/SafeCard.tsx | 170 +++ .../src/hooks/useLocalStorageState.ts | 35 + .../src/index.css | 72 + .../src/logic/passkeys.ts | 116 ++ .../src/logic/storage.ts | 26 + .../src/logic/userOp.ts | 130 ++ .../src/main.tsx | 18 + .../src/utils.ts | 15 + .../src/vite-env.d.ts | 1 + .../tsconfig.json | 25 + .../tsconfig.node.json | 10 + .../vite.config.ts | 18 + 24 files changed, 2218 insertions(+) create mode 100644 examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/.env.example create mode 100644 examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/.eslintrc.cjs create mode 100644 examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/.gitignore create mode 100644 examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/README.md create mode 100644 examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/index.html create mode 100644 examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/package-lock.json create mode 100644 examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/package.json create mode 100644 examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/public/candide-atelier-logo.svg create mode 100644 examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/public/safe-logo-white.svg create mode 100644 examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/App.css create mode 100644 examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/App.tsx create mode 100644 examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/components/PasskeyCard.tsx create mode 100644 examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/components/SafeCard.tsx create mode 100644 examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/hooks/useLocalStorageState.ts create mode 100644 examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/index.css create mode 100644 examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/logic/passkeys.ts create mode 100644 examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/logic/storage.ts create mode 100644 examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/logic/userOp.ts create mode 100644 examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/main.tsx create mode 100644 examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/utils.ts create mode 100644 examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/vite-env.d.ts create mode 100644 examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/tsconfig.json create mode 100644 examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/tsconfig.node.json create mode 100644 examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/vite.config.ts diff --git a/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/.env.example b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/.env.example new file mode 100644 index 0000000..f1a44ff --- /dev/null +++ b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/.env.example @@ -0,0 +1,6 @@ +VITE_CHAIN_NAME=sepolia +VITE_CHAIN_ID=11155111 +VITE_BUNDLER_URL=https://sepolia.test.voltaire.candidewallet.com/rpc +VITE_JSON_RPC_PROVIDER=https://ethereum-sepolia-rpc.publicnode.com +VITE_PAYMASTER_URL=https://api.candide.dev/paymaster/v1/sepolia/f70ee1e3efa3f7a67ff392eb99dafc78 +VITE_ENTRYPOINT_ADDRESS=0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 diff --git a/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/.eslintrc.cjs b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/.eslintrc.cjs new file mode 100644 index 0000000..a231897 --- /dev/null +++ b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/.eslintrc.cjs @@ -0,0 +1,8 @@ +module.exports = { + ignorePatterns: ['dist', '.eslintrc.cjs'], + extends: ['../../.eslintrc.js', 'plugin:react-hooks/recommended'], + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], + }, +} diff --git a/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/.gitignore b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/README.md b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/README.md new file mode 100644 index 0000000..b3abe0e --- /dev/null +++ b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/README.md @@ -0,0 +1,36 @@ +# Passkeys Safe Owner + +This minimalistic example application demonstrates a Safe Account deployment leveraging 4337 and Passkeys. It uses "WebAuthnSigner" as owners to validate webauth transactions. During account initialization, it uses the singleton contract that stores the signer publickey to the account storage directly to avoid 4337 storage restrictions, then it replaces it with a "WebAuthnSigner". + +"WebAuthnSigner" addresses are deterministic, we created a proxy and a proxy factory for this purpose (Almost the same proxy and factory as Safe's ). + +## Install dependencies + +**Change Directory**: Go to path containing the main abstractionkit library in the webauthn branch. +Install, build, and establish a symbolic link for the for the package. + +```bash +npm install +npm run build +npm link +``` + +Now that the library is linked, you can return to the main example path here and connect locally installed abstractionkit. The command below will install the rest of the dependencies as well + +```bash +npm link abstractionkit +``` + +## Fill in the environment variables + +```bash +cp .env.example .env +``` + +and fill in the variables in `.env` file. + +### Run the app in development mode + +```bash +npm run dev +``` \ No newline at end of file diff --git a/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/index.html b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/index.html new file mode 100644 index 0000000..6fbf0e5 --- /dev/null +++ b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/index.html @@ -0,0 +1,13 @@ + + + + + + + Candide & Safe Account Abstraction Passkeys + + +
+ + + diff --git a/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/package-lock.json b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/package-lock.json new file mode 100644 index 0000000..9ab1dfd --- /dev/null +++ b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/package-lock.json @@ -0,0 +1,1296 @@ +{ + "name": "safe-candide-passkeys", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "safe-candide-passkeys", + "version": "0.0.0", + "dependencies": { + "abstractionkit": "^0.1.3", + "buffer": "^6.0.3", + "ethers": "^6.10.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.48", + "@types/react-dom": "^18.2.18", + "@vitejs/plugin-react-swc": "^3.5.0", + "typescript": "^5.3.3", + "vite": "^5.0.12" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", + "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz", + "integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz", + "integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz", + "integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz", + "integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz", + "integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz", + "integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz", + "integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz", + "integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz", + "integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz", + "integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz", + "integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz", + "integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz", + "integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@swc/core": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.4.11.tgz", + "integrity": "sha512-WKEakMZxkVwRdgMN4AMJ9K5nysY8g8npgQPczmjBeNK5In7QEAZAJwnyccrWwJZU0XjVeHn2uj+XbOKdDW17rg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@swc/counter": "^0.1.2", + "@swc/types": "^0.1.5" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.4.11", + "@swc/core-darwin-x64": "1.4.11", + "@swc/core-linux-arm-gnueabihf": "1.4.11", + "@swc/core-linux-arm64-gnu": "1.4.11", + "@swc/core-linux-arm64-musl": "1.4.11", + "@swc/core-linux-x64-gnu": "1.4.11", + "@swc/core-linux-x64-musl": "1.4.11", + "@swc/core-win32-arm64-msvc": "1.4.11", + "@swc/core-win32-ia32-msvc": "1.4.11", + "@swc/core-win32-x64-msvc": "1.4.11" + }, + "peerDependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.4.11.tgz", + "integrity": "sha512-C1j1Qp/IHSelVWdEnT7f0iONWxQz6FAqzjCF2iaL+0vFg4V5f2nlgrueY8vj5pNNzSGhrAlxsMxEIp4dj1MXkg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.4.11.tgz", + "integrity": "sha512-0TTy3Ni8ncgaMCchSQ7FK8ZXQLlamy0FXmGWbR58c+pVZWYZltYPTmheJUvVcR0H2+gPAymRKyfC0iLszDALjg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.4.11.tgz", + "integrity": "sha512-XJLB71uw0rog4DjYAPxFGAuGCBQpgJDlPZZK6MTmZOvI/1t0+DelJ24IjHIxk500YYM26Yv47xPabqFPD7I2zQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.4.11.tgz", + "integrity": "sha512-vYQwzJvm/iu052d5Iw27UFALIN5xSrGkPZXxLNMHPySVko2QMNNBv35HLatkEQHbQ3X+VKSW9J9SkdtAvAVRAQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.4.11.tgz", + "integrity": "sha512-eV+KduiRYUFjPsvbZuJ9aknQH9Tj0U2/G9oIZSzLx/18WsYi+upzHbgxmIIHJ2VJgfd7nN40RI/hMtxNsUzR/g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.4.11.tgz", + "integrity": "sha512-WA1iGXZ2HpqM1OR9VCQZJ8sQ1KP2or9O4bO8vWZo6HZJIeoQSo7aa9waaCLRpkZvkng1ct/TF/l6ymqSNFXIzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.4.11.tgz", + "integrity": "sha512-UkVJToKf0owwQYRnGvjHAeYVDfeimCEcx0VQSbJoN7Iy0ckRZi7YPlmWJU31xtKvikE2bQWCOVe0qbSDqqcWXA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.4.11.tgz", + "integrity": "sha512-35khwkyly7lF5NDSyvIrukBMzxPorgc5iTSDfVO/LvnmN5+fm4lTlrDr4tUfTdOhv3Emy7CsKlsNAeFRJ+Pm+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.4.11.tgz", + "integrity": "sha512-Wx8/6f0ufgQF2pbVPsJ2dAmFLwIOW+xBE5fxnb7VnEbGkTgP1qMDWiiAtD9rtvDSuODG3i1AEmAak/2HAc6i6A==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.4.11.tgz", + "integrity": "sha512-0xRFW6K9UZQH2NVC/0pVB0GJXS45lY24f+6XaPBF1YnMHd8A8GoHl7ugyM5yNUTe2AKhSgk5fJV00EJt/XBtdQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true + }, + "node_modules/@swc/types": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.6.tgz", + "integrity": "sha512-/JLo/l2JsT/LRd80C3HfbmVpxOAJ11FO2RCEslFrgzLltoP9j8XIbsyDcfCt2WWyX+CM96rBoNM+IToAkFOugg==", + "dev": true, + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.15.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz", + "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.12", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", + "dev": true + }, + "node_modules/@types/react": { + "version": "18.2.72", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.72.tgz", + "integrity": "sha512-/e7GWxGzXQF7OJAua7UAYqYi/4VpXEfbGtmYQcAQwP3SjjjAXfybTf/JK5S+SaetB/ChXl8Y2g1hCsj7jDXxcg==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.2.22", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.22.tgz", + "integrity": "sha512-fHkBXPeNtfvri6gdsMYyW+dW7RXFo6Ad09nLFK0VQWR7yGLai/Cyvyj696gbwYvBnhGtevUG9cET0pmUbMtoPQ==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@vitejs/plugin-react-swc": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.6.0.tgz", + "integrity": "sha512-XFRbsGgpGxGzEV5i5+vRiro1bwcIaZDIdBRP16qwm+jP68ue/S8FJTBEgOeojtVDYrbSua3XFp71kC8VJE6v+g==", + "dev": true, + "dependencies": { + "@swc/core": "^1.3.107" + }, + "peerDependencies": { + "vite": "^4 || ^5" + } + }, + "node_modules/abstractionkit": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/abstractionkit/-/abstractionkit-0.1.3.tgz", + "integrity": "sha512-jCj6UuUpJl1EBY8S0cLNLyfylrFE6zJ/JztyUJuodeJEmYoWwV3oYkuDHjUQCdQCKLXKB6lhRPtnRh3BMgztfg==", + "dependencies": { + "ethers": "^6.6.4", + "isomorphic-unfetch": "^3.1.0" + } + }, + "node_modules/aes-js": { + "version": "4.0.0-beta.5", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", + "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "node_modules/ethers": { + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.11.1.tgz", + "integrity": "sha512-mxTAE6wqJQAbp5QAe/+o+rXOID7Nw91OZXvgpjDa1r4fAbq2Nu314oEZSbjoRLacuCzs7kUC3clEvkCQowffGg==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/ethers-io/" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@adraffy/ens-normalize": "1.10.1", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.2", + "@types/node": "18.15.13", + "aes-js": "4.0.0-beta.5", + "tslib": "2.4.0", + "ws": "8.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/isomorphic-unfetch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz", + "integrity": "sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==", + "dependencies": { + "node-fetch": "^2.6.1", + "unfetch": "^4.2.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/rollup": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz", + "integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.13.0", + "@rollup/rollup-android-arm64": "4.13.0", + "@rollup/rollup-darwin-arm64": "4.13.0", + "@rollup/rollup-darwin-x64": "4.13.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.13.0", + "@rollup/rollup-linux-arm64-gnu": "4.13.0", + "@rollup/rollup-linux-arm64-musl": "4.13.0", + "@rollup/rollup-linux-riscv64-gnu": "4.13.0", + "@rollup/rollup-linux-x64-gnu": "4.13.0", + "@rollup/rollup-linux-x64-musl": "4.13.0", + "@rollup/rollup-win32-arm64-msvc": "4.13.0", + "@rollup/rollup-win32-ia32-msvc": "4.13.0", + "@rollup/rollup-win32-x64-msvc": "4.13.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "node_modules/typescript": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", + "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unfetch": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", + "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==" + }, + "node_modules/vite": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.6.tgz", + "integrity": "sha512-FPtnxFlSIKYjZ2eosBQamz4CbyrTizbZ3hnGJlh/wMtCrlp1Hah6AzBLjGI5I2urTfNnpovpHdrL6YRuBOPnCA==", + "dev": true, + "dependencies": { + "esbuild": "^0.20.1", + "postcss": "^8.4.36", + "rollup": "^4.13.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/ws": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", + "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/package.json b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/package.json new file mode 100644 index 0000000..57bdb2a --- /dev/null +++ b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/package.json @@ -0,0 +1,26 @@ +{ + "name": "safe-candide-passkeys", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "abstractionkit": "^0.1.3", + "buffer": "^6.0.3", + "ethers": "^6.10.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.48", + "@types/react-dom": "^18.2.18", + "@vitejs/plugin-react-swc": "^3.5.0", + "typescript": "^5.3.3", + "vite": "^5.0.12" + } +} diff --git a/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/public/candide-atelier-logo.svg b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/public/candide-atelier-logo.svg new file mode 100644 index 0000000..7a94c43 --- /dev/null +++ b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/public/candide-atelier-logo.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/public/safe-logo-white.svg b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/public/safe-logo-white.svg new file mode 100644 index 0000000..c817daa --- /dev/null +++ b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/public/safe-logo-white.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/App.css b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/App.css new file mode 100644 index 0000000..7628d1d --- /dev/null +++ b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/App.css @@ -0,0 +1,27 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; +} + +.header { + display: flex; + align-items: center; + justify-content: space-between; +} + +.logo { + height: 5em; + will-change: filter; + transition: filter 300ms; + + @media screen and (max-width: 768px) { + height: 3em; + } +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} diff --git a/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/App.tsx b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/App.tsx new file mode 100644 index 0000000..2b29b45 --- /dev/null +++ b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/App.tsx @@ -0,0 +1,71 @@ +import safeLogo from "/safe-logo-white.svg"; +import candideLogo from "/candide-atelier-logo.svg"; +import { + PasskeyLocalStorageFormat, + createPasskey, + toLocalStorageFormat, +} from "./logic/passkeys.ts"; +import "./App.css"; +import { useLocalStorageState } from "./hooks/useLocalStorageState.ts"; +import { useState } from "react"; +import { PasskeyCard } from "./components/PasskeyCard.tsx"; +import { SafeCard } from "./components/SafeCard.tsx"; + +const PASSKEY_LOCALSTORAGE_KEY = "passkeyId"; + +function App() { + const [passkey, setPasskey] = useLocalStorageState< + PasskeyLocalStorageFormat | undefined + >(PASSKEY_LOCALSTORAGE_KEY, undefined); + const [error, setError] = useState(); + + const handleCreatePasskeyClick = async () => { + setError(undefined); + try { + const passkey = await createPasskey(); + + setPasskey(toLocalStorageFormat(passkey)); + } catch (error) { + if (error instanceof Error) { + setError(error.message); + } else { + setError("Unknown error"); + } + } + }; + + let content = ( + <> + + + {passkey && } + + {error && ( +
+

Error: {error}

+
+ )} + + ); + + return ( + <> +
+ + Safe logo + + + Safe logo + +
+

Passkeys Demo

+ + {content} + + ); +} + +export default App; diff --git a/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/components/PasskeyCard.tsx b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/components/PasskeyCard.tsx new file mode 100644 index 0000000..6ec0be2 --- /dev/null +++ b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/components/PasskeyCard.tsx @@ -0,0 +1,34 @@ +import { useMemo } from 'react' +import { SafeAccountWebAuth as SafeAccount, WebauthPublicKey } from 'abstractionkit' + +import { PasskeyLocalStorageFormat } from '../logic/passkeys' +import { setItem } from '../logic/storage' + +function PasskeyCard({ passkey, handleCreatePasskeyClick }: { passkey?: PasskeyLocalStorageFormat; handleCreatePasskeyClick: () => void }) { + const getAccountAddress = useMemo(() => { + if (!passkey) return undefined + + const webauthPublicKey: WebauthPublicKey = { + x: BigInt(passkey.pubkeyCoordinates.x), + y: BigInt(passkey.pubkeyCoordinates.y), + } + + const smartAccount = SafeAccount.initializeNewAccount([webauthPublicKey]) + + setItem('accountAddress', smartAccount.accountAddress) + return smartAccount.accountAddress + }, [passkey]) + + return passkey ? ( +
+

Account Address: {getAccountAddress}

+
+ ) : ( +
+

First, you need to create a passkey which will be used to sign transactions

+ +
+ ) +} + +export { PasskeyCard } diff --git a/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/components/SafeCard.tsx b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/components/SafeCard.tsx new file mode 100644 index 0000000..e747880 --- /dev/null +++ b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/components/SafeCard.tsx @@ -0,0 +1,170 @@ +import { useEffect, useState } from "react"; +import { + SafeAccountWebAuth as SafeAccount, + getFunctionSelector, + createCallData, + MetaTransaction, + DummySignature, + CandidePaymaster, + WebauthPublicKey, +} from "abstractionkit"; + +import { PasskeyLocalStorageFormat } from "../logic/passkeys"; +import { signAndSendUserOp } from "../logic/userOp"; +import { getItem } from "../logic/storage"; +import { JsonRpcProvider } from "ethers"; + +const jsonRPCProvider = import.meta.env.VITE_JSON_RPC_PROVIDER; +const bundlerUrl = import.meta.env.VITE_BUNDLER_URL; +const paymasterUrl = import.meta.env.VITE_PAYMASTER_URL; +const entrypoint = import.meta.env.VITE_ENTRYPOINT_ADDRESS; +const chainId = import.meta.env.VITE_CHAIN_ID; +const chainName = import.meta.env.VITE_CHAIN_NAME as string; + +function SafeCard({ passkey }: { passkey: PasskeyLocalStorageFormat }) { + const [userOpHash, setUserOpHash] = useState(); + const [deployed, setDeployed] = useState(false); + const [loadingTx, setLoadingTx] = useState(false); + const [error, setError] = useState(); + const [txHash, setTxHash] = useState(); + + const accountAddress = getItem("accountAddress") as string; + const provider = new JsonRpcProvider(import.meta.env.VITE_JSON_RPC_PROVIDER); + + const isDeployed = async () => { + const safeCode = await provider.getCode(accountAddress); + setDeployed(safeCode !== "0x"); + }; + + const handleDeploySafeClick = async () => { + setLoadingTx(true); + // mint an NFT + const nftContractAddress = "0x9a7af758aE5d7B6aAE84fe4C5Ba67c041dFE5336"; + const mintFunctionSignature = "mint(address)"; + const mintFunctionSelector = getFunctionSelector(mintFunctionSignature); + const mintTransactionCallData = createCallData( + mintFunctionSelector, + ["address"], + [accountAddress], + ); + const mintTransaction: MetaTransaction = { + to: nftContractAddress, + value: 0n, + data: mintTransactionCallData, + }; + + const webauthPublicKey: WebauthPublicKey = { + x: BigInt(passkey.pubkeyCoordinates.x), + y: BigInt(passkey.pubkeyCoordinates.y), + }; + + const safeAccount = SafeAccount.initializeNewAccount([webauthPublicKey]); + + let userOperation = await safeAccount.createUserOperation( + [mintTransaction], + jsonRPCProvider, + bundlerUrl, + { + dummySingatures: [DummySignature.webauth], + }, + ); + + let paymaster: CandidePaymaster = new CandidePaymaster(paymasterUrl); + userOperation = await paymaster.createSponsorPaymasterUserOperation( + userOperation, + bundlerUrl, + ); + setLoadingTx(false); + try { + const bundlerResponse = await signAndSendUserOp( + safeAccount, + userOperation, + passkey, + entrypoint, + chainId, + ); + setUserOpHash(bundlerResponse.userOperationHash); + let userOperationReceiptResult = await bundlerResponse.included(); + if (userOperationReceiptResult.success) { + setTxHash(userOperationReceiptResult.receipt.transactionHash); + console.log( + "Two Nfts were minted. The transaction hash is : " + + userOperationReceiptResult.receipt.transactionHash, + ); + } else { + setError("Useroperation execution failed"); + } + } catch (error) { + if (error instanceof Error) { + setError(error.message); + } else { + setError("Unknown error"); + } + } + }; + + const readyToDeploy = !userOpHash && !deployed; + + useEffect(() => { + if (accountAddress) { + async function isAccountDeployed() { + await isDeployed(); + } + isAccountDeployed(); + } + }, [deployed, accountAddress]); + + return ( +
+ {userOpHash && ( +

+ Your Safe is being deployed. Track the user operation on{" "} + + jiffyscan explorer + +

+ )} + {(deployed || txHash) && ( +

+ You deployed a Safe Account and collected an NFT, secured with your + Device Passkeys +
+
+ View more on{" "} + {txHash ? ( + + etherscan + + ) : ( + + etherscan + + )} +

+ )} + {loadingTx ? ( +

"Preparing transaction.."

+ ) : ( + readyToDeploy && ( + + ) + )}{" "} + {error && ( +
+

Error: {error}

+
+ )} +
+ ); +} + +export { SafeCard }; diff --git a/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/hooks/useLocalStorageState.ts b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/hooks/useLocalStorageState.ts new file mode 100644 index 0000000..06682fc --- /dev/null +++ b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/hooks/useLocalStorageState.ts @@ -0,0 +1,35 @@ +import { useEffect, useState } from 'react' +import { setItem, getItem } from '../logic/storage.ts' + +/** + * Custom hook that manages state in local storage. + * + * @template T - The type of the state value. + * @param {string} key - The key used to store the state value in local storage. + * @param {T} initialValue - The initial value of the state. + * @returns {[T, React.Dispatch>]} - An array containing the current state value and a function to update the state. + */ +function useLocalStorageState(key: string, initialValue: T): [T, React.Dispatch>] { + const [state, setState] = useState(() => { + const storedValue = getItem(key) + + // this naive hook might write 'undefined' or 'null' to local storage as a string + if (storedValue && storedValue !== 'undefined' && storedValue !== 'null') { + try { + return JSON.parse(storedValue) as T + } catch { + // trick eslint with a no-op + } + } + + return initialValue + }) + + useEffect(() => { + setItem(key, state) + }, [key, state]) + + return [state, setState] +} + +export { useLocalStorageState } diff --git a/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/index.css b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/index.css new file mode 100644 index 0000000..dd0c354 --- /dev/null +++ b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/index.css @@ -0,0 +1,72 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + justify-content: flex-start; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; + + @media screen and (max-width: 768px) { + font-size: 2.4em; + } +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/logic/passkeys.ts b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/logic/passkeys.ts new file mode 100644 index 0000000..aae4851 --- /dev/null +++ b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/logic/passkeys.ts @@ -0,0 +1,116 @@ +import { Buffer } from 'buffer' + +type PasskeyCredential = { + id: 'string' + rawId: ArrayBuffer + response: { + clientDataJSON: ArrayBuffer + attestationObject: ArrayBuffer + getPublicKey(): ArrayBuffer + } + type: 'public-key' +} + +type PasskeyCredentialWithPubkeyCoordinates = PasskeyCredential & { + pubkeyCoordinates: { + x: string + y: string + } +} + +/** + * Creates a passkey for signing. + * + * @returns A promise that resolves to a PasskeyCredentialWithPubkeyCoordinates object, which includes the passkey credential information and its public key coordinates. + * @throws Throws an error if the passkey generation fails or if the credential received is null. + */ +async function createPasskey(): Promise { + // Generate a passkey credential using WebAuthn API + const passkeyCredential = (await navigator.credentials.create({ + publicKey: { + pubKeyCredParams: [ + { + // ECDSA w/ SHA-256: https://datatracker.ietf.org/doc/html/rfc8152#section-8.1 + alg: -7, + type: 'public-key', + }, + ], + challenge: crypto.getRandomValues(new Uint8Array(32)), + rp: { + name: 'Safe Wallet', + }, + user: { + displayName: 'Safe Owner', + id: crypto.getRandomValues(new Uint8Array(32)), + name: 'safe-owner', + }, + timeout: 60000, + attestation: 'none', + }, + })) as PasskeyCredential | null + + if (!passkeyCredential) { + throw new Error('Failed to generate passkey. Received null as a credential') + } + + // Import the public key to later export it to get the XY coordinates + const key = await crypto.subtle.importKey( + 'spki', + passkeyCredential.response.getPublicKey(), + { + name: 'ECDSA', + namedCurve: 'P-256', + hash: { name: 'SHA-256' }, + }, + true, // boolean that marks the key as an exportable one + ['verify'], + ) + + // Export the public key in JWK format and extract XY coordinates + const exportedKeyWithXYCoordinates = await crypto.subtle.exportKey('jwk', key) + if (!exportedKeyWithXYCoordinates.x || !exportedKeyWithXYCoordinates.y) { + throw new Error('Failed to retrieve x and y coordinates') + } + + // Create a PasskeyCredentialWithPubkeyCoordinates object + const passkeyWithCoordinates: PasskeyCredentialWithPubkeyCoordinates = Object.assign(passkeyCredential, { + pubkeyCoordinates: { + x: '0x' + Buffer.from(exportedKeyWithXYCoordinates.x, 'base64').toString('hex'), + y: '0x' + Buffer.from(exportedKeyWithXYCoordinates.y, 'base64').toString('hex'), + }, + }) + + return passkeyWithCoordinates +} + +export type PasskeyLocalStorageFormat = { + rawId: string + pubkeyCoordinates: { + x: string + y: string + } +} + +/** + * Converts a PasskeyCredentialWithPubkeyCoordinates object to a format that can be stored in the local storage. + * The rawId is required for signing and pubkey coordinates are for our convenience. + * @param passkey - The passkey to be converted. + * @returns The passkey in a format that can be stored in the local storage. + */ +function toLocalStorageFormat(passkey: PasskeyCredentialWithPubkeyCoordinates): PasskeyLocalStorageFormat { + return { + rawId: Buffer.from(passkey.rawId).toString('hex'), + pubkeyCoordinates: passkey.pubkeyCoordinates, + } +} + +/** + * Checks if the provided value is in the format of a Local Storage Passkey. + * @param x The value to check. + * @returns A boolean indicating whether the value is in the format of a Local Storage Passkey. + */ +function isLocalStoragePasskey(x: unknown): x is PasskeyLocalStorageFormat { + return typeof x === 'object' && x !== null && 'rawId' in x && 'pubkeyCoordinates' in x +} + +export { createPasskey, toLocalStorageFormat, isLocalStoragePasskey } diff --git a/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/logic/storage.ts b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/logic/storage.ts new file mode 100644 index 0000000..8535db3 --- /dev/null +++ b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/logic/storage.ts @@ -0,0 +1,26 @@ +/** + * Sets an item in the local storage. + * @param key - The key to set the item with. + * @param value - The value to be stored. It will be converted to a string using JSON.stringify. + * @template T - The type of the value being stored. + */ +function setItem(key: string, value: T) { + // to prevent silly mistakes with double stringifying + if (typeof value === 'string') { + localStorage.setItem(key, value) + } else { + localStorage.setItem(key, JSON.stringify(value)) + } +} + +/** + * Retrieves the value associated with the specified key from the local storage. + * + * @param key - The key of the item to retrieve. + * @returns The value associated with the key, or null if the key does not exist. + */ +function getItem(key: string): string | null { + return localStorage.getItem(key) +} + +export { setItem, getItem } diff --git a/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/logic/userOp.ts b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/logic/userOp.ts new file mode 100644 index 0000000..4828cfb --- /dev/null +++ b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/logic/userOp.ts @@ -0,0 +1,130 @@ +import { ethers } from 'ethers' +import { + SafeAccountWebAuth as SafeAccount, + SignerSignaturePair, + WebauthSignatureData, + SendUseroperationResponse, + UserOperation, + WebauthPublicKey, +} from 'abstractionkit' + +import { PasskeyLocalStorageFormat } from './passkeys' +import { hexStringToUint8Array } from '../utils' + +/** + * Compute the additional client data JSON fields. This is the fields other than `type` and + * `challenge` (including `origin` and any other additional client data fields that may be + * added by the authenticator). + * + * See + */ +function extractClientDataFields(response: AuthenticatorAssertionResponse): string { + const clientDataJSON = new TextDecoder('utf-8').decode(response.clientDataJSON) + const match = clientDataJSON.match(/^\{"type":"webauthn.get","challenge":"[A-Za-z0-9\-_]{43}",(.*)\}$/) + + if (!match) { + throw new Error('challenge not found in client data JSON') + } + + const [, fields] = match + return ethers.hexlify(ethers.toUtf8Bytes(fields)) +} + +/** + * Extracts the signature into R and S values from the authenticator response. + * + * See: + * - + * - + */ +function extractSignature(response: AuthenticatorAssertionResponse): [bigint, bigint] { + const check = (x: boolean) => { + if (!x) { + throw new Error('invalid signature encoding') + } + } + + // Decode the DER signature. Note that we assume that all lengths fit into 8-bit integers, + // which is true for the kinds of signatures we are decoding but generally false. I.e. this + // code should not be used in any serious application. + const view = new DataView(response.signature) + + // check that the sequence header is valid + check(view.getUint8(0) === 0x30) + check(view.getUint8(1) === view.byteLength - 2) + + // read r and s + const readInt = (offset: number) => { + check(view.getUint8(offset) === 0x02) + const len = view.getUint8(offset + 1) + const start = offset + 2 + const end = start + len + const n = BigInt(ethers.hexlify(new Uint8Array(view.buffer.slice(start, end)))) + check(n < ethers.MaxUint256) + return [n, end] as const + } + const [r, sOffset] = readInt(2) + const [s] = readInt(sOffset) + + return [r, s] +} + +type Assertion = { + response: AuthenticatorAssertionResponse +} + +/** + * Signs and sends a user operation to the specified entry point on the blockchain. + * @param userOp The unsigned user operation to sign and send. + * @param passkey The passkey used for signing the user operation. + * @param entryPoint The entry point address on the blockchain. Defaults to ENTRYPOINT_ADDRESS if not provided. + * @param chainId The chain ID of the blockchain. Defaults to APP_CHAIN_ID if not provided. + * @returns User Operation hash promise. + * @throws An error if signing the user operation fails. + */ +async function signAndSendUserOp( + smartAccount: SafeAccount, + userOp: UserOperation, + passkey: PasskeyLocalStorageFormat, + entryPoint: string = import.meta.env.VITE_ENTRYPOINT_ADDRESS, + chainId: ethers.BigNumberish = import.meta.env.VITE_CHAIN_ID, + bundlerUrl: string = import.meta.env.VITE_BUNDLER_URL, +): Promise { + const safeInitOpHash = SafeAccount.getUserOperationEip712Hash(userOp, BigInt(chainId), 0n, 0n, entryPoint) + + const assertion = (await navigator.credentials.get({ + publicKey: { + challenge: ethers.getBytes(safeInitOpHash), + allowCredentials: [{ type: 'public-key', id: hexStringToUint8Array(passkey.rawId) }], + }, + })) as Assertion | null + + if (!assertion) { + throw new Error('Failed to sign user operation') + } + + const webauthSignatureData: WebauthSignatureData = { + authenticatorData: assertion.response.authenticatorData, + clientDataFields: extractClientDataFields(assertion.response), + rs: extractSignature(assertion.response), + } + + const webauthSignature: string = SafeAccount.createWebAuthnSignature(webauthSignatureData) + + const webauthPublicKey: WebauthPublicKey = { + x: BigInt(passkey.pubkeyCoordinates.x), + y: BigInt(passkey.pubkeyCoordinates.y), + } + + const SignerSignaturePair: SignerSignaturePair = { + signer: webauthPublicKey, + signature: webauthSignature, + } + + userOp.signature = SafeAccount.formatSignaturesToUseroperationSignature([SignerSignaturePair], userOp.nonce == 0n) + + console.log(userOp, "userOp"); + return await smartAccount.sendUserOperation(userOp, bundlerUrl) +} + +export { signAndSendUserOp } diff --git a/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/main.tsx b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/main.tsx new file mode 100644 index 0000000..cc88e88 --- /dev/null +++ b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/main.tsx @@ -0,0 +1,18 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App.tsx' +// Keep the import of the wallets file to eval so w3m package can initialise +// the required globals +import './index.css' + +const rootElement = document.getElementById('root') + +if (!rootElement) { + throw new Error('Root element not found') +} + +ReactDOM.createRoot(rootElement).render( + + + , +) diff --git a/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/utils.ts b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/utils.ts new file mode 100644 index 0000000..7de5733 --- /dev/null +++ b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/utils.ts @@ -0,0 +1,15 @@ +/** + * Converts a hexadecimal string to a Uint8Array. + * + * @param hexString The hexadecimal string to convert. + * @returns The Uint8Array representation of the hexadecimal string. + */ +function hexStringToUint8Array(hexString: string): Uint8Array { + const arr = [] + for (let i = 0; i < hexString.length; i += 2) { + arr.push(parseInt(hexString.substr(i, 2), 16)) + } + return new Uint8Array(arr) +} + +export { hexStringToUint8Array } diff --git a/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/vite-env.d.ts b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/tsconfig.json b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/tsconfig.json new file mode 100644 index 0000000..a7fc6fb --- /dev/null +++ b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/tsconfig.node.json b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/tsconfig.node.json new file mode 100644 index 0000000..42872c5 --- /dev/null +++ b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/vite.config.ts b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/vite.config.ts new file mode 100644 index 0000000..d0dbee0 --- /dev/null +++ b/examples/SafeAccountExamples/PasskeysCreateAccountandSendTransaction/vite.config.ts @@ -0,0 +1,18 @@ +import { defineConfig, loadEnv } from 'vite' +import react from '@vitejs/plugin-react-swc' + +const REQUIRED_ENV_VARS = ['VITE_CHAIN_ID', 'VITE_BUNDLER_URL', 'VITE_JSON_RPC_PROVIDER', 'VITE_PAYMASTER_URL'] + +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, process.cwd()) + + for (const key of REQUIRED_ENV_VARS) { + if (!env[key]) { + throw new Error(`Environment variable ${key} is missing`) + } + } + + return { + plugins: [react()], + } +})