diff --git a/biome.json b/biome.json
index 254e692f..7b593b4a 100644
--- a/biome.json
+++ b/biome.json
@@ -36,8 +36,7 @@
"noUnusedVariables": "error"
},
"complexity": {
- "noForEach": "off",
- "noBannedTypes": "off"
+ "noForEach": "off"
},
"performance": {
"noDelete": "off"
@@ -51,7 +50,6 @@
"noArrayIndexKey": "off",
"noAssignInExpressions": "off",
"noExplicitAny": "off",
- "noConfusingVoidType": "off",
"noRedeclare": "off"
}
}
diff --git a/bun.lockb b/bun.lockb
index 02c39a22..6cc44e02 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/examples/suave-web-demo/.gitignore b/examples/suave-web-demo/.gitignore
new file mode 100644
index 00000000..a547bf36
--- /dev/null
+++ b/examples/suave-web-demo/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/examples/suave-web-demo/bun.lockb b/examples/suave-web-demo/bun.lockb
new file mode 100755
index 00000000..71abfba4
Binary files /dev/null and b/examples/suave-web-demo/bun.lockb differ
diff --git a/examples/suave-web-demo/index.html b/examples/suave-web-demo/index.html
new file mode 100644
index 00000000..74acdc1d
--- /dev/null
+++ b/examples/suave-web-demo/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ SUAVE Web Demo
+
+
+
+
+
+
+
diff --git a/examples/suave-web-demo/package.json b/examples/suave-web-demo/package.json
new file mode 100644
index 00000000..053b298f
--- /dev/null
+++ b/examples/suave-web-demo/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "vite-project",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "viem": "workspace:*"
+ },
+ "devDependencies": {
+ "typescript": "^5.2.2",
+ "vite": "^5.0.0"
+ }
+}
diff --git a/examples/suave-web-demo/public/vite.svg b/examples/suave-web-demo/public/vite.svg
new file mode 100644
index 00000000..e7b8dfb1
--- /dev/null
+++ b/examples/suave-web-demo/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/suave-web-demo/src/components/index.ts b/examples/suave-web-demo/src/components/index.ts
new file mode 100644
index 00000000..a93d26fc
--- /dev/null
+++ b/examples/suave-web-demo/src/components/index.ts
@@ -0,0 +1,5 @@
+export function Logo(href: string, src: string, alt: string, className: string = "logo") {
+ return `
+
+ `
+}
diff --git a/examples/suave-web-demo/src/flashbots_icon.svg b/examples/suave-web-demo/src/flashbots_icon.svg
new file mode 100644
index 00000000..f376fafa
--- /dev/null
+++ b/examples/suave-web-demo/src/flashbots_icon.svg
@@ -0,0 +1,16 @@
+
diff --git a/examples/suave-web-demo/src/main.ts b/examples/suave-web-demo/src/main.ts
new file mode 100644
index 00000000..abeeb305
--- /dev/null
+++ b/examples/suave-web-demo/src/main.ts
@@ -0,0 +1,89 @@
+import './style.css'
+import viteLogo from '/vite.svg'
+import typescriptLogo from './typescript.svg'
+import flashbotsLogo from './flashbots_icon.svg'
+import { setupConnectButton, setupDripFaucetButton, setupSendBidButton } from './suave'
+import { Logo } from './components'
+import { custom, formatEther } from 'viem'
+import { suaveRigil } from 'viem/chains'
+
+document.querySelector('#app')!.innerHTML = `
+
+ ${Logo('https://suave.flashbots.net', flashbotsLogo, 'Flashbots logo')}
+
MEV-Share on SUAVE
+
+
+`
+
+document.querySelector('#footer')!.innerHTML = `
+
+ built with
+ ${Logo('https://vite.org', viteLogo, 'Vite logo', "logo logo-tiny")}
+ +${Logo('https://www.typescriptlang.org', typescriptLogo, 'Typescript logo', "logo logo-tiny")}
+ +${Logo('https://flashbots.net', flashbotsLogo, 'Flashbots logo', "logo logo-tiny")}
+
+`
+
+setupConnectButton(document.querySelector('#connect')!,
+(account, ethereum, err) => {
+ if (err) {
+ console.error(err)
+ alert(err.message)
+ }
+ const suaveWallet = suaveRigil.newWallet({jsonRpcAccount: account, transport: custom(ethereum)})
+ console.log(suaveWallet)
+ const suaveProvider = suaveRigil.newPublicClient(custom(ethereum))
+ suaveProvider.getBalance({ address: account }).then((balance) => {
+ document.querySelector('#status-content')!.innerHTML = `
+
+
SUAVE-ETH balance: ${formatEther(balance)}
+
+ `
+ })
+
+ // setup other buttons once we've connected
+ setupSendBidButton(document.querySelector('#sendBid')!, suaveWallet, (txHash, err) => {
+ if (err) {
+ console.error("error in setupSendBidButton", err)
+ alert(err.message + (err as any).data)
+ }
+ const suaveProvider = suaveRigil.newPublicClient(custom(ethereum))
+ suaveProvider.getTransactionReceipt({hash: txHash}).then((receipt) => {
+ console.log("receipt", receipt)
+ document.querySelector('#status-content')!.innerHTML = `
+
+
bid sent. tx hash: ${txHash}
+
+
+
+ `
+ })
+ console.log("sent bid.", txHash)
+ document.querySelector('#status-content')!.innerHTML = `
+
+
bid sent. tx hash: ${txHash}
+
+ `
+ })
+ setupDripFaucetButton(
+ document.querySelector('#dripFaucet')!,
+ account,
+ (txHash, err) => {
+ if (err) {
+ console.error("error in setupDripFaucetButton", err)
+ alert(err.message + (err as any).data)
+ }
+ console.log("funded account", txHash)
+ })
+})
diff --git a/examples/suave-web-demo/src/style.css b/examples/suave-web-demo/src/style.css
new file mode 100644
index 00000000..aa652c9f
--- /dev/null
+++ b/examples/suave-web-demo/src/style.css
@@ -0,0 +1,119 @@
+:root {
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
+ line-height: 1.5;
+ font-weight: 400;
+
+ color-scheme: light dark;
+ color: rgba(255, 255, 255, 0.87);
+ background-color: #dead;
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+a {
+ font-weight: 500;
+ color: #646cff;
+ text-decoration: inherit;
+}
+
+a:hover {
+ color: #535bf2;
+}
+
+body {
+ margin: 0;
+ display: flex;
+ place-items: center;
+ min-width: 320px;
+ min-height: 100vh;
+}
+
+h1 {
+ font-size: 3.2em;
+ line-height: 1.1;
+}
+
+#app {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 2rem;
+ text-align: center;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+
+.logo.vanilla:hover {
+ filter: drop-shadow(0 0 2em #3178c6aa);
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+button {
+ border-radius: 8px;
+ border: 1px solid transparent;
+ padding: 0.6em 1.2em;
+ font-size: 1em;
+ font-weight: 500;
+ font-family: inherit;
+ background-color: #1a1a1a;
+ cursor: pointer;
+ transition: border-color 0.25s;
+}
+
+button:hover {
+ border-color: #646cff;
+}
+
+button:focus,
+button:focus-visible {
+ outline: 4px auto -webkit-focus-ring-color;
+}
+
+@media (prefers-color-scheme: light) {
+ :root {
+ color: #213547;
+ background-color: #ffffff;
+ }
+
+ a:hover {
+ color: #747bff;
+ }
+
+ button {
+ background-color: #f9f9f9;
+ }
+}
+
+#footer {
+ position: fixed;
+ width: 100%;
+ bottom: 0px;
+ background-color: #9a7d;
+ padding: 4px;
+ color: #fffefeaa;
+}
+
+.logo-tiny {
+ height: 1.3em;
+ padding: 0em;
+ bottom: -4px;
+ position: relative;
+}
\ No newline at end of file
diff --git a/examples/suave-web-demo/src/suave.ts b/examples/suave-web-demo/src/suave.ts
new file mode 100644
index 00000000..f2294e5c
--- /dev/null
+++ b/examples/suave-web-demo/src/suave.ts
@@ -0,0 +1,109 @@
+import { Address, Hex, createPublicClient, createWalletClient, http } from 'viem'
+import { privateKeyToAccount } from 'viem/accounts'
+import { suaveRigil, goerli } from "viem/chains"
+import { MevShareBid } from "../../suave/bids"
+
+// TODO: get this from .env
+// TODO: make zero-config script to generate .env (deploy contracts)
+const KETTLE_ADDRESS: Address = "0xb5feafbdd752ad52afb7e1bd2e40432a485bbb7f"
+const BID_CONTRACT: Address = "0xcb632cC0F166712f09107a7587485f980e524fF6"
+const GOERLI_RPC_URL_HTTP: string = "https://ethereum-goerli.publicnode.com"
+const ADMIN_KEY: Hex = "0x91ab9a7e53c220e6210460b65a7a3bb2ca181412a8a7b43ff336b3df1737ce12"
+
+const goerliWallet = createWalletClient({
+ account: privateKeyToAccount("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"),
+ chain: goerli,
+ transport: http(GOERLI_RPC_URL_HTTP)
+})
+const goerliProvider = createPublicClient({
+ transport: http(GOERLI_RPC_URL_HTTP),
+ chain: goerli
+})
+const suaveAdminWallet = suaveRigil.newWallet({
+ privateKey: ADMIN_KEY,
+ transport: http("http://localhost:8545"),
+})
+
+/** Sets up "connect to wallet" button and holds wallet instance. */
+export function setupConnectButton(element: HTMLButtonElement, onConnect: (account: Hex, ethereum: any, err?: any) => void) {
+ let connected = false
+ let account = null
+ element.innerHTML = `connect to wallet`
+
+ console.log(suaveRigil.id)
+ const setConnected = async (ethereum: any) => {
+ if (connected) return
+ element.innerHTML = `connecting to ${connected}`
+ const accounts = await ethereum.request({ method: 'eth_requestAccounts' })
+ account = accounts[0]
+ element.innerHTML = `connected with ${account}`
+ connected = true
+ onConnect(account, ethereum)
+ }
+
+ if ('ethereum' in window) {
+ element.addEventListener('click', () => setConnected((window as any).ethereum))
+ } else {
+ return onConnect('0x', null, new Error("window.ethereum not found"))
+ }
+}
+
+export function setupSendBidButton(element: HTMLButtonElement, suaveWallet: any, onSendBid: (txHash: Hex, err?: any) => void) {
+ element.innerHTML = `send bid`
+ const sendBid = async (suaveWallet: any) => {
+ // create sample transaction; won't land onchain, but will pass payload validation
+ const sampleTx = {
+ type: "eip1559" as 'eip1559',
+ chainId: 5,
+ nonce: 0,
+ maxBaseFeePerGas: 0x3b9aca00n,
+ maxPriorityFeePerGas: 0x5208n,
+ to: '0x0000000000000000000000000000000000000000' as Address,
+ value: 0n,
+ data: '0xf00ba7' as Hex,
+ }
+ const signedTx = await goerliWallet.signTransaction(sampleTx)
+ console.log("signed goerli tx", signedTx)
+
+ // create bid & send ccr
+ try {
+ const bid = new MevShareBid(
+ 1n + await goerliProvider.getBlockNumber(),
+ signedTx,
+ KETTLE_ADDRESS,
+ BID_CONTRACT,
+ suaveRigil.id
+ )
+ console.log(bid)
+ const ccr = bid.toConfidentialRequest()
+ const txHash = await suaveWallet.sendTransaction(ccr)
+ console.log("sendResult", txHash)
+ // callback with result
+ onSendBid(txHash)
+ } catch (e) {
+ return onSendBid('0x', e)
+ }
+ }
+ element.addEventListener('click', () => sendBid(suaveWallet))
+}
+
+export function setupDripFaucetButton(element: HTMLButtonElement, account: Address, onFaucet: (txHash: Hex, err?: any) => void) {
+ element.innerHTML = `get 0.5 SUAVE-ETH`
+ const getEth = async () => {
+ const fundTxRequest = {
+ to: account,
+ value: 500000000000000000n,
+ gas: 21000n,
+ gasPrice: 10000000000n,
+ type: '0x0' as '0x0',
+ }
+ try {
+ const fundTxHash = await suaveAdminWallet.sendTransaction(fundTxRequest)
+ console.log("fundRes", fundTxHash)
+ onFaucet(fundTxHash)
+ } catch (e) {
+ return onFaucet('0x', e)
+ }
+ }
+ element.addEventListener('click', () => getEth())
+}
\ No newline at end of file
diff --git a/examples/suave-web-demo/src/typescript.svg b/examples/suave-web-demo/src/typescript.svg
new file mode 100644
index 00000000..d91c910c
--- /dev/null
+++ b/examples/suave-web-demo/src/typescript.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/suave-web-demo/src/vite-env.d.ts b/examples/suave-web-demo/src/vite-env.d.ts
new file mode 100644
index 00000000..11f02fe2
--- /dev/null
+++ b/examples/suave-web-demo/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/examples/suave-web-demo/tsconfig.json b/examples/suave-web-demo/tsconfig.json
new file mode 100644
index 00000000..677fa348
--- /dev/null
+++ b/examples/suave-web-demo/tsconfig.json
@@ -0,0 +1,28 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "module": "ESNext",
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+
+ "paths": {
+ "viem": ["../../src"],
+ "viem/*": ["../../src/*"]
+ }
+ },
+ "include": ["src"]
+}
diff --git a/examples/suave/bids/index.ts b/examples/suave/bids/index.ts
index 9500df0e..34586a93 100644
--- a/examples/suave/bids/index.ts
+++ b/examples/suave/bids/index.ts
@@ -5,8 +5,8 @@ import {
encodeFunctionData,
toHex,
} from 'viem'
-import precompiles from 'viem/chains/suave/precompiles'
-import { SuaveTxTypes, TransactionRequestSuave } from 'viem/chains/suave/types'
+import { suaveRigil } from '../../../src/chains'
+import { SuaveTxTypes, TransactionRequestSuave } from '../../../src/chains/suave/types'
import MevShareBidContract from '../contracts/out/bids.sol/MevShareBidContract.json'
export interface MevShareBid {
@@ -35,7 +35,7 @@ export class MevShareBid {
this.chainId = chainId
this.allowedPeekers = [
// no idea what I'm doing here
- precompiles.ANYALLOWED,
+ suaveRigil.contracts.ANYALLOWED.address,
]
this.allowedStores = []
}
diff --git a/examples/suave/index.ts b/examples/suave/index.ts
index e8848db0..ac2f9d9e 100644
--- a/examples/suave/index.ts
+++ b/examples/suave/index.ts
@@ -31,11 +31,14 @@ const goerliProvider = createPublicClient({
chain: goerli,
transport: http(GOERLI_RPC_URL_HTTP),
})
-const adminWallet = suaveRigil.newWallet(PRIVATE_KEY, http(SUAVE_RPC_URL_HTTP))
-const wallet = suaveRigil.newWallet(
- '0x01000070530220062104600650003002001814120800043ff33603df10300012',
- http(SUAVE_RPC_URL_HTTP),
-)
+const adminWallet = suaveRigil.newWallet({
+ transport: http(SUAVE_RPC_URL_HTTP),
+ privateKey: PRIVATE_KEY,
+})
+const wallet = suaveRigil.newWallet({
+ transport: http(SUAVE_RPC_URL_HTTP),
+ privateKey: '0x01000070530220062104600650003002001814120800043ff33603df10300012',
+})
console.log('admin', adminWallet.account.address)
console.log('wallet', wallet.account.address)
diff --git a/src/chains/definitions/suaveRigil.ts b/src/chains/definitions/suaveRigil.ts
index b308b746..cf538d89 100644
--- a/src/chains/definitions/suaveRigil.ts
+++ b/src/chains/definitions/suaveRigil.ts
@@ -1,5 +1,6 @@
+import type { Address } from 'abitype'
import { createPublicClient } from '../../clients/createPublicClient.js'
-import { type Transport } from '../../clients/transports/createTransport.js'
+import type { Transport } from '../../clients/transports/createTransport.js'
import { http } from '../../clients/transports/http.js'
import { type Hex } from '../../types/misc.js'
import { defineChain } from '../../utils/chain.js'
@@ -38,17 +39,99 @@ export const suaveRigil = /*#__PURE__*/ defineChain(
url: testnetExplorerUrl,
},
},
- contracts: {},
+ contracts: {
+ ANYALLOWED: {
+ address: '0xc8df3686b4afb2bb53e60eae97ef043fe03fb829' as Address,
+ },
+ IS_CONFIDENTIAL_ADDR: {
+ address: '0x0000000000000000000000000000000042010000' as Address,
+ },
+ BUILD_ETH_BLOCK: {
+ address: '0x0000000000000000000000000000000042100001' as Address,
+ },
+ CONFIDENTIAL_INPUTS: {
+ address: '0x0000000000000000000000000000000042010001' as Address,
+ },
+ CONFIDENTIAL_RETRIEVE: {
+ address: '0x0000000000000000000000000000000042020001' as Address,
+ },
+ CONFIDENTIAL_STORE: {
+ address: '0x0000000000000000000000000000000042020000' as Address,
+ },
+ ETHCALL: {
+ address: '0x0000000000000000000000000000000042100003' as Address,
+ },
+ EXTRACT_HINT: {
+ address: '0x0000000000000000000000000000000042100037' as Address,
+ },
+ FETCH_BIDS: {
+ address: '0x0000000000000000000000000000000042030001' as Address,
+ },
+ FILL_MEV_SHARE_BUNDLE: {
+ address: '0x0000000000000000000000000000000043200001' as Address,
+ },
+ NEW_BID: {
+ address: '0x0000000000000000000000000000000042030000' as Address,
+ },
+ SIGN_ETH_TRANSACTION: {
+ address: '0x0000000000000000000000000000000040100001' as Address,
+ },
+ SIMULATE_BUNDLE: {
+ address: '0x0000000000000000000000000000000042100000' as Address,
+ },
+ SUBMIT_BUNDLE_JSON_RPC: {
+ address: '0x0000000000000000000000000000000043000001' as Address,
+ },
+ SUBMIT_ETH_BLOCK_BID_TO_RELAY: {
+ address: '0x0000000000000000000000000000000042100002' as Address,
+ },
+ },
testnet: true,
- newWallet: (privateKey: Hex, transport?: Transport) =>
- getSuaveWallet(
- {
- transport: transport ?? defaultTransport,
- chain: suaveRigil,
+ /** Creates a new SUAVE Wallet instance.
+ * @example
+ // new wallet with a private key
+ import { suaveRigil } from 'viem/chains'
+ import { http } from 'viem'
+ const wallet = suaveRigil.newWallet({
+ transport: http('http://localhost:8545'),
+ privateKey: '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
+ })
+ * @example
+ // new wallet with a json-rpc account using window.ethereum
+ // it's assumed that the wallet is already connected to SUAVE chain
+ import { suaveRigil } from 'viem/chains'
+ import { custom } from 'viem'
+ const wallet = suaveRigil.newWallet({
+ transport: custom(window.ethereum),
+ jsonRpcAccount: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
+ })
+ */
+ newWallet: (params: {
+ privateKey?: Hex
+ jsonRpcAccount?: Hex
+ transport?: Transport
+ }) =>
+ getSuaveWallet({
+ transport: params.transport ?? defaultTransport,
+ chain: suaveRigil,
+ privateKey: params.privateKey,
+ jsonRpcAccount: params.jsonRpcAccount && {
+ address: params.jsonRpcAccount,
+ type: 'json-rpc',
},
- privateKey,
- ),
- newPublicClient: (transport?: any) =>
+ }),
+ /** Creates a new SUAVE Public Client instance.
+ * @example
+ import { suaveRigil } from 'viem/chains'
+ import { http } from 'viem'
+ const client = suaveRigil.newPublicClient(http('http://localhost:8545'))
+ * @example
+ // using window.ethereum
+ import { suaveRigil } from 'viem/chains'
+ import { custom } from 'viem'
+ const client = suaveRigil.newPublicClient(custom(window.ethereum))
+ */
+ newPublicClient: (transport?: Transport) =>
createPublicClient({
transport: transport ?? defaultTransport,
chain: suaveRigil,
diff --git a/src/chains/suave/precompiles.ts b/src/chains/suave/precompiles.ts
deleted file mode 100644
index 1a8d0550..00000000
--- a/src/chains/suave/precompiles.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import type { Address } from 'abitype'
-
-// TODO: is it possible to generate this file from the contracts?
-export default {
- ANYALLOWED: '0xc8df3686b4afb2bb53e60eae97ef043fe03fb829' as Address,
- IS_CONFIDENTIAL_ADDR: '0x0000000000000000000000000000000042010000' as Address,
- BUILD_ETH_BLOCK: '0x0000000000000000000000000000000042100001' as Address,
- CONFIDENTIAL_INPUTS: '0x0000000000000000000000000000000042010001' as Address,
- CONFIDENTIAL_RETRIEVE:
- '0x0000000000000000000000000000000042020001' as Address,
- CONFIDENTIAL_STORE: '0x0000000000000000000000000000000042020000' as Address,
- ETHCALL: '0x0000000000000000000000000000000042100003' as Address,
- EXTRACT_HINT: '0x0000000000000000000000000000000042100037' as Address,
- FETCH_BIDS: '0x0000000000000000000000000000000042030001' as Address,
- FILL_MEV_SHARE_BUNDLE:
- '0x0000000000000000000000000000000043200001' as Address,
- NEW_BID: '0x0000000000000000000000000000000042030000' as Address,
- SIGN_ETH_TRANSACTION: '0x0000000000000000000000000000000040100001' as Address,
- SIMULATE_BUNDLE: '0x0000000000000000000000000000000042100000' as Address,
- SUBMIT_BUNDLE_JSON_RPC:
- '0x0000000000000000000000000000000043000001' as Address,
- SUBMIT_ETH_BLOCK_BID_TO_RELAY:
- '0x0000000000000000000000000000000042100002' as Address,
-}
diff --git a/src/chains/suave/wallet.ts b/src/chains/suave/wallet.ts
index ce20f0e1..ed8067fa 100644
--- a/src/chains/suave/wallet.ts
+++ b/src/chains/suave/wallet.ts
@@ -2,10 +2,12 @@ import { privateKeyToAccount } from '../../accounts/privateKeyToAccount.js'
import { sign } from '../../accounts/utils/sign.js'
import {
type Chain,
+ type JsonRpcAccount,
type PrivateKeyAccount,
type Transport,
type WalletClient,
createWalletClient,
+ hexToSignature,
keccak256,
} from '../../index.js'
import { type Hex } from '../../types/misc.js'
@@ -20,6 +22,18 @@ import {
type TransactionSerializableSuave,
} from './types.js'
+function formatSignature(signature: {
+ r: Hex
+ s: Hex
+ v: bigint
+}): { r: Hex; s: Hex; v: bigint } {
+ return {
+ r: signature.r,
+ s: signature.s,
+ v: signature.v === 27n ? 0n : 1n,
+ }
+}
+
async function signConfidentialComputeRecord(
transaction: TransactionSerializableSuave,
privateKey: Hex,
@@ -30,31 +44,57 @@ async function signConfidentialComputeRecord(
)
}
const serialized = serializeConfidentialComputeRecord(transaction)
- const { r, s, v } = await sign({ hash: keccak256(serialized), privateKey })
- const signature = {
- r,
- s,
- v: v === 27n ? 0n : 1n,
- }
+ const signature = await sign({ hash: keccak256(serialized), privateKey })
return {
...transaction,
- ...signature,
+ ...formatSignature(signature),
+ }
+}
+
+function getSigningMethod(transport: any, privateKey?: Hex, address?: Hex) {
+ if (transport.type === 'custom') {
+ return async (txRequest: TransactionSerializableSuave) => {
+ const rawSignature = await transport.request({
+ method: 'eth_sign',
+ params: [
+ address,
+ keccak256(serializeConfidentialComputeRecord(txRequest)),
+ ],
+ })
+ const parsedSignature = hexToSignature(rawSignature)
+ return formatSignature(parsedSignature)
+ }
+ } else {
+ if (!privateKey) {
+ throw new Error('privateKey is required for non-custom transports')
+ }
+ return async (txRequest: TransactionSerializableSuave) => {
+ return await signConfidentialComputeRecord(txRequest, privateKey)
+ }
}
}
export function getSuaveWallet<
TTransport extends Transport,
TChain extends Chain,
->(
- params: { transport: TTransport; chain: TChain },
- privateKey: Hex,
-): WalletClient<
+>(params: {
+ transport: TTransport
+ chain: TChain
+ jsonRpcAccount?: JsonRpcAccount
+ privateKey?: Hex
+}): WalletClient<
TTransport,
TChain,
PrivateKeyAccount // TODO: generalize account types (required to make metamask transport work)
> {
+ if (!params.jsonRpcAccount && !params.privateKey) {
+ throw new Error("Must provide either 'jsonRpcAccount' or 'privateKey'")
+ }
+ if (params.jsonRpcAccount && params.privateKey) {
+ throw new Error("Cannot provide both 'jsonRpcAccount' and 'privateKey'")
+ }
return createWalletClient({
- account: privateKey ? privateKeyToAccount(privateKey) : undefined,
+ account: params.jsonRpcAccount ?? privateKeyToAccount(params.privateKey!),
transport: params.transport,
chain: params.chain,
}).extend((client) => ({
@@ -80,16 +120,21 @@ export function getSuaveWallet<
async signTransaction(txRequest: TransactionRequestSuave) {
if (txRequest.type === SuaveTxTypes.ConfidentialRequest) {
const confidentialInputs = txRequest.confidentialInputs || '0x'
+
+ // determine signing method based on transport type
+ const signCcr = getSigningMethod(
+ client.transport,
+ params.privateKey,
+ client.account.address,
+ )
const presignTx = {
...txRequest,
type: SuaveTxTypes.ConfidentialRecord,
confidentialInputsHash: keccak256(confidentialInputs),
chainId: txRequest.chainId ?? suaveRigil.id,
}
- const { r, s, v } = await signConfidentialComputeRecord(
- presignTx,
- privateKey,
- )
+ const sig = await signCcr(presignTx)
+ const { r, s, v } = sig
return serializeConfidentialComputeRequest({
...presignTx,
confidentialInputs,