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 (
+ <>
+
+
+ Register
+ Login
+ >
+ )
+ if (!account) return Loading...
+
+ return (
+ <>
+ Account
+ Address: {account?.address}
+
+ Send Transaction
+
+ Get address
+ SignPersonalMessage
+ SignTypedData
+ {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"