Skip to content

Commit f2c14c5

Browse files
committed
add proper typings to provider methods and fix all errors
1 parent 9c61b70 commit f2c14c5

File tree

1 file changed

+114
-40
lines changed

1 file changed

+114
-40
lines changed

packages/connector/src/provider.ts

Lines changed: 114 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import type {
1919
UserOperationReceipt,
2020
} from 'viem/account-abstraction';
2121
import { 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';
2323
import type { GianoSmartAccountImplementation } from './account';
2424
import { toGianoSmartAccount } from './account';
2525
import { GianoEntryPointAddress, GianoEntryPointVersion } from './giano-entry-point'
@@ -32,11 +32,22 @@ import { withValidation } from './provider-injection/_with-validation'
3232
import { v4 as uuidv4 } from 'uuid';
3333
import { getWebAuthnAccount } from './account'
3434
import { 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

3640
export 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+
4051
export 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

71124
export 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

Comments
 (0)