diff --git a/examples/circle-smart-account/package.json b/examples/circle-smart-account/package.json index 7aa877b..2c8813f 100644 --- a/examples/circle-smart-account/package.json +++ b/examples/circle-smart-account/package.json @@ -17,6 +17,6 @@ "@types/react-dom": "^18.0.10", "@vitejs/plugin-react": "^4.3.2", "typescript": "^5.0.3", - "vite": "^5.4.8" + "vite": "^5.4.14" } } diff --git a/examples/eip-1193/.env.example b/examples/eip-1193/.env.example new file mode 100644 index 0000000..60db47c --- /dev/null +++ b/examples/eip-1193/.env.example @@ -0,0 +1,3 @@ +VITE_CLIENT_KEY= +VITE_CLIENT_URL= +VITE_PUBLIC_RPC_URL= diff --git a/examples/eip-1193/README.md b/examples/eip-1193/README.md new file mode 100644 index 0000000..8107db2 --- /dev/null +++ b/examples/eip-1193/README.md @@ -0,0 +1,69 @@ +# EIP-1193 Example + +This example Vite application demonstrates how to register and log in to a Circle Smart Account using passkeys. It also showcases how to perform RPC actions, including retrieving an account address, signing personal messages, signing typed data, and sending transactions using the EIP-1193 provider. + +## Run the example app + +Please follow the instructions to run the example app on your local machine. + +### Environment Variables + +Before you start to run the app, you need to make sure that all environment variables are configured correctly. + +Make a copy of `.env.example` and rename it to `.env`. + +Under `.env`, make sure the following environment variables are configured properly: + +- `VITE_CLIENT_KEY`: Paste your Client Key here. You can create one in [Circle Developer Console](https://console.circle.com/wallets/modular/configurator). +- `VITE_CLIENT_URL`: Paste the Client URL here. You can copy it from [Circle Developer Console](https://console.circle.com/wallets/modular/configurator). +- `VITE_PUBLIC_RPC_URL`: Paste your public RPC endpoint URL here. You can create one in [Infura](https://infura.io/). + +Once you have these environment variables setup, you can now follow the steps below to run the app locally. + +### Install dependencies + +You first need to make sure you have followed the [README](https://github.com/circlefin/w3s-web-core-sdk/blob/master/README.md) under project root and have installed all dependencies under root folder: + +```bash +$ yarn install +``` + +Now you need to go to this example folder: + +```bash +$ cd examples/eip-1193 +``` + +Once you are under the example folder, install all dependencies for the app: + +```bash +$ yarn install +``` + +### Run the app + +To run the app locally: + +```bash +$ yarn dev +``` + +Now you should be able to see your app up and running in your browser at: `http://localhost:5173/`. + +### Important Notes + +- __Do Not Import from `src` or `dist` Directories Directly:__ + + Always import the Core SDK using the package name: + + ```ts + import { yourFunction } from 'w3s-web-core-sdk' + ``` + +- __Watching Changes from the Core SDK Package__ + + If you are developing new SDK features, run `yarn dev` from the [core SDK package directory](../../packages/w3s-web-core-sdk) to build your changes in real time. + +- __Ensure Build-Time Constants Are Replaced:__ + + Variables like `SDK_VERSION` should be replaced during the build process. If you encounter issues, make sure you're using the compiled code from the dist directory. diff --git a/examples/eip-1193/index.html b/examples/eip-1193/index.html new file mode 100644 index 0000000..f45310b --- /dev/null +++ b/examples/eip-1193/index.html @@ -0,0 +1,12 @@ + + + + + + + +

EIP-1193 example

+
+ + + diff --git a/examples/eip-1193/index.tsx b/examples/eip-1193/index.tsx new file mode 100644 index 0000000..236488e --- /dev/null +++ b/examples/eip-1193/index.tsx @@ -0,0 +1,233 @@ +import * as React from "react" +import * as ReactDOM from "react-dom/client" +import { polygonAmoy } from "viem/chains" +import Web3 from "web3" + +import { type Hex, createClient, createPublicClient, http } from "viem" +import { + type P256Credential, + type SmartAccount, + WebAuthnAccount, + createBundlerClient, + toWebAuthnAccount, +} from "viem/account-abstraction" +import { + EIP1193Provider, + WebAuthnMode, + toCircleSmartAccount, + toModularTransport, + toPasskeyTransport, + toWebAuthnCredential, +} from "w3s-web-core-sdk" + +const clientKey = import.meta.env.VITE_CLIENT_KEY as string +const clientUrl = import.meta.env.VITE_CLIENT_URL as string +const publicRpcUrl = import.meta.env.VITE_PUBLIC_RPC_URL as string + +// Create Circle transports +const passkeyTransport = toPasskeyTransport(clientUrl, clientKey) +const modularTransport = toModularTransport(`${clientUrl}/polygonAmoy`, clientKey) + +// Create a public client +const client = createClient({ + chain: polygonAmoy, + transport: modularTransport, +}) + +function Example() { + const [account, setAccount] = React.useState() + const [credential, setCredential] = React.useState(() => + JSON.parse(localStorage.getItem("credential") || "null") + ) + + const [web3, setWeb3] = React.useState() + const [hash, setHash] = React.useState() + const [address, setAddress] = React.useState() + const [personalSignature, setPersonalSignature] = React.useState() + const [typedDataSignature, setTypedDataSignature] = React.useState() + + React.useEffect(() => { + if (!credential) return + + init() + + async function init() { + // Create a circle smart account + const account = await toCircleSmartAccount({ + client, + owner: toWebAuthnAccount({ credential }) as WebAuthnAccount, + }) + + setAccount(account) + + const publicClientInstance = createPublicClient({ + chain: polygonAmoy, + transport: http(publicRpcUrl), + }) + const bundlerClientInstance = createBundlerClient({ + account, + chain: polygonAmoy, + transport: modularTransport, + }) + + const provider = new EIP1193Provider(bundlerClientInstance, publicClientInstance) + setWeb3(new Web3(provider)) + } + }, [credential]) + + const register = async () => { + const username = (document.getElementById("username") as HTMLInputElement).value + const credential = await toWebAuthnCredential({ + transport: passkeyTransport, + mode: WebAuthnMode.Register, + username, + }) + localStorage.setItem("credential", JSON.stringify(credential)) + setCredential(credential) + } + + const login = async () => { + const credential = await toWebAuthnCredential({ + transport: passkeyTransport, + mode: WebAuthnMode.Login, + }) + localStorage.setItem("credential", JSON.stringify(credential)) + setCredential(credential) + } + + const getProviderAddress = async () => { + if (!web3) return + + const accounts = await web3.eth.getAccounts() + + setAddress(accounts[0]) + } + + const signPersonalMessage = async () => { + if (!web3) return + + const accounts = await web3.eth.getAccounts() + const signature = await web3.eth.personal.sign("Hello World", accounts[0], "passphrase") + + setPersonalSignature(signature) + } + + const sendTx = async (event: React.FormEvent) => { + event.preventDefault() + + if (!web3) return + + const formData = new FormData(event.currentTarget) + const to = formData.get("to") as `0x${string}` + const value = formData.get("value") as string + + try { + const suggestedGasPrice = ((await web3.eth.getGasPrice()) * 11n) / 10n // 10% higher than the current gas price to ensure the transaction goes through + + // Send tokens to the address that was input + const tx = await web3.eth.sendTransaction({ + to, + value: web3.utils.toWei(value, "ether"), + gas: 53638, // Estimated gas limit for a simple transaction + gasPrice: suggestedGasPrice, + }) + + setHash(tx.transactionHash as Hex) + } catch (err) { + console.log(err) + } + } + + const signTypedData = async () => { + if (!web3) return + + const accounts = await web3.eth.getAccounts() + const from = accounts[0] + + const domain = { + name: "MyDApp", + version: "1.0", + chainId: 80002, + verifyingContract: "0x1111111111111111111111111111111111111111", + } + const message = { + content: "Hello from typed data!", + sender: from, + timestamp: Math.floor(Date.now() / 1000), + } + const dataToSign = { + domain, + message, + primaryType: "Message", + types: { + EIP712Domain: [ + { name: "name", type: "string" }, + { name: "version", type: "string" }, + { name: "chainId", type: "uint256" }, + { name: "verifyingContract", type: "address" }, + ], + Message: [ + { name: "content", type: "string" }, + { name: "sender", type: "address" }, + { name: "timestamp", type: "uint256" }, + ], + }, + } + + const signature = await web3.eth.signTypedData(from, dataToSign) + + setTypedDataSignature(signature) + } + + if (!credential) + return ( + <> + +
+ + + + ) + if (!account) return

