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

ID-913 ZkEvm provider events [NO-CHANGELOG] #580

Merged
merged 7 commits into from
Jul 21, 2023
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ enum EthereumParamType {
interface EthereumParam {
name: string;
type?: EthereumParamType;
default?: string;
}

interface EthereumMethod {
Expand All @@ -37,15 +38,15 @@ const EthereumMethods: EthereumMethod[] = [
name: 'eth_getBalance',
params: [
{ name: 'address' },
{ name: 'blockNumber/tag' },
{ name: 'blockNumber/tag', default: 'latest' },
],
},
{
name: 'eth_getStorageAt',
params: [
{ name: 'address' },
{ name: 'position' },
{ name: 'blockNumber' },
{ name: 'blockNumber', default: 'latest' },
],
},
{
Expand All @@ -58,7 +59,7 @@ const EthereumMethods: EthereumMethod[] = [
name: 'eth_call',
params: [
{ name: 'transaction' },
{ name: 'blockNumber/tag' },
{ name: 'blockNumber/tag', default: 'latest' },
],
},
{ name: 'eth_blockNumber' },
Expand Down Expand Up @@ -93,7 +94,6 @@ const EthereumMethods: EthereumMethod[] = [
name: 'eth_getTransactionCount',
params: [
{ name: 'address' },
{ name: 'blockNumber' },
],
},
];
Expand Down Expand Up @@ -147,10 +147,8 @@ function Request({ showRequest, setShowRequest }: RequestProps) {
addMessage(selectedEthMethod?.name, result);
handleClose();
} catch (err) {
if (err instanceof Error) {
addMessage('Request', err);
handleClose();
}
addMessage('Request', err);
handleClose();
}
} else {
setInvalid(true);
Expand All @@ -164,7 +162,7 @@ function Request({ showRequest, setShowRequest }: RequestProps) {
console.error('Invalid eth method');
} else {
setSelectedEthMethod(ethMethod);
setParams(Array(ethMethod.params?.length || 0).fill(''));
setParams(ethMethod.params ? ethMethod.params.map((param) => param.default || '') : []);
}
};

Expand Down Expand Up @@ -199,6 +197,7 @@ function Request({ showRequest, setShowRequest }: RequestProps) {
<Form.Control
key={param.name}
type="text"
value={param.default}
onChange={(e) => {
const newParams = [...params];
newParams[index] = e.target.value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ const getPassportConfig = (environment: EnvironmentNames): PassportModuleConfigu
},
}),
zkEvmRpcUrl: 'https://zkevm-rpc.dev.x.immutable.com',
zkEvmChainId: 'eip155:13403',
zkEvmChainId: 'eip155:13413',
relayerUrl: 'https://evm-relayer.dev.imtbl.com',
indexerMrBasePath: 'https://indexer-mr.dev.imtbl.com',
orderBookMrBasePath: 'https://order-book-mr.dev.imtbl.com',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export function PassportProvider({
} finally {
setIsLoading(false);
}
}, [passportClient, setIsLoading]);
}, [addMessage, passportClient, setIsLoading]);

