Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🔄 synced file(s) with circlefin/modularwallets-web-sdk-internal #6

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/circle-smart-account/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
3 changes: 3 additions & 0 deletions examples/eip-1193/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
VITE_CLIENT_KEY=
VITE_CLIENT_URL=
VITE_PUBLIC_RPC_URL=
69 changes: 69 additions & 0 deletions examples/eip-1193/README.md
Original file line number Diff line number Diff line change
@@ -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.
12 changes: 12 additions & 0 deletions examples/eip-1193/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<h1>EIP-1193 example</h1>
<div id="root"></div>
<script type="module" src="/index.tsx"></script>
</body>
</html>
233 changes: 233 additions & 0 deletions examples/eip-1193/index.tsx
Original file line number Diff line number Diff line change
@@ -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<SmartAccount>()
const [credential, setCredential] = React.useState<P256Credential>(() =>
JSON.parse(localStorage.getItem("credential") || "null")
)

const [web3, setWeb3] = React.useState<Web3>()
const [hash, setHash] = React.useState<Hex>()
const [address, setAddress] = React.useState<string>()
const [personalSignature, setPersonalSignature] = React.useState<string>()
const [typedDataSignature, setTypedDataSignature] = React.useState<string>()

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<HTMLFormElement>) => {
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 (
<>
<input id="username" name="username" placeholder="Username" />
<br />
<button onClick={register}>Register</button>
<button onClick={login}>Login</button>
</>
)
if (!account) return <p>Loading...</p>

return (
<>
<h2>Account</h2>
<p>Address: {account?.address}</p>

<h2>Send Transaction</h2>
<form onSubmit={sendTx}>
<input name="to" placeholder="Address" />
<input name="value" placeholder="Amount (ETH)" />
<button type="submit">Send</button>
</form>
<button onClick={getProviderAddress}>Get address</button>
<button onClick={signPersonalMessage}>SignPersonalMessage</button>
<button onClick={signTypedData}>SignTypedData</button>
{address && <p>Address: {address}</p>}
{personalSignature && (
<p
style={{
width: "100%",
wordWrap: "break-word",
}}
>
Personal Signature: {personalSignature}
</p>
)}
{typedDataSignature && (
<p
style={{
width: "100%",
wordWrap: "break-word",
}}
>
Typed Data Signature: {typedDataSignature}
</p>
)}
{hash && <p>Transaction Hash: {hash}</p>}
</>
)
}

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(<Example />)
23 changes: 23 additions & 0 deletions examples/eip-1193/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
21 changes: 21 additions & 0 deletions examples/eip-1193/tsconfig.json
Original file line number Diff line number Diff line change
@@ -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" }]
}
9 changes: 9 additions & 0 deletions examples/eip-1193/tsconfig.node.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}
10 changes: 10 additions & 0 deletions examples/eip-1193/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <reference types="vite/client" />

interface ImportMetaEnv {
readonly VITE_APP_TITLE: string
// add other env variables here
}

interface ImportMeta {
readonly env: ImportMetaEnv
}
Loading
Loading