diff --git a/src/components/signatures/index.js b/src/components/signatures/index.js
index f04254ce..5456bf2b 100644
--- a/src/components/signatures/index.js
+++ b/src/components/signatures/index.js
@@ -5,6 +5,7 @@ export * from './signTypedData';
export * from './signTypedData-variants';
export * from './signTypedDataV3-sign';
export * from './signTypedDataV4-sign';
+export * from './signTypedDataV4-sign-with-salt';
export * from './siwe';
export * from './malformed-signatures';
export * from './malformed-transactions';
diff --git a/src/components/signatures/signTypedDataV4-sign-with-salt.js b/src/components/signatures/signTypedDataV4-sign-with-salt.js
new file mode 100644
index 00000000..88101d5c
--- /dev/null
+++ b/src/components/signatures/signTypedDataV4-sign-with-salt.js
@@ -0,0 +1,251 @@
+import { recoverTypedSignature } from '@metamask/eth-sig-util';
+import { toChecksumAddress } from 'ethereumjs-util';
+import globalContext from '../..';
+
+export function signTypedDataV4WithSaltComponent(parentContainer) {
+ parentContainer.insertAdjacentHTML(
+ 'beforeend',
+ `
+
+
+
+ Sign Typed Data V4 with salt
+
+
+
+
+
+ Result:
+
+
+
+
+
+
+ Recovery result:
+
+
+
+
+
`,
+ );
+
+ const signTypedDataV4WithSalt = document.getElementById(
+ 'signTypedDataV4WithSalt',
+ );
+ const signTypedDataV4WithSaltResult = document.getElementById(
+ 'signTypedDataV4WithSaltResult',
+ );
+ const signTypedDataV4WithSaltVerify = document.getElementById(
+ 'signTypedDataV4WithSaltVerify',
+ );
+ const signTypedDataV4WithSaltVerifyResult = document.getElementById(
+ 'signTypedDataV4WithSaltVerifyResult',
+ );
+
+ document.addEventListener('globalConnectionChange', function (e) {
+ if (e.detail.connected) {
+ // MetaMask is connected, enable the button
+ signTypedDataV4WithSalt.disabled = false;
+ }
+ });
+
+ document.addEventListener('disableAndClear', function () {
+ signTypedDataV4WithSalt.disabled = true;
+ signTypedDataV4WithSaltVerify.disabled = true;
+ });
+
+ /**
+ * Sign Typed Data V4
+ */
+ signTypedDataV4WithSalt.onclick = async () => {
+ const msgParams = {
+ domain: {
+ chainId: globalContext.chainIdInt.toString(),
+ name: 'Ether Mail',
+ verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
+ version: '1',
+ salt: 'test',
+ },
+ message: {
+ contents: 'Hello, Bob!',
+ from: {
+ name: 'Cow',
+ wallets: [
+ '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
+ '0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF',
+ ],
+ },
+ to: [
+ {
+ name: 'Bob',
+ wallets: [
+ '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
+ '0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57',
+ '0xB0B0b0b0b0b0B000000000000000000000000000',
+ ],
+ },
+ ],
+ attachment: '0x',
+ },
+ primaryType: 'Mail',
+ types: {
+ EIP712Domain: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ {
+ name: 'version',
+ type: 'string',
+ },
+ {
+ name: 'chainId',
+ type: 'uint256',
+ },
+ {
+ name: 'verifyingContract',
+ type: 'string',
+ },
+ {
+ name: 'salt',
+ type: 'string',
+ },
+ ],
+ Group: [
+ { name: 'name', type: 'string' },
+ { name: 'members', type: 'Person[]' },
+ ],
+ Mail: [
+ { name: 'from', type: 'Person' },
+ { name: 'to', type: 'Person[]' },
+ { name: 'contents', type: 'string' },
+ { name: 'attachment', type: 'bytes' },
+ ],
+ Person: [
+ { name: 'name', type: 'string' },
+ { name: 'wallets', type: 'address[]' },
+ ],
+ },
+ };
+ try {
+ const from = globalContext.accounts[0];
+ const sign = await globalContext.provider.request({
+ method: 'eth_signTypedData_v4',
+ params: [from, JSON.stringify(msgParams)],
+ });
+ signTypedDataV4WithSaltResult.innerHTML = sign;
+ signTypedDataV4WithSaltVerify.disabled = false;
+ } catch (err) {
+ console.error(err);
+ signTypedDataV4WithSaltResult.innerHTML = `Error: ${err.message}`;
+ }
+ };
+
+ /**
+ * Sign Typed Data V4 Verification
+ */
+ signTypedDataV4WithSaltVerify.onclick = async () => {
+ const msgParams = {
+ domain: {
+ chainId: globalContext.chainIdInt,
+ name: 'Ether Mail',
+ verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
+ version: '1',
+ salt: 'test',
+ },
+ message: {
+ contents: 'Hello, Bob!',
+ from: {
+ name: 'Cow',
+ wallets: [
+ '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
+ '0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF',
+ ],
+ },
+ to: [
+ {
+ name: 'Bob',
+ wallets: [
+ '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
+ '0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57',
+ '0xB0B0b0b0b0b0B000000000000000000000000000',
+ ],
+ },
+ ],
+ attachment: '0x',
+ },
+ primaryType: 'Mail',
+ types: {
+ EIP712Domain: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ {
+ name: 'version',
+ type: 'string',
+ },
+ {
+ name: 'chainId',
+ type: 'uint256',
+ },
+ {
+ name: 'verifyingContract',
+ type: 'string',
+ },
+ {
+ name: 'salt',
+ type: 'string',
+ },
+ ],
+ Group: [
+ { name: 'name', type: 'string' },
+ { name: 'members', type: 'Person[]' },
+ ],
+ Mail: [
+ { name: 'from', type: 'Person' },
+ { name: 'to', type: 'Person[]' },
+ { name: 'contents', type: 'string' },
+ { name: 'attachment', type: 'bytes' },
+ ],
+ Person: [
+ { name: 'name', type: 'string' },
+ { name: 'wallets', type: 'address[]' },
+ ],
+ },
+ };
+ try {
+ const from = globalContext.accounts[0];
+ const sign = signTypedDataV4WithSaltResult.innerHTML;
+ const recoveredAddr = recoverTypedSignature({
+ data: msgParams,
+ signature: sign,
+ version: 'V4',
+ });
+ if (toChecksumAddress(recoveredAddr) === toChecksumAddress(from)) {
+ console.log(`Successfully verified signer as ${recoveredAddr}`);
+ signTypedDataV4WithSaltVerifyResult.innerHTML = recoveredAddr;
+ } else {
+ console.log(
+ `Failed to verify signer when comparing ${recoveredAddr} to ${from}`,
+ );
+ }
+ } catch (err) {
+ console.error(err);
+ signTypedDataV4WithSaltVerifyResult.innerHTML = `Error: ${err.message}`;
+ }
+ };
+}
diff --git a/src/index.js b/src/index.js
index f2aa6651..ecfa78b8 100644
--- a/src/index.js
+++ b/src/index.js
@@ -33,6 +33,7 @@ import {
signTypedDataVariantsComponent,
signTypedDataV3Component,
signTypedDataV4Component,
+ signTypedDataV4WithSaltComponent,
siweComponent,
malformedSignaturesComponent,
malformedTransactionsComponent,
@@ -201,6 +202,7 @@ personalSignComponent(signaturesRow);
signTypedDataComponent(signaturesRow);
signTypedDataV3Component(signaturesRow);
signTypedDataV4Component(signaturesRow);
+signTypedDataV4WithSaltComponent(signaturesRow);
permitSignComponent(signaturesRow);
signTypedDataVariantsComponent(signaturesRow);
siweComponent(signaturesRow);