@@ -19,7 +19,7 @@ import type {
1919 UserOperationReceipt ,
2020} from 'viem/account-abstraction' ;
2121import { createWebAuthnCredential , toWebAuthnAccount } from 'viem/account-abstraction' ;
22- import type { EIP1193EventMap , EIP1193Parameters , EIP1193RequestFn } from 'viem/types/eip1193' ;
22+ import type { EIP1193EventMap , EIP1193Parameters , EIP1193RequestFn , EIP1474Methods } from 'viem/types/eip1193' ;
2323import type { GianoSmartAccountImplementation } from './account' ;
2424import { toGianoSmartAccount } from './account' ;
2525import { GianoEntryPointAddress , GianoEntryPointVersion } from './giano-entry-point'
@@ -32,11 +32,22 @@ import { withValidation } from './provider-injection/_with-validation'
3232import { v4 as uuidv4 } from 'uuid' ;
3333import { getWebAuthnAccount } from './account'
3434import { ensureSmartAccountIsDeployed , isSmartAccountDeployed } from './account/deployment'
35+ import { GianoError } from './giano-error'
36+ import { TransactionRequest } from 'viem'
37+ import { ExactPartial } from 'viem'
38+ import { RpcTransactionRequest } from 'viem'
3539
3640export enum ChainType {
3741 HARDHAT = 0 , // NOTE: This is just a placeholder for now
3842}
3943
44+ type PrepareUserOperationOptions = Partial <
45+ Omit <
46+ SendUserOperationParameters < SmartAccount < GianoSmartAccountImplementation > , undefined , TransactionRequest [ ] > ,
47+ 'account' | 'calls' | 'callData'
48+ >
49+ >
50+
4051export const isChainType = ( x : unknown ) : x is ChainType => {
4152 return typeof x === 'number' && Object . values ( ChainType ) . includes ( x )
4253}
@@ -62,11 +73,53 @@ type EventListeners = {
6273 [ E in keyof EIP1193EventMap ] : Set < EventHandler < E > > ;
6374} ;
6475
65- type GianoProviderCustomMethods = [ {
66- Method : 'waitForUserOperationReceipt' ;
67- Parameters : [ hash : Hash ] ;
68- ReturnType : UserOperationReceipt ;
69- } ]
76+ type GianoProviderCustomMethods = [
77+ {
78+ Method : 'waitForUserOperationReceipt'
79+ Parameters : [ hash : Hash ]
80+ ReturnType : UserOperationReceipt
81+ } ,
82+ {
83+ Method : 'signed_eth_call'
84+ Parameters : readonly [ call : ExactPartial < RpcTransactionRequest > ]
85+ ReturnType : Hex
86+ } ,
87+ {
88+ Method : 'eth_prepareUserOperation'
89+ Parameters : [ calls : Call [ ] , options ?: PrepareUserOperationOptions ]
90+ ReturnType : UserOperation < GianoEntryPointVersion >
91+ } ,
92+ {
93+ Method : 'eth_signUserOperation'
94+ Parameters : [ userOp : UserOperation < GianoEntryPointVersion > ]
95+ ReturnType : Hex
96+ } ,
97+ {
98+ Method : 'eth_sendSignedUserOperation'
99+ Parameters : [ signedUserOp : UserOperation < GianoEntryPointVersion > ]
100+ ReturnType : Hash
101+ } ,
102+ ]
103+
104+ export type ProviderRequestMethod <
105+ T extends EIP1474Methods | GianoProviderCustomMethods ,
106+ Name extends T [ number ] [ 'Method' ]
107+ > = (
108+ params : Extract <
109+ T [ number ] ,
110+ { Method : Name }
111+ > [ 'Parameters' ]
112+ ) => Promise < Extract < T [ number ] , { Method : Name } > [ 'ReturnType' ] >
113+
114+ /**
115+ * EIP1474 methods as optional
116+ * Giano custom methods as required
117+ **/
118+ type GianoProviderMethodsMap = Partial < {
119+ [ T in EIP1474Methods [ number ] as T [ 'Method' ] ] : ProviderRequestMethod < EIP1474Methods , T [ 'Method' ] >
120+ } > & Required < {
121+ [ T in GianoProviderCustomMethods [ number ] as T [ 'Method' ] ] : ProviderRequestMethod < GianoProviderCustomMethods , T [ 'Method' ] >
122+ } >
70123
71124export type GianoProvider = EIP1193Provider & {
72125 request : EIP1193RequestFn < GianoProviderCustomMethods >
@@ -93,7 +146,7 @@ export const createGianoProvider = (options: CreateGianoProviderParams) => {
93146 const eventListeners : Partial < EventListeners > = { } ;
94147
95148 const submitUserOperation = async (
96- userOpRequest : SendUserOperationParameters < SmartAccount < GianoSmartAccountImplementation > , undefined , Call [ ] > & {
149+ userOpRequest : SendUserOperationParameters < SmartAccount < GianoSmartAccountImplementation > , undefined , TransactionRequest [ ] > & {
97150 account : SmartAccount < GianoSmartAccountImplementation >
98151 }
99152 ) => {
@@ -142,29 +195,30 @@ export const createGianoProvider = (options: CreateGianoProviderParams) => {
142195 eventListeners [ event ] ?. forEach ( ( listener ) => listener ( payload ) ) ;
143196 } ;
144197
145- const methods : Record < string , ( params ?: any ) => any > = {
198+ const methods = {
146199 eth_accounts : async ( ) => {
147200 return smartAccount ? [ await smartAccount . getAddress ( ) ] : [ ] ;
148201 } ,
149202 eth_chainId : async ( ) => {
150203 return `0x${ chain ! . id . toString ( 16 ) } ` ;
151204 } ,
152- eth_call : async ( [ call , blockTag ] ) => {
153- console . log ( 'eth_call' , { call, blockTag } ) ;
154- return client ! . request ( { method : 'eth_call' , params : [ call , blockTag ] } ) ;
205+ eth_call : async ( params ) => {
206+ console . log ( 'eth_call' , params )
207+
208+ return client ! . request ( { method : 'eth_call' , params } )
155209 } ,
156- signed_eth_call : async ( [ call , blockTag ] ) => {
210+ signed_eth_call : async ( params ) => {
157211 // Check if smartAccount is available before proceeding with authenticated calls
158212 if ( ! smartAccount ) {
159213 console . warn ( 'Smart account not available, falling back to regular call' )
160- return client ! . request ( { method : 'eth_call' , params : [ call , blockTag ] } )
214+ return client ! . request ( { method : 'eth_call' , params } )
161215 }
162216
163217 // Check if the account is deployed before attempting authenticated calls
164218 const isDeployed = await isSmartAccountDeployed ( client ! , smartAccount )
165219 if ( ! isDeployed ) {
166220 console . warn ( 'Smart account not deployed yet, falling back to regular call' )
167- return client ! . request ( { method : 'eth_call' , params : [ call , blockTag ] } )
221+ return client ! . request ( { method : 'eth_call' , params } )
168222 }
169223
170224 // if the lifetime of the static signature is not known, fetch and cache it
@@ -182,33 +236,52 @@ export const createGianoProvider = (options: CreateGianoProviderParams) => {
182236 staticSignature = signature
183237 staticSignatureSignedAt = signedAt
184238 }
239+
240+ const [ transaction ] = params
241+
242+ if ( ! transaction . to ) {
243+ throw new GianoError ( 'signed_eth_call: `transaction.to` is required' )
244+ }
245+ if ( ! transaction . data ) {
246+ throw new GianoError ( 'signed_eth_call: `transaction.data` is required' )
247+ }
248+
185249 // encode the intended call and forward it to the Giano account contract
186250 const result = await client ! . readContract ( {
187251 abi : smartAccount . abi ,
188252 address : smartAccount . address ,
189253 functionName : 'signedStaticCall' ,
190- args : [ { target : call . to , data : call . data ! , signedAt : BigInt ( staticSignatureSignedAt ) , signature : staticSignature } ] ,
254+ args : [ {
255+ target : transaction . to ,
256+ data : transaction . data ,
257+ signedAt : BigInt ( staticSignatureSignedAt ) ,
258+ signature : staticSignature ,
259+ } ] ,
191260 } )
192261 return result
193262 } ,
194- wallet_addEthereumChain : ( ) => {
263+ wallet_addEthereumChain : async ( [ chain ] ) => {
195264 //TODO: implement
265+ return null
196266 } ,
197- wallet_revokePermissions : ( ) => {
198- smartAccount = null ;
267+ wallet_revokePermissions : async ( [ permissions ] ) => {
268+ // ignoring permissions, revoking all
269+ // we only support one connected account per provider instance
270+ smartAccount = null
199271 emit ( 'accountsChanged' , [ ] )
200272 emit ( 'disconnect' , {
201273 code : 4900 ,
202274 name : 'Disconnected' ,
203275 message : 'User disconnected' ,
204276 details : 'User disconnected' ,
205- } ) ;
277+ } )
278+
279+ return null
206280 } ,
207- wallet_switchEthereumChain : ( params ) => {
208- const [ { chainId : chainIdHex } ] = params ;
281+ wallet_switchEthereumChain : async ( [ { chainId : chainIdHex } ] ) => {
209282 const chainId = parseInt ( chainIdHex , 16 ) ;
210283 if ( chainId === chain ?. id ) {
211- return ;
284+ return null
212285 }
213286 const newChain = chains . find ( ( chain ) => chain . id === chainId ) ;
214287 if ( ! newChain ) {
@@ -223,7 +296,8 @@ export const createGianoProvider = (options: CreateGianoProviderParams) => {
223296 transport = newTransport ;
224297 client = createPublicClient ( { transport, chain } ) ;
225298
226- emit ( 'chainChanged' , `0x${ chainId . toString ( 16 ) } ` ) ;
299+ emit ( 'chainChanged' , `0x${ chainId . toString ( 16 ) } ` )
300+ return null
227301 } ,
228302 eth_requestAccounts : async ( ) => {
229303 if ( smartAccount ) {
@@ -316,12 +390,24 @@ export const createGianoProvider = (options: CreateGianoProviderParams) => {
316390 emit ( 'accountsChanged' , [ smartAccountAddress ] ) ;
317391 return [ smartAccountAddress ] ;
318392 } ,
319- eth_sendTransaction : async ( calls : Call [ ] ) : Promise < Hash > => {
393+ eth_sendTransaction : async ( [ transaction ] ) => {
320394 if ( ! smartAccount ) {
321- throw new Error ( 'Giano not connected' ) ;
395+ throw new GianoError ( 'Giano not connected' )
322396 }
323397
324- return await submitUserOperation ( { calls, account : smartAccount } ) ;
398+ if ( ! transaction . to ) {
399+ throw new GianoError ( 'eth_sendTransaction: `to` field is required' ) ;
400+ }
401+
402+ const calls : Call [ ] = [ {
403+ to : transaction . to ,
404+ value : transaction . value === undefined
405+ ? 0n
406+ : BigInt ( transaction . value ) ,
407+ data : transaction . data ?? '0x' ,
408+ } ]
409+
410+ return await submitUserOperation ( { calls, account : smartAccount } )
325411 } ,
326412 waitForUserOperationReceipt : async ( [ hash ] : [ Hash ] ) => {
327413 return bundler . waitForUserOperationReceipt ( { hash } ) ;
@@ -351,18 +437,6 @@ export const createGianoProvider = (options: CreateGianoProviderParams) => {
351437 // eth_sign expects raw message hash, not prefixed
352438 return smartAccount . signMessage ( { message } ) ;
353439 } ,
354- eth_signTypedData : async ( [ address , typedData ] : [ Address , any ] ) => {
355- console . log ( 'eth_signTypedData' , { address, typedData } ) ;
356- if ( ! smartAccount ) {
357- throw new Error ( 'Giano not connected' ) ;
358- }
359- const accountAddress = await smartAccount . getAddress ( ) ;
360- if ( address . toLowerCase ( ) !== accountAddress . toLowerCase ( ) ) {
361- throw new Error ( 'Address mismatch' ) ;
362- }
363-
364- return smartAccount . signTypedData ( typedData ) ;
365- } ,
366440 eth_signTypedData_v4 : async ( [ address , typedData ] : [ Address , string ] ) => {
367441 console . log ( 'eth_signTypedData_v4' , { address, typedData } ) ;
368442 if ( ! smartAccount ) {
@@ -396,7 +470,7 @@ export const createGianoProvider = (options: CreateGianoProviderParams) => {
396470 ...signedUserOp
397471 } ) ;
398472 } ,
399- eth_prepareUserOperation : async ( [ calls , options = { } ] : [ Call [ ] , any ] ) => {
473+ eth_prepareUserOperation : async ( [ calls , options = { } ] ) => {
400474 console . log ( 'eth_prepareUserOperation' , { calls, options } ) ;
401475 if ( ! smartAccount ) {
402476 throw new Error ( 'Giano not connected' ) ;
@@ -425,7 +499,7 @@ export const createGianoProvider = (options: CreateGianoProviderParams) => {
425499 maxPriorityFeePerGas : options . maxPriorityFeePerGas || parseGwei ( '400' ) ,
426500 } ;
427501 } ,
428- } ;
502+ } satisfies GianoProviderMethodsMap
429503
430504 methods . wallet_switchEthereumChain ( [ { chainId : initialChainId . toString ( 16 ) } ] ) ;
431505
0 commit comments