diff --git a/.changeset/shiny-knives-chew.md b/.changeset/shiny-knives-chew.md new file mode 100644 index 00000000..57175c31 --- /dev/null +++ b/.changeset/shiny-knives-chew.md @@ -0,0 +1,15 @@ +--- +"porto": patch +--- + +**Breaking:** Renamed APIs: + +- JSON-RPC Methods: + - `experimental_grantSession` → `experimental_authorizeKey` + - `experimental_importAccount` → `experimental_createAccount` + - `experimental_prepareImportAccount` → `experimental_prepareCreateAccount` + - `experimental_sessions` → `experimental_keys` +- Capabilities: + - `grantSession` → `authorizeKey` + - `sessions` → `keys` + diff --git a/.changeset/shy-trainers-taste.md b/.changeset/shy-trainers-taste.md new file mode 100644 index 00000000..8c148d31 --- /dev/null +++ b/.changeset/shy-trainers-taste.md @@ -0,0 +1,5 @@ +--- +"porto": patch +--- + +**Breaking:** Migrated to new [Account contracts](https://github.com/ithacaxyz/account). diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..8ae5a5bc --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +VITE_ANVIL_FORK_URL= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0833657b..5e7ae134 100644 --- a/.gitignore +++ b/.gitignore @@ -39,5 +39,4 @@ src/_generated out # vercel -.vercel - +.vercel \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index df5dbe5d..788ad1d1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "contracts/lib/forge-std"] - path = contracts/lib/forge-std - url = https://github.com/foundry-rs/forge-std -[submodule "contracts/lib/solady"] - path = contracts/lib/solady - url = https://github.com/Vectorized/solady +[submodule "contracts"] + path = contracts + url = git@github.com:ithacaxyz/account.git diff --git a/README.md b/README.md index b603c6c6..90cac233 100644 --- a/README.md +++ b/README.md @@ -34,17 +34,15 @@ Experimental Next-gen Account for Ethereum. - [Usage](#usage) - [Usage with Wagmi](#usage-with-wagmi) - [JSON-RPC Reference](#json-rpc-reference) - - [`experimental_connect`](#experimental_connect) + - [`experimental_authorizeKey`](#experimental_authorizeKey) - [`experimental_createAccount`](#experimental_createaccount) - - [`experimental_disconnect`](#experimental_disconnect) - - [`experimental_grantSession`](#experimental_grantsession) - - [`experimental_prepareImportAccount`](#experimental_prepareImportAccount) - - [`experimental_importAccount`](#experimental_importAccount) - - [`experimental_sessions`](#experimental_sessions) + - [`experimental_prepareCreateAccount`](#experimental_prepareCreateAccount) + - [`experimental_keys`](#experimental_keys) + - [`experimental_revokeKey`](#experimental_revokeKey) - [Available ERC-5792 Capabilities](#available-erc-5792-capabilities) - [`atomicBatch`](#atomicbatch) - [`createAccount`](#createaccount) - - [`sessions`](#sessions) + - [`keys`](#keys) - [Wagmi Reference](#wagmi-reference) - [Development](#development) - [License](#license) @@ -64,9 +62,8 @@ import { Porto } from 'porto' const porto = Porto.create() -const account = await porto.provider.request({ - method: 'experimental_connect', - params: [{ capabilities: { grantSession: true } }] +const { accounts } = await porto.provider.request({ + method: 'wallet_connect' }) ``` @@ -114,8 +111,7 @@ function Connect() { {accountData &&
{JSON.stringify(accountData, null, 2)}
}

@@ -152,10 +168,10 @@ function ImportAccount() { {connectors @@ -164,19 +180,19 @@ function ImportAccount() { ))} -
{importAccount.status}
-
{importAccount.error?.message}
+
{upgradeAccount.status}
+
{upgradeAccount.error?.message}
) } @@ -201,21 +217,21 @@ function Balance() { ) } -function GrantSession() { - const sessions = Hooks.useSessions() - const grantSession = Hooks.useGrantSession() +function AuthorizeKey() { + const keys = Hooks.useKeys() + const authorizeKey = Hooks.useAuthorizeKey() - if (sessions.data?.length !== 0) return null + if (keys.data?.length !== 0) return null return (
-

Grant Session

- - {grantSession.data &&
Session granted.
} - {grantSession.error && ( + {authorizeKey.data &&
Key authorized.
} + {authorizeKey.error && (
- Error: {grantSession.error.shortMessage || grantSession.error.message} + Error: {authorizeKey.error.shortMessage || authorizeKey.error.message}
)}
diff --git a/examples/wagmi/src/main.tsx b/examples/wagmi/src/main.tsx index 0d8ef9a0..6c78c466 100644 --- a/examples/wagmi/src/main.tsx +++ b/examples/wagmi/src/main.tsx @@ -3,8 +3,8 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import { WagmiProvider } from 'wagmi' -import { App } from './App' -import { wagmiConfig } from './config' +import { App } from './App.js' +import { wagmiConfig } from './config.js' import './main.css' diff --git a/package.json b/package.json index c30d5a20..85c5f3b7 100644 --- a/package.json +++ b/package.json @@ -19,10 +19,12 @@ "dev": "pnpm preconstruct && pnpm --filter playground dev", "dev:wagmi": "pnpm preconstruct && pnpm --filter wagmi-example dev", "format": "biome format --write", - "postinstall": "pnpm preconstruct", + "postinstall": "git submodule update --init --recursive && pnpm preconstruct", "preconstruct": "tsx ./scripts/preconstruct.ts", "preinstall": "pnpx only-allow pnpm", - "prepare": "pnpm simple-git-hooks" + "prepare": "pnpm simple-git-hooks", + "test": "vitest -c ./test/vitest.config.ts", + "test:ci": "CI=true vitest -c ./test/vitest.config.ts --retry=3 --bail=1" }, "devDependencies": { "@arethetypeswrong/cli": "^0.17.1", @@ -33,8 +35,11 @@ "@tanstack/react-query": "catalog:", "@types/node": "^22.5.4", "@types/react": "catalog:", + "@vitest/coverage-v8": "2.1.8", "@wagmi/cli": "^2.1.18", "knip": "^5.30.6", + "ox": "^0.6.0", + "prool": "^0.0.16", "publint": "^0.2.12", "sherif": "^0.11.0", "simple-git-hooks": "^2.11.1", @@ -42,11 +47,12 @@ "tsx": "^4.17.0", "typescript": "catalog:", "viem": "catalog:", + "vitest": "^2.1.8", "wagmi": "catalog:" }, - "packageManager": "pnpm@9.7.0", + "packageManager": "pnpm@9.15.0", "engines": { - "node": ">=22" + "node": ">=22.5" }, "simple-git-hooks": { "pre-commit": "pnpm check" @@ -54,7 +60,11 @@ "knip": { "entry": ["src/{index,wagmi/index}.ts!"], "project": ["src/**"], - "ignore": ["**/internal/**", "src/core/internal/generated.ts"], + "ignore": [ + "**/internal/**", + "playground/**", + "src/core/internal/generated.ts" + ], "ignoreDependencies": ["@tanstack/react-query", "react", "wagmi"] }, "size-limit": [ @@ -62,7 +72,7 @@ "name": "import * from 'ox'", "path": "./src/_dist/index.js", "import": "*", - "limit": "54 kB" + "limit": "60 kB" } ] } diff --git a/playground/.gitignore b/playground/.gitignore index a547bf36..7bc756df 100644 --- a/playground/.gitignore +++ b/playground/.gitignore @@ -22,3 +22,5 @@ dist-ssr *.njsproj *.sln *.sw? + +anvil.dump.json \ No newline at end of file diff --git a/playground/anvil.json b/playground/anvil.json new file mode 100644 index 00000000..ff0ea3e2 --- /dev/null +++ b/playground/anvil.json @@ -0,0 +1,431 @@ +{ + "block": { + "number": "0x1", + "coinbase": "0x0000000000000000000000000000000000000000", + "timestamp": "0x677af086", + "gas_limit": "0x1c9c380", + "basefee": "0x1fb38612b", + "difficulty": "0x0", + "prevrandao": "0x3ddabd3dc0745f323e86e08c3a8e73b0550eb34492202657073ced08a84a0e48", + "blob_excess_gas_and_price": { + "excess_blob_gas": 1179648, + "blob_gasprice": 1 + } + }, + "accounts": { + "0x0000000000000000000000000000000000000000": { + "nonce": 0, + "balance": "0x2d88fcf9e6a2b336c7b", + "code": "0x", + "storage": {} + }, + "0x0000000000000000000000000000000000000001": { + "nonce": 0, + "balance": "0x31409d7260fbc7b7c", + "code": "0x", + "storage": {} + }, + "0x0ff027b63351364071425cf65d4fece75a8e17b8": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x6080604052600436106101c5575f3560e01c806384b0196e116100f6578063bf53096911610094578063e9ae5c5311610063578063e9ae5c53146105f9578063f0c60f1a1461060c578063fac750e014610620578063ff619c6b14610634576101cc565b8063bf5309691461054e578063cb4774c41461056d578063cebfe3361461058e578063d03c7914146105ad576101cc565b8063a2e2f930116100d0578063a2e2f930146104d2578063a840fe49146104f1578063b70e36f014610510578063b75c7dc61461052f576101cc565b806384b0196e1461045957806394430fa5146104805780639c42fb59146104a3576101cc565b80632f3f30c711610163578063515c9d6d1161013d578063515c9d6d146103cb57806360d2f33d146103eb5780636ae269cc1461041e5780636fd914541461043a576101cc565b80632f3f30c71461037857806335058501146103925780634223b5c2146103ac576101cc565b8063136a12f71161019f578063136a12f7146102ab5780631626ba7e146102cc5780631a912f3e1461030457806320606b7014610345576101cc565b80630cef73b41461020557806311a86fd61461024057806312aaac701461027f576101cc565b366101cc57005b5f3560e01c63bc197c81811463f23a6e6182141763150b7a02821417156101f757806020526020603cf35b50633c10b94e5f526004601cfd5b348015610210575f80fd5b5061022461021f366004612068565b610653565b6040805192151583526020830191909152015b60405180910390f35b34801561024b575f80fd5b5061026773323232323232323232323232323232323232323281565b6040516001600160a01b039091168152602001610237565b34801561028a575f80fd5b5061029e6102993660046120b0565b61066d565b6040516102379190612109565b3480156102b6575f80fd5b506102ca6102c536600461218b565b61075d565b005b3480156102d7575f80fd5b506102eb6102e6366004612068565b610868565b6040516001600160e01b03199091168152602001610237565b34801561030f575f80fd5b506103377f84fa2cf05cd88e992eae77e851af68a4ee278dcff6ef504e487a55b3baadfbe581565b604051908152602001610237565b348015610350575f80fd5b506103377f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f81565b348015610383575f80fd5b506102eb630707070760e51b81565b34801561039d575f80fd5b506102eb631919191960e11b81565b3480156103b7575f80fd5b5061029e6103c63660046120b0565b61089b565b3480156103d6575f80fd5b506103375f8051602061266f83398151915281565b3480156103f6575f80fd5b506103377fe530e62dece51c9bec26701907051ddc8420a62f028096eeb58263193e84e04981565b348015610429575f80fd5b50686d3d4e7fb92a52381554610337565b348015610445575f80fd5b506103376104543660046121e5565b6108d9565b348015610464575f80fd5b5061046d610a2c565b604051610237979695949392919061225a565b34801561048b575f80fd5b506102676fac830f1181f6aab6862e71edc248941c81565b3480156104ae575f80fd5b506104c26104bd3660046122f0565b610a8d565b6040519015158152602001610237565b3480156104dd575f80fd5b506104c26104ec3660046120b0565b610ad1565b3480156104fc575f80fd5b5061033761050b3660046123e2565b610af9565b34801561051b575f80fd5b506102ca61052a3660046120b0565b610b32565b34801561053a575f80fd5b506102ca6105493660046120b0565b610b5d565b348015610559575f80fd5b506102ca610568366004612491565b610bb2565b348015610578575f80fd5b50610581610c56565b60405161023791906124d0565b348015610599575f80fd5b506103376105a83660046123e2565b610c6f565b3480156105b8575f80fd5b506104c26105c73660046120b0565b690100000000007821000160b09190911c69ffff00000000ffffffff1690811460011b600160481b9190911417151590565b6102ca610607366004612068565b610cd7565b348015610617575f80fd5b50610337610d60565b34801561062b575f80fd5b50610337610df3565b34801561063f575f80fd5b506104c261064e3660046124e2565b610e06565b5f806106618585855f611099565b91509150935093915050565b604080516080810182525f80825260208201819052918101919091526060808201525f828152686d3d4e7fb92a523817602052604081206106ad906112bb565b905080515f036106d05760405163395ed8c160e21b815260040160405180910390fd5b8051600619015f6106e48383016020015190565b60d881901c855260c881901c915060d01c60ff166002811115610709576107096120c7565b8460200190600281111561071f5761071f6120c7565b90816002811115610732576107326120c7565b90525060ff81161515604085015261074f83838151811082025290565b606085015250919392505050565b33301461077c576040516282b42960e81b815260040160405180910390fd5b8361079a57604051638707510560e01b815260040160405180910390fd5b6107a48383611321565b156107cf576107b28461134b565b6107cf576040516303a6f8c760e21b815260040160405180910390fd5b5f82815260188490526004859052603881208152683c149ebf7b8e6c5e226020818152604092839020805485151560ff19909116811790915583518881526001600160a01b038816928101929092526001600160e01b03198616828501526060820152915190917f7eb91b8ac56c0864a4e4f5598082d140d04bed1a4dd62a41d605be2430c494e1919081900360800190a15050505050565b5f806108778585856001611099565b509050806108895763ffffffff61088f565b631626ba7e5b60e01b95945050505050565b604080516080810182525f80825260208201819052918101919091526060808201526108d3610299686d3d4e7fb92a5238168461135f565b92915050565b5f806108f58460408051828152600190920160051b8201905290565b90505f5b848110156109b857368686838181106109145761091461253a565b9050602002810190610926919061254e565b90506109ae8261099f7f84fa2cf05cd88e992eae77e851af68a4ee278dcff6ef504e487a55b3baadfbe561095d602086018661256c565b6001600160a01b0316602086013561098061097b6040890189612587565b6113a8565b6040805194855260208501939093529183015260608201526080902090565b600190910160051b8501528390565b50506001016108f9565b50610a23610a1e7fe530e62dece51c9bec26701907051ddc8420a62f028096eeb58263193e84e0496109f284805160051b60209091012090565b686d3d4e7fb92a5238155460408051938452602084019290925290820187905260608201526080902090565b6113b9565b95945050505050565b600f60f81b6060805f808083610a7b604080518082018252600a8152692232b632b3b0ba34b7b760b11b60208083019190915282518084019093526005835264302e302e3160d81b9083015291565b97989097965046955030945091925090565b5f336fac830f1181f6aab6862e71edc248941c14610abd576040516282b42960e81b815260040160405180910390fd5b610ac88333846114cf565b50600192915050565b600881901c5f908152686d3d4e7fb92a523814602052604081205460ff83161c6001166108d3565b5f6108d382602001516002811115610b1357610b136120c7565b60ff168360600151805190602001205f1c5f9182526020526040902090565b333014610b51576040516282b42960e81b815260040160405180910390fd5b610b5a816114f7565b50565b333014610b7c576040516282b42960e81b815260040160405180910390fd5b610b8581611553565b60405181907fe5af7daed5ab2a2dc5f98d53619f05089c0c14d11a6621f6b906a2366c9a7ab3905f90a250565b333014610bd1576040516282b42960e81b815260040160405180910390fd5b610c1982828080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92019190915250610c1392506112ae915050565b906115a7565b7faec6ef4baadc9acbdf52442522dfffda03abe29adba8d4af611bcef4cbe0c9ad8282604051610c4a9291906125ca565b60405180910390a15050565b6060610c6a686d3d4e7fb92a5238136112bb565b905090565b5f333014610c8f576040516282b42960e81b815260040160405180910390fd5b610c98826115ff565b9050807f3d3a48be5a98628ecf98a6201185102da78bbab8f63a4b2d6b9eef354f5131f583604051610cca9190612109565b60405180910390a2919050565b690100000000007821000160b084901c69ffff00000000ffffffff1690811460011b600160481b9190911417365f818184610d1957637f1812755f526004601cfd5b5085358087016020810194503592505f90604011600286141115610d47575050602080860135860190810190355b610d5688888887878787611674565b5050505050505050565b5f333014610d80576040516282b42960e81b815260040160405180910390fd5b50686d3d4e7fb92a523815805460408051828152426020808301919091523382840152606090912063ffffffff169092019283905580518381529051686d3d4e7fb92a523813927f7328006ebae4716b8db9514af69091e0ad74bec61dfdc256ed5446b234aa5424928290030190a15090565b5f610c6a686d3d4e7fb92a52381661178b565b5f84610e1457506001611091565b683c149ebf7b8e6c5e22631919191960e11b60048410610e32575083355b83610e415750630707070760e51b5b610e4b8682611321565b15610e6757610e598761134b565b610e67575f92505050611091565b5f818152601887905260048890526038812081526020839052604090205460ff1615610e9857600192505050611091565b5f81815273323232323232323232323232323232323232323260185260048890526038812081526020839052604090205460ff1615610edc57600192505050611091565b5f81815260188790525f8051602061266f8339815191526004526038812081526020839052604090205460ff1615610f1957600192505050611091565b5f8181527332323232323232323232323232323232323232326018525f8051602061266f8339815191526004526038812081526020839052604090205460ff1615610f6957600192505050611091565b631919191960e11b5f908152601887905260048890526038812081526020839052604090205460ff1615610fa257600192505050611091565b631919191960e11b5f90815273323232323232323232323232323232323232323260185260048890526038812081526020839052604090205460ff1615610fee57600192505050611091565b631919191960e11b5f90815260188790525f8051602061266f8339815191526004526038812081526020839052604090205460ff161561103357600192505050611091565b631919191960e11b5f9081527332323232323232323232323232323232323232326018525f8051602061266f8339815191526004526038812081526020839052604090205460ff161561108b57600192505050611091565b5f925050505b949350505050565b5f80604184146040851417156110c957306110b58787876117d7565b6001600160a01b03161491505f90506112a5565b60218410156110dc57505f9050806112a5565b506020198381018481118186180281189486019182013591601f19013560ff161561110d5761110a8761185f565b96505b505f6111188261066d565b6040810151909150158415151615611133575f9250506112a5565b805164ffffffffff164281109015151615611151575f9250506112a5565b5f81602001516002811115611168576111686120c7565b036111c3575f80603f8711883581029060208a013502915091505f806111a7856060015180516020820151604090920151603f90911191820292910290565b915091506111b88b8585858561187d565b9650505050506112a3565b6001816020015160028111156111db576111db6120c7565b0361126057606081810151805160208083015160409384015184518084018e9052855180820385018152601f8d018590049094028101870186529485018b8152603f9490941091820295910293611257935f92611250928e918e918291018382808284375f9201919091525061190f92505050565b85856119f3565b945050506112a3565b600281602001516002811115611278576112786120c7565b036112a3576112a0816060015180602001905181019061129891906125f8565b888888611b12565b92505b505b94509492505050565b686d3d4e7fb92a52381390565b60405181546020820190600881901c5f8260ff8417146112e957505080825260ff8116601f8082111561130b575b855f5260205f205b8160051c810154828601526020820191508282106112f157505b508084525f920191825250602001604052919050565b5f63e9ae5c5360e01b6001600160e01b0319831614306001600160a01b03851614165b9392505050565b5f6113558261066d565b6040015192915050565b6318fb58646004525f8281526024902081015468fbb67fda52d4bfb8bf811415026113898361178b565b82106108d357604051634e23d03560e01b815260040160405180910390fd5b5f8183604051375060405120919050565b7f06012d2aedbb397c1914278c3206cec06702f79ceb18361e1ddb2fcc068c324f7f0000000000000000000000005fbdb2315678afecb367f032d93f642f64180aa330147f0000000000000000000000000000000000000000000000000000000000007a694614166114ac5750604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f81527f6c157b110f3bc0280dba8ae624dab3ba623d9bab8692057c96d59c9188d52cb260208201527fae209a0b48f21c054280f2455d32cf309387644879d9acbd8ffc1991638118859181019190915246606082015230608082015260a090205b6719010000000000005f5280601a5281603a52604260182090505f603a52919050565b6001600160a01b0383166114ec576114e78282611be0565b505050565b6114e7838383611bf9565b600881901c5f908152686d3d4e7fb92a523814602052604090208054600160ff84161b1790556040518181527f4d9dbebf1d909894d9c26fe228c27cec643b2cb490124e5b658f4edd203c20c19060200160405180910390a150565b5f818152686d3d4e7fb92a5238176020526040812055686d3d4e7fb92a523813611586686d3d4e7fb92a52381683611c43565b6115a35760405163395ed8c160e21b815260040160405180910390fd5b5050565b80518060081b60ff175f60fe83116115d0575050601f8281015160081b821790808311156115f7575b60208401855f5260205f205b828201518360051c8201556020830192508483106115dc5750505b509092555050565b5f61160982610af9565b90505f686d3d4e7fb92a5238136060840151845160208087015160408089015190519596506116609561163e95949301612613565b60408051601f198184030181529181525f8581526004850160205220906115a7565b61166d6003820183611d4f565b5050919050565b6fac830f1181f6aab6862e71edc248941b1933016116cc5760408110156116ae576040516355fe73fd60e11b815260040160405180910390fd5b6116b88235611e6a565b6116c784846020850135611e91565b611782565b806116fb573330146116f0576040516282b42960e81b815260040160405180910390fd5b6116c784845f611e91565b602081101561171d576040516355fe73fd60e11b815260040160405180910390fd5b813561172881611e6a565b5f806117526117388888866108d9565b602080871081881802188088019080880390881102610653565b9150915081611773576040516282b42960e81b815260040160405180910390fd5b61177e878783611e91565b5050505b50505050505050565b6318fb58646004525f818152602481208019548060011c92508061166d5781545f93501561166d5760019250828201541561166d5760029250828201541561166d575060039392505050565b5f60405182604081146117f25760418114611819575061184a565b60208581013560ff81901c601b0190915285356040526001600160ff1b031660605261182a565b60408501355f1a6020526040856040375b50845f526020600160805f60015afa5191505f606052806040523d611857575b638baa579f5f526004601cfd5b509392505050565b5f815f526020600160205f60025afa5190503d61187857fe5b919050565b5f6040518681528560208201528460408201528360608201528260808201525f805260205f60a0836101005afa503d6118da5760203d60a0836dcb83347beb24c695bbb85dbe99b75afa503d6118da5763d0d5039b3d526004601cfd5b505f516001147f7fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a8851110905095945050505050565b6040805160c0810182526060808252602082018190525f92820183905281018290526080810182905260a0810191909152815160c081106119ed5760208301818101818251018281108260c0830111171561196c575050506119ed565b808151019250806020820151018181108382111782851084861117171561199657505050506119ed565b82815160208301011183855160208701011117156119b757505050506119ed565b8386528060208701525060408101516040860152606081015160608601526080810151608086015260a081015160a08601525050505b50919050565b5f805f611a0288600180611edf565b905060208601518051602082019150604088015160608901518451600d81016c1131b430b63632b733b2911d1160991b60981c8752848482011060228286890101515f1a14168160138901208286890120141685846014011085851760801c1074113a3cb832911d113bb2b130baba34371733b2ba1160591b60581c8589015160581c14161698505080865250505087515189151560021b600117808160218c5101511614602083118816169650508515611ae657602089510181810180516020600160208601856020868a8c60025afa60011b5afa51915295503d9050611ae657fe5b5050508215611b0757611b048287608001518860a00151888861187d565b92505b505095945050505050565b5f6001600160a01b03851615611091576040518260408114611b3c5760418114611b635750611b98565b60208581013560ff81901c601b0190915285356040526001600160ff1b0316606052611b74565b60408501355f1a6020526040856040375b50845f526020600160805f60015afa5180871860601b3d119250505f606052806040525b81611bd757631626ba7e60e01b80825285600483015260248201604081528460448401528486606485013760208160648701858b5afa90519091141691505b50949350505050565b5f385f3884865af16115a35763b12d13eb5f526004601cfd5b816014528060345263a9059cbb60601b5f5260205f604460105f875af18060015f511416611c3957803d853b151710611c39576390b8ec185f526004601cfd5b505f603452505050565b6318fb58646004525f8281526024812068fbb67fda52d4bfb8bf8303611c705763f5a267f15f526004601cfd5b82611c825768fbb67fda52d4bfb8bf92505b80195480611ceb576001925083825403611caf5760018201805483556002830180549091555f9055611d47565b83600183015403611ccd5760028201805460018401555f9055611d47565b83600283015403611ce3575f6002830155611d47565b5f9250611d47565b81602052835f5260405f20805480611d04575050611d47565b60018360011c039250826001820314611d33578284015480600183038601555f84860155805f52508060405f20555b5060018260011b178319555f815550600192505b505092915050565b6318fb58646004525f8281526024812068fbb67fda52d4bfb8bf8303611d7c5763f5a267f15f526004601cfd5b82611d8e5768fbb67fda52d4bfb8bf92505b8019548160205280611e3257815480611dae578483556001935050611d47565b848103611dbb5750611d47565b600183015480611dd657856001850155600194505050611d47565b858103611de4575050611d47565b600284015480611e005786600286015560019550505050611d47565b868103611e0f57505050611d47565b5f9283526040808420600190559183528183206002905582529020600390555060075b835f5260405f208054611e6157600191821c8381018690558083019182905590821b8217831955909250611d47565b50505092915050565b611e7381610ad1565b15610b5157604051633ab3447f60e11b815260040160405180910390fd5b600582901b5f5b818114611ed8576020818101918601358601803580153002179181810135916040810135019081019035611ecf848484848b611fd0565b50505050611e98565b5050505050565b606083518015611857576003600282010460021b60405192507f4142434445464748494a4b4c4d4e4f505152535455565758595a616263646566601f526106708515027f6768696a6b6c6d6e6f707172737475767778797a303132333435363738392d5f18603f526020830181810183886020010180515f82525b60038a0199508951603f8160121c16515f53603f81600c1c1651600153603f8160061c1651600253603f811651600353505f518452600484019350828410611f5a579052602001604052613d3d60f01b60038406600204808303919091525f861515909102918290035290038252509392505050565b611fdc81868585610e06565b611ff8576040516282b42960e81b815260040160405180910390fd5b611ed88585858585604051828482375f388483888a5af161201b573d5f823e3d81fd5b505050505050565b5f8083601f840112612033575f80fd5b50813567ffffffffffffffff81111561204a575f80fd5b602083019150836020828501011115612061575f80fd5b9250929050565b5f805f6040848603121561207a575f80fd5b83359250602084013567ffffffffffffffff811115612097575f80fd5b6120a386828701612023565b9497909650939450505050565b5f602082840312156120c0575f80fd5b5035919050565b634e487b7160e01b5f52602160045260245ffd5b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b6020815264ffffffffff82511660208201525f60208301516003811061213d57634e487b7160e01b5f52602160045260245ffd5b80604084015250604083015115156060830152606083015160808084015261109160a08401826120db565b6001600160a01b0381168114610b5a575f80fd5b80358015158114611878575f80fd5b5f805f806080858703121561219e575f80fd5b8435935060208501356121b081612168565b925060408501356001600160e01b0319811681146121cc575f80fd5b91506121da6060860161217c565b905092959194509250565b5f805f604084860312156121f7575f80fd5b833567ffffffffffffffff81111561220d575f80fd5b8401601f8101861361221d575f80fd5b803567ffffffffffffffff811115612233575f80fd5b8660208260051b8401011115612247575f80fd5b6020918201979096509401359392505050565b60ff60f81b8816815260e060208201525f61227860e08301896120db565b828103604084015261228a81896120db565b606084018890526001600160a01b038716608085015260a0840186905283810360c0850152845180825260208087019350909101905f5b818110156122df5783518352602093840193909201916001016122c1565b50909b9a5050505050505050505050565b5f8060408385031215612301575f80fd5b823561230c81612168565b946020939093013593505050565b634e487b7160e01b5f52604160045260245ffd5b6040516080810167ffffffffffffffff811182821017156123515761235161231a565b60405290565b5f82601f830112612366575f80fd5b813567ffffffffffffffff8111156123805761238061231a565b604051601f8201601f19908116603f0116810167ffffffffffffffff811182821017156123af576123af61231a565b6040528181528382016020018510156123c6575f80fd5b816020850160208301375f918101602001919091529392505050565b5f602082840312156123f2575f80fd5b813567ffffffffffffffff811115612408575f80fd5b820160808185031215612419575f80fd5b61242161232e565b813564ffffffffff81168114612435575f80fd5b8152602082013560038110612448575f80fd5b60208201526124596040830161217c565b6040820152606082013567ffffffffffffffff811115612477575f80fd5b61248386828501612357565b606083015250949350505050565b5f80602083850312156124a2575f80fd5b823567ffffffffffffffff8111156124b8575f80fd5b6124c485828601612023565b90969095509350505050565b602081525f61134460208301846120db565b5f805f80606085870312156124f5575f80fd5b84359350602085013561250781612168565b9250604085013567ffffffffffffffff811115612522575f80fd5b61252e87828801612023565b95989497509550505050565b634e487b7160e01b5f52603260045260245ffd5b5f8235605e19833603018112612562575f80fd5b9190910192915050565b5f6020828403121561257c575f80fd5b813561134481612168565b5f808335601e1984360301811261259c575f80fd5b83018035915067ffffffffffffffff8211156125b6575f80fd5b602001915036819003821315612061575f80fd5b60208152816020820152818360408301375f818301604090810191909152601f909201601f19160101919050565b5f60208284031215612608575f80fd5b815161134481612168565b5f85518060208801845e60d886901b6001600160d81b0319169083019081526003851061264e57634e487b7160e01b5f52602160045260245ffd5b60f894851b600582015292151590931b600683015250600701939250505056fe3232323232323232323232323232323232323232323232323232323232323232a2646970667358221220fedfc3184b2b333b20591e8e82d687d62f41e28fa92d349d78b640fb4d41df5764736f6c634300081a0033", + "storage": {} + }, + "0x14dc79964da2c08b23698b3d3cc7ca32193d9955": { + "nonce": 7, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x15d34aaf54267db7d7c367839aaf71a00a2c6a65": { + "nonce": 19, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f": { + "nonce": 11, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x238c8cd93ee9f8c7edf395548ef60c0d2e46665e": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x60806040526004361015610010575b005b5f3560e01c806306fdde03146106f7578063095ea7b31461068c57806318160ddd1461066757806323b872dd146105a15780632bb7c5951461050e578063313ce567146104f35780633644e5151461045557806340c10f191461043057806370a08231146103fe5780637ecebe00146103cc57806395d89b4114610366578063a9059cbb146102ea578063ad0c8fdd146102ad578063d505accf146100fb5763dd62ed3e0361000e57346100f75760403660031901126100f7576100d261075c565b6100da610772565b602052637f5e9f20600c525f5260206034600c2054604051908152f35b5f80fd5b346100f75760e03660031901126100f75761011461075c565b61011c610772565b6084359160643560443560ff851685036100f757610138610788565b60208101906e04578706572696d656e74455243323608c1b8252519020908242116102a0576040519360018060a01b03169460018060a01b03169565383775081901600e52855f5260c06020600c20958654957f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8252602082019586528660408301967fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc688528b6060850198468a528c608087019330855260a08820602e527f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9885252528688525260a082015220604e526042602c205f5260ff1660205260a43560405260c43560605260208060805f60015afa93853d5103610293577f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92594602094019055856303faf4f960a51b176040526034602c2055a3005b63ddafbaef5f526004601cfd5b631a15a3cc5f526004601cfd5b5f3660031901126100f7576103e834023481046103e814341517156102d65761000e90336107ac565b634e487b7160e01b5f52601160045260245ffd5b346100f75760403660031901126100f75761030361075c565b602435906387a211a2600c52335f526020600c2080548084116103595783900390555f526020600c20818154019055602052600c5160601c335f51602061080d5f395f51905f52602080a3602060405160018152f35b63f4d678b85f526004601cfd5b346100f7575f3660031901126100f757604051604081019080821067ffffffffffffffff8311176103b8576103b491604052600381526204558560ec1b602082015260405191829182610732565b0390f35b634e487b7160e01b5f52604160045260245ffd5b346100f75760203660031901126100f7576103e561075c565b6338377508600c525f52602080600c2054604051908152f35b346100f75760203660031901126100f75761041761075c565b6387a211a2600c525f52602080600c2054604051908152f35b346100f75760403660031901126100f75761000e61044c61075c565b602435906107ac565b346100f7575f3660031901126100f757602060a0610471610788565b828101906e04578706572696d656e74455243323608c1b8252519020604051907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8252838201527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6604082015246606082015230608082015220604051908152f35b346100f7575f3660031901126100f757602060405160128152f35b346100f75760203660031901126100f7576004356387a211a2600c52335f526020600c2090815490818111610359575f80806103e88487839688039055806805345cdf77eb68f44c54036805345cdf77eb68f44c5580835282335f51602061080d5f395f51905f52602083a304818115610598575b3390f11561058d57005b6040513d5f823e3d90fd5b506108fc610583565b346100f75760603660031901126100f7576105ba61075c565b6105c2610772565b604435908260601b33602052637f5e9f208117600c526034600c20908154918219610643575b506387a211a2915017600c526020600c2080548084116103595783900390555f526020600c20818154019055602052600c5160601c9060018060a01b03165f51602061080d5f395f51905f52602080a3602060405160018152f35b82851161065a57846387a211a293039055856105e8565b6313be252b5f526004601cfd5b346100f7575f3660031901126100f75760206805345cdf77eb68f44c54604051908152f35b346100f75760403660031901126100f7576106a561075c565b60243590602052637f5e9f20600c52335f52806034600c20555f52602c5160601c337f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560205fa3602060405160018152f35b346100f7575f3660031901126100f7576103b4610712610788565b6e04578706572696d656e74455243323608c1b6020820152604051918291825b602060409281835280519182918282860152018484015e5f828201840152601f01601f1916010190565b600435906001600160a01b03821682036100f757565b602435906001600160a01b03821682036100f757565b604051906040820182811067ffffffffffffffff8211176103b857604052600f8252565b6805345cdf77eb68f44c548281019081106107ff576805345cdf77eb68f44c556387a211a2600c525f526020600c20818154019055602052600c5160601c5f5f51602061080d5f395f51905f52602080a3565b63e5cfe9575f526004601cfdfeddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220fbe302881d9891005ba1448ba48547cc1cb17dea1a5c4011dfcb035de325bb1d64736f6c634300081b0033", + "storage": {} + }, + "0x35202a6e6317f3cc3a177eeee562d3bcda4a6fcc": { + "nonce": 0, + "balance": "0x0", + "code": "0x60806040526004361015610018575b361561001657005b005b5f3560e01c806309c5eabe146100c75780630cb6aaf1146100c257806330f6a8e5146100bd5780635fce1927146100b8578063641cdfe2146100b357806376ba882d146100ae5780638d80ff0a146100a9578063972ce4bc146100a4578063a78fc2441461009f578063a82e44e01461009a5763b34893910361000e576108e1565b6108b5565b610786565b610646565b6105ba565b610529565b6103f8565b6103a2565b61034c565b6102c0565b61020b565b634e487b7160e01b5f52604160045260245ffd5b6040810190811067ffffffffffffffff8211176100fc57604052565b6100cc565b6080810190811067ffffffffffffffff8211176100fc57604052565b60a0810190811067ffffffffffffffff8211176100fc57604052565b90601f8019910116810190811067ffffffffffffffff8211176100fc57604052565b6040519061016a608083610139565b565b67ffffffffffffffff81116100fc57601f01601f191660200190565b9291926101948261016c565b916101a26040519384610139565b8294818452818301116101be578281602093845f960137010152565b5f80fd5b9080601f830112156101be578160206101dd93359101610188565b90565b60206003198201126101be576004359067ffffffffffffffff82116101be576101dd916004016101c2565b346101be57610219366101e0565b3033036102295761001690610ae6565b636f6a1b8760e11b5f5260045ffd5b634e487b7160e01b5f52603260045260245ffd5b5f54811015610284575f8080526005919091027f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5630191565b610238565b8054821015610284575f52600560205f20910201905f90565b906040516102af816100e0565b602060018294805484520154910152565b346101be5760203660031901126101be576004355f548110156101be576102e69061024c565b5060ff815416600182015491610306600360ff60028401541692016102a2565b926040519215158352602083015260028110156103385760a09260209160408401528051606084015201516080820152f35b634e487b7160e01b5f52602160045260245ffd5b346101be575f3660031901126101be576020600254604051908152f35b6004359063ffffffff821682036101be57565b6064359063ffffffff821682036101be57565b6084359063ffffffff821682036101be57565b346101be5760203660031901126101be576103bb610369565b303303610229576103cb9061024c565b50805460ff19169055005b60609060231901126101be57602490565b60609060831901126101be57608490565b346101be5760803660031901126101be57610411610369565b60205f61041d366103d6565b60015461043161042c82610a0b565b600155565b60405184810191825260e086901b6001600160e01b031916602083015261046581602484015b03601f198101835282610139565b51902060ff61047660408401610a19565b161583146104fe576104b2601b925b85813591013590604051948594859094939260ff6060936080840197845216602083015260408201520152565b838052039060015afa156104f9575f51306001600160a01b03909116036104ea576104df6100169161024c565b50805460ff19169055565b638baa579f60e01b5f5260045ffd5b610a27565b6104b2601c92610485565b60409060031901126101be57600490565b6044359060028210156101be57565b346101be5760803660031901126101be5761054336610509565b61054b61051a565b606435903033036102295761059192610580610587926040519461056e86610101565b60018652602086015260408501610a32565b36906105f3565b6060820152610a3e565b5f545f1981019081116105b55760405163ffffffff919091168152602090f35b0390f35b6109f7565b6100166105c6366101e0565b610ae6565b60409060231901126101be57604051906105e4826100e0565b60243582526044356020830152565b91908260409103126101be5760405161060b816100e0565b6020808294803584520135910152565b6084359081151582036101be57565b60a4359081151582036101be57565b359081151582036101be57565b346101be5760a03660031901126101be5760043567ffffffffffffffff81116101be576106779036906004016101c2565b610680366105cb565b61068861037c565b61069061061b565b906002546106a56106a082610a0b565b600255565b6040516106bb8161045788602083019586610b6a565b51902091610747575b6106d06106d69161024c565b50610b7b565b906106e86106e48351151590565b1590565b610738576020820151801515908161072e575b5061071f576107129260606106e493015191610ce3565b6104ea5761001690610ae6565b632572e3a960e01b5f5260045ffd5b905042115f6106fb565b637dd286d760e11b5f5260045ffd5b905f61077361045761076760209460405192839187830160209181520190565b60405191828092610b58565b039060025afa156104f9575f51906106c4565b346101be5760e03660031901126101be576107a036610509565b6107a861051a565b6064359060205f6107b8366103e7565b6001546107c761042c82610a0b565b60408051808601928352883560208401528589013591830191909152606082018790526107f78160808401610457565b51902060ff61080860408401610a19565b161583146108aa5760408051918252601b602083015282359082015290830135606082015280608081015b838052039060015afa156104f9575f51306001600160a01b03909116036104ea5761087a926105806105879261086761015b565b6001815294602086015260408501610a32565b6105b161089361088a5f54610ad8565b63ffffffff1690565b60405163ffffffff90911681529081906020820190565b610833601c92610485565b346101be575f3660031901126101be576020600154604051908152f35b359061ffff821682036101be57565b346101be5760c03660031901126101be5760043567ffffffffffffffff81116101be576109129036906004016101c2565b61091b366105cb565b906064359167ffffffffffffffff83116101be5760a060031984360301126101be576040516109498161011d565b836004013567ffffffffffffffff81116101be5761096d90600436918701016101c2565b8152602484013567ffffffffffffffff81116101be57840193366023860112156101be5760846109db916109ae610016973690602460048201359101610188565b60208501526109bf604482016108d2565b60408501526109d0606482016108d2565b606085015201610639565b60808201526109e861038f565b916109f161062a565b93610bc3565b634e487b7160e01b5f52601160045260245ffd5b5f1981146105b55760010190565b3560ff811681036101be5790565b6040513d5f823e3d90fd5b60028210156103385752565b5f54680100000000000000008110156100fc57806001610a6192015f555f610289565b610ac557610a7e82511515829060ff801983541691151516179055565b6020820151600182015560028101604083015160028110156103385761016a9360039260609260ff8019835416911617905501519101906020600191805184550151910155565b634e487b7160e01b5f525f60045260245ffd5b5f198101919082116105b557565b80519060205b828110610af857505050565b808201805160f81c600182015160601c91601581015160358201519384915f9493845f14610b4257505050506001146101be575b15610b3a5701605501610aec565b3d5f803e3d5ffd5b5f95508594506055019130811502175af1610b2c565b805191908290602001825e015f815290565b6020906101dd939281520190610b58565b90604051610b8881610101565b6060610bbe6003839560ff8154161515855260018101546020860152610bb860ff60028301541660408701610a32565b016102a2565b910152565b93909192600254610bd66106a082610a0b565b604051610bec8161045789602083019586610b6a565b51902091610c50575b6106d0610c019161024c565b91610c0f6106e48451151590565b6107385760208301518015159081610c46575b5061071f57610c399360606106e494015192610e0d565b6104ea5761016a90610ae6565b905042115f610c22565b905f610c7061045761076760209460405192839187830160209181520190565b039060025afa156104f9575f5190610bf5565b3d15610cad573d90610c948261016c565b91610ca26040519384610139565b82523d5f602084013e565b606090565b8051601f101561028457603f0190565b8051602010156102845760400190565b908151811015610284570160200190565b5f9291839260208251920151906020815191015191604051936020850195865260408501526060840152608083015260a082015260a08152610d2660c082610139565b519060145afa610d34610c83565b81610d74575b81610d43575090565b600160f81b91506001600160f81b031990610d6f90610d6190610cb2565b516001600160f81b03191690565b161490565b80516020149150610d3a565b60405190610d8f604083610139565b6015825274113a3cb832911d113bb2b130baba34371733b2ba1160591b6020830152565b9061016a6001610de3936040519485916c1131b430b63632b733b2911d1160991b6020840152602d830190610b58565b601160f91b815203601e19810185520183610139565b610e069060209392610b58565b9081520190565b92919281516025815110908115610f0a575b50610ef957610e2c610d80565b90610e596106e460208501938451610e53610e4c606089015161ffff1690565b61ffff1690565b91610f9b565b610f01576106e4610e8d610e88610457610e83610ea1956040519283916020830160209181520190565b611012565b610db3565b8351610e53610e4c604088015161ffff1690565b610ef9575f610eb96020925160405191828092610b58565b039060025afa156104f9575f610ee360209261076783519151610457604051938492888401610df9565b039060025afa156104f9576101dd915f51610ce3565b505050505f90565b50505050505f90565b610f2b9150610f1e610d616106e492610cc2565b6080850151151590610f31565b5f610e1f565b906001600160f81b0319600160f81b831601610f955780610f85575b610f8057601f60fb1b600160fb1b821601610f69575b50600190565b600160fc1b90811614610f7c575f610f63565b5f90565b505f90565b50600160fa1b8181161415610f4d565b50505f90565b80519282515f5b858110610fb457505050505050600190565b8083018084116105b5578281101561100757610fe56001600160f81b0319610fdc8488610cd2565b51169187610cd2565b516001600160f81b03191603610ffd57600101610fa2565b5050505050505f90565b505050505050505f90565b80516060929181611021575050565b9092506003600284010460021b604051937f4142434445464748494a4b4c4d4e4f505152535455565758595a616263646566601f527f6768696a6b6c6d6e6f707172737475767778797a303132333435363738392d5f603f52602085019282860191602083019460208284010190600460038351955f85525b0191603f8351818160121c16515f538181600c1c1651600153818160061c165160025316516003535f5181520190878210156110db5760049060039061109a565b5095505f93600393604092520160405206600204809303613d3d60f01b81525203825256fea26469706673582212200ba93b78f286a25ece47e9403c47be9862f9b8b70ba1a95098667b90c47308b064736f6c634300081a0033", + "storage": {} + }, + "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc": { + "nonce": 47, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x4e59b44847b379578588920ca78fbf26c0b4956c": { + "nonce": 6684, + "balance": "0x0", + "code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3", + "storage": {} + }, + "0x5b385c0c98b160b9f366c4da8a130c1fefb50db3": { + "nonce": 1, + "balance": "0x0", + "code": "0xef01000ff027b63351364071425cf65d4fece75a8e17b8", + "storage": { + "0x00000000000000000000000000000000000000000000006d3d4e7fb92a523815": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x162c94570e4a6cec59dd402e32a5d9002c563081cb2c8a689d93ce3e2c7f33a2": "0x0000000000080000000000000000000000000000000000000000000000000000", + "0x36cb44b74eb342e5eccbda90d174abab7cfbee199dc4173a3738e72c7c3b7b5a": "0x9e1120342ba6a679b2fb6fe87ea0658d4efc3cbe4bdf671bb35b6ded1e769147", + "0x409062aa07888d0648ef58219cc6dd5865976fad1e5625fa7a6b10e7fed68ac8": "0x27446672ba030b287d6f5bf090e17275d38c05063e2bb2fddc7998de4dd87fc0", + "0x409062aa07888d0648ef58219cc6dd5865976fad1e5625fa7a6b10e7fed68ac9": "0xa500000000000101000000000000000000000000000000000000000000000000", + "0x4d3077b15616cae6c0e33614754fa60a49116b724b45c9fe889d135307dfea42": "0xf85bad144aaee537a0bc3ef12e6e9dbfc0a7a5bc5295f6f976ea7d0d90bae0c0", + "0xb2cf884ea9e935193f1cc9eb8ab059f5b6ee948db4ba36017762ecacf82015bd": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xc354997ea30e80477cc70ce4a3a2ec0ce448d0ee45ea4eca11f6286b8fdfbb02": "0x0000000000000000000000000000000000000000000000000000000000000001" + } + }, + "0x6634f723546ecc92277e8a2f93d4f248bf1189ea": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x70997970c51812dc3a010c7d01b50e0d17dc79c8": { + "nonce": 162, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x90f79bf6eb2c4f870365e785982e1f101e93b906": { + "nonce": 42, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x976ea74026e726554db657fa54763abd0c3a0aa9": { + "nonce": 13, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc": { + "nonce": 30, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0xa0ee7a142d267c1f36714e4a8f75612f20a79720": { + "nonce": 9, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266": { + "nonce": 827, + "balance": "0x21e19d9b6e24c7c2d8d", + "code": "0x", + "storage": {} + } + }, + "best_block_number": "0x148fdc7", + "blocks": [ + { + "header": { + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "miner": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x0", + "number": "0x0", + "gasLimit": "0x1c9c380", + "gasUsed": "0x0", + "timestamp": "0x67749feb", + "extraData": "0x", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "baseFeePerGas": "0x3b9aca00", + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "blobGasUsed": "0x0", + "excessBlobGas": "0x0", + "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "transactions": [], + "ommers": [] + }, + { + "header": { + "parentHash": "0xfc934e843d5ba9237771640232dd4969492667de1bc25a5e60e7ccaf4b83953d", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "miner": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionsRoot": "0x3e6fb9fa95c6729b5c2cfe39010f59f6019e3558d372e04e49bbd8852e8f05c8", + "receiptsRoot": "0xa9f93cad339fb35647cabe8dae60ee37950fb4ff2d9f295a40147113ea68bd25", + "logsBloom": "0x00000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000100000000000000000400000000000000000000000000000000000000000000000000040000000020000000040000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000200000000000000080040000020000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x0", + "number": "0x1", + "gasLimit": "0x1c9c380", + "gasUsed": "0x331d9", + "timestamp": "0x677af086", + "extraData": "0x", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "baseFeePerGas": "0x1fb38612b", + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "blobGasUsed": "0x0", + "excessBlobGas": "0x120000", + "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "requestsHash": "0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "transactions": [ + { + "transaction": { + "EIP7702": { + "chainId": "0x1", + "nonce": "0x33a", + "gas": "0x989680", + "maxFeePerGas": "0x28609e998", + "maxPriorityFeePerGas": "0x3b9aca00", + "to": "0x5b385c0c98b160b9f366c4da8a130c1fefb50db3", + "value": "0x0", + "accessList": [], + "authorizationList": [ + { + "chainId": "0x1", + "address": "0x0ff027b63351364071425cf65d4fece75a8e17b8", + "nonce": "0x0", + "yParity": "0x0", + "r": "0x63dd0f8a584572599eafadf0cd1dd26ae3fecc9d3ee7a2b45567300fb6e23d77", + "s": "0x7d1adbee43eb55d72c3997b5e40a160ee09e06894567ed62b7940ec0a46a8e04" + } + ], + "input": "0xe9ae5c53010000000000782100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001600000000000000000000000005b385c0c98b160b9f366c4da8a130c1fefb50db3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000084136a12f7f85bad144aaee537a0bc3ef12e6e9dbfc0a7a5bc5295f6f976ea7d0d90bae0c0000000000000000000000000323232323232323232323232323232323232323232323232000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000005b385c0c98b160b9f366c4da8a130c1fefb50db3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000104cebfe3360000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000409e1120342ba6a679b2fb6fe87ea0658d4efc3cbe4bdf671bb35b6ded1e769127446672ba030b287d6f5bf090e17275d38c05063e2bb2fddc7998de4dd87fc0a500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006128b789542bf8d47234a05b65c6b29a62be2eaf2aae07c6071e8a64f0a606aed373eaa4574392c69825320f72364f965d03d759dd1b618a276f5f6070a31a91065bcc3ecbe94c53603ef79c4b010e8ac106ff31fa333dc455a64f933a465f6ccf1c00000000000000000000000000000000000000000000000000000000000000", + "r": "0x72006087df55bfffcedad8244eaf26b2b7c2cc7e359324833b62f166b5154a4e", + "s": "0x6b81a434bd3f4cec7d3eda40f44404990f57c3935c3994432ea1b699e468456f", + "yParity": "0x1", + "v": "0x1", + "hash": "0x9c6cd4909019afc8448c1671f5287596b52cedbe5f384823697f1662c0442b4f" + } + }, + "impersonated_sender": null + } + ], + "ommers": [] + } + ], + "transactions": [ + { + "info": { + "transaction_hash": "0x9c6cd4909019afc8448c1671f5287596b52cedbe5f384823697f1662c0442b4f", + "transaction_index": 0, + "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "to": "0x5b385c0c98b160b9f366c4da8a130c1fefb50db3", + "contract_address": null, + "traces": [ + { + "parent": null, + "children": [1, 2, 3], + "idx": 0, + "trace": { + "depth": 0, + "success": true, + "caller": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "address": "0x5b385c0c98b160b9f366c4da8a130c1fefb50db3", + "maybe_precompile": null, + "selfdestruct_address": null, + "selfdestruct_refund_target": null, + "selfdestruct_transferred_value": null, + "kind": "CALL", + "value": "0x0", + "data": "0xe9ae5c53010000000000782100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001600000000000000000000000005b385c0c98b160b9f366c4da8a130c1fefb50db3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000084136a12f7f85bad144aaee537a0bc3ef12e6e9dbfc0a7a5bc5295f6f976ea7d0d90bae0c0000000000000000000000000323232323232323232323232323232323232323232323232000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000005b385c0c98b160b9f366c4da8a130c1fefb50db3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000104cebfe3360000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000409e1120342ba6a679b2fb6fe87ea0658d4efc3cbe4bdf671bb35b6ded1e769127446672ba030b287d6f5bf090e17275d38c05063e2bb2fddc7998de4dd87fc0a500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006128b789542bf8d47234a05b65c6b29a62be2eaf2aae07c6071e8a64f0a606aed373eaa4574392c69825320f72364f965d03d759dd1b618a276f5f6070a31a91065bcc3ecbe94c53603ef79c4b010e8ac106ff31fa333dc455a64f933a465f6ccf1c00000000000000000000000000000000000000000000000000000000000000", + "output": "0x", + "gas_used": 155345, + "gas_limit": 9945976, + "status": "Stop", + "steps": [], + "decoded": { + "label": null, + "return_data": null, + "call_data": null + } + }, + "logs": [ + { + "raw_log": { + "topics": [ + "0x4d9dbebf1d909894d9c26fe228c27cec643b2cb490124e5b658f4edd203c20c1" + ], + "data": "0x28b789542bf8d47234a05b65c6b29a62be2eaf2aae07c6071e8a64f0a606aed3" + }, + "decoded": { "name": null, "params": null }, + "position": 0 + } + ], + "ordering": [ + { "Log": 0 }, + { "Call": 0 }, + { "Call": 1 }, + { "Call": 2 } + ] + }, + { + "parent": 0, + "children": [], + "idx": 1, + "trace": { + "depth": 1, + "success": true, + "caller": "0x5b385c0c98b160b9f366c4da8a130c1fefb50db3", + "address": "0x0000000000000000000000000000000000000001", + "maybe_precompile": null, + "selfdestruct_address": null, + "selfdestruct_refund_target": null, + "selfdestruct_transferred_value": null, + "kind": "STATICCALL", + "value": "0x0", + "data": "0xc52c70ef04a0b5d4fedb96c866ae7c99b326a30f75d0ab453c4b4257c4cbfcf4000000000000000000000000000000000000000000000000000000000000001c73eaa4574392c69825320f72364f965d03d759dd1b618a276f5f6070a31a91065bcc3ecbe94c53603ef79c4b010e8ac106ff31fa333dc455a64f933a465f6ccf", + "output": "0x0000000000000000000000005b385c0c98b160b9f366c4da8a130c1fefb50db3", + "gas_used": 3000, + "gas_limit": 9761586, + "status": "Return", + "steps": [], + "decoded": { + "label": null, + "return_data": null, + "call_data": null + } + }, + "logs": [], + "ordering": [] + }, + { + "parent": 0, + "children": [], + "idx": 2, + "trace": { + "depth": 1, + "success": true, + "caller": "0x5b385c0c98b160b9f366c4da8a130c1fefb50db3", + "address": "0x5b385c0c98b160b9f366c4da8a130c1fefb50db3", + "maybe_precompile": null, + "selfdestruct_address": null, + "selfdestruct_refund_target": null, + "selfdestruct_transferred_value": null, + "kind": "CALL", + "value": "0x0", + "data": "0x136a12f7f85bad144aaee537a0bc3ef12e6e9dbfc0a7a5bc5295f6f976ea7d0d90bae0c0000000000000000000000000323232323232323232323232323232323232323232323232000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", + "output": "0x", + "gas_used": 24924, + "gas_limit": 9757889, + "status": "Stop", + "steps": [], + "decoded": { + "label": null, + "return_data": null, + "call_data": null + } + }, + "logs": [ + { + "raw_log": { + "topics": [ + "0x7eb91b8ac56c0864a4e4f5598082d140d04bed1a4dd62a41d605be2430c494e1" + ], + "data": "0xf85bad144aaee537a0bc3ef12e6e9dbfc0a7a5bc5295f6f976ea7d0d90bae0c0000000000000000000000000323232323232323232323232323232323232323232323232000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001" + }, + "decoded": { "name": null, "params": null }, + "position": 0 + } + ], + "ordering": [{ "Log": 0 }] + }, + { + "parent": 0, + "children": [], + "idx": 3, + "trace": { + "depth": 1, + "success": true, + "caller": "0x5b385c0c98b160b9f366c4da8a130c1fefb50db3", + "address": "0x5b385c0c98b160b9f366c4da8a130c1fefb50db3", + "maybe_precompile": null, + "selfdestruct_address": null, + "selfdestruct_refund_target": null, + "selfdestruct_transferred_value": null, + "kind": "CALL", + "value": "0x0", + "data": "0xcebfe3360000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000409e1120342ba6a679b2fb6fe87ea0658d4efc3cbe4bdf671bb35b6ded1e769127446672ba030b287d6f5bf090e17275d38c05063e2bb2fddc7998de4dd87fc0a5", + "output": "0xf85bad144aaee537a0bc3ef12e6e9dbfc0a7a5bc5295f6f976ea7d0d90bae0c0", + "gas_used": 96444, + "gas_limit": 9732758, + "status": "Return", + "steps": [], + "decoded": { + "label": null, + "return_data": null, + "call_data": null + } + }, + "logs": [ + { + "raw_log": { + "topics": [ + "0x3d3a48be5a98628ecf98a6201185102da78bbab8f63a4b2d6b9eef354f5131f5", + "0xf85bad144aaee537a0bc3ef12e6e9dbfc0a7a5bc5295f6f976ea7d0d90bae0c0" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000409e1120342ba6a679b2fb6fe87ea0658d4efc3cbe4bdf671bb35b6ded1e769127446672ba030b287d6f5bf090e17275d38c05063e2bb2fddc7998de4dd87fc0a5" + }, + "decoded": { "name": null, "params": null }, + "position": 0 + } + ], + "ordering": [{ "Log": 0 }] + } + ], + "exit": "Stop", + "out": "0x", + "nonce": 826, + "gas_used": 209369 + }, + "receipt": { + "type": "0x4", + "status": "0x1", + "cumulativeGasUsed": "0x331d9", + "logs": [ + { + "address": "0x5b385c0c98b160b9f366c4da8a130c1fefb50db3", + "topics": [ + "0x4d9dbebf1d909894d9c26fe228c27cec643b2cb490124e5b658f4edd203c20c1" + ], + "data": "0x28b789542bf8d47234a05b65c6b29a62be2eaf2aae07c6071e8a64f0a606aed3" + }, + { + "address": "0x5b385c0c98b160b9f366c4da8a130c1fefb50db3", + "topics": [ + "0x7eb91b8ac56c0864a4e4f5598082d140d04bed1a4dd62a41d605be2430c494e1" + ], + "data": "0xf85bad144aaee537a0bc3ef12e6e9dbfc0a7a5bc5295f6f976ea7d0d90bae0c0000000000000000000000000323232323232323232323232323232323232323232323232000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001" + }, + { + "address": "0x5b385c0c98b160b9f366c4da8a130c1fefb50db3", + "topics": [ + "0x3d3a48be5a98628ecf98a6201185102da78bbab8f63a4b2d6b9eef354f5131f5", + "0xf85bad144aaee537a0bc3ef12e6e9dbfc0a7a5bc5295f6f976ea7d0d90bae0c0" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000409e1120342ba6a679b2fb6fe87ea0658d4efc3cbe4bdf671bb35b6ded1e769127446672ba030b287d6f5bf090e17275d38c05063e2bb2fddc7998de4dd87fc0a5" + } + ], + "logsBloom": "0x00000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000100000000000000000400000000000000000000000000000000000000000000000000040000000020000000040000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000200000000000000080040000020000000000000000000000000000000000000000000000000000000000000000000000000000" + }, + "block_hash": "0x206243f4840d0238f105be71f04abe7da52a501322706d61875ff636fa438284", + "block_number": 21560775 + } + ], + "historical_states": null +} diff --git a/playground/index.html b/playground/index.html index de294004..9dac85c1 100644 --- a/playground/index.html +++ b/playground/index.html @@ -7,6 +7,7 @@

Playground

+
diff --git a/playground/package.json b/playground/package.json index a1d0917b..28c0c8ef 100644 --- a/playground/package.json +++ b/playground/package.json @@ -6,8 +6,8 @@ "dev": "vite" }, "dependencies": { + "ox": "^0.6.0", "porto": "workspace:*", - "ox": "^0.2.2", "react": "catalog:", "react-dom": "catalog:", "viem": "catalog:" @@ -15,9 +15,10 @@ "devDependencies": { "@types/react": "catalog:", "@types/react-dom": "catalog:", - "@vitejs/plugin-react": "^4.3.1", + "@vitejs/plugin-react": "^4.3.4", "globals": "^15.9.0", + "prool": "^0.0.16", "typescript": "^5.5.3", - "vite": "^5.4.1" + "vite": "^6.0.7" } } diff --git a/playground/src/App.tsx b/playground/src/App.tsx index 8b3136d6..cfe4b9f6 100644 --- a/playground/src/App.tsx +++ b/playground/src/App.tsx @@ -1,4 +1,4 @@ -import { AbiFunction, Hex, Json, PublicKey, TypedData, Value } from 'ox' +import { AbiFunction, Hex, Json, TypedData, Value } from 'ox' import { Porto } from 'porto' import { useEffect, useState, useSyncExternalStore } from 'react' import { createClient, custom } from 'viem' @@ -9,28 +9,53 @@ import { } from 'viem/accounts' import { verifyMessage, verifyTypedData } from 'viem/actions' +import * as Anvil from './anvil' import { ExperimentERC20 } from './contracts' -const porto = Porto.create() +// export const porto = Porto.create() +export const porto = Porto.create({ + chains: [Anvil.chain(), ...Porto.defaultConfig.chains], + transports: { + ...Porto.defaultConfig.transports, + [Anvil.chain().id]: Anvil.transport(), + }, +}) const client = createClient({ transport: custom(porto.provider), }) +const callScopes = [ + { + signature: 'mint(address,uint256)', + to: ExperimentERC20.address, + }, +] as const + export function App() { return (
+ +

+


+

- - - + - - - + + +

+


+

+ + + +

+


+

@@ -56,20 +81,7 @@ function State() {

Chain ID: {state.chain.id}

Keys:{' '} -

-              {Json.stringify(
-                state.accounts?.[0]?.keys
-                  .filter((x) => x.status === 'unlocked')
-                  .map((x) => ({
-                    expiry: x.expiry,
-                    publicKey: PublicKey.toHex(x.publicKey),
-                    status: x.status,
-                    type: x.type,
-                  })),
-                null,
-                2,
-              )}
-            
+
{Json.stringify(state.accounts?.[0]?.keys, null, 2)}

)} @@ -114,26 +126,32 @@ function Events() { } function Connect() { - const [grantSession, setGrantSession] = useState(true) + const [authorizeKey, setAuthorizeKey] = useState(true) const [result, setResult] = useState(null) return (
-

experimental_connect

+

wallet_connect

-
{JSON.stringify(result, null, 2)}
+ {result ?
{JSON.stringify(result, null, 2)}
: null}
) } @@ -181,7 +204,7 @@ function Accounts() { } function Register() { - const [result, setResult] = useState(null) + const [result, setResult] = useState(null) return (

experimental_createAccount

@@ -195,7 +218,7 @@ function Register() { > Register -
{result}
+ {result ?
{JSON.stringify(result, null, 2)}
: null}
) } @@ -223,11 +246,9 @@ function Login() { function Disconnect() { return (
-

experimental_disconnect

+

wallet_disconnect

-
{JSON.stringify(result, null, 2)}
+ {result ?
{JSON.stringify(result, null, 2)}
: null}
) } -function GrantSession() { - const [result, setResult] = useState(null) +function AuthorizeKey() { + const [result, setResult] = useState(null) return (
-

experimental_grantSession

+

experimental_authorizeKey

{ e.preventDefault() @@ -270,16 +291,19 @@ function GrantSession() { const [account] = await porto.provider.request({ method: 'eth_accounts', }) - const { id } = await porto.provider.request({ - method: 'experimental_grantSession', + const result = await porto.provider.request({ + method: 'experimental_authorizeKey', params: [ { address: account, - expiry: Math.floor(Date.now() / 1000) + expiry, + key: { + callScopes, + expiry: Math.floor(Date.now() / 1000) + expiry, + }, }, ], }) - setResult(id) + setResult(result) }} > - + +
+ {result &&
key: {JSON.stringify(result, null, 2)}
} +
+ ) +} + +function RevokeKey() { + const [revoked, setRevoked] = useState(false) + return ( +
+

experimental_revokeKey

+
{ + e.preventDefault() + const formData = new FormData(e.target as HTMLFormElement) + const publicKey = formData.get('publicKey') as `0x${string}` + + setRevoked(false) + await porto.provider.request({ + method: 'experimental_revokeKey', + params: [{ publicKey }], + }) + setRevoked(true) + }} + > + +
- {result &&
session id: {result}
} + {revoked &&

Key revoked.

}
) } -function GetSessions() { +function GetKeys() { const [result, setResult] = useState(null) return (
-

experimental_sessions

+

experimental_keys

{result ?
{JSON.stringify(result, null, 2)}
: null}
) } -function ImportAccount() { +function UpgradeAccount() { const [accountData, setAccountData] = useState<{ address: string privateKey: string } | null>(null) - const [grantSession, setGrantSession] = useState(true) + const [authorizeKey, setAuthorizeKey] = useState(true) const [privateKey, setPrivateKey] = useState('') const [result, setResult] = useState(null) return (
-

experimental_importAccount

+

experimental_prepareCreateAccount

{accountData &&

{JSON.stringify(accountData, null, 2)}
}

@@ -356,10 +407,10 @@ function ImportAccount() {
{result ? (

- Imported account.

{JSON.stringify(result, null, 2)}
+ Upgraded account.
{JSON.stringify(result, null, 2)}

) : null}
@@ -446,12 +502,12 @@ function SendCalls() { return [ { - data: '0xdeadbeef', to: '0x0000000000000000000000000000000000000000', + value: '0x0', }, { - data: '0xcafebabe', to: '0x0000000000000000000000000000000000000000', + value: '0x0', }, ] as const })() @@ -525,7 +581,7 @@ function SendTransaction() { { from: account, to: '0x0000000000000000000000000000000000000000', - data: '0xdeadbeef', + value: '0x0', }, ] as const })() as any diff --git a/playground/src/anvil.ts b/playground/src/anvil.ts new file mode 100644 index 00000000..51110729 --- /dev/null +++ b/playground/src/anvil.ts @@ -0,0 +1,79 @@ +import { Chains } from 'porto' +import { + http, + type Chain, + type HttpTransport, + type TransactionRequest, + createClient, + defineChain, + formatTransaction, + parseGwei, +} from 'viem' +import { privateKeyToAccount } from 'viem/accounts' +import { prepareTransactionRequest, signTransaction } from 'viem/actions' + +export const chain = (id = 1) => + defineChain({ + id, + name: 'Anvil', + nativeCurrency: { + name: 'Ether', + symbol: 'ETH', + decimals: 18, + }, + contracts: { + delegation: { + address: '0x0ff027b63351364071425cF65d4FEce75a8e17B8', + }, + }, + rpcUrls: { + default: { + http: ['http://127.0.0.1:8545/' + id], + }, + }, + }) + +export function transport(): HttpTransport { + return (options) => { + const transport = http()(options) + + return { + ...transport, + async request({ params, method }, opts) { + let body = { params, method } + + if (method === 'eth_sendTransaction') { + const client = createClient({ + chain: options.chain as Chain, + transport: http(), + }) + + const transaction = formatTransaction( + (params as any)[0], + ) as TransactionRequest + + const request = await prepareTransactionRequest(client, { + ...transaction, + account: privateKeyToAccount( + '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80', + ), + chain: null, + maxFeePerGas: parseGwei('100') as any, + maxPriorityFeePerGas: parseGwei('10') as any, + gas: 1_000_000n, + }) + + const serialized = await signTransaction(client, request) + + body = { + ...body, + method: 'eth_sendRawTransaction', + params: [serialized], + } + } + + return await transport.request(body, opts) + }, + } + } +} diff --git a/playground/vite.config.ts b/playground/vite.config.ts index 36f7f4e1..90899318 100644 --- a/playground/vite.config.ts +++ b/playground/vite.config.ts @@ -1,7 +1,31 @@ +import { resolve } from 'node:path' import react from '@vitejs/plugin-react' +import { createServer } from 'prool' +import { anvil } from 'prool/instances' import { defineConfig } from 'vite' // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [ + react(), + { + name: 'anvil', + async configureServer() { + await createServer({ + instance(key) { + return anvil({ + chainId: key, + forkUrl: 'https://eth.merkle.io', + hardfork: 'Prague', + // @ts-ignore + odyssey: true, + loadState: resolve(import.meta.dirname, './anvil.json'), + dumpState: resolve(import.meta.dirname, './anvil.dump.json'), + }) + }, + port: 8545, + }).start() + }, + }, + ], }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9762b560..ce7c5f2d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -25,8 +25,8 @@ catalogs: specifier: ^5.5.4 version: 5.6.3 viem: - specifier: ^2.21.51 - version: 2.21.51 + specifier: ^2.22.3 + version: 2.22.3 wagmi: specifier: ^2.12.30 version: 2.12.30 @@ -59,12 +59,21 @@ importers: '@types/react': specifier: 'catalog:' version: 18.3.12 + '@vitest/coverage-v8': + specifier: 2.1.8 + version: 2.1.8(vitest@2.1.8(@types/node@22.9.0)(terser@5.37.0)) '@wagmi/cli': specifier: ^2.1.18 version: 2.1.18(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10) knip: specifier: ^5.30.6 version: 5.37.1(@types/node@22.9.0)(typescript@5.6.3) + ox: + specifier: ^0.6.0 + version: 0.6.0(typescript@5.6.3)(zod@3.23.8) + prool: + specifier: ^0.0.16 + version: 0.0.16 publint: specifier: ^0.2.12 version: 0.2.12 @@ -85,10 +94,13 @@ importers: version: 5.6.3 viem: specifier: 'catalog:' - version: 2.21.51(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + version: 2.22.3(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + vitest: + specifier: ^2.1.8 + version: 2.1.8(@types/node@22.9.0)(terser@5.37.0) wagmi: specifier: 'catalog:' - version: 2.12.30(@tanstack/query-core@5.59.20)(@tanstack/react-query@5.59.20(react@18.3.1))(@types/node@22.9.0)(@types/react@18.3.12)(@upstash/redis@1.34.3)(bufferutil@4.0.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(terser@5.37.0)(typescript@5.6.3)(utf-8-validate@5.0.10)(viem@2.21.51(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8) + version: 2.12.30(@tanstack/query-core@5.59.20)(@tanstack/react-query@5.59.20(react@18.3.1))(@types/node@22.9.0)(@types/react@18.3.12)(@upstash/redis@1.34.3)(bufferutil@4.0.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(terser@5.37.0)(typescript@5.6.3)(utf-8-validate@5.0.10)(viem@2.22.3(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8) app: dependencies: @@ -143,10 +155,10 @@ importers: version: 18.3.1(react@18.3.1) viem: specifier: 'catalog:' - version: 2.21.51(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + version: 2.22.3(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) wagmi: specifier: 'catalog:' - version: 2.12.30(@tanstack/query-core@5.59.20)(@tanstack/react-query@5.59.20(react@18.3.1))(@types/node@22.9.0)(@types/react@18.3.12)(@upstash/redis@1.34.3)(bufferutil@4.0.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(terser@5.37.0)(typescript@5.6.3)(utf-8-validate@5.0.10)(viem@2.21.51(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8) + version: 2.12.30(@tanstack/query-core@5.59.20)(@tanstack/react-query@5.59.20(react@18.3.1))(@types/node@22.9.0)(@types/react@18.3.12)(@upstash/redis@1.34.3)(bufferutil@4.0.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(terser@5.37.0)(typescript@5.6.3)(utf-8-validate@5.0.10)(viem@2.22.3(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8) devDependencies: '@types/react': specifier: 'catalog:' @@ -155,8 +167,8 @@ importers: specifier: 'catalog:' version: 18.3.1 '@vitejs/plugin-react': - specifier: ^4.3.3 - version: 4.3.3(vite@5.4.11(@types/node@22.9.0)(terser@5.37.0)) + specifier: ^4.3.4 + version: 4.3.4(vite@6.0.7(@types/node@22.9.0)(jiti@2.4.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.0)) globals: specifier: ^15.11.0 version: 15.12.0 @@ -164,14 +176,14 @@ importers: specifier: ~5.6.3 version: 5.6.3 vite: - specifier: ^5.4.10 - version: 5.4.11(@types/node@22.9.0)(terser@5.37.0) + specifier: ^6.0.7 + version: 6.0.7(@types/node@22.9.0)(jiti@2.4.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.0) playground: dependencies: ox: - specifier: ^0.2.2 - version: 0.2.2(typescript@5.6.3)(zod@3.23.8) + specifier: ^0.6.0 + version: 0.6.0(typescript@5.6.3)(zod@3.23.8) porto: specifier: workspace:* version: link:../src @@ -183,7 +195,7 @@ importers: version: 18.3.1(react@18.3.1) viem: specifier: 'catalog:' - version: 2.21.51(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + version: 2.22.3(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) devDependencies: '@types/react': specifier: 'catalog:' @@ -192,17 +204,20 @@ importers: specifier: 'catalog:' version: 18.3.1 '@vitejs/plugin-react': - specifier: ^4.3.1 - version: 4.3.3(vite@5.4.11(@types/node@22.9.0)(terser@5.37.0)) + specifier: ^4.3.4 + version: 4.3.4(vite@6.0.7(@types/node@22.9.0)(jiti@2.4.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.0)) globals: specifier: ^15.9.0 version: 15.12.0 + prool: + specifier: ^0.0.16 + version: 0.0.16 typescript: specifier: ^5.5.3 version: 5.6.3 vite: - specifier: ^5.4.1 - version: 5.4.11(@types/node@22.9.0)(terser@5.37.0) + specifier: ^6.0.7 + version: 6.0.7(@types/node@22.9.0)(jiti@2.4.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.0) src: dependencies: @@ -213,8 +228,8 @@ importers: specifier: ^0.0.7 version: 0.0.7(typescript@5.6.3) ox: - specifier: ^0.2.2 - version: 0.2.2(typescript@5.6.3)(zod@3.23.8) + specifier: ^0.6.0 + version: 0.6.0(typescript@5.6.3)(zod@3.23.8) zustand: specifier: ^5.0.1 version: 5.0.1(@types/react@18.3.12)(react@18.3.1)(use-sync-external-store@1.2.0(react@18.3.1)) @@ -327,6 +342,9 @@ packages: resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@biomejs/biome@1.9.4': resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} engines: {node: '>=14.21.3'} @@ -478,6 +496,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.24.2': + resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.19.12': resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} engines: {node: '>=12'} @@ -496,6 +520,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.24.2': + resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.19.12': resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} engines: {node: '>=12'} @@ -514,6 +544,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.24.2': + resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.19.12': resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} engines: {node: '>=12'} @@ -532,6 +568,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.24.2': + resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.19.12': resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} engines: {node: '>=12'} @@ -550,6 +592,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.24.2': + resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.19.12': resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} engines: {node: '>=12'} @@ -568,6 +616,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.24.2': + resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.19.12': resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} engines: {node: '>=12'} @@ -586,6 +640,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.24.2': + resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.19.12': resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} engines: {node: '>=12'} @@ -604,6 +664,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.24.2': + resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.19.12': resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} engines: {node: '>=12'} @@ -622,6 +688,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.24.2': + resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.19.12': resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} engines: {node: '>=12'} @@ -640,6 +712,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.24.2': + resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.19.12': resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} engines: {node: '>=12'} @@ -658,6 +736,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.24.2': + resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.19.12': resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} engines: {node: '>=12'} @@ -676,6 +760,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.24.2': + resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.19.12': resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} engines: {node: '>=12'} @@ -694,6 +784,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.24.2': + resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.19.12': resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} engines: {node: '>=12'} @@ -712,6 +808,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.24.2': + resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.19.12': resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} engines: {node: '>=12'} @@ -730,6 +832,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.24.2': + resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.19.12': resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} engines: {node: '>=12'} @@ -748,6 +856,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.24.2': + resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.19.12': resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} engines: {node: '>=12'} @@ -766,6 +880,18 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.24.2': + resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.24.2': + resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.19.12': resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} engines: {node: '>=12'} @@ -784,12 +910,24 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.24.2': + resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.23.1': resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.24.2': + resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.19.12': resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} engines: {node: '>=12'} @@ -808,6 +946,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.24.2': + resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/sunos-x64@0.19.12': resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} engines: {node: '>=12'} @@ -826,6 +970,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.24.2': + resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.19.12': resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} engines: {node: '>=12'} @@ -844,6 +994,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.24.2': + resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.19.12': resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} engines: {node: '>=12'} @@ -862,6 +1018,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.24.2': + resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.19.12': resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} engines: {node: '>=12'} @@ -880,6 +1042,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.24.2': + resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@ethereumjs/common@3.2.0': resolution: {integrity: sha512-pksvzI0VyLgmuEF2FA/JR/4/y6hcPq8OUail3/AvycBaW1d5VSauOZzqGvJ3RTmR4MU35lWE8KseKOsEhrFRBA==} @@ -1005,6 +1173,14 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + '@jridgewell/gen-mapping@0.3.5': resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} engines: {node: '>=6.0.0'} @@ -1214,6 +1390,10 @@ packages: resolution: {integrity: sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==} engines: {node: ^14.21.3 || >=16} + '@noble/curves@1.7.0': + resolution: {integrity: sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw==} + engines: {node: ^14.21.3 || >=16} + '@noble/hashes@1.4.0': resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} engines: {node: '>= 16'} @@ -1222,6 +1402,14 @@ packages: resolution: {integrity: sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==} engines: {node: ^14.21.3 || >=16} + '@noble/hashes@1.6.0': + resolution: {integrity: sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.6.1': + resolution: {integrity: sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w==} + engines: {node: ^14.21.3 || >=16} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1439,22 +1627,38 @@ packages: '@scure/base@1.1.9': resolution: {integrity: sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==} + '@scure/base@1.2.1': + resolution: {integrity: sha512-DGmGtC8Tt63J5GfHgfl5CuAXh96VF/LD8K9Hr/Gv0J2lAoRGlPOMpqMpMbCTOoOJMZCk2Xt+DskdDyn6dEFdzQ==} + '@scure/bip32@1.4.0': resolution: {integrity: sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==} '@scure/bip32@1.5.0': resolution: {integrity: sha512-8EnFYkqEQdnkuGBVpCzKxyIwDCBLDVj3oiX0EKUFre/tOjL/Hqba1D6n/8RcmaQy4f95qQFrO2A8Sr6ybh4NRw==} + '@scure/bip32@1.6.0': + resolution: {integrity: sha512-82q1QfklrUUdXJzjuRU7iG7D7XiFx5PHYVS0+oeNKhyDLT7WPqs6pBcM2W5ZdwOwKCwoE1Vy1se+DHjcXwCYnA==} + '@scure/bip39@1.3.0': resolution: {integrity: sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==} '@scure/bip39@1.4.0': resolution: {integrity: sha512-BEEm6p8IueV/ZTfQLp/0vhw4NPnT9oWf5+28nvmeUICjP99f4vr2d+qc7AVGDDtwRep6ifR43Yed9ERVmiITzw==} + '@scure/bip39@1.5.0': + resolution: {integrity: sha512-Dop+ASYhnrwm9+HA/HwXg7j2ZqM6yk2fyLWb5znexjctFY3+E+eU8cIWI0Pql0Qx4hPZCijlGq4OL71g+Uz30A==} + + '@sec-ant/readable-stream@0.4.1': + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + '@sindresorhus/is@4.6.0': resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} + '@sindresorhus/merge-streams@4.0.0': + resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + engines: {node: '>=18'} + '@sitespeed.io/tracium@0.3.3': resolution: {integrity: sha512-dNZafjM93Y+F+sfwTO5gTpsGXlnc/0Q+c2+62ViqP3gkMWvHEMSKkaEHgVJLcLg3i/g19GSIPziiKpgyne07Bw==} engines: {node: '>=8'} @@ -1625,17 +1829,26 @@ packages: '@upstash/redis@1.34.3': resolution: {integrity: sha512-VT25TyODGy/8ljl7GADnJoMmtmJ1F8d84UXfGonRRF8fWYJz7+2J6GzW+a6ETGtk4OyuRTt7FRSvFG5GvrfSdQ==} - '@vitejs/plugin-react@4.3.3': - resolution: {integrity: sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA==} + '@vitejs/plugin-react@4.3.4': + resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: - vite: ^4.2.0 || ^5.0.0 + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 - '@vitest/expect@2.1.4': - resolution: {integrity: sha512-DOETT0Oh1avie/D/o2sgMHGrzYUFFo3zqESB2Hn70z6QB1HrS2IQ9z5DfyTqU8sg4Bpu13zZe9V4+UTNQlUeQA==} + '@vitest/coverage-v8@2.1.8': + resolution: {integrity: sha512-2Y7BPlKH18mAZYAW1tYByudlCYrQyl5RGvnnDYJKW5tCiO5qg3KSAy3XAxcxKz900a0ZXxWtKrMuZLe3lKBpJw==} + peerDependencies: + '@vitest/browser': 2.1.8 + vitest: 2.1.8 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@2.1.8': + resolution: {integrity: sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==} - '@vitest/mocker@2.1.4': - resolution: {integrity: sha512-Ky/O1Lc0QBbutJdW0rqLeFNbuLEyS+mIPiNdlVlp2/yhJ0SbyYqObS5IHdhferJud8MbbwMnexg4jordE5cCoQ==} + '@vitest/mocker@2.1.8': + resolution: {integrity: sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==} peerDependencies: msw: ^2.4.9 vite: ^5.0.0 @@ -1645,20 +1858,20 @@ packages: vite: optional: true - '@vitest/pretty-format@2.1.4': - resolution: {integrity: sha512-L95zIAkEuTDbUX1IsjRl+vyBSLh3PwLLgKpghl37aCK9Jvw0iP+wKwIFhfjdUtA2myLgjrG6VU6JCFLv8q/3Ww==} + '@vitest/pretty-format@2.1.8': + resolution: {integrity: sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==} - '@vitest/runner@2.1.4': - resolution: {integrity: sha512-sKRautINI9XICAMl2bjxQM8VfCMTB0EbsBc/EDFA57V6UQevEKY/TOPOF5nzcvCALltiLfXWbq4MaAwWx/YxIA==} + '@vitest/runner@2.1.8': + resolution: {integrity: sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==} - '@vitest/snapshot@2.1.4': - resolution: {integrity: sha512-3Kab14fn/5QZRog5BPj6Rs8dc4B+mim27XaKWFWHWA87R56AKjHTGcBFKpvZKDzC4u5Wd0w/qKsUIio3KzWW4Q==} + '@vitest/snapshot@2.1.8': + resolution: {integrity: sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==} - '@vitest/spy@2.1.4': - resolution: {integrity: sha512-4JOxa+UAizJgpZfaCPKK2smq9d8mmjZVPMt2kOsg/R8QkoRzydHH1qHxIYNvr1zlEaFj4SXiaaJWxq/LPLKaLg==} + '@vitest/spy@2.1.8': + resolution: {integrity: sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==} - '@vitest/utils@2.1.4': - resolution: {integrity: sha512-MXDnZn0Awl2S86PSNIim5PWXgIAx8CIkzu35mBdSApUip6RFOGXBCf3YFyeEu8n1IHk4bWD46DeYFu9mQlFIRg==} + '@vitest/utils@2.1.8': + resolution: {integrity: sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==} '@wagmi/cli@2.1.18': resolution: {integrity: sha512-1Vqz3Kj0WY/p6vUq1a2m8NH+64wAcoV+fUC8j5BrEgaeCDbQrWdZ2nMLbz/I6ao2oPNvv1eNWCjR6r8QiscXlA==} @@ -1834,6 +2047,17 @@ packages: zod: optional: true + abitype@1.0.7: + resolution: {integrity: sha512-ZfYYSktDQUwc2eduYu8C4wOs+RDPmnRYMh7zNfzeMtGGgb0U+6tLGjixUic6mXf5xKKCcgT5Qp6cv39tOARVFw==} + peerDependencies: + typescript: '>=5.0.4' + zod: ^3 >=3.22.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + acorn@8.14.0: resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} engines: {node: '>=0.4.0'} @@ -2064,6 +2288,10 @@ packages: resolution: {integrity: sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==} engines: {node: '>= 14.16.0'} + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + chrome-trace-event@1.0.4: resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} engines: {node: '>=6.0'} @@ -2399,6 +2627,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.24.2: + resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -2465,6 +2698,9 @@ packages: eventemitter2@6.4.9: resolution: {integrity: sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==} + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} @@ -2476,6 +2712,10 @@ packages: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} + execa@9.5.2: + resolution: {integrity: sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==} + engines: {node: ^18.19.0 || >=20.5.0} + expect-type@1.1.0: resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} engines: {node: '>=12.0.0'} @@ -2533,6 +2773,10 @@ packages: fflate@0.8.2: resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + figures@6.1.0: + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + engines: {node: '>=18'} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -2553,6 +2797,15 @@ packages: resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -2598,6 +2851,10 @@ packages: get-port-please@3.1.2: resolution: {integrity: sha512-Gxc29eLs1fbn6LQ4jSU4vXjlwyZhF5HsGuMAa7gqBP4Rw4yxxltyDUuF5MBclFzDTXO+ACchGQoeela4DSfzdQ==} + get-port@7.1.0: + resolution: {integrity: sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==} + engines: {node: '>=16'} + get-stream@5.2.0: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} @@ -2606,6 +2863,10 @@ packages: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} + get-stream@9.0.1: + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} + get-tsconfig@4.8.1: resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} @@ -2689,10 +2950,17 @@ packages: hmac-drbg@1.0.1: resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} + http-proxy@1.18.1: + resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} + engines: {node: '>=8.0.0'} + http-shutdown@1.2.2: resolution: {integrity: sha512-S9wWkJ/VSY9/k4qcjG318bqJNruzE4HySUhFYknwmu6LBP97KLLfwNf+n4V1BHurvFNkSKLFnK/RsuUnRTf9Vw==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} @@ -2708,6 +2976,10 @@ packages: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} + human-signals@8.0.0: + resolution: {integrity: sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==} + engines: {node: '>=18.18.0'} + i18next-browser-languagedetector@7.1.0: resolution: {integrity: sha512-cr2k7u1XJJ4HTOjM9GyOMtbOA47RtUoWRAtt52z43r3AoMs2StYKyjS3URPhzHaf+mn10hY9dZWamga5WPQjhA==} @@ -2806,6 +3078,10 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -2814,6 +3090,10 @@ packages: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} + is-subdir@1.2.0: resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} engines: {node: '>=4'} @@ -2826,6 +3106,10 @@ packages: resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} engines: {node: '>=12'} + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + is-windows@1.0.2: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} @@ -2849,6 +3133,22 @@ packages: peerDependencies: ws: '*' + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} + jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} @@ -2999,6 +3299,13 @@ packages: magic-string@0.30.12: resolution: {integrity: sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==} + magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + marked-terminal@7.2.1: resolution: {integrity: sha512-rQ1MoMFXZICWNsKMiiHwP/Z+92PLKskTPXj+e7uwXmuMPkNn7iTqC+IvDekVm1MPeC9wYQeLxeFaOvudRR/XbQ==} engines: {node: '>=16.0.0'} @@ -3066,6 +3373,10 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + minizlib@3.0.1: + resolution: {integrity: sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==} + engines: {node: '>= 18'} + mipd@0.0.7: resolution: {integrity: sha512-aAPZPNDQ3uMTdKbuO2YmAw2TxLHO0moa4YKAyETM/DTj5FloZo+a+8tU+iv4GmW+sOxKLSRwcSFuczk+Cpt6fg==} peerDependencies: @@ -3077,6 +3388,11 @@ packages: mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + mlly@1.7.2: resolution: {integrity: sha512-tN3dvVHYVz4DhSXinXIk7u9syPYaJvio118uomkovAtWBT+RdbP6Lfh/5Lvo519YMmwBafwlh20IPTXIStscpA==} @@ -3199,6 +3515,10 @@ packages: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + npm-run-path@6.0.0: + resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} + engines: {node: '>=18'} + obj-multiplex@1.0.0: resolution: {integrity: sha512-0GNJAOsHoBHeNTvl5Vt6IWnpUEcc3uSRxzBri7EDyIcMgYvnY2JL2qdeV5zTMjWQX5OHcD5amcW2HFfDh0gjIA==} @@ -3241,16 +3561,8 @@ packages: outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} - ox@0.1.2: - resolution: {integrity: sha512-ak/8K0Rtphg9vnRJlbOdaX9R7cmxD2MiSthjWGaQdMk3D7hrAlDoM+6Lxn7hN52Za3vrXfZ7enfke/5WjolDww==} - peerDependencies: - typescript: '>=5.4.0' - peerDependenciesMeta: - typescript: - optional: true - - ox@0.2.2: - resolution: {integrity: sha512-QWCyFfVk5hFOhg13SGqRKih5B7EBucrf+Z1dfmN9jJQ8MZdrRx9mbD78JQL5ogSzDT7fcHgyMCaXd/3AWn6xHQ==} + ox@0.6.0: + resolution: {integrity: sha512-blUzTLidvUlshv0O02CnLFqBLidNzPoAZdIth894avUAotTuWziznv6IENv5idRuOSSP3dH8WzcYw84zVdu0Aw==} peerDependencies: typescript: '>=5.4.0' peerDependenciesMeta: @@ -3485,6 +3797,15 @@ packages: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} + prool@0.0.16: + resolution: {integrity: sha512-s+i66jsINIJQd8w5iGunT8hCeM6RSXjL8qgXDTvcIuaAOjVRVMh0/PgbJ90KR3JAprXWXpa5XQnDEc4fLnvy1Q==} + engines: {node: '>=22'} + peerDependencies: + '@pimlico/alto': '*' + peerDependenciesMeta: + '@pimlico/alto': + optional: true + proxy-agent@6.4.0: resolution: {integrity: sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==} engines: {node: '>= 14'} @@ -3608,6 +3929,9 @@ packages: require-main-filename@2.0.0: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} @@ -3627,6 +3951,10 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rimraf@5.0.10: + resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} + hasBin: true + rollup@4.25.0: resolution: {integrity: sha512-uVbClXmR6wvx5R1M3Od4utyLUxrmOcEm3pAtMphn73Apq19PDtHpgZoEvqH2YnnaNUuvKmg2DgRd2Sqv+odyqg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -3880,6 +4208,10 @@ packages: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} + strip-final-newline@4.0.0: + resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} + engines: {node: '>=18'} + strip-json-comments@5.0.1: resolution: {integrity: sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==} engines: {node: '>=14.16'} @@ -3947,6 +4279,10 @@ packages: tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + tar@7.2.0: + resolution: {integrity: sha512-hctwP0Nb4AB60bj8WQgRYaMOuJYRAPMGiQUAotms5igN8ppfQM+IvjQ5HcKu1MaZh2Wy2KWVTe563Yj8dfc14w==} + engines: {node: '>=18'} + term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} @@ -3972,6 +4308,10 @@ packages: engines: {node: '>=10'} hasBin: true + test-exclude@7.0.1: + resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} + engines: {node: '>=18'} + text-decoder@1.2.2: resolution: {integrity: sha512-/MDslo7ZyWTA2vnk1j7XoDVfXsGk3tp+zFEJHJGm0UjIlQifonVFwlVbQDFh8KJzTBnT8ie115TYqir6bclddA==} @@ -4067,6 +4407,10 @@ packages: resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==} engines: {node: '>=4'} + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} + universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} @@ -4177,16 +4521,16 @@ packages: react: optional: true - viem@2.21.51: - resolution: {integrity: sha512-IBZTFoo9cZvMBkFqaJq5G8Ori4IeEDe9AHE5CmOlvNw7ytkC3vdVrJ/APL+V3H4d/5i1FiV331UsckIqQLIM0w==} + viem@2.22.3: + resolution: {integrity: sha512-lO8K4lL5vWfJ9dmeJo9BfwlJJ0vNDrgLXgwFJNzjLJ6eDfOGXr48yzNhqt96ybYS7SlM7ecT7yhJIVfhZLkOkw==} peerDependencies: typescript: '>=5.0.4' peerDependenciesMeta: typescript: optional: true - vite-node@2.1.4: - resolution: {integrity: sha512-kqa9v+oi4HwkG6g8ufRnb5AeplcRw8jUF6/7/Qz1qRQOXHImG8YnLbB+LLszENwFnoBl9xIf9nVdCFzNd7GQEg==} + vite-node@2.1.8: + resolution: {integrity: sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -4221,15 +4565,55 @@ packages: terser: optional: true - vitest@2.1.4: - resolution: {integrity: sha512-eDjxbVAJw1UJJCHr5xr/xM86Zx+YxIEXGAR+bmnEID7z9qWfoxpHw0zdobz+TQAFOLT+nEXz3+gx6nUJ7RgmlQ==} + vite@6.0.7: + resolution: {integrity: sha512-RDt8r/7qx9940f8FcOIAH9PTViRrghKaK2K1jY3RaAURrEUbm9Du1mJ72G+jlhtG3WwodnfzY8ORQZbBavZEAQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@2.1.8: + resolution: {integrity: sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 2.1.4 - '@vitest/ui': 2.1.4 + '@vitest/browser': 2.1.8 + '@vitest/ui': 2.1.8 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -4398,6 +4782,10 @@ packages: yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + yaml@2.6.0: resolution: {integrity: sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==} engines: {node: '>= 14'} @@ -4434,6 +4822,10 @@ packages: resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==} engines: {node: '>=12.20'} + yoctocolors@2.1.1: + resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==} + engines: {node: '>=18'} + zod-validation-error@3.4.0: resolution: {integrity: sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ==} engines: {node: '>=18.0.0'} @@ -4536,7 +4928,7 @@ snapshots: '@babel/traverse': 7.25.9 '@babel/types': 7.26.0 convert-source-map: 2.0.0 - debug: 4.3.7 + debug: 4.4.0 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -4619,7 +5011,7 @@ snapshots: '@babel/parser': 7.26.2 '@babel/template': 7.25.9 '@babel/types': 7.26.0 - debug: 4.3.7 + debug: 4.4.0 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -4629,6 +5021,8 @@ snapshots: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 + '@bcoe/v8-coverage@0.2.3': {} + '@biomejs/biome@1.9.4': optionalDependencies: '@biomejs/cli-darwin-arm64': 1.9.4 @@ -4837,11 +5231,11 @@ snapshots: '@coinbase/wallet-sdk@4.2.1(@types/node@22.9.0)(terser@5.37.0)': dependencies: - '@noble/hashes': 1.5.0 + '@noble/hashes': 1.6.1 clsx: 1.2.1 eventemitter3: 5.0.1 preact: 10.24.3 - vitest: 2.1.4(@types/node@22.9.0)(terser@5.37.0) + vitest: 2.1.8(@types/node@22.9.0)(terser@5.37.0) transitivePeerDependencies: - '@edge-runtime/vm' - '@types/node' @@ -4880,6 +5274,9 @@ snapshots: '@esbuild/aix-ppc64@0.23.1': optional: true + '@esbuild/aix-ppc64@0.24.2': + optional: true + '@esbuild/android-arm64@0.19.12': optional: true @@ -4889,6 +5286,9 @@ snapshots: '@esbuild/android-arm64@0.23.1': optional: true + '@esbuild/android-arm64@0.24.2': + optional: true + '@esbuild/android-arm@0.19.12': optional: true @@ -4898,6 +5298,9 @@ snapshots: '@esbuild/android-arm@0.23.1': optional: true + '@esbuild/android-arm@0.24.2': + optional: true + '@esbuild/android-x64@0.19.12': optional: true @@ -4907,6 +5310,9 @@ snapshots: '@esbuild/android-x64@0.23.1': optional: true + '@esbuild/android-x64@0.24.2': + optional: true + '@esbuild/darwin-arm64@0.19.12': optional: true @@ -4916,6 +5322,9 @@ snapshots: '@esbuild/darwin-arm64@0.23.1': optional: true + '@esbuild/darwin-arm64@0.24.2': + optional: true + '@esbuild/darwin-x64@0.19.12': optional: true @@ -4925,6 +5334,9 @@ snapshots: '@esbuild/darwin-x64@0.23.1': optional: true + '@esbuild/darwin-x64@0.24.2': + optional: true + '@esbuild/freebsd-arm64@0.19.12': optional: true @@ -4934,6 +5346,9 @@ snapshots: '@esbuild/freebsd-arm64@0.23.1': optional: true + '@esbuild/freebsd-arm64@0.24.2': + optional: true + '@esbuild/freebsd-x64@0.19.12': optional: true @@ -4943,6 +5358,9 @@ snapshots: '@esbuild/freebsd-x64@0.23.1': optional: true + '@esbuild/freebsd-x64@0.24.2': + optional: true + '@esbuild/linux-arm64@0.19.12': optional: true @@ -4952,6 +5370,9 @@ snapshots: '@esbuild/linux-arm64@0.23.1': optional: true + '@esbuild/linux-arm64@0.24.2': + optional: true + '@esbuild/linux-arm@0.19.12': optional: true @@ -4961,6 +5382,9 @@ snapshots: '@esbuild/linux-arm@0.23.1': optional: true + '@esbuild/linux-arm@0.24.2': + optional: true + '@esbuild/linux-ia32@0.19.12': optional: true @@ -4970,6 +5394,9 @@ snapshots: '@esbuild/linux-ia32@0.23.1': optional: true + '@esbuild/linux-ia32@0.24.2': + optional: true + '@esbuild/linux-loong64@0.19.12': optional: true @@ -4979,6 +5406,9 @@ snapshots: '@esbuild/linux-loong64@0.23.1': optional: true + '@esbuild/linux-loong64@0.24.2': + optional: true + '@esbuild/linux-mips64el@0.19.12': optional: true @@ -4988,6 +5418,9 @@ snapshots: '@esbuild/linux-mips64el@0.23.1': optional: true + '@esbuild/linux-mips64el@0.24.2': + optional: true + '@esbuild/linux-ppc64@0.19.12': optional: true @@ -4997,6 +5430,9 @@ snapshots: '@esbuild/linux-ppc64@0.23.1': optional: true + '@esbuild/linux-ppc64@0.24.2': + optional: true + '@esbuild/linux-riscv64@0.19.12': optional: true @@ -5006,6 +5442,9 @@ snapshots: '@esbuild/linux-riscv64@0.23.1': optional: true + '@esbuild/linux-riscv64@0.24.2': + optional: true + '@esbuild/linux-s390x@0.19.12': optional: true @@ -5015,6 +5454,9 @@ snapshots: '@esbuild/linux-s390x@0.23.1': optional: true + '@esbuild/linux-s390x@0.24.2': + optional: true + '@esbuild/linux-x64@0.19.12': optional: true @@ -5024,6 +5466,12 @@ snapshots: '@esbuild/linux-x64@0.23.1': optional: true + '@esbuild/linux-x64@0.24.2': + optional: true + + '@esbuild/netbsd-arm64@0.24.2': + optional: true + '@esbuild/netbsd-x64@0.19.12': optional: true @@ -5033,9 +5481,15 @@ snapshots: '@esbuild/netbsd-x64@0.23.1': optional: true + '@esbuild/netbsd-x64@0.24.2': + optional: true + '@esbuild/openbsd-arm64@0.23.1': optional: true + '@esbuild/openbsd-arm64@0.24.2': + optional: true + '@esbuild/openbsd-x64@0.19.12': optional: true @@ -5045,6 +5499,9 @@ snapshots: '@esbuild/openbsd-x64@0.23.1': optional: true + '@esbuild/openbsd-x64@0.24.2': + optional: true + '@esbuild/sunos-x64@0.19.12': optional: true @@ -5054,6 +5511,9 @@ snapshots: '@esbuild/sunos-x64@0.23.1': optional: true + '@esbuild/sunos-x64@0.24.2': + optional: true + '@esbuild/win32-arm64@0.19.12': optional: true @@ -5063,6 +5523,9 @@ snapshots: '@esbuild/win32-arm64@0.23.1': optional: true + '@esbuild/win32-arm64@0.24.2': + optional: true + '@esbuild/win32-ia32@0.19.12': optional: true @@ -5072,6 +5535,9 @@ snapshots: '@esbuild/win32-ia32@0.23.1': optional: true + '@esbuild/win32-ia32@0.24.2': + optional: true + '@esbuild/win32-x64@0.19.12': optional: true @@ -5081,6 +5547,9 @@ snapshots: '@esbuild/win32-x64@0.23.1': optional: true + '@esbuild/win32-x64@0.24.2': + optional: true + '@ethereumjs/common@3.2.0': dependencies: '@ethereumjs/util': 8.1.0 @@ -5185,6 +5654,12 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + + '@istanbuljs/schema@0.1.3': {} + '@jridgewell/gen-mapping@0.3.5': dependencies: '@jridgewell/set-array': 1.2.1 @@ -5304,7 +5779,7 @@ snapshots: bufferutil: 4.0.8 cross-fetch: 4.0.0 date-fns: 2.30.0 - debug: 4.3.7 + debug: 4.4.0 eciesjs: 0.4.11 eventemitter2: 6.4.9 readable-stream: 3.6.2 @@ -5330,7 +5805,7 @@ snapshots: '@metamask/sdk-install-modal-web': 0.30.0(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) bowser: 2.11.0 cross-fetch: 4.0.0 - debug: 4.3.7 + debug: 4.4.0 eciesjs: 0.4.11 eth-rpc-errors: 4.0.3 eventemitter2: 6.4.9 @@ -5370,10 +5845,10 @@ snapshots: dependencies: '@ethereumjs/tx': 4.2.0 '@metamask/superstruct': 3.1.0 - '@noble/hashes': 1.5.0 + '@noble/hashes': 1.6.1 '@scure/base': 1.1.9 '@types/debug': 4.1.12 - debug: 4.3.7 + debug: 4.4.0 pony-cause: 2.1.11 semver: 7.6.3 uuid: 9.0.1 @@ -5384,10 +5859,10 @@ snapshots: dependencies: '@ethereumjs/tx': 4.2.0 '@metamask/superstruct': 3.1.0 - '@noble/hashes': 1.5.0 + '@noble/hashes': 1.6.1 '@scure/base': 1.1.9 '@types/debug': 4.1.12 - debug: 4.3.7 + debug: 4.4.0 pony-cause: 2.1.11 semver: 7.6.3 uuid: 9.0.1 @@ -5475,10 +5950,18 @@ snapshots: dependencies: '@noble/hashes': 1.5.0 + '@noble/curves@1.7.0': + dependencies: + '@noble/hashes': 1.6.0 + '@noble/hashes@1.4.0': {} '@noble/hashes@1.5.0': {} + '@noble/hashes@1.6.0': {} + + '@noble/hashes@1.6.1': {} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -5652,7 +6135,7 @@ snapshots: '@safe-global/safe-apps-sdk@9.1.0(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)': dependencies: '@safe-global/safe-gateway-typescript-sdk': 3.22.2 - viem: 2.21.51(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + viem: 2.22.3(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) transitivePeerDependencies: - bufferutil - typescript @@ -5663,6 +6146,8 @@ snapshots: '@scure/base@1.1.9': {} + '@scure/base@1.2.1': {} + '@scure/bip32@1.4.0': dependencies: '@noble/curves': 1.4.2 @@ -5675,6 +6160,12 @@ snapshots: '@noble/hashes': 1.5.0 '@scure/base': 1.1.9 + '@scure/bip32@1.6.0': + dependencies: + '@noble/curves': 1.7.0 + '@noble/hashes': 1.6.1 + '@scure/base': 1.2.1 + '@scure/bip39@1.3.0': dependencies: '@noble/hashes': 1.4.0 @@ -5685,11 +6176,20 @@ snapshots: '@noble/hashes': 1.5.0 '@scure/base': 1.1.9 + '@scure/bip39@1.5.0': + dependencies: + '@noble/hashes': 1.6.1 + '@scure/base': 1.2.1 + + '@sec-ant/readable-stream@0.4.1': {} + '@sindresorhus/is@4.6.0': {} + '@sindresorhus/merge-streams@4.0.0': {} + '@sitespeed.io/tracium@0.3.3': dependencies: - debug: 4.3.7 + debug: 4.4.0 transitivePeerDependencies: - supports-color @@ -5916,54 +6416,72 @@ snapshots: dependencies: crypto-js: 4.2.0 - '@vitejs/plugin-react@4.3.3(vite@5.4.11(@types/node@22.9.0)(terser@5.37.0))': + '@vitejs/plugin-react@4.3.4(vite@6.0.7(@types/node@22.9.0)(jiti@2.4.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.0))': dependencies: '@babel/core': 7.26.0 '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0) '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 5.4.11(@types/node@22.9.0)(terser@5.37.0) + vite: 6.0.7(@types/node@22.9.0)(jiti@2.4.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.0) + transitivePeerDependencies: + - supports-color + + '@vitest/coverage-v8@2.1.8(vitest@2.1.8(@types/node@22.9.0)(terser@5.37.0))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 0.2.3 + debug: 4.4.0 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.1.7 + magic-string: 0.30.12 + magicast: 0.3.5 + std-env: 3.8.0 + test-exclude: 7.0.1 + tinyrainbow: 1.2.0 + vitest: 2.1.8(@types/node@22.9.0)(terser@5.37.0) transitivePeerDependencies: - supports-color - '@vitest/expect@2.1.4': + '@vitest/expect@2.1.8': dependencies: - '@vitest/spy': 2.1.4 - '@vitest/utils': 2.1.4 + '@vitest/spy': 2.1.8 + '@vitest/utils': 2.1.8 chai: 5.1.2 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.4(vite@5.4.11(@types/node@22.9.0)(terser@5.37.0))': + '@vitest/mocker@2.1.8(vite@5.4.11(@types/node@22.9.0)(terser@5.37.0))': dependencies: - '@vitest/spy': 2.1.4 + '@vitest/spy': 2.1.8 estree-walker: 3.0.3 magic-string: 0.30.12 optionalDependencies: vite: 5.4.11(@types/node@22.9.0)(terser@5.37.0) - '@vitest/pretty-format@2.1.4': + '@vitest/pretty-format@2.1.8': dependencies: tinyrainbow: 1.2.0 - '@vitest/runner@2.1.4': + '@vitest/runner@2.1.8': dependencies: - '@vitest/utils': 2.1.4 + '@vitest/utils': 2.1.8 pathe: 1.1.2 - '@vitest/snapshot@2.1.4': + '@vitest/snapshot@2.1.8': dependencies: - '@vitest/pretty-format': 2.1.4 + '@vitest/pretty-format': 2.1.8 magic-string: 0.30.12 pathe: 1.1.2 - '@vitest/spy@2.1.4': + '@vitest/spy@2.1.8': dependencies: tinyspy: 3.0.2 - '@vitest/utils@2.1.4': + '@vitest/utils@2.1.8': dependencies: - '@vitest/pretty-format': 2.1.4 + '@vitest/pretty-format': 2.1.8 loupe: 3.1.2 tinyrainbow: 1.2.0 @@ -5987,7 +6505,7 @@ snapshots: picocolors: 1.1.1 picomatch: 3.0.1 prettier: 3.3.3 - viem: 2.21.51(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + viem: 2.22.3(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) zod: 3.23.8 optionalDependencies: typescript: 5.6.3 @@ -5995,16 +6513,16 @@ snapshots: - bufferutil - utf-8-validate - '@wagmi/connectors@5.3.8(@types/node@22.9.0)(@types/react@18.3.12)(@upstash/redis@1.34.3)(@wagmi/core@2.14.5(@tanstack/query-core@5.59.20)(@types/react@18.3.12)(react@18.3.1)(typescript@5.6.3)(use-sync-external-store@1.2.0(react@18.3.1))(viem@2.21.51(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)))(bufferutil@4.0.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(terser@5.37.0)(typescript@5.6.3)(utf-8-validate@5.0.10)(viem@2.21.51(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8)': + '@wagmi/connectors@5.3.8(@types/node@22.9.0)(@types/react@18.3.12)(@upstash/redis@1.34.3)(@wagmi/core@2.14.5(@tanstack/query-core@5.59.20)(@types/react@18.3.12)(react@18.3.1)(typescript@5.6.3)(use-sync-external-store@1.2.0(react@18.3.1))(viem@2.22.3(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)))(bufferutil@4.0.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(terser@5.37.0)(typescript@5.6.3)(utf-8-validate@5.0.10)(viem@2.22.3(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8)': dependencies: '@coinbase/wallet-sdk': 4.2.1(@types/node@22.9.0)(terser@5.37.0) '@metamask/sdk': 0.30.1(bufferutil@4.0.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(utf-8-validate@5.0.10) '@safe-global/safe-apps-provider': 0.18.4(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) '@safe-global/safe-apps-sdk': 9.1.0(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) - '@wagmi/core': 2.14.5(@tanstack/query-core@5.59.20)(@types/react@18.3.12)(react@18.3.1)(typescript@5.6.3)(use-sync-external-store@1.2.0(react@18.3.1))(viem@2.21.51(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)) + '@wagmi/core': 2.14.5(@tanstack/query-core@5.59.20)(@types/react@18.3.12)(react@18.3.1)(typescript@5.6.3)(use-sync-external-store@1.2.0(react@18.3.1))(viem@2.22.3(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)) '@walletconnect/ethereum-provider': 2.17.0(@types/react@18.3.12)(@upstash/redis@1.34.3)(bufferutil@4.0.8)(react@18.3.1)(utf-8-validate@5.0.10) cbw-sdk: '@coinbase/wallet-sdk@3.9.3' - viem: 2.21.51(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + viem: 2.22.3(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) optionalDependencies: typescript: 5.6.3 transitivePeerDependencies: @@ -6045,11 +6563,11 @@ snapshots: - utf-8-validate - zod - '@wagmi/core@2.14.5(@tanstack/query-core@5.59.20)(@types/react@18.3.12)(react@18.3.1)(typescript@5.6.3)(use-sync-external-store@1.2.0(react@18.3.1))(viem@2.21.51(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))': + '@wagmi/core@2.14.5(@tanstack/query-core@5.59.20)(@types/react@18.3.12)(react@18.3.1)(typescript@5.6.3)(use-sync-external-store@1.2.0(react@18.3.1))(viem@2.22.3(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))': dependencies: eventemitter3: 5.0.1 mipd: 0.0.7(typescript@5.6.3) - viem: 2.21.51(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + viem: 2.22.3(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) zustand: 5.0.0(@types/react@18.3.12)(react@18.3.1)(use-sync-external-store@1.2.0(react@18.3.1)) optionalDependencies: '@tanstack/query-core': 5.59.20 @@ -6455,6 +6973,11 @@ snapshots: typescript: 5.6.3 zod: 3.23.8 + abitype@1.0.7(typescript@5.6.3)(zod@3.23.8): + optionalDependencies: + typescript: 5.6.3 + zod: 3.23.8 + acorn@8.14.0: {} agent-base@7.1.3: {} @@ -6674,6 +7197,8 @@ snapshots: dependencies: readdirp: 4.0.2 + chownr@3.0.0: {} + chrome-trace-event@1.0.4: {} chromium-bidi@0.5.17(devtools-protocol@0.0.1262051): @@ -6910,8 +7435,8 @@ snapshots: dependencies: '@ecies/ciphers': 0.2.1(@noble/ciphers@1.0.0) '@noble/ciphers': 1.0.0 - '@noble/curves': 1.6.0 - '@noble/hashes': 1.5.0 + '@noble/curves': 1.7.0 + '@noble/hashes': 1.6.1 electron-to-chromium@1.5.56: {} @@ -7050,6 +7575,34 @@ snapshots: '@esbuild/win32-ia32': 0.23.1 '@esbuild/win32-x64': 0.23.1 + esbuild@0.24.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.24.2 + '@esbuild/android-arm': 0.24.2 + '@esbuild/android-arm64': 0.24.2 + '@esbuild/android-x64': 0.24.2 + '@esbuild/darwin-arm64': 0.24.2 + '@esbuild/darwin-x64': 0.24.2 + '@esbuild/freebsd-arm64': 0.24.2 + '@esbuild/freebsd-x64': 0.24.2 + '@esbuild/linux-arm': 0.24.2 + '@esbuild/linux-arm64': 0.24.2 + '@esbuild/linux-ia32': 0.24.2 + '@esbuild/linux-loong64': 0.24.2 + '@esbuild/linux-mips64el': 0.24.2 + '@esbuild/linux-ppc64': 0.24.2 + '@esbuild/linux-riscv64': 0.24.2 + '@esbuild/linux-s390x': 0.24.2 + '@esbuild/linux-x64': 0.24.2 + '@esbuild/netbsd-arm64': 0.24.2 + '@esbuild/netbsd-x64': 0.24.2 + '@esbuild/openbsd-arm64': 0.24.2 + '@esbuild/openbsd-x64': 0.24.2 + '@esbuild/sunos-x64': 0.24.2 + '@esbuild/win32-arm64': 0.24.2 + '@esbuild/win32-ia32': 0.24.2 + '@esbuild/win32-x64': 0.24.2 + escalade@3.2.0: {} escape-string-regexp@2.0.0: {} @@ -7131,6 +7684,8 @@ snapshots: eventemitter2@6.4.9: {} + eventemitter3@4.0.7: {} + eventemitter3@5.0.1: {} events@3.3.0: {} @@ -7147,6 +7702,21 @@ snapshots: signal-exit: 4.1.0 strip-final-newline: 3.0.0 + execa@9.5.2: + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + cross-spawn: 7.0.5 + figures: 6.1.0 + get-stream: 9.0.1 + human-signals: 8.0.0 + is-plain-obj: 4.1.0 + is-stream: 4.0.1 + npm-run-path: 6.0.0 + pretty-ms: 9.2.0 + signal-exit: 4.1.0 + strip-final-newline: 4.0.0 + yoctocolors: 2.1.1 + expect-type@1.1.0: {} extendable-error@0.1.7: {} @@ -7164,7 +7734,7 @@ snapshots: extract-zip@2.0.1: dependencies: - debug: 4.3.7 + debug: 4.4.0 get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -7208,6 +7778,10 @@ snapshots: fflate@0.8.2: {} + figures@6.1.0: + dependencies: + is-unicode-supported: 2.1.0 + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -7230,6 +7804,8 @@ snapshots: locate-path: 7.2.0 path-exists: 5.0.0 + follow-redirects@1.15.9: {} + for-each@0.3.3: dependencies: is-callable: 1.2.7 @@ -7278,12 +7854,19 @@ snapshots: get-port-please@3.1.2: {} + get-port@7.1.0: {} + get-stream@5.2.0: dependencies: pump: 3.0.2 get-stream@8.0.1: {} + get-stream@9.0.1: + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 + get-tsconfig@4.8.1: dependencies: resolve-pkg-maps: 1.0.0 @@ -7292,7 +7875,7 @@ snapshots: dependencies: basic-ftp: 5.0.5 data-uri-to-buffer: 6.0.2 - debug: 4.3.7 + debug: 4.4.0 transitivePeerDependencies: - supports-color @@ -7388,19 +7971,29 @@ snapshots: minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 + html-escaper@2.0.2: {} + http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.3 - debug: 4.3.7 + debug: 4.4.0 transitivePeerDependencies: - supports-color + http-proxy@1.18.1: + dependencies: + eventemitter3: 4.0.7 + follow-redirects: 1.15.9 + requires-port: 1.0.0 + transitivePeerDependencies: + - debug + http-shutdown@1.2.2: {} https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.3 - debug: 4.3.7 + debug: 4.4.0 transitivePeerDependencies: - supports-color @@ -7408,6 +8001,8 @@ snapshots: human-signals@5.0.0: {} + human-signals@8.0.0: {} + i18next-browser-languagedetector@7.1.0: dependencies: '@babel/runtime': 7.26.0 @@ -7490,10 +8085,14 @@ snapshots: is-number@7.0.0: {} + is-plain-obj@4.1.0: {} + is-stream@2.0.1: {} is-stream@3.0.0: {} + is-stream@4.0.1: {} + is-subdir@1.2.0: dependencies: better-path-resolve: 1.0.0 @@ -7504,6 +8103,8 @@ snapshots: is-unicode-supported@1.3.0: {} + is-unicode-supported@2.1.0: {} + is-windows@1.0.2: {} is-wsl@3.1.0: @@ -7522,6 +8123,27 @@ snapshots: dependencies: ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + debug: 4.4.0 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.1.7: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 @@ -7696,6 +8318,16 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + magicast@0.3.5: + dependencies: + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.6.3 + marked-terminal@7.2.1(marked@9.1.6): dependencies: ansi-escapes: 7.0.0 @@ -7748,12 +8380,19 @@ snapshots: minipass@7.1.2: {} + minizlib@3.0.1: + dependencies: + minipass: 7.1.2 + rimraf: 5.0.10 + mipd@0.0.7(typescript@5.6.3): optionalDependencies: typescript: 5.6.3 mitt@3.0.1: {} + mkdirp@3.0.1: {} + mlly@1.7.2: dependencies: acorn: 8.14.0 @@ -7865,6 +8504,11 @@ snapshots: dependencies: path-key: 4.0.0 + npm-run-path@6.0.0: + dependencies: + path-key: 4.0.0 + unicorn-magic: 0.3.0 + obj-multiplex@1.0.0: dependencies: end-of-stream: 1.4.4 @@ -7913,21 +8557,7 @@ snapshots: outdent@0.5.0: {} - ox@0.1.2(typescript@5.6.3)(zod@3.23.8): - dependencies: - '@adraffy/ens-normalize': 1.11.0 - '@noble/curves': 1.6.0 - '@noble/hashes': 1.5.0 - '@scure/bip32': 1.5.0 - '@scure/bip39': 1.4.0 - abitype: 1.0.6(typescript@5.6.3)(zod@3.23.8) - eventemitter3: 5.0.1 - optionalDependencies: - typescript: 5.6.3 - transitivePeerDependencies: - - zod - - ox@0.2.2(typescript@5.6.3)(zod@3.23.8): + ox@0.6.0(typescript@5.6.3)(zod@3.23.8): dependencies: '@adraffy/ens-normalize': 1.11.0 '@noble/curves': 1.6.0 @@ -7973,7 +8603,7 @@ snapshots: dependencies: '@tootallnate/quickjs-emscripten': 0.23.0 agent-base: 7.1.3 - debug: 4.3.7 + debug: 4.4.0 get-uri: 6.0.4 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 @@ -8134,10 +8764,21 @@ snapshots: progress@2.0.3: {} + prool@0.0.16: + dependencies: + change-case: 5.4.4 + eventemitter3: 5.0.1 + execa: 9.5.2 + get-port: 7.1.0 + http-proxy: 1.18.1 + tar: 7.2.0 + transitivePeerDependencies: + - debug + proxy-agent@6.4.0: dependencies: agent-base: 7.1.3 - debug: 4.3.7 + debug: 4.4.0 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 lru-cache: 7.18.3 @@ -8284,6 +8925,8 @@ snapshots: require-main-filename@2.0.0: {} + requires-port@1.0.0: {} + resolve-from@5.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -8301,6 +8944,10 @@ snapshots: reusify@1.0.4: {} + rimraf@5.0.10: + dependencies: + glob: 10.4.5 + rollup@4.25.0: dependencies: '@types/estree': 1.0.6 @@ -8499,7 +9146,7 @@ snapshots: socks-proxy-agent@8.0.5: dependencies: agent-base: 7.1.3 - debug: 4.3.7 + debug: 4.4.0 socks: 2.8.3 transitivePeerDependencies: - supports-color @@ -8589,6 +9236,8 @@ snapshots: strip-final-newline@3.0.0: {} + strip-final-newline@4.0.0: {} + strip-json-comments@5.0.1: {} styled-jsx@5.1.6(react@18.3.1): @@ -8678,6 +9327,15 @@ snapshots: fast-fifo: 1.3.2 streamx: 2.21.1 + tar@7.2.0: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.0.1 + mkdirp: 3.0.1 + yallist: 5.0.0 + term-size@2.2.1: {} terser-webpack-plugin@5.3.10(webpack@5.97.1): @@ -8696,6 +9354,12 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 + test-exclude@7.0.1: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 10.4.5 + minimatch: 9.0.5 + text-decoder@1.2.2: dependencies: b4a: 1.6.7 @@ -8781,6 +9445,8 @@ snapshots: unicode-emoji-modifier-base@1.0.0: {} + unicorn-magic@0.3.0: {} + universalify@0.1.2: {} universalify@2.0.1: {} @@ -8853,15 +9519,15 @@ snapshots: '@types/react': 18.3.12 react: 18.3.1 - viem@2.21.51(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8): + viem@2.22.3(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8): dependencies: - '@noble/curves': 1.6.0 - '@noble/hashes': 1.5.0 - '@scure/bip32': 1.5.0 - '@scure/bip39': 1.4.0 - abitype: 1.0.6(typescript@5.6.3)(zod@3.23.8) + '@noble/curves': 1.7.0 + '@noble/hashes': 1.6.1 + '@scure/bip32': 1.6.0 + '@scure/bip39': 1.5.0 + abitype: 1.0.7(typescript@5.6.3)(zod@3.23.8) isows: 1.0.6(ws@8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - ox: 0.1.2(typescript@5.6.3)(zod@3.23.8) + ox: 0.6.0(typescript@5.6.3)(zod@3.23.8) webauthn-p256: 0.0.10 ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) optionalDependencies: @@ -8871,10 +9537,11 @@ snapshots: - utf-8-validate - zod - vite-node@2.1.4(@types/node@22.9.0)(terser@5.37.0): + vite-node@2.1.8(@types/node@22.9.0)(terser@5.37.0): dependencies: cac: 6.7.14 - debug: 4.3.7 + debug: 4.4.0 + es-module-lexer: 1.5.4 pathe: 1.1.2 vite: 5.4.11(@types/node@22.9.0)(terser@5.37.0) transitivePeerDependencies: @@ -8898,17 +9565,30 @@ snapshots: fsevents: 2.3.3 terser: 5.37.0 - vitest@2.1.4(@types/node@22.9.0)(terser@5.37.0): + vite@6.0.7(@types/node@22.9.0)(jiti@2.4.0)(terser@5.37.0)(tsx@4.19.2)(yaml@2.6.0): + dependencies: + esbuild: 0.24.2 + postcss: 8.4.49 + rollup: 4.25.0 + optionalDependencies: + '@types/node': 22.9.0 + fsevents: 2.3.3 + jiti: 2.4.0 + terser: 5.37.0 + tsx: 4.19.2 + yaml: 2.6.0 + + vitest@2.1.8(@types/node@22.9.0)(terser@5.37.0): dependencies: - '@vitest/expect': 2.1.4 - '@vitest/mocker': 2.1.4(vite@5.4.11(@types/node@22.9.0)(terser@5.37.0)) - '@vitest/pretty-format': 2.1.4 - '@vitest/runner': 2.1.4 - '@vitest/snapshot': 2.1.4 - '@vitest/spy': 2.1.4 - '@vitest/utils': 2.1.4 + '@vitest/expect': 2.1.8 + '@vitest/mocker': 2.1.8(vite@5.4.11(@types/node@22.9.0)(terser@5.37.0)) + '@vitest/pretty-format': 2.1.8 + '@vitest/runner': 2.1.8 + '@vitest/snapshot': 2.1.8 + '@vitest/spy': 2.1.8 + '@vitest/utils': 2.1.8 chai: 5.1.2 - debug: 4.3.7 + debug: 4.4.0 expect-type: 1.1.0 magic-string: 0.30.12 pathe: 1.1.2 @@ -8918,7 +9598,7 @@ snapshots: tinypool: 1.0.1 tinyrainbow: 1.2.0 vite: 5.4.11(@types/node@22.9.0)(terser@5.37.0) - vite-node: 2.1.4(@types/node@22.9.0)(terser@5.37.0) + vite-node: 2.1.8(@types/node@22.9.0)(terser@5.37.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 22.9.0 @@ -8933,14 +9613,14 @@ snapshots: - supports-color - terser - wagmi@2.12.30(@tanstack/query-core@5.59.20)(@tanstack/react-query@5.59.20(react@18.3.1))(@types/node@22.9.0)(@types/react@18.3.12)(@upstash/redis@1.34.3)(bufferutil@4.0.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(terser@5.37.0)(typescript@5.6.3)(utf-8-validate@5.0.10)(viem@2.21.51(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8): + wagmi@2.12.30(@tanstack/query-core@5.59.20)(@tanstack/react-query@5.59.20(react@18.3.1))(@types/node@22.9.0)(@types/react@18.3.12)(@upstash/redis@1.34.3)(bufferutil@4.0.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(terser@5.37.0)(typescript@5.6.3)(utf-8-validate@5.0.10)(viem@2.22.3(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8): dependencies: '@tanstack/react-query': 5.59.20(react@18.3.1) - '@wagmi/connectors': 5.3.8(@types/node@22.9.0)(@types/react@18.3.12)(@upstash/redis@1.34.3)(@wagmi/core@2.14.5(@tanstack/query-core@5.59.20)(@types/react@18.3.12)(react@18.3.1)(typescript@5.6.3)(use-sync-external-store@1.2.0(react@18.3.1))(viem@2.21.51(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)))(bufferutil@4.0.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(terser@5.37.0)(typescript@5.6.3)(utf-8-validate@5.0.10)(viem@2.21.51(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8) - '@wagmi/core': 2.14.5(@tanstack/query-core@5.59.20)(@types/react@18.3.12)(react@18.3.1)(typescript@5.6.3)(use-sync-external-store@1.2.0(react@18.3.1))(viem@2.21.51(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)) + '@wagmi/connectors': 5.3.8(@types/node@22.9.0)(@types/react@18.3.12)(@upstash/redis@1.34.3)(@wagmi/core@2.14.5(@tanstack/query-core@5.59.20)(@types/react@18.3.12)(react@18.3.1)(typescript@5.6.3)(use-sync-external-store@1.2.0(react@18.3.1))(viem@2.22.3(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)))(bufferutil@4.0.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(terser@5.37.0)(typescript@5.6.3)(utf-8-validate@5.0.10)(viem@2.22.3(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8) + '@wagmi/core': 2.14.5(@tanstack/query-core@5.59.20)(@types/react@18.3.12)(react@18.3.1)(typescript@5.6.3)(use-sync-external-store@1.2.0(react@18.3.1))(viem@2.22.3(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)) react: 18.3.1 use-sync-external-store: 1.2.0(react@18.3.1) - viem: 2.21.51(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + viem: 2.22.3(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) optionalDependencies: typescript: 5.6.3 transitivePeerDependencies: @@ -8993,8 +9673,8 @@ snapshots: webauthn-p256@0.0.10: dependencies: - '@noble/curves': 1.6.0 - '@noble/hashes': 1.5.0 + '@noble/curves': 1.7.0 + '@noble/hashes': 1.6.1 webextension-polyfill@0.10.0: {} @@ -9114,6 +9794,8 @@ snapshots: yallist@4.0.0: {} + yallist@5.0.0: {} + yaml@2.6.0: {} yargs-parser@18.1.3: @@ -9166,6 +9848,8 @@ snapshots: yocto-queue@1.1.1: {} + yoctocolors@2.1.1: {} + zod-validation-error@3.4.0(zod@3.23.8): dependencies: zod: 3.23.8 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index f873e200..5fc254f0 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -11,5 +11,5 @@ catalog: react: "^18.3.1" react-dom: "^18.3.1" typescript: "^5.5.4" - viem: "^2.21.51" + viem: "^2.22.3" wagmi: "^2.12.30" diff --git a/src/core/Chains.ts b/src/core/Chains.ts index fd7be62a..f15f509c 100644 --- a/src/core/Chains.ts +++ b/src/core/Chains.ts @@ -1,11 +1,11 @@ import type * as Address from 'ox/Address' import type { Chain as Chain_viem } from 'viem' import * as chains from 'viem/chains' -import { experimentalDelegationAddress } from './internal/generated.js' +import { delegationAddress } from './internal/generated.js' export type Chain = Chain_viem & { contracts: Chain_viem['contracts'] & { - accountDelegation: { + delegation: { address: Address.Address } } @@ -19,8 +19,8 @@ export const odysseyTestnet = /*#__PURE__*/ define({ ...chains.odysseyTestnet, contracts: { ...chains.odysseyTestnet.contracts, - accountDelegation: { - address: experimentalDelegationAddress[chains.odysseyTestnet.id], + delegation: { + address: delegationAddress[chains.odysseyTestnet.id], }, }, }) diff --git a/src/core/Implementation.ts b/src/core/Implementation.ts new file mode 100644 index 00000000..5d1647e5 --- /dev/null +++ b/src/core/Implementation.ts @@ -0,0 +1,649 @@ +import type { RpcRequest } from 'ox' +import * as AbiItem from 'ox/AbiItem' +import * as Address from 'ox/Address' +import * as Bytes from 'ox/Bytes' +import * as Hex from 'ox/Hex' +import * as Json from 'ox/Json' +import * as PersonalMessage from 'ox/PersonalMessage' +import * as PublicKey from 'ox/PublicKey' +import * as Secp256k1 from 'ox/Secp256k1' +import * as TypedData from 'ox/TypedData' +import * as WebAuthnP256 from 'ox/WebAuthnP256' +import type { Hash } from 'viem' +import { readContract } from 'viem/actions' + +import type { Clients, Config } from './Porto.js' +import * as Account from './internal/account.js' +import * as Call from './internal/call.js' +import * as Delegation from './internal/delegation.js' +import { delegationAbi } from './internal/generated.js' +import * as Key from './internal/key.js' +import type * as RpcSchema from './internal/rpcSchema.js' +import type { Compute } from './internal/types.js' + +type Request = Pick + +export type Implementation = { + actions: { + authorizeKey: (parameters: { + /** Account to authorize the keys for. */ + account: Account.Account + /** Key to authorize. */ + key?: RpcSchema.AuthorizeKeyParameters['key'] | undefined + /** Viem Clients. */ + clients: Clients + /** Porto config. */ + config: Config + /** RPC Request. */ + request: Request + }) => Promise<{ hash: Hex.Hex; key: Key.Key }> + + createAccount: (parameters: { + /** Extra keys to authorize. */ + authorizeKeys?: + | readonly RpcSchema.AuthorizeKeyParameters['key'][] + | undefined + /** Viem Clients. */ + clients: Clients + /** Preparation context (from `prepareCreateAccount`). */ + context?: unknown | undefined + /** Porto config. */ + config: Config + /** Label to associate with the WebAuthn credential. */ + label?: string | undefined + /** RPC Request. */ + request: Request + /** Preparation signatures (from `prepareCreateAccount`). */ + signatures?: readonly Hex.Hex[] | undefined + }) => Promise<{ + /** Account. */ + account: Account.Account + /** Transaction hash. */ + hash: Hash + }> + + execute: (parameters: { + /** Account to execute the calls with. */ + account: Account.Account + /** Calls to execute. */ + calls: readonly Call.Call[] + /** Viem Clients. */ + clients: Clients + /** Key to use to execute the calls. */ + key?: { publicKey: Hex.Hex } | undefined + /** Porto config. */ + config: Config + /** RPC Request. */ + request: Request + }) => Promise + + loadAccounts: (parameters: { + /** Address of the account to load. */ + address?: Address.Address | undefined + /** Extra keys to authorize. */ + authorizeKeys?: + | readonly RpcSchema.AuthorizeKeyParameters['key'][] + | undefined + /** Viem Clients. */ + clients: Clients + /** Porto config. */ + config: Config + /** Credential ID to use to load an existing account. */ + credentialId?: string | undefined + /** RPC Request. */ + request: Request + }) => Promise<{ + /** Accounts. */ + accounts: readonly Account.Account[] + }> + + prepareCreateAccount: (parameters: { + /** Address of the account to import. */ + address: Address.Address + /** Extra keys to authorize. */ + authorizeKeys?: + | readonly RpcSchema.AuthorizeKeyParameters['key'][] + | undefined + /** Viem Clients. */ + clients: Clients + /** Porto config. */ + config: Config + /** Label to associate with the account. */ + label?: string | undefined + /** RPC Request. */ + request: Request + }) => Promise<{ + /** Filled context for the `createAccount` implementation. */ + context: unknown + /** Hex payloads to sign over. */ + signPayloads: Hex.Hex[] + }> + + revokeKey: (parameters: { + /** Account to revoke the key for. */ + account: Account.Account + /** Public key of the key to revoke. */ + publicKey: Hex.Hex + /** Viem Clients. */ + clients: Clients + /** Porto config. */ + config: Config + /** RPC Request. */ + request: Request + }) => Promise + + signPersonalMessage: (parameters: { + /** Account to sign the message with. */ + account: Account.Account + /** Viem Clients. */ + clients: Clients + /** Porto config. */ + config: Config + /** Data to sign. */ + data: Hex.Hex + /** RPC Request. */ + request: Request + }) => Promise + + signTypedData: (parameters: { + /** Account to sign the message with. */ + account: Account.Account + /** Viem Clients. */ + clients: Clients + /** Porto config. */ + config: Config + /** Data to sign. */ + data: string + /** RPC Request. */ + request: Request + }) => Promise + } +} + +/** + * Instantiates an implementation. + * + * @param implementation - Implementation. + * @returns Implementation. + */ +export function from( + implementation: implementation | Implementation, +): Compute { + return implementation as implementation +} + +/** + * Implementation for a WebAuthn-based local environment. Account management + * and signing is handled locally. + * + * @param parameters - Parameters. + * @returns Implementation. + */ +export function local(parameters: local.Parameters = {}) { + const defaultExpiry = Math.floor(Date.now() / 1000) + 60 * 60 // 1 hour + + const keystoreHost = (() => { + if (parameters.keystoreHost === 'self') return undefined + if ( + typeof window !== 'undefined' && + window.location.hostname === 'localhost' + ) + return undefined + return parameters.keystoreHost + })() + + return from({ + actions: { + async authorizeKey(parameters) { + const { account, clients, key: keyToAuthorize } = parameters + + const keys = await getKeysToAuthorize({ + authorizeKeys: [keyToAuthorize], + defaultExpiry, + }) + + // TODO: wait for tx to be included? + const hash = await Delegation.execute(clients.relay, { + account, + calls: getAuthorizeCalls(keys!), + }) + + return { hash, key: keys![0]! } + }, + + async createAccount(parameters) { + const { authorizeKeys, clients, label } = parameters + + const { account, context, signatures } = await (async () => { + if (parameters.context && parameters.signatures) + return { + account: (parameters.context as any).account, + context: parameters.context, + signatures: parameters.signatures, + } + + const privateKey = Secp256k1.randomPrivateKey() + const address = Address.fromPublicKey( + Secp256k1.getPublicKey({ privateKey }), + ) + + const { context, signPayloads } = await prepareCreateAccount({ + address, + authorizeKeys, + clients, + defaultExpiry, + keystoreHost, + label, + }) + + const account = Account.fromPrivateKey(privateKey, { + keys: context.account.keys, + }) + const signatures = await Account.sign(account, { + payloads: signPayloads, + }) + + return { account, context, signatures } + })() + + const hash = await Delegation.execute(clients.relay, { + ...(context as any), + account, + signatures, + }) + + return { account, hash } + }, + + async execute(parameters) { + const { account, calls, clients } = parameters + + const key = (() => { + // If a key is provided, use it. + if (parameters.key) { + const key = account.keys?.find( + (key) => + key.publicKey === parameters.key?.publicKey && key.canSign, + ) + if (!key) + throw new Error( + `key (publicKey: ${parameters.key?.publicKey}) does not exist or is not a provider-managed key.`, + ) + return key + } + + // Otherwise, try and find a valid session key. + const sessionKey = account.keys?.find((key) => { + if (!key.canSign) return false + if (key.role !== 'session') return false + if (key.expiry < BigInt(Math.floor(Date.now() / 1000))) return false + + const hasInvalidScope = key.callScopes?.some((scope) => + calls.some((call) => { + if (scope.to && scope.to !== call.to) return true + if (scope.signature) { + if (!call.data) return true + const selector = Hex.slice(call.data, 0, 4) + if ( + Hex.validate(scope.signature) && + scope.signature !== selector + ) + return true + if (AbiItem.getSelector(scope.signature) !== selector) + return true + } + return false + }), + ) + if (hasInvalidScope) return false + + return true + }) + + // Fall back to an admin key. + const adminKey = account.keys?.find( + (key) => key.role === 'admin' && key.canSign, + ) + + return sessionKey ?? adminKey + })() + + const hash = await Delegation.execute(clients.relay, { + account, + calls, + key, + }) + + return hash + }, + + async loadAccounts(parameters) { + const { authorizeKeys, clients } = parameters + + const { address, credentialId } = await (async () => { + if (parameters.address && parameters.credentialId) + return { + address: parameters.address, + credentialId: parameters.credentialId, + } + + // We will sign a random challenge. We need to do this to extract the + // user id (ie. the address) to query for the Account's keys. + const credential = await WebAuthnP256.sign({ + challenge: '0x', + rpId: keystoreHost, + }) + const response = credential.raw + .response as AuthenticatorAssertionResponse + + const address = Bytes.toHex(new Uint8Array(response.userHandle!)) + const credentialId = credential.raw.id + + return { address, credentialId } + })() + + // Fetch the delegated account's keys. + const [keyCount, extraKeys] = await Promise.all([ + readContract(clients.default, { + abi: delegationAbi, + address, + functionName: 'keyCount', + }), + getKeysToAuthorize({ + authorizeKeys, + defaultExpiry, + }), + ]) + const keys = await Promise.all( + Array.from({ length: Number(keyCount) }, (_, index) => + Delegation.keyAt(clients.default, { account: address, index }), + ), + ) + + // Instantiate the account based off the extracted address and keys. + const account = Account.from({ + address, + keys: [...keys, ...(extraKeys ?? [])].map((key, i) => { + // Assume that the first key is the admin WebAuthn key. + if (i === 0 && key.type === 'webauthn-p256') + return Key.fromWebAuthnP256({ + ...key, + credential: { + id: credentialId, + publicKey: PublicKey.fromHex(key.publicKey), + }, + }) + return key + }), + }) + + if (extraKeys) + await Delegation.execute(clients.relay, { + account, + calls: getAuthorizeCalls(extraKeys), + }) + + return { + accounts: [account], + } + }, + + async prepareCreateAccount(parameters) { + const { address, authorizeKeys, clients, label } = parameters + + return await prepareCreateAccount({ + address, + authorizeKeys, + clients, + defaultExpiry, + keystoreHost, + label, + }) + }, + + async revokeKey(parameters) { + const { account, clients, publicKey } = parameters + + const key = account.keys?.find((key) => key.publicKey === publicKey) + if (!key) return + + if ( + key.role === 'admin' && + account.keys?.map((x) => x.role === 'admin').length === 1 + ) + throw new Error( + 'cannot revoke key. account must have at least one admin key.', + ) + + await Delegation.execute(clients.relay, { + account, + calls: [Call.setCanExecute({ key, enabled: false })], + }) + }, + + async signPersonalMessage(parameters) { + const { account, data } = parameters + + const key = account.keys?.find( + (key) => key.role === 'admin' && key.canSign, + ) + if (!key) throw new Error('cannot find admin key to sign with.') + + const [signature] = await Account.sign(account, { + key, + payloads: [PersonalMessage.getSignPayload(data)], + }) + + return signature + }, + + async signTypedData(parameters) { + const { account, data } = parameters + + const key = account.keys?.find( + (key) => key.role === 'admin' && key.canSign, + ) + if (!key) throw new Error('cannot find admin key to sign with.') + + const [signature] = await Account.sign(account, { + key, + payloads: [TypedData.getSignPayload(Json.parse(data))], + }) + + return signature + }, + }, + }) +} + +export declare namespace local { + type Parameters = { + /** + * Keystore host (WebAuthn relying party). + * @default 'self' + */ + keystoreHost?: 'self' | (string & {}) | undefined + } +} + +// TODO +export function iframe() { + throw new Error('Not implemented.') +} + +/** + * Mock P256 implementation for testing. + * + * @param parameters - Parameters. + * @returns Implementation. + */ +export function mock() { + let address: Address.Address | undefined + + return from({ + actions: { + ...local().actions, + + async createAccount(parameters) { + const { authorizeKeys, clients } = parameters + + const privateKey = Secp256k1.randomPrivateKey() + + const key = Key.createP256({ + role: 'admin', + }) + + const extraKeys = await getKeysToAuthorize({ + authorizeKeys, + defaultExpiry: 694206942069, + }) + + const account = Account.fromPrivateKey(privateKey, { + keys: [key, ...(extraKeys ?? [])], + }) + const delegation = clients.default.chain.contracts.delegation.address + + address = account.address + + const hash = await Delegation.execute(clients.relay, { + account, + calls: getAuthorizeCalls(account.keys), + delegation, + }) + + return { account, hash } + }, + + async loadAccounts(parameters) { + const { clients } = parameters + + if (!address) throw new Error('no address found.') + + const keyCount = await readContract(clients.default, { + abi: delegationAbi, + address, + functionName: 'keyCount', + }) + const keys = await Promise.all( + Array.from({ length: Number(keyCount) }, (_, index) => + Delegation.keyAt(clients.default, { account: address!, index }), + ), + ) + + const account = Account.from({ + address, + keys, + }) + + return { + accounts: [account], + } + }, + }, + }) +} + +/////////////////////////////////////////////////////////////////////////// +// Internal +/////////////////////////////////////////////////////////////////////////// + +async function prepareCreateAccount(parameters: { + address: Address.Address + authorizeKeys: readonly RpcSchema.AuthorizeKeyParameters['key'][] | undefined + clients: Clients + defaultExpiry: number + label?: string | undefined + keystoreHost?: string | undefined +}) { + const { address, authorizeKeys, clients, defaultExpiry, keystoreHost } = + parameters + + const label = + parameters.label ?? `${address.slice(0, 8)}\u2026${address.slice(-6)}` + + const key = await Key.createWebAuthnP256({ + label, + role: 'admin', + rpId: keystoreHost, + userId: Bytes.from(address), + }) + + const extraKeys = await getKeysToAuthorize({ + authorizeKeys, + defaultExpiry, + }) + + const keys = [key, ...(extraKeys ?? [])] + + const account = Account.from({ + address, + keys, + }) + const delegation = clients.default.chain.contracts.delegation.address + + const { request, signPayloads } = await Delegation.prepareExecute( + clients.default, + { + account, + calls: getAuthorizeCalls(account.keys), + delegation, + }, + ) + + return { context: request, signPayloads } +} + +function getAuthorizeCalls(keys: readonly Key.Key[]) { + return keys.flatMap((key) => { + if (key.role === 'session' && (key.callScopes ?? []).length === 0) + throw new Error( + 'session key must have at least one call scope (`callScope`).', + ) + const scopes = key.callScopes + ? key.callScopes.map((scope) => { + const selector = (() => { + if (!scope.signature) return undefined + if (scope.signature.startsWith('0x')) + return scope.signature as Hex.Hex + return AbiItem.getSelector(scope.signature) + })() + return Call.setCanExecute({ + key, + selector, + to: scope.to, + }) + }) + : [Call.setCanExecute({ key })] + return [...scopes, Call.authorize({ key })] + }) +} + +async function getKeysToAuthorize(parameters: { + authorizeKeys: readonly RpcSchema.AuthorizeKeyParameters['key'][] | undefined + defaultExpiry: number +}) { + const { authorizeKeys, defaultExpiry } = parameters + + // Don't need to authorize extra keys if none are provided. + if (!authorizeKeys) return undefined + + // Otherwise, authorize the provided keys. + return await Promise.all( + authorizeKeys.map(async (key) => { + const expiry = key?.expiry ?? defaultExpiry + const role = key?.role ?? 'session' + if (key?.publicKey) + return Key.from({ + ...key, + canSign: false, + expiry, + role, + }) + return await Key.createWebCryptoP256({ + callScopes: key?.callScopes, + expiry, + role: 'session', + }) + }), + ) +} diff --git a/src/core/Porto.ts b/src/core/Porto.ts index 110591fb..e2d6885b 100644 --- a/src/core/Porto.ts +++ b/src/core/Porto.ts @@ -1,24 +1,114 @@ -import type * as Address from 'ox/Address' -import { http, type Client, type Transport, createClient } from 'viem' +import { + http, + type Client, + createClient, + fallback, + type Transport as viem_Transport, +} from 'viem' import { persist, subscribeWithSelector } from 'zustand/middleware' import { type Mutate, type StoreApi, createStore } from 'zustand/vanilla' import * as Chains from './Chains.js' -import type * as AccountDelegation from './internal/accountDelegation.js' +import * as Implementation from './Implementation.js' +import * as Storage from './Storage.js' +import type * as Account from './internal/account.js' import * as Provider from './internal/provider.js' -import * as Storage from './internal/storage.js' -import * as WebAuthn from './internal/webauthn.js' +import type { ExactPartial } from './internal/types.js' export const defaultConfig = { announceProvider: true, chains: [Chains.odysseyTestnet], - headless: true, - keystoreHost: 'self', + implementation: Implementation.local(), + storage: Storage.idb(), transports: { - [Chains.odysseyTestnet.id]: http(), + [Chains.odysseyTestnet.id]: { + default: http(), + // relay: http('https://t9uhvbea8n.eu-central-1.awsapprunner.com'), + }, }, } as const satisfies Config +export type Clients = { + default: Client + relay: Client +} + +export type Porto< + chains extends readonly [Chains.Chain, ...Chains.Chain[]] = readonly [ + Chains.Chain, + ...Chains.Chain[], + ], +> = { + destroy: () => void + provider: Provider.Provider + /** + * Not part of versioned API, proceed with caution. + * @deprecated + */ + _internal: { + config: Config + store: Store + } +} + +export type Config< + chains extends readonly [Chains.Chain, ...Chains.Chain[]] = readonly [ + Chains.Chain, + ...Chains.Chain[], + ], +> = { + /** + * Whether to announce the provider via EIP-6963. + * @default true + */ + announceProvider: boolean + /** + * List of supported chains. + */ + chains: chains | readonly [Chains.Chain, ...Chains.Chain[]] + /** + * Implementation to use. + * @default Implementation.local() + */ + implementation: Implementation.Implementation + /** + * Storage to use. + * @default Storage.idb() + */ + storage: Storage.Storage + /** + * Transport to use for each chain. + */ + transports: Record< + chains[number]['id'], + Transport | { default: Transport; relay?: Transport | undefined } + > +} + +export type State< + chains extends readonly [Chains.Chain, ...Chains.Chain[]] = readonly [ + Chains.Chain, + ...Chains.Chain[], + ], +> = { + accounts: readonly Account.Account[] + chain: chains[number] +} + +export type Store< + chains extends readonly [Chains.Chain, ...Chains.Chain[]] = readonly [ + Chains.Chain, + ...Chains.Chain[], + ], +> = Mutate< + StoreApi>, + [['zustand/subscribeWithSelector', never], ['zustand/persist', any]] +> + +export type Transport = + | viem_Transport + | { default: viem_Transport; relay?: viem_Transport | undefined } + /** * Instantiates an Porto instance. * @@ -36,82 +126,60 @@ export function create< Chains.Chain, ...Chains.Chain[], ] = typeof defaultConfig.chains, ->(config?: Config): Porto -export function create(config: Config | undefined = {}): Porto { +>(parameters?: ExactPartial | undefined): Porto +export function create( + parameters: ExactPartial | undefined = {}, +): Porto { const { announceProvider = defaultConfig.announceProvider, chains = defaultConfig.chains, - headless = defaultConfig.headless, - keystoreHost: keystoreHost_ = defaultConfig.keystoreHost, + implementation = defaultConfig.implementation, + storage = defaultConfig.storage, transports = defaultConfig.transports, - } = config - - const keystoreHost = (() => { - if (keystoreHost_ === 'self') return undefined - if ( - typeof window !== 'undefined' && - window.location.hostname === 'localhost' - ) - return undefined - return keystoreHost_ - })() - - if (headless && keystoreHost) WebAuthn.touchWellknown({ rpId: keystoreHost }) + } = parameters const store = createStore( subscribeWithSelector( persist( - (_, get) => ({ + (_) => ({ accounts: [], chain: chains[0], - - // computed - get chainId() { - const { chain } = get() - return chain.id - }, - get client() { - const { chain } = get() - return createClient({ - chain, - transport: (transports as Record)[chain.id]!, - }) - }, - get delegation() { - const { chain } = get() - return chain.contracts.accountDelegation.address - }, }), { name: 'porto.store', - merge, partialize(state) { return { accounts: state.accounts.map((account) => ({ ...account, - keys: account.keys.map((key) => ({ + sign: undefined, + keys: account.keys?.map((key) => ({ ...key, - ...('raw' in key ? { raw: undefined } : {}), + privateKey: + typeof key.privateKey === 'function' + ? undefined + : key.privateKey, })), })), chain: state.chain, } as unknown as State }, - storage: Storage.idb, + storage, }, ), ), ) store.persist.rehydrate() + const config = { + announceProvider, + chains, + implementation, + storage, + transports, + } satisfies Config + const provider = Provider.from({ - config: { - announceProvider, - chains, - headless, - keystoreHost, - transports, - } satisfies Config, + config, store, }) @@ -121,98 +189,58 @@ export function create(config: Config | undefined = {}): Porto { }, provider, _internal: { + config, store, }, } } -export type Porto< - chains extends readonly [Chains.Chain, ...Chains.Chain[]] = readonly [ - Chains.Chain, - ...Chains.Chain[], - ], -> = { - destroy: () => void - provider: Provider.Provider - /** - * Not part of versioned API, proceed with caution. - * @deprecated - */ - _internal: { - store: StoreApi> - } -} +/** + * Extracts a Viem Client from a Porto instance, and an optional chain ID. + * By default, the Client for the current chain ID will be extracted. + * + * @param porto - Porto instance. + * @param parameters - Parameters. + * @returns Client. + */ +export function getClients< + chains extends readonly [Chains.Chain, ...Chains.Chain[]], +>( + porto: { _internal: Porto['_internal'] }, + parameters: { chainId?: number | undefined } = {}, +): Clients { + const { chainId } = parameters + const { config, store } = porto._internal + const { chains } = config -export type Config< - chains extends readonly [Chains.Chain, ...Chains.Chain[]] = readonly [ - Chains.Chain, - ...Chains.Chain[], - ], -> = { - /** - * Whether to announce the provider via EIP-6963. - * @default true - */ - announceProvider?: boolean | undefined - /** - * List of supported chains. - */ - chains?: chains | readonly [Chains.Chain, ...Chains.Chain[]] - /** - * Whether to run EIP-1193 Provider in headless mode. - * @default true - */ - headless?: boolean | undefined - /** - * Keystore host (WebAuthn relying party). - * @default 'self' - */ - keystoreHost?: 'self' | (string & {}) | undefined - /** - * Transport to use for each chain. - */ - transports?: Record -} + const state = store.getState() + const chain = chains.find((chain) => chain.id === chainId || state.chain.id) + if (!chain) throw new Error('chain not found') -export type State< - chains extends readonly [Chains.Chain, ...Chains.Chain[]] = readonly [ - Chains.Chain, - ...Chains.Chain[], - ], -> = { - accounts: AccountDelegation.Account[] - chain: chains[number] - readonly chainId: chains[number]['id'] - readonly client: Client< - Transport, - Extract - > - readonly delegation: Address.Address -} + const transport = (config.transports as Record)[chain.id] + if (!transport) throw new Error('transport not found') -export type Store< - chains extends readonly [Chains.Chain, ...Chains.Chain[]] = readonly [ - Chains.Chain, - ...Chains.Chain[], - ], -> = Mutate< - StoreApi>, - [['zustand/subscribeWithSelector', never], ['zustand/persist', any]] -> + const { default: default_, relay } = (() => { + if (typeof transport === 'object') { + if (transport.relay) + return { + default: transport.default, + relay: transport.relay, + } as const + return { default: transport.default, relay: undefined } as const + } + return { default: transport, relay: undefined } as const + })() + + const client = (transport: viem_Transport) => + createClient({ + chain, + transport, + pollingInterval: 1_000, + }) -function merge(p: unknown, currentState: State): State { - const persistedState = p as State - const state = { ...currentState, ...persistedState } return { - ...state, - // TODO: Fix - // When filtering out keys, `keyIndex` changes and no longer matches index in - // `WrappedSignature` (via `wrapSignature`). - // accounts: state.accounts.map((account) => ({ - // ...account, - // keys: account.keys.filter( - // (key) => key.expiry === 0n || AccountDelegation.isActiveSessionKey(key), - // ), - // })), - } satisfies State + default: client(default_), + relay: relay ? client(fallback([relay, default_])) : client(default_), + } } diff --git a/src/core/Storage.ts b/src/core/Storage.ts new file mode 100644 index 00000000..e2347294 --- /dev/null +++ b/src/core/Storage.ts @@ -0,0 +1,45 @@ +import { createStore, del, get, set } from 'idb-keyval' +import type { MaybePromise } from './internal/types.js' + +export type Storage = { + getItem: (name: string) => MaybePromise + removeItem: (name: string) => MaybePromise + setItem: (name: string, value: unknown) => MaybePromise +} + +export function from(storage: Storage): Storage { + return storage +} + +export function idb() { + const store = + typeof indexedDB !== 'undefined' ? createStore('porto', 'store') : undefined + return from({ + async getItem(name) { + const value = await get(name, store) + if (value === null) return null + return value + }, + async removeItem(name) { + await del(name, store) + }, + async setItem(name, value) { + await set(name, value, store) + }, + }) +} + +export function memory() { + const store = new Map() + return from({ + getItem(name) { + return store.get(name) ?? null + }, + removeItem(name) { + store.delete(name) + }, + setItem(name, value) { + store.set(name, value) + }, + }) +} diff --git a/src/core/internal/__snapshots__/provider.test.ts.snap b/src/core/internal/__snapshots__/provider.test.ts.snap new file mode 100644 index 00000000..d48b1c6c --- /dev/null +++ b/src/core/internal/__snapshots__/provider.test.ts.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`smoke 1`] = `"0x6080604052600436106101c5575f3560e01c806384b0196e116100f6578063bf53096911610094578063e9ae5c5311610063578063e9ae5c53146105f9578063f0c60f1a1461060c578063fac750e014610620578063ff619c6b14610634576101cc565b8063bf5309691461054e578063cb4774c41461056d578063cebfe3361461058e578063d03c7914146105ad576101cc565b8063a2e2f930116100d0578063a2e2f930146104d2578063a840fe49146104f1578063b70e36f014610510578063b75c7dc61461052f576101cc565b806384b0196e1461045957806394430fa5146104805780639c42fb59146104a3576101cc565b80632f3f30c711610163578063515c9d6d1161013d578063515c9d6d146103cb57806360d2f33d146103eb5780636ae269cc1461041e5780636fd914541461043a576101cc565b80632f3f30c71461037857806335058501146103925780634223b5c2146103ac576101cc565b8063136a12f71161019f578063136a12f7146102ab5780631626ba7e146102cc5780631a912f3e1461030457806320606b7014610345576101cc565b80630cef73b41461020557806311a86fd61461024057806312aaac701461027f576101cc565b366101cc57005b5f3560e01c63bc197c81811463f23a6e6182141763150b7a02821417156101f757806020526020603cf35b50633c10b94e5f526004601cfd5b348015610210575f80fd5b5061022461021f366004612068565b610653565b6040805192151583526020830191909152015b60405180910390f35b34801561024b575f80fd5b5061026773323232323232323232323232323232323232323281565b6040516001600160a01b039091168152602001610237565b34801561028a575f80fd5b5061029e6102993660046120b0565b61066d565b6040516102379190612109565b3480156102b6575f80fd5b506102ca6102c536600461218b565b61075d565b005b3480156102d7575f80fd5b506102eb6102e6366004612068565b610868565b6040516001600160e01b03199091168152602001610237565b34801561030f575f80fd5b506103377f84fa2cf05cd88e992eae77e851af68a4ee278dcff6ef504e487a55b3baadfbe581565b604051908152602001610237565b348015610350575f80fd5b506103377f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f81565b348015610383575f80fd5b506102eb630707070760e51b81565b34801561039d575f80fd5b506102eb631919191960e11b81565b3480156103b7575f80fd5b5061029e6103c63660046120b0565b61089b565b3480156103d6575f80fd5b506103375f8051602061266f83398151915281565b3480156103f6575f80fd5b506103377fe530e62dece51c9bec26701907051ddc8420a62f028096eeb58263193e84e04981565b348015610429575f80fd5b50686d3d4e7fb92a52381554610337565b348015610445575f80fd5b506103376104543660046121e5565b6108d9565b348015610464575f80fd5b5061046d610a2c565b604051610237979695949392919061225a565b34801561048b575f80fd5b506102676fac830f1181f6aab6862e71edc248941c81565b3480156104ae575f80fd5b506104c26104bd3660046122f0565b610a8d565b6040519015158152602001610237565b3480156104dd575f80fd5b506104c26104ec3660046120b0565b610ad1565b3480156104fc575f80fd5b5061033761050b3660046123e2565b610af9565b34801561051b575f80fd5b506102ca61052a3660046120b0565b610b32565b34801561053a575f80fd5b506102ca6105493660046120b0565b610b5d565b348015610559575f80fd5b506102ca610568366004612491565b610bb2565b348015610578575f80fd5b50610581610c56565b60405161023791906124d0565b348015610599575f80fd5b506103376105a83660046123e2565b610c6f565b3480156105b8575f80fd5b506104c26105c73660046120b0565b690100000000007821000160b09190911c69ffff00000000ffffffff1690811460011b600160481b9190911417151590565b6102ca610607366004612068565b610cd7565b348015610617575f80fd5b50610337610d60565b34801561062b575f80fd5b50610337610df3565b34801561063f575f80fd5b506104c261064e3660046124e2565b610e06565b5f806106618585855f611099565b91509150935093915050565b604080516080810182525f80825260208201819052918101919091526060808201525f828152686d3d4e7fb92a523817602052604081206106ad906112bb565b905080515f036106d05760405163395ed8c160e21b815260040160405180910390fd5b8051600619015f6106e48383016020015190565b60d881901c855260c881901c915060d01c60ff166002811115610709576107096120c7565b8460200190600281111561071f5761071f6120c7565b90816002811115610732576107326120c7565b90525060ff81161515604085015261074f83838151811082025290565b606085015250919392505050565b33301461077c576040516282b42960e81b815260040160405180910390fd5b8361079a57604051638707510560e01b815260040160405180910390fd5b6107a48383611321565b156107cf576107b28461134b565b6107cf576040516303a6f8c760e21b815260040160405180910390fd5b5f82815260188490526004859052603881208152683c149ebf7b8e6c5e226020818152604092839020805485151560ff19909116811790915583518881526001600160a01b038816928101929092526001600160e01b03198616828501526060820152915190917f7eb91b8ac56c0864a4e4f5598082d140d04bed1a4dd62a41d605be2430c494e1919081900360800190a15050505050565b5f806108778585856001611099565b509050806108895763ffffffff61088f565b631626ba7e5b60e01b95945050505050565b604080516080810182525f80825260208201819052918101919091526060808201526108d3610299686d3d4e7fb92a5238168461135f565b92915050565b5f806108f58460408051828152600190920160051b8201905290565b90505f5b848110156109b857368686838181106109145761091461253a565b9050602002810190610926919061254e565b90506109ae8261099f7f84fa2cf05cd88e992eae77e851af68a4ee278dcff6ef504e487a55b3baadfbe561095d602086018661256c565b6001600160a01b0316602086013561098061097b6040890189612587565b6113a8565b6040805194855260208501939093529183015260608201526080902090565b600190910160051b8501528390565b50506001016108f9565b50610a23610a1e7fe530e62dece51c9bec26701907051ddc8420a62f028096eeb58263193e84e0496109f284805160051b60209091012090565b686d3d4e7fb92a5238155460408051938452602084019290925290820187905260608201526080902090565b6113b9565b95945050505050565b600f60f81b6060805f808083610a7b604080518082018252600a8152692232b632b3b0ba34b7b760b11b60208083019190915282518084019093526005835264302e302e3160d81b9083015291565b97989097965046955030945091925090565b5f336fac830f1181f6aab6862e71edc248941c14610abd576040516282b42960e81b815260040160405180910390fd5b610ac88333846114cf565b50600192915050565b600881901c5f908152686d3d4e7fb92a523814602052604081205460ff83161c6001166108d3565b5f6108d382602001516002811115610b1357610b136120c7565b60ff168360600151805190602001205f1c5f9182526020526040902090565b333014610b51576040516282b42960e81b815260040160405180910390fd5b610b5a816114f7565b50565b333014610b7c576040516282b42960e81b815260040160405180910390fd5b610b8581611553565b60405181907fe5af7daed5ab2a2dc5f98d53619f05089c0c14d11a6621f6b906a2366c9a7ab3905f90a250565b333014610bd1576040516282b42960e81b815260040160405180910390fd5b610c1982828080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92019190915250610c1392506112ae915050565b906115a7565b7faec6ef4baadc9acbdf52442522dfffda03abe29adba8d4af611bcef4cbe0c9ad8282604051610c4a9291906125ca565b60405180910390a15050565b6060610c6a686d3d4e7fb92a5238136112bb565b905090565b5f333014610c8f576040516282b42960e81b815260040160405180910390fd5b610c98826115ff565b9050807f3d3a48be5a98628ecf98a6201185102da78bbab8f63a4b2d6b9eef354f5131f583604051610cca9190612109565b60405180910390a2919050565b690100000000007821000160b084901c69ffff00000000ffffffff1690811460011b600160481b9190911417365f818184610d1957637f1812755f526004601cfd5b5085358087016020810194503592505f90604011600286141115610d47575050602080860135860190810190355b610d5688888887878787611674565b5050505050505050565b5f333014610d80576040516282b42960e81b815260040160405180910390fd5b50686d3d4e7fb92a523815805460408051828152426020808301919091523382840152606090912063ffffffff169092019283905580518381529051686d3d4e7fb92a523813927f7328006ebae4716b8db9514af69091e0ad74bec61dfdc256ed5446b234aa5424928290030190a15090565b5f610c6a686d3d4e7fb92a52381661178b565b5f84610e1457506001611091565b683c149ebf7b8e6c5e22631919191960e11b60048410610e32575083355b83610e415750630707070760e51b5b610e4b8682611321565b15610e6757610e598761134b565b610e67575f92505050611091565b5f818152601887905260048890526038812081526020839052604090205460ff1615610e9857600192505050611091565b5f81815273323232323232323232323232323232323232323260185260048890526038812081526020839052604090205460ff1615610edc57600192505050611091565b5f81815260188790525f8051602061266f8339815191526004526038812081526020839052604090205460ff1615610f1957600192505050611091565b5f8181527332323232323232323232323232323232323232326018525f8051602061266f8339815191526004526038812081526020839052604090205460ff1615610f6957600192505050611091565b631919191960e11b5f908152601887905260048890526038812081526020839052604090205460ff1615610fa257600192505050611091565b631919191960e11b5f90815273323232323232323232323232323232323232323260185260048890526038812081526020839052604090205460ff1615610fee57600192505050611091565b631919191960e11b5f90815260188790525f8051602061266f8339815191526004526038812081526020839052604090205460ff161561103357600192505050611091565b631919191960e11b5f9081527332323232323232323232323232323232323232326018525f8051602061266f8339815191526004526038812081526020839052604090205460ff161561108b57600192505050611091565b5f925050505b949350505050565b5f80604184146040851417156110c957306110b58787876117d7565b6001600160a01b03161491505f90506112a5565b60218410156110dc57505f9050806112a5565b506020198381018481118186180281189486019182013591601f19013560ff161561110d5761110a8761185f565b96505b505f6111188261066d565b6040810151909150158415151615611133575f9250506112a5565b805164ffffffffff164281109015151615611151575f9250506112a5565b5f81602001516002811115611168576111686120c7565b036111c3575f80603f8711883581029060208a013502915091505f806111a7856060015180516020820151604090920151603f90911191820292910290565b915091506111b88b8585858561187d565b9650505050506112a3565b6001816020015160028111156111db576111db6120c7565b0361126057606081810151805160208083015160409384015184518084018e9052855180820385018152601f8d018590049094028101870186529485018b8152603f9490941091820295910293611257935f92611250928e918e918291018382808284375f9201919091525061190f92505050565b85856119f3565b945050506112a3565b600281602001516002811115611278576112786120c7565b036112a3576112a0816060015180602001905181019061129891906125f8565b888888611b12565b92505b505b94509492505050565b686d3d4e7fb92a52381390565b60405181546020820190600881901c5f8260ff8417146112e957505080825260ff8116601f8082111561130b575b855f5260205f205b8160051c810154828601526020820191508282106112f157505b508084525f920191825250602001604052919050565b5f63e9ae5c5360e01b6001600160e01b0319831614306001600160a01b03851614165b9392505050565b5f6113558261066d565b6040015192915050565b6318fb58646004525f8281526024902081015468fbb67fda52d4bfb8bf811415026113898361178b565b82106108d357604051634e23d03560e01b815260040160405180910390fd5b5f8183604051375060405120919050565b7f06012d2aedbb397c1914278c3206cec06702f79ceb18361e1ddb2fcc068c324f7f0000000000000000000000005fbdb2315678afecb367f032d93f642f64180aa330147f0000000000000000000000000000000000000000000000000000000000007a694614166114ac5750604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f81527f6c157b110f3bc0280dba8ae624dab3ba623d9bab8692057c96d59c9188d52cb260208201527fae209a0b48f21c054280f2455d32cf309387644879d9acbd8ffc1991638118859181019190915246606082015230608082015260a090205b6719010000000000005f5280601a5281603a52604260182090505f603a52919050565b6001600160a01b0383166114ec576114e78282611be0565b505050565b6114e7838383611bf9565b600881901c5f908152686d3d4e7fb92a523814602052604090208054600160ff84161b1790556040518181527f4d9dbebf1d909894d9c26fe228c27cec643b2cb490124e5b658f4edd203c20c19060200160405180910390a150565b5f818152686d3d4e7fb92a5238176020526040812055686d3d4e7fb92a523813611586686d3d4e7fb92a52381683611c43565b6115a35760405163395ed8c160e21b815260040160405180910390fd5b5050565b80518060081b60ff175f60fe83116115d0575050601f8281015160081b821790808311156115f7575b60208401855f5260205f205b828201518360051c8201556020830192508483106115dc5750505b509092555050565b5f61160982610af9565b90505f686d3d4e7fb92a5238136060840151845160208087015160408089015190519596506116609561163e95949301612613565b60408051601f198184030181529181525f8581526004850160205220906115a7565b61166d6003820183611d4f565b5050919050565b6fac830f1181f6aab6862e71edc248941b1933016116cc5760408110156116ae576040516355fe73fd60e11b815260040160405180910390fd5b6116b88235611e6a565b6116c784846020850135611e91565b611782565b806116fb573330146116f0576040516282b42960e81b815260040160405180910390fd5b6116c784845f611e91565b602081101561171d576040516355fe73fd60e11b815260040160405180910390fd5b813561172881611e6a565b5f806117526117388888866108d9565b602080871081881802188088019080880390881102610653565b9150915081611773576040516282b42960e81b815260040160405180910390fd5b61177e878783611e91565b5050505b50505050505050565b6318fb58646004525f818152602481208019548060011c92508061166d5781545f93501561166d5760019250828201541561166d5760029250828201541561166d575060039392505050565b5f60405182604081146117f25760418114611819575061184a565b60208581013560ff81901c601b0190915285356040526001600160ff1b031660605261182a565b60408501355f1a6020526040856040375b50845f526020600160805f60015afa5191505f606052806040523d611857575b638baa579f5f526004601cfd5b509392505050565b5f815f526020600160205f60025afa5190503d61187857fe5b919050565b5f6040518681528560208201528460408201528360608201528260808201525f805260205f60a0836101005afa503d6118da5760203d60a0836dcb83347beb24c695bbb85dbe99b75afa503d6118da5763d0d5039b3d526004601cfd5b505f516001147f7fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a8851110905095945050505050565b6040805160c0810182526060808252602082018190525f92820183905281018290526080810182905260a0810191909152815160c081106119ed5760208301818101818251018281108260c0830111171561196c575050506119ed565b808151019250806020820151018181108382111782851084861117171561199657505050506119ed565b82815160208301011183855160208701011117156119b757505050506119ed565b8386528060208701525060408101516040860152606081015160608601526080810151608086015260a081015160a08601525050505b50919050565b5f805f611a0288600180611edf565b905060208601518051602082019150604088015160608901518451600d81016c1131b430b63632b733b2911d1160991b60981c8752848482011060228286890101515f1a14168160138901208286890120141685846014011085851760801c1074113a3cb832911d113bb2b130baba34371733b2ba1160591b60581c8589015160581c14161698505080865250505087515189151560021b600117808160218c5101511614602083118816169650508515611ae657602089510181810180516020600160208601856020868a8c60025afa60011b5afa51915295503d9050611ae657fe5b5050508215611b0757611b048287608001518860a00151888861187d565b92505b505095945050505050565b5f6001600160a01b03851615611091576040518260408114611b3c5760418114611b635750611b98565b60208581013560ff81901c601b0190915285356040526001600160ff1b0316606052611b74565b60408501355f1a6020526040856040375b50845f526020600160805f60015afa5180871860601b3d119250505f606052806040525b81611bd757631626ba7e60e01b80825285600483015260248201604081528460448401528486606485013760208160648701858b5afa90519091141691505b50949350505050565b5f385f3884865af16115a35763b12d13eb5f526004601cfd5b816014528060345263a9059cbb60601b5f5260205f604460105f875af18060015f511416611c3957803d853b151710611c39576390b8ec185f526004601cfd5b505f603452505050565b6318fb58646004525f8281526024812068fbb67fda52d4bfb8bf8303611c705763f5a267f15f526004601cfd5b82611c825768fbb67fda52d4bfb8bf92505b80195480611ceb576001925083825403611caf5760018201805483556002830180549091555f9055611d47565b83600183015403611ccd5760028201805460018401555f9055611d47565b83600283015403611ce3575f6002830155611d47565b5f9250611d47565b81602052835f5260405f20805480611d04575050611d47565b60018360011c039250826001820314611d33578284015480600183038601555f84860155805f52508060405f20555b5060018260011b178319555f815550600192505b505092915050565b6318fb58646004525f8281526024812068fbb67fda52d4bfb8bf8303611d7c5763f5a267f15f526004601cfd5b82611d8e5768fbb67fda52d4bfb8bf92505b8019548160205280611e3257815480611dae578483556001935050611d47565b848103611dbb5750611d47565b600183015480611dd657856001850155600194505050611d47565b858103611de4575050611d47565b600284015480611e005786600286015560019550505050611d47565b868103611e0f57505050611d47565b5f9283526040808420600190559183528183206002905582529020600390555060075b835f5260405f208054611e6157600191821c8381018690558083019182905590821b8217831955909250611d47565b50505092915050565b611e7381610ad1565b15610b5157604051633ab3447f60e11b815260040160405180910390fd5b600582901b5f5b818114611ed8576020818101918601358601803580153002179181810135916040810135019081019035611ecf848484848b611fd0565b50505050611e98565b5050505050565b606083518015611857576003600282010460021b60405192507f4142434445464748494a4b4c4d4e4f505152535455565758595a616263646566601f526106708515027f6768696a6b6c6d6e6f707172737475767778797a303132333435363738392d5f18603f526020830181810183886020010180515f82525b60038a0199508951603f8160121c16515f53603f81600c1c1651600153603f8160061c1651600253603f811651600353505f518452600484019350828410611f5a579052602001604052613d3d60f01b60038406600204808303919091525f861515909102918290035290038252509392505050565b611fdc81868585610e06565b611ff8576040516282b42960e81b815260040160405180910390fd5b611ed88585858585604051828482375f388483888a5af161201b573d5f823e3d81fd5b505050505050565b5f8083601f840112612033575f80fd5b50813567ffffffffffffffff81111561204a575f80fd5b602083019150836020828501011115612061575f80fd5b9250929050565b5f805f6040848603121561207a575f80fd5b83359250602084013567ffffffffffffffff811115612097575f80fd5b6120a386828701612023565b9497909650939450505050565b5f602082840312156120c0575f80fd5b5035919050565b634e487b7160e01b5f52602160045260245ffd5b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b6020815264ffffffffff82511660208201525f60208301516003811061213d57634e487b7160e01b5f52602160045260245ffd5b80604084015250604083015115156060830152606083015160808084015261109160a08401826120db565b6001600160a01b0381168114610b5a575f80fd5b80358015158114611878575f80fd5b5f805f806080858703121561219e575f80fd5b8435935060208501356121b081612168565b925060408501356001600160e01b0319811681146121cc575f80fd5b91506121da6060860161217c565b905092959194509250565b5f805f604084860312156121f7575f80fd5b833567ffffffffffffffff81111561220d575f80fd5b8401601f8101861361221d575f80fd5b803567ffffffffffffffff811115612233575f80fd5b8660208260051b8401011115612247575f80fd5b6020918201979096509401359392505050565b60ff60f81b8816815260e060208201525f61227860e08301896120db565b828103604084015261228a81896120db565b606084018890526001600160a01b038716608085015260a0840186905283810360c0850152845180825260208087019350909101905f5b818110156122df5783518352602093840193909201916001016122c1565b50909b9a5050505050505050505050565b5f8060408385031215612301575f80fd5b823561230c81612168565b946020939093013593505050565b634e487b7160e01b5f52604160045260245ffd5b6040516080810167ffffffffffffffff811182821017156123515761235161231a565b60405290565b5f82601f830112612366575f80fd5b813567ffffffffffffffff8111156123805761238061231a565b604051601f8201601f19908116603f0116810167ffffffffffffffff811182821017156123af576123af61231a565b6040528181528382016020018510156123c6575f80fd5b816020850160208301375f918101602001919091529392505050565b5f602082840312156123f2575f80fd5b813567ffffffffffffffff811115612408575f80fd5b820160808185031215612419575f80fd5b61242161232e565b813564ffffffffff81168114612435575f80fd5b8152602082013560038110612448575f80fd5b60208201526124596040830161217c565b6040820152606082013567ffffffffffffffff811115612477575f80fd5b61248386828501612357565b606083015250949350505050565b5f80602083850312156124a2575f80fd5b823567ffffffffffffffff8111156124b8575f80fd5b6124c485828601612023565b90969095509350505050565b602081525f61134460208301846120db565b5f805f80606085870312156124f5575f80fd5b84359350602085013561250781612168565b9250604085013567ffffffffffffffff811115612522575f80fd5b61252e87828801612023565b95989497509550505050565b634e487b7160e01b5f52603260045260245ffd5b5f8235605e19833603018112612562575f80fd5b9190910192915050565b5f6020828403121561257c575f80fd5b813561134481612168565b5f808335601e1984360301811261259c575f80fd5b83018035915067ffffffffffffffff8211156125b6575f80fd5b602001915036819003821315612061575f80fd5b60208152816020820152818360408301375f818301604090810191909152601f909201601f19160101919050565b5f60208284031215612608575f80fd5b815161134481612168565b5f85518060208801845e60d886901b6001600160d81b0319169083019081526003851061264e57634e487b7160e01b5f52602160045260245ffd5b60f894851b600582015292151590931b600683015250600701939250505056fe3232323232323232323232323232323232323232323232323232323232323232a2646970667358221220fedfc3184b2b333b20591e8e82d687d62f41e28fa92d349d78b640fb4d41df5764736f6c634300081a0033"`; diff --git a/src/core/internal/account.test.ts b/src/core/internal/account.test.ts new file mode 100644 index 00000000..023f5350 --- /dev/null +++ b/src/core/internal/account.test.ts @@ -0,0 +1,294 @@ +import { Hex } from 'ox' +import { verifyHash } from 'viem/actions' +import { describe, expect, test } from 'vitest' + +import { getAccount } from '../../../test/src/account.js' +import { client, delegation } from '../../../test/src/porto.js' +import * as Account from './account.js' +import * as Call from './call.js' +import * as Delegation from './delegation.js' +import * as Key from './key.js' + +describe('from', () => { + test('default', () => { + const account = Account.from({ + address: '0x0000000000000000000000000000000000000001', + keys: [ + Key.fromP256({ + expiry: 42069, + privateKey: + '0x59ff6b8de3b3b39e94b6f9fc0590cf4e3eaa9b6736e6a49c9a6b324c4f58cb9f', + role: 'admin', + }), + ], + }) + + expect(account).toMatchInlineSnapshot(` + { + "address": "0x0000000000000000000000000000000000000001", + "keys": [ + { + "callScopes": undefined, + "canSign": true, + "expiry": 42069, + "privateKey": [Function], + "publicKey": "0xec0effa5f2f378cbf7fd2fa7ca1e8dc51cf777c129fa1c00a0e9a9205f2e511ff3f20b34a4e0b50587d055c0e0fad33d32cf1147d3bb2538fbab0d15d8e65008", + "role": "admin", + "type": "p256", + }, + ], + "type": "delegated", + } + `) + }) + + test('args: address', () => { + const account = Account.from('0x0000000000000000000000000000000000000000') + + expect(account).toMatchInlineSnapshot(` + { + "address": "0x0000000000000000000000000000000000000000", + "type": "delegated", + } + `) + }) +}) + +describe('fromPrivateKey', () => { + test('default', () => { + const account = Account.fromPrivateKey( + '0x59ff6b8de3b3b39e94b6f9fc0590cf4e3eaa9b6736e6a49c9a6b324c4f58cb9f', + ) + + expect(account).toMatchInlineSnapshot(` + { + "address": "0x673ee8aabd3a62434cb9e3d7c6f9492e286bcb08", + "keys": undefined, + "sign": [Function], + "type": "delegated", + } + `) + }) +}) + +describe('sign', () => { + test('default', async () => { + const { account } = await getAccount(client) + + await Delegation.execute(client, { + account, + calls: [], + delegation, + }) + + const payload = Hex.random(32) + const [signature] = await Account.sign(account, { + payloads: [payload], + }) + + const valid = await verifyHash(client, { + address: account.address, + hash: payload, + signature, + }) + + expect(valid).toBe(true) + }) + + test('args: key', async () => { + const key = Key.createP256({ + role: 'admin', + }) + + const { account } = await getAccount(client, { + keys: [key], + }) + + await Delegation.execute(client, { + account, + calls: [ + Call.authorize({ + key, + }), + ], + delegation, + }) + + const payload = Hex.random(32) + + { + const [signature] = await Account.sign(account, { + key, + payloads: [payload], + }) + + const valid = await verifyHash(client, { + address: account.address, + hash: payload, + signature, + }) + + expect(valid).toBe(true) + } + + { + const [signature] = await Account.sign(account, { + key: 0, + payloads: [payload], + }) + + const valid = await verifyHash(client, { + address: account.address, + hash: payload, + signature, + }) + + expect(valid).toBe(true) + } + }) + + test('behavior: with key', async () => { + const key = Key.createP256({ + role: 'admin', + }) + + const { account } = await getAccount(client, { + keys: [key], + }) + + await Delegation.execute(client, { + account, + calls: [ + Call.authorize({ + key, + }), + ], + delegation, + }) + + const payload = Hex.random(32) + + const [signature] = await Account.sign(account, { + payloads: [payload], + }) + + const valid = await verifyHash(client, { + address: account.address, + hash: payload, + signature, + }) + + expect(valid).toBe(true) + }) + + test('behavior: with authorization payload', async () => { + const key = Key.createP256({ + role: 'admin', + }) + + const { account } = await getAccount(client, { + keys: [key], + }) + + await Delegation.execute(client, { + account, + calls: [ + Call.authorize({ + key, + }), + ], + delegation, + }) + + const payloads = [Hex.random(32), Hex.random(32)] as const + + const signatures = await Account.sign(account, { + payloads, + }) + + expect(signatures.length).toBe(2) + expect( + await verifyHash(client, { + address: account.address, + hash: payloads[0], + signature: signatures[0], + }), + ).toBe(true) + expect( + await verifyHash(client, { + address: account.address, + hash: payloads[1], + signature: signatures[1]!, + }), + ).toBe(true) + }) + + test('behavior: with authorization payload, no root signing key', async () => { + const key = Key.createP256({ + role: 'admin', + }) + + const { account } = await getAccount(client) + + await Delegation.execute(client, { + account, + calls: [ + Call.authorize({ + key, + }), + ], + delegation, + }) + + const nextAccount = Account.from({ + ...account, + sign: undefined, + keys: [key], + }) + + const payloads = [Hex.random(32), Hex.random(32)] as const + + await expect( + Account.sign(nextAccount, { + // @ts-expect-error: test + payloads, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + '[Error: cannot find root signing key to sign authorization.]', + ) + }) + + test('behavior: no keys', async () => { + const key = Key.createP256({ + role: 'admin', + }) + + const { account } = await getAccount(client) + + await Delegation.execute(client, { + account, + calls: [ + Call.authorize({ + key, + }), + ], + delegation, + }) + + const nextAccount = Account.from({ + ...account, + keys: undefined, + sign: undefined, + }) + + const payloads = [Hex.random(32)] as const + + await expect( + Account.sign(nextAccount, { + payloads, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + '[Error: cannot find key to sign with.]', + ) + }) +}) diff --git a/src/core/internal/account.ts b/src/core/internal/account.ts new file mode 100644 index 00000000..aa34aae5 --- /dev/null +++ b/src/core/internal/account.ts @@ -0,0 +1,186 @@ +import * as Address from 'ox/Address' +import type * as Hex from 'ox/Hex' +import * as Secp256k1 from 'ox/Secp256k1' +import * as Signature from 'ox/Signature' + +import * as Key from './key.js' +import type { Compute, RequiredBy } from './types.js' + +/** A delegated account. */ +export type Account = { + address: Address.Address + keys?: readonly Key.Key[] | undefined + sign?: ((parameters: { payload: Hex.Hex }) => Promise) | undefined + type: 'delegated' +} + +/** + * Instantiates a delegated account. + * + * @param account - Account to instantiate. + * @returns An instantiated delegated account. + */ +export function from( + parameters: from.Parameters, +): Compute> { + const account = ( + typeof parameters === 'string' ? { address: parameters } : parameters + ) as from.AccountParameter + return { ...account, type: 'delegated' } as never +} + +export declare namespace from { + type AccountParameter = Omit + + type Parameters< + account extends Address.Address | AccountParameter = + | Address.Address + | AccountParameter, + > = account | Address.Address | AccountParameter + + type ReturnType< + account extends Address.Address | AccountParameter = + | Address.Address + | AccountParameter, + > = Readonly< + (account extends AccountParameter ? account : { address: account }) & { + type: 'delegated' + } + > +} + +/** + * Instantiates a delegated account from a private key. + * + * @param privateKey - Private key. + * @param options - Options. + * @returns An instantiated delegated account. + */ +export function fromPrivateKey< + const options extends fromPrivateKey.Options = fromPrivateKey.Options, +>( + privateKey: Hex.Hex, + options: options | fromPrivateKey.Options = {}, +): Compute> { + const { keys } = options + const address = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey })) + return from({ + address, + keys, + async sign({ payload }) { + return Signature.toHex( + Secp256k1.sign({ + privateKey, + payload, + }), + ) + }, + }) as fromPrivateKey.ReturnType +} + +export declare namespace fromPrivateKey { + type Options = { + /** + * Keys to instantiate. + */ + keys?: readonly Key.Key[] | undefined + } + + type ReturnType = Readonly< + RequiredBy, 'sign'> & + (options['keys'] extends readonly Key.Key[] + ? { keys: options['keys'] } + : { keys?: Account['keys'] }) + > +} + +/** + * Extracts a signing key from a delegated account and signs payload(s). + * + * @example + * TODO + * + * @param parameters - Parameters. + * @returns Signatures. + */ +export async function sign< + const account extends Account, + payloads extends sign.Payloads, +>( + account: account | Account, + parameters: sign.Parameters, +): Promise> { + const { payloads } = parameters + + const [payload, authorizationPayload] = payloads as {} as [Hex.Hex, Hex.Hex] + + // If we have an authorization payload, but no root signing key on the account, + // then we cannot perform an authorization as we need the EOA's private key. + if (authorizationPayload && !account.sign) + throw new Error('cannot find root signing key to sign authorization.') + + // Extract a key to sign the payload with. + const key = (() => { + const key = parameters.key + + // Extract from `key` parameter. + if (typeof key === 'object') return key + + // If we have an authorization payload, use the root signing key. + if (authorizationPayload) return undefined + + // Extract from `account.keys` (with optional `key` index). + if (account.keys && account.keys.length > 0) { + if (typeof key === 'number') return account.keys[key] + return account.keys.find((key) => key.canSign) + } + + return undefined + })() + + const sign = (() => { + // If we have no key, use the root signing key. + if (!key) return account.sign + return (parameters: { payload: Hex.Hex }) => + Key.sign(key, { + ...parameters, + address: account.address, + }) + })() + + // If the account has no valid signing key, then we cannot sign the payload. + if (!sign) throw new Error('cannot find key to sign with.') + + // Sign the payload(s). + const signatures = await Promise.all([ + sign({ payload }), + authorizationPayload && account.sign + ? account.sign({ payload: authorizationPayload }) + : undefined, + ]) + + return signatures as never +} + +export declare namespace sign { + type Parameters< + account extends Account = Account, + payloads extends Payloads = Payloads, + > = { + /** + * Key to sign the payloads with. If not provided, a key will be extracted from the `account`. + */ + key?: number | Key.Key | undefined + /** + * Payloads to sign. + */ + payloads: payloads & + (account extends { sign: NonNullable } + ? Payloads + : readonly [execute: Hex.Hex]) + } + + type Payloads = + | readonly [execute: Hex.Hex] + | readonly [execute: Hex.Hex, authorization: Hex.Hex] +} diff --git a/src/core/internal/accountDelegation.ts b/src/core/internal/accountDelegation.ts deleted file mode 100644 index 90917d6f..00000000 --- a/src/core/internal/accountDelegation.ts +++ /dev/null @@ -1,711 +0,0 @@ -import * as AbiParameters from 'ox/AbiParameters' -import * as Address from 'ox/Address' -import * as Bytes from 'ox/Bytes' -import * as Hash from 'ox/Hash' -import * as Hex from 'ox/Hex' -import * as PublicKey from 'ox/PublicKey' -import * as Secp256k1 from 'ox/Secp256k1' -import type * as Signature from 'ox/Signature' -import * as WebAuthnP256 from 'ox/WebAuthnP256' -import * as WebCryptoP256 from 'ox/WebCryptoP256' -import type { Chain, Client, Transport } from 'viem' -import { privateKeyToAccount } from 'viem/accounts' -import { readContract, writeContract } from 'viem/actions' -import { - type Authorization as Authorization_viem, - prepareAuthorization, - signAuthorization, -} from 'viem/experimental' - -import { experimentalDelegationAbi } from './generated.js' -import type { OneOf, Undefined } from './types.js' - -//////////////////////////////////////////////////////////// -// Types -//////////////////////////////////////////////////////////// - -export type Account = { - address: Address.Address - label: string - keys: readonly Key[] -} - -export type BaseKey = { - expiry: bigint - publicKey: PublicKey.PublicKey - type: type -} & OneOf< - | ({ - status: 'unlocked' - } & properties) - | ({ - status: 'locked' - } & Undefined) -> - -export type Calls = readonly { - to: Address.Address - value?: bigint | undefined - data?: Hex.Hex | undefined -}[] - -export type Key = WebAuthnKey | WebCryptoKey - -export type SerializedKey = { - expiry: bigint - keyType: number - publicKey: PublicKey.PublicKey -} - -export type WebAuthnKey = BaseKey<'webauthn', WebAuthnP256.P256Credential> - -export type WebCryptoKey = BaseKey< - 'p256', - { - privateKey: CryptoKey - } -> - -//////////////////////////////////////////////////////////////// -// Constants -//////////////////////////////////////////////////////////////// - -const keyType = { - p256: 0, - webauthn: 1, -} as const - -const keyTypeSerialized = { - 0: 'p256', - 1: 'webauthn', -} as const - -//////////////////////////////////////////////////////////// -// Actions -//////////////////////////////////////////////////////////// - -/** Authorizes a key onto an Account. */ -export async function authorize( - client: Client, - parameters: authorize.Parameters, -) { - const { account, keys, keyIndex = 0, rpId } = parameters - - const { payload, serializedKeys } = await getAuthorizeSignPayload(client, { - address: account.address, - keys, - }) - - const signature = await sign({ account, payload, keyIndex, rpId }) - - const hash = await writeContract(client, { - abi: experimentalDelegationAbi, - address: account.address, - functionName: 'authorize', - args: [serializedKeys, signature], - account: null, - chain: null, - }) - - return { hash } -} - -export declare namespace authorize { - type Parameters = { - /** Account to add the key to. */ - account: Account - /** Keys to authorize. */ - keys: readonly Key[] - /** Index of the key to sign with. */ - keyIndex?: number | undefined - /** Relying Party ID. */ - rpId?: string | undefined - } -} - -/** Creates a new Account. */ -export async function create( - client: Client, - parameters: create.Parameters, -) { - // Generate a random private key to instantiate the Account. - // We will only hold onto the private key for the duration of this lexical scope - // (we will not persist it). - const privateKey = Secp256k1.randomPrivateKey() - - // Derive the Account's address from the private key. We will use this as the - // Transaction target, as well as for the label/id on the WebAuthn credential. - const address = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey })) - - // Prepare values needed to fill the initialize call, and extract the payloads - // to sign over. - const result = await prepareInitialize(client, { - ...parameters, - address, - }) - - // Sign the authorization to designate the delegation contract onto the - // account. - const authorization = await signAuthorization(client, { - account: privateKeyToAccount(privateKey), - ...result.authorization, - }) - - // Sign the `initialize` payload for account initialization. - const signature = Secp256k1.sign({ - payload: result.signPayload, - privateKey, - }) - - return initialize(client, { - ...result, - authorization, - signature, - }) -} - -export declare namespace create { - type Parameters = { - /** Extra keys to authorize. */ - authorizeKeys?: readonly Key[] | undefined - /** Contract address to delegate to. */ - delegation: Address.Address - /** Label for the account. */ - label?: string | undefined - /** Relying Party ID. */ - rpId?: string | undefined - } -} - -/** Creates a new WebAuthn-P256 Account key. */ -export async function createWebAuthnKey( - parameters: createWebAuthnKey.Parameters, -): Promise { - const { expiry = 0n, rpId, label, userId } = parameters - - const key = await WebAuthnP256.createCredential({ - authenticatorSelection: { - requireResidentKey: false, - residentKey: 'preferred', - userVerification: 'required', - }, - rp: rpId - ? { - id: rpId, - name: rpId, - } - : undefined, - user: { - displayName: label, - name: label, - id: userId, - }, - }) - - return { - ...key, - expiry, - status: 'unlocked', - type: 'webauthn', - } -} - -export declare namespace createWebAuthnKey { - type Parameters = { - /** Expiry for the key. */ - expiry?: bigint | undefined - /** Relying Party ID. */ - rpId?: string | undefined - /** Label for the key. */ - label: string - /** User ID. */ - userId: Bytes.Bytes - } -} - -/** Creates a new WebCrypto-P256 Account key. */ -export async function createWebCryptoKey( - parameters: createWebCryptoKey.Parameters, -): Promise { - const { expiry } = parameters - const keyPair = await WebCryptoP256.createKeyPair() - return { - ...keyPair, - expiry, - status: 'unlocked', - type: 'p256', - } -} - -export declare namespace createWebCryptoKey { - type Parameters = { - /** Expiry for the key. */ - expiry: bigint - } -} - -/** Executes calls on an Account. */ -export async function execute( - client: Client, - parameters: execute.Parameters, -) { - const { account, calls, keyIndex = 0, rpId } = parameters - - // Fetch the latest nonce. We will need to sign over it for replay protection. - const nonce = await readContract(client, { - abi: experimentalDelegationAbi, - address: account.address, - functionName: 'nonce', - }) - - // Encode the calls. - const encodedCalls = Hex.concat( - ...calls.map((call) => - AbiParameters.encodePacked( - ['uint8', 'address', 'uint256', 'uint256', 'bytes'], - [ - 0, - call.to, - call.value ?? 0n, - BigInt(Hex.size(call.data ?? '0x')), - call.data ?? '0x', - ], - ), - ), - ) - - // Construct the signing payload. - const payload = Hash.keccak256( - AbiParameters.encodePacked(['uint256', 'bytes'], [nonce, encodedCalls]), - ) - - // Sign the payload with a provided key index (we will use the key at the - // provided index to sign). - const signature = await sign({ account, payload, keyIndex, rpId }) - - // Execute the calls. - return await writeContract(client, { - abi: experimentalDelegationAbi, - address: account.address, - functionName: 'execute', - args: [encodedCalls, signature], - account: null, - chain: null, - }) -} - -export declare namespace execute { - type Parameters = { - /** Account to execute the calls on. */ - account: Account - /** Calls to execute. */ - calls: Calls - /** Index of the key to sign with. */ - keyIndex?: number | undefined - /** Relying Party ID. */ - rpId?: string | undefined - } -} - -/** Gets the signing payload for authorizing a key. */ -export async function getAuthorizeSignPayload( - client: Client, - parameters: getAuthorizeSignPayload.Parameters, -) { - const { address, keys } = parameters - - // Fetch the latest nonce. We will need to sign over it for replay protection. - const nonce = await readContract(client, { - abi: experimentalDelegationAbi, - address: address!, - functionName: 'nonce', - }) - - // Serialize the key. - const serializedKeys = serializeKeys(keys) - - // Construct the signing payload. - const payload = Hash.keccak256( - AbiParameters.encode( - AbiParameters.from([ - 'struct PublicKey { uint256 x; uint256 y; }', - 'struct Key { uint256 expiry; uint8 keyType; PublicKey publicKey; }', - 'uint256 nonce', - 'Key[] keys', - ]), - [nonce, serializedKeys], - ), - ) - - return { payload, serializedKeys } -} - -export declare namespace getAuthorizeSignPayload { - type Parameters = { - address: Address.Address - keys: readonly Key[] - } -} - -/** Initializes an Account. */ -export async function initialize( - client: Client, - parameters: initialize.Parameters, -) { - const { account, authorization, signature } = parameters - const { address, keys, label } = account - - // Serialize keys into format for contract. - const serializedKeys = serializeKeys(keys) - - // Designate the delegation with the authorization, and initialize (and authorize keys) the Account. - const hash = await writeContract(client, { - abi: experimentalDelegationAbi, - address, - functionName: 'initialize', - args: [label, serializedKeys, signature!], - authorizationList: [authorization], - account: null, - chain: null, - }) - - return { - account, - hash, - } -} - -export declare namespace initialize { - type Parameters = { - account: Account - authorization: Authorization_viem - signature: Signature.Signature - } -} - -/** Whether or not the provided key is an active session key. */ -export function isActiveSessionKey(key: Key) { - return ( - key.expiry > BigInt(Math.floor(Date.now() / 1000)) && - key.status === 'unlocked' - ) -} - -/** Loads an existing Account. */ -export async function load( - client: Client, - parameters: load.Parameters = {}, -) { - const { authorizeKeys = [], rpId } = parameters - - let address: Address.Address - let raw: PublicKeyCredential - let credentialId: string - if (parameters.address && parameters.credentialId) { - address = parameters.address - credentialId = parameters.credentialId - } else { - // We will sign a random challenge. We need to do this to extract the - // user id (ie. the address) to query for the Account's keys. - const credential = await WebAuthnP256.sign({ - challenge: '0x', - rpId, - }) - - const response = credential.raw.response as AuthenticatorAssertionResponse - address = Bytes.toHex(new Uint8Array(response.userHandle!)) - credentialId = credential.raw.id - raw = credential.raw - } - - // If there are extra keys to authorize (ie. session keys), sign over them. - const authorizeKeysResult = await (async () => { - if (authorizeKeys.length === 0) return undefined - - const { serializedKeys, payload } = await getAuthorizeSignPayload(client, { - address, - keys: authorizeKeys ?? [], - }) - - const { signature, metadata, ...rest } = await WebAuthnP256.sign({ - challenge: payload, - credentialId, - rpId, - }) - raw = rest.raw - - // if `address` and `credentialId` were passed (to remove first signature), - // check to make sure `address` matches the key used for the second signature. - if (parameters.address && parameters.credentialId) { - const response = rest.raw.response as AuthenticatorAssertionResponse - const userHandle = Bytes.toHex(new Uint8Array(response.userHandle!)) - if (address !== userHandle) - throw new Error( - `supplied address "${address}" does not match signature address "${userHandle}"`, - ) - } - - const wrappedSignature = wrapSignature({ - metadata: getWebAuthnMetadata(metadata), - signature, - }) - - return { serializedKeys, signature: wrappedSignature } - })() - - // Query for the Account's keys and label, and authorize additional keys if provided. - const [serializedKeys, label] = await Promise.all([ - readContract(client, { - abi: experimentalDelegationAbi, - address, - functionName: 'getKeys', - }), - readContract(client, { - abi: experimentalDelegationAbi, - address, - functionName: 'label', - }), - authorizeKeysResult - ? writeContract(client, { - abi: experimentalDelegationAbi, - address, - functionName: 'authorize', - args: [ - authorizeKeysResult.serializedKeys, - authorizeKeysResult.signature, - ], - account: null, - chain: null, - }) - : null, - ]) - - const keys = [ - // Hydrate the keys from the Account's contract. - ...serializedKeys.map((key, index) => { - // Assume that the first key is the "master" WebAuthn key. - if (index === 0) - return { - expiry: 0n, - id: credentialId, - publicKey: PublicKey.from(key.publicKey), - raw, - status: 'unlocked', - type: 'webauthn', - } satisfies WebAuthnKey - - return { - expiry: key.expiry, - publicKey: PublicKey.from(key.publicKey), - status: 'locked', - type: (keyTypeSerialized as any)[key.keyType], - } satisfies Key - }), - // Add the additional keys that were authorized. - ...(authorizeKeys ?? []), - ] satisfies Key[] - - return { - account: { - address, - label, - keys, - }, - } -} - -export declare namespace load { - type Parameters = { - /** Address of the account to load. */ - address?: Address.Address | undefined - /** Extra keys to authorize. */ - authorizeKeys?: readonly Key[] | undefined - /** Credential ID to use to load an existing account. */ - credentialId?: string | undefined - /** Relying Party ID. */ - rpId?: string | undefined - } -} - -/** - * Prepares values needed to fill the `initialize` function, as well as the payloads to - * sign for account initialization. - */ -export async function prepareInitialize( - client: Client, - parameters: prepareInitialize.Parameters, -): Promise { - const { address, authorizeKeys, delegation, rpId } = parameters - - // Create an identifiable label for the Account. - const label = - parameters.label ?? `${address.slice(0, 8)}\u2026${address.slice(-6)}` - - // Create a WebAuthn-P256 key to attach and authorize onto the the Account. - const key = await createWebAuthnKey({ - label, - rpId, - userId: Bytes.from(address), - }) - - const keys = [key, ...(authorizeKeys ?? [])] - - // Serialize keys into format for contract. - const serializedKeys = serializeKeys(keys) - - // Construct the initialize payload to sign (nonce will always be zero for instantiation). - const signPayload = Hash.keccak256( - AbiParameters.encode( - AbiParameters.from([ - 'struct PublicKey { uint256 x; uint256 y; }', - 'struct Key { uint256 expiry; uint8 keyType; PublicKey publicKey; }', - 'uint256 nonce', - 'string label', - 'Key[] keys', - ]), - [0n, label, serializedKeys], - ), - ) - - // Prepare an authorization to sign to designate the delegation contract onto the Account. - const authorization = await prepareAuthorization(client, { - account: address, - contractAddress: delegation, - delegate: true, - }) - - return { - account: { - address, - keys, - label, - }, - authorization, - signPayload, - } -} - -export declare namespace prepareInitialize { - type Parameters = { - /** Address of the account to import. */ - address: Address.Address - /** Extra keys to authorize. */ - authorizeKeys?: readonly Key[] | undefined - /** Contract address to delegate to. */ - delegation: Address.Address - /** Label for the account. */ - label?: string | undefined - /** Relying Party ID. */ - rpId?: string | undefined - } - - type ReturnType = { - account: Account - authorization: Authorization_viem - signPayload: Hex.Hex - } -} - -/** Serializes keys into format for the delegation contract. */ -export function serializeKeys(keys: readonly Key[]) { - return keys.map((key) => ({ - expiry: key.expiry, - keyType: keyType[key.type], - publicKey: key.publicKey, - })) -} - -/** Signs a payload with a key on the Account. */ -export async function sign(parameters: sign.Parameters) { - const { account, payload, keyIndex = 0, rpId } = parameters - - const key = account.keys[keyIndex] - - // If the key is not found, or is locked, we cannot sign. - if (!key) throw new Error('key not found') - if (key.status === 'locked') throw new Error('key is locked') - - if (key.type === 'webauthn') { - const { signature, metadata } = await WebAuthnP256.sign({ - challenge: payload, - credentialId: key.id, - rpId, - }) - - return wrapSignature({ - metadata: getWebAuthnMetadata(metadata), - signature, - }) - } - - if (key.type === 'p256') { - const signature = await WebCryptoP256.sign({ - payload, - privateKey: key.privateKey, - }) - - return wrapSignature({ - signature, - keyIndex, - prehash: true, - }) - } - - throw new Error(`type not supported: ${(key as any).type}`) -} - -export declare namespace sign { - export type Parameters = { - /** Account to sign with. */ - account: Account - /** Payload to sign. */ - payload: Hex.Hex - /** Index of the key to sign with. */ - keyIndex?: number | undefined - /** Relying Party ID. */ - rpId?: string | undefined - } -} - -//////////////////////////////////////////////////////////////// -// Helpers -//////////////////////////////////////////////////////////////// - -function wrapSignature(parameters: { - keyIndex?: number | undefined - metadata?: Hex.Hex | undefined - prehash?: boolean | undefined - signature: Signature.Signature -}) { - const { - keyIndex = 0, - metadata = '0x', - prehash = false, - signature, - } = parameters - return AbiParameters.encode( - AbiParameters.from([ - 'struct Signature { uint256 r; uint256 s; uint8 yParity; }', - 'struct WrappedSignature { uint32 keyIndex; Signature signature; bool prehash; bytes metadata; }', - 'WrappedSignature wrappedSignature', - ]), - [ - { - keyIndex, - signature: { r: signature.r, s: signature.s, yParity: 0 }, - prehash, - metadata, - }, - ], - ) -} - -function getWebAuthnMetadata(metadata: WebAuthnP256.SignMetadata) { - return AbiParameters.encode( - AbiParameters.from([ - 'struct Metadata { bytes authenticatorData; string clientDataJSON; uint16 challengeIndex; uint16 typeIndex; bool userVerificationRequired; }', - 'Metadata metadata', - ]), - [metadata], - ) -} diff --git a/src/core/internal/call.test.ts b/src/core/internal/call.test.ts new file mode 100644 index 00000000..5a395baa --- /dev/null +++ b/src/core/internal/call.test.ts @@ -0,0 +1,121 @@ +import { describe, expect, test } from 'vitest' + +import * as Call from './call.js' +import * as Key from './key.js' + +describe('authorize', () => { + test('default', () => { + const key = Key.fromP256({ + privateKey: + '0x59ff6b8de3b3b39e94b6f9fc0590cf4e3eaa9b6736e6a49c9a6b324c4f58cb9f', + role: 'admin', + }) + + const call = Call.authorize({ + key, + }) + + expect(call).toMatchInlineSnapshot(` + { + "data": "0xcebfe336000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000040ec0effa5f2f378cbf7fd2fa7ca1e8dc51cf777c129fa1c00a0e9a9205f2e511ff3f20b34a4e0b50587d055c0e0fad33d32cf1147d3bb2538fbab0d15d8e65008", + "to": "0x2323232323232323232323232323232323232323", + } + `) + }) +}) + +describe('setCanExecute', () => { + test('default', () => { + const call = Call.setCanExecute() + + expect(call).toMatchInlineSnapshot(` + { + "data": "0x136a12f73232323232323232323232323232323232323232323232323232323232323232000000000000000000000000323232323232323232323232323232323232323232323232000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", + "to": "0x2323232323232323232323232323232323232323", + } + `) + }) + + test('args: enabled', () => { + const call = Call.setCanExecute({ enabled: false }) + + expect(call).toMatchInlineSnapshot(` + { + "data": "0x136a12f73232323232323232323232323232323232323232323232323232323232323232000000000000000000000000323232323232323232323232323232323232323232323232000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "to": "0x2323232323232323232323232323232323232323", + } + `) + }) + + test('args: key', () => { + const key = Key.fromP256({ + privateKey: + '0x59ff6b8de3b3b39e94b6f9fc0590cf4e3eaa9b6736e6a49c9a6b324c4f58cb9f', + role: 'admin', + }) + + const call = Call.setCanExecute({ key }) + + expect(call).toMatchInlineSnapshot(` + { + "data": "0x136a12f7ed7ac7c7b35b77e97be67b84f5889e0ab3ecc69ab65d57db191e11f8811e9965000000000000000000000000323232323232323232323232323232323232323232323232000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", + "to": "0x2323232323232323232323232323232323232323", + } + `) + }) + + test('args: to', () => { + const key = Key.fromP256({ + privateKey: + '0x59ff6b8de3b3b39e94b6f9fc0590cf4e3eaa9b6736e6a49c9a6b324c4f58cb9f', + role: 'admin', + }) + + const call = Call.setCanExecute({ + key, + to: '0x0000000000000000000000000000000000000000', + }) + + expect(call).toMatchInlineSnapshot(` + { + "data": "0x136a12f7ed7ac7c7b35b77e97be67b84f5889e0ab3ecc69ab65d57db191e11f8811e9965000000000000000000000000000000000000000000000000000000000000000032323232000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", + "to": "0x2323232323232323232323232323232323232323", + } + `) + }) + + test('args: selector', () => { + const key = Key.fromP256({ + privateKey: + '0x59ff6b8de3b3b39e94b6f9fc0590cf4e3eaa9b6736e6a49c9a6b324c4f58cb9f', + role: 'admin', + }) + + const call = Call.setCanExecute({ + key, + selector: '0xdeadbeef', + }) + + expect(call).toMatchInlineSnapshot(` + { + "data": "0x136a12f7ed7ac7c7b35b77e97be67b84f5889e0ab3ecc69ab65d57db191e11f8811e99650000000000000000000000003232323232323232323232323232323232323232deadbeef000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", + "to": "0x2323232323232323232323232323232323232323", + } + `) + }) +}) + +describe('setLabel', () => { + test('default', () => { + const call = Call.setLabel({ + label: 'test', + }) + + expect(call).toMatchInlineSnapshot(` + { + "data": "0xbf530969000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000", + "to": "0x2323232323232323232323232323232323232323", + } + `) + }) +}) diff --git a/src/core/internal/call.ts b/src/core/internal/call.ts new file mode 100644 index 00000000..d2bf9c5e --- /dev/null +++ b/src/core/internal/call.ts @@ -0,0 +1,105 @@ +import * as AbiFunction from 'ox/AbiFunction' +import type * as Address from 'ox/Address' +import type * as Hex from 'ox/Hex' + +import { delegationAbi } from './generated.js' +import * as Key from './key.js' + +/** Stub address for self-execution. */ +export const self = '0x2323232323232323232323232323232323232323' + +export type Call = { + to: Address.Address + value?: bigint | undefined + data?: Hex.Hex | undefined +} + +/** + * Instantiates values to populate a call to authorize a key. + * + * @param parameters - Parameters. + * @returns Instantiated values. + */ +export function authorize(parameters: authorize.Parameters) { + const { key } = parameters + return { + data: AbiFunction.encodeData( + AbiFunction.fromAbi(delegationAbi, 'authorize'), + [Key.serialize(key)], + ), + to: self, + } as const satisfies Call +} + +export declare namespace authorize { + export type Parameters = { + /** Key to authorize. */ + key: Key.Key + } +} + +const anyHash = + '0x3232323232323232323232323232323232323232323232323232323232323232' +const anyTarget = '0x3232323232323232323232323232323232323232' +const anySelector = '0x32323232' + +/** + * Instantiates values to populate a call to set the label of a delegated account. + * + * @param parameters - Parameters. + * @returns Instantiated values. + */ +export function setCanExecute(parameters: setCanExecute.Parameters = {}) { + const { + enabled = true, + key, + selector = anySelector, + to = anyTarget, + } = parameters + const hash = key ? Key.hash(key) : anyHash + + return { + data: AbiFunction.encodeData( + AbiFunction.fromAbi(delegationAbi, 'setCanExecute'), + [hash, to, selector, enabled], + ), + to: self, + } as const satisfies Call +} + +export declare namespace setCanExecute { + export type Parameters = { + /** Whether to enable execution. */ + enabled?: boolean | undefined + /** Key to authorize. */ + key?: Key.Key | undefined + /** Target to authorize. */ + to?: Address.Address | undefined + /** Function selector to authorize. */ + selector?: Hex.Hex | undefined + } +} + +/** + * Instantiates values to populate a call to set the label of a delegated account. + * + * @param parameters - Parameters. + * @returns Instantiated values. + */ +export function setLabel(parameters: setLabel.Parameters) { + const { label } = parameters + return { + data: AbiFunction.encodeData( + AbiFunction.fromAbi(delegationAbi, 'setLabel'), + [label], + ), + to: self, + } as const satisfies Call +} + +export declare namespace setLabel { + export type Parameters = { + /** Label to set. */ + label: string + } +} diff --git a/src/core/internal/delegation.test.ts b/src/core/internal/delegation.test.ts new file mode 100644 index 00000000..f60feafc --- /dev/null +++ b/src/core/internal/delegation.test.ts @@ -0,0 +1,716 @@ +import { Secp256k1, Value } from 'ox' +import { privateKeyToAccount } from 'viem/accounts' +import { getBalance } from 'viem/actions' +import { describe, expect, test } from 'vitest' + +import { getAccount } from '../../../test/src/account.js' +import { client, delegation } from '../../../test/src/porto.js' +import * as Call from './call.js' +import * as Delegation from './delegation.js' +import * as Key from './key.js' + +describe('execute', () => { + describe('authorize', () => { + test('delegated: false, key: owner, keysToAuthorize: [P256], executor: JSON-RPC', async () => { + const { account } = await getAccount(client) + + const key = Key.createP256({ + role: 'admin', + }) + + await Delegation.execute(client, { + account, + calls: [ + Call.authorize({ + key, + }), + ], + delegation, + }) + + expect( + await Delegation.keyAt(client, { + account, + index: 0, + }), + ).toEqual({ + canSign: false, + expiry: key.expiry, + publicKey: key.publicKey, + role: key.role, + type: 'p256', + }) + }) + + test('delegated: true, key: owner, keysToAuthorize: [P256], executor: JSON-RPC', async () => { + const { account } = await getAccount(client) + + await Delegation.execute(client, { + account, + calls: [], + delegation, + }) + + const key = Key.createP256({ + role: 'admin', + }) + + await Delegation.execute(client, { + account, + calls: [ + Call.authorize({ + key, + }), + ], + }) + + expect( + await Delegation.keyAt(client, { + account, + index: 0, + }), + ).toEqual({ + expiry: key.expiry, + publicKey: key.publicKey, + role: key.role, + canSign: false, + type: 'p256', + }) + }) + + test('delegated: false, key: owner, keysToAuthorize: [P256], executor: EOA', async () => { + const { account, privateKey } = await getAccount(client) + + const key = Key.createP256({ + role: 'admin', + }) + + await Delegation.execute(client, { + account, + calls: [ + Call.authorize({ + key, + }), + ], + delegation, + executor: privateKeyToAccount(privateKey), + }) + + expect( + await Delegation.keyAt(client, { + account, + index: 0, + }), + ).toEqual({ + expiry: key.expiry, + publicKey: key.publicKey, + role: key.role, + canSign: false, + type: 'p256', + }) + }) + + test('delegated: true, key: owner, keysToAuthorize: [P256], executor: EOA', async () => { + const { account, privateKey } = await getAccount(client) + + await Delegation.execute(client, { + account, + calls: [], + delegation, + }) + + const key = Key.createP256({ + role: 'admin', + }) + + await Delegation.execute(client, { + account, + calls: [ + Call.authorize({ + key, + }), + ], + executor: privateKeyToAccount(privateKey), + }) + + expect( + await Delegation.keyAt(client, { + account, + index: 0, + }), + ).toEqual({ + expiry: key.expiry, + publicKey: key.publicKey, + role: key.role, + canSign: false, + type: 'p256', + }) + }) + + test('key: P256, keysToAuthorize: [P256]', async () => { + const { account } = await getAccount(client) + + const key = Key.createP256({ + role: 'admin', + }) + + await Delegation.execute(client, { + account, + calls: [ + Call.authorize({ + key, + }), + Call.setCanExecute(), + ], + delegation, + }) + + const nextKey = Key.createP256({ + role: 'admin', + }) + + await Delegation.execute(client, { + account, + calls: [ + Call.authorize({ + key: nextKey, + }), + ], + key, + }) + + expect( + await Delegation.keyAt(client, { + account, + index: 1, + }), + ).toEqual({ + expiry: nextKey.expiry, + publicKey: nextKey.publicKey, + role: nextKey.role, + canSign: false, + type: 'p256', + }) + }) + + test('key: P256, keysToAuthorize: [WebCryptoP256]', async () => { + const { account } = await getAccount(client) + + const key = Key.createP256({ + role: 'admin', + }) + + await Delegation.execute(client, { + account, + calls: [ + Call.authorize({ + key, + }), + Call.setCanExecute(), + ], + delegation, + }) + + const nextKey = await Key.createWebCryptoP256({ + role: 'admin', + }) + + await Delegation.execute(client, { + account, + calls: [ + Call.authorize({ + key: nextKey, + }), + ], + key, + }) + + expect( + await Delegation.keyAt(client, { + account, + index: 1, + }), + ).toEqual({ + expiry: nextKey.expiry, + publicKey: nextKey.publicKey, + role: nextKey.role, + canSign: false, + type: 'p256', + }) + }) + }) + + describe('arbitrary calls', () => { + test('key: p256, executor: JSON-RPC', async () => { + const key = Key.createP256({ + role: 'admin', + }) + + const { account } = await getAccount(client, { keys: [key] }) + + await Delegation.execute(client, { + account, + calls: [Call.setCanExecute(), Call.authorize({ key })], + delegation, + }) + + const alice = privateKeyToAccount(Secp256k1.randomPrivateKey()) + const bob = privateKeyToAccount(Secp256k1.randomPrivateKey()) + + const balances_before = await Promise.all([ + getBalance(client, { address: account.address }), + getBalance(client, { address: alice.address }), + getBalance(client, { address: bob.address }), + ]) + + expect(balances_before[1]).toEqual(Value.fromEther('0')) + expect(balances_before[2]).toEqual(Value.fromEther('0')) + + await Delegation.execute(client, { + account, + calls: [ + { to: alice.address, value: Value.fromEther('1') }, + { to: bob.address, value: Value.fromEther('0.5') }, + ], + key, + }) + + const balances_after = await Promise.all([ + getBalance(client, { address: account.address }), + getBalance(client, { address: alice.address }), + getBalance(client, { address: bob.address }), + ]) + + expect(balances_after[0]).not.toBeGreaterThan( + balances_before[0] - Value.fromEther('1'), + ) + expect(balances_after[1]).toEqual(Value.fromEther('1')) + expect(balances_after[2]).toEqual(Value.fromEther('0.5')) + }) + + test('key: secp256k1, executor: JSON-RPC', async () => { + const key = Key.createSecp256k1({ + role: 'admin', + }) + + const { account } = await getAccount(client, { keys: [key] }) + + await Delegation.execute(client, { + account, + calls: [Call.setCanExecute(), Call.authorize({ key })], + delegation, + }) + + const alice = privateKeyToAccount(Secp256k1.randomPrivateKey()) + const bob = privateKeyToAccount(Secp256k1.randomPrivateKey()) + + const balances_before = await Promise.all([ + getBalance(client, { address: account.address }), + getBalance(client, { address: alice.address }), + getBalance(client, { address: bob.address }), + ]) + + expect(balances_before[1]).toEqual(Value.fromEther('0')) + expect(balances_before[2]).toEqual(Value.fromEther('0')) + + await Delegation.execute(client, { + account, + calls: [ + { to: alice.address, value: Value.fromEther('1') }, + { to: bob.address, value: Value.fromEther('0.5') }, + ], + key, + }) + + const balances_after = await Promise.all([ + getBalance(client, { address: account.address }), + getBalance(client, { address: alice.address }), + getBalance(client, { address: bob.address }), + ]) + + expect(balances_after[0]).not.toBeGreaterThan( + balances_before[0] - Value.fromEther('1'), + ) + expect(balances_after[1]).toEqual(Value.fromEther('1')) + expect(balances_after[2]).toEqual(Value.fromEther('0.5')) + }) + + test('key: webcrypto, executor: JSON-RPC', async () => { + const key = await Key.createWebCryptoP256({ + role: 'admin', + }) + + const { account } = await getAccount(client, { keys: [key] }) + + await Delegation.execute(client, { + account, + calls: [Call.setCanExecute(), Call.authorize({ key })], + delegation, + }) + + const alice = privateKeyToAccount(Secp256k1.randomPrivateKey()) + const bob = privateKeyToAccount(Secp256k1.randomPrivateKey()) + + const balances_before = await Promise.all([ + getBalance(client, { address: account.address }), + getBalance(client, { address: alice.address }), + getBalance(client, { address: bob.address }), + ]) + + expect(balances_before[1]).toEqual(Value.fromEther('0')) + expect(balances_before[2]).toEqual(Value.fromEther('0')) + + await Delegation.execute(client, { + account, + calls: [ + { to: alice.address, value: Value.fromEther('1') }, + { to: bob.address, value: Value.fromEther('0.5') }, + ], + key, + }) + + const balances_after = await Promise.all([ + getBalance(client, { address: account.address }), + getBalance(client, { address: alice.address }), + getBalance(client, { address: bob.address }), + ]) + + expect(balances_after[0]).not.toBeGreaterThan( + balances_before[0] - Value.fromEther('1'), + ) + expect(balances_after[1]).toEqual(Value.fromEther('1')) + expect(balances_after[2]).toEqual(Value.fromEther('0.5')) + }) + + test('key: owner, executor: JSON-RPC', async () => { + const { account } = await getAccount(client) + + await Delegation.execute(client, { + account, + calls: [], + delegation, + }) + + const alice = privateKeyToAccount(Secp256k1.randomPrivateKey()) + const bob = privateKeyToAccount(Secp256k1.randomPrivateKey()) + + const balances_before = await Promise.all([ + getBalance(client, { address: account.address }), + getBalance(client, { address: alice.address }), + getBalance(client, { address: bob.address }), + ]) + + expect(balances_before[1]).toEqual(Value.fromEther('0')) + expect(balances_before[2]).toEqual(Value.fromEther('0')) + + await Delegation.execute(client, { + account, + calls: [ + { to: alice.address, value: Value.fromEther('1') }, + { to: bob.address, value: Value.fromEther('0.5') }, + ], + }) + + const balances_after = await Promise.all([ + getBalance(client, { address: account.address }), + getBalance(client, { address: alice.address }), + getBalance(client, { address: bob.address }), + ]) + + expect(balances_after[0]).not.toBeGreaterThan( + balances_before[0] - Value.fromEther('1'), + ) + expect(balances_after[1]).toEqual(Value.fromEther('1')) + expect(balances_after[2]).toEqual(Value.fromEther('0.5')) + }) + + test('key: owner, executor: EOA', async () => { + const { account, privateKey } = await getAccount(client) + + await Delegation.execute(client, { + account, + calls: [], + delegation, + }) + + const alice = privateKeyToAccount(Secp256k1.randomPrivateKey()) + const bob = privateKeyToAccount(Secp256k1.randomPrivateKey()) + + const balances_before = await Promise.all([ + getBalance(client, { address: account.address }), + getBalance(client, { address: alice.address }), + getBalance(client, { address: bob.address }), + ]) + + expect(balances_before[1]).toEqual(Value.fromEther('0')) + expect(balances_before[2]).toEqual(Value.fromEther('0')) + + await Delegation.execute(client, { + account, + calls: [ + { to: alice.address, value: Value.fromEther('1') }, + { to: bob.address, value: Value.fromEther('0.5') }, + ], + executor: privateKeyToAccount(privateKey), + }) + + const balances_after = await Promise.all([ + getBalance(client, { address: account.address }), + getBalance(client, { address: alice.address }), + getBalance(client, { address: bob.address }), + ]) + + expect(balances_after[0]).not.toBeGreaterThan( + balances_before[0] - Value.fromEther('1'), + ) + expect(balances_after[1]).toEqual(Value.fromEther('1')) + expect(balances_after[2]).toEqual(Value.fromEther('0.5')) + }) + }) + + test('error: insufficient funds', async () => { + const { account } = await getAccount(client) + + await expect(() => + Delegation.execute(client, { + account, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: Value.fromEther('99999'), + }, + ], + delegation, + }), + ).rejects.toThrowError('An error occurred while executing calls.') + }) + + test('error: unauthorized', async () => { + const key = Key.createP256({ + role: 'admin', + }) + + const { account } = await getAccount(client) + + await Delegation.execute(client, { + account, + calls: [ + Call.setCanExecute({ enabled: false, key }), + Call.authorize({ key }), + ], + delegation, + }) + + await expect(() => + Delegation.execute(client, { + account, + calls: [{ to: '0x0000000000000000000000000000000000000000' }], + key, + }), + ).rejects.toThrowError('Reason: Unauthorized') + }) + + test('error: key does not exist ', async () => { + const { account } = await getAccount(client) + + const key = Key.createP256({ + role: 'admin', + }) + + await expect(() => + Delegation.execute(client, { + account, + calls: [], + delegation, + key, + }), + ).rejects.toThrowError('Reason: KeyDoesNotExist') + }) +}) + +describe('prepareExecute', () => { + describe('authorize', () => { + test('delegated: false, key: owner, keysToAuthorize: [P256], executor: JSON-RPC', async () => { + const { account } = await getAccount(client) + + const keyToAuthorize = Key.createP256({ + role: 'admin', + }) + + const { request, signPayloads } = await Delegation.prepareExecute( + client, + { + account, + calls: [ + Call.authorize({ + key: keyToAuthorize, + }), + ], + delegation, + }, + ) + + const signatures = await Promise.all( + signPayloads.map((payload) => account.sign({ payload })), + ) + + await Delegation.execute(client, { + ...request, + signatures, + }) + + expect( + await Delegation.keyAt(client, { + account, + index: 0, + }), + ).toEqual({ + expiry: keyToAuthorize.expiry, + publicKey: keyToAuthorize.publicKey, + role: keyToAuthorize.role, + canSign: false, + type: 'p256', + }) + }) + + test('delegated: true, key: owner, keysToAuthorize: [P256], executor: JSON-RPC', async () => { + const { account } = await getAccount(client) + + await Delegation.execute(client, { + account, + calls: [], + delegation, + }) + + const keyToAuthorize = Key.createP256({ + role: 'admin', + }) + + const { request, signPayloads } = await Delegation.prepareExecute( + client, + { + account, + calls: [ + Call.authorize({ + key: keyToAuthorize, + }), + ], + }, + ) + + const signatures = await Promise.all( + signPayloads.map((payload) => account.sign({ payload })), + ) + + await Delegation.execute(client, { + ...request, + signatures, + }) + + expect( + await Delegation.keyAt(client, { + account, + index: 0, + }), + ).toEqual({ + expiry: keyToAuthorize.expiry, + publicKey: keyToAuthorize.publicKey, + role: keyToAuthorize.role, + canSign: false, + type: 'p256', + }) + }) + + test('delegated: false, key: owner, keysToAuthorize: [P256], executor: EOA', async () => { + const { account, privateKey } = await getAccount(client) + + const keyToAuthorize = Key.createP256({ + role: 'admin', + }) + + const { request, signPayloads } = await Delegation.prepareExecute( + client, + { + account, + calls: [ + Call.authorize({ + key: keyToAuthorize, + }), + ], + delegation, + executor: privateKeyToAccount(privateKey), + }, + ) + + const signatures = await Promise.all( + signPayloads.map((payload) => account.sign({ payload })), + ) + + await Delegation.execute(client, { + ...request, + signatures, + }) + + expect( + await Delegation.keyAt(client, { + account, + index: 0, + }), + ).toEqual({ + expiry: keyToAuthorize.expiry, + publicKey: keyToAuthorize.publicKey, + role: keyToAuthorize.role, + canSign: false, + type: 'p256', + }) + }) + }) +}) + +describe('getExecuteSignPayload', () => { + test('default', async () => { + const { account } = await getAccount(client) + + const key = Key.createP256({ + role: 'admin', + }) + + const payload = await Delegation.getExecuteSignPayload(client, { + account, + calls: [ + Call.authorize({ + key, + }), + ], + nonce: 0n, + }) + + expect(payload).toBeDefined() + }) + + test('behavior: account already delegated', async () => { + const { account } = await getAccount(client) + + await Delegation.execute(client, { + account, + calls: [], + delegation, + }) + + const key = Key.createP256({ + role: 'admin', + }) + + const payload = await Delegation.getExecuteSignPayload(client, { + account, + calls: [ + Call.authorize({ + key, + }), + ], + nonce: 0n, + }) + + expect(payload).toBeDefined() + }) +}) diff --git a/src/core/internal/delegation.ts b/src/core/internal/delegation.ts new file mode 100644 index 00000000..94a81b96 --- /dev/null +++ b/src/core/internal/delegation.ts @@ -0,0 +1,481 @@ +import * as AbiError from 'ox/AbiError' +import * as AbiParameters from 'ox/AbiParameters' +import type * as Address from 'ox/Address' +import * as Authorization from 'ox/Authorization' +import * as Errors from 'ox/Errors' +import * as Hex from 'ox/Hex' +import * as Signature from 'ox/Signature' +import * as TypedData from 'ox/TypedData' +import type { Account, BaseError, Chain, Client, Transport } from 'viem' +import { + getEip712Domain as getEip712Domain_viem, + readContract, +} from 'viem/actions' +import { + type Authorization as Authorization_viem, + prepareAuthorization, +} from 'viem/experimental' +import { + type ExecuteParameters, + type ExecuteReturnType, + execute as execute_viem, +} from 'viem/experimental/erc7821' + +import * as DelegatedAccount from './account.js' +import * as Call from './call.js' +import { delegationAbi } from './generated.js' +import * as Key from './key.js' +import type { OneOf } from './types.js' + +export const domainNameAndVersion = { + name: 'Delegation', + version: '0.0.1', +} as const + +/** + * Executes a set of calls on a delegated account. + * + * @example + * TODO + * + * @param client - Client. + * @param parameters - Execution parameters. + * @returns Transaction hash. + */ +export async function execute< + const calls extends readonly unknown[], + chain extends Chain | undefined, + account extends DelegatedAccount.Account, +>( + client: Client, + parameters: execute.Parameters, +): Promise { + // Block expression to obtain the execution request and signatures. + const { request, signatures } = await (async () => { + const { account, nonce, key, signatures } = parameters + + // If an execution has been prepared, we can early return the request and signatures. + if (nonce && signatures) return { request: parameters, signatures } + + // Otherwise, we need to prepare the execution (compute payloads and sign over them). + const { request, signPayloads: payloads } = await prepareExecute( + client, + parameters, + ) + return { + request, + signatures: await DelegatedAccount.sign(account, { + key, + payloads: payloads as any, + }), + } + })() + + const { account, authorization, executor, nonce, ...rest } = request + + const [executeSignature, authorizationSignature] = + (signatures as [Hex.Hex, Hex.Hex]) || [] + + // If an authorization signature is provided, it means that we will need to designate + // the EOA to the delegation contract. We will need to construct an authorization list + // to do so. + const authorizationList = (() => { + if (!authorizationSignature) return undefined + const signature = Signature.from(authorizationSignature) + return [ + { + ...authorization, + r: Hex.fromNumber(signature.r), + s: Hex.fromNumber(signature.s), + yParity: signature.yParity, + }, + ] + })() + + // Structure the operation data to be passed to EIP-7821 execution. + // The operation data contains the nonce of the execution, as well as the + // signature. + const opData = AbiParameters.encodePacked( + ['uint256', 'bytes'], + [nonce, executeSignature], + ) + + try { + return await execute_viem(client, { + ...rest, + address: account.address, + account: typeof executor === 'undefined' ? null : executor, + authorizationList, + opData, + } as ExecuteParameters) + } catch (e) { + const error = e as BaseError + const abiError = (() => { + const cause = error.walk((e) => 'data' in (e as BaseError)) + if (!cause) return undefined + if (!('data' in cause && cause.data)) return undefined + if (cause.data === '0x') return undefined + return AbiError.fromAbi(delegationAbi, cause.data as Hex.Hex) + })() + throw new ExecutionError(error, { abiError }) + } +} + +export declare namespace execute { + export type Parameters< + calls extends readonly unknown[] = readonly unknown[], + chain extends Chain | undefined = Chain | undefined, + account extends DelegatedAccount.Account = DelegatedAccount.Account, + > = Omit< + ExecuteParameters, + 'account' | 'address' | 'authorizationList' | 'opData' + > & { + /** + * The delegated account to execute the calls on. + */ + account: account | DelegatedAccount.Account + /** + * Contract address to delegate to. + */ + delegation?: account extends { + sign: NonNullable + } + ? Address.Address | undefined + : undefined + /** + * The executor of the execute transaction. + * + * - `Account`: execution will be attempted with the specified account. + * - `undefined`: the transaction will be filled by the JSON-RPC server. + */ + executor?: Account | undefined + } & OneOf< + | { + /** + * EIP-7702 Authorization to use for delegation. + */ + authorization?: Authorization_viem | undefined + /** + * Nonce to use for execution that will be invalidated by the delegated account. + */ + nonce: bigint + /** + * Signature for execution. Required if the `executor` is not the EOA. + */ + signatures: readonly Hex.Hex[] + } + | { + /** + * Key to use for execution. + */ + key?: number | Key.Key | undefined + } + | {} + > + + export type ReturnType = ExecuteReturnType +} + +/** + * Returns the EIP-712 domain for a delegated account. Used for the execution + * signing payload. + * + * @param client - Client. + * @param parameters - Parameters. + * @returns EIP-712 domain. + */ +export async function getEip712Domain( + client: Client, + parameters: getEip712Domain.Parameters, +): Promise { + const { account } = parameters + + const { + domain: { name, version }, + } = await getEip712Domain_viem(client, { + address: account.address, + }).catch(() => ({ domain: domainNameAndVersion })) + + if (!client.chain) throw new Error('client.chain is required') + return { + chainId: client.chain.id, + name, + version, + verifyingContract: account.address, + } +} + +export declare namespace getEip712Domain { + export type Parameters = { + /** + * The delegated account to get the EIP-712 domain for. + */ + account: DelegatedAccount.Account + } +} + +/** + * Computes the digest to sign in order to execute a set of calls on a delegated account. + * + * @example + * TODO + * + * @param client - Client. + * @param parameters - Parameters. + * @returns Sign digest. + */ +export async function getExecuteSignPayload< + const calls extends readonly unknown[], + chain extends Chain | undefined, +>( + client: Client, + parameters: getExecuteSignPayload.Parameters, +): Promise { + const { account, nonce } = parameters + + // Structure calls into EIP-7821 execution format. + const calls = parameters.calls.map((call: any) => ({ + data: call.data ?? '0x', + target: call.to === Call.self ? account.address : call.to, + value: call.value ?? 0n, + })) + + const [nonceSalt, domain] = await Promise.all([ + parameters.nonceSalt ?? + (await readContract(client, { + abi: delegationAbi, + address: account.address, + functionName: 'nonceSalt', + }).catch(() => 0n)), + getEip712Domain(client, { account }), + ]) + + if (!client.chain) throw new Error('chain is required.') + return TypedData.getSignPayload({ + domain: { + name: domain.name, + chainId: client.chain.id, + verifyingContract: account.address, + version: domain.version, + }, + types: { + Call: [ + { name: 'target', type: 'address' }, + { name: 'value', type: 'uint256' }, + { name: 'data', type: 'bytes' }, + ], + Execute: [ + { name: 'calls', type: 'Call[]' }, + { name: 'nonce', type: 'uint256' }, + { name: 'nonceSalt', type: 'uint256' }, + ], + }, + message: { + calls, + nonce, + nonceSalt, + }, + primaryType: 'Execute', + }) +} + +export declare namespace getExecuteSignPayload { + export type Parameters< + calls extends readonly unknown[] = readonly unknown[], + > = { + /** + * The delegated account to execute the calls on. + */ + account: DelegatedAccount.Account + /** + * Calls to execute. + */ + calls: calls + /** + * Nonce to use for execution that will be invalidated by the delegated account. + */ + nonce: bigint + /** + * Nonce salt. + */ + nonceSalt?: bigint | undefined + } +} + +/** + * Returns the key at the given index. + * + * @param client - Client. + * @param parameters - Parameters. + * @returns Key. + */ +export async function keyAt( + client: Client, + parameters: keyAt.Parameters, +) { + const { index } = parameters + + const account = DelegatedAccount.from(parameters.account) + + const key = await readContract(client, { + abi: delegationAbi, + address: account.address, + functionName: 'keyAt', + args: [BigInt(index)], + }) + + return Key.deserialize(key) +} + +export declare namespace keyAt { + export type Parameters = { + /** + * The delegated account to extract the key from. + */ + account: DelegatedAccount.Account | Address.Address + /** + * Index of the key to extract. + */ + index: number + } +} + +/** + * Prepares the payloads to sign over and fills the request to execute a set of calls. + * + * @example + * TODO + * + * @param client - Client. + * @param parameters - Parameters. + * @returns Prepared properties. + */ +export async function prepareExecute< + const calls extends readonly unknown[], + chain extends Chain | undefined, + account extends DelegatedAccount.Account, +>( + client: Client, + parameters: prepareExecute.Parameters, +): Promise> { + const { + account, + delegation, + executor, + nonce = Hex.toBigInt(Hex.random(32)), + ...rest + } = parameters + + const calls = parameters.calls.map((call: any) => ({ + data: call.data ?? '0x', + to: call.to === Call.self ? account.address : call.to, + value: call.value ?? 0n, + })) + + // Compute the signing payloads for execution and EIP-7702 authorization (optional). + const [executePayload, [authorization, authorizationPayload]] = + await Promise.all([ + getExecuteSignPayload(client, { + account, + calls, + nonce, + }), + + // Only need to compute an authorization payload if we are delegating to an EOA. + (async () => { + if (!delegation) return [] + + const authorization = await prepareAuthorization(client, { + account: account.address, + contractAddress: delegation, + delegate: !executor || executor, + }) + return [ + authorization, + Authorization.getSignPayload({ + address: authorization.contractAddress, + chainId: authorization.chainId, + nonce: BigInt(authorization.nonce), + }), + ] + })(), + ]) + + return { + signPayloads: [ + executePayload, + ...(authorizationPayload ? [authorizationPayload] : []), + ], + request: { + ...rest, + account, + authorization, + calls, + executor, + nonce, + }, + } as never +} + +export declare namespace prepareExecute { + export type Parameters< + calls extends readonly unknown[] = readonly unknown[], + chain extends Chain | undefined = Chain | undefined, + account extends DelegatedAccount.Account = DelegatedAccount.Account, + > = Omit< + ExecuteParameters, + 'account' | 'address' | 'authorizationList' | 'opData' + > & { + /** + * The delegated account to execute the calls on. + */ + account: account | DelegatedAccount.Account + /** + * Contract address to delegate to. + */ + delegation?: Address.Address | undefined + /** + * The executor of the execute transaction. + * + * - `Account`: execution will be attempted with the specified account. + * - `undefined`: the transaction will be filled by the JSON-RPC server. + */ + executor?: Account | undefined + /** + * Nonce to use for execution that will be invalidated by the delegated account. + */ + nonce?: bigint | undefined + } + + export type ReturnType< + calls extends readonly unknown[] = readonly unknown[], + chain extends Chain | undefined = Chain | undefined, + > = { + request: Omit, 'delegation'> & { + authorization?: Authorization_viem | undefined + nonce: bigint + } + signPayloads: + | [executePayload: Hex.Hex] + | [executePayload: Hex.Hex, authorizationPayload: Hex.Hex] + } +} + +/** Thrown when the execution fails. */ +export class ExecutionError extends Errors.BaseError { + override readonly name = 'Delegation.ExecutionError' + + abiError?: AbiError.AbiError | undefined + + constructor( + cause: BaseError, + { abiError }: { abiError?: AbiError.AbiError | undefined } = {}, + ) { + super('An error occurred while executing calls.', { + cause, + metaMessages: [abiError && 'Reason: ' + abiError.name].filter(Boolean), + }) + + this.abiError = abiError + } +} diff --git a/src/core/internal/generated.ts b/src/core/internal/generated.ts index dc01be3d..a564c455 100644 --- a/src/core/internal/generated.ts +++ b/src/core/internal/generated.ts @@ -1,113 +1,165 @@ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// AccountDelegation +// Delegation ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -export const accountDelegationAbi = [ +/** + * [__View Contract on Odyssey Testnet Odyssey Explorer__](https://odyssey-explorer.ithaca.xyz/address/0xc8f36b7222e22d192aa0b73046cdd47444392570) + */ +export const delegationAbi = [ { type: 'fallback', stateMutability: 'payable' }, { type: 'receive', stateMutability: 'payable' }, + { + type: 'function', + inputs: [], + name: 'ANY_FN_SEL', + outputs: [{ name: '', internalType: 'bytes4', type: 'bytes4' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'ANY_KEYHASH', + outputs: [{ name: '', internalType: 'bytes32', type: 'bytes32' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'ANY_TARGET', + outputs: [{ name: '', internalType: 'address', type: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'CALL_TYPEHASH', + outputs: [{ name: '', internalType: 'bytes32', type: 'bytes32' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'DOMAIN_TYPEHASH', + outputs: [{ name: '', internalType: 'bytes32', type: 'bytes32' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'EMPTY_CALLDATA_FN_SEL', + outputs: [{ name: '', internalType: 'bytes4', type: 'bytes4' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'ENTRY_POINT', + outputs: [{ name: '', internalType: 'address', type: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'EXECUTE_TYPEHASH', + outputs: [{ name: '', internalType: 'bytes32', type: 'bytes32' }], + stateMutability: 'view', + }, { type: 'function', inputs: [ { name: 'key', - internalType: 'struct AccountDelegation.Key', + internalType: 'struct Delegation.Key', type: 'tuple', components: [ - { name: 'expiry', internalType: 'uint256', type: 'uint256' }, + { name: 'expiry', internalType: 'uint40', type: 'uint40' }, { name: 'keyType', - internalType: 'enum AccountDelegation.KeyType', + internalType: 'enum Delegation.KeyType', type: 'uint8', }, - { - name: 'publicKey', - internalType: 'struct ECDSA.PublicKey', - type: 'tuple', - components: [ - { name: 'x', internalType: 'uint256', type: 'uint256' }, - { name: 'y', internalType: 'uint256', type: 'uint256' }, - ], - }, + { name: 'isSuperAdmin', internalType: 'bool', type: 'bool' }, + { name: 'publicKey', internalType: 'bytes', type: 'bytes' }, ], }, - { name: 'signature', internalType: 'bytes', type: 'bytes' }, ], name: 'authorize', - outputs: [{ name: 'keyIndex', internalType: 'uint32', type: 'uint32' }], + outputs: [{ name: 'keyHash', internalType: 'bytes32', type: 'bytes32' }], stateMutability: 'nonpayable', }, + { + type: 'function', + inputs: [ + { name: 'keyHash', internalType: 'bytes32', type: 'bytes32' }, + { name: 'target', internalType: 'address', type: 'address' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, + ], + name: 'canExecute', + outputs: [{ name: '', internalType: 'bool', type: 'bool' }], + stateMutability: 'view', + }, { type: 'function', inputs: [ { - name: 'key', - internalType: 'struct AccountDelegation.Key', - type: 'tuple', + name: 'calls', + internalType: 'struct ERC7821.Call[]', + type: 'tuple[]', components: [ - { name: 'expiry', internalType: 'uint256', type: 'uint256' }, - { - name: 'keyType', - internalType: 'enum AccountDelegation.KeyType', - type: 'uint8', - }, - { - name: 'publicKey', - internalType: 'struct ECDSA.PublicKey', - type: 'tuple', - components: [ - { name: 'x', internalType: 'uint256', type: 'uint256' }, - { name: 'y', internalType: 'uint256', type: 'uint256' }, - ], - }, + { name: 'target', internalType: 'address', type: 'address' }, + { name: 'value', internalType: 'uint256', type: 'uint256' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, ], }, + { name: 'nonce', internalType: 'uint256', type: 'uint256' }, ], - name: 'authorize', - outputs: [{ name: 'keyIndex', internalType: 'uint32', type: 'uint32' }], - stateMutability: 'nonpayable', + name: 'computeDigest', + outputs: [{ name: 'result', internalType: 'bytes32', type: 'bytes32' }], + stateMutability: 'view', }, { type: 'function', - inputs: [{ name: 'calls', internalType: 'bytes', type: 'bytes' }], - name: 'execute', - outputs: [], - stateMutability: 'nonpayable', + inputs: [], + name: 'eip712Domain', + outputs: [ + { name: 'fields', internalType: 'bytes1', type: 'bytes1' }, + { name: 'name', internalType: 'string', type: 'string' }, + { name: 'version', internalType: 'string', type: 'string' }, + { name: 'chainId', internalType: 'uint256', type: 'uint256' }, + { name: 'verifyingContract', internalType: 'address', type: 'address' }, + { name: 'salt', internalType: 'bytes32', type: 'bytes32' }, + { name: 'extensions', internalType: 'uint256[]', type: 'uint256[]' }, + ], + stateMutability: 'view', }, { type: 'function', inputs: [ - { name: 'calls', internalType: 'bytes', type: 'bytes' }, - { name: 'signature', internalType: 'bytes', type: 'bytes' }, + { name: 'mode', internalType: 'bytes32', type: 'bytes32' }, + { name: 'executionData', internalType: 'bytes', type: 'bytes' }, ], name: 'execute', outputs: [], - stateMutability: 'nonpayable', + stateMutability: 'payable', }, { type: 'function', - inputs: [], - name: 'getKeys', + inputs: [{ name: 'keyHash', internalType: 'bytes32', type: 'bytes32' }], + name: 'getKey', outputs: [ { - name: '', - internalType: 'struct AccountDelegation.Key[]', - type: 'tuple[]', + name: 'key', + internalType: 'struct Delegation.Key', + type: 'tuple', components: [ - { name: 'expiry', internalType: 'uint256', type: 'uint256' }, + { name: 'expiry', internalType: 'uint40', type: 'uint40' }, { name: 'keyType', - internalType: 'enum AccountDelegation.KeyType', + internalType: 'enum Delegation.KeyType', type: 'uint8', }, - { - name: 'publicKey', - internalType: 'struct ECDSA.PublicKey', - type: 'tuple', - components: [ - { name: 'x', internalType: 'uint256', type: 'uint256' }, - { name: 'y', internalType: 'uint256', type: 'uint256' }, - ], - }, + { name: 'isSuperAdmin', internalType: 'bool', type: 'bool' }, + { name: 'publicKey', internalType: 'bytes', type: 'bytes' }, ], }, ], @@ -116,73 +168,40 @@ export const accountDelegationAbi = [ { type: 'function', inputs: [ - { name: 'label_', internalType: 'string', type: 'string' }, { name: 'key', - internalType: 'struct AccountDelegation.Key', + internalType: 'struct Delegation.Key', type: 'tuple', components: [ - { name: 'expiry', internalType: 'uint256', type: 'uint256' }, + { name: 'expiry', internalType: 'uint40', type: 'uint40' }, { name: 'keyType', - internalType: 'enum AccountDelegation.KeyType', + internalType: 'enum Delegation.KeyType', type: 'uint8', }, - { - name: 'publicKey', - internalType: 'struct ECDSA.PublicKey', - type: 'tuple', - components: [ - { name: 'x', internalType: 'uint256', type: 'uint256' }, - { name: 'y', internalType: 'uint256', type: 'uint256' }, - ], - }, + { name: 'isSuperAdmin', internalType: 'bool', type: 'bool' }, + { name: 'publicKey', internalType: 'bytes', type: 'bytes' }, ], }, ], - name: 'initialize', - outputs: [{ name: 'keyIndex', internalType: 'uint32', type: 'uint32' }], - stateMutability: 'nonpayable', + name: 'hash', + outputs: [{ name: '', internalType: 'bytes32', type: 'bytes32' }], + stateMutability: 'pure', }, { type: 'function', - inputs: [ - { name: 'label_', internalType: 'string', type: 'string' }, - { - name: 'key', - internalType: 'struct AccountDelegation.Key', - type: 'tuple', - components: [ - { name: 'expiry', internalType: 'uint256', type: 'uint256' }, - { - name: 'keyType', - internalType: 'enum AccountDelegation.KeyType', - type: 'uint8', - }, - { - name: 'publicKey', - internalType: 'struct ECDSA.PublicKey', - type: 'tuple', - components: [ - { name: 'x', internalType: 'uint256', type: 'uint256' }, - { name: 'y', internalType: 'uint256', type: 'uint256' }, - ], - }, - ], - }, - { - name: 'signature', - internalType: 'struct ECDSA.Signature', - type: 'tuple', - components: [ - { name: 'r', internalType: 'uint256', type: 'uint256' }, - { name: 's', internalType: 'uint256', type: 'uint256' }, - { name: 'yParity', internalType: 'uint8', type: 'uint8' }, - ], - }, + inputs: [], + name: 'incrementNonceSalt', + outputs: [ + { name: 'newNonceSalt', internalType: 'uint256', type: 'uint256' }, ], - name: 'initialize', - outputs: [{ name: 'keyIndex', internalType: 'uint32', type: 'uint32' }], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [{ name: 'nonce', internalType: 'uint256', type: 'uint256' }], + name: 'invalidateNonce', + outputs: [], stateMutability: 'nonpayable', }, { @@ -192,27 +211,27 @@ export const accountDelegationAbi = [ { name: 'signature', internalType: 'bytes', type: 'bytes' }, ], name: 'isValidSignature', - outputs: [{ name: 'magicValue', internalType: 'bytes4', type: 'bytes4' }], + outputs: [{ name: '', internalType: 'bytes4', type: 'bytes4' }], stateMutability: 'view', }, { type: 'function', - inputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], - name: 'keys', + inputs: [{ name: 'i', internalType: 'uint256', type: 'uint256' }], + name: 'keyAt', outputs: [ - { name: 'expiry', internalType: 'uint256', type: 'uint256' }, - { - name: 'keyType', - internalType: 'enum AccountDelegation.KeyType', - type: 'uint8', - }, { - name: 'publicKey', - internalType: 'struct ECDSA.PublicKey', + name: '', + internalType: 'struct Delegation.Key', type: 'tuple', components: [ - { name: 'x', internalType: 'uint256', type: 'uint256' }, - { name: 'y', internalType: 'uint256', type: 'uint256' }, + { name: 'expiry', internalType: 'uint40', type: 'uint40' }, + { + name: 'keyType', + internalType: 'enum Delegation.KeyType', + type: 'uint8', + }, + { name: 'isSuperAdmin', internalType: 'bool', type: 'bool' }, + { name: 'publicKey', internalType: 'bytes', type: 'bytes' }, ], }, ], @@ -221,215 +240,253 @@ export const accountDelegationAbi = [ { type: 'function', inputs: [], - name: 'label', - outputs: [{ name: '', internalType: 'string', type: 'string' }], + name: 'keyCount', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], stateMutability: 'view', }, { type: 'function', inputs: [], - name: 'nonce', - outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + name: 'label', + outputs: [{ name: '', internalType: 'string', type: 'string' }], stateMutability: 'view', }, { type: 'function', - inputs: [{ name: 'keyIndex', internalType: 'uint32', type: 'uint32' }], - name: 'revoke', - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - inputs: [ - { name: 'keyIndex', internalType: 'uint32', type: 'uint32' }, - { name: 'signature', internalType: 'bytes', type: 'bytes' }, - ], - name: 'revoke', - outputs: [], - stateMutability: 'nonpayable', - }, - { type: 'error', inputs: [], name: 'AlreadyInitialized' }, - { type: 'error', inputs: [], name: 'InvalidAuthority' }, - { type: 'error', inputs: [], name: 'InvalidSignature' }, - { type: 'error', inputs: [], name: 'KeyExpiredOrUnauthorized' }, -] as const - -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ERC20 -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -export const erc20Abi = [ - { - type: 'function', - inputs: [], - name: 'DOMAIN_SEPARATOR', - outputs: [{ name: 'result', internalType: 'bytes32', type: 'bytes32' }], + inputs: [{ name: 'nonce', internalType: 'uint256', type: 'uint256' }], + name: 'nonceIsInvalidated', + outputs: [{ name: '', internalType: 'bool', type: 'bool' }], stateMutability: 'view', }, { type: 'function', - inputs: [ - { name: 'owner', internalType: 'address', type: 'address' }, - { name: 'spender', internalType: 'address', type: 'address' }, - ], - name: 'allowance', - outputs: [{ name: 'result', internalType: 'uint256', type: 'uint256' }], + inputs: [], + name: 'nonceSalt', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], stateMutability: 'view', }, { type: 'function', inputs: [ - { name: 'spender', internalType: 'address', type: 'address' }, - { name: 'amount', internalType: 'uint256', type: 'uint256' }, + { name: 'paymentToken', internalType: 'address', type: 'address' }, + { name: 'paymentAmount', internalType: 'uint256', type: 'uint256' }, ], - name: 'approve', + name: 'payEntryPoint', outputs: [{ name: '', internalType: 'bool', type: 'bool' }], stateMutability: 'nonpayable', }, { type: 'function', - inputs: [{ name: 'owner', internalType: 'address', type: 'address' }], - name: 'balanceOf', - outputs: [{ name: 'result', internalType: 'uint256', type: 'uint256' }], - stateMutability: 'view', - }, - { - type: 'function', - inputs: [], - name: 'decimals', - outputs: [{ name: '', internalType: 'uint8', type: 'uint8' }], - stateMutability: 'view', - }, - { - type: 'function', - inputs: [], - name: 'name', - outputs: [{ name: '', internalType: 'string', type: 'string' }], - stateMutability: 'view', - }, - { - type: 'function', - inputs: [{ name: 'owner', internalType: 'address', type: 'address' }], - name: 'nonces', - outputs: [{ name: 'result', internalType: 'uint256', type: 'uint256' }], - stateMutability: 'view', + inputs: [{ name: 'keyHash', internalType: 'bytes32', type: 'bytes32' }], + name: 'revoke', + outputs: [], + stateMutability: 'nonpayable', }, { type: 'function', inputs: [ - { name: 'owner', internalType: 'address', type: 'address' }, - { name: 'spender', internalType: 'address', type: 'address' }, - { name: 'value', internalType: 'uint256', type: 'uint256' }, - { name: 'deadline', internalType: 'uint256', type: 'uint256' }, - { name: 'v', internalType: 'uint8', type: 'uint8' }, - { name: 'r', internalType: 'bytes32', type: 'bytes32' }, - { name: 's', internalType: 'bytes32', type: 'bytes32' }, + { name: 'keyHash', internalType: 'bytes32', type: 'bytes32' }, + { name: 'target', internalType: 'address', type: 'address' }, + { name: 'fnSel', internalType: 'bytes4', type: 'bytes4' }, + { name: 'can', internalType: 'bool', type: 'bool' }, ], - name: 'permit', + name: 'setCanExecute', outputs: [], stateMutability: 'nonpayable', }, { type: 'function', - inputs: [], - name: 'symbol', - outputs: [{ name: '', internalType: 'string', type: 'string' }], - stateMutability: 'view', + inputs: [{ name: 'newLabel', internalType: 'string', type: 'string' }], + name: 'setLabel', + outputs: [], + stateMutability: 'nonpayable', }, { type: 'function', - inputs: [], - name: 'totalSupply', - outputs: [{ name: 'result', internalType: 'uint256', type: 'uint256' }], + inputs: [{ name: 'mode', internalType: 'bytes32', type: 'bytes32' }], + name: 'supportsExecutionMode', + outputs: [{ name: 'result', internalType: 'bool', type: 'bool' }], stateMutability: 'view', }, { type: 'function', inputs: [ - { name: 'to', internalType: 'address', type: 'address' }, - { name: 'amount', internalType: 'uint256', type: 'uint256' }, + { name: 'digest', internalType: 'bytes32', type: 'bytes32' }, + { name: 'signature', internalType: 'bytes', type: 'bytes' }, ], - name: 'transfer', - outputs: [{ name: '', internalType: 'bool', type: 'bool' }], - stateMutability: 'nonpayable', + name: 'unwrapAndValidateSignature', + outputs: [ + { name: 'isValid', internalType: 'bool', type: 'bool' }, + { name: 'keyHash', internalType: 'bytes32', type: 'bytes32' }, + ], + stateMutability: 'view', }, { - type: 'function', + type: 'event', + anonymous: false, inputs: [ - { name: 'from', internalType: 'address', type: 'address' }, - { name: 'to', internalType: 'address', type: 'address' }, - { name: 'amount', internalType: 'uint256', type: 'uint256' }, + { + name: 'keyHash', + internalType: 'bytes32', + type: 'bytes32', + indexed: true, + }, + { + name: 'key', + internalType: 'struct Delegation.Key', + type: 'tuple', + components: [ + { name: 'expiry', internalType: 'uint40', type: 'uint40' }, + { + name: 'keyType', + internalType: 'enum Delegation.KeyType', + type: 'uint8', + }, + { name: 'isSuperAdmin', internalType: 'bool', type: 'bool' }, + { name: 'publicKey', internalType: 'bytes', type: 'bytes' }, + ], + indexed: false, + }, ], - name: 'transferFrom', - outputs: [{ name: '', internalType: 'bool', type: 'bool' }], - stateMutability: 'nonpayable', + name: 'Authorized', }, { type: 'event', anonymous: false, inputs: [ { - name: 'owner', + name: 'keyHash', + internalType: 'bytes32', + type: 'bytes32', + indexed: false, + }, + { + name: 'target', internalType: 'address', type: 'address', - indexed: true, + indexed: false, }, + { name: 'fnSel', internalType: 'bytes4', type: 'bytes4', indexed: false }, + { name: 'can', internalType: 'bool', type: 'bool', indexed: false }, + ], + name: 'CanExecuteSet', + }, + { + type: 'event', + anonymous: false, + inputs: [ { - name: 'spender', - internalType: 'address', - type: 'address', - indexed: true, + name: 'newLabel', + internalType: 'string', + type: 'string', + indexed: false, }, + ], + name: 'LabelSet', + }, + { + type: 'event', + anonymous: false, + inputs: [ { - name: 'amount', + name: 'nonce', internalType: 'uint256', type: 'uint256', indexed: false, }, ], - name: 'Approval', + name: 'NonceInvalidated', }, { type: 'event', anonymous: false, inputs: [ - { name: 'from', internalType: 'address', type: 'address', indexed: true }, - { name: 'to', internalType: 'address', type: 'address', indexed: true }, { - name: 'amount', + name: 'newNonceSalt', internalType: 'uint256', type: 'uint256', indexed: false, }, ], - name: 'Transfer', + name: 'NonceSaltIncremented', }, - { type: 'error', inputs: [], name: 'AllowanceOverflow' }, - { type: 'error', inputs: [], name: 'AllowanceUnderflow' }, - { type: 'error', inputs: [], name: 'InsufficientAllowance' }, - { type: 'error', inputs: [], name: 'InsufficientBalance' }, - { type: 'error', inputs: [], name: 'InvalidPermit' }, - { type: 'error', inputs: [], name: 'Permit2AllowanceIsFixedAtInfinity' }, - { type: 'error', inputs: [], name: 'PermitExpired' }, - { type: 'error', inputs: [], name: 'TotalSupplyOverflow' }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'keyHash', + internalType: 'bytes32', + type: 'bytes32', + indexed: true, + }, + ], + name: 'Revoked', + }, + { type: 'error', inputs: [], name: 'CannotSelfExecute' }, + { type: 'error', inputs: [], name: 'FnSelectorNotRecognized' }, + { type: 'error', inputs: [], name: 'IndexOutOfBounds' }, + { type: 'error', inputs: [], name: 'InvalidNonce' }, + { type: 'error', inputs: [], name: 'InvalidSignature' }, + { type: 'error', inputs: [], name: 'KeyDoesNotExist' }, + { type: 'error', inputs: [], name: 'KeyExpiredOrUnauthorized' }, + { type: 'error', inputs: [], name: 'KeyHashIsZero' }, + { type: 'error', inputs: [], name: 'OpDataTooShort' }, + { type: 'error', inputs: [], name: 'Unauthorized' }, + { type: 'error', inputs: [], name: 'Unimplemented' }, + { type: 'error', inputs: [], name: 'UnsupportedExecutionMode' }, ] as const -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ExperimentERC20 -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/** + * [__View Contract on Odyssey Testnet Odyssey Explorer__](https://odyssey-explorer.ithaca.xyz/address/0xc8f36b7222e22d192aa0b73046cdd47444392570) + */ +export const delegationAddress = { + 911867: '0xc8f36b7222E22D192Aa0B73046Cdd47444392570', +} as const /** - * [__View Contract on Odyssey Testnet Odyssey Explorer__](https://odyssey-explorer.ithaca.xyz/address/0x238c8CD93ee9F8c7Edf395548eF60c0d2e46665E) + * [__View Contract on Odyssey Testnet Odyssey Explorer__](https://odyssey-explorer.ithaca.xyz/address/0xc8f36b7222e22d192aa0b73046cdd47444392570) */ -export const experimentErc20Abi = [ +export const delegationConfig = { + address: delegationAddress, + abi: delegationAbi, +} as const + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ECDSA +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const ecdsaAbi = [ + { type: 'error', inputs: [], name: 'InvalidSignature' }, +] as const + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// EIP712 +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const eip712Abi = [ { - type: 'constructor', - inputs: [{ name: 'origin', internalType: 'address', type: 'address' }], - stateMutability: 'nonpayable', + type: 'function', + inputs: [], + name: 'eip712Domain', + outputs: [ + { name: 'fields', internalType: 'bytes1', type: 'bytes1' }, + { name: 'name', internalType: 'string', type: 'string' }, + { name: 'version', internalType: 'string', type: 'string' }, + { name: 'chainId', internalType: 'uint256', type: 'uint256' }, + { name: 'verifyingContract', internalType: 'address', type: 'address' }, + { name: 'salt', internalType: 'bytes32', type: 'bytes32' }, + { name: 'extensions', internalType: 'uint256[]', type: 'uint256[]' }, + ], + stateMutability: 'view', }, - { type: 'fallback', stateMutability: 'payable' }, - { type: 'receive', stateMutability: 'payable' }, +] as const + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ERC20 +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const erc20Abi = [ { type: 'function', inputs: [], @@ -457,13 +514,6 @@ export const experimentErc20Abi = [ outputs: [{ name: '', internalType: 'bool', type: 'bool' }], stateMutability: 'nonpayable', }, - { - type: 'function', - inputs: [], - name: 'authorizedOrigin', - outputs: [{ name: '', internalType: 'address', type: 'address' }], - stateMutability: 'view', - }, { type: 'function', inputs: [{ name: 'owner', internalType: 'address', type: 'address' }], @@ -471,13 +521,6 @@ export const experimentErc20Abi = [ outputs: [{ name: 'result', internalType: 'uint256', type: 'uint256' }], stateMutability: 'view', }, - { - type: 'function', - inputs: [{ name: 'amount', internalType: 'uint256', type: 'uint256' }], - name: 'burnForEther', - outputs: [], - stateMutability: 'nonpayable', - }, { type: 'function', inputs: [], @@ -485,23 +528,6 @@ export const experimentErc20Abi = [ outputs: [{ name: '', internalType: 'uint8', type: 'uint8' }], stateMutability: 'view', }, - { - type: 'function', - inputs: [ - { name: 'to', internalType: 'address', type: 'address' }, - { name: 'value', internalType: 'uint256', type: 'uint256' }, - ], - name: 'mint', - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - inputs: [], - name: 'mintForEther', - outputs: [], - stateMutability: 'payable', - }, { type: 'function', inputs: [], @@ -616,302 +642,367 @@ export const experimentErc20Abi = [ { type: 'error', inputs: [], name: 'TotalSupplyOverflow' }, ] as const -/** - * [__View Contract on Odyssey Testnet Odyssey Explorer__](https://odyssey-explorer.ithaca.xyz/address/0x238c8CD93ee9F8c7Edf395548eF60c0d2e46665E) - */ -export const experimentErc20Address = { - 911867: '0x238c8CD93ee9F8c7Edf395548eF60c0d2e46665E', -} as const +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ERC7821 +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/** - * [__View Contract on Odyssey Testnet Odyssey Explorer__](https://odyssey-explorer.ithaca.xyz/address/0x238c8CD93ee9F8c7Edf395548eF60c0d2e46665E) - */ -export const experimentErc20Config = { - address: experimentErc20Address, - abi: experimentErc20Abi, -} as const +export const erc7821Abi = [ + { type: 'fallback', stateMutability: 'payable' }, + { type: 'receive', stateMutability: 'payable' }, + { + type: 'function', + inputs: [ + { name: 'mode', internalType: 'bytes32', type: 'bytes32' }, + { name: 'executionData', internalType: 'bytes', type: 'bytes' }, + ], + name: 'execute', + outputs: [], + stateMutability: 'payable', + }, + { + type: 'function', + inputs: [{ name: 'mode', internalType: 'bytes32', type: 'bytes32' }], + name: 'supportsExecutionMode', + outputs: [{ name: 'result', internalType: 'bool', type: 'bool' }], + stateMutability: 'view', + }, + { type: 'error', inputs: [], name: 'FnSelectorNotRecognized' }, + { type: 'error', inputs: [], name: 'UnsupportedExecutionMode' }, +] as const ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ExperimentalDelegation +// EntryPoint ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/** - * [__View Contract on Odyssey Testnet Odyssey Explorer__](https://odyssey-explorer.ithaca.xyz/address/0xb46b3f3f7F8B198894d1787b9d6c0effbd3928c9) - */ -export const experimentalDelegationAbi = [ +export const entryPointAbi = [ { type: 'fallback', stateMutability: 'payable' }, { type: 'receive', stateMutability: 'payable' }, { type: 'function', inputs: [], - name: 'OWNER_METADATA', - outputs: [{ name: '', internalType: 'bytes', type: 'bytes' }], + name: 'CALL_TYPEHASH', + outputs: [{ name: '', internalType: 'bytes32', type: 'bytes32' }], stateMutability: 'view', }, { type: 'function', - inputs: [ - { - name: 'keys_', - internalType: 'struct ExperimentalDelegation.Key[]', - type: 'tuple[]', - components: [ - { name: 'expiry', internalType: 'uint256', type: 'uint256' }, - { - name: 'keyType', - internalType: 'enum ExperimentalDelegation.KeyType', - type: 'uint8', - }, - { - name: 'publicKey', - internalType: 'struct ECDSA.PublicKey', - type: 'tuple', - components: [ - { name: 'x', internalType: 'uint256', type: 'uint256' }, - { name: 'y', internalType: 'uint256', type: 'uint256' }, - ], - }, - ], - }, - ], - name: 'authorize', - outputs: [], - stateMutability: 'nonpayable', + inputs: [], + name: 'DOMAIN_TYPEHASH', + outputs: [{ name: '', internalType: 'bytes32', type: 'bytes32' }], + stateMutability: 'view', }, { type: 'function', - inputs: [ - { - name: 'keys_', - internalType: 'struct ExperimentalDelegation.Key[]', - type: 'tuple[]', - components: [ - { name: 'expiry', internalType: 'uint256', type: 'uint256' }, - { - name: 'keyType', - internalType: 'enum ExperimentalDelegation.KeyType', - type: 'uint8', - }, - { - name: 'publicKey', - internalType: 'struct ECDSA.PublicKey', - type: 'tuple', - components: [ - { name: 'x', internalType: 'uint256', type: 'uint256' }, - { name: 'y', internalType: 'uint256', type: 'uint256' }, - ], - }, - ], - }, - { name: 'signature', internalType: 'bytes', type: 'bytes' }, - ], - name: 'authorize', - outputs: [], - stateMutability: 'nonpayable', + inputs: [], + name: 'USER_OP_TYPEHASH', + outputs: [{ name: '', internalType: 'bytes32', type: 'bytes32' }], + stateMutability: 'view', }, { type: 'function', - inputs: [{ name: 'calls', internalType: 'bytes', type: 'bytes' }], - name: 'execute', + inputs: [], + name: 'cancelOwnershipHandover', outputs: [], - stateMutability: 'nonpayable', + stateMutability: 'payable', }, { type: 'function', inputs: [ - { name: 'calls', internalType: 'bytes', type: 'bytes' }, - { name: 'signature', internalType: 'bytes', type: 'bytes' }, + { name: 'pendingOwner', internalType: 'address', type: 'address' }, ], - name: 'execute', + name: 'completeOwnershipHandover', outputs: [], - stateMutability: 'nonpayable', + stateMutability: 'payable', }, { type: 'function', inputs: [], - name: 'getKeys', + name: 'eip712Domain', outputs: [ - { - name: '', - internalType: 'struct ExperimentalDelegation.Key[]', - type: 'tuple[]', - components: [ - { name: 'expiry', internalType: 'uint256', type: 'uint256' }, - { - name: 'keyType', - internalType: 'enum ExperimentalDelegation.KeyType', - type: 'uint8', - }, - { - name: 'publicKey', - internalType: 'struct ECDSA.PublicKey', - type: 'tuple', - components: [ - { name: 'x', internalType: 'uint256', type: 'uint256' }, - { name: 'y', internalType: 'uint256', type: 'uint256' }, - ], - }, - ], - }, + { name: 'fields', internalType: 'bytes1', type: 'bytes1' }, + { name: 'name', internalType: 'string', type: 'string' }, + { name: 'version', internalType: 'string', type: 'string' }, + { name: 'chainId', internalType: 'uint256', type: 'uint256' }, + { name: 'verifyingContract', internalType: 'address', type: 'address' }, + { name: 'salt', internalType: 'bytes32', type: 'bytes32' }, + { name: 'extensions', internalType: 'uint256[]', type: 'uint256[]' }, ], stateMutability: 'view', }, { type: 'function', inputs: [ - { name: 'label_', internalType: 'string', type: 'string' }, - { - name: 'keys_', - internalType: 'struct ExperimentalDelegation.Key[]', - type: 'tuple[]', - components: [ - { name: 'expiry', internalType: 'uint256', type: 'uint256' }, - { - name: 'keyType', - internalType: 'enum ExperimentalDelegation.KeyType', - type: 'uint8', - }, - { - name: 'publicKey', - internalType: 'struct ECDSA.PublicKey', - type: 'tuple', - components: [ - { name: 'x', internalType: 'uint256', type: 'uint256' }, - { name: 'y', internalType: 'uint256', type: 'uint256' }, - ], - }, - ], - }, + { name: 'encodedUserOps', internalType: 'bytes[]', type: 'bytes[]' }, + ], + name: 'execute', + outputs: [ { - name: 'signature', - internalType: 'struct ECDSA.Signature', - type: 'tuple', - components: [ - { name: 'r', internalType: 'uint256', type: 'uint256' }, - { name: 's', internalType: 'uint256', type: 'uint256' }, - { name: 'yParity', internalType: 'uint8', type: 'uint8' }, - ], + name: 'statuses', + internalType: 'enum EntryPoint.UserOpStatus[]', + type: 'uint8[]', }, ], - name: 'initialize', - outputs: [], - stateMutability: 'nonpayable', + stateMutability: 'payable', }, { type: 'function', inputs: [ - { name: 'label_', internalType: 'string', type: 'string' }, - { - name: 'keys_', - internalType: 'struct ExperimentalDelegation.Key[]', - type: 'tuple[]', - components: [ - { name: 'expiry', internalType: 'uint256', type: 'uint256' }, - { - name: 'keyType', - internalType: 'enum ExperimentalDelegation.KeyType', - type: 'uint8', - }, - { - name: 'publicKey', - internalType: 'struct ECDSA.PublicKey', - type: 'tuple', - components: [ - { name: 'x', internalType: 'uint256', type: 'uint256' }, - { name: 'y', internalType: 'uint256', type: 'uint256' }, - ], - }, - ], - }, + { name: 'orderId', internalType: 'bytes32', type: 'bytes32' }, + { name: 'originData', internalType: 'bytes', type: 'bytes' }, + { name: '', internalType: 'bytes', type: 'bytes' }, ], - name: 'initialize', - outputs: [], - stateMutability: 'nonpayable', + name: 'fill', + outputs: [ + { name: '', internalType: 'enum EntryPoint.UserOpStatus', type: 'uint8' }, + ], + stateMutability: 'payable', }, { type: 'function', - inputs: [ - { name: 'digest', internalType: 'bytes32', type: 'bytes32' }, - { name: 'signature', internalType: 'bytes', type: 'bytes' }, - ], - name: 'isValidSignature', - outputs: [{ name: 'magicValue', internalType: 'bytes4', type: 'bytes4' }], + inputs: [{ name: 'orderId', internalType: 'bytes32', type: 'bytes32' }], + name: 'orderIdIsFilled', + outputs: [{ name: '', internalType: 'bool', type: 'bool' }], stateMutability: 'view', }, { type: 'function', - inputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], - name: 'keys', - outputs: [ - { name: 'expiry', internalType: 'uint256', type: 'uint256' }, - { - name: 'keyType', - internalType: 'enum ExperimentalDelegation.KeyType', - type: 'uint8', - }, - { - name: 'publicKey', - internalType: 'struct ECDSA.PublicKey', - type: 'tuple', - components: [ - { name: 'x', internalType: 'uint256', type: 'uint256' }, - { name: 'y', internalType: 'uint256', type: 'uint256' }, - ], - }, + inputs: [], + name: 'owner', + outputs: [{ name: 'result', internalType: 'address', type: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [ + { name: 'pendingOwner', internalType: 'address', type: 'address' }, ], + name: 'ownershipHandoverExpiresAt', + outputs: [{ name: 'result', internalType: 'uint256', type: 'uint256' }], stateMutability: 'view', }, { type: 'function', inputs: [], - name: 'label', - outputs: [{ name: '', internalType: 'string', type: 'string' }], + name: 'proxiableUUID', + outputs: [{ name: '', internalType: 'bytes32', type: 'bytes32' }], stateMutability: 'view', }, { type: 'function', inputs: [], - name: 'nonce', - outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], - stateMutability: 'view', + name: 'renounceOwnership', + outputs: [], + stateMutability: 'payable', }, { type: 'function', - inputs: [{ name: 'keyIndex', internalType: 'uint32', type: 'uint32' }], - name: 'revoke', + inputs: [], + name: 'requestOwnershipHandover', outputs: [], - stateMutability: 'nonpayable', + stateMutability: 'payable', + }, + { + type: 'function', + inputs: [{ name: 'newOwner', internalType: 'address', type: 'address' }], + name: 'transferOwnership', + outputs: [], + stateMutability: 'payable', }, { type: 'function', inputs: [ - { name: 'keyIndex', internalType: 'uint32', type: 'uint32' }, - { name: 'signature', internalType: 'bytes', type: 'bytes' }, + { name: 'newImplementation', internalType: 'address', type: 'address' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, ], - name: 'revoke', + name: 'upgradeToAndCall', outputs: [], - stateMutability: 'nonpayable', + stateMutability: 'payable', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'pendingOwner', + internalType: 'address', + type: 'address', + indexed: true, + }, + ], + name: 'OwnershipHandoverCanceled', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'pendingOwner', + internalType: 'address', + type: 'address', + indexed: true, + }, + ], + name: 'OwnershipHandoverRequested', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'oldOwner', + internalType: 'address', + type: 'address', + indexed: true, + }, + { + name: 'newOwner', + internalType: 'address', + type: 'address', + indexed: true, + }, + ], + name: 'OwnershipTransferred', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'implementation', + internalType: 'address', + type: 'address', + indexed: true, + }, + ], + name: 'Upgraded', }, { type: 'error', inputs: [], name: 'AlreadyInitialized' }, - { type: 'error', inputs: [], name: 'InvalidAuthority' }, - { type: 'error', inputs: [], name: 'InvalidSignature' }, - { type: 'error', inputs: [], name: 'KeyExpiredOrUnauthorized' }, + { type: 'error', inputs: [], name: 'EntryPointPaymentFailed' }, + { type: 'error', inputs: [], name: 'FnSelectorNotRecognized' }, + { type: 'error', inputs: [], name: 'InsufficientGas' }, + { type: 'error', inputs: [], name: 'NewOwnerIsZeroAddress' }, + { type: 'error', inputs: [], name: 'NoHandoverRequest' }, + { type: 'error', inputs: [], name: 'OrderAlreadyFilled' }, + { type: 'error', inputs: [], name: 'OriginDataDecodeError' }, + { type: 'error', inputs: [], name: 'Reentrancy' }, + { type: 'error', inputs: [], name: 'SelfCallUnauthorized' }, + { type: 'error', inputs: [], name: 'Unauthorized' }, + { type: 'error', inputs: [], name: 'UnauthorizedCallContext' }, + { type: 'error', inputs: [], name: 'UpgradeFailed' }, + { type: 'error', inputs: [], name: 'UserOpDecodeError' }, ] as const -/** - * [__View Contract on Odyssey Testnet Odyssey Explorer__](https://odyssey-explorer.ithaca.xyz/address/0xb46b3f3f7F8B198894d1787b9d6c0effbd3928c9) - */ -export const experimentalDelegationAddress = { - 911867: '0xb46b3f3f7F8B198894d1787b9d6c0effbd3928c9', -} as const +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// EnumerableSetLib +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/** - * [__View Contract on Odyssey Testnet Odyssey Explorer__](https://odyssey-explorer.ithaca.xyz/address/0xb46b3f3f7F8B198894d1787b9d6c0effbd3928c9) - */ -export const experimentalDelegationConfig = { - address: experimentalDelegationAddress, - abi: experimentalDelegationAbi, -} as const +export const enumerableSetLibAbi = [ + { type: 'error', inputs: [], name: 'IndexOutOfBounds' }, + { type: 'error', inputs: [], name: 'ValueIsZeroSentinel' }, +] as const + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// GuardedExecutor +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const guardedExecutorAbi = [ + { type: 'fallback', stateMutability: 'payable' }, + { type: 'receive', stateMutability: 'payable' }, + { + type: 'function', + inputs: [], + name: 'ANY_FN_SEL', + outputs: [{ name: '', internalType: 'bytes4', type: 'bytes4' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'ANY_KEYHASH', + outputs: [{ name: '', internalType: 'bytes32', type: 'bytes32' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'ANY_TARGET', + outputs: [{ name: '', internalType: 'address', type: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'EMPTY_CALLDATA_FN_SEL', + outputs: [{ name: '', internalType: 'bytes4', type: 'bytes4' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [ + { name: 'keyHash', internalType: 'bytes32', type: 'bytes32' }, + { name: 'target', internalType: 'address', type: 'address' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, + ], + name: 'canExecute', + outputs: [{ name: '', internalType: 'bool', type: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [ + { name: 'mode', internalType: 'bytes32', type: 'bytes32' }, + { name: 'executionData', internalType: 'bytes', type: 'bytes' }, + ], + name: 'execute', + outputs: [], + stateMutability: 'payable', + }, + { + type: 'function', + inputs: [ + { name: 'keyHash', internalType: 'bytes32', type: 'bytes32' }, + { name: 'target', internalType: 'address', type: 'address' }, + { name: 'fnSel', internalType: 'bytes4', type: 'bytes4' }, + { name: 'can', internalType: 'bool', type: 'bool' }, + ], + name: 'setCanExecute', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [{ name: 'mode', internalType: 'bytes32', type: 'bytes32' }], + name: 'supportsExecutionMode', + outputs: [{ name: 'result', internalType: 'bool', type: 'bool' }], + stateMutability: 'view', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'keyHash', + internalType: 'bytes32', + type: 'bytes32', + indexed: false, + }, + { + name: 'target', + internalType: 'address', + type: 'address', + indexed: false, + }, + { name: 'fnSel', internalType: 'bytes4', type: 'bytes4', indexed: false }, + { name: 'can', internalType: 'bool', type: 'bool', indexed: false }, + ], + name: 'CanExecuteSet', + }, + { type: 'error', inputs: [], name: 'CannotSelfExecute' }, + { type: 'error', inputs: [], name: 'FnSelectorNotRecognized' }, + { type: 'error', inputs: [], name: 'KeyHashIsZero' }, + { type: 'error', inputs: [], name: 'Unauthorized' }, + { type: 'error', inputs: [], name: 'UnsupportedExecutionMode' }, +] as const ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // IMulticall3 @@ -1157,12 +1248,1670 @@ export const iMulticall3Abi = [ ] as const ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Receiver +// LibClone ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -export const receiverAbi = [ +export const libCloneAbi = [ + { type: 'error', inputs: [], name: 'DeploymentFailed' }, + { type: 'error', inputs: [], name: 'ETHTransferFailed' }, + { type: 'error', inputs: [], name: 'SaltDoesNotStartWith' }, +] as const + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// LibERC7579 +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const libErc7579Abi = [ + { type: 'error', inputs: [], name: 'DecodingError' }, +] as const + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// MockEntryPoint +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const mockEntryPointAbi = [ + { type: 'constructor', inputs: [], stateMutability: 'nonpayable' }, { type: 'fallback', stateMutability: 'payable' }, { type: 'receive', stateMutability: 'payable' }, + { + type: 'function', + inputs: [], + name: 'CALL_TYPEHASH', + outputs: [{ name: '', internalType: 'bytes32', type: 'bytes32' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'DOMAIN_TYPEHASH', + outputs: [{ name: '', internalType: 'bytes32', type: 'bytes32' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'USER_OP_TYPEHASH', + outputs: [{ name: '', internalType: 'bytes32', type: 'bytes32' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'cancelOwnershipHandover', + outputs: [], + stateMutability: 'payable', + }, + { + type: 'function', + inputs: [ + { name: 'pendingOwner', internalType: 'address', type: 'address' }, + ], + name: 'completeOwnershipHandover', + outputs: [], + stateMutability: 'payable', + }, + { + type: 'function', + inputs: [ + { + name: 'userOp', + internalType: 'struct EntryPoint.UserOp', + type: 'tuple', + components: [ + { name: 'eoa', internalType: 'address', type: 'address' }, + { name: 'executionData', internalType: 'bytes', type: 'bytes' }, + { name: 'nonce', internalType: 'uint256', type: 'uint256' }, + { name: 'paymentToken', internalType: 'address', type: 'address' }, + { + name: 'paymentRecipient', + internalType: 'address', + type: 'address', + }, + { name: 'paymentAmount', internalType: 'uint256', type: 'uint256' }, + { + name: 'paymentMaxAmount', + internalType: 'uint256', + type: 'uint256', + }, + { name: 'paymentGas', internalType: 'uint256', type: 'uint256' }, + { name: 'verificationGas', internalType: 'uint256', type: 'uint256' }, + { name: 'callGas', internalType: 'uint256', type: 'uint256' }, + { name: 'signature', internalType: 'bytes', type: 'bytes' }, + ], + }, + ], + name: 'computeDigest', + outputs: [{ name: '', internalType: 'bytes32', type: 'bytes32' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'eip712Domain', + outputs: [ + { name: 'fields', internalType: 'bytes1', type: 'bytes1' }, + { name: 'name', internalType: 'string', type: 'string' }, + { name: 'version', internalType: 'string', type: 'string' }, + { name: 'chainId', internalType: 'uint256', type: 'uint256' }, + { name: 'verifyingContract', internalType: 'address', type: 'address' }, + { name: 'salt', internalType: 'bytes32', type: 'bytes32' }, + { name: 'extensions', internalType: 'uint256[]', type: 'uint256[]' }, + ], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [ + { name: 'encodedUserOps', internalType: 'bytes[]', type: 'bytes[]' }, + ], + name: 'execute', + outputs: [ + { + name: 'statuses', + internalType: 'enum EntryPoint.UserOpStatus[]', + type: 'uint8[]', + }, + ], + stateMutability: 'payable', + }, + { + type: 'function', + inputs: [ + { name: 'orderId', internalType: 'bytes32', type: 'bytes32' }, + { name: 'originData', internalType: 'bytes', type: 'bytes' }, + { name: '', internalType: 'bytes', type: 'bytes' }, + ], + name: 'fill', + outputs: [ + { name: '', internalType: 'enum EntryPoint.UserOpStatus', type: 'uint8' }, + ], + stateMutability: 'payable', + }, + { + type: 'function', + inputs: [{ name: 'orderId', internalType: 'bytes32', type: 'bytes32' }], + name: 'orderIdIsFilled', + outputs: [{ name: '', internalType: 'bool', type: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'owner', + outputs: [{ name: 'result', internalType: 'address', type: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [ + { name: 'pendingOwner', internalType: 'address', type: 'address' }, + ], + name: 'ownershipHandoverExpiresAt', + outputs: [{ name: 'result', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'proxiableUUID', + outputs: [{ name: '', internalType: 'bytes32', type: 'bytes32' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'renounceOwnership', + outputs: [], + stateMutability: 'payable', + }, + { + type: 'function', + inputs: [], + name: 'requestOwnershipHandover', + outputs: [], + stateMutability: 'payable', + }, + { + type: 'function', + inputs: [{ name: 'newOwner', internalType: 'address', type: 'address' }], + name: 'transferOwnership', + outputs: [], + stateMutability: 'payable', + }, + { + type: 'function', + inputs: [ + { name: 'newImplementation', internalType: 'address', type: 'address' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, + ], + name: 'upgradeToAndCall', + outputs: [], + stateMutability: 'payable', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'pendingOwner', + internalType: 'address', + type: 'address', + indexed: true, + }, + ], + name: 'OwnershipHandoverCanceled', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'pendingOwner', + internalType: 'address', + type: 'address', + indexed: true, + }, + ], + name: 'OwnershipHandoverRequested', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'oldOwner', + internalType: 'address', + type: 'address', + indexed: true, + }, + { + name: 'newOwner', + internalType: 'address', + type: 'address', + indexed: true, + }, + ], + name: 'OwnershipTransferred', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'implementation', + internalType: 'address', + type: 'address', + indexed: true, + }, + ], + name: 'Upgraded', + }, + { type: 'error', inputs: [], name: 'AlreadyInitialized' }, + { type: 'error', inputs: [], name: 'EntryPointPaymentFailed' }, + { type: 'error', inputs: [], name: 'FnSelectorNotRecognized' }, + { type: 'error', inputs: [], name: 'InsufficientGas' }, + { type: 'error', inputs: [], name: 'NewOwnerIsZeroAddress' }, + { type: 'error', inputs: [], name: 'NoHandoverRequest' }, + { type: 'error', inputs: [], name: 'OrderAlreadyFilled' }, + { type: 'error', inputs: [], name: 'OriginDataDecodeError' }, + { type: 'error', inputs: [], name: 'Reentrancy' }, + { type: 'error', inputs: [], name: 'SelfCallUnauthorized' }, + { type: 'error', inputs: [], name: 'Unauthorized' }, + { type: 'error', inputs: [], name: 'UnauthorizedCallContext' }, + { type: 'error', inputs: [], name: 'UpgradeFailed' }, + { type: 'error', inputs: [], name: 'UserOpDecodeError' }, +] as const + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// MockPaymentToken +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const mockPaymentTokenAbi = [ + { + type: 'function', + inputs: [], + name: 'DOMAIN_SEPARATOR', + outputs: [{ name: 'result', internalType: 'bytes32', type: 'bytes32' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [ + { name: 'owner', internalType: 'address', type: 'address' }, + { name: 'spender', internalType: 'address', type: 'address' }, + ], + name: 'allowance', + outputs: [{ name: 'result', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [ + { name: 'spender', internalType: 'address', type: 'address' }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + name: 'approve', + outputs: [{ name: '', internalType: 'bool', type: 'bool' }], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [{ name: 'owner', internalType: 'address', type: 'address' }], + name: 'balanceOf', + outputs: [{ name: 'result', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'decimals', + outputs: [{ name: '', internalType: 'uint8', type: 'uint8' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [ + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + name: 'mint', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [], + name: 'name', + outputs: [{ name: '', internalType: 'string', type: 'string' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [{ name: 'owner', internalType: 'address', type: 'address' }], + name: 'nonces', + outputs: [{ name: 'result', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [ + { name: 'owner', internalType: 'address', type: 'address' }, + { name: 'spender', internalType: 'address', type: 'address' }, + { name: 'value', internalType: 'uint256', type: 'uint256' }, + { name: 'deadline', internalType: 'uint256', type: 'uint256' }, + { name: 'v', internalType: 'uint8', type: 'uint8' }, + { name: 'r', internalType: 'bytes32', type: 'bytes32' }, + { name: 's', internalType: 'bytes32', type: 'bytes32' }, + ], + name: 'permit', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [], + name: 'symbol', + outputs: [{ name: '', internalType: 'string', type: 'string' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'totalSupply', + outputs: [{ name: 'result', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [ + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + name: 'transfer', + outputs: [{ name: '', internalType: 'bool', type: 'bool' }], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [ + { name: 'from', internalType: 'address', type: 'address' }, + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + name: 'transferFrom', + outputs: [{ name: '', internalType: 'bool', type: 'bool' }], + stateMutability: 'nonpayable', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'owner', + internalType: 'address', + type: 'address', + indexed: true, + }, + { + name: 'spender', + internalType: 'address', + type: 'address', + indexed: true, + }, + { + name: 'amount', + internalType: 'uint256', + type: 'uint256', + indexed: false, + }, + ], + name: 'Approval', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'from', internalType: 'address', type: 'address', indexed: true }, + { name: 'to', internalType: 'address', type: 'address', indexed: true }, + { + name: 'amount', + internalType: 'uint256', + type: 'uint256', + indexed: false, + }, + ], + name: 'Transfer', + }, + { type: 'error', inputs: [], name: 'AllowanceOverflow' }, + { type: 'error', inputs: [], name: 'AllowanceUnderflow' }, + { type: 'error', inputs: [], name: 'InsufficientAllowance' }, + { type: 'error', inputs: [], name: 'InsufficientBalance' }, + { type: 'error', inputs: [], name: 'InvalidPermit' }, + { type: 'error', inputs: [], name: 'Permit2AllowanceIsFixedAtInfinity' }, + { type: 'error', inputs: [], name: 'PermitExpired' }, + { type: 'error', inputs: [], name: 'TotalSupplyOverflow' }, +] as const + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Ownable +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const ownableAbi = [ + { + type: 'function', + inputs: [], + name: 'cancelOwnershipHandover', + outputs: [], + stateMutability: 'payable', + }, + { + type: 'function', + inputs: [ + { name: 'pendingOwner', internalType: 'address', type: 'address' }, + ], + name: 'completeOwnershipHandover', + outputs: [], + stateMutability: 'payable', + }, + { + type: 'function', + inputs: [], + name: 'owner', + outputs: [{ name: 'result', internalType: 'address', type: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [ + { name: 'pendingOwner', internalType: 'address', type: 'address' }, + ], + name: 'ownershipHandoverExpiresAt', + outputs: [{ name: 'result', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'renounceOwnership', + outputs: [], + stateMutability: 'payable', + }, + { + type: 'function', + inputs: [], + name: 'requestOwnershipHandover', + outputs: [], + stateMutability: 'payable', + }, + { + type: 'function', + inputs: [{ name: 'newOwner', internalType: 'address', type: 'address' }], + name: 'transferOwnership', + outputs: [], + stateMutability: 'payable', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'pendingOwner', + internalType: 'address', + type: 'address', + indexed: true, + }, + ], + name: 'OwnershipHandoverCanceled', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'pendingOwner', + internalType: 'address', + type: 'address', + indexed: true, + }, + ], + name: 'OwnershipHandoverRequested', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'oldOwner', + internalType: 'address', + type: 'address', + indexed: true, + }, + { + name: 'newOwner', + internalType: 'address', + type: 'address', + indexed: true, + }, + ], + name: 'OwnershipTransferred', + }, + { type: 'error', inputs: [], name: 'AlreadyInitialized' }, + { type: 'error', inputs: [], name: 'NewOwnerIsZeroAddress' }, + { type: 'error', inputs: [], name: 'NoHandoverRequest' }, + { type: 'error', inputs: [], name: 'Unauthorized' }, +] as const + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// P256 +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const p256Abi = [ + { type: 'error', inputs: [], name: 'P256VerificationFailed' }, +] as const + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Receiver +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const receiverAbi = [ + { type: 'fallback', stateMutability: 'payable' }, + { type: 'receive', stateMutability: 'payable' }, + { type: 'error', inputs: [], name: 'FnSelectorNotRecognized' }, +] as const + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ReentrancyGuard +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const reentrancyGuardAbi = [ + { type: 'error', inputs: [], name: 'Reentrancy' }, +] as const + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// SafeTransferLib +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const safeTransferLibAbi = [ + { type: 'error', inputs: [], name: 'ApproveFailed' }, + { type: 'error', inputs: [], name: 'ETHTransferFailed' }, + { type: 'error', inputs: [], name: 'Permit2AmountOverflow' }, + { type: 'error', inputs: [], name: 'Permit2Failed' }, + { type: 'error', inputs: [], name: 'TotalSupplyQueryFailed' }, + { type: 'error', inputs: [], name: 'TransferFailed' }, + { type: 'error', inputs: [], name: 'TransferFromFailed' }, +] as const + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// SoladyTest +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const soladyTestAbi = [ + { + type: 'function', + inputs: [], + name: 'IS_TEST', + outputs: [{ name: '', internalType: 'bool', type: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'excludeArtifacts', + outputs: [ + { + name: 'excludedArtifacts_', + internalType: 'string[]', + type: 'string[]', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'excludeContracts', + outputs: [ + { + name: 'excludedContracts_', + internalType: 'address[]', + type: 'address[]', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'excludeSelectors', + outputs: [ + { + name: 'excludedSelectors_', + internalType: 'struct StdInvariant.FuzzSelector[]', + type: 'tuple[]', + components: [ + { name: 'addr', internalType: 'address', type: 'address' }, + { name: 'selectors', internalType: 'bytes4[]', type: 'bytes4[]' }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'excludeSenders', + outputs: [ + { + name: 'excludedSenders_', + internalType: 'address[]', + type: 'address[]', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'failed', + outputs: [{ name: '', internalType: 'bool', type: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'targetArtifactSelectors', + outputs: [ + { + name: 'targetedArtifactSelectors_', + internalType: 'struct StdInvariant.FuzzArtifactSelector[]', + type: 'tuple[]', + components: [ + { name: 'artifact', internalType: 'string', type: 'string' }, + { name: 'selectors', internalType: 'bytes4[]', type: 'bytes4[]' }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'targetArtifacts', + outputs: [ + { + name: 'targetedArtifacts_', + internalType: 'string[]', + type: 'string[]', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'targetContracts', + outputs: [ + { + name: 'targetedContracts_', + internalType: 'address[]', + type: 'address[]', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'targetInterfaces', + outputs: [ + { + name: 'targetedInterfaces_', + internalType: 'struct StdInvariant.FuzzInterface[]', + type: 'tuple[]', + components: [ + { name: 'addr', internalType: 'address', type: 'address' }, + { name: 'artifacts', internalType: 'string[]', type: 'string[]' }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'targetSelectors', + outputs: [ + { + name: 'targetedSelectors_', + internalType: 'struct StdInvariant.FuzzSelector[]', + type: 'tuple[]', + components: [ + { name: 'addr', internalType: 'address', type: 'address' }, + { name: 'selectors', internalType: 'bytes4[]', type: 'bytes4[]' }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'targetSenders', + outputs: [ + { + name: 'targetedSenders_', + internalType: 'address[]', + type: 'address[]', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'test__codesize', + outputs: [], + stateMutability: 'view', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'name', internalType: 'string', type: 'string', indexed: false }, + { + name: 'value', + internalType: 'address', + type: 'address', + indexed: false, + }, + ], + name: 'LogAddress', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'value', + internalType: 'address', + type: 'address', + indexed: false, + }, + ], + name: 'LogAddress', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'name', internalType: 'string', type: 'string', indexed: false }, + { + name: 'value', + internalType: 'address[]', + type: 'address[]', + indexed: false, + }, + ], + name: 'LogAddressArray', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'value', + internalType: 'address[]', + type: 'address[]', + indexed: false, + }, + ], + name: 'LogAddressArray', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'name', internalType: 'string', type: 'string', indexed: false }, + { name: 'value', internalType: 'bool', type: 'bool', indexed: false }, + ], + name: 'LogBool', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'value', internalType: 'bool', type: 'bool', indexed: false }, + ], + name: 'LogBool', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'name', internalType: 'string', type: 'string', indexed: false }, + { name: 'value', internalType: 'bool[]', type: 'bool[]', indexed: false }, + ], + name: 'LogBoolArray', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'value', internalType: 'bool[]', type: 'bool[]', indexed: false }, + ], + name: 'LogBoolArray', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'name', internalType: 'string', type: 'string', indexed: false }, + { name: 'value', internalType: 'bytes', type: 'bytes', indexed: false }, + ], + name: 'LogBytes', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'value', internalType: 'bytes', type: 'bytes', indexed: false }, + ], + name: 'LogBytes', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'name', internalType: 'string', type: 'string', indexed: false }, + { + name: 'value', + internalType: 'bytes32', + type: 'bytes32', + indexed: false, + }, + ], + name: 'LogBytes32', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'value', + internalType: 'bytes32', + type: 'bytes32', + indexed: false, + }, + ], + name: 'LogBytes32', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'name', internalType: 'string', type: 'string', indexed: false }, + { + name: 'value', + internalType: 'bytes32[]', + type: 'bytes32[]', + indexed: false, + }, + ], + name: 'LogBytes32Array', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'value', + internalType: 'bytes32[]', + type: 'bytes32[]', + indexed: false, + }, + ], + name: 'LogBytes32Array', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'name', internalType: 'string', type: 'string', indexed: false }, + { + name: 'value', + internalType: 'bytes[]', + type: 'bytes[]', + indexed: false, + }, + ], + name: 'LogBytesArray', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'value', + internalType: 'bytes[]', + type: 'bytes[]', + indexed: false, + }, + ], + name: 'LogBytesArray', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'name', internalType: 'string', type: 'string', indexed: false }, + { name: 'value', internalType: 'int256', type: 'int256', indexed: false }, + ], + name: 'LogInt', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'value', internalType: 'int256', type: 'int256', indexed: false }, + ], + name: 'LogInt', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'name', internalType: 'string', type: 'string', indexed: false }, + { + name: 'value', + internalType: 'int256[]', + type: 'int256[]', + indexed: false, + }, + ], + name: 'LogIntArray', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'value', + internalType: 'int256[]', + type: 'int256[]', + indexed: false, + }, + ], + name: 'LogIntArray', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'name', internalType: 'string', type: 'string', indexed: false }, + { name: 'value', internalType: 'string', type: 'string', indexed: false }, + ], + name: 'LogString', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'value', internalType: 'string', type: 'string', indexed: false }, + ], + name: 'LogString', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'name', internalType: 'string', type: 'string', indexed: false }, + { + name: 'value', + internalType: 'string[]', + type: 'string[]', + indexed: false, + }, + ], + name: 'LogStringArray', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'value', + internalType: 'string[]', + type: 'string[]', + indexed: false, + }, + ], + name: 'LogStringArray', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'name', internalType: 'string', type: 'string', indexed: false }, + { + name: 'value', + internalType: 'uint256', + type: 'uint256', + indexed: false, + }, + ], + name: 'LogUint', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'value', + internalType: 'uint256', + type: 'uint256', + indexed: false, + }, + ], + name: 'LogUint', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'name', internalType: 'string', type: 'string', indexed: false }, + { + name: 'value', + internalType: 'uint256[]', + type: 'uint256[]', + indexed: false, + }, + ], + name: 'LogUintArray', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'value', + internalType: 'uint256[]', + type: 'uint256[]', + indexed: false, + }, + ], + name: 'LogUintArray', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: '', internalType: 'string', type: 'string', indexed: false }, + ], + name: 'log', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: '', internalType: 'address', type: 'address', indexed: false }, + ], + name: 'log_address', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'val', + internalType: 'uint256[]', + type: 'uint256[]', + indexed: false, + }, + ], + name: 'log_array', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'val', + internalType: 'int256[]', + type: 'int256[]', + indexed: false, + }, + ], + name: 'log_array', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'val', + internalType: 'address[]', + type: 'address[]', + indexed: false, + }, + ], + name: 'log_array', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: '', internalType: 'bytes', type: 'bytes', indexed: false }, + ], + name: 'log_bytes', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: '', internalType: 'bytes32', type: 'bytes32', indexed: false }, + ], + name: 'log_bytes32', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: '', internalType: 'int256', type: 'int256', indexed: false }, + ], + name: 'log_int', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'key', internalType: 'string', type: 'string', indexed: false }, + { name: 'val', internalType: 'address', type: 'address', indexed: false }, + ], + name: 'log_named_address', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'key', internalType: 'string', type: 'string', indexed: false }, + { + name: 'val', + internalType: 'uint256[]', + type: 'uint256[]', + indexed: false, + }, + ], + name: 'log_named_array', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'key', internalType: 'string', type: 'string', indexed: false }, + { + name: 'val', + internalType: 'int256[]', + type: 'int256[]', + indexed: false, + }, + ], + name: 'log_named_array', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'key', internalType: 'string', type: 'string', indexed: false }, + { + name: 'val', + internalType: 'address[]', + type: 'address[]', + indexed: false, + }, + ], + name: 'log_named_array', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'key', internalType: 'string', type: 'string', indexed: false }, + { name: 'val', internalType: 'bytes', type: 'bytes', indexed: false }, + ], + name: 'log_named_bytes', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'key', internalType: 'string', type: 'string', indexed: false }, + { name: 'val', internalType: 'bytes32', type: 'bytes32', indexed: false }, + ], + name: 'log_named_bytes32', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'key', internalType: 'string', type: 'string', indexed: false }, + { name: 'val', internalType: 'int256', type: 'int256', indexed: false }, + { + name: 'decimals', + internalType: 'uint256', + type: 'uint256', + indexed: false, + }, + ], + name: 'log_named_decimal_int', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'key', internalType: 'string', type: 'string', indexed: false }, + { name: 'val', internalType: 'uint256', type: 'uint256', indexed: false }, + { + name: 'decimals', + internalType: 'uint256', + type: 'uint256', + indexed: false, + }, + ], + name: 'log_named_decimal_uint', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'key', internalType: 'string', type: 'string', indexed: false }, + { name: 'val', internalType: 'int256', type: 'int256', indexed: false }, + ], + name: 'log_named_int', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'key', internalType: 'string', type: 'string', indexed: false }, + { name: 'val', internalType: 'string', type: 'string', indexed: false }, + ], + name: 'log_named_string', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'key', internalType: 'string', type: 'string', indexed: false }, + { name: 'val', internalType: 'uint256', type: 'uint256', indexed: false }, + ], + name: 'log_named_uint', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: '', internalType: 'string', type: 'string', indexed: false }, + ], + name: 'log_string', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: '', internalType: 'uint256', type: 'uint256', indexed: false }, + ], + name: 'log_uint', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: '', internalType: 'bytes', type: 'bytes', indexed: false }, + ], + name: 'logs', + }, +] as const + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// TestPlus +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const testPlusAbi = [ + { + type: 'function', + inputs: [], + name: 'test__codesize', + outputs: [], + stateMutability: 'view', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'name', internalType: 'string', type: 'string', indexed: false }, + { + name: 'value', + internalType: 'address', + type: 'address', + indexed: false, + }, + ], + name: 'LogAddress', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'value', + internalType: 'address', + type: 'address', + indexed: false, + }, + ], + name: 'LogAddress', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'name', internalType: 'string', type: 'string', indexed: false }, + { + name: 'value', + internalType: 'address[]', + type: 'address[]', + indexed: false, + }, + ], + name: 'LogAddressArray', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'value', + internalType: 'address[]', + type: 'address[]', + indexed: false, + }, + ], + name: 'LogAddressArray', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'name', internalType: 'string', type: 'string', indexed: false }, + { name: 'value', internalType: 'bool', type: 'bool', indexed: false }, + ], + name: 'LogBool', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'value', internalType: 'bool', type: 'bool', indexed: false }, + ], + name: 'LogBool', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'name', internalType: 'string', type: 'string', indexed: false }, + { name: 'value', internalType: 'bool[]', type: 'bool[]', indexed: false }, + ], + name: 'LogBoolArray', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'value', internalType: 'bool[]', type: 'bool[]', indexed: false }, + ], + name: 'LogBoolArray', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'name', internalType: 'string', type: 'string', indexed: false }, + { name: 'value', internalType: 'bytes', type: 'bytes', indexed: false }, + ], + name: 'LogBytes', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'value', internalType: 'bytes', type: 'bytes', indexed: false }, + ], + name: 'LogBytes', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'name', internalType: 'string', type: 'string', indexed: false }, + { + name: 'value', + internalType: 'bytes32', + type: 'bytes32', + indexed: false, + }, + ], + name: 'LogBytes32', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'value', + internalType: 'bytes32', + type: 'bytes32', + indexed: false, + }, + ], + name: 'LogBytes32', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'name', internalType: 'string', type: 'string', indexed: false }, + { + name: 'value', + internalType: 'bytes32[]', + type: 'bytes32[]', + indexed: false, + }, + ], + name: 'LogBytes32Array', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'value', + internalType: 'bytes32[]', + type: 'bytes32[]', + indexed: false, + }, + ], + name: 'LogBytes32Array', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'name', internalType: 'string', type: 'string', indexed: false }, + { + name: 'value', + internalType: 'bytes[]', + type: 'bytes[]', + indexed: false, + }, + ], + name: 'LogBytesArray', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'value', + internalType: 'bytes[]', + type: 'bytes[]', + indexed: false, + }, + ], + name: 'LogBytesArray', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'name', internalType: 'string', type: 'string', indexed: false }, + { name: 'value', internalType: 'int256', type: 'int256', indexed: false }, + ], + name: 'LogInt', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'value', internalType: 'int256', type: 'int256', indexed: false }, + ], + name: 'LogInt', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'name', internalType: 'string', type: 'string', indexed: false }, + { + name: 'value', + internalType: 'int256[]', + type: 'int256[]', + indexed: false, + }, + ], + name: 'LogIntArray', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'value', + internalType: 'int256[]', + type: 'int256[]', + indexed: false, + }, + ], + name: 'LogIntArray', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'name', internalType: 'string', type: 'string', indexed: false }, + { name: 'value', internalType: 'string', type: 'string', indexed: false }, + ], + name: 'LogString', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'value', internalType: 'string', type: 'string', indexed: false }, + ], + name: 'LogString', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'name', internalType: 'string', type: 'string', indexed: false }, + { + name: 'value', + internalType: 'string[]', + type: 'string[]', + indexed: false, + }, + ], + name: 'LogStringArray', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'value', + internalType: 'string[]', + type: 'string[]', + indexed: false, + }, + ], + name: 'LogStringArray', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'name', internalType: 'string', type: 'string', indexed: false }, + { + name: 'value', + internalType: 'uint256', + type: 'uint256', + indexed: false, + }, + ], + name: 'LogUint', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'value', + internalType: 'uint256', + type: 'uint256', + indexed: false, + }, + ], + name: 'LogUint', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'name', internalType: 'string', type: 'string', indexed: false }, + { + name: 'value', + internalType: 'uint256[]', + type: 'uint256[]', + indexed: false, + }, + ], + name: 'LogUintArray', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'value', + internalType: 'uint256[]', + type: 'uint256[]', + indexed: false, + }, + ], + name: 'LogUintArray', + }, ] as const ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/core/internal/key.test.ts b/src/core/internal/key.test.ts new file mode 100644 index 00000000..cd4800f4 --- /dev/null +++ b/src/core/internal/key.test.ts @@ -0,0 +1,555 @@ +import { + Bytes, + Hex, + PublicKey, + Secp256k1, + WebAuthnP256, + WebCryptoP256, +} from 'ox' +import { verifyHash } from 'viem/actions' +import { afterAll, beforeAll, describe, expect, test, vi } from 'vitest' + +import { getAccount } from '../../../test/src/account.js' +import { client, delegation } from '../../../test/src/porto.js' +import * as Call from './call.js' +import * as Delegation from './delegation.js' +import * as Key from './key.js' + +describe('createP256', () => { + test('default', () => { + const key = Key.createP256({ + role: 'admin', + }) + + const { publicKey, ...rest } = key + + expect(publicKey).toBeDefined() + expect(rest).toMatchInlineSnapshot(` + { + "callScopes": undefined, + "canSign": true, + "expiry": 0, + "privateKey": [Function], + "role": "admin", + "type": "p256", + } + `) + }) + + test('behavior: authorize + sign', async () => { + const { account } = await getAccount(client) + + const key = Key.createP256({ + role: 'admin', + }) + + await Delegation.execute(client, { + account, + calls: [ + Call.authorize({ + key, + }), + ], + delegation, + }) + + const payload = Hex.random(32) + const signature = await Key.sign(key, { + payload, + }) + + expect( + await verifyHash(client, { + address: account.address, + hash: payload, + signature, + }), + ).toBe(true) + }) +}) + +describe('createSecp256k1', () => { + test('default', () => { + const key = Key.createSecp256k1({ + role: 'admin', + }) + + const { publicKey, ...rest } = key + + expect(publicKey).toBeDefined() + expect(rest).toMatchInlineSnapshot(` + { + "callScopes": undefined, + "canSign": true, + "expiry": 0, + "privateKey": [Function], + "role": "admin", + "type": "secp256k1", + } + `) + }) + + test('behavior: authorize + sign', async () => { + const { account } = await getAccount(client) + + const key = Key.createSecp256k1({ + role: 'admin', + }) + + await Delegation.execute(client, { + account, + calls: [ + Call.authorize({ + key, + }), + ], + delegation, + }) + + const payload = Hex.random(32) + const signature = await Key.sign(key, { + payload, + }) + + expect( + await verifyHash(client, { + address: account.address, + hash: payload, + signature, + }), + ).toBe(true) + }) +}) + +describe('createWebAuthnP256', () => { + beforeAll(() => { + vi.stubGlobal('window', { + location: { + hostname: 'https://example.com', + }, + document: { + title: 'My Website', + }, + }) + }) + + afterAll(() => { + vi.restoreAllMocks() + }) + + test('default', async () => { + const key = await Key.createWebAuthnP256({ + createFn() { + return Promise.resolve({ + id: 'm1-bMPuAqpWhCxHZQZTT6e-lSPntQbh3opIoGe7g4Qs', + response: { + getPublicKey() { + return [ + 48, 89, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, 134, + 72, 206, 61, 3, 1, 7, 3, 66, 0, 4, 171, 137, 20, 0, 20, 15, 196, + 248, 233, 65, 206, 15, 249, 14, 65, 157, 233, 71, 10, 202, 202, + 97, 59, 189, 113, 122, 71, 117, 67, 80, 49, 167, 216, 132, 49, + 142, 145, 159, 211, 179, 229, 166, 49, 216, 102, 216, 163, 128, + 180, 64, 99, 231, 15, 12, 56, 30, 225, 110, 6, 82, 247, 249, + 117, 84, + ] + }, + }, + } as any) + }, + label: 'test', + role: 'admin', + userId: Bytes.from('0x0000000000000000000000000000000000000000'), + }) + + const { publicKey, ...rest } = key + + expect(publicKey).toBeDefined() + expect(rest).toMatchInlineSnapshot(` + { + "callScopes": undefined, + "canSign": true, + "credential": { + "id": "m1-bMPuAqpWhCxHZQZTT6e-lSPntQbh3opIoGe7g4Qs", + "publicKey": { + "prefix": 4, + "x": 77587693192652859874025541476425832478302972220661277688017673393936226333095n, + "y": 97933141135755737384413290261786792525004108403409931527059712582886746584404n, + }, + }, + "expiry": 0, + "role": "admin", + "rpId": undefined, + "type": "webauthn-p256", + } + `) + }) +}) + +describe('createWebCryptoP256', () => { + test('default', async () => { + const key = await Key.createWebCryptoP256({ + role: 'admin', + }) + + const { publicKey, ...rest } = key + + expect(publicKey).toBeDefined() + expect(rest).toMatchInlineSnapshot(` + { + "callScopes": undefined, + "canSign": true, + "expiry": 0, + "privateKey": CryptoKey {}, + "role": "admin", + "type": "p256", + } + `) + }) + + test('behavior: authorize + sign', async () => { + const { account } = await getAccount(client) + + const key = await Key.createWebCryptoP256({ + role: 'admin', + }) + + await Delegation.execute(client, { + account, + calls: [ + Call.authorize({ + key, + }), + ], + delegation, + }) + + const payload = Hex.random(32) + const signature = await Key.sign(key, { + payload, + }) + + expect( + await verifyHash(client, { + address: account.address, + hash: payload, + signature, + }), + ).toBe(true) + }) +}) + +describe('deserialize', () => { + test('default', () => { + const key = Key.fromP256({ + privateKey: + '0x59ff6b8de3b3b39e94b6f9fc0590cf4e3eaa9b6736e6a49c9a6b324c4f58cb9f', + role: 'admin', + }) + const serialized = Key.serialize(key) + const deserialized = Key.deserialize(serialized) + + expect(deserialized).toMatchInlineSnapshot(` + { + "canSign": false, + "expiry": 0, + "publicKey": "0xec0effa5f2f378cbf7fd2fa7ca1e8dc51cf777c129fa1c00a0e9a9205f2e511ff3f20b34a4e0b50587d055c0e0fad33d32cf1147d3bb2538fbab0d15d8e65008", + "role": "admin", + "type": "p256", + } + `) + }) +}) + +describe('from', () => { + test('default', () => { + const publicKey = PublicKey.toHex( + Secp256k1.getPublicKey({ + privateKey: + '0x72685afe259e683fa3b7819c4745383ba36366c7571fd17456fd4cd9777aedcb', + }), + { + includePrefix: false, + }, + ) + + const key = Key.from({ + expiry: 69420, + publicKey, + role: 'admin', + canSign: true, + privateKey() { + return '0x' + }, + type: 'p256', + }) + + expect(key).toMatchInlineSnapshot(` + { + "canSign": true, + "expiry": 69420, + "privateKey": [Function], + "publicKey": "0x144f4bf8bda60e5bf0e9f11a509e55a14987a6c5a63aed81bcb6939f9f5abc7c3598cce19015350ce8d30f11e57cbdd55ccfbc5f30d9ccf59ffd080967229fe9", + "role": "admin", + "type": "p256", + } + `) + }) + + test('serialized', () => { + const publicKey = PublicKey.toHex( + Secp256k1.getPublicKey({ + privateKey: Secp256k1.randomPrivateKey(), + }), + { + includePrefix: false, + }, + ) + + const key = Key.from({ + expiry: 69420, + publicKey, + role: 'admin', + canSign: true, + privateKey() { + return '0x' + }, + type: 'p256', + }) + const serialized = Key.serialize(key) + + expect(Key.from(serialized)).toEqual({ + expiry: 69420, + publicKey, + canSign: false, + role: 'admin', + type: 'p256', + }) + }) +}) + +describe('fromP256', () => { + test('default', () => { + const key = Key.fromP256({ + privateKey: + '0x59ff6b8de3b3b39e94b6f9fc0590cf4e3eaa9b6736e6a49c9a6b324c4f58cb9f', + role: 'admin', + }) + + expect(key).toMatchInlineSnapshot(` + { + "callScopes": undefined, + "canSign": true, + "expiry": 0, + "privateKey": [Function], + "publicKey": "0xec0effa5f2f378cbf7fd2fa7ca1e8dc51cf777c129fa1c00a0e9a9205f2e511ff3f20b34a4e0b50587d055c0e0fad33d32cf1147d3bb2538fbab0d15d8e65008", + "role": "admin", + "type": "p256", + } + `) + }) + + test('args: expiry', () => { + const key = Key.fromP256({ + expiry: 69420, + privateKey: + '0x59ff6b8de3b3b39e94b6f9fc0590cf4e3eaa9b6736e6a49c9a6b324c4f58cb9f', + role: 'admin', + }) + + expect(key).toMatchInlineSnapshot(` + { + "callScopes": undefined, + "canSign": true, + "expiry": 69420, + "privateKey": [Function], + "publicKey": "0xec0effa5f2f378cbf7fd2fa7ca1e8dc51cf777c129fa1c00a0e9a9205f2e511ff3f20b34a4e0b50587d055c0e0fad33d32cf1147d3bb2538fbab0d15d8e65008", + "role": "admin", + "type": "p256", + } + `) + }) +}) + +describe('fromSecp256k1', () => { + test('args: privateKey', () => { + const key = Key.fromSecp256k1({ + privateKey: + '0x59ff6b8de3b3b39e94b6f9fc0590cf4e3eaa9b6736e6a49c9a6b324c4f58cb9f', + role: 'admin', + }) + + expect(key).toMatchInlineSnapshot(` + { + "callScopes": undefined, + "canSign": true, + "expiry": 0, + "privateKey": [Function], + "publicKey": "0x000000000000000000000000673ee8aabd3a62434cb9e3d7c6f9492e286bcb08", + "role": "admin", + "type": "secp256k1", + } + `) + }) + + test('args: address', () => { + const key = Key.fromSecp256k1({ + address: '0x0000000000000000000000000000000000000000', + role: 'admin', + }) + + expect(key).toMatchInlineSnapshot(` + { + "callScopes": undefined, + "canSign": false, + "expiry": 0, + "privateKey": undefined, + "publicKey": "0x0000000000000000000000000000000000000000000000000000000000000000", + "role": "admin", + "type": "secp256k1", + } + `) + }) + + test('args: publicKey', () => { + const key = Key.fromSecp256k1({ + publicKey: PublicKey.fromHex( + '0x626c7f1042b6d3971be0e4c054165e36a6d6a5ace6af1773654d3360fccf0b25b0c998938d9b73e749023eb1c77f5930b5a87660deec42261a9a22fac9a56536', + ), + role: 'admin', + }) + + expect(key).toMatchInlineSnapshot(` + { + "callScopes": undefined, + "canSign": false, + "expiry": 0, + "privateKey": undefined, + "publicKey": "0x000000000000000000000000673ee8aabd3a62434cb9e3d7c6f9492e286bcb08", + "role": "admin", + "type": "secp256k1", + } + `) + }) +}) + +describe('fromWebAuthnP256', () => { + beforeAll(() => { + vi.stubGlobal('window', { + location: { + hostname: 'https://example.com', + }, + document: { + title: 'My Website', + }, + }) + }) + + afterAll(() => { + vi.restoreAllMocks() + }) + + test('default', async () => { + const credential = await WebAuthnP256.createCredential({ + createFn() { + return Promise.resolve({ + id: 'm1-bMPuAqpWhCxHZQZTT6e-lSPntQbh3opIoGe7g4Qs', + response: { + getPublicKey() { + return [ + 48, 89, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, 134, + 72, 206, 61, 3, 1, 7, 3, 66, 0, 4, 171, 137, 20, 0, 20, 15, 196, + 248, 233, 65, 206, 15, 249, 14, 65, 157, 233, 71, 10, 202, 202, + 97, 59, 189, 113, 122, 71, 117, 67, 80, 49, 167, 216, 132, 49, + 142, 145, 159, 211, 179, 229, 166, 49, 216, 102, 216, 163, 128, + 180, 64, 99, 231, 15, 12, 56, 30, 225, 110, 6, 82, 247, 249, + 117, 84, + ] + }, + }, + } as any) + }, + name: 'test', + }) + + const key = Key.fromWebAuthnP256({ + credential, + role: 'admin', + }) + + expect(key).toMatchInlineSnapshot(` + { + "callScopes": undefined, + "canSign": true, + "credential": { + "id": "m1-bMPuAqpWhCxHZQZTT6e-lSPntQbh3opIoGe7g4Qs", + "publicKey": { + "prefix": 4, + "x": 77587693192652859874025541476425832478302972220661277688017673393936226333095n, + "y": 97933141135755737384413290261786792525004108403409931527059712582886746584404n, + }, + "raw": { + "id": "m1-bMPuAqpWhCxHZQZTT6e-lSPntQbh3opIoGe7g4Qs", + "response": { + "getPublicKey": [Function], + }, + }, + }, + "expiry": 0, + "publicKey": "0xab891400140fc4f8e941ce0ff90e419de9470acaca613bbd717a4775435031a7d884318e919fd3b3e5a631d866d8a380b44063e70f0c381ee16e0652f7f97554", + "role": "admin", + "rpId": undefined, + "type": "webauthn-p256", + } + `) + }) +}) + +describe('fromWebCryptoP256', () => { + test('default', async () => { + const keyPair = await WebCryptoP256.createKeyPair() + + const key = Key.fromWebCryptoP256({ + keyPair: { + privateKey: keyPair.privateKey, + publicKey: { + prefix: 4, + x: 29425393363637877844360099756708459701670665037779565927194637716883031208592n, + y: 4454192741171077737571435183656715320148197913661532282490480175757904146724n, + }, + }, + role: 'admin', + }) + + expect(key).toMatchInlineSnapshot(` + { + "callScopes": undefined, + "canSign": true, + "expiry": 0, + "privateKey": CryptoKey {}, + "publicKey": "0x410e2eb4820de45c0dd6730c300c3c66b8bc5885c963067fe0ff29c5e480329009d8fbd71e76257a2d5577e2211a62114eca15c9218d488209fa789a45497124", + "role": "admin", + "type": "p256", + } + `) + }) +}) + +describe('serialize', () => { + test('default', () => { + const key = Key.fromP256({ + privateKey: + '0x59ff6b8de3b3b39e94b6f9fc0590cf4e3eaa9b6736e6a49c9a6b324c4f58cb9f', + role: 'admin', + }) + + expect(Key.serialize(key)).toMatchInlineSnapshot(` + { + "expiry": 0, + "isSuperAdmin": true, + "keyType": 0, + "publicKey": "0xec0effa5f2f378cbf7fd2fa7ca1e8dc51cf777c129fa1c00a0e9a9205f2e511ff3f20b34a4e0b50587d055c0e0fad33d32cf1147d3bb2538fbab0d15d8e65008", + } + `) + }) +}) diff --git a/src/core/internal/key.ts b/src/core/internal/key.ts new file mode 100644 index 00000000..7d07218c --- /dev/null +++ b/src/core/internal/key.ts @@ -0,0 +1,769 @@ +import * as AbiParameters from 'ox/AbiParameters' +import * as Address from 'ox/Address' +import * as Bytes from 'ox/Bytes' +import * as Hash from 'ox/Hash' +import * as Hex from 'ox/Hex' +import * as Json from 'ox/Json' +import * as P256 from 'ox/P256' +import * as PublicKey from 'ox/PublicKey' +import * as Secp256k1 from 'ox/Secp256k1' +import * as Signature from 'ox/Signature' +import * as WebAuthnP256 from 'ox/WebAuthnP256' +import * as WebCryptoP256 from 'ox/WebCryptoP256' +import type { OneOf, Undefined } from './types.js' + +type PrivateKeyFn = () => Hex.Hex + +/** Key on a delegated account. */ +export type BaseKey = { + callScopes?: CallScopes | undefined + expiry: number + publicKey: Hex.Hex + role: 'admin' | 'session' + type: type +} & OneOf< + | ({ + canSign: true + } & properties) + | ({ + canSign: false + } & Undefined) +> + +export type CallScope = OneOf< + | { + signature: string + to: Address.Address + } + | { + signature: string + } + | { + to: Address.Address + } +> +export type CallScopes = readonly [CallScope, ...CallScope[]] + +export type Key = OneOf + +export type P256Key = BaseKey<'p256', { privateKey: PrivateKeyFn }> +export type Secp256k1Key = BaseKey<'secp256k1', { privateKey: PrivateKeyFn }> +export type WebCryptoKey = BaseKey< + 'p256', + { + privateKey: CryptoKey + } +> +export type WebAuthnKey = BaseKey< + 'webauthn-p256', + { + credential: Pick + rpId: string | undefined + } +> + +/** Serialized (contract-compatible) format of a key. */ +export type Serialized = { + expiry: number + isSuperAdmin: boolean + keyType: number + publicKey: Hex.Hex +} + +/** Key type to serialized key type mapping. */ +export const toSerializedKeyType = { + p256: 0, + 'webauthn-p256': 1, + secp256k1: 2, +} as const + +/** Serialized key type to key type mapping. */ +export const fromSerializedKeyType = { + 0: 'p256', + 1: 'webauthn-p256', + 2: 'secp256k1', +} as const + +/** + * Creates a random P256 key. + * + * @example + * ```ts + * import * as Key from './key.js' + * + * // Admin Key + * const key = Key.createP256({ + * role: 'admin', + * }) + * + * // Session Key + * const key = Key.createP256({ + * expiry: 1714857600, + * role: 'session', + * }) + * ``` + * + * @param parameters - Key parameters. + * @returns P256 key. + */ +export function createP256( + parameters: createP256.Parameters, +) { + const privateKey = P256.randomPrivateKey() + return fromP256({ + ...parameters, + privateKey, + }) +} + +export declare namespace createP256 { + type Parameters = { + /** Call scopes. */ + callScopes?: CallScopes | undefined + /** Expiry. */ + expiry?: fromP256.Parameters['expiry'] + /** Role. */ + role: fromP256.Parameters['role'] + } +} + +/** + * Creates a random Secp256k1 key. + * + * @example + * ```ts + * import * as Key from './key.js' + * + * // Admin Key + * const key = Key.createSecp256k1({ + * role: 'admin', + * }) + * + * // Session Key + * const key = Key.createSecp256k1({ + * expiry: 1714857600, + * role: 'session', + * }) + * ``` + * + * @param parameters - Key parameters. + * @returns Secp256k1 key. + */ +export function createSecp256k1( + parameters: createSecp256k1.Parameters, +) { + const privateKey = Secp256k1.randomPrivateKey() + return fromSecp256k1({ + ...parameters, + privateKey, + }) +} + +export declare namespace createSecp256k1 { + type Parameters = { + /** Call scopes. */ + callScopes?: CallScopes | undefined + /** Expiry. */ + expiry?: fromSecp256k1.Parameters['expiry'] + /** Role. */ + role: fromSecp256k1.Parameters['role'] + } +} + +/** + * Creates a WebAuthnP256 key. + * + * @example + * ```ts + * import { Bytes } from 'ox' + * import * as Key from './key.js' + * + * // Admin Key + * const key = Key.createWebAuthnP256({ + * label: 'My Key', + * role: 'admin', + * userId: Bytes.from('0x0000000000000000000000000000000000000000'), + * }) + * + * // Session Key + * const key = Key.createWebAuthnP256({ + * expiry: 1714857600, + * label: 'My Key', + * role: 'session', + * userId: Bytes.from('0x0000000000000000000000000000000000000000'), + * }) + * ``` + * + * @param parameters - Key parameters. + * @returns WebAuthnP256 key. + */ +export async function createWebAuthnP256( + parameters: createWebAuthnP256.Parameters, +) { + const { createFn, label, rpId, userId } = parameters + + const credential = await WebAuthnP256.createCredential({ + authenticatorSelection: { + requireResidentKey: false, + residentKey: 'preferred', + userVerification: 'required', + }, + createFn, + rp: rpId + ? { + id: rpId, + name: rpId, + } + : undefined, + user: { + displayName: label, + name: label, + id: userId, + }, + }) + + return fromWebAuthnP256({ + ...parameters, + credential: { + id: credential.id, + publicKey: credential.publicKey, + }, + }) +} + +export declare namespace createWebAuthnP256 { + type Parameters = { + /** Call scopes. */ + callScopes?: CallScopes | undefined + /** + * Credential creation function. Useful for environments that do not support + * the WebAuthn API natively (i.e. React Native or testing environments). + * + * @default window.navigator.credentials.create + */ + createFn?: WebAuthnP256.createCredential.Options['createFn'] | undefined + /** Expiry. */ + expiry?: fromWebAuthnP256.Parameters['expiry'] + /** Label. */ + label: string + /** Role. */ + role: fromWebAuthnP256.Parameters['role'] + /** Relying Party ID. */ + rpId?: string | undefined + /** User ID. */ + userId: Bytes.Bytes + } +} + +/** + * Creates a random WebCryptoP256 key. + * + * @example + * ```ts + * import * as Key from './key.js' + * + * // Admin Key + * const key = Key.createWebCryptoP256({ + * role: 'admin', + * }) + * + * // Session Key + * const key = Key.createWebCryptoP256({ + * expiry: 1714857600, + * role: 'session', + * }) + * ``` + * + * @param parameters - Key parameters. + * @returns WebCryptoP256 key. + */ +export async function createWebCryptoP256( + parameters: createWebCryptoP256.Parameters, +) { + const keyPair = await WebCryptoP256.createKeyPair() + return fromWebCryptoP256({ + ...parameters, + keyPair, + }) +} + +export declare namespace createWebCryptoP256 { + type Parameters = { + /** Call scopes. */ + callScopes?: CallScopes | undefined + /** Expiry. */ + expiry?: fromP256.Parameters['expiry'] + /** Role. */ + role: fromP256.Parameters['role'] + } +} + +/** + * Deserializes a key from its serialized format. + * + * @example + * ```ts + * import * as Key from './key.js' + * + * const key = Key.deserialize({ + * expiry: 0, + * isSuperAdmin: false, + * keyType: 0, + * publicKey: '0x04ec0effa5f2f378cbf7fd2fa7ca1e8dc51cf777c129fa1c00a0e9a9205f2e511ff3f20b34a4e0b50587d055c0e0fad33d32cf1147d3bb2538fbab0d15d8e65008', + * }) + * ``` + * + * @param serialized - Serialized key. + * @returns Key. + */ +export function deserialize(serialized: Serialized): Key { + return { + expiry: serialized.expiry, + publicKey: serialized.publicKey, + role: serialized.isSuperAdmin ? 'admin' : 'session', + canSign: false, + type: (fromSerializedKeyType as any)[serialized.keyType], + } +} + +/** + * Instantiates a key from its parameters. + * + * @example + * ```ts + * import { P256 } from 'ox' + * import * as Key from './key.js' + * + * const privateKey = P256.randomPrivateKey() + * const publicKey = P256.getPublicKey({ privateKey }) + * + * const key = Key.from({ + * expiry: 0, + * publicKey, + * role: 'admin', + * async sign({ payload }) { + * return P256.sign({ payload, privateKey }) + * }, + * type: 'p256', + * }) + * ``` + * + * @param key - Key. + * @returns Key. + */ +export function from( + key: key | Key | Serialized, +): key extends Key ? key : Key { + if ('isSuperAdmin' in key) return deserialize(key) as never + return { ...key, expiry: key.expiry ?? 0 } as never +} + +/** + * Instantiates a P256 key from its parameters. + * + * @example + * ```ts + * import { P256 } from 'ox' + * import * as Key from './key.js' + * + * // Admin Key + * const key = Key.fromP256({ + * privateKey: P256.randomPrivateKey(), + * role: 'admin', + * }) + * + * // Session Key + * const key = Key.fromP256({ + * expiry: 1714857600, + * privateKey: P256.randomPrivateKey(), + * role: 'session', + * }) + * ``` + * + * @param parameters - Key parameters. + * @returns P256 key. + */ +export function fromP256( + parameters: fromP256.Parameters, +) { + const { privateKey } = parameters + const publicKey = PublicKey.toHex(P256.getPublicKey({ privateKey }), { + includePrefix: false, + }) + return from({ + callScopes: parameters.callScopes, + expiry: parameters.expiry ?? 0, + publicKey, + role: parameters.role as Key['role'], + canSign: true, + privateKey() { + return privateKey + }, + type: 'p256', + }) +} + +export declare namespace fromP256 { + type Parameters = { + /** Call scopes. */ + callScopes?: CallScopes | undefined + /** Expiry. */ + expiry?: Key['expiry'] | undefined + /** P256 private key. */ + privateKey: Hex.Hex + /** Role. */ + role: role | Key['role'] + } +} + +/** + * Instantiates a Secp256k1 key from its parameters. + * + * @example + * ```ts + * import { Secp256k1 } from 'ox' + * import * as Key from './key.js' + * + * // Admin Key + * const key = Key.fromSecp256k1({ + * privateKey: Secp256k1.randomPrivateKey(), + * role: 'admin', + * }) + * + * // Session Key + * const key = Key.fromSecp256k1({ + * expiry: 1714857600, + * privateKey: Secp256k1.randomPrivateKey(), + * role: 'session', + * }) + * ``` + * + * @param parameters - Key parameters. + * @returns Secp256k1 key. + */ +export function fromSecp256k1( + parameters: fromSecp256k1.Parameters, +) { + const { privateKey, role } = parameters + const address = (() => { + if (parameters.address) return parameters.address.toLowerCase() as Hex.Hex + const publicKey = + parameters.publicKey ?? + Secp256k1.getPublicKey({ privateKey: privateKey! }) + return Address.fromPublicKey(publicKey) + })() + const publicKey = AbiParameters.encode([{ type: 'address' }], [address]) + return from({ + callScopes: parameters.callScopes, + expiry: parameters.expiry ?? 0, + publicKey, + role, + canSign: Boolean(privateKey), + privateKey: privateKey ? () => privateKey : undefined, + type: 'secp256k1', + } as Secp256k1Key) +} + +export declare namespace fromSecp256k1 { + type Parameters = { + /** Call scopes. */ + callScopes?: CallScopes | undefined + /** Expiry. */ + expiry?: Key['expiry'] | undefined + /** Role. */ + role: role | Key['role'] + } & OneOf< + | { + /** Ethereum address. */ + address: Address.Address + } + | { + /** Secp256k1 public key. */ + publicKey: PublicKey.PublicKey + } + | { + /** Secp256k1 private key. */ + privateKey: Hex.Hex + } + > +} + +/** + * Instantiates a WebAuthnP256 key from its parameters. + * + * @example + * ```ts + * import { WebAuthnP256 } from 'ox' + * import * as Key from './key.js' + * + * const credential = await WebAuthnP256.createCredential({ name: 'My Key' }) + * + * // Admin Key + * const key = Key.fromWebAuthnP256({ + * credential, + * role: 'admin', + * }) + * + * // Session Key + * const key = Key.fromWebAuthnP256({ + * expiry: 1714857600, + * credential, + * role: 'session', + * }) + * ``` + * + * @param parameters - Key parameters. + * @returns WebAuthnP256 key. + */ +export function fromWebAuthnP256( + parameters: fromWebAuthnP256.Parameters, +) { + const { credential, rpId } = parameters + const publicKey = PublicKey.toHex(credential.publicKey, { + includePrefix: false, + }) + return from({ + callScopes: parameters.callScopes, + credential, + expiry: parameters.expiry ?? 0, + publicKey, + role: parameters.role as Key['role'], + canSign: true, + rpId, + type: 'webauthn-p256', + }) +} + +export declare namespace fromWebAuthnP256 { + type Parameters = { + /** Call scopes. */ + callScopes?: CallScopes | undefined + /** Expiry. */ + expiry?: Key['expiry'] | undefined + /** WebAuthnP256 Credential. */ + credential: Pick + /** Role. */ + role: role | Key['role'] + /** Relying Party ID. */ + rpId?: string | undefined + } +} + +/** + * Instantiates a WebCryptoP256 key from its parameters. + * + * @example + * ```ts + * import { WebCryptoP256 } from 'ox' + * import * as Key from './key.js' + * + * const keyPair = await WebCryptoP256.createKeyPair() + * + * // Admin Key + * const key = Key.fromWebCryptoP256({ + * keyPair, + * role: 'admin', + * }) + * + * // Session Key + * const key = Key.fromWebCryptoP256({ + * expiry: 1714857600, + * keyPair, + * role: 'session', + * }) + * ``` + * + * @param parameters - Key parameters. + * @returns WebCryptoP256 key. + */ +export function fromWebCryptoP256( + parameters: fromWebCryptoP256.Parameters, +) { + const { keyPair } = parameters + const { privateKey } = keyPair + const publicKey = PublicKey.toHex(keyPair.publicKey, { + includePrefix: false, + }) + return from({ + callScopes: parameters.callScopes, + expiry: parameters.expiry ?? 0, + publicKey, + role: parameters.role as Key['role'], + canSign: true, + privateKey, + type: 'p256', + }) +} + +export declare namespace fromWebCryptoP256 { + type Parameters = { + /** Call scopes. */ + callScopes?: CallScopes | undefined + /** Expiry. */ + expiry?: Key['expiry'] | undefined + /** P256 private key. */ + keyPair: Awaited> + /** Role. */ + role: role | Key['role'] + } +} + +/** + * Hashes a key. + * + * @example + * ```ts + * import * as Key from './key.js' + * + * const key = Key.createP256({ + * role: 'admin', + * }) + * + * const hash = Key.hash(key) + * ``` + * + * @param key - Key. + * @returns Hashed key. + */ +export function hash(key: Pick): Hex.Hex { + const { publicKey, type } = key + return Hash.keccak256( + AbiParameters.encode( + [{ type: 'uint8' }, { type: 'bytes32' }], + [toSerializedKeyType[type], Hash.keccak256(publicKey)], + ), + ) +} + +/** + * Serializes a key to a contract-compatible format. + * + * @example + * ```ts + * import * as Key from './key.js' + * + * const key = Key.createP256({ + * role: 'admin', + * }) + * + * const serialized = Key.serialize(key) + * ``` + * + * @param key - Key. + * @returns Serialized key. + */ +export function serialize(key: Key): Serialized { + const { expiry = 0, publicKey, role, type } = key + return { + expiry, + isSuperAdmin: role === 'admin', + keyType: toSerializedKeyType[type], + publicKey, + } +} + +export async function sign( + key: Key, + parameters: { address?: Hex.Hex | undefined; payload: Hex.Hex }, +) { + const { address, payload } = parameters + const { canSign, publicKey, type: keyType } = key + + if (!canSign) + throw new Error( + 'Key is not canSign.\n\nKey:\n' + Json.stringify(key, null, 2), + ) + + const [signature, prehash] = await (async () => { + if (keyType === 'p256') { + const { privateKey } = key + if (typeof privateKey === 'function') + return [ + Signature.toHex(P256.sign({ payload, privateKey: privateKey() })), + false, + ] + if (privateKey instanceof CryptoKey) { + const signature = Signature.toHex( + await WebCryptoP256.sign({ payload, privateKey }), + ) + return [signature, true] + } + } + if (keyType === 'secp256k1') { + const { privateKey } = key + return [ + Signature.toHex(Secp256k1.sign({ payload, privateKey: privateKey() })), + false, + ] + } + if (keyType === 'webauthn-p256') { + const { credential, rpId } = key + const { + signature: { r, s }, + raw, + metadata, + } = await WebAuthnP256.sign({ + challenge: payload, + credentialId: credential.id, + rpId, + }) + + const response = raw.response as AuthenticatorAssertionResponse + const userHandle = Bytes.toHex(new Uint8Array(response.userHandle!)) + if (address !== userHandle) + throw new Error( + `supplied address "${address}" does not match signature address "${userHandle}"`, + ) + + const signature = AbiParameters.encode( + AbiParameters.from([ + 'struct WebAuthnAuth { bytes authenticatorData; string clientDataJSON; uint256 challengeIndex; uint256 typeIndex; bytes32 r; bytes32 s; }', + 'WebAuthnAuth auth', + ]), + [ + { + authenticatorData: metadata.authenticatorData, + challengeIndex: BigInt(metadata.challengeIndex), + clientDataJSON: metadata.clientDataJSON, + r: Hex.fromNumber(r, { size: 32 }), + s: Hex.fromNumber(s, { size: 32 }), + typeIndex: BigInt(metadata.typeIndex), + }, + ], + ) + return [signature, false] + } + throw new Error( + `Key type "${keyType}" is not supported.\n\nKey:\n` + + Json.stringify(key, null, 2), + ) + })() + + return wrapSignature(signature, { + keyType, + publicKey, + prehash, + }) +} + +/////////////////////////////////////////////////////////////////////////// +// Internal +/////////////////////////////////////////////////////////////////////////// + +function wrapSignature(signature: Hex.Hex, options: wrapSignature.Options) { + const { keyType: type, prehash = false, publicKey } = options + + const keyHash = hash({ publicKey, type }) + return AbiParameters.encodePacked( + ['bytes', 'bytes32', 'bool'], + [signature, keyHash, prehash], + ) +} + +declare namespace wrapSignature { + type Options = { + keyType: Key['type'] + prehash?: boolean | undefined + publicKey: Hex.Hex + } +} diff --git a/src/core/internal/provider.test.ts b/src/core/internal/provider.test.ts new file mode 100644 index 00000000..4209abf5 --- /dev/null +++ b/src/core/internal/provider.test.ts @@ -0,0 +1,1027 @@ +import { Hex, P256, PublicKey, TypedData, Value } from 'ox' +import { Porto } from 'porto' +import { + getBalance, + setBalance, + verifyMessage, + verifyTypedData, +} from 'viem/actions' +import { describe, expect, test } from 'vitest' + +import { createPorto, delegation } from '../../../test/src/porto.js' + +describe('eth_accounts', () => { + test('default', async () => { + const porto = createPorto() + await porto.provider.request({ + method: 'wallet_connect', + params: [ + { + capabilities: { + createAccount: true, + }, + }, + ], + }) + const accounts = await porto.provider.request({ + method: 'eth_accounts', + }) + expect(accounts.length).toBe(1) + }) + + test('behavior: disconnected', async () => { + const porto = createPorto() + await expect( + porto.provider.request({ + method: 'eth_accounts', + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + '[Provider.DisconnectedError: The provider is disconnected from all chains.]', + ) + }) +}) + +describe('eth_requestAccounts', () => { + test('default', async () => { + const porto = createPorto() + await porto.provider.request({ + method: 'experimental_createAccount', + }) + await porto.provider.request({ + method: 'wallet_disconnect', + }) + const accounts = await porto.provider.request({ + method: 'eth_requestAccounts', + }) + expect(accounts.length).toBeGreaterThan(0) + }) +}) + +describe('eth_sendTransaction', () => { + test('default', async () => { + const porto = createPorto() + const client = Porto.getClients(porto).default.extend(() => ({ + mode: 'anvil', + })) + + const { address } = await porto.provider.request({ + method: 'experimental_createAccount', + }) + await setBalance(client, { + address, + value: Value.fromEther('10000'), + }) + + const alice = '0x0000000000000000000000000000000000069420' + + const hash = await porto.provider.request({ + method: 'eth_sendTransaction', + params: [ + { + from: address, + to: alice, + value: Hex.fromNumber(69420), + }, + ], + }) + + expect(hash).toBeDefined() + expect(await getBalance(client, { address: alice })).toBe(69420n) + }) +}) + +describe('eth_signTypedData_v4', () => { + test('default', async () => { + const porto = createPorto() + const client = Porto.getClients(porto).default + const { address } = await porto.provider.request({ + method: 'experimental_createAccount', + }) + const signature = await porto.provider.request({ + method: 'eth_signTypedData_v4', + params: [address, TypedData.serialize(typedData)], + }) + expect(signature).toBeDefined() + + const valid = await verifyTypedData(client, { + ...typedData, + address, + signature, + }) + expect(valid).toBe(true) + }) +}) + +describe('experimental_authorizeKey', () => { + test('default', async () => { + const messages: any[] = [] + + const porto = createPorto() + porto.provider.on('message', (message) => messages.push(message)) + + await porto.provider.request({ + method: 'experimental_createAccount', + }) + await porto.provider.request({ + method: 'experimental_authorizeKey', + params: [ + { + key: { + callScopes: [{ signature: 'mint()' }], + }, + }, + ], + }) + const accounts = porto._internal.store.getState().accounts + expect(accounts.length).toBe(1) + expect(accounts![0]!.keys?.length).toBe(2) + expect( + accounts![0]!.keys?.map((x) => ({ ...x, expiry: null, publicKey: null })), + ).toMatchInlineSnapshot(` + [ + { + "callScopes": undefined, + "canSign": true, + "expiry": null, + "privateKey": [Function], + "publicKey": null, + "role": "admin", + "type": "p256", + }, + { + "callScopes": [ + { + "signature": "mint()", + }, + ], + "canSign": true, + "expiry": null, + "privateKey": CryptoKey {}, + "publicKey": null, + "role": "session", + "type": "p256", + }, + ] + `) + + expect(messages[0].type).toBe('keysChanged') + expect(messages[0].data.length).toBe(2) + }) + + test('behavior: provided key', async () => { + const messages: any[] = [] + + const porto = createPorto() + porto.provider.on('message', (message) => messages.push(message)) + + await porto.provider.request({ + method: 'experimental_createAccount', + }) + await porto.provider.request({ + method: 'experimental_authorizeKey', + params: [ + { + key: { + callScopes: [{ signature: 'mint()' }], + publicKey: + '0x86a0d77beccf47a0a78cccfc19fdfe7317816740c9f9e6d7f696a02b0c66e0e21744d93c5699e9ce658a64ce60df2f32a17954cd577c713922bf62a1153cf68e', + role: 'session', + type: 'p256', + }, + }, + ], + }) + const accounts = porto._internal.store.getState().accounts + expect(accounts.length).toBe(1) + expect(accounts![0]!.keys?.length).toBe(2) + expect( + accounts![0]!.keys?.map((x) => ({ ...x, expiry: null, publicKey: null })), + ).toMatchInlineSnapshot(` + [ + { + "callScopes": undefined, + "canSign": true, + "expiry": null, + "privateKey": [Function], + "publicKey": null, + "role": "admin", + "type": "p256", + }, + { + "callScopes": [ + { + "signature": "mint()", + }, + ], + "canSign": false, + "expiry": null, + "publicKey": null, + "role": "session", + "type": "p256", + }, + ] + `) + + expect(messages[0].type).toBe('keysChanged') + expect(messages[0].data.length).toBe(2) + }) + + test('behavior: no call scopes', async () => { + const porto = createPorto() + await porto.provider.request({ + method: 'experimental_createAccount', + }) + await expect( + porto.provider.request({ + method: 'experimental_authorizeKey', + params: [ + { + // @ts-expect-error + key: { + publicKey: + '0x86a0d77beccf47a0a78cccfc19fdfe7317816740c9f9e6d7f696a02b0c66e0e21744d93c5699e9ce658a64ce60df2f32a17954cd577c713922bf62a1153cf68e', + role: 'session', + type: 'p256', + }, + }, + ], + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + '[Error: session key must have at least one call scope (`callScope`).]', + ) + }) +}) + +describe('experimental_createAccount', () => { + test('default', async () => { + const porto = createPorto() + const account = await porto.provider.request({ + method: 'experimental_createAccount', + }) + expect(account).toBeDefined() + }) +}) + +describe('experimental_keys', () => { + test('default', async () => { + const porto = createPorto() + await porto.provider.request({ + method: 'experimental_createAccount', + }) + await porto.provider.request({ + method: 'experimental_authorizeKey', + params: [ + { + key: { + callScopes: [{ signature: 'mint()' }], + }, + }, + ], + }) + const keys = await porto.provider.request({ + method: 'experimental_keys', + }) + expect(keys.length).toBe(2) + expect( + keys.map((x) => ({ ...x, expiry: null, publicKey: null })), + ).toMatchInlineSnapshot(` + [ + { + "callScopes": undefined, + "expiry": null, + "publicKey": null, + "role": "admin", + "type": "p256", + }, + { + "callScopes": [ + { + "signature": "mint()", + }, + ], + "expiry": null, + "publicKey": null, + "role": "session", + "type": "p256", + }, + ] + `) + }) +}) + +describe('experimental_revokeKey', () => { + test('default', async () => { + const porto = createPorto() + + const messages: any[] = [] + porto.provider.on('message', (message) => messages.push(message)) + + await porto.provider.request({ + method: 'experimental_createAccount', + }) + const { publicKey } = await porto.provider.request({ + method: 'experimental_authorizeKey', + params: [ + { + key: { + callScopes: [{ signature: 'mint()' }], + }, + }, + ], + }) + let accounts = porto._internal.store.getState().accounts + expect(accounts.length).toBe(1) + expect(accounts![0]!.keys?.length).toBe(2) + expect( + accounts![0]!.keys?.map((x) => ({ ...x, expiry: null, publicKey: null })), + ).toMatchInlineSnapshot(` + [ + { + "callScopes": undefined, + "canSign": true, + "expiry": null, + "privateKey": [Function], + "publicKey": null, + "role": "admin", + "type": "p256", + }, + { + "callScopes": [ + { + "signature": "mint()", + }, + ], + "canSign": true, + "expiry": null, + "privateKey": CryptoKey {}, + "publicKey": null, + "role": "session", + "type": "p256", + }, + ] + `) + + expect(messages[0].type).toBe('keysChanged') + expect(messages[0].data.length).toBe(2) + + await porto.provider.request({ + method: 'experimental_revokeKey', + params: [{ publicKey }], + }) + + accounts = porto._internal.store.getState().accounts + expect(accounts![0]!.keys?.length).toBe(1) + expect( + accounts![0]!.keys?.map((x) => ({ ...x, expiry: null, publicKey: null })), + ).toMatchInlineSnapshot(` + [ + { + "callScopes": undefined, + "canSign": true, + "expiry": null, + "privateKey": [Function], + "publicKey": null, + "role": "admin", + "type": "p256", + }, + ] + `) + + expect(messages[1].type).toBe('keysChanged') + expect(messages[1].data.length).toBe(1) + }) + + test('behavior: revoke last admin key', async () => { + const porto = createPorto() + + const messages: any[] = [] + porto.provider.on('message', (message) => messages.push(message)) + + await porto.provider.request({ + method: 'experimental_createAccount', + }) + + const accounts = porto._internal.store.getState().accounts + const publicKey = accounts![0]!.keys![0]!.publicKey + + await expect(() => + porto.provider.request({ + method: 'experimental_revokeKey', + params: [{ publicKey }], + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + '[Error: cannot revoke key. account must have at least one admin key.]', + ) + }) +}) + +describe('personal_sign', () => { + test('default', async () => { + const porto = createPorto() + const client = Porto.getClients(porto).default + const { address } = await porto.provider.request({ + method: 'experimental_createAccount', + }) + const signature = await porto.provider.request({ + method: 'personal_sign', + params: [Hex.fromString('hello'), address], + }) + expect(signature).toBeDefined() + + const valid = await verifyMessage(client, { + address, + message: 'hello', + signature, + }) + expect(valid).toBe(true) + }) +}) + +describe('wallet_connect', () => { + test('default', async () => { + const messages: any[] = [] + + const porto = createPorto() + porto.provider.on('connect', (message) => messages.push(message)) + + await porto.provider.request({ + method: 'experimental_createAccount', + }) + await porto.provider.request({ + method: 'wallet_disconnect', + }) + await porto.provider.request({ + method: 'wallet_connect', + }) + const accounts = porto._internal.store.getState().accounts + expect(accounts.length).toBe(1) + expect(accounts![0]!.keys?.length).toBe(1) + expect( + accounts![0]!.keys?.map((x) => ({ ...x, expiry: null, publicKey: null })), + ).toMatchInlineSnapshot(` + [ + { + "canSign": false, + "expiry": null, + "publicKey": null, + "role": "admin", + "type": "p256", + }, + ] + `) + + expect(messages[0].chainId).toBe(Hex.fromNumber(1)) + }) + + test('behavior: `createAccount` capability', async () => { + const messages: any[] = [] + + const porto = createPorto() + porto.provider.on('connect', (message) => messages.push(message)) + + await porto.provider.request({ + method: 'wallet_connect', + params: [ + { + capabilities: { + createAccount: true, + }, + }, + ], + }) + const accounts = porto._internal.store.getState().accounts + expect(accounts.length).toBe(1) + expect(accounts![0]!.keys?.length).toBe(1) + expect( + accounts![0]!.keys?.map((x) => ({ ...x, expiry: null, publicKey: null })), + ).toMatchInlineSnapshot(` + [ + { + "callScopes": undefined, + "canSign": true, + "expiry": null, + "privateKey": [Function], + "publicKey": null, + "role": "admin", + "type": "p256", + }, + ] + `) + + expect(messages[0].chainId).toBe(Hex.fromNumber(1)) + }) + + test('behavior: `createAccount` + `authorizeKey` capability', async () => { + const messages: any[] = [] + + const porto = createPorto() + porto.provider.on('connect', (message) => messages.push(message)) + + await porto.provider.request({ + method: 'wallet_connect', + params: [ + { + capabilities: { + createAccount: true, + authorizeKey: { + callScopes: [{ signature: 'mint()' }], + }, + }, + }, + ], + }) + const accounts = porto._internal.store.getState().accounts + expect(accounts.length).toBe(1) + expect(accounts![0]!.keys?.length).toBe(2) + expect( + accounts![0]!.keys?.map((x) => ({ ...x, expiry: null, publicKey: null })), + ).toMatchInlineSnapshot(` + [ + { + "callScopes": undefined, + "canSign": true, + "expiry": null, + "privateKey": [Function], + "publicKey": null, + "role": "admin", + "type": "p256", + }, + { + "callScopes": [ + { + "signature": "mint()", + }, + ], + "canSign": true, + "expiry": null, + "privateKey": CryptoKey {}, + "publicKey": null, + "role": "session", + "type": "p256", + }, + ] + `) + + expect(messages[0].chainId).toBe(Hex.fromNumber(1)) + }) + + test('behavior: `createAccount` + `authorizeKey` capability (provided key)', async () => { + const messages: any[] = [] + + const porto = createPorto() + porto.provider.on('connect', (message) => messages.push(message)) + + const privateKey = + '0x1e8dd87f21bc6bbfc86e726ca9c21a285c13984461cf2e3adb265019fb78203d' + const publicKey = PublicKey.toHex(P256.getPublicKey({ privateKey }), { + includePrefix: false, + }) + + await porto.provider.request({ + method: 'wallet_connect', + params: [ + { + capabilities: { + createAccount: true, + authorizeKey: { + callScopes: [{ signature: 'mint()' }], + publicKey, + role: 'session', + type: 'p256', + }, + }, + }, + ], + }) + const accounts = porto._internal.store.getState().accounts + expect(accounts.length).toBe(1) + expect(accounts![0]!.keys?.length).toBe(2) + expect( + accounts![0]!.keys?.map((x, i) => ({ + ...x, + expiry: i === 0 ? null : x.expiry, + publicKey: i === 0 ? null : x.publicKey, + })), + ).toMatchInlineSnapshot(` + [ + { + "callScopes": undefined, + "canSign": true, + "expiry": null, + "privateKey": [Function], + "publicKey": null, + "role": "admin", + "type": "p256", + }, + { + "callScopes": [ + { + "signature": "mint()", + }, + ], + "canSign": false, + "expiry": 694206942069, + "publicKey": "0x86a0d77beccf47a0a78cccfc19fdfe7317816740c9f9e6d7f696a02b0c66e0e21744d93c5699e9ce658a64ce60df2f32a17954cd577c713922bf62a1153cf68e", + "role": "session", + "type": "p256", + }, + ] + `) + + expect(messages[0].chainId).toBe(Hex.fromNumber(1)) + }) +}) + +describe('wallet_disconnect', () => { + test('default', async () => { + const messages: any[] = [] + + const porto = createPorto() + porto.provider.on('disconnect', (message) => messages.push(message)) + + await porto.provider.request({ + method: 'experimental_createAccount', + }) + await porto.provider.request({ + method: 'wallet_disconnect', + }) + + const accounts = porto._internal.store.getState().accounts + expect(accounts.length).toBe(0) + expect(messages).toMatchInlineSnapshot(` + [ + [Provider.DisconnectedError: The provider is disconnected from all chains.], + ] + `) + }) +}) + +describe('wallet_sendCalls', () => { + test('default', async () => { + const porto = createPorto() + const client = Porto.getClients(porto).default.extend(() => ({ + mode: 'anvil', + })) + + const { address } = await porto.provider.request({ + method: 'experimental_createAccount', + }) + await setBalance(client, { + address, + value: Value.fromEther('10000'), + }) + + const alice = '0x0000000000000000000000000000000000069421' + + const hash = await porto.provider.request({ + method: 'wallet_sendCalls', + params: [ + { + from: address, + calls: [ + { + to: alice, + value: Hex.fromNumber(69420), + }, + ], + version: '1', + }, + ], + }) + + expect(hash).toBeDefined() + expect(await getBalance(client, { address: alice })).toBe(69420n) + }) + + test('behavior: `key` capability', async () => { + const porto = createPorto() + const client = Porto.getClients(porto).default.extend(() => ({ + mode: 'anvil', + })) + + const { address } = await porto.provider.request({ + method: 'experimental_createAccount', + }) + await setBalance(client, { + address, + value: Value.fromEther('10000'), + }) + + const alice = '0x0000000000000000000000000000000000069422' + + const key = await porto.provider.request({ + method: 'experimental_authorizeKey', + params: [ + { + key: { + callScopes: [{ to: alice }], + }, + }, + ], + }) + const hash = await porto.provider.request({ + method: 'wallet_sendCalls', + params: [ + { + capabilities: { + key, + }, + from: address, + calls: [ + { + to: alice, + value: Hex.fromNumber(69420), + }, + ], + version: '1', + }, + ], + }) + + expect(hash).toBeDefined() + expect(await getBalance(client, { address: alice })).toBe(69420n) + }) + + test('behavior: `key.callScopes` mismatch', async () => { + const porto = createPorto() + const client = Porto.getClients(porto).default.extend(() => ({ + mode: 'anvil', + })) + + const { address } = await porto.provider.request({ + method: 'experimental_createAccount', + }) + await setBalance(client, { + address, + value: Value.fromEther('10000'), + }) + + const alice = '0x0000000000000000000000000000000000069422' + + const key = await porto.provider.request({ + method: 'experimental_authorizeKey', + params: [ + { + key: { + callScopes: [{ to: '0x0000000000000000000000000000000000000000' }], + }, + }, + ], + }) + await expect(() => + porto.provider.request({ + method: 'wallet_sendCalls', + params: [ + { + capabilities: { + key, + }, + from: address, + calls: [ + { + to: alice, + value: Hex.fromNumber(69420), + }, + ], + version: '1', + }, + ], + }), + ).rejects.toThrowError('Unauthorized') + }) + + test('behavior: revoked key', async () => { + const porto = createPorto() + const client = Porto.getClients(porto).default.extend(() => ({ + mode: 'anvil', + })) + + const { address } = await porto.provider.request({ + method: 'experimental_createAccount', + }) + await setBalance(client, { + address, + value: Value.fromEther('10000'), + }) + + const alice = '0x0000000000000000000000000000000000069422' + + const key = await porto.provider.request({ + method: 'experimental_authorizeKey', + params: [ + { + key: { + callScopes: [{ to: alice }], + }, + }, + ], + }) + const hash = await porto.provider.request({ + method: 'wallet_sendCalls', + params: [ + { + capabilities: { + key, + }, + from: address, + calls: [ + { + to: alice, + value: Hex.fromNumber(69420), + }, + ], + version: '1', + }, + ], + }) + + expect(hash).toBeDefined() + expect(await getBalance(client, { address: alice })).toBe(69420n) + + await porto.provider.request({ + method: 'experimental_revokeKey', + params: [{ publicKey: key.publicKey }], + }) + await expect(() => + porto.provider.request({ + method: 'wallet_sendCalls', + params: [ + { + capabilities: { + key, + }, + from: address, + calls: [ + { + to: alice, + value: Hex.fromNumber(69420), + }, + ], + version: '1', + }, + ], + }), + ).rejects.toThrowError() + }) + + test('behavior: not provider-managed key', async () => { + const porto = createPorto() + const client = Porto.getClients(porto).default.extend(() => ({ + mode: 'anvil', + })) + + const { address } = await porto.provider.request({ + method: 'experimental_createAccount', + }) + await setBalance(client, { + address, + value: Value.fromEther('10000'), + }) + + const alice = '0x0000000000000000000000000000000000069421' + + const key = await porto.provider.request({ + method: 'experimental_authorizeKey', + params: [ + { + key: { + callScopes: [{ to: alice }], + publicKey: + '0x86a0d77beccf47a0a78cccfc19fdfe7317816740c9f9e6d7f696a02b0c66e0e21744d93c5699e9ce658a64ce60df2f32a17954cd577c713922bf62a1153cf68e', + role: 'session', + type: 'p256', + }, + }, + ], + }) + await expect(() => + porto.provider.request({ + method: 'wallet_sendCalls', + params: [ + { + capabilities: { + key, + }, + from: address, + calls: [ + { + to: alice, + value: Hex.fromNumber(69420), + }, + ], + version: '1', + }, + ], + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + '[Error: key (publicKey: 0x86a0d77beccf47a0a78cccfc19fdfe7317816740c9f9e6d7f696a02b0c66e0e21744d93c5699e9ce658a64ce60df2f32a17954cd577c713922bf62a1153cf68e) does not exist or is not a provider-managed key.]', + ) + }) + + test('behavior: key does not exist', async () => { + const porto = createPorto() + const client = Porto.getClients(porto).default.extend(() => ({ + mode: 'anvil', + })) + + const { address } = await porto.provider.request({ + method: 'experimental_createAccount', + }) + await setBalance(client, { + address, + value: Value.fromEther('10000'), + }) + + const alice = '0x0000000000000000000000000000000000069421' + + await expect(() => + porto.provider.request({ + method: 'wallet_sendCalls', + params: [ + { + capabilities: { + key: { + publicKey: + '0x86a0d77beccf47a0a78cccfc19fdfe7317816740c9f9e6d7f696a02b0c66e0e21744d93c5699e9ce658a64ce60df2f32a17954cd577c713922bf62a1153cf68e', + }, + }, + from: address, + calls: [ + { + to: alice, + value: Hex.fromNumber(69420), + }, + ], + version: '1', + }, + ], + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + '[Error: key (publicKey: 0x86a0d77beccf47a0a78cccfc19fdfe7317816740c9f9e6d7f696a02b0c66e0e21744d93c5699e9ce658a64ce60df2f32a17954cd577c713922bf62a1153cf68e) does not exist or is not a provider-managed key.]', + ) + }) +}) + +test('smoke', async () => { + const porto = createPorto() + const code = await porto.provider.request({ + method: 'eth_getCode', + params: [delegation, 'latest'], + }) + expect(code).toMatchSnapshot() +}) + +const typedData = { + domain: { + name: 'Ether Mail 🥵', + version: '1.1.1', + chainId: 1, + verifyingContract: '0x0000000000000000000000000000000000000000', + }, + types: { + Name: [ + { name: 'first', type: 'string' }, + { name: 'last', type: 'string' }, + ], + Person: [ + { name: 'name', type: 'Name' }, + { name: 'wallet', type: 'address' }, + { name: 'favoriteColors', type: 'string[3]' }, + { name: 'foo', type: 'uint256' }, + { name: 'age', type: 'uint8' }, + { name: 'isCool', type: 'bool' }, + ], + Mail: [ + { name: 'timestamp', type: 'uint256' }, + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + { name: 'hash', type: 'bytes' }, + ], + }, + primaryType: 'Mail', + message: { + timestamp: 1234567890n, + contents: 'Hello, Bob! 🖤', + hash: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', + from: { + name: { + first: 'Cow', + last: 'Burns', + }, + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + age: 69, + foo: 123123123123123123n, + favoriteColors: ['red', 'green', 'blue'], + isCool: false, + }, + to: { + name: { first: 'Bob', last: 'Builder' }, + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + age: 70, + foo: 123123123123123123n, + favoriteColors: ['orange', 'yellow', 'green'], + isCool: true, + }, + }, +} as const diff --git a/src/core/internal/provider.ts b/src/core/internal/provider.ts index a38b3c6c..65f194b8 100644 --- a/src/core/internal/provider.ts +++ b/src/core/internal/provider.ts @@ -1,19 +1,14 @@ import * as Mipd from 'mipd' import type { RpcSchema } from 'ox' import * as Address from 'ox/Address' -import * as Authorization from 'ox/Authorization' import * as Hex from 'ox/Hex' -import * as Json from 'ox/Json' -import * as PersonalMessage from 'ox/PersonalMessage' import * as ox_Provider from 'ox/Provider' -import * as PublicKey from 'ox/PublicKey' import * as RpcResponse from 'ox/RpcResponse' -import * as Signature from 'ox/Signature' -import * as TypedData from 'ox/TypedData' import type * as Chains from '../Chains.js' -import type { Config, Store } from '../Porto.js' -import * as AccountDelegation from './accountDelegation.js' +import * as Porto from '../Porto.js' +import type * as Call from './call.js' +import type * as Key from './key.js' import type * as Schema from './rpcSchema.js' export type Provider = ox_Provider.Provider<{ @@ -36,12 +31,19 @@ export function from< ], >(parameters: from.Parameters): Provider { const { config, store } = parameters - const { announceProvider, headless, keystoreHost } = config + const { announceProvider, implementation } = config + + function getClients(chainId_?: Hex.Hex | number | undefined) { + const chainId = + typeof chainId_ === 'string' ? Hex.toNumber(chainId_) : chainId_ + return Porto.getClients({ _internal: parameters }, { chainId }) + } const emitter = ox_Provider.createEmitter() const provider = ox_Provider.from({ ...emitter, - async request({ method, params }) { + async request(request) { + const { method, params } = request const state = store.getState() switch (method) { @@ -55,28 +57,34 @@ export function from< case 'eth_chainId': { return Hex.fromNumber( - state.chainId, + state.chain.id, ) satisfies RpcSchema.ExtractReturnType } case 'eth_requestAccounts': { - if (!headless) throw new ox_Provider.UnsupportedMethodError() + const clients = getClients() - const { account } = await AccountDelegation.load(state.client, { - rpId: keystoreHost, + const { accounts } = await implementation.actions.loadAccounts({ + clients, + config, + request, }) - store.setState((x) => ({ ...x, accounts: [account] })) + store.setState((x) => ({ ...x, accounts })) - emitter.emit('connect', { chainId: Hex.fromNumber(state.chainId) }) - return [account.address] satisfies RpcSchema.ExtractReturnType< + emitter.emit('connect', { + chainId: Hex.fromNumber(clients.default.chain.id), + }) + + return accounts.map( + (account) => account.address, + ) satisfies RpcSchema.ExtractReturnType< Schema.Schema, 'eth_requestAccounts' > } case 'eth_sendTransaction': { - if (!headless) throw new ox_Provider.UnsupportedMethodError() if (state.accounts.length === 0) throw new ox_Provider.DisconnectedError() @@ -86,7 +94,9 @@ export function from< 'eth_sendTransaction' > - if (chainId && Hex.toNumber(chainId) !== state.chainId) + const clients = getClients(chainId) + + if (chainId && Hex.toNumber(chainId) !== clients.default.chain.id) throw new ox_Provider.ChainDisconnectedError() requireParameter(to, 'to') @@ -97,9 +107,7 @@ export function from< ) if (!account) throw new ox_Provider.UnauthorizedError() - const keyIndex = getActiveSessionKeyIndex({ account }) - - return (await AccountDelegation.execute(state.client, { + const hash = await implementation.actions.execute({ account, calls: [ { @@ -108,16 +116,18 @@ export function from< value: Hex.toBigInt(value), }, ], - keyIndex, - rpId: keystoreHost, - })) satisfies RpcSchema.ExtractReturnType< + clients, + config, + request, + }) + + return hash satisfies RpcSchema.ExtractReturnType< Schema.Schema, 'eth_sendTransaction' > } case 'eth_signTypedData_v4': { - if (!headless) throw new ox_Provider.UnsupportedMethodError() if (state.accounts.length === 0) throw new ox_Provider.DisconnectedError() @@ -131,13 +141,14 @@ export function from< ) if (!account) throw new ox_Provider.UnauthorizedError() - const keyIndex = getActiveSessionKeyIndex({ account }) + const clients = getClients() - const signature = await AccountDelegation.sign({ + const signature = await implementation.actions.signTypedData({ account, - keyIndex, - payload: TypedData.getSignPayload(Json.parse(data)), - rpId: keystoreHost, + clients, + config, + data, + request, }) return signature satisfies RpcSchema.ExtractReturnType< @@ -146,102 +157,15 @@ export function from< > } - case 'experimental_connect': { - if (!headless) throw new ox_Provider.UnsupportedMethodError() - - const [{ capabilities }] = (params as RpcSchema.ExtractParams< - Schema.Schema, - 'experimental_connect' - >) ?? [{}] - const { createAccount, grantSession } = capabilities ?? {} - - const { expiry = Math.floor(Date.now() / 1000) + 60 * 60 } = - typeof grantSession === 'object' ? grantSession : {} - const key = grantSession - ? await AccountDelegation.createWebCryptoKey({ - expiry: BigInt(expiry), - }) - : undefined - - const { account } = await (async () => { - if (createAccount) { - const { label } = - typeof createAccount === 'object' ? createAccount : {} - return await AccountDelegation.create(state.client, { - authorizeKeys: key ? [key] : undefined, - delegation: state.delegation, - label, - rpId: keystoreHost, - }) - } - return await AccountDelegation.load(state.client, { - authorizeKeys: key ? [key] : undefined, - rpId: keystoreHost, - }) - })() - - const sessions = getActiveSessionKeys(account.keys) - - store.setState((x) => ({ ...x, accounts: [account] })) - - emitter.emit('connect', { chainId: Hex.fromNumber(state.chainId) }) - - return [ - { - address: account.address, - capabilities: { - sessions, - }, - }, - ] satisfies RpcSchema.ExtractReturnType< - Schema.Schema, - 'experimental_connect' - > - } - - case 'experimental_createAccount': { - if (!headless) throw new ox_Provider.UnsupportedMethodError() - - const [{ label }] = (params as RpcSchema.ExtractParams< - Schema.Schema, - 'experimental_createAccount' - >) ?? [{}] - - // TODO: wait for tx to be included/make counterfactual? - const { account } = await AccountDelegation.create(state.client, { - delegation: state.delegation, - label, - rpId: keystoreHost, - }) - - store.setState((x) => ({ ...x, accounts: [account] })) - - emitter.emit('connect', { chainId: Hex.fromNumber(state.chainId) }) - return account.address satisfies RpcSchema.ExtractReturnType< - Schema.Schema, - 'experimental_createAccount' - > - } - - case 'experimental_disconnect': { - store.setState((x) => ({ ...x, accounts: [] })) - return - } - - case 'experimental_grantSession': { - if (!headless) throw new ox_Provider.UnsupportedMethodError() + case 'experimental_authorizeKey': { if (state.accounts.length === 0) throw new ox_Provider.DisconnectedError() - const [ - { - address, - expiry = Math.floor(Date.now() / 1_000) + 60 * 60, // 1 hour - }, - ] = (params as RpcSchema.ExtractParams< - Schema.Schema, - 'experimental_grantSession' - >) ?? [{}] + const [{ address, key: keyToAuthorize }] = + (params as RpcSchema.ExtractParams< + Schema.Schema, + 'experimental_authorizeKey' + >) ?? [{}] const account = address ? state.accounts.find((account) => @@ -250,15 +174,14 @@ export function from< : state.accounts[0] if (!account) throw new ox_Provider.UnauthorizedError() - const key = await AccountDelegation.createWebCryptoKey({ - expiry: BigInt(expiry), - }) + const clients = getClients() - // TODO: wait for tx to be included? - await AccountDelegation.authorize(state.client, { + const { key } = await implementation.actions.authorizeKey({ account, - keys: [key], - rpId: keystoreHost, + clients, + key: keyToAuthorize, + config, + request, }) store.setState((x) => { @@ -270,118 +193,102 @@ export function from< ...x, accounts: x.accounts.map((account, i) => i === index - ? { ...account, keys: [...account.keys, key] } + ? { ...account, keys: [...(account.keys ?? []), key] } : account, ), } }) emitter.emit('message', { - data: getActiveSessionKeys([...account.keys, key]), - type: 'sessionsChanged', + data: getActiveKeys([...(account.keys ?? []), key]), + type: 'keysChanged', }) return { - expiry, - id: PublicKey.toHex(key.publicKey), + callScopes: key.callScopes, + expiry: key.expiry, + publicKey: key.publicKey, + role: key.role, + type: key.type, } satisfies RpcSchema.ExtractReturnType< Schema.Schema, - 'experimental_grantSession' + 'experimental_authorizeKey' > } - case 'experimental_prepareImportAccount': { - if (!headless) throw new ox_Provider.UnsupportedMethodError() - - const [{ address, capabilities, label }] = + case 'experimental_createAccount': { + const [{ chainId, label, context, signatures }] = (params as RpcSchema.ExtractParams< Schema.Schema, - 'experimental_prepareImportAccount' + 'experimental_createAccount' >) ?? [{}] - const { expiry = Math.floor(Date.now() / 1000) + 60 * 60 } = - typeof capabilities?.grantSession === 'object' - ? capabilities.grantSession - : {} - const key = capabilities?.grantSession - ? await AccountDelegation.createWebCryptoKey({ - expiry: BigInt(expiry), - }) - : undefined + const clients = getClients(chainId) - const { account, authorization, signPayload } = - await AccountDelegation.prepareInitialize(state.client, { - address, - authorizeKeys: key ? [key] : undefined, - delegation: state.delegation, - label, - rpId: keystoreHost, - }) - - const authorizationPayload = Authorization.getSignPayload({ - address: authorization.contractAddress, - chainId: authorization.chainId, - nonce: BigInt(authorization.nonce), + const { account } = await implementation.actions.createAccount({ + clients, + config, + context, + label, + request, + signatures, }) + store.setState((x) => ({ ...x, accounts: [account] })) + + emitter.emit('connect', { + chainId: Hex.fromNumber(clients.default.chain.id), + }) return { - context: { - account, - authorization, + address: account.address, + capabilities: { + keys: account.keys ? getActiveKeys(account.keys) : [], }, - signPayloads: [authorizationPayload, signPayload], } satisfies RpcSchema.ExtractReturnType< Schema.Schema, - 'experimental_prepareImportAccount' + 'experimental_createAccount' > } - case 'experimental_importAccount': { - const [{ context, signatures }] = (params as RpcSchema.ExtractParams< - Schema.Schema, - 'experimental_importAccount' - >) ?? [{}] + case 'experimental_prepareCreateAccount': { + const [{ address, capabilities, label }] = + (params as RpcSchema.ExtractParams< + Schema.Schema, + 'experimental_prepareCreateAccount' + >) ?? [{}] - const { authorization } = context + const { authorizeKey } = capabilities ?? {} - const authorizationSignature = Signature.from(signatures[0]!) - const initializeSignature = Signature.from(signatures[1]!) + const authorizeKeys = authorizeKey ? [authorizeKey] : undefined - const { account } = await AccountDelegation.initialize(state.client, { - ...context, - authorization: { - ...authorization, - r: Hex.fromNumber(authorizationSignature.r), - s: Hex.fromNumber(authorizationSignature.s), - yParity: authorizationSignature.yParity, - }, - signature: Signature.from(initializeSignature), - }) + const clients = getClients() - const sessions = getActiveSessionKeys(account.keys) - - store.setState((x) => ({ ...x, accounts: [account] })) - - emitter.emit('connect', { chainId: Hex.fromNumber(state.chainId) }) + const { context, signPayloads } = + await implementation.actions.prepareCreateAccount({ + address, + authorizeKeys, + clients, + config, + label, + request, + }) return { - address: account.address, - capabilities: { - sessions, - }, + context, + signPayloads, } satisfies RpcSchema.ExtractReturnType< Schema.Schema, - 'experimental_importAccount' + 'experimental_prepareCreateAccount' > } - case 'experimental_sessions': { + case 'experimental_keys': { if (state.accounts.length === 0) throw new ox_Provider.DisconnectedError() const [{ address }] = (params as RpcSchema.ExtractParams< Schema.Schema, - 'experimental_sessions' + 'experimental_keys' >) ?? [{}] const account = address @@ -390,7 +297,57 @@ export function from< ) : state.accounts[0] - return getActiveSessionKeys(account?.keys ?? []) + return getActiveKeys(account?.keys ?? []) + } + + case 'experimental_revokeKey': { + if (state.accounts.length === 0) + throw new ox_Provider.DisconnectedError() + + const [{ address, publicKey }] = params as RpcSchema.ExtractParams< + Schema.Schema, + 'experimental_revokeKey' + > + + const account = address + ? state.accounts.find((account) => + Address.isEqual(account.address, address), + ) + : state.accounts[0] + if (!account) throw new ox_Provider.UnauthorizedError() + + const clients = getClients() + + await implementation.actions.revokeKey({ + account, + clients, + config, + publicKey, + request, + }) + + const keys = account.keys?.filter( + (key) => key.publicKey !== publicKey, + ) + + store.setState((x) => ({ + ...x, + accounts: x.accounts.map((x) => + Address.isEqual(x.address, account.address) + ? { + ...x, + keys, + } + : x, + ), + })) + + emitter.emit('message', { + data: getActiveKeys(keys ?? []), + type: 'keysChanged', + }) + + return } case 'porto_ping': { @@ -401,7 +358,6 @@ export function from< } case 'personal_sign': { - if (!headless) throw new ox_Provider.UnsupportedMethodError() if (state.accounts.length === 0) throw new ox_Provider.DisconnectedError() @@ -415,13 +371,14 @@ export function from< ) if (!account) throw new ox_Provider.UnauthorizedError() - const keyIndex = getActiveSessionKeyIndex({ account }) + const clients = getClients() - const signature = await AccountDelegation.sign({ + const signature = await implementation.actions.signPersonalMessage({ account, - keyIndex, - payload: PersonalMessage.getSignPayload(data), - rpId: keystoreHost, + clients, + config, + data, + request, }) return signature satisfies RpcSchema.ExtractReturnType< @@ -430,6 +387,64 @@ export function from< > } + case 'wallet_connect': { + const [{ capabilities }] = (params as RpcSchema.ExtractParams< + Schema.Schema, + 'wallet_connect' + >) ?? [{}] + + const clients = getClients() + + const { createAccount, authorizeKey } = capabilities ?? {} + + const authorizeKeys = authorizeKey ? [authorizeKey] : undefined + + const { accounts } = await (async () => { + if (createAccount) { + const { label = undefined } = + typeof createAccount === 'object' ? createAccount : {} + const { account } = await implementation.actions.createAccount({ + authorizeKeys, + clients, + config, + label, + request, + }) + return { accounts: [account] } + } + return await implementation.actions.loadAccounts({ + authorizeKeys, + clients, + config, + request, + }) + })() + + store.setState((x) => ({ ...x, accounts })) + + emitter.emit('connect', { + chainId: Hex.fromNumber(clients.default.chain.id), + }) + + return { + accounts: accounts.map((account) => ({ + address: account.address, + capabilities: { + keys: account.keys ? getActiveKeys(account.keys) : [], + }, + })), + } satisfies RpcSchema.ExtractReturnType< + Schema.Schema, + 'wallet_connect' + > + } + + case 'wallet_disconnect': { + store.setState((x) => ({ ...x, accounts: [] })) + emitter.emit('disconnect', new ox_Provider.DisconnectedError()) + return + } + case 'wallet_getCallsStatus': { const [id] = (params as RpcSchema.ExtractParams< @@ -437,7 +452,9 @@ export function from< 'wallet_getCallsStatus' >) ?? [] - const receipt = await state.client.request({ + const clients = getClients() + + const receipt = await clients.default.request({ method: 'eth_getTransactionReceipt', params: [id! as Hex.Hex], }) @@ -453,33 +470,41 @@ export function from< } case 'wallet_getCapabilities': { - return { - [Hex.fromNumber(state.chainId)]: { - atomicBatch: { - supported: true, - }, - createAccount: { - supported: true, - }, - sessions: { - supported: true, - }, + const value = { + atomicBatch: { + supported: true, }, - } satisfies RpcSchema.ExtractReturnType< + createAccount: { + supported: true, + }, + keys: { + supported: true, + }, + } + + const capabilities = {} as Record + for (const chain of config.chains) + capabilities[Hex.fromNumber(chain.id)] = value + + return capabilities satisfies RpcSchema.ExtractReturnType< Schema.Schema, 'wallet_getCapabilities' > } case 'wallet_sendCalls': { - if (!headless) throw new ox_Provider.UnsupportedMethodError() if (state.accounts.length === 0) throw new ox_Provider.DisconnectedError() - const [{ chainId, calls, from, capabilities }] = - params as RpcSchema.ExtractParams + const [parameters] = params as RpcSchema.ExtractParams< + Schema.Schema, + 'wallet_sendCalls' + > + const { capabilities, chainId, from } = parameters + + const clients = getClients(chainId) - if (chainId && Hex.toNumber(chainId) !== state.chainId) + if (chainId && Hex.toNumber(chainId) !== clients.default.chain.id) throw new ox_Provider.ChainDisconnectedError() requireParameter(from, 'from') @@ -489,20 +514,21 @@ export function from< ) if (!account) throw new ox_Provider.UnauthorizedError() - const { enabled = true, id } = capabilities?.session ?? {} + const calls = parameters.calls.map((x) => { + requireParameter(x, 'to') + return x + }) as Call.Call[] - const keyIndex = enabled - ? getActiveSessionKeyIndex({ account, id }) - : undefined - if (typeof keyIndex !== 'number') - throw new ox_Provider.UnauthorizedError() - - return (await AccountDelegation.execute(state.client, { + const hash = await implementation.actions.execute({ account, - calls: calls as AccountDelegation.Calls, - keyIndex, - rpId: keystoreHost, - })) satisfies RpcSchema.ExtractReturnType< + calls, + clients, + config, + key: capabilities?.key, + request, + }) + + return hash satisfies RpcSchema.ExtractReturnType< Schema.Schema, 'wallet_sendCalls' > @@ -511,7 +537,7 @@ export function from< default: { if (method.startsWith('wallet_')) throw new ox_Provider.UnsupportedMethodError() - return state.client.request({ method, params } as any) + return getClients().default.request({ method, params } as any) } } }, @@ -559,8 +585,8 @@ export declare namespace from { ...Chains.Chain[], ], > = { - config: Config - store: Store + config: Porto.Config + store: Porto.Store } } @@ -577,30 +603,20 @@ export function announce(provider: Provider) { }) } -function getActiveSessionKeyIndex(parameters: { - account: AccountDelegation.Account - id?: Hex.Hex | undefined -}) { - const { account, id } = parameters - if (id) - return account.keys.findIndex( - (key) => PublicKey.toHex(key.publicKey) === id, - ) - const index = account.keys.findIndex(AccountDelegation.isActiveSessionKey) - if (index === -1) return 0 - return index -} - -function getActiveSessionKeys( - keys: readonly AccountDelegation.Key[], -): Schema.GrantSessionReturnType[] { +function getActiveKeys( + keys: readonly Key.Key[], +): Schema.AuthorizeKeyReturnType[] { return keys .map((key) => { - if (!AccountDelegation.isActiveSessionKey(key)) return undefined + if (key.expiry > 0 && key.expiry < BigInt(Math.floor(Date.now() / 1000))) + return undefined return { - expiry: Number(key.expiry), - id: PublicKey.toHex(key.publicKey), - } + callScopes: key.callScopes, + expiry: key.expiry, + publicKey: key.publicKey, + role: key.role, + type: key.type, + } satisfies Schema.AuthorizeKeyReturnType }) .filter(Boolean) as never } diff --git a/src/core/internal/rpcSchema.ts b/src/core/internal/rpcSchema.ts index d3bdb6a6..ff4968e1 100644 --- a/src/core/internal/rpcSchema.ts +++ b/src/core/internal/rpcSchema.ts @@ -1,9 +1,8 @@ import type * as Address from 'ox/Address' import type * as Hex from 'ox/Hex' import type * as RpcSchema from 'ox/RpcSchema' -import type { Authorization } from 'viem/experimental' - -import type * as AccountDelegation from './accountDelegation.js' +import type * as Key from './key.js' +import type { OneOf } from './types.js' export type Schema = RpcSchema.From< | RpcSchema.Default @@ -15,132 +14,151 @@ export type Schema = RpcSchema.From< } | { Request: { - method: 'experimental_connect' - params?: [ConnectParameters] | undefined + method: 'experimental_authorizeKey' + params: [AuthorizeKeyParameters] } - ReturnType: ConnectReturnType + ReturnType: AuthorizeKeyReturnType } | { Request: { method: 'experimental_createAccount' params?: [CreateAccountParameters] | undefined } - ReturnType: Address.Address + ReturnType: CreateAccountReturnType } | { Request: { - method: 'experimental_disconnect' + method: 'experimental_prepareCreateAccount' + params: [PrepareCreateAccountParameters] } - ReturnType: undefined + ReturnType: PrepareCreateAccountReturnType } | { Request: { - method: 'experimental_grantSession' - params?: [GrantSessionParameters] | undefined + method: 'experimental_keys' + params?: [GetKeysParameters] | undefined } - ReturnType: GrantSessionReturnType + ReturnType: GetKeysReturnType } | { Request: { - method: 'experimental_importAccount' - params: [ImportAccountParameters] + method: 'experimental_revokeKey' + params: [RevokeKeyParameters] } - ReturnType: ImportAccountReturnType + ReturnType: undefined } | { Request: { - method: 'experimental_prepareImportAccount' - params: [PrepareImportAccountParameters] + method: 'wallet_connect' + params?: [ConnectParameters] | undefined } - ReturnType: PrepareImportAccountReturnType + ReturnType: ConnectReturnType } | { Request: { - method: 'experimental_sessions' - params?: [GetSessionsParameters] | undefined + method: 'wallet_disconnect' } - ReturnType: GetSessionsReturnType + ReturnType: undefined } > +export type AuthorizeKeyParameters = { + address?: Address.Address | undefined + key?: + | OneOf< + | { + callScopes: Key.CallScopes + expiry?: number | undefined + } + | { + callScopes: Key.CallScopes + expiry?: number | undefined + publicKey: Hex.Hex + role?: 'session' | undefined + type: 'p256' | 'secp256k1' | 'webauthn-p256' + } + | { + callScopes?: Key.CallScopes | undefined + expiry?: number | undefined + publicKey: Hex.Hex + role: 'admin' + type: 'p256' | 'secp256k1' | 'webauthn-p256' + } + > + | undefined +} + +export type AuthorizeKeyReturnType = GetKeysReturnType[number] + export type ConnectParameters = { capabilities?: | { + authorizeKey?: AuthorizeKeyParameters['key'] | undefined createAccount?: boolean | CreateAccountParameters | undefined - grantSession?: - | boolean - | Omit - | undefined } | undefined } -export type ConnectReturnType = readonly { +export type ConnectReturnType = { + accounts: readonly { + address: Address.Address + capabilities?: + | { + keys?: GetKeysReturnType | undefined + } + | undefined + }[] +} + +export type CreateAccountParameters = { + chainId?: Hex.Hex | undefined +} & OneOf< + | { + label?: string | undefined + } + | { + context: unknown + signatures: readonly Hex.Hex[] + } +> + +export type CreateAccountReturnType = { address: Address.Address capabilities?: | { - sessions?: GetSessionsReturnType | undefined + keys?: GetKeysReturnType | undefined } | undefined -}[] - -export type CreateAccountParameters = { - label?: string | undefined } -export type GetSessionsParameters = { +export type GetKeysParameters = { address?: Address.Address | undefined } -export type GetSessionsReturnType = readonly GrantSessionReturnType[] - -export type GrantSessionParameters = { - address?: Address.Address | undefined - expiry?: number | undefined - keys?: - | readonly { - algorithm: 'p256' | 'secp256k1' - publicKey: Hex.Hex - }[] - | undefined -} - -export type GrantSessionReturnType = { +export type GetKeysReturnType = readonly { + callScopes?: Key.CallScopes | undefined expiry: number - id: Hex.Hex -} - -export type ImportAccountParameters = { - context: PrepareImportAccountReturnType['context'] - signatures: readonly Hex.Hex[] -} - -export type ImportAccountReturnType = { - address: Address.Address - capabilities?: - | { - sessions?: GetSessionsReturnType | undefined - } - | undefined -} + publicKey: Hex.Hex + role: 'admin' | 'session' + type: 'p256' | 'secp256k1' | 'webauthn-p256' +}[] -export type PrepareImportAccountParameters = { +export type PrepareCreateAccountParameters = { address: Address.Address capabilities?: | { - grantSession?: - | boolean - | Omit - | undefined + authorizeKey?: AuthorizeKeyParameters['key'] | undefined } | undefined label?: string | undefined } -export type PrepareImportAccountReturnType = { - context: { - account: AccountDelegation.Account - authorization: Authorization - } +export type PrepareCreateAccountReturnType = { + context: unknown signPayloads: readonly Hex.Hex[] } + +export type RevokeKeyParameters = { + address?: Address.Address | undefined + publicKey: Hex.Hex +} diff --git a/src/core/internal/storage.ts b/src/core/internal/storage.ts deleted file mode 100644 index 6b1cef6a..00000000 --- a/src/core/internal/storage.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { createStore, del, get, set } from 'idb-keyval' -import type { PersistStorage } from 'zustand/middleware' -import type { State } from '../Porto.js' - -const store = - typeof indexedDB !== 'undefined' ? createStore('porto', 'store') : undefined - -export const idb = { - async getItem(name) { - const value = await get(name, store) - if (value === null) return null - return value - }, - async removeItem(name) { - await del(name, store) - }, - async setItem(name, value) { - await set(name, value, store) - }, -} satisfies PersistStorage diff --git a/src/core/internal/webauthn.ts b/src/core/internal/webauthn.ts deleted file mode 100644 index eba53d84..00000000 --- a/src/core/internal/webauthn.ts +++ /dev/null @@ -1,27 +0,0 @@ -export function touchWellknown(parameters: touchWellknown.Parameters) { - if (typeof window === 'undefined') return - - const { rpId } = parameters - const origin = `${window.location.protocol}//${window.location.hostname}` - const url = `https://${rpId}/.well-known/webauthn` - fetch(url) - .then((x) => x.json()) - .then((x) => { - if (x.origins.includes(origin)) return - fetch(url, { - method: 'PATCH', - body: JSON.stringify({ - origin: `${window.location.protocol}//${window.location.hostname}`, - }), - headers: { - 'Content-Type': 'application/json', - }, - }) - }) -} - -export declare namespace touchWellknown { - export type Parameters = { - rpId: string - } -} diff --git a/src/index.ts b/src/index.ts index 01aa6597..e597608a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,4 @@ export * as Chains from './core/Chains.js' +export * as Implementation from './core/Implementation.js' export * as Porto from './core/Porto.js' +export * as Storage from './core/Storage.js' diff --git a/src/package.json b/src/package.json index 1148454a..5a359b03 100644 --- a/src/package.json +++ b/src/package.json @@ -17,14 +17,14 @@ "dependencies": { "idb-keyval": "^6.2.1", "mipd": "^0.0.7", - "ox": "^0.2.2", + "ox": "^0.6.0", "zustand": "^5.0.1" }, "peerDependencies": { "@tanstack/react-query": ">=5.59.0", "react": ">=18", "typescript": ">=5.4.0", - "viem": ">=2.21.51", + "viem": ">=2.21.60", "wagmi": ">=2.0.0" }, "peerDependenciesMeta": { diff --git a/src/wagmi/Actions.ts b/src/wagmi/Actions.ts index a6af4ba0..e5b5d8cf 100644 --- a/src/wagmi/Actions.ts +++ b/src/wagmi/Actions.ts @@ -1,8 +1,8 @@ export { + authorizeKey, connect, createAccount, disconnect, - grantSession, - importAccount, - sessions, + upgradeAccount, + keys, } from './internal/core.js' diff --git a/src/wagmi/Hooks.ts b/src/wagmi/Hooks.ts index 91ad1474..7fd8dad7 100644 --- a/src/wagmi/Hooks.ts +++ b/src/wagmi/Hooks.ts @@ -1,8 +1,8 @@ export { + useAuthorizeKey, useConnect, useCreateAccount, useDisconnect, - useGrantSession, - useImportAccount, - useSessions, + useUpgradeAccount, + useKeys, } from './internal/react.js' diff --git a/src/wagmi/Query.ts b/src/wagmi/Query.ts index eee61d89..4cc43f55 100644 --- a/src/wagmi/Query.ts +++ b/src/wagmi/Query.ts @@ -1 +1 @@ -export { sessionsQueryKey } from './internal/query.js' +export { keysQueryKey } from './internal/query.js' diff --git a/src/wagmi/internal/core.ts b/src/wagmi/internal/core.ts index f11cd5e8..57cce557 100644 --- a/src/wagmi/internal/core.ts +++ b/src/wagmi/internal/core.ts @@ -4,7 +4,6 @@ import { type Chain, ChainMismatchError, type EIP1193Provider, - type Hex, type PrivateKeyAccount, } from 'viem' import { @@ -22,12 +21,52 @@ import { } from 'wagmi/actions' import type { + AuthorizeKeyParameters, + AuthorizeKeyReturnType, CreateAccountParameters, - GrantSessionParameters, + GetKeysReturnType, + RevokeKeyParameters, Schema, } from '../../core/internal/rpcSchema.js' import type { ChainIdParameter, ConnectorParameter } from './types.js' +export async function authorizeKey( + config: config, + parameters: authorizeKey.Parameters, +): Promise { + const { address, chainId, connector, key } = parameters + + const client = await getConnectorClient(config, { + account: address, + chainId, + connector, + }) + + const method = 'experimental_authorizeKey' + type method = typeof method + return client.request<{ + Method: method + Parameters?: RpcSchema.ExtractParams + ReturnType: RpcSchema.ExtractReturnType + }>({ + method, + params: [{ address, key }], + }) +} + +export declare namespace authorizeKey { + type Parameters = ChainIdParameter & + ConnectorParameter & { + address?: Address | undefined + key?: AuthorizeKeyParameters['key'] | undefined + } + + type ReturnType = AuthorizeKeyReturnType + + // TODO: Exhaustive ErrorType + type ErrorType = BaseErrorType +} + export async function connect( config: config, parameters: connect.Parameters, @@ -62,8 +101,8 @@ export async function connect( | undefined if (!provider) throw new ProviderNotFoundError() - const { createAccount, grantSession } = parameters - const method = 'experimental_connect' + const { authorizeKey, createAccount } = parameters + const method = 'wallet_connect' type method = typeof method await provider.request<{ Method: method @@ -71,7 +110,7 @@ export async function connect( ReturnType: RpcSchema.ExtractReturnType }>({ method, - params: [{ capabilities: { createAccount, grantSession } }], + params: [{ capabilities: { authorizeKey, createAccount } }], }) // we already connected, but call `connector.connect` so connector even listeners are set up const data = await connector.connect({ @@ -109,9 +148,9 @@ export async function connect( export declare namespace connect { type Parameters = ChainIdParameter & { + authorizeKey?: AuthorizeKeyParameters['key'] | undefined connector: Connector | CreateConnectorFn createAccount?: boolean | CreateAccountParameters | undefined - grantSession?: boolean | GrantSessionParameters | undefined } type ReturnType = ConnectReturnType @@ -229,7 +268,7 @@ export async function disconnect( await wagmi_disconnect(config, parameters) - const method = 'experimental_disconnect' + const method = 'wallet_disconnect' type method = typeof method await provider?.request<{ Method: method @@ -248,11 +287,11 @@ export declare namespace disconnect { type ErrorType = BaseErrorType } -export async function grantSession( +export async function keys( config: config, - parameters: grantSession.Parameters, -): Promise { - const { address, chainId, connector, expiry } = parameters + parameters: keys.Parameters, +): Promise { + const { address, chainId, connector } = parameters const client = await getConnectorClient(config, { account: address, @@ -260,7 +299,7 @@ export async function grantSession( connector, }) - const method = 'experimental_grantSession' + const method = 'experimental_keys' type method = typeof method return client.request<{ Method: method @@ -268,30 +307,61 @@ export async function grantSession( ReturnType: RpcSchema.ExtractReturnType }>({ method, - params: [{ address, expiry }], + params: [{ address }], }) } -export declare namespace grantSession { +export declare namespace keys { type Parameters = ChainIdParameter & ConnectorParameter & { address?: Address | undefined - expiry?: number | undefined } - type ReturnType = { - expiry: number - id: Hex - } + type ReturnType = GetKeysReturnType + + // TODO: Exhaustive ErrorType + type ErrorType = BaseErrorType +} + +export async function revokeKey( + config: config, + parameters: revokeKey.Parameters, +) { + const { address, chainId, connector, publicKey } = parameters + + const client = await getConnectorClient(config, { + account: address, + chainId, + connector, + }) + + const method = 'experimental_revokeKey' + type method = typeof method + return client.request<{ + Method: method + Parameters?: RpcSchema.ExtractParams + ReturnType: RpcSchema.ExtractReturnType + }>({ + method, + params: [{ address, publicKey }], + }) +} + +export declare namespace revokeKey { + type Parameters = ChainIdParameter & + ConnectorParameter & { + address?: Address | undefined + publicKey: RevokeKeyParameters['publicKey'] + } // TODO: Exhaustive ErrorType type ErrorType = BaseErrorType } -export async function importAccount( +export async function upgradeAccount( config: config, - parameters: importAccount.Parameters, -): Promise { + parameters: upgradeAccount.Parameters, +): Promise { // "Register" connector if not already created let connector: Connector if (typeof parameters.connector === 'function') { @@ -322,26 +392,26 @@ export async function importAccount( | undefined if (!provider) throw new ProviderNotFoundError() - const { account, grantSession, label } = parameters + const { account, authorizeKey, label } = parameters - const experimental_prepareImportAccount = - 'experimental_prepareImportAccount' - type experimental_prepareImportAccount = - typeof experimental_prepareImportAccount + const experimental_prepareCreateAccount = + 'experimental_prepareCreateAccount' + type experimental_prepareCreateAccount = + typeof experimental_prepareCreateAccount const { context, signPayloads } = await provider.request<{ - Method: experimental_prepareImportAccount + Method: experimental_prepareCreateAccount Parameters?: RpcSchema.ExtractParams< Schema, - experimental_prepareImportAccount + experimental_prepareCreateAccount > ReturnType: RpcSchema.ExtractReturnType< Schema, - experimental_prepareImportAccount + experimental_prepareCreateAccount > }>({ - method: experimental_prepareImportAccount, + method: experimental_prepareCreateAccount, params: [ - { address: account.address, capabilities: { grantSession }, label }, + { address: account.address, capabilities: { authorizeKey }, label }, ], }) @@ -349,17 +419,17 @@ export async function importAccount( signPayloads.map((hash) => account.sign({ hash })), ) - const experimental_importAccount = 'experimental_importAccount' - type experimental_importAccount = typeof experimental_importAccount + const experimental_createAccount = 'experimental_createAccount' + type experimental_createAccount = typeof experimental_createAccount await provider.request<{ - Method: experimental_importAccount - Parameters?: RpcSchema.ExtractParams + Method: experimental_createAccount + Parameters?: RpcSchema.ExtractParams ReturnType: RpcSchema.ExtractReturnType< Schema, - experimental_importAccount + experimental_createAccount > }>({ - method: experimental_importAccount, + method: experimental_createAccount, params: [{ context, signatures }], }) @@ -397,11 +467,11 @@ export async function importAccount( } } -export declare namespace importAccount { +export declare namespace upgradeAccount { type Parameters = ChainIdParameter & { + authorizeKey?: AuthorizeKeyParameters['key'] | undefined account: PrivateKeyAccount connector: Connector | CreateConnectorFn - grantSession?: boolean | GrantSessionParameters | undefined label?: string | undefined } @@ -410,42 +480,3 @@ export declare namespace importAccount { // TODO: Exhaustive ErrorType type ErrorType = BaseErrorType } - -export async function sessions( - config: config, - parameters: sessions.Parameters, -): Promise { - const { address, chainId, connector } = parameters - - const client = await getConnectorClient(config, { - account: address, - chainId, - connector, - }) - - const method = 'experimental_sessions' - type method = typeof method - return client.request<{ - Method: method - Parameters?: RpcSchema.ExtractParams - ReturnType: RpcSchema.ExtractReturnType - }>({ - method, - params: [{ address }], - }) -} - -export declare namespace sessions { - type Parameters = ChainIdParameter & - ConnectorParameter & { - address?: Address | undefined - } - - type ReturnType = readonly { - expiry: number - id: Hex - }[] - - // TODO: Exhaustive ErrorType - type ErrorType = BaseErrorType -} diff --git a/src/wagmi/internal/query.ts b/src/wagmi/internal/query.ts index cf6e59bd..c9d60d9d 100644 --- a/src/wagmi/internal/query.ts +++ b/src/wagmi/internal/query.ts @@ -1,20 +1,18 @@ import type { Config } from 'wagmi' -import type { sessions } from './core.js' +import type { keys } from './core.js' import { filterQueryOptions } from './utils.js' -export function sessionsQueryKey( - options: sessions.Parameters = {}, +export function keysQueryKey( + options: keys.Parameters = {}, ) { const { connector, ...parameters } = options return [ - 'sessions', + 'keys', { ...filterQueryOptions(parameters), connectorUid: connector?.uid }, ] as const } -export declare namespace sessionsQueryKey { - type Value = ReturnType< - typeof sessionsQueryKey - > +export declare namespace keysQueryKey { + type Value = ReturnType> } diff --git a/src/wagmi/internal/react.ts b/src/wagmi/internal/react.ts index abf517e9..bf3c0e1e 100644 --- a/src/wagmi/internal/react.ts +++ b/src/wagmi/internal/react.ts @@ -23,16 +23,60 @@ import type { } from 'wagmi/query' import { + authorizeKey, connect, createAccount, disconnect, - grantSession, - importAccount, - sessions, + keys, + revokeKey, + upgradeAccount, } from './core.js' -import { sessionsQueryKey } from './query.js' +import { keysQueryKey } from './query.js' import type { ConfigParameter } from './types.js' +export function useAuthorizeKey< + config extends Config = ResolvedRegister['config'], + context = unknown, +>( + parameters: useAuthorizeKey.Parameters = {}, +): useAuthorizeKey.ReturnType { + const { mutation } = parameters + const config = useConfig(parameters) + return useMutation({ + ...mutation, + async mutationFn(variables) { + return authorizeKey(config, variables) + }, + mutationKey: ['authorizeKey'], + }) +} + +export declare namespace useAuthorizeKey { + type Parameters< + config extends Config = Config, + context = unknown, + > = ConfigParameter & { + mutation?: + | UseMutationParameters< + authorizeKey.ReturnType, + authorizeKey.ErrorType, + authorizeKey.Parameters, + context + > + | undefined + } + + type ReturnType< + config extends Config = Config, + context = unknown, + > = UseMutationResult< + authorizeKey.ReturnType, + authorizeKey.ErrorType, + authorizeKey.Parameters, + context + > +} + export function useConnect< config extends Config = ResolvedRegister['config'], context = unknown, @@ -156,98 +200,12 @@ export declare namespace useDisconnect { > } -export function useGrantSession< - config extends Config = ResolvedRegister['config'], - context = unknown, ->( - parameters: useGrantSession.Parameters = {}, -): useGrantSession.ReturnType { - const { mutation } = parameters - const config = useConfig(parameters) - return useMutation({ - ...mutation, - async mutationFn(variables) { - return grantSession(config, variables) - }, - mutationKey: ['grantSession'], - }) -} - -export declare namespace useGrantSession { - type Parameters< - config extends Config = Config, - context = unknown, - > = ConfigParameter & { - mutation?: - | UseMutationParameters< - grantSession.ReturnType, - grantSession.ErrorType, - grantSession.Parameters, - context - > - | undefined - } - - type ReturnType< - config extends Config = Config, - context = unknown, - > = UseMutationResult< - grantSession.ReturnType, - grantSession.ErrorType, - grantSession.Parameters, - context - > -} - -export function useImportAccount< +export function useKeys< config extends Config = ResolvedRegister['config'], - context = unknown, + selectData = keys.ReturnType, >( - parameters: useImportAccount.Parameters = {}, -): useImportAccount.ReturnType { - const { mutation } = parameters - const config = useConfig(parameters) - return useMutation({ - ...mutation, - async mutationFn(variables) { - return importAccount(config as Config, variables) - }, - mutationKey: ['importAccount'], - }) -} - -export declare namespace useImportAccount { - type Parameters< - config extends Config = Config, - context = unknown, - > = ConfigParameter & { - mutation?: - | UseMutationParameters< - importAccount.ReturnType, - importAccount.ErrorType, - importAccount.Parameters, - context - > - | undefined - } - - type ReturnType< - config extends Config = Config, - context = unknown, - > = UseMutationResult< - importAccount.ReturnType, - importAccount.ErrorType, - importAccount.Parameters, - context - > -} - -export function useSessions< - config extends Config = ResolvedRegister['config'], - selectData = sessions.ReturnType, ->( - parameters: useSessions.Parameters = {}, -): useSessions.ReturnType { + parameters: useKeys.Parameters = {}, +): useKeys.ReturnType { const { query = {}, ...rest } = parameters const config = useConfig(rest) @@ -263,7 +221,7 @@ export function useSessions< ) const queryKey = useMemo( () => - sessionsQueryKey({ + keysQueryKey({ address, chainId: parameters.chainId ?? chainId, connector: activeConnector, @@ -279,7 +237,7 @@ export function useSessions< provider.current ??= (await activeConnector.getProvider?.()) as EIP1193Provider provider.current?.on('message', (event) => { - if (event.type !== 'sessionsChanged') return + if (event.type !== 'keysChanged') return queryClient.setQueryData(queryKey, event.data) }) })() @@ -297,7 +255,7 @@ export function useSessions< )[1] provider.current ??= (await activeConnector.getProvider()) as EIP1193Provider - return await sessions(config, { + return await keys(config, { ...options, connector: activeConnector, }) @@ -307,27 +265,113 @@ export function useSessions< }) as never } -export declare namespace useSessions { +export declare namespace useKeys { type Parameters< config extends Config = Config, - selectData = sessions.ReturnType, - > = sessions.Parameters & + selectData = keys.ReturnType, + > = keys.Parameters & ConfigParameter & { query?: | Omit< UseQueryParameters< - sessions.ReturnType, - sessions.ErrorType, + keys.ReturnType, + keys.ErrorType, selectData, - sessionsQueryKey.Value + keysQueryKey.Value >, 'gcTime' | 'staleTime' > | undefined } - type ReturnType = UseQueryReturnType< + type ReturnType = UseQueryReturnType< selectData, - sessions.ErrorType + keys.ErrorType + > +} + +export function useRevokeKey< + config extends Config = ResolvedRegister['config'], + context = unknown, +>( + parameters: useRevokeKey.Parameters = {}, +): useRevokeKey.ReturnType { + const { mutation } = parameters + const config = useConfig(parameters) + return useMutation({ + ...mutation, + async mutationFn(variables) { + return revokeKey(config, variables) + }, + mutationKey: ['revokeKey'], + }) +} + +export declare namespace useRevokeKey { + type Parameters< + config extends Config = Config, + context = unknown, + > = ConfigParameter & { + mutation?: + | UseMutationParameters< + undefined, + revokeKey.ErrorType, + revokeKey.Parameters, + context + > + | undefined + } + + type ReturnType< + config extends Config = Config, + context = unknown, + > = UseMutationResult< + undefined, + revokeKey.ErrorType, + revokeKey.Parameters, + context + > +} + +export function useUpgradeAccount< + config extends Config = ResolvedRegister['config'], + context = unknown, +>( + parameters: useUpgradeAccount.Parameters = {}, +): useUpgradeAccount.ReturnType { + const { mutation } = parameters + const config = useConfig(parameters) + return useMutation({ + ...mutation, + async mutationFn(variables) { + return upgradeAccount(config as Config, variables) + }, + mutationKey: ['upgradeAccount'], + }) +} + +export declare namespace useUpgradeAccount { + type Parameters< + config extends Config = Config, + context = unknown, + > = ConfigParameter & { + mutation?: + | UseMutationParameters< + upgradeAccount.ReturnType, + upgradeAccount.ErrorType, + upgradeAccount.Parameters, + context + > + | undefined + } + + type ReturnType< + config extends Config = Config, + context = unknown, + > = UseMutationResult< + upgradeAccount.ReturnType, + upgradeAccount.ErrorType, + upgradeAccount.Parameters, + context > } diff --git a/test/globalSetup.ts b/test/globalSetup.ts new file mode 100644 index 00000000..fbbd117c --- /dev/null +++ b/test/globalSetup.ts @@ -0,0 +1,11 @@ +import * as instances from './src/anvil.js' + +export default async function () { + // Set up Anvil instances + const shutdown = await Promise.all( + Object.values(instances).map((instance) => instance.start()), + ) + + // Teardown + return () => Promise.all(shutdown.map((fn) => fn())) +} diff --git a/test/setup.ts b/test/setup.ts new file mode 100644 index 00000000..53b17f92 --- /dev/null +++ b/test/setup.ts @@ -0,0 +1,11 @@ +import { afterAll, vi } from 'vitest' +import * as instances from './src/anvil.js' + +afterAll(async () => { + vi.restoreAllMocks() + + // Reset the anvil instances to the same state it was in before the tests started. + await Promise.all( + Object.values(instances).map((instance) => instance.restart()), + ) +}) diff --git a/test/src/account.ts b/test/src/account.ts new file mode 100644 index 00000000..ba8c2524 --- /dev/null +++ b/test/src/account.ts @@ -0,0 +1,28 @@ +import { Secp256k1 } from 'ox' +import { type Client, parseEther } from 'viem' +import { setBalance } from 'viem/actions' + +import * as Account from '../../src/core/internal/account.js' +import type * as Key from '../../src/core/internal/key.js' + +export async function getAccount( + client: Client, + parameters: { + keys?: readonly Key.Key[] | undefined + } = {}, +) { + const { keys } = parameters + + const privateKey = Secp256k1.randomPrivateKey() + const account = Account.fromPrivateKey(privateKey, { keys }) + + await setBalance(client as any, { + address: account.address, + value: parseEther('10000'), + }) + + return { + account, + privateKey, + } +} diff --git a/test/src/anvil.json b/test/src/anvil.json new file mode 100644 index 00000000..66d650a7 --- /dev/null +++ b/test/src/anvil.json @@ -0,0 +1,117 @@ +{ + "block": { + "number": "0x0", + "coinbase": "0x0000000000000000000000000000000000000000", + "timestamp": "0x1", + "gas_limit": "0x1c9c380", + "basefee": "0x3b9aca00", + "difficulty": "0x0", + "prevrandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blob_excess_gas_and_price": { "excess_blob_gas": 0, "blob_gasprice": 1 } + }, + "accounts": { + "0x14dc79964da2c08b23698b3d3cc7ca32193d9955": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x15d34aaf54267db7d7c367839aaf71a00a2c6a65": { + "nonce": 12, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f": { + "nonce": 10, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc": { + "nonce": 45, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x4e59b44847b379578588920ca78fbf26c0b4956c": { + "nonce": 3731, + "balance": "0x0", + "code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3", + "storage": {} + }, + "0x70997970c51812dc3a010c7d01b50e0d17dc79c8": { + "nonce": 112, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x90f79bf6eb2c4f870365e785982e1f101e93b906": { + "nonce": 40, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x976ea74026e726554db657fa54763abd0c3a0aa9": { + "nonce": 12, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc": { + "nonce": 30, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0xa0ee7a142d267c1f36714e4a8f75612f20a79720": { + "nonce": 9, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266": { + "nonce": 663, + "balance": "0x21e19e0c9bab2400000", + "code": "0x", + "storage": {} + }, + "0x0ff027b63351364071425cf65d4fece75a8e17b8": { + "nonce": 0, + "balance": "0x21e19e0c9bab2400000", + "code": "0x6080604052600436106101c5575f3560e01c806384b0196e116100f6578063bf53096911610094578063e9ae5c5311610063578063e9ae5c53146105f9578063f0c60f1a1461060c578063fac750e014610620578063ff619c6b14610634576101cc565b8063bf5309691461054e578063cb4774c41461056d578063cebfe3361461058e578063d03c7914146105ad576101cc565b8063a2e2f930116100d0578063a2e2f930146104d2578063a840fe49146104f1578063b70e36f014610510578063b75c7dc61461052f576101cc565b806384b0196e1461045957806394430fa5146104805780639c42fb59146104a3576101cc565b80632f3f30c711610163578063515c9d6d1161013d578063515c9d6d146103cb57806360d2f33d146103eb5780636ae269cc1461041e5780636fd914541461043a576101cc565b80632f3f30c71461037857806335058501146103925780634223b5c2146103ac576101cc565b8063136a12f71161019f578063136a12f7146102ab5780631626ba7e146102cc5780631a912f3e1461030457806320606b7014610345576101cc565b80630cef73b41461020557806311a86fd61461024057806312aaac701461027f576101cc565b366101cc57005b5f3560e01c63bc197c81811463f23a6e6182141763150b7a02821417156101f757806020526020603cf35b50633c10b94e5f526004601cfd5b348015610210575f80fd5b5061022461021f366004612068565b610653565b6040805192151583526020830191909152015b60405180910390f35b34801561024b575f80fd5b5061026773323232323232323232323232323232323232323281565b6040516001600160a01b039091168152602001610237565b34801561028a575f80fd5b5061029e6102993660046120b0565b61066d565b6040516102379190612109565b3480156102b6575f80fd5b506102ca6102c536600461218b565b61075d565b005b3480156102d7575f80fd5b506102eb6102e6366004612068565b610868565b6040516001600160e01b03199091168152602001610237565b34801561030f575f80fd5b506103377f84fa2cf05cd88e992eae77e851af68a4ee278dcff6ef504e487a55b3baadfbe581565b604051908152602001610237565b348015610350575f80fd5b506103377f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f81565b348015610383575f80fd5b506102eb630707070760e51b81565b34801561039d575f80fd5b506102eb631919191960e11b81565b3480156103b7575f80fd5b5061029e6103c63660046120b0565b61089b565b3480156103d6575f80fd5b506103375f8051602061266f83398151915281565b3480156103f6575f80fd5b506103377fe530e62dece51c9bec26701907051ddc8420a62f028096eeb58263193e84e04981565b348015610429575f80fd5b50686d3d4e7fb92a52381554610337565b348015610445575f80fd5b506103376104543660046121e5565b6108d9565b348015610464575f80fd5b5061046d610a2c565b604051610237979695949392919061225a565b34801561048b575f80fd5b506102676fac830f1181f6aab6862e71edc248941c81565b3480156104ae575f80fd5b506104c26104bd3660046122f0565b610a8d565b6040519015158152602001610237565b3480156104dd575f80fd5b506104c26104ec3660046120b0565b610ad1565b3480156104fc575f80fd5b5061033761050b3660046123e2565b610af9565b34801561051b575f80fd5b506102ca61052a3660046120b0565b610b32565b34801561053a575f80fd5b506102ca6105493660046120b0565b610b5d565b348015610559575f80fd5b506102ca610568366004612491565b610bb2565b348015610578575f80fd5b50610581610c56565b60405161023791906124d0565b348015610599575f80fd5b506103376105a83660046123e2565b610c6f565b3480156105b8575f80fd5b506104c26105c73660046120b0565b690100000000007821000160b09190911c69ffff00000000ffffffff1690811460011b600160481b9190911417151590565b6102ca610607366004612068565b610cd7565b348015610617575f80fd5b50610337610d60565b34801561062b575f80fd5b50610337610df3565b34801561063f575f80fd5b506104c261064e3660046124e2565b610e06565b5f806106618585855f611099565b91509150935093915050565b604080516080810182525f80825260208201819052918101919091526060808201525f828152686d3d4e7fb92a523817602052604081206106ad906112bb565b905080515f036106d05760405163395ed8c160e21b815260040160405180910390fd5b8051600619015f6106e48383016020015190565b60d881901c855260c881901c915060d01c60ff166002811115610709576107096120c7565b8460200190600281111561071f5761071f6120c7565b90816002811115610732576107326120c7565b90525060ff81161515604085015261074f83838151811082025290565b606085015250919392505050565b33301461077c576040516282b42960e81b815260040160405180910390fd5b8361079a57604051638707510560e01b815260040160405180910390fd5b6107a48383611321565b156107cf576107b28461134b565b6107cf576040516303a6f8c760e21b815260040160405180910390fd5b5f82815260188490526004859052603881208152683c149ebf7b8e6c5e226020818152604092839020805485151560ff19909116811790915583518881526001600160a01b038816928101929092526001600160e01b03198616828501526060820152915190917f7eb91b8ac56c0864a4e4f5598082d140d04bed1a4dd62a41d605be2430c494e1919081900360800190a15050505050565b5f806108778585856001611099565b509050806108895763ffffffff61088f565b631626ba7e5b60e01b95945050505050565b604080516080810182525f80825260208201819052918101919091526060808201526108d3610299686d3d4e7fb92a5238168461135f565b92915050565b5f806108f58460408051828152600190920160051b8201905290565b90505f5b848110156109b857368686838181106109145761091461253a565b9050602002810190610926919061254e565b90506109ae8261099f7f84fa2cf05cd88e992eae77e851af68a4ee278dcff6ef504e487a55b3baadfbe561095d602086018661256c565b6001600160a01b0316602086013561098061097b6040890189612587565b6113a8565b6040805194855260208501939093529183015260608201526080902090565b600190910160051b8501528390565b50506001016108f9565b50610a23610a1e7fe530e62dece51c9bec26701907051ddc8420a62f028096eeb58263193e84e0496109f284805160051b60209091012090565b686d3d4e7fb92a5238155460408051938452602084019290925290820187905260608201526080902090565b6113b9565b95945050505050565b600f60f81b6060805f808083610a7b604080518082018252600a8152692232b632b3b0ba34b7b760b11b60208083019190915282518084019093526005835264302e302e3160d81b9083015291565b97989097965046955030945091925090565b5f336fac830f1181f6aab6862e71edc248941c14610abd576040516282b42960e81b815260040160405180910390fd5b610ac88333846114cf565b50600192915050565b600881901c5f908152686d3d4e7fb92a523814602052604081205460ff83161c6001166108d3565b5f6108d382602001516002811115610b1357610b136120c7565b60ff168360600151805190602001205f1c5f9182526020526040902090565b333014610b51576040516282b42960e81b815260040160405180910390fd5b610b5a816114f7565b50565b333014610b7c576040516282b42960e81b815260040160405180910390fd5b610b8581611553565b60405181907fe5af7daed5ab2a2dc5f98d53619f05089c0c14d11a6621f6b906a2366c9a7ab3905f90a250565b333014610bd1576040516282b42960e81b815260040160405180910390fd5b610c1982828080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92019190915250610c1392506112ae915050565b906115a7565b7faec6ef4baadc9acbdf52442522dfffda03abe29adba8d4af611bcef4cbe0c9ad8282604051610c4a9291906125ca565b60405180910390a15050565b6060610c6a686d3d4e7fb92a5238136112bb565b905090565b5f333014610c8f576040516282b42960e81b815260040160405180910390fd5b610c98826115ff565b9050807f3d3a48be5a98628ecf98a6201185102da78bbab8f63a4b2d6b9eef354f5131f583604051610cca9190612109565b60405180910390a2919050565b690100000000007821000160b084901c69ffff00000000ffffffff1690811460011b600160481b9190911417365f818184610d1957637f1812755f526004601cfd5b5085358087016020810194503592505f90604011600286141115610d47575050602080860135860190810190355b610d5688888887878787611674565b5050505050505050565b5f333014610d80576040516282b42960e81b815260040160405180910390fd5b50686d3d4e7fb92a523815805460408051828152426020808301919091523382840152606090912063ffffffff169092019283905580518381529051686d3d4e7fb92a523813927f7328006ebae4716b8db9514af69091e0ad74bec61dfdc256ed5446b234aa5424928290030190a15090565b5f610c6a686d3d4e7fb92a52381661178b565b5f84610e1457506001611091565b683c149ebf7b8e6c5e22631919191960e11b60048410610e32575083355b83610e415750630707070760e51b5b610e4b8682611321565b15610e6757610e598761134b565b610e67575f92505050611091565b5f818152601887905260048890526038812081526020839052604090205460ff1615610e9857600192505050611091565b5f81815273323232323232323232323232323232323232323260185260048890526038812081526020839052604090205460ff1615610edc57600192505050611091565b5f81815260188790525f8051602061266f8339815191526004526038812081526020839052604090205460ff1615610f1957600192505050611091565b5f8181527332323232323232323232323232323232323232326018525f8051602061266f8339815191526004526038812081526020839052604090205460ff1615610f6957600192505050611091565b631919191960e11b5f908152601887905260048890526038812081526020839052604090205460ff1615610fa257600192505050611091565b631919191960e11b5f90815273323232323232323232323232323232323232323260185260048890526038812081526020839052604090205460ff1615610fee57600192505050611091565b631919191960e11b5f90815260188790525f8051602061266f8339815191526004526038812081526020839052604090205460ff161561103357600192505050611091565b631919191960e11b5f9081527332323232323232323232323232323232323232326018525f8051602061266f8339815191526004526038812081526020839052604090205460ff161561108b57600192505050611091565b5f925050505b949350505050565b5f80604184146040851417156110c957306110b58787876117d7565b6001600160a01b03161491505f90506112a5565b60218410156110dc57505f9050806112a5565b506020198381018481118186180281189486019182013591601f19013560ff161561110d5761110a8761185f565b96505b505f6111188261066d565b6040810151909150158415151615611133575f9250506112a5565b805164ffffffffff164281109015151615611151575f9250506112a5565b5f81602001516002811115611168576111686120c7565b036111c3575f80603f8711883581029060208a013502915091505f806111a7856060015180516020820151604090920151603f90911191820292910290565b915091506111b88b8585858561187d565b9650505050506112a3565b6001816020015160028111156111db576111db6120c7565b0361126057606081810151805160208083015160409384015184518084018e9052855180820385018152601f8d018590049094028101870186529485018b8152603f9490941091820295910293611257935f92611250928e918e918291018382808284375f9201919091525061190f92505050565b85856119f3565b945050506112a3565b600281602001516002811115611278576112786120c7565b036112a3576112a0816060015180602001905181019061129891906125f8565b888888611b12565b92505b505b94509492505050565b686d3d4e7fb92a52381390565b60405181546020820190600881901c5f8260ff8417146112e957505080825260ff8116601f8082111561130b575b855f5260205f205b8160051c810154828601526020820191508282106112f157505b508084525f920191825250602001604052919050565b5f63e9ae5c5360e01b6001600160e01b0319831614306001600160a01b03851614165b9392505050565b5f6113558261066d565b6040015192915050565b6318fb58646004525f8281526024902081015468fbb67fda52d4bfb8bf811415026113898361178b565b82106108d357604051634e23d03560e01b815260040160405180910390fd5b5f8183604051375060405120919050565b7f06012d2aedbb397c1914278c3206cec06702f79ceb18361e1ddb2fcc068c324f7f0000000000000000000000005fbdb2315678afecb367f032d93f642f64180aa330147f0000000000000000000000000000000000000000000000000000000000007a694614166114ac5750604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f81527f6c157b110f3bc0280dba8ae624dab3ba623d9bab8692057c96d59c9188d52cb260208201527fae209a0b48f21c054280f2455d32cf309387644879d9acbd8ffc1991638118859181019190915246606082015230608082015260a090205b6719010000000000005f5280601a5281603a52604260182090505f603a52919050565b6001600160a01b0383166114ec576114e78282611be0565b505050565b6114e7838383611bf9565b600881901c5f908152686d3d4e7fb92a523814602052604090208054600160ff84161b1790556040518181527f4d9dbebf1d909894d9c26fe228c27cec643b2cb490124e5b658f4edd203c20c19060200160405180910390a150565b5f818152686d3d4e7fb92a5238176020526040812055686d3d4e7fb92a523813611586686d3d4e7fb92a52381683611c43565b6115a35760405163395ed8c160e21b815260040160405180910390fd5b5050565b80518060081b60ff175f60fe83116115d0575050601f8281015160081b821790808311156115f7575b60208401855f5260205f205b828201518360051c8201556020830192508483106115dc5750505b509092555050565b5f61160982610af9565b90505f686d3d4e7fb92a5238136060840151845160208087015160408089015190519596506116609561163e95949301612613565b60408051601f198184030181529181525f8581526004850160205220906115a7565b61166d6003820183611d4f565b5050919050565b6fac830f1181f6aab6862e71edc248941b1933016116cc5760408110156116ae576040516355fe73fd60e11b815260040160405180910390fd5b6116b88235611e6a565b6116c784846020850135611e91565b611782565b806116fb573330146116f0576040516282b42960e81b815260040160405180910390fd5b6116c784845f611e91565b602081101561171d576040516355fe73fd60e11b815260040160405180910390fd5b813561172881611e6a565b5f806117526117388888866108d9565b602080871081881802188088019080880390881102610653565b9150915081611773576040516282b42960e81b815260040160405180910390fd5b61177e878783611e91565b5050505b50505050505050565b6318fb58646004525f818152602481208019548060011c92508061166d5781545f93501561166d5760019250828201541561166d5760029250828201541561166d575060039392505050565b5f60405182604081146117f25760418114611819575061184a565b60208581013560ff81901c601b0190915285356040526001600160ff1b031660605261182a565b60408501355f1a6020526040856040375b50845f526020600160805f60015afa5191505f606052806040523d611857575b638baa579f5f526004601cfd5b509392505050565b5f815f526020600160205f60025afa5190503d61187857fe5b919050565b5f6040518681528560208201528460408201528360608201528260808201525f805260205f60a0836101005afa503d6118da5760203d60a0836dcb83347beb24c695bbb85dbe99b75afa503d6118da5763d0d5039b3d526004601cfd5b505f516001147f7fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a8851110905095945050505050565b6040805160c0810182526060808252602082018190525f92820183905281018290526080810182905260a0810191909152815160c081106119ed5760208301818101818251018281108260c0830111171561196c575050506119ed565b808151019250806020820151018181108382111782851084861117171561199657505050506119ed565b82815160208301011183855160208701011117156119b757505050506119ed565b8386528060208701525060408101516040860152606081015160608601526080810151608086015260a081015160a08601525050505b50919050565b5f805f611a0288600180611edf565b905060208601518051602082019150604088015160608901518451600d81016c1131b430b63632b733b2911d1160991b60981c8752848482011060228286890101515f1a14168160138901208286890120141685846014011085851760801c1074113a3cb832911d113bb2b130baba34371733b2ba1160591b60581c8589015160581c14161698505080865250505087515189151560021b600117808160218c5101511614602083118816169650508515611ae657602089510181810180516020600160208601856020868a8c60025afa60011b5afa51915295503d9050611ae657fe5b5050508215611b0757611b048287608001518860a00151888861187d565b92505b505095945050505050565b5f6001600160a01b03851615611091576040518260408114611b3c5760418114611b635750611b98565b60208581013560ff81901c601b0190915285356040526001600160ff1b0316606052611b74565b60408501355f1a6020526040856040375b50845f526020600160805f60015afa5180871860601b3d119250505f606052806040525b81611bd757631626ba7e60e01b80825285600483015260248201604081528460448401528486606485013760208160648701858b5afa90519091141691505b50949350505050565b5f385f3884865af16115a35763b12d13eb5f526004601cfd5b816014528060345263a9059cbb60601b5f5260205f604460105f875af18060015f511416611c3957803d853b151710611c39576390b8ec185f526004601cfd5b505f603452505050565b6318fb58646004525f8281526024812068fbb67fda52d4bfb8bf8303611c705763f5a267f15f526004601cfd5b82611c825768fbb67fda52d4bfb8bf92505b80195480611ceb576001925083825403611caf5760018201805483556002830180549091555f9055611d47565b83600183015403611ccd5760028201805460018401555f9055611d47565b83600283015403611ce3575f6002830155611d47565b5f9250611d47565b81602052835f5260405f20805480611d04575050611d47565b60018360011c039250826001820314611d33578284015480600183038601555f84860155805f52508060405f20555b5060018260011b178319555f815550600192505b505092915050565b6318fb58646004525f8281526024812068fbb67fda52d4bfb8bf8303611d7c5763f5a267f15f526004601cfd5b82611d8e5768fbb67fda52d4bfb8bf92505b8019548160205280611e3257815480611dae578483556001935050611d47565b848103611dbb5750611d47565b600183015480611dd657856001850155600194505050611d47565b858103611de4575050611d47565b600284015480611e005786600286015560019550505050611d47565b868103611e0f57505050611d47565b5f9283526040808420600190559183528183206002905582529020600390555060075b835f5260405f208054611e6157600191821c8381018690558083019182905590821b8217831955909250611d47565b50505092915050565b611e7381610ad1565b15610b5157604051633ab3447f60e11b815260040160405180910390fd5b600582901b5f5b818114611ed8576020818101918601358601803580153002179181810135916040810135019081019035611ecf848484848b611fd0565b50505050611e98565b5050505050565b606083518015611857576003600282010460021b60405192507f4142434445464748494a4b4c4d4e4f505152535455565758595a616263646566601f526106708515027f6768696a6b6c6d6e6f707172737475767778797a303132333435363738392d5f18603f526020830181810183886020010180515f82525b60038a0199508951603f8160121c16515f53603f81600c1c1651600153603f8160061c1651600253603f811651600353505f518452600484019350828410611f5a579052602001604052613d3d60f01b60038406600204808303919091525f861515909102918290035290038252509392505050565b611fdc81868585610e06565b611ff8576040516282b42960e81b815260040160405180910390fd5b611ed88585858585604051828482375f388483888a5af161201b573d5f823e3d81fd5b505050505050565b5f8083601f840112612033575f80fd5b50813567ffffffffffffffff81111561204a575f80fd5b602083019150836020828501011115612061575f80fd5b9250929050565b5f805f6040848603121561207a575f80fd5b83359250602084013567ffffffffffffffff811115612097575f80fd5b6120a386828701612023565b9497909650939450505050565b5f602082840312156120c0575f80fd5b5035919050565b634e487b7160e01b5f52602160045260245ffd5b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b6020815264ffffffffff82511660208201525f60208301516003811061213d57634e487b7160e01b5f52602160045260245ffd5b80604084015250604083015115156060830152606083015160808084015261109160a08401826120db565b6001600160a01b0381168114610b5a575f80fd5b80358015158114611878575f80fd5b5f805f806080858703121561219e575f80fd5b8435935060208501356121b081612168565b925060408501356001600160e01b0319811681146121cc575f80fd5b91506121da6060860161217c565b905092959194509250565b5f805f604084860312156121f7575f80fd5b833567ffffffffffffffff81111561220d575f80fd5b8401601f8101861361221d575f80fd5b803567ffffffffffffffff811115612233575f80fd5b8660208260051b8401011115612247575f80fd5b6020918201979096509401359392505050565b60ff60f81b8816815260e060208201525f61227860e08301896120db565b828103604084015261228a81896120db565b606084018890526001600160a01b038716608085015260a0840186905283810360c0850152845180825260208087019350909101905f5b818110156122df5783518352602093840193909201916001016122c1565b50909b9a5050505050505050505050565b5f8060408385031215612301575f80fd5b823561230c81612168565b946020939093013593505050565b634e487b7160e01b5f52604160045260245ffd5b6040516080810167ffffffffffffffff811182821017156123515761235161231a565b60405290565b5f82601f830112612366575f80fd5b813567ffffffffffffffff8111156123805761238061231a565b604051601f8201601f19908116603f0116810167ffffffffffffffff811182821017156123af576123af61231a565b6040528181528382016020018510156123c6575f80fd5b816020850160208301375f918101602001919091529392505050565b5f602082840312156123f2575f80fd5b813567ffffffffffffffff811115612408575f80fd5b820160808185031215612419575f80fd5b61242161232e565b813564ffffffffff81168114612435575f80fd5b8152602082013560038110612448575f80fd5b60208201526124596040830161217c565b6040820152606082013567ffffffffffffffff811115612477575f80fd5b61248386828501612357565b606083015250949350505050565b5f80602083850312156124a2575f80fd5b823567ffffffffffffffff8111156124b8575f80fd5b6124c485828601612023565b90969095509350505050565b602081525f61134460208301846120db565b5f805f80606085870312156124f5575f80fd5b84359350602085013561250781612168565b9250604085013567ffffffffffffffff811115612522575f80fd5b61252e87828801612023565b95989497509550505050565b634e487b7160e01b5f52603260045260245ffd5b5f8235605e19833603018112612562575f80fd5b9190910192915050565b5f6020828403121561257c575f80fd5b813561134481612168565b5f808335601e1984360301811261259c575f80fd5b83018035915067ffffffffffffffff8211156125b6575f80fd5b602001915036819003821315612061575f80fd5b60208152816020820152818360408301375f818301604090810191909152601f909201601f19160101919050565b5f60208284031215612608575f80fd5b815161134481612168565b5f85518060208801845e60d886901b6001600160d81b0319169083019081526003851061264e57634e487b7160e01b5f52602160045260245ffd5b60f894851b600582015292151590931b600683015250600701939250505056fe3232323232323232323232323232323232323232323232323232323232323232a2646970667358221220fedfc3184b2b333b20591e8e82d687d62f41e28fa92d349d78b640fb4d41df5764736f6c634300081a0033", + "storage": {} + } + }, + "best_block_number": "0x12f2974", + "blocks": [ + { + "header": { + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "miner": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x0", + "number": "0x0", + "gasLimit": "0x1c9c380", + "gasUsed": "0x0", + "timestamp": "0x67749feb", + "extraData": "0x", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "baseFeePerGas": "0x3b9aca00", + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "blobGasUsed": "0x0", + "excessBlobGas": "0x0", + "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "transactions": [], + "ommers": [] + } + ], + "transactions": [], + "historical_states": null +} diff --git a/test/src/anvil.ts b/test/src/anvil.ts new file mode 100644 index 00000000..c36baac8 --- /dev/null +++ b/test/src/anvil.ts @@ -0,0 +1,90 @@ +import { resolve } from 'node:path' +import { Provider, RpcTransport } from 'ox' +import { createServer } from 'prool' +import { type AnvilParameters, anvil } from 'prool/instances' +import { + http, + type TransactionRequest, + createClient, + formatTransaction, +} from 'viem' +import { privateKeyToAccount } from 'viem/accounts' +import { prepareTransactionRequest, signTransaction } from 'viem/actions' + +export const anvilMainnet = defineAnvil({ + forkUrl: getEnv('VITE_ANVIL_FORK_URL', 'https://eth.merkle.io'), + forkBlockNumber: 19868020n, + port: 8545, + loadState: resolve(import.meta.dirname, 'anvil.json'), +}) + +///////////////////////////////////////////////////////////////// +// Utilities +///////////////////////////////////////////////////////////////// + +function getEnv(key: string, fallback: string): string { + if (typeof process.env[key] === 'string') return process.env[key] as string + console.warn( + `\`process.env.${key}\` not found. Falling back to \`${fallback}\`.`, + ) + return fallback +} + +function defineAnvil(parameters: AnvilParameters) { + const { port } = parameters + const poolId = + Number(process.env.VITEST_POOL_ID ?? 1) * + Number(process.env.VITEST_SHARD_ID ?? 1) + const rpcUrl = `http://127.0.0.1:${port}/${poolId}` + + const config = { + ...parameters, + odyssey: true, + hardfork: 'Prague', + } as const + + const client = createClient({ + transport: http(rpcUrl), + }) + + const transport = RpcTransport.fromHttp(rpcUrl) + const provider = Provider.from({ + async request(args) { + if (args.method === 'eth_sendTransaction') { + const transaction = formatTransaction( + (args.params as any)[0], + ) as TransactionRequest + + const request = await prepareTransactionRequest(client, { + ...transaction, + account: privateKeyToAccount( + '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80', + ), + chain: null, + }) + + const serialized = await signTransaction(client, request) + + args.method = 'eth_sendRawTransaction' as any + args.params = [serialized] as any + } + + return transport.request(args as any) + }, + }) + + return { + config, + request: provider.request, + async restart() { + await fetch(`${rpcUrl}/restart`) + }, + rpcUrl, + async start() { + return await createServer({ + instance: anvil(config), + port, + }).start() + }, + } +} diff --git a/test/src/porto.ts b/test/src/porto.ts new file mode 100644 index 00000000..27a60b9a --- /dev/null +++ b/test/src/porto.ts @@ -0,0 +1,34 @@ +import { Implementation, Porto, Storage } from 'porto' +import { custom, defineChain } from 'viem' +import * as chains from 'viem/chains' + +import { anvilMainnet } from './anvil.js' + +const anvil = defineChain({ + ...chains.mainnet, + contracts: { + ...chains.mainnet.contracts, + delegation: { + address: '0x0ff027b63351364071425cF65d4FEce75a8e17B8', + }, + }, + rpcUrls: { + default: { http: [anvilMainnet.rpcUrl] }, + }, +}) + +export const createPorto = () => + Porto.create({ + chains: [anvil], + implementation: Implementation.mock(), + storage: Storage.memory(), + transports: { + [anvil.id]: custom(anvilMainnet), + }, + }) + +export const porto = createPorto() +export const client = Porto.getClients(porto).default.extend(() => ({ + mode: 'anvil', +})) +export const delegation = client.chain.contracts.delegation.address diff --git a/test/vitest.config.ts b/test/vitest.config.ts new file mode 100644 index 00000000..d323f05c --- /dev/null +++ b/test/vitest.config.ts @@ -0,0 +1,21 @@ +import { join } from 'node:path' +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + alias: { + porto: join(__dirname, '../src'), + }, + coverage: { + all: false, + include: ['**/src/**'], + provider: 'v8', + reporter: process.env.CI ? ['lcov'] : ['text', 'json', 'html'], + }, + globalSetup: [join(__dirname, './globalSetup.ts')], + include: ['src/**/*.test.ts'], + passWithNoTests: true, + setupFiles: [join(__dirname, './setup.ts')], + testTimeout: 10_000, + }, +}) diff --git a/tsconfig.json b/tsconfig.json index 4f1d1ced..e9734b8f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,14 @@ { // This configuration is used for local development and type checking. "extends": "./tsconfig.base.json", - "include": ["scripts", "src"], + "include": ["examples", "scripts", "src", "test"], "exclude": ["src/_dist"], "compilerOptions": { - "baseUrl": "." + "baseUrl": ".", + "jsx": "react-jsx", + "paths": { + "porto": ["src/index.ts"], + "porto/wagmi": ["src/wagmi/index.ts"] + } } } diff --git a/wagmi.config.ts b/wagmi.config.ts index 2b4da541..e988a48a 100644 --- a/wagmi.config.ts +++ b/wagmi.config.ts @@ -3,16 +3,15 @@ import { foundry } from '@wagmi/cli/plugins' import { odysseyTestnet } from 'viem/chains' export default defineConfig({ - out: 'src/generated.ts', + out: 'src/core/internal/generated.ts', contracts: [], plugins: [ foundry({ deployments: { - ExperimentalDelegation: { - [odysseyTestnet.id]: '0xb46b3f3f7F8B198894d1787b9d6c0effbd3928c9', - }, - ExperimentERC20: { - [odysseyTestnet.id]: '0x238c8CD93ee9F8c7Edf395548eF60c0d2e46665E', + Delegation: { + // TODO: uncomment when odyssey has P256VERIFY precompile at 0x100 + // [odysseyTestnet.id]: '0x0ff027b63351364071425cf65d4fece75a8e17b8', + [odysseyTestnet.id]: '0xc8f36b7222e22d192aa0b73046cdd47444392570', }, }, project: 'contracts',