Loading...

+ + return ( + <> +

Account

+

Address: {account?.address}

+ +

Send Transaction

+
+ + + +
+ + + + {address &&

Address: {address}

} + {personalSignature && ( +

+ Personal Signature: {personalSignature} +

+ )} + {typedDataSignature && ( +

+ Typed Data Signature: {typedDataSignature} +

+ )} + {hash &&

Transaction Hash: {hash}

} + + ) +} + +ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render() diff --git a/examples/eip-1193/package.json b/examples/eip-1193/package.json new file mode 100644 index 0000000..8610336 --- /dev/null +++ b/examples/eip-1193/package.json @@ -0,0 +1,23 @@ +{ + "name": "eip-1193-web3js", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0", + "viem": "^2.21.27", + "w3s-web-core-sdk": "file:../../packages/w3s-web-core-sdk", + "web3": "^4.16.0" + }, + "devDependencies": { + "@types/react": "^18.0.27", + "@types/react-dom": "^18.0.10", + "@vitejs/plugin-react": "^4.3.2", + "typescript": "^5.0.3", + "vite": "^5.4.14" + } +} diff --git a/examples/eip-1193/tsconfig.json b/examples/eip-1193/tsconfig.json new file mode 100644 index 0000000..bc8d714 --- /dev/null +++ b/examples/eip-1193/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/examples/eip-1193/tsconfig.node.json b/examples/eip-1193/tsconfig.node.json new file mode 100644 index 0000000..9d31e2a --- /dev/null +++ b/examples/eip-1193/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/examples/eip-1193/vite-env.d.ts b/examples/eip-1193/vite-env.d.ts new file mode 100644 index 0000000..53d6a86 --- /dev/null +++ b/examples/eip-1193/vite-env.d.ts @@ -0,0 +1,10 @@ +/// + +interface ImportMetaEnv { + readonly VITE_APP_TITLE: string + // add other env variables here +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} diff --git a/examples/eip-1193/vite.config.ts b/examples/eip-1193/vite.config.ts new file mode 100644 index 0000000..36f7f4e --- /dev/null +++ b/examples/eip-1193/vite.config.ts @@ -0,0 +1,7 @@ +import react from '@vitejs/plugin-react' +import { defineConfig } from 'vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/packages/w3s-web-core-sdk/README.md b/packages/w3s-web-core-sdk/README.md index 0e5561f..d267ed9 100644 --- a/packages/w3s-web-core-sdk/README.md +++ b/packages/w3s-web-core-sdk/README.md @@ -22,3 +22,5 @@ npm install @circle-fin/modular-wallets-core We've created some example apps in the [examples](https://github.com/circlefin/modularwallets-web-sdk/tree/master/examples) folder: - [circle-smart-account](https://github.com/circlefin/modularwallets-web-sdk/tree/master/examples/circle-smart-account) + +- [eip-1193](https://github.com/circlefin/modularwallets-web-sdk/tree/master/examples/eip-1193) diff --git a/packages/w3s-web-core-sdk/package.json b/packages/w3s-web-core-sdk/package.json index 753b0c4..7f26194 100644 --- a/packages/w3s-web-core-sdk/package.json +++ b/packages/w3s-web-core-sdk/package.json @@ -1,7 +1,7 @@ { "name": "@circle-fin/modular-wallets-core", "description": "Serverless Typescript SDK", - "version": "1.0.1", + "version": "1.0.2", "main": "./dist/index.js", "module": "./dist/index.mjs", "types": "./dist/index.d.ts", diff --git a/packages/w3s-web-core-sdk/src/__mocks__/providers/eip-1193/EIP1193.Mock.ts b/packages/w3s-web-core-sdk/src/__mocks__/providers/eip-1193/EIP1193.Mock.ts new file mode 100644 index 0000000..80d8cf5 --- /dev/null +++ b/packages/w3s-web-core-sdk/src/__mocks__/providers/eip-1193/EIP1193.Mock.ts @@ -0,0 +1,159 @@ +/** + * Copyright 2025 Circle Internet Group, Inc. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at. + * + * Http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Mocks for EIP-1193 rpc responses - eth_accounts and eth_requestAccounts. + */ +export const EthAccountsResponse = { + result: ['0x2F96BdFdef089e4219375Df39fee743AF5D8C0BA'], +} + +/** + * Mocks for EIP-1193 rpc params - personal_sign. + */ +export const PersonalSignWrongAddressParams = ['0xdeadbeef', '0xdeadbeef'] + +/** + * Mocks for EIP-1193 rpc params - personal_sign. + */ +export const PersonalSignParams = [ + '0xdeadbeef', + '0x2F96BdFdef089e4219375Df39fee743AF5D8C0BA', +] + +/** + * Mocks for EIP-1193 rpc responses - personal_sign. + */ +export const PersonalSignResponse = { + result: + '0x000000000000000000000000c83d88c018d143d08afe910221d445189fc6817a0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000046000000000000000000000000000000000000000000000000000000000000003c481d0dff1863bb58aafe20b10fc2e347a735fb95755f3bbcbc50e78da76846d7dad5b34b2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000340000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000020000000000000000000000005a2262d58eb72b84701d6efbf6bb6586c793a65b00000000000000000000000003431fb00fb2e26b5bc502dfef8da30e1c8643b80000000000000000000000000000000000000000000000000000000000000002a043327d77a74c1c55cfa799284b831fe09535a88b9f5fa4173d334e5ba0fd91d892482cc7e665eca1d358d318d38aa3a63c10247d473d04fc3538f4069ce4ae0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012b09685da7842117bfedb0668ffe1b6abe4d7e57298402cc996caafebce98b979edfd692718a0962b4ed55a3e768747fe0b6541c1f348fa1e7c2582c672da94c0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a1863bb58aafe20b10fc2e347a735fb95755f3bbcbc50e78da76846d7dad5b34b2000000000000000000000000000000000000000000000000000000000000004102000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000060a0c43a5828012752b439cca8e2b006a55f41737ae96057d38839ecd03271406f4beaf0d77def012616d6a863c1de9ac097a59c4340d57e8e714d17c4dc736c7600000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000001700000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d07b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22636f6f354d4238424162314d596673514d537748506d4d596f534e5754624a677962676d34466d6d372d49222c226f726967696e223a22616e64726f69643a61706b2d6b65792d686173683a50474f45667162664253726d6838666664526e45325a5453647072506c634152554a4e5a6e304156596c49222c22616e64726f69645061636b6167654e616d65223a22636972636c652e707773646b2e6277636f726573646b2e74657374227d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006492649264926492649264926492649264926492649264926492649264926492', +} + +/** + * Mocks for EIP-1193 rpc params - eth_sendTransaction. + */ +export const SendTransactionToAddressMissingParams = [ + { + data: '0xdeadbeef', + value: 100000000n, + }, +] + +/** + * Mocks for EIP-1193 rpc params - eth_sendTransaction. + */ +export const SendTransactionParams = [ + { + to: '0x2F96BdFdef089e4219375Df39fee743AF5D8C0BA', + data: '0xdeadbeef', + value: 100000000n, + }, +] + +/** + * Mocks for EIP-1193 rpc responses - eth_sendTransaction. + */ +export const MockSendUserOperationResponse = '0xdeadbeef' + +/** + * Mocks for EIP-1193 rpc responses - eth_sendTransaction. + */ +export const MockWaitForUserOperationReceiptResponse = { + receipt: { + transactionHash: '0xdeadbeef', + }, +} + +/** + * Mocks for EIP-1193 rpc responses - eth_sendTransaction. + */ +export const MockSendTransactionResponse = { + id: undefined, + jsonrpc: undefined, + result: MockWaitForUserOperationReceiptResponse.receipt.transactionHash, +} + +/** + * Mocks for EIP-1193 rpc params - eth_getTransactionReceipt. + */ +export const GetTransactionReceiptParams = ['0xdeadbeef'] + +/** + * Mocks for EIP-1193 rpc responses - eth_getTransactionReceipt. + */ +export const MockGetTransactionReceiptResponse = { + id: undefined, + jsonrpc: undefined, + result: { receipt: { transactionHash: GetTransactionReceiptParams[0] } }, +} + +/** + * Mocks for EIP-1193 rpc params - eth_signTypedData_v4. + */ +export const SignTypedDataV4WrongAddressParams = [ + '0xdeadbeef', + { + domain: { + name: 'MockDomain', + version: '1', + chainId: 1, + verifyingContract: '0x9876543210fedcba9876543210fedcba98765432', + }, + message: { + content: 'This is a test message', + }, + primaryType: 'Message', + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + Message: [{ name: 'content', type: 'string' }], + }, + }, +] + +/** + * Mocks for EIP-1193 rpc params - eth_signTypedData_v4. + */ +export const SignTypedDataV4Params = [ + '0x2F96BdFdef089e4219375Df39fee743AF5D8C0BA', + { + domain: { + name: 'MockDomain', + version: '1', + chainId: 1, + verifyingContract: '0x9876543210fedcba9876543210fedcba98765432', + }, + message: { + content: 'This is a test message', + }, + primaryType: 'Message', + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + Message: [{ name: 'content', type: 'string' }], + }, + }, +] diff --git a/packages/w3s-web-core-sdk/src/__mocks__/providers/eip-1193/index.ts b/packages/w3s-web-core-sdk/src/__mocks__/providers/eip-1193/index.ts new file mode 100644 index 0000000..a5fb6e7 --- /dev/null +++ b/packages/w3s-web-core-sdk/src/__mocks__/providers/eip-1193/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2025 Circle Internet Group, Inc. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at. + * + * Http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './EIP1193.Mock' diff --git a/packages/w3s-web-core-sdk/src/__tests__/providers/eip-1193/provider.test.ts b/packages/w3s-web-core-sdk/src/__tests__/providers/eip-1193/provider.test.ts new file mode 100644 index 0000000..65b463e --- /dev/null +++ b/packages/w3s-web-core-sdk/src/__tests__/providers/eip-1193/provider.test.ts @@ -0,0 +1,412 @@ +/** + * Copyright 2025 Circle Internet Group, Inc. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at. + * + * Http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createPublicClient, http } from 'viem' +import { + createBundlerClient, + toWebAuthnAccount, +} from 'viem/account-abstraction' +import { sepolia } from 'viem/chains' +import { MethodNotImplementedError } from 'web3' + +import { + LoginCredentialMock, + toModularTransport, + toPasskeyTransport, +} from '../../../__mocks__' +import { + EthAccountsResponse, + GetTransactionReceiptParams, + MockGetTransactionReceiptResponse, + MockSendTransactionResponse, + MockSendUserOperationResponse, + MockWaitForUserOperationReceiptResponse, + PersonalSignParams, + PersonalSignResponse, + PersonalSignWrongAddressParams, + SendTransactionParams, + SendTransactionToAddressMissingParams, + SignTypedDataV4Params, + SignTypedDataV4WrongAddressParams, +} from '../../../__mocks__/providers/eip-1193' +import { toCircleSmartAccount, toWebAuthnCredential } from '../../../accounts' +import { EIP1193Provider } from '../../../providers/eip-1193' +import { WebAuthnMode } from '../../../types' + +import type { + ToCircleSmartAccountReturnType, + ToWebAuthnAccountParameters, + WebAuthnCredential, +} from '../../../types' +import type { BundlerClient, WebAuthnAccount } from 'viem/account-abstraction' + +const mockNavigatorGet = globalThis.window.navigator.credentials[ + 'get' +] as jest.Mock + +const passkeyTransport = toPasskeyTransport() +const loginParameters: ToWebAuthnAccountParameters = { + transport: passkeyTransport, + mode: WebAuthnMode.Login, +} + +let credential: WebAuthnCredential +let owner: WebAuthnAccount + +let bundlerClient: BundlerClient +let provider: EIP1193Provider + +const modularTransport = toModularTransport() +const publicClient = createPublicClient({ + chain: sepolia, + transport: http(), +}) +let account: ToCircleSmartAccountReturnType + +beforeAll(async () => { + mockNavigatorGet.mockResolvedValue(LoginCredentialMock) + credential = await toWebAuthnCredential(loginParameters) + owner = toWebAuthnAccount({ credential }) + + account = await toCircleSmartAccount({ + client: publicClient, + owner, + }) +}) + +describe('Providers > eip-1193 > EIP1193Provider > constructor and inherited methods', () => { + it('should be defined', () => { + expect(EIP1193Provider).toBeDefined() + }) + + it('should be a function', () => { + expect(typeof EIP1193Provider).toBe('function') + }) + + it('should throw an error when the bundler account is not provided', () => { + bundlerClient = createBundlerClient({ + chain: sepolia, + transport: modularTransport, + }) + + expect(() => new EIP1193Provider(bundlerClient, publicClient)).toThrow( + new Error('Account is required'), + ) + }) + + it('should create an instance correctly', () => { + bundlerClient = createBundlerClient({ + account, + chain: sepolia, + transport: modularTransport, + }) + + provider = new EIP1193Provider(bundlerClient, publicClient) + expect(provider).toBeInstanceOf(EIP1193Provider) + }) + + it('should throw MethodNotImplementedError for unsupported methods', async () => { + const mockPayload = { + method: 'unsupported_method', + } + + await expect(provider.request(mockPayload)).rejects.toThrow( + MethodNotImplementedError, + ) + }) + + it('should return false for supportsSubscriptions', () => { + expect(provider.supportsSubscriptions()).toBe(false) + }) + + it('should throw MethodNotImplementedError when calling any of the mandatory abstract methods', () => { + expect(() => provider.getStatus()).toThrow(MethodNotImplementedError) + expect(() => provider.on()).toThrow(MethodNotImplementedError) + expect(() => provider.removeListener()).toThrow(MethodNotImplementedError) + expect(() => provider.once()).toThrow(MethodNotImplementedError) + expect(() => provider.removeAllListeners()).toThrow( + MethodNotImplementedError, + ) + expect(() => provider.connect()).toThrow(MethodNotImplementedError) + expect(() => provider.disconnect()).toThrow(MethodNotImplementedError) + expect(() => provider.reset()).toThrow(MethodNotImplementedError) + expect(() => provider.reconnect()).toThrow(MethodNotImplementedError) + }) +}) + +describe('Providers > eip-1193 > EIP1193Provider > rpc methods', () => { + beforeEach(() => { + bundlerClient = createBundlerClient({ + account, + chain: sepolia, + transport: modularTransport, + }) + + provider = new EIP1193Provider(bundlerClient, publicClient) + }) + + it('should return the correct response for eth_accounts', async () => { + const mockPayload = { method: 'eth_accounts' } + + const response = await provider.request( + mockPayload, + ) + + expect(response).toEqual(EthAccountsResponse) + }) + + it('should return the correct response for eth_requestAccounts', async () => { + const mockPayload = { method: 'eth_requestAccounts' } + + const response = await provider.request( + mockPayload, + ) + + expect(response).toEqual(EthAccountsResponse) + }) + + it('should throw an error when the addresses are different for personal_sign', async () => { + const mockPayload = { + method: 'personal_sign', + params: PersonalSignWrongAddressParams, + } + + await expect(() => provider.request(mockPayload)).rejects.toThrow( + new Error('Invalid account'), + ) + }) + + it('should return the correct response for personal_sign', async () => { + const mockPayload = { + method: 'personal_sign', + params: PersonalSignParams, + } + + const response = await provider.request< + string, + typeof PersonalSignResponse + >(mockPayload) + + expect(response).toEqual(PersonalSignResponse) + }) + + it('should throw an error when the to parameter is not provided for eth_sendTransaction', async () => { + const mockPayload = { + method: 'eth_sendTransaction', + params: SendTransactionToAddressMissingParams, + } + + await expect(() => provider.request(mockPayload)).rejects.toThrow( + new Error('Missing to address'), + ) + }) + + it('should return the correct response for eth_sendTransaction', async () => { + const mockPayload = { + method: 'eth_sendTransaction', + params: SendTransactionParams, + } + + // Spy on `sendUserOperation` and `waitForUserOperationReceipt` + const sendUserOperationSpy = jest + .spyOn(bundlerClient, 'sendUserOperation') + .mockResolvedValue(MockSendUserOperationResponse) + const waitForUserOperationReceiptSpy = jest + .spyOn(bundlerClient, 'waitForUserOperationReceipt') + .mockResolvedValue(MockWaitForUserOperationReceiptResponse as never) + + const response = await provider.request< + string, + typeof MockSendTransactionResponse + >(mockPayload) + + expect(sendUserOperationSpy).toHaveBeenCalledWith({ + calls: [ + { + to: SendTransactionParams[0].to, + value: SendTransactionParams[0].value, + data: SendTransactionParams[0].data, + }, + ], + account: bundlerClient.account, + }) + expect(waitForUserOperationReceiptSpy).toHaveBeenCalledWith({ + hash: MockSendUserOperationResponse, + }) + + expect(response).toEqual(MockSendTransactionResponse) + + sendUserOperationSpy.mockRestore() + waitForUserOperationReceiptSpy.mockRestore() + }) + + it('should return the correct response when value is not provided for eth_sendTransaction', async () => { + const mockPayload = { + method: 'eth_sendTransaction', + params: [ + { + ...SendTransactionParams[0], + value: undefined, + }, + ], + } + + // Spy on `sendUserOperation` and `waitForUserOperationReceipt` + const sendUserOperationSpy = jest + .spyOn(bundlerClient, 'sendUserOperation') + .mockResolvedValue(MockSendUserOperationResponse) + const waitForUserOperationReceiptSpy = jest + .spyOn(bundlerClient, 'waitForUserOperationReceipt') + .mockResolvedValue(MockWaitForUserOperationReceiptResponse as never) + + const response = await provider.request< + string, + typeof MockSendTransactionResponse + >(mockPayload) + + expect(sendUserOperationSpy).toHaveBeenCalledWith({ + calls: [ + { + to: SendTransactionParams[0].to, + value: BigInt(0), + data: SendTransactionParams[0].data, + }, + ], + account: bundlerClient.account, + }) + expect(waitForUserOperationReceiptSpy).toHaveBeenCalledWith({ + hash: MockSendUserOperationResponse, + }) + + expect(response).toEqual(MockSendTransactionResponse) + + sendUserOperationSpy.mockRestore() + waitForUserOperationReceiptSpy.mockRestore() + }) + + it('should return the correct response when data is not provided for eth_sendTransaction', async () => { + const mockPayload = { + method: 'eth_sendTransaction', + params: [ + { + ...SendTransactionParams[0], + data: undefined, + }, + ], + } + + // Spy on `sendUserOperation` and `waitForUserOperationReceipt` + const sendUserOperationSpy = jest + .spyOn(bundlerClient, 'sendUserOperation') + .mockResolvedValue(MockSendUserOperationResponse) + const waitForUserOperationReceiptSpy = jest + .spyOn(bundlerClient, 'waitForUserOperationReceipt') + .mockResolvedValue(MockWaitForUserOperationReceiptResponse as never) + + const response = await provider.request< + string, + typeof MockSendTransactionResponse + >(mockPayload) + + expect(sendUserOperationSpy).toHaveBeenCalledWith({ + calls: [ + { + to: SendTransactionParams[0].to, + value: SendTransactionParams[0].value, + data: '0x', + }, + ], + account: bundlerClient.account, + }) + expect(waitForUserOperationReceiptSpy).toHaveBeenCalledWith({ + hash: MockSendUserOperationResponse, + }) + + expect(response).toEqual(MockSendTransactionResponse) + + sendUserOperationSpy.mockRestore() + waitForUserOperationReceiptSpy.mockRestore() + }) + + it('should return the correct response for eth_getTransactionReceipt', async () => { + const mockPayload = { + method: 'eth_getTransactionReceipt', + params: GetTransactionReceiptParams, + } + + // Spy on `waitForTransactionReceipt` + const waitForGetTransactionReceiptSpy = jest + .spyOn(publicClient, 'waitForTransactionReceipt') + .mockResolvedValue(MockWaitForUserOperationReceiptResponse as never) + + const response = await provider.request< + string, + typeof MockGetTransactionReceiptResponse + >(mockPayload) + + expect(waitForGetTransactionReceiptSpy).toHaveBeenCalledWith({ + hash: GetTransactionReceiptParams[0], + }) + + expect(response).toEqual(MockGetTransactionReceiptResponse) + + waitForGetTransactionReceiptSpy.mockRestore() + }) + + it('should throw an error when the addresses are different for eth_signTypedData_v4', async () => { + const mockPayload = { + method: 'eth_signTypedData_v4', + params: SignTypedDataV4WrongAddressParams, + } + + await expect(() => provider.request(mockPayload)).rejects.toThrow( + new Error('Invalid account'), + ) + }) + + it('should return the correct response for eth_signTypedData_v4', async () => { + const mockPayload = { + method: 'eth_signTypedData_v4', + params: SignTypedDataV4Params, + } + + const response = await provider.request< + string, + typeof PersonalSignResponse + >(mockPayload) + + expect(response).toEqual(PersonalSignResponse) + }) + + it('should forward the rpc requests to the bundler client when the method is not handled by itself', async () => { + const mockPayload = { + method: 'unsupported_method', + } + + // Spy on `request` + const requestSpy = jest + .spyOn(bundlerClient.transport, 'request') + .mockResolvedValue({} as never) + + await provider.request(mockPayload) + + expect(requestSpy).toHaveBeenCalledWith(mockPayload) + + requestSpy.mockRestore() + }) +}) diff --git a/packages/w3s-web-core-sdk/src/providers/base/index.ts b/packages/w3s-web-core-sdk/src/providers/base/index.ts new file mode 100644 index 0000000..80a430f --- /dev/null +++ b/packages/w3s-web-core-sdk/src/providers/base/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2025 Circle Internet Group, Inc. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at. + * + * Http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { default as BaseProvider } from './provider' diff --git a/packages/w3s-web-core-sdk/src/providers/base/provider.ts b/packages/w3s-web-core-sdk/src/providers/base/provider.ts new file mode 100644 index 0000000..4402e2c --- /dev/null +++ b/packages/w3s-web-core-sdk/src/providers/base/provider.ts @@ -0,0 +1,128 @@ +/** + * Copyright 2025 Circle Internet Group, Inc. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at. + * + * Http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { MethodNotImplementedError } from 'web3-errors' +import { Web3BaseProvider } from 'web3-types' + +import type { + EthExecutionAPI, + JsonRpcResponseWithResult, + Web3APIMethod, + Web3APIPayload, + Web3APISpec, + Web3ProviderStatus, +} from 'web3-types' + +/** + * A base EIP-1193 provider that reduces repetitive code. + */ +export default abstract class BaseProvider< + API extends Web3APISpec = EthExecutionAPI, +> extends Web3BaseProvider { + public constructor() { + super() + } + + /** + * Sends a request to the API client. This is an abstract method that must be implemented by the child class. + * @param args - The payload to send in the request. + */ + abstract request, ResultType = unknown>( + args: Web3APIPayload, + ): Promise> + + /** + * This is an abstract method from the parent class but we don't need it for this provider. + * @throws MethodNotImplementedError. + */ + public getStatus(): Web3ProviderStatus { + throw new MethodNotImplementedError() + } + + /** + * This is an abstract method from the parent class but we don't need it for this provider. + * @Returns false. + */ + public supportsSubscriptions() { + return false + } + + /** + * This is an abstract method from the parent class but we don't need it for this provider. + * @throws MethodNotImplementedError. + */ + public on() { + throw new MethodNotImplementedError() + } + + /** + * This is an abstract method from the parent class but we don't need it for this provider. + * @throws MethodNotImplementedError. + */ + public removeListener() { + throw new MethodNotImplementedError() + } + + /** + * This is an abstract method from the parent class but we don't need it for this provider. + * @throws MethodNotImplementedError. + */ + public once() { + throw new MethodNotImplementedError() + } + + /** + * This is an abstract method from the parent class but we don't need it for this provider. + * @throws MethodNotImplementedError. + */ + public removeAllListeners() { + throw new MethodNotImplementedError() + } + + /** + * This is an abstract method from the parent class but we don't need it for this provider. + * @throws MethodNotImplementedError. + */ + public connect() { + throw new MethodNotImplementedError() + } + + /** + * This is an abstract method from the parent class but we don't need it for this provider. + * @throws MethodNotImplementedError. + */ + public disconnect() { + throw new MethodNotImplementedError() + } + + /** + * This is an abstract method from the parent class but we don't need it for this provider. + * @throws MethodNotImplementedError. + */ + public reset() { + throw new MethodNotImplementedError() + } + + /** + * This is an abstract method from the parent class but we don't need it for this provider. + * @throws MethodNotImplementedError. + */ + public reconnect() { + throw new MethodNotImplementedError() + } +} diff --git a/packages/w3s-web-core-sdk/src/providers/eip-1193/index.ts b/packages/w3s-web-core-sdk/src/providers/eip-1193/index.ts new file mode 100644 index 0000000..287717e --- /dev/null +++ b/packages/w3s-web-core-sdk/src/providers/eip-1193/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2025 Circle Internet Group, Inc. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at. + * + * Http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { default as EIP1193Provider } from './provider' diff --git a/packages/w3s-web-core-sdk/src/providers/eip-1193/provider.ts b/packages/w3s-web-core-sdk/src/providers/eip-1193/provider.ts new file mode 100644 index 0000000..0b1c10a --- /dev/null +++ b/packages/w3s-web-core-sdk/src/providers/eip-1193/provider.ts @@ -0,0 +1,167 @@ +/** + * Copyright 2025 Circle Internet Group, Inc. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at. + * + * Http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { type BundlerClient } from 'viem/_types/account-abstraction' + +import { BaseProvider } from '../base' + +import type { TypedData } from 'abitype' +import type { + Hex, + PublicClient, + SendTransactionParameters, + TypedDataDefinition, +} from 'viem' +import type { + EthExecutionAPI, + Web3APIMethod, + Web3APIPayload, + Web3APIReturnType, + Web3APISpec, +} from 'web3-types' + +/** + * An EIP-1193 provider that connects to the Modular Wallets API. + * @param bundlerClient - The bundler client. + * @param publicClient - The public client. + */ +export default class EIP1193Provider< + API extends Web3APISpec = EthExecutionAPI, +> extends BaseProvider { + private readonly publicClient: PublicClient + private readonly bundlerClient: BundlerClient + + public constructor(bundlerClient: BundlerClient, publicClient: PublicClient) { + super() + + this.bundlerClient = bundlerClient + this.publicClient = publicClient + + if (this.bundlerClient.account === undefined) { + throw new Error('Account is required') + } + } + + public async request< + Method extends Web3APIMethod, + ResultType = Web3APIReturnType, + >(payload: Web3APIPayload): Promise { + const { method, params } = payload + + switch (method) { + case 'eth_accounts': + case 'eth_requestAccounts': { + const address = await this.bundlerClient.account!.getAddress() + + return this.getResponse([address], payload) + } + case 'personal_sign': { + const [challenge, address] = params as [Hex, Hex] + + await this.validateAddress(address) + + const result = await this.bundlerClient.account!.signMessage({ + message: challenge, + }) + + return this.getResponse(result, payload) + } + case 'eth_sendTransaction': { + const [{ data, to, value }] = params as [SendTransactionParameters] + + if (!to) throw new Error('Missing to address') + + const userOpHash = await this.bundlerClient.sendUserOperation({ + calls: [ + { + to, + value: value ?? BigInt(0), + data: data ?? '0x', + }, + ], + account: this.bundlerClient.account, + }) + + const { receipt } = + await this.bundlerClient.waitForUserOperationReceipt({ + hash: userOpHash, + }) + + return this.getResponse(receipt.transactionHash, payload) + } + case 'eth_getTransactionReceipt': { + const [hash] = params as [Hex] + + const receipt = await this.publicClient.waitForTransactionReceipt({ + hash, + }) + + return this.getResponse(receipt, payload) + } + case 'eth_signTypedData_v4': { + const [address, typedData] = params as [ + Hex, + TypedDataDefinition, + ] + + await this.validateAddress(address) + + const result = + await this.bundlerClient.account!.signTypedData(typedData) + + return this.getResponse(result, payload) + } + default: { + const result = await this.bundlerClient.transport.request(payload) + + return this.getResponse(result, payload) + } + } + } + + /** + * Validates the specified address. + * @param address - The address to validate. + */ + private async validateAddress(address: Hex) { + const clientAddress = await this.bundlerClient.account!.getAddress() + + if (clientAddress !== address) { + throw new Error('Invalid account') + } + } + + /** + * Creates a JSON-RPC response with the specified result. + * @param result - The result to include in the response. + * @param payload - The payload of the request. + */ + private getResponse< + API extends Web3APISpec, + Method extends Web3APIMethod, + ResultType, + >(result: unknown, payload: Web3APIPayload): ResultType { + const { jsonrpc, id } = payload + + return { + result, + jsonrpc, + id, + } as ResultType + } +} diff --git a/packages/w3s-web-core-sdk/src/providers/index.ts b/packages/w3s-web-core-sdk/src/providers/index.ts index 5508612..b7d065c 100644 --- a/packages/w3s-web-core-sdk/src/providers/index.ts +++ b/packages/w3s-web-core-sdk/src/providers/index.ts @@ -16,6 +16,8 @@ * limitations under the License. */ +export * from './base' export * from './modular-wallets' export * from './paymaster' export * from './rp' +export * from './eip-1193' diff --git a/packages/w3s-web-core-sdk/src/providers/modular-wallets/provider.ts b/packages/w3s-web-core-sdk/src/providers/modular-wallets/provider.ts index 7d59031..29bf253 100644 --- a/packages/w3s-web-core-sdk/src/providers/modular-wallets/provider.ts +++ b/packages/w3s-web-core-sdk/src/providers/modular-wallets/provider.ts @@ -17,9 +17,9 @@ */ import { InvalidProviderError, MethodNotImplementedError } from 'web3-errors' -import { Web3BaseProvider } from 'web3-types' import { fetchFromApi, validateClientUrl } from '../../utils' +import { BaseProvider } from '../base' import type { EthExecutionAPI, @@ -28,7 +28,6 @@ import type { Web3APIPayload, Web3APIReturnType, Web3APISpec, - Web3ProviderStatus, } from 'web3-types' /** @@ -38,7 +37,7 @@ import type { */ export default class ModularWalletsProvider< API extends Web3APISpec = EthExecutionAPI, -> extends Web3BaseProvider { +> extends BaseProvider { public readonly clientUrl: string private readonly clientKey: string @@ -90,84 +89,4 @@ export default class ModularWalletsProvider< throw new MethodNotImplementedError() } } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @throws MethodNotImplementedError. - */ - public getStatus(): Web3ProviderStatus { - throw new MethodNotImplementedError() - } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @Returns false. - */ - public supportsSubscriptions() { - return false - } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @throws MethodNotImplementedError. - */ - public on() { - throw new MethodNotImplementedError() - } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @throws MethodNotImplementedError. - */ - public removeListener() { - throw new MethodNotImplementedError() - } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @throws MethodNotImplementedError. - */ - public once() { - throw new MethodNotImplementedError() - } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @throws MethodNotImplementedError. - */ - public removeAllListeners() { - throw new MethodNotImplementedError() - } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @throws MethodNotImplementedError. - */ - public connect() { - throw new MethodNotImplementedError() - } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @throws MethodNotImplementedError. - */ - public disconnect() { - throw new MethodNotImplementedError() - } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @throws MethodNotImplementedError. - */ - public reset() { - throw new MethodNotImplementedError() - } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @throws MethodNotImplementedError. - */ - public reconnect() { - throw new MethodNotImplementedError() - } } diff --git a/packages/w3s-web-core-sdk/src/providers/paymaster/provider.ts b/packages/w3s-web-core-sdk/src/providers/paymaster/provider.ts index aecb127..229057b 100644 --- a/packages/w3s-web-core-sdk/src/providers/paymaster/provider.ts +++ b/packages/w3s-web-core-sdk/src/providers/paymaster/provider.ts @@ -17,9 +17,9 @@ */ import { InvalidProviderError, MethodNotImplementedError } from 'web3-errors' -import { Web3BaseProvider } from 'web3-types' import { fetchFromApi, validateClientUrl } from '../../utils' +import { BaseProvider } from '../base' import type { EthExecutionAPI, @@ -28,7 +28,6 @@ import type { Web3APIPayload, Web3APIReturnType, Web3APISpec, - Web3ProviderStatus, } from 'web3-types' /** @@ -38,7 +37,7 @@ import type { */ export default class PaymasterProvider< API extends Web3APISpec = EthExecutionAPI, -> extends Web3BaseProvider { +> extends BaseProvider { public readonly clientUrl: string private readonly clientKey: string @@ -76,84 +75,4 @@ export default class PaymasterProvider< throw new MethodNotImplementedError() } } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @throws MethodNotImplementedError. - */ - public getStatus(): Web3ProviderStatus { - throw new MethodNotImplementedError() - } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @Returns false. - */ - public supportsSubscriptions() { - return false - } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @throws MethodNotImplementedError. - */ - public on() { - throw new MethodNotImplementedError() - } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @throws MethodNotImplementedError. - */ - public removeListener() { - throw new MethodNotImplementedError() - } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @throws MethodNotImplementedError. - */ - public once() { - throw new MethodNotImplementedError() - } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @throws MethodNotImplementedError. - */ - public removeAllListeners() { - throw new MethodNotImplementedError() - } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @throws MethodNotImplementedError. - */ - public connect() { - throw new MethodNotImplementedError() - } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @throws MethodNotImplementedError. - */ - public disconnect() { - throw new MethodNotImplementedError() - } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @throws MethodNotImplementedError. - */ - public reset() { - throw new MethodNotImplementedError() - } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @throws MethodNotImplementedError. - */ - public reconnect() { - throw new MethodNotImplementedError() - } } diff --git a/packages/w3s-web-core-sdk/src/providers/rp/provider.ts b/packages/w3s-web-core-sdk/src/providers/rp/provider.ts index ca1a9fe..436cc4b 100644 --- a/packages/w3s-web-core-sdk/src/providers/rp/provider.ts +++ b/packages/w3s-web-core-sdk/src/providers/rp/provider.ts @@ -17,9 +17,9 @@ */ import { InvalidProviderError, MethodNotImplementedError } from 'web3-errors' -import { Web3BaseProvider } from 'web3-types' import { fetchFromApi, validateClientUrl } from '../../utils' +import { BaseProvider } from '../base' import type { EthExecutionAPI, @@ -27,7 +27,6 @@ import type { Web3APIPayload, Web3APIReturnType, Web3APISpec, - Web3ProviderStatus, } from 'web3-types' /** @@ -37,7 +36,7 @@ import type { */ export default class RpProvider< API extends Web3APISpec = EthExecutionAPI, -> extends Web3BaseProvider { +> extends BaseProvider { public readonly clientUrl: string private readonly clientKey: string @@ -77,84 +76,4 @@ export default class RpProvider< throw new MethodNotImplementedError() } } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @throws MethodNotImplementedError. - */ - public getStatus(): Web3ProviderStatus { - throw new MethodNotImplementedError() - } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @Returns false. - */ - public supportsSubscriptions() { - return false - } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @throws MethodNotImplementedError. - */ - public on() { - throw new MethodNotImplementedError() - } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @throws MethodNotImplementedError. - */ - public removeListener() { - throw new MethodNotImplementedError() - } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @throws MethodNotImplementedError. - */ - public once() { - throw new MethodNotImplementedError() - } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @throws MethodNotImplementedError. - */ - public removeAllListeners() { - throw new MethodNotImplementedError() - } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @throws MethodNotImplementedError. - */ - public connect() { - throw new MethodNotImplementedError() - } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @throws MethodNotImplementedError. - */ - public disconnect() { - throw new MethodNotImplementedError() - } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @throws MethodNotImplementedError. - */ - public reset() { - throw new MethodNotImplementedError() - } - - /** - * This is an abstract method from the parent class but we don't need it for this provider. - * @throws MethodNotImplementedError. - */ - public reconnect() { - throw new MethodNotImplementedError() - } } diff --git a/yarn.lock b/yarn.lock index fc0ed32..41b6ab1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5398,10 +5398,10 @@ viem@^2.21.27: webauthn-p256 "0.0.10" ws "8.18.0" -vite@^5.4.8: - version "5.4.10" - resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.10.tgz#d358a7bd8beda6cf0f3b7a450a8c7693a4f80c18" - integrity sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ== +vite@^5.4.14: + version "5.4.14" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.14.tgz#ff8255edb02134df180dcfca1916c37a6abe8408" + integrity sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA== dependencies: esbuild "^0.21.3" postcss "^8.4.43" @@ -5416,6 +5416,14 @@ w3c-xmlserializer@^4.0.0: dependencies: xml-name-validator "^4.0.0" +"w3s-web-core-sdk@file:packages/w3s-web-core-sdk": + version "1.0.2" + dependencies: + uuid "^11.0.3" + viem "^2.21.27" + web3 "^4.13.0" + webauthn-p256 "0.0.5" + walker@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" @@ -5439,6 +5447,22 @@ web3-core@^4.4.0, web3-core@^4.5.0, web3-core@^4.5.1, web3-core@^4.6.0: optionalDependencies: web3-providers-ipc "^4.0.7" +web3-core@^4.7.1: + version "4.7.1" + resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-4.7.1.tgz#bc56cd7959fe44ee77139d591211f69851140009" + integrity sha512-9KSeASCb/y6BG7rwhgtYC4CvYY66JfkmGNEYb7q1xgjt9BWfkf09MJPaRyoyT5trdOxYDHkT9tDlypvQWaU8UQ== + dependencies: + web3-errors "^1.3.1" + web3-eth-accounts "^4.3.1" + web3-eth-iban "^4.0.7" + web3-providers-http "^4.2.0" + web3-providers-ws "^4.0.8" + web3-types "^1.10.0" + web3-utils "^4.3.3" + web3-validator "^2.0.6" + optionalDependencies: + web3-providers-ipc "^4.0.7" + web3-errors@^1.1.3, web3-errors@^1.2.0, web3-errors@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/web3-errors/-/web3-errors-1.3.0.tgz#504e4d3218899df108856940087a8022d6688d74" @@ -5446,6 +5470,13 @@ web3-errors@^1.1.3, web3-errors@^1.2.0, web3-errors@^1.3.0: dependencies: web3-types "^1.7.0" +web3-errors@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/web3-errors/-/web3-errors-1.3.1.tgz#163bc4d869f98614760b683d733c3ed1fb415d98" + integrity sha512-w3NMJujH+ZSW4ltIZZKtdbkbyQEvBzyp3JRn59Ckli0Nz4VMsVq8aF1bLWM7A2kuQ+yVEm3ySeNU+7mSRwx7RQ== + dependencies: + web3-types "^1.10.0" + web3-eth-abi@^4.2.3, web3-eth-abi@^4.2.4: version "4.2.4" resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-4.2.4.tgz#b66f4b067ba06c0aecc013e98a4d717547ab8174" @@ -5457,6 +5488,17 @@ web3-eth-abi@^4.2.3, web3-eth-abi@^4.2.4: web3-utils "^4.3.1" web3-validator "^2.0.6" +web3-eth-abi@^4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-4.4.1.tgz#1dca9d80341b3cd7a1ae07dc98080c2073d62a29" + integrity sha512-60ecEkF6kQ9zAfbTY04Nc9q4eEYM0++BySpGi8wZ2PD1tw/c0SDvsKhV6IKURxLJhsDlb08dATc3iD6IbtWJmg== + dependencies: + abitype "0.7.1" + web3-errors "^1.3.1" + web3-types "^1.10.0" + web3-utils "^4.3.3" + web3-validator "^2.0.6" + web3-eth-accounts@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-4.2.1.tgz#db27399137e1a26f9d467b9500019a70771f5724" @@ -5470,6 +5512,19 @@ web3-eth-accounts@^4.2.1: web3-utils "^4.3.1" web3-validator "^2.0.6" +web3-eth-accounts@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-4.3.1.tgz#6712ea915940d03d596015a87f9167171e8306a6" + integrity sha512-rTXf+H9OKze6lxi7WMMOF1/2cZvJb2AOnbNQxPhBDssKOllAMzLhg1FbZ4Mf3lWecWfN6luWgRhaeSqO1l+IBQ== + dependencies: + "@ethereumjs/rlp" "^4.0.1" + crc-32 "^1.2.2" + ethereum-cryptography "^2.0.0" + web3-errors "^1.3.1" + web3-types "^1.10.0" + web3-utils "^4.3.3" + web3-validator "^2.0.6" + web3-eth-contract@^4.5.0, web3-eth-contract@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-4.7.0.tgz#119a744e8a35f60fd7bc3e4f8637f0380a3d0e85" @@ -5484,6 +5539,20 @@ web3-eth-contract@^4.5.0, web3-eth-contract@^4.7.0: web3-utils "^4.3.1" web3-validator "^2.0.6" +web3-eth-contract@^4.7.2: + version "4.7.2" + resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-4.7.2.tgz#a1851e566ceb4b0da3792ff4d8f7cb6fd91d3401" + integrity sha512-3ETqs2pMNPEAc7BVY/C3voOhTUeJdkf2aM3X1v+edbngJLHAxbvxKpOqrcO0cjXzC4uc2Q8Zpf8n8zT5r0eLnA== + dependencies: + "@ethereumjs/rlp" "^5.0.2" + web3-core "^4.7.1" + web3-errors "^1.3.1" + web3-eth "^4.11.1" + web3-eth-abi "^4.4.1" + web3-types "^1.10.0" + web3-utils "^4.3.3" + web3-validator "^2.0.6" + web3-eth-ens@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-4.4.0.tgz#bc0d11d755cb15ed4b82e38747c5104622d9a4b9" @@ -5521,6 +5590,23 @@ web3-eth-personal@^4.1.0: web3-utils "^4.3.1" web3-validator "^2.0.6" +web3-eth@^4.11.1: + version "4.11.1" + resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-4.11.1.tgz#f558ab1482d4196de0f0a7da5985de42d06664ea" + integrity sha512-q9zOkzHnbLv44mwgLjLXuyqszHuUgZWsQayD2i/rus2uk0G7hMn11bE2Q3hOVnJS4ws4VCtUznlMxwKQ+38V2w== + dependencies: + setimmediate "^1.0.5" + web3-core "^4.7.1" + web3-errors "^1.3.1" + web3-eth-abi "^4.4.1" + web3-eth-accounts "^4.3.1" + web3-net "^4.1.0" + web3-providers-ws "^4.0.8" + web3-rpc-methods "^1.3.0" + web3-types "^1.10.0" + web3-utils "^4.3.3" + web3-validator "^2.0.6" + web3-eth@^4.8.0, web3-eth@^4.8.2, web3-eth@^4.9.0: version "4.9.0" resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-4.9.0.tgz#324403d913cc29bcae6cc1ad50a6defeb762828a" @@ -5600,6 +5686,23 @@ web3-rpc-providers@^1.0.0-rc.2: web3-utils "^4.3.1" web3-validator "^2.0.6" +web3-rpc-providers@^1.0.0-rc.4: + version "1.0.0-rc.4" + resolved "https://registry.yarnpkg.com/web3-rpc-providers/-/web3-rpc-providers-1.0.0-rc.4.tgz#93cec88175eb2f7972e12be30af4c2f296b1923f" + integrity sha512-PXosCqHW0EADrYzgmueNHP3Y5jcSmSwH+Dkqvn7EYD0T2jcsdDAIHqk6szBiwIdhumM7gv9Raprsu/s/f7h1fw== + dependencies: + web3-errors "^1.3.1" + web3-providers-http "^4.2.0" + web3-providers-ws "^4.0.8" + web3-types "^1.10.0" + web3-utils "^4.3.3" + web3-validator "^2.0.6" + +web3-types@^1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/web3-types/-/web3-types-1.10.0.tgz#41b0b4d2dd75e919d5b6f37bf139e29f445db04e" + integrity sha512-0IXoaAFtFc8Yin7cCdQfB9ZmjafrbP6BO0f0KT/khMhXKUpoJ6yShrVhiNpyRBo8QQjuOagsWzwSK2H49I7sbw== + web3-types@^1.3.0, web3-types@^1.6.0, web3-types@^1.7.0, web3-types@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/web3-types/-/web3-types-1.8.0.tgz#d2151fd9e87d711ef5a13079885665b458243e46" @@ -5616,6 +5719,17 @@ web3-utils@^4.0.7, web3-utils@^4.3.0, web3-utils@^4.3.1: web3-types "^1.7.0" web3-validator "^2.0.6" +web3-utils@^4.3.3: + version "4.3.3" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-4.3.3.tgz#e380a1c03a050d3704f94bd08c1c9f50a1487205" + integrity sha512-kZUeCwaQm+RNc2Bf1V3BYbF29lQQKz28L0y+FA4G0lS8IxtJVGi5SeDTUkpwqqkdHHC7JcapPDnyyzJ1lfWlOw== + dependencies: + ethereum-cryptography "^2.0.0" + eventemitter3 "^5.0.1" + web3-errors "^1.3.1" + web3-types "^1.10.0" + web3-validator "^2.0.6" + web3-validator@^2.0.3, web3-validator@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/web3-validator/-/web3-validator-2.0.6.tgz#a0cdaa39e1d1708ece5fae155b034e29d6a19248" @@ -5650,6 +5764,29 @@ web3@^4.13.0: web3-utils "^4.3.1" web3-validator "^2.0.6" +web3@^4.16.0: + version "4.16.0" + resolved "https://registry.yarnpkg.com/web3/-/web3-4.16.0.tgz#1da10d8405bf27a76de6cbbce3de9fa93f7c0449" + integrity sha512-SgoMSBo6EsJ5GFCGar2E/pR2lcR/xmUSuQ61iK6yDqzxmm42aPPxSqZfJz2z/UCR6pk03u77pU8TGV6lgMDdIQ== + dependencies: + web3-core "^4.7.1" + web3-errors "^1.3.1" + web3-eth "^4.11.1" + web3-eth-abi "^4.4.1" + web3-eth-accounts "^4.3.1" + web3-eth-contract "^4.7.2" + web3-eth-ens "^4.4.0" + web3-eth-iban "^4.0.7" + web3-eth-personal "^4.1.0" + web3-net "^4.1.0" + web3-providers-http "^4.2.0" + web3-providers-ws "^4.0.8" + web3-rpc-methods "^1.3.0" + web3-rpc-providers "^1.0.0-rc.4" + web3-types "^1.10.0" + web3-utils "^4.3.3" + web3-validator "^2.0.6" + webauthn-p256@0.0.10: version "0.0.10" resolved "https://registry.yarnpkg.com/webauthn-p256/-/webauthn-p256-0.0.10.tgz#877e75abe8348d3e14485932968edf3325fd2fdd"