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

feat: integrating signature decoding api #4855

Merged
merged 56 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from 51 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
06b350f
Integrating signature decoding api
jpuri Oct 28, 2024
39cc9ac
Integrating signature decoding api
jpuri Oct 28, 2024
15203de
update
jpuri Oct 29, 2024
b4a1ec5
Update
jpuri Oct 29, 2024
0cc8436
Update
jpuri Oct 29, 2024
1edf2f8
Update
jpuri Oct 29, 2024
fd39034
Update
jpuri Oct 29, 2024
7553a4e
Update
jpuri Oct 29, 2024
fcde11b
Update
jpuri Oct 29, 2024
0b01158
Merge branch 'main' into sign_api
jpuri Oct 29, 2024
6cd0423
Update
jpuri Oct 29, 2024
e596371
Merge branch 'sign_api' of github.com:MetaMask/core into sign_api
jpuri Oct 29, 2024
68dc5ff
Update
jpuri Oct 29, 2024
83969e3
update
jpuri Oct 30, 2024
9e25b9a
update
jpuri Oct 30, 2024
1ac68ca
update
jpuri Oct 30, 2024
be100d3
Merge branch 'main' into sign_api
jpuri Oct 30, 2024
3c9b7b4
update
jpuri Oct 30, 2024
ac2d149
update
jpuri Oct 30, 2024
dc7fcb7
update
jpuri Oct 30, 2024
dfd539b
update
jpuri Oct 30, 2024
d5df2c4
update
jpuri Oct 30, 2024
2cc2d7c
update
jpuri Oct 30, 2024
7fc39cf
Merge branch 'main' into sign_api
jpuri Oct 30, 2024
8ed4da6
update
jpuri Oct 30, 2024
92e752f
update
jpuri Oct 30, 2024
12e91ad
Merge branch 'sign_api' of github.com:MetaMask/core into sign_api
jpuri Oct 30, 2024
964dd73
update
jpuri Oct 30, 2024
329b097
update
jpuri Oct 30, 2024
83f4ede
update
jpuri Oct 30, 2024
9927cca
update
jpuri Oct 30, 2024
deb925d
update
jpuri Oct 30, 2024
e7cf0cf
Update packages/signature-controller/src/SignatureController.ts
jpuri Nov 4, 2024
0d9818d
update
jpuri Nov 4, 2024
92fa9ea
Merge branch 'sign_api' of github.com:MetaMask/core into sign_api
jpuri Nov 4, 2024
33cb319
Update packages/signature-controller/src/types.ts
jpuri Nov 4, 2024
01d4425
Merge branch 'main' into sign_api
jpuri Nov 4, 2024
4ae4551
update
jpuri Nov 4, 2024
adb98cc
update
jpuri Nov 4, 2024
80ff99b
update
jpuri Nov 4, 2024
b6b7af4
Merge branch 'main' into sign_api
jpuri Nov 4, 2024
6b100c0
update
jpuri Nov 4, 2024
e6cfd86
Merge branch 'sign_api' of github.com:MetaMask/core into sign_api
jpuri Nov 4, 2024
82e3076
update
jpuri Nov 4, 2024
94caf4c
update
jpuri Nov 5, 2024
3edc64d
update
jpuri Nov 6, 2024
4b165e1
update
jpuri Nov 6, 2024
e963191
Merge branch 'main' into sign_api
jpuri Nov 6, 2024
cdefae9
update
jpuri Nov 6, 2024
7d718f8
Merge branch 'sign_api' of github.com:MetaMask/core into sign_api
jpuri Nov 6, 2024
f1d7dc8
update
jpuri Nov 6, 2024
108749f
update
jpuri Nov 6, 2024
64395a3
update
jpuri Nov 6, 2024
fae20c7
update
jpuri Nov 6, 2024
658146d
Merge branch 'main' into sign_api
jpuri Nov 6, 2024
bd75416
Merge branch 'main' into sign_api
matthewwalsh0 Nov 7, 2024
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
1 change: 1 addition & 0 deletions packages/signature-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"devDependencies": {
"@metamask/approval-controller": "^7.1.1",
"@metamask/auto-changelog": "^3.4.4",
"@metamask/keyring-api": "^8.1.3",
jpuri marked this conversation as resolved.
Show resolved Hide resolved
"@metamask/keyring-controller": "^17.3.1",
"@metamask/logging-controller": "^6.0.1",
"@metamask/network-controller": "^22.0.1",
Expand Down
111 changes: 111 additions & 0 deletions packages/signature-controller/src/SignatureController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type {
SignatureRequest,
} from './types';
import { SignatureRequestStatus, SignatureRequestType } from './types';
import * as DecodingDataUtils from './utils/decoding-api';
import {
normalizePersonalMessageParams,
normalizeTypedMessageParams,
Expand Down Expand Up @@ -52,6 +53,7 @@ const PARAMS_MOCK = {

const REQUEST_MOCK = {
networkClientId: NETWORK_CLIENT_ID_MOCK,
params: [],
};

const SIGNATURE_REQUEST_MOCK: SignatureRequest = {
Expand All @@ -64,6 +66,27 @@ const SIGNATURE_REQUEST_MOCK: SignatureRequest = {
type: SignatureRequestType.PersonalSign,
};

const PERMIT_PARAMS_MOCK = {
data: '{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Permit":[{"name":"owner","type":"address"},{"name":"spender","type":"address"},{"name":"value","type":"uint256"},{"name":"nonce","type":"uint256"},{"name":"deadline","type":"uint256"}]},"primaryType":"Permit","domain":{"name":"MyToken","version":"1","verifyingContract":"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC","chainId":1},"message":{"owner":"0x975e73efb9ff52e23bac7f7e043a1ecd06d05477","spender":"0x5B38Da6a701c568545dCfcB03FcB875f56beddC4","value":3000,"nonce":0,"deadline":50000000000}}',
from: '0x975e73efb9ff52e23bac7f7e043a1ecd06d05477',
version: 'V4',
signatureMethod: 'eth_signTypedData_v4',
};

const PERMIT_REQUEST_MOCK = {
method: 'eth_signTypedData_v4',
params: [
'0x975e73efb9ff52e23bac7f7e043a1ecd06d05477',
'{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Permit":[{"name":"owner","type":"address"},{"name":"spender","type":"address"},{"name":"value","type":"uint256"},{"name":"nonce","type":"uint256"},{"name":"deadline","type":"uint256"}]},"primaryType":"Permit","domain":{"name":"MyToken","version":"1","verifyingContract":"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC","chainId":1},"message":{"owner":"0x975e73efb9ff52e23bac7f7e043a1ecd06d05477","spender":"0x5B38Da6a701c568545dCfcB03FcB875f56beddC4","value":3000,"nonce":0,"deadline":50000000000}}',
],
jsonrpc: '2.0',
id: 1680528590,
origin: 'https://metamask.github.io',
networkClientId: 'mainnet',
tabId: 1048807181,
traceContext: null,
};

/**
* Create a mock messenger instance.
* @returns The mock messenger instance plus individual mock functions for each action.
Expand Down Expand Up @@ -890,6 +913,90 @@ describe('SignatureController', () => {
).version,
).toBe(SignTypedDataVersion.V3);
});

describe('decodeSignature', () => {
it('invoke decodeSignature to get decoding data', async () => {
const MOCK_STATE_CHANGES = {
stateChanges: [
{
assetType: 'ERC20',
changeType: 'APPROVE',
address: '0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad',
amount: '1461501637330902918203684832716283019655932542975',
contractAddress: '0x6b175474e89094c44da98b954eedeac495271d0f',
},
],
};
const { controller } = createController();

jest
.spyOn(DecodingDataUtils, 'decodeSignature')
.mockResolvedValue(MOCK_STATE_CHANGES);

await controller.newUnsignedTypedMessage(
PERMIT_PARAMS_MOCK,
PERMIT_REQUEST_MOCK,
SignTypedDataVersion.V4,
{ parseJsonData: false },
);

expect(
controller.state.signatureRequests[ID_MOCK].decodingLoading,
).toBe(false);
expect(
controller.state.signatureRequests[ID_MOCK].decodingData,
).toStrictEqual(MOCK_STATE_CHANGES);
});

it('correctly set decoding data if decodeSignature fails', async () => {
const { controller } = createController();

jest
.spyOn(DecodingDataUtils, 'decodeSignature')
.mockRejectedValue(new Error('some error'));

await controller.newUnsignedTypedMessage(
PERMIT_PARAMS_MOCK,
PERMIT_REQUEST_MOCK,
SignTypedDataVersion.V4,
{ parseJsonData: false },
);

expect(
controller.state.signatureRequests[ID_MOCK].decodingLoading,
).toBe(false);
expect(
controller.state.signatureRequests[ID_MOCK].decodingData?.error?.type,
).toStrictEqual(
DecodingDataUtils.DECODING_API_ERRORS.DECODING_FAILED_WITH_ERROR,
);
});

it('set decodingLoading to true while api request is in progress', async () => {
const { controller } = createController();

jest
.spyOn(DecodingDataUtils, 'decodeSignature')
.mockImplementation(() => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({});
}, 300);
});
});

await controller.newUnsignedTypedMessage(
PERMIT_PARAMS_MOCK,
PERMIT_REQUEST_MOCK,
SignTypedDataVersion.V4,
{ parseJsonData: false },
);

expect(
controller.state.signatureRequests[ID_MOCK].decodingLoading,
).toBe(true);
});
});
jpuri marked this conversation as resolved.
Show resolved Hide resolved
});

describe('setDeferredSignSuccess', () => {
Expand Down Expand Up @@ -920,6 +1027,8 @@ describe('SignatureController', () => {
const { controller } = createController();
let resolved = false;

jest.spyOn(DecodingDataUtils, 'decodeSignature').mockResolvedValue({});

const signaturePromise = controller
.newUnsignedPersonalMessage(
{
Expand Down Expand Up @@ -998,6 +1107,8 @@ describe('SignatureController', () => {
const { controller } = createController();
let rejectedError;

jest.spyOn(DecodingDataUtils, 'decodeSignature').mockResolvedValue({});

controller
.newUnsignedPersonalMessage(
{
Expand Down
47 changes: 46 additions & 1 deletion packages/signature-controller/src/SignatureController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import type {
LegacyStateMessage,
StateSIWEMessage,
} from './types';
import { DECODING_API_ERRORS, decodeSignature } from './utils/decoding-api';
import {
normalizePersonalMessageParams,
normalizeTypedMessageParams,
Expand Down Expand Up @@ -155,6 +156,11 @@ export type SignatureControllerOptions = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) => Promise<any>;

/**
* URL of API to retrieve decoding data for typed requests.
*/
decodingApiUrl?: string;

/**
* Initial state of the controller.
*/
Expand All @@ -176,6 +182,8 @@ export class SignatureController extends BaseController<
> {
hub: EventEmitter;

#decodingApiUrl?: string;

#trace: TraceCallback;

/**
Expand All @@ -185,8 +193,14 @@ export class SignatureController extends BaseController<
* @param options.messenger - The restricted controller messenger for the sign controller.
* @param options.state - Initial state to set on this controller.
* @param options.trace - Callback to generate trace information.
* @param options.decodingApiUrl - Api used to get decoded data for permits.
matthewwalsh0 marked this conversation as resolved.
Show resolved Hide resolved
*/
constructor({ messenger, state, trace }: SignatureControllerOptions) {
constructor({
decodingApiUrl,
messenger,
state,
trace,
}: SignatureControllerOptions) {
super({
name: controllerName,
metadata: stateMetadata,
Expand All @@ -199,6 +213,7 @@ export class SignatureController extends BaseController<

this.hub = new EventEmitter();
this.#trace = trace ?? (((_request, fn) => fn?.()) as TraceCallback);
this.#decodingApiUrl = decodingApiUrl;
}

/**
Expand Down Expand Up @@ -462,6 +477,7 @@ export class SignatureController extends BaseController<
let approveOrSignError: unknown;

const finalMetadataPromise = this.#waitForFinished(metadata.id);
this.#decodePermitSignatureRequest(metadata.id, request, chainId);

try {
resultCallbacks = await this.#processApproval({
Expand Down Expand Up @@ -880,4 +896,33 @@ export class SignatureController extends BaseController<

return networkClient.configuration.chainId;
}

#decodePermitSignatureRequest(
signatureRequestId: string,
request: OriginalRequest,
chainId: string,
) {
this.#updateMetadata(signatureRequestId, (draftMetadata) => {
draftMetadata.decodingLoading = true;
});
decodeSignature(request, chainId, this.#decodingApiUrl)
.then((decodingData) =>
this.#updateMetadata(signatureRequestId, (draftMetadata) => {
draftMetadata.decodingData = decodingData;
draftMetadata.decodingLoading = false;
}),
)
.catch((error) =>
this.#updateMetadata(signatureRequestId, (draftMetadata) => {
draftMetadata.decodingData = {
stateChanges: null,
error: {
message: (error as unknown as Error).message,
type: DECODING_API_ERRORS.DECODING_FAILED_WITH_ERROR,
},
};
draftMetadata.decodingLoading = false;
}),
);
}
}
8 changes: 8 additions & 0 deletions packages/signature-controller/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const EthMethod = {
Copy link
Member

Choose a reason for hiding this comment

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

Minor, this feels more like an enum as it's a fixed set of values to identify a type of something.

Which would also mean it could go in types.ts.

But we can do this later since it's not exported.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried that but it is giving build issues somehow.

PersonalSign: 'personal_sign',
Sign: 'eth_sign',
SignTransaction: 'eth_signTransaction',
SignTypedDataV1: 'eth_signTypedData_v1',
SignTypedDataV3: 'eth_signTypedData_v3',
SignTypedDataV4: 'eth_signTypedData_v4',
};
47 changes: 46 additions & 1 deletion packages/signature-controller/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@ export type OriginalRequest = {
/** Unique ID to identify the client request. */
id?: number;

/** Method of signature request */
method?: string;

/** ID of the network client associated with the request. */
networkClientId?: string;

/** Source of the client request. */
origin?: string;

/** Parameters in signature request */
params: string[];
matthewwalsh0 marked this conversation as resolved.
Show resolved Hide resolved

/** Response following a security scan of the request. */
securityAlertResponse?: Record<string, Json>;
};
Expand Down Expand Up @@ -71,15 +77,54 @@ export type MessageParamsTyped = MessageParams & {
primaryType: string;
message: Json;
};

/** Version of the signTypedData request. */
version?: string;
};

/** Different decoding data state change types */
export type DecodingDataChangeType =
| 'RECEIVE'
| 'TRANSFER'
| 'APPROVE'
| 'REVOKE_APPROVE'
| 'BIDDING'
| 'LISTING';

/** Information about a single state change returned by decoding api. */
export type DecodingDataStateChange = {
assetType: string;
changeType: DecodingDataChangeType;
address: string;
amount: string;
contractAddress: string;
matthewwalsh0 marked this conversation as resolved.
Show resolved Hide resolved
tokenID?: string;
};

/** Array of the various state changes returned by decoding api. */
export type DecodingDataStateChanges = DecodingDataStateChange[];

/** Error details for unfulfilled the decoding request. */
export type DecodingDataError = {
message: string;
matthewwalsh0 marked this conversation as resolved.
Show resolved Hide resolved
type: string;
matthewwalsh0 marked this conversation as resolved.
Show resolved Hide resolved
};

/** Decoding data about typed sign V4 signature request. */
export type DecodingData = {
stateChanges: DecodingDataStateChanges | null;
matthewwalsh0 marked this conversation as resolved.
Show resolved Hide resolved
error?: DecodingDataError;
};

type SignatureRequestBase = {
/** ID of the associated chain. */
chainId: Hex;

/** Response from message decoding api. */
decodingData?: DecodingData;

/** Whether decoding is in progress. */
decodingLoading?: boolean;

/** Error message that occurred during the signing. */
error?: string;

Expand Down
Loading
Loading