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 ` + ${alt} + ` +} 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,