Created
December 21, 2023 15:29
-
-
Save vkobel/fd23cfbed3c5c288da5b67a7faea3c5b to your computer and use it in GitHub Desktop.
Web3 signer for the EIP-20 Permit method
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
Author
vkobel
commented
Dec 21, 2023

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