Skip to content

Commit

Permalink
refactor: revamp starkNet_signDeclareTransaction (#333)
Browse files Browse the repository at this point in the history
* refactor: revamp sign delcare transaction

* chore: add superstruct test

* chore: update get-starknet interface

* chore: restrict tx version to v3 and v2
  • Loading branch information
stanleyyconsensys authored Aug 27, 2024
1 parent b393fe8 commit d7708da
Show file tree
Hide file tree
Showing 9 changed files with 509 additions and 228 deletions.
6 changes: 3 additions & 3 deletions packages/get-starknet/src/snap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,16 +88,16 @@ export class MetaMaskSnap {
})) as Signature;
}

async signDeclareTransaction(signerAddress: string, transaction: DeclareSignerDetails): Promise<Signature> {
async signDeclareTransaction(address: string, details: DeclareSignerDetails): Promise<Signature> {
return (await this.#provider.request({
method: 'wallet_invokeSnap',
params: {
snapId: this.#snapId,
request: {
method: 'starkNet_signDeclareTransaction',
params: this.removeUndefined({
signerAddress,
transaction,
address,
details,
...(await this.#getSnapParams()),
}),
},
Expand Down
8 changes: 4 additions & 4 deletions packages/starknet-snap/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,17 @@ import type {
DisplayPrivateKeyParams,
SignMessageParams,
SignTransactionParams,
SignDeclareTransactionParams,
VerifySignatureParams,
} from './rpcs';
import {
displayPrivateKey,
signMessage,
signTransaction,
signDeclareTransaction,
verifySignature,
} from './rpcs';
import { sendTransaction } from './sendTransaction';
import { signDeclareTransaction } from './signDeclareTransaction';
import { signDeployAccountTransaction } from './signDeployAccountTransaction';
import { switchNetwork } from './switchNetwork';
import type {
Expand Down Expand Up @@ -192,9 +193,8 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => {
);

case 'starkNet_signDeclareTransaction':
apiParams.keyDeriver = await getAddressKeyDeriver(snap);
return await signDeclareTransaction(
apiParams as unknown as ApiParamsWithKeyDeriver,
return await signDeclareTransaction.execute(
apiParams as unknown as SignDeclareTransactionParams,
);

case 'starkNet_signDeployAccountTransaction':
Expand Down
1 change: 1 addition & 0 deletions packages/starknet-snap/src/rpcs/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './signMessage';
export * from './displayPrivateKey';
export * from './signTransaction';
export * from './sign-declare-transaction';
export * from './verify-signature';
134 changes: 134 additions & 0 deletions packages/starknet-snap/src/rpcs/sign-declare-transaction.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import {
InvalidParamsError,
UserRejectedRequestError,
} from '@metamask/snaps-sdk';
import type { DeclareSignerDetails } from 'starknet';
import { constants } from 'starknet';

import type { SnapState } from '../types/snapState';
import { toJson } from '../utils';
import { STARKNET_SEPOLIA_TESTNET_NETWORK } from '../utils/constants';
import * as starknetUtils from '../utils/starknetUtils';
import {
mockAccount,
prepareMockAccount,
prepareConfirmDialog,
} from './__tests__/helper';
import { signDeclareTransaction } from './sign-declare-transaction';
import type { SignDeclareTransactionParams } from './sign-declare-transaction';

jest.mock('../utils/snap');
jest.mock('../utils/logger');

describe('signDeclareTransaction', () => {
const state: SnapState = {
accContracts: [],
erc20Tokens: [],
networks: [STARKNET_SEPOLIA_TESTNET_NETWORK],
transactions: [],
};

const createRequest = (
chainId: constants.StarknetChainId,
address: string,
) => ({
details: {
classHash:
'0x025ec026985a3bf9d0cc1fe17326b245dfdc3ff89b8fde106542a3ea56c5a918',
senderAddress: address,
chainId,
version: constants.TRANSACTION_VERSION.V2,
maxFee: 0,
nonce: 0,
},
address,
chainId,
});

it('signs message correctly', async () => {
const chainId = constants.StarknetChainId.SN_SEPOLIA;
const account = await mockAccount(chainId);

prepareMockAccount(account, state);
prepareConfirmDialog();

const request = createRequest(chainId, account.address);

const expectedResult = await starknetUtils.signDeclareTransaction(
account.privateKey,
request.details as unknown as DeclareSignerDetails,
);

const result = await signDeclareTransaction.execute(request);

expect(result).toStrictEqual(expectedResult);
});

it('renders confirmation dialog', async () => {
const chainId = constants.StarknetChainId.SN_SEPOLIA;
const account = await mockAccount(chainId);

prepareMockAccount(account, state);
const { confirmDialogSpy } = prepareConfirmDialog();

const request = createRequest(chainId, account.address);

await signDeclareTransaction.execute(request);

const calls = confirmDialogSpy.mock.calls[0][0];
expect(calls).toStrictEqual([
{ type: 'heading', value: 'Do you want to sign this transaction?' },
{
type: 'row',
label: 'Network',
value: {
value: STARKNET_SEPOLIA_TESTNET_NETWORK.name,
markdown: false,
type: 'text',
},
},
{
type: 'row',
label: 'Signer Address',
value: {
value: account.address,
markdown: false,
type: 'text',
},
},
{
type: 'row',
label: 'Declare Transaction Details',
value: {
value: toJson(request.details),
markdown: false,
type: 'text',
},
},
]);
});

it('throws `UserRejectedRequestError` if user denied the operation', async () => {
const chainId = constants.StarknetChainId.SN_SEPOLIA;
const account = await mockAccount(chainId);

prepareMockAccount(account, state);
const { confirmDialogSpy } = prepareConfirmDialog();

confirmDialogSpy.mockResolvedValue(false);

const request = createRequest(chainId, account.address);

await expect(signDeclareTransaction.execute(request)).rejects.toThrow(
UserRejectedRequestError,
);
});

it('throws `InvalidParamsError` when request parameter is not correct', async () => {
await expect(
signDeclareTransaction.execute(
{} as unknown as SignDeclareTransactionParams,
),
).rejects.toThrow(InvalidParamsError);
});
});
120 changes: 120 additions & 0 deletions packages/starknet-snap/src/rpcs/sign-declare-transaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import type { Component } from '@metamask/snaps-sdk';
import {
heading,
row,
text,
UserRejectedRequestError,
} from '@metamask/snaps-sdk';
import type { DeclareSignerDetails } from 'starknet';
import type { Infer } from 'superstruct';
import { array, object, string, assign } from 'superstruct';

import {
confirmDialog,
AddressStruct,
toJson,
BaseRequestStruct,
AccountRpcController,
DeclareSignDetailsStruct,
} from '../utils';
import { signDeclareTransaction as signDeclareTransactionUtil } from '../utils/starknetUtils';

export const SignDeclareTransactionRequestStruct = assign(
object({
address: AddressStruct,
details: DeclareSignDetailsStruct,
}),
BaseRequestStruct,
);

export const SignDeclareTransactionResponseStruct = array(string());

export type SignDeclareTransactionParams = Infer<
typeof SignDeclareTransactionRequestStruct
>;

export type SignDeclareTransactionResponse = Infer<
typeof SignDeclareTransactionResponseStruct
>;

/**
* The RPC handler to sign a declare transaction.
*/
export class SignDeclareTransactionRpc extends AccountRpcController<
SignDeclareTransactionParams,
SignDeclareTransactionResponse
> {
protected requestStruct = SignDeclareTransactionRequestStruct;

protected responseStruct = SignDeclareTransactionResponseStruct;

/**
* Execute the sign declare transaction request handler.
* It will show a confirmation dialog to the user before signing the declare transaction.
*
* @param params - The parameters of the request.
* @param params.address - The address of the signer.
* @param params.details - The declare transaction details to sign.
* @param [params.enableAuthorize] - Optional, a flag to enable or display the confirmation dialog to the user.
* @param params.chainId - The chain id of the network.
* @returns the signature of the message in string array.
*/
async execute(
params: SignDeclareTransactionParams,
): Promise<SignDeclareTransactionResponse> {
return super.execute(params);
}

protected async handleRequest(
params: SignDeclareTransactionParams,
): Promise<SignDeclareTransactionResponse> {
const { details } = params;
if (!(await this.getSignDeclareTransactionConsensus(details))) {
throw new UserRejectedRequestError() as unknown as Error;
}

return (await signDeclareTransactionUtil(
this.account.privateKey,
details as unknown as DeclareSignerDetails,
)) as unknown as SignDeclareTransactionResponse;
}

protected async getSignDeclareTransactionConsensus(
details: Infer<typeof DeclareSignDetailsStruct>,
) {
const components: Component[] = [];
components.push(heading('Do you want to sign this transaction?'));
components.push(
row(
'Network',
text({
value: this.network.name,
markdown: false,
}),
),
);
components.push(
row(
'Signer Address',
text({
value: details.senderAddress,
markdown: false,
}),
),
);

components.push(
row(
'Declare Transaction Details',
text({
value: toJson(details),
markdown: false,
}),
),
);

return await confirmDialog(components);
}
}

export const signDeclareTransaction = new SignDeclareTransactionRpc();
80 changes: 0 additions & 80 deletions packages/starknet-snap/src/signDeclareTransaction.ts

This file was deleted.

Loading

0 comments on commit d7708da

Please sign in to comment.