Skip to content

Instantly share code, notes, and snippets.

@vkobel
Created December 21, 2023 15:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vkobel/fd23cfbed3c5c288da5b67a7faea3c5b to your computer and use it in GitHub Desktop.
Save vkobel/fd23cfbed3c5c288da5b67a7faea3c5b to your computer and use it in GitHub Desktop.
Web3 signer for the EIP-20 Permit method
<!DOCTYPE html>
<html>
<head>
<title>ERC20 Permit Signing</title>
<script src="https://cdn.jsdelivr.net/npm/web3/dist/web3.min.js"></script>
</head>
<style>
body {
background: #444;
color: #EEE;
text-align: center;
font-family: 'Courier New', Courier, monospace;
font-weight: bold;
}
input, textarea {
width: 40%;
height: 30px;
border-radius: 5px;
border: 1px solid #CCC;
padding: 5px;
font-size: 16px;
margin: 5px;
}
textarea {
height: 300px;
font-size: 10px;
}
</style>
<body>
ABI:<br /><textarea id="abi" placeholder="Paste ERC20 ABI here"></textarea><br />
<br />
Contract Address:<br /><input type="text" id="tokenAddress" placeholder="Token Contract Address" /><br />
<br />
Spender:<br /><input type="text" id="spender" placeholder="Spender Address" /><br />
<br />
Value<br /><input type="number" id="value" placeholder="Value" /><br />
<br />
Deadline:<br /><input type="text" id="deadline" placeholder="Deadline" /><br />
<br />
<button onclick="initiateSigning()">Sign Permit</button>
<br />
<h3>Signature Components:</h3>
<p><strong>v:</strong> <span id="sigV"></span></p>
<p><strong>r:</strong> <span id="sigR"></span></p>
<p><strong>s:</strong> <span id="sigS"></span></p>
<script>
let web3;
window.addEventListener('load', async () => {
if (window.ethereum) {
web3 = new Web3(window.ethereum);
try {
// Request account access
await window.ethereum.enable();
} catch (error) {
console.error("User denied account access")
}
}
// Else if the older web3 is available
else if (window.web3) {
web3 = new Web3(web3.currentProvider);
}
// No web3 provider
else {
console.log('No web3 provider detected');
}
});
async function signERC20Permit(account, tokenAddress, spender, value, deadline, abi) {
const tokenContract = new web3.eth.Contract(JSON.parse(abi), tokenAddress);
const permitData = {
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: await tokenContract.methods.name().call(),
version: '1',
chainId: await web3.eth.getChainId(),
verifyingContract: tokenAddress,
},
message: {
owner: account,
spender: spender,
value: value,
nonce: await tokenContract.methods.nonces(account).call(),
deadline: deadline,
},
};
function replacer(key, value) {
if (typeof value === 'bigint') {
return value.toString();
} else {
return value;
}
}
// Request signature from MetaMask
const signature = await web3.currentProvider.send('eth_signTypedData_v4', [
account,
JSON.stringify(permitData, replacer),
]);
// Decompose the signature
const { v, r, s } = await getSignatureComponents(signature.result);
// Return the signature components
return { v, r, s };
}
async function getSignatureComponents(signature) {
// Ensure the signature has the correct length
if (signature.length !== 132) {
throw new Error("Invalid signature length");
}
const r = signature.slice(0, 66);
const s = '0x' + signature.slice(66, 130);
let v = '0x' + signature.slice(130, 132);
v = parseInt(v, 16);
// Adjust the v value if necessary (depends on the chain)
if (![27, 28].includes(v)) v += 27;
return { r, s, v };
}
async function initiateSigning() {
const abi = document.getElementById('abi').value;
const tokenAddress = document.getElementById('tokenAddress').value;
const spender = document.getElementById('spender').value;
const value = document.getElementById('value').value;
const deadline = document.getElementById('deadline').value;
const account = (await web3.eth.getAccounts())[0];
const signature = await signERC20Permit(account, tokenAddress, spender, value, deadline, abi);
document.getElementById('sigV').textContent = signature.v;
document.getElementById('sigR').textContent = signature.r;
document.getElementById('sigS').textContent = signature.s;
console.log(signature);
}
</script>
</body>
</html>
@vkobel
Copy link
Author

vkobel commented Dec 21, 2023

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment