Skip to content

Commit

Permalink
Merge pull request #21 from 0xProject/feat/add-sign_typedData_v4-to-m…
Browse files Browse the repository at this point in the history
…etamask-subprovider

more robust eth_signTypedData support
  • Loading branch information
dorothy-zbornak authored Jan 26, 2021
2 parents aa7460d + 7c3ded1 commit 1332f8a
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 27 deletions.
9 changes: 9 additions & 0 deletions subproviders/CHANGELOG.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
[
{
"version": "6.4.0",
"changes": [
{
"note": "Support `eth_signTypedData_v4` in `MetamaskSubprovider` and use the method name passed in",
"pr": 21
}
]
},
{
"timestamp": 1610044030,
"version": "6.3.2",
Expand Down
17 changes: 10 additions & 7 deletions subproviders/src/subproviders/metamask_subprovider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,18 +84,21 @@ export class MetamaskSubprovider extends Subprovider {
end(err);
}
return;
// Metamask supports different versions of the `eth_signTypedData` RPC method.
case 'eth_signTypedData':
case 'eth_signTypedData_v3':
case 'eth_signTypedData_v4':
[address, message] = payload.params;
try {
// Metamask supports multiple versions and has namespaced signTypedData to v3 for an indeterminate period of time.
// eth_signTypedData is mapped to an older implementation before the spec was finalized.
// Source: https://github.com/MetaMask/metamask-extension/blob/c49d854b55b3efd34c7fd0414b76f7feaa2eec7c/app/scripts/metamask-controller.js#L1262
// and expects message to be serialised as JSON
const messageJSON = JSON.stringify(message);
// We accept either JSON-serialized or object messages.
const messageObject = typeof message === 'object' ? message : JSON.parse(message);
const signature = await this._web3Wrapper.sendRawPayloadAsync<string>({
method: 'eth_signTypedData_v3',
params: [address, messageJSON],
method: payload.method,
// `eth_signTypedData` takes a raw object.
params: [
address,
payload.method === 'eth_signTypedData' ? messageObject : JSON.stringify(messageObject),
],
});
signature ? end(null, signature) : end(new Error('Error performing eth_signTypedData'), null);
} catch (err) {
Expand Down
9 changes: 9 additions & 0 deletions web3-wrapper/CHANGELOG.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
[
{
"version": "7.4.0",
"changes": [
{
"note": "Remove `Web3Wrapper.signTypedDataV4Async()` and add backoff support for multiple versions of `eth_signTypedData` to `Web3Wrapper.signTypedDataAsync()`.",
"pr": 21
}
]
},
{
"timestamp": 1610044030,
"version": "7.3.2",
Expand Down
41 changes: 21 additions & 20 deletions web3-wrapper/src/web3_wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,26 +342,27 @@ export class Web3Wrapper {
public async signTypedDataAsync(address: string, typedData: any): Promise<string> {
assert.isETHAddressHex('address', address);
assert.doesConformToSchema('typedData', typedData, schemas.eip712TypedDataSchema);
const signData = await this.sendRawPayloadAsync<string>({
method: 'eth_signTypedData',
params: [address, typedData],
});
return signData;
}
/**
* Sign an EIP712 typed data message with a specific address's private key (MetaMask's `eth_signTypedData_v4`)
* @param address Address of signer
* @param typedData Typed data message to sign
* @returns Signature string (as RSV)
*/
public async signTypedDataV4Async(address: string, typedData: any): Promise<string> {
assert.isETHAddressHex('address', address);
assert.doesConformToSchema('typedData', typedData, schemas.eip712TypedDataSchema);
const signData = await this.sendRawPayloadAsync<string>({
method: 'eth_signTypedData_v4',
params: [address, JSON.stringify(typedData)],
});
return signData;
// Try decreasing versions of `eth_signTypedData` until it works.
const methodsToTry = ['eth_signTypedData_v4', 'eth_signTypedData_v3', 'eth_signTypedData'];
let lastErr: Error | undefined;
for (const method of methodsToTry) {
try {
return await this.sendRawPayloadAsync<string>({
method,
// `eth_signTypedData` expects an object, whereas the others expect
// a JSON string.
params: [address, method === 'eth_signTypedData' ? typedData : JSON.stringify(typedData)],
});
} catch (err) {
lastErr = err;
// If there are no more methods to try or the error says something other
// than the method not existing, throw.
if (!/(not handled|does not exist)/.test(err.message)) {
throw err;
}
}
}
throw lastErr;
}
/**
* Fetches the latest block number
Expand Down

0 comments on commit 1332f8a

Please sign in to comment.