const providerValues = useMemo(() => ({
imxProvider,
Expand Down
7 changes: 7 additions & 0 deletions packages/passport/sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ export {
JsonRpcResponsePayload,
JsonRpcRequestCallback,
Provider,
ProviderEventNames,
AccountsChangedEvent,
} from './zkEvm/types';
export {
JsonRpcError,
ProviderErrorCode,
RpcErrorCode,
} from './zkEvm/JsonRpcError';
export {
UserProfile,
Networks,
Expand Down
15 changes: 12 additions & 3 deletions packages/passport/sdk/src/zkEvm/JsonRpcError.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
/**
* ProviderErrors should take priority over RpcErrorCodes
*/
export enum ProviderErrorCode {
USER_REJECTED_REQUEST = 4001,
UNAUTHORIZED = 4100,
UNSUPPORTED_METHOD = 4200,
DISCONNECTED = 4900,
}

export enum RpcErrorCode {
RPC_SERVER_ERROR = -32000,
INVALID_REQUEST = -32600,
METHOD_NOT_FOUND = -32601,
INVALID_PARAMS = -32602,
INTERNAL_ERROR = -32603,
UNAUTHORIZED = -32604,
PARSE_ERROR = -32700,
}

export class JsonRpcError extends Error {
public readonly message: string;

public readonly code: RpcErrorCode;
public readonly code: ProviderErrorCode | RpcErrorCode;

constructor(code: RpcErrorCode, message: string) {
constructor(code: ProviderErrorCode | RpcErrorCode, message: string) {
super(message);
this.message = message;
this.code = code;
Expand Down
1 change: 0 additions & 1 deletion packages/passport/sdk/src/zkEvm/rpcMethods/index.ts

This file was deleted.

13 changes: 0 additions & 13 deletions packages/passport/sdk/src/zkEvm/rpcMethods/types.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { JsonRpcProvider, TransactionRequest } from '@ethersproject/providers';
import { getSignedMetaTransactions } from '../walletHelpers';
import { ethSendTransaction } from './eth_sendTransaction';
import { mockUserZkEvm } from '../../test/mocks';
import { RelayerClient } from '../relayerClient';
import { PassportConfiguration } from '../../config';
import { retryWithDelay } from '../../network/retry';
import { RelayerTransaction, RelayerTransactionStatus } from '../types';
import { JsonRpcError, RpcErrorCode } from '../JsonRpcError';
import { getSignedMetaTransactions } from './walletHelpers';
import { sendTransaction } from './sendTransaction';
import { mockUserZkEvm } from '../test/mocks';
import { RelayerClient } from './relayerClient';
import { PassportConfiguration } from '../config';
import { retryWithDelay } from '../network/retry';
import { RelayerTransaction, RelayerTransactionStatus } from './types';
import { JsonRpcError, RpcErrorCode } from './JsonRpcError';

jest.mock('@ethersproject/providers');
jest.mock('../walletHelpers');
jest.mock('../../network/retry');
jest.mock('./walletHelpers');
jest.mock('../network/retry');

describe('ethSendTransaction', () => {
describe('sendTransaction', () => {
const signedTransaction = 'signedTransaction123';
const signedTransactions = 'signedTransactions123';
const relayerTransactionId = 'relayerTransactionId123';
Expand Down Expand Up @@ -54,7 +54,7 @@ describe('ethSendTransaction', () => {
hash: transactionHash,
} as RelayerTransaction);

const result = await ethSendTransaction({
const result = await sendTransaction({
params: [transactionRequest],
magicProvider,
jsonRpcProvider: jsonRpcProvider as JsonRpcProvider,
Expand All @@ -72,7 +72,7 @@ describe('ethSendTransaction', () => {
status: RelayerTransactionStatus.FAILED,
} as RelayerTransaction);

await expect(ethSendTransaction({
await expect(sendTransaction({
params: [transactionRequest],
magicProvider,
jsonRpcProvider: jsonRpcProvider as JsonRpcProvider,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,37 @@
import {
ExternalProvider,
JsonRpcProvider,
TransactionRequest,
Web3Provider,
} from '@ethersproject/providers';
import { getNonce, getSignedMetaTransactions, chainIdNumber } from '../walletHelpers';
import { MetaTransaction, RelayerTransactionStatus } from '../types';
import { EthMethodWithAuthParams } from './types';
import { JsonRpcError, RpcErrorCode } from '../JsonRpcError';
import { retryWithDelay } from '../../network/retry';
import { getNonce, getSignedMetaTransactions, chainIdNumber } from './walletHelpers';
import { MetaTransaction, RelayerTransactionStatus } from './types';
import { JsonRpcError, RpcErrorCode } from './JsonRpcError';
import { retryWithDelay } from '../network/retry';
import { PassportConfiguration } from '../config';
import { RelayerClient } from './relayerClient';
import { UserZkEvm } from '../types';

const MAX_TRANSACTION_HASH_RETRIEVAL_RETRIES = 30;
const TRANSACTION_HASH_RETRIEVAL_WAIT = 1000;

export const ethSendTransaction = async ({
export type EthSendTransactionParams = {
magicProvider: ExternalProvider;
jsonRpcProvider: JsonRpcProvider;
config: PassportConfiguration;
relayerClient: RelayerClient;
user: UserZkEvm;
params: Array<any>;
};

export const sendTransaction = async ({
params,
magicProvider,
jsonRpcProvider,
relayerClient,
config,
user,
}: EthMethodWithAuthParams): Promise<string> => {
}: EthSendTransactionParams): Promise<string> => {
const transactionRequest: TransactionRequest = params[0];
if (!transactionRequest.to) {
throw new JsonRpcError(RpcErrorCode.INVALID_PARAMS, 'eth_sendTransaction requires a "to" field');
Expand Down
33 changes: 33 additions & 0 deletions packages/passport/sdk/src/zkEvm/typedEventEmitter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import TypedEventEmitter from './typedEventEmitter';

type TestEvents = {
testEvent1: [Array<number>];
testEvent2: [{ id: number }],
};

describe('TypedEventEmitter', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥

it('should be able to emit and listen to events', () => {
const eventEmitter = new TypedEventEmitter<TestEvents>();

const testEvent1Handler = jest.fn();
const testEvent2Handler = jest.fn();

eventEmitter.on('testEvent1', testEvent1Handler);
eventEmitter.on('testEvent2', testEvent2Handler);

eventEmitter.emit('testEvent1', [1, 2, 3]);
eventEmitter.emit('testEvent2', { id: 1 });

expect(testEvent1Handler).toHaveBeenCalledWith([1, 2, 3]);
expect(testEvent2Handler).toHaveBeenCalledWith({ id: 1 });

eventEmitter.removeListener('testEvent1', testEvent1Handler);
eventEmitter.removeListener('testEvent2', testEvent2Handler);

eventEmitter.emit('testEvent1', [4, 5, 6]);
eventEmitter.emit('testEvent2', { id: 2 });

expect(testEvent1Handler).toHaveBeenCalledTimes(1);
expect(testEvent2Handler).toHaveBeenCalledTimes(1);
});
});
26 changes: 26 additions & 0 deletions packages/passport/sdk/src/zkEvm/typedEventEmitter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { EventEmitter } from 'events';

export default class TypedEventEmitter<TEvents extends Record<string, any>> {
private emitter = new EventEmitter();

emit<TEventName extends keyof TEvents & string>(
eventName: TEventName,
...eventArg: TEvents[TEventName]
) {
this.emitter.emit(eventName, ...(eventArg as []));
}

on<TEventName extends keyof TEvents & string>(
eventName: TEventName,
handler: (...eventArg: TEvents[TEventName]) => void,
) {
this.emitter.on(eventName, handler as any);
Copy link
Contributor

@Jon-Alonso Jon-Alonso Jul 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Types look off in this class, can we avoid any casting here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class comes from here, and the reason is:

In strict mode, an error is raised because TEvents[TEventName] != any[]. This is because (...args: TEvents[TEventName]) => void is narrower than the default typings on the EventEmitter type of (...args: any[]) => void and since function parameters are contravariant, this results in a type error. Because we constrain types when events are raised via the emit function, we can safely suppress this error with an as any type

}

removeListener<TEventName extends keyof TEvents & string>(
eventName: TEventName,
handler: (...eventArg: TEvents[TEventName]) => void,
) {
this.emitter.removeListener(eventName, handler as any);
}
}
18 changes: 15 additions & 3 deletions packages/passport/sdk/src/zkEvm/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,26 @@ export interface JsonRpcResponsePayload {
}

export type Provider = {
request: (request: RequestArguments) => Promise<any>
request: (request: RequestArguments) => Promise<any>;
sendAsync: (
request: JsonRpcRequestPayload | JsonRpcRequestPayload[],
callback: JsonRpcRequestCallback,
) => void
) => void;
send: (
request: string | JsonRpcRequestPayload | JsonRpcRequestPayload[],
callbackOrParams?: JsonRpcRequestCallback | Array<any>,
callback?: JsonRpcRequestCallback,
) => void
) => void;
on: (event: string, listener: (...args: any[]) => void) => void;
removeListener: (event: string, listener: (...args: any[]) => void) => void;
};

export enum ProviderEventNames {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export enum ProviderEventNames {
export enum ProviderEvent {

ACCOUNTS_CHANGED = 'accountsChanged',
}

export type AccountsChangedEvent = Array<string>;

export interface ProviderEvents extends Record<string, any> {
[ProviderEventNames.ACCOUNTS_CHANGED]: [AccountsChangedEvent],
}
2 changes: 2 additions & 0 deletions packages/passport/sdk/src/zkEvm/user/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './registerZkEvmUser';
export * from './loginZkEvmUser';
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
import { MultiRollupApiClients } from '@imtbl/generated-clients';
import { ExternalProvider } from '@ethersproject/providers';
import { createCounterfactualAddress } from './createCounterfactualAddress';
import { registerZkEvmUser } from './registerZkEvmUser';
import { UserZkEvm } from '../../types';
import AuthManager from '../../authManager';
import { PassportConfiguration } from '../../config';
import MagicAdapter from '../../magicAdapter';

type RegisterZkEvmUserInput = {
type LoginZkEvmUserInput = {
authManager: AuthManager;
config: PassportConfiguration;
magicAdapter: MagicAdapter;
multiRollupApiClients: MultiRollupApiClients;
};

type RegisterZkEvmUserOutput = {
type LoginZkEvmUserOutput = {
user: UserZkEvm;
magicProvider: ExternalProvider;
};

export const registerZkEvmUser = async ({
export const loginZkEvmUser = async ({
authManager,
config,
magicAdapter,
multiRollupApiClients,
}: RegisterZkEvmUserInput): Promise<RegisterZkEvmUserOutput> => {
}: LoginZkEvmUserInput): Promise<LoginZkEvmUserOutput> => {
const user = await authManager.getUser() || await authManager.login();
if (!user.idToken) {
throw new Error('User is missing idToken');
Expand All @@ -36,7 +36,7 @@ export const registerZkEvmUser = async ({

if (!user.zkEvm) {
// Generate counterfactual address and retrieve updated Auth0 user
const userZkevm = await createCounterfactualAddress({
const userZkevm = await registerZkEvmUser({
authManager,
magicProvider,
multiRollupApiClients,
Expand Down
Loading