Skip to content

Commit

Permalink
feat: Add a new Passport config option to automatically request a tra…
Browse files Browse the repository at this point in the history
…nsaction when SCW is not deployed (#2149)

Co-authored-by: Hayden Fowler <[email protected]>
  • Loading branch information
Jon-Alonso and haydenfowler authored Sep 12, 2024
1 parent 52c855b commit 1e9b20b
Show file tree
Hide file tree
Showing 10 changed files with 819 additions and 361 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ const getPassportConfig = (environment: EnvironmentNames): PassportModuleConfigu
logoutRedirectUri: LOGOUT_MODE === 'silent'
? SILENT_LOGOUT_REDIRECT_URI
: LOGOUT_REDIRECT_URI,
forceScwDeployBeforeMessageSignature: true,
};

switch (environment) {
Expand Down
4 changes: 4 additions & 0 deletions packages/passport/sdk/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,15 @@ export class PassportConfiguration {

readonly crossSdkBridgeEnabled: boolean;

readonly forceScwDeployBeforeMessageSignature: boolean;

readonly popupOverlayOptions: PopupOverlayOptions;

constructor({
baseConfig,
overrides,
crossSdkBridgeEnabled,
forceScwDeployBeforeMessageSignature,
popupOverlayOptions,
...oidcConfiguration
}: PassportModuleConfiguration) {
Expand All @@ -66,6 +69,7 @@ export class PassportConfiguration {
this.oidcConfiguration = oidcConfiguration;
this.baseConfig = baseConfig;
this.crossSdkBridgeEnabled = crossSdkBridgeEnabled || false;
this.forceScwDeployBeforeMessageSignature = forceScwDeployBeforeMessageSignature || false;
this.popupOverlayOptions = popupOverlayOptions || {
disableGenericPopupOverlay: false,
disableBlockedPopupOverlay: false,
Expand Down
9 changes: 9 additions & 0 deletions packages/passport/sdk/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,19 @@ export interface PassportModuleConfiguration
* and not directly on the web.
*/
crossSdkBridgeEnabled?: boolean;

/**
* Options for disabling the Passport popup overlays.
*/
popupOverlayOptions?: PopupOverlayOptions;

/**
* This flag controls whether a deploy transaction is sent before signing an ERC191 message.
*
* @default false - By default, this behavior is disabled and the user will not be asked
* to approve a deploy transaction before signing.
*/
forceScwDeployBeforeMessageSignature?: boolean;
}

type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import { StaticJsonRpcProvider } from '@ethersproject/providers';
import { Signer } from '@ethersproject/abstract-signer';
import { Flow } from '@imtbl/metrics';
import { BigNumber } from 'ethers';
import { sendDeployTransactionAndPersonalSign } from './sendDeployTransactionAndPersonalSign';
import { mockUserZkEvm } from '../test/mocks';
import { RelayerClient } from './relayerClient';
import GuardianClient from '../guardian';
import * as transactionHelpers from './transactionHelpers';
import * as personalSign from './personalSign';

jest.mock('./transactionHelpers');
jest.mock('./personalSign');

describe('sendDeployTransactionAndPersonalSign', () => {
const signedTransactions = 'signedTransactions123';
const relayerTransactionId = 'relayerTransactionId123';
const transactionHash = 'transactionHash123';
const signedMessage = 'signedMessage123';

const nonce = BigNumber.from(5);

const params = ['message to sign'];
const rpcProvider = {
detectNetwork: jest.fn(),
};
const relayerClient = {
imGetFeeOptions: jest.fn(),
ethSendTransaction: jest.fn(),
imGetTransactionByHash: jest.fn(),
};
const guardianClient = {
validateEVMTransaction: jest.fn(),
withConfirmationScreen: jest.fn(),
};
const ethSigner = {
getAddress: jest.fn(),
} as Partial<Signer> as Signer;
const flow = {
addEvent: jest.fn(),
};

beforeEach(() => {
jest.resetAllMocks();
(transactionHelpers.prepareAndSignTransaction as jest.Mock).mockResolvedValue({
signedTransactions,
relayerId: relayerTransactionId,
nonce,
});
(transactionHelpers.pollRelayerTransaction as jest.Mock).mockResolvedValue({
hash: transactionHash,
});
(personalSign.personalSign as jest.Mock).mockResolvedValue(signedMessage);
(guardianClient.withConfirmationScreen as jest.Mock)
.mockImplementation(() => (task: () => void) => task());
});

it('calls prepareAndSignTransaction with the correct arguments', async () => {
await sendDeployTransactionAndPersonalSign({
params,
ethSigner,
rpcProvider: rpcProvider as unknown as StaticJsonRpcProvider,
relayerClient: relayerClient as unknown as RelayerClient,
zkEvmAddress: mockUserZkEvm.zkEvm.ethAddress,
guardianClient: guardianClient as unknown as GuardianClient,
flow: flow as unknown as Flow,
});

expect(transactionHelpers.prepareAndSignTransaction).toHaveBeenCalledWith({
transactionRequest: { to: mockUserZkEvm.zkEvm.ethAddress, value: 0 },
ethSigner,
rpcProvider: rpcProvider as unknown as StaticJsonRpcProvider,
relayerClient: relayerClient as unknown as RelayerClient,
guardianClient: guardianClient as unknown as GuardianClient,
zkEvmAddress: mockUserZkEvm.zkEvm.ethAddress,
flow: flow as unknown as Flow,
});
});

it('calls personalSign with the correct arguments', async () => {
await sendDeployTransactionAndPersonalSign({
params,
ethSigner,
rpcProvider: rpcProvider as unknown as StaticJsonRpcProvider,
relayerClient: relayerClient as unknown as RelayerClient,
zkEvmAddress: mockUserZkEvm.zkEvm.ethAddress,
guardianClient: guardianClient as unknown as GuardianClient,
flow: flow as unknown as Flow,
});

expect(personalSign.personalSign).toHaveBeenCalledWith({
params,
ethSigner,
zkEvmAddress: mockUserZkEvm.zkEvm.ethAddress,
rpcProvider: rpcProvider as unknown as StaticJsonRpcProvider,
guardianClient: guardianClient as unknown as GuardianClient,
relayerClient: relayerClient as unknown as RelayerClient,
flow: flow as unknown as Flow,
});
});

it('calls pollRelayerTransaction with the correct arguments', async () => {
await sendDeployTransactionAndPersonalSign({
params,
ethSigner,
rpcProvider: rpcProvider as unknown as StaticJsonRpcProvider,
relayerClient: relayerClient as unknown as RelayerClient,
zkEvmAddress: mockUserZkEvm.zkEvm.ethAddress,
guardianClient: guardianClient as unknown as GuardianClient,
flow: flow as unknown as Flow,
});

expect(transactionHelpers.pollRelayerTransaction).toHaveBeenCalledWith(
relayerClient as unknown as RelayerClient,
relayerTransactionId,
flow as unknown as Flow,
);
});

it('returns the signed message', async () => {
const result = await sendDeployTransactionAndPersonalSign({
params,
ethSigner,
rpcProvider: rpcProvider as unknown as StaticJsonRpcProvider,
relayerClient: relayerClient as unknown as RelayerClient,
zkEvmAddress: mockUserZkEvm.zkEvm.ethAddress,
guardianClient: guardianClient as unknown as GuardianClient,
flow: flow as unknown as Flow,
});

expect(result).toEqual(signedMessage);
});

it('calls guardianClient.withConfirmationScreen with the correct arguments', async () => {
await sendDeployTransactionAndPersonalSign({
params,
ethSigner,
rpcProvider: rpcProvider as unknown as StaticJsonRpcProvider,
relayerClient: relayerClient as unknown as RelayerClient,
zkEvmAddress: mockUserZkEvm.zkEvm.ethAddress,
guardianClient: guardianClient as unknown as GuardianClient,
flow: flow as unknown as Flow,
});

expect(guardianClient.withConfirmationScreen).toHaveBeenCalled();
});

it('throws an error if any step fails', async () => {
const error = new Error('Something went wrong');
(transactionHelpers.prepareAndSignTransaction as jest.Mock).mockRejectedValue(error);

await expect(
sendDeployTransactionAndPersonalSign({
params,
ethSigner,
rpcProvider: rpcProvider as unknown as StaticJsonRpcProvider,
relayerClient: relayerClient as unknown as RelayerClient,
zkEvmAddress: mockUserZkEvm.zkEvm.ethAddress,
guardianClient: guardianClient as unknown as GuardianClient,
flow: flow as unknown as Flow,
}),
).rejects.toThrow(error);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { prepareAndSignTransaction, pollRelayerTransaction, TransactionParams } from './transactionHelpers';
import { personalSign } from './personalSign';

type EthSendDeployTransactionParams = TransactionParams & {
params: Array<any>;
};

export const sendDeployTransactionAndPersonalSign = async ({
params,
ethSigner,
rpcProvider,
relayerClient,
guardianClient,
zkEvmAddress,
flow,
}: EthSendDeployTransactionParams): Promise<string> => {
const deployTransaction = { to: zkEvmAddress, value: 0 };

const { relayerId } = await prepareAndSignTransaction({
transactionRequest: deployTransaction,
ethSigner,
rpcProvider,
guardianClient,
relayerClient,
zkEvmAddress,
flow,
});

return guardianClient.withConfirmationScreen()(async () => {
const signedMessage = await personalSign({
params,
ethSigner,
zkEvmAddress,
rpcProvider,
guardianClient,
relayerClient,
flow,
});

await pollRelayerTransaction(relayerClient, relayerId, flow);

return signedMessage;
});
};
Loading

0 comments on commit 1e9b20b

Please sign in to comment.