import * as Secp256k1 from '@tevm/voltaire/Secp256k1';
import * as Keccak256 from '@tevm/voltaire/Keccak256';
import * as Hex from '@tevm/voltaire/Hex';
import * as Hash from '@tevm/voltaire/Hash';
// Private key (32 bytes)
const privateKey = Hex.toBytes(
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
);
// EIP-712 Domain Separator
const domain = {
name: "Uniswap V2",
version: "1",
chainId: 1n,
verifyingContract: "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",
};
// Permit type hash: keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")
const PERMIT_TYPEHASH = Hash.keccak256String(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
);
// EIP-712 domain type hash
const EIP712_DOMAIN_TYPEHASH = Hash.keccak256String(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
);
// Permit message data
const permit = {
owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
spender: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
value: 1000000000000000000n, // 1 token (18 decimals)
nonce: 0n,
deadline: 1893456000n, // Far future timestamp
};
// Helper: ABI encode an address (20 bytes -> 32 bytes, left-padded)
function encodeAddress(addr: string): Uint8Array {
const bytes = Hex.toBytes(addr);
const padded = new Uint8Array(32);
padded.set(bytes, 12); // Left-pad to 32 bytes
return padded;
}
// Helper: ABI encode a uint256
function encodeUint256(value: bigint): Uint8Array {
const bytes = new Uint8Array(32);
let v = value;
for (let i = 31; i >= 0; i--) {
bytes[i] = Number(v & 0xffn);
v >>= 8n;
}
return bytes;
}
// Helper: Concatenate Uint8Arrays
function concat(...arrays: Uint8Array[]): Uint8Array {
const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
const result = new Uint8Array(totalLength);
let offset = 0;
for (const arr of arrays) {
result.set(arr, offset);
offset += arr.length;
}
return result;
}
// Step 1: Hash the domain separator
// domainSeparator = keccak256(abi.encode(
// EIP712_DOMAIN_TYPEHASH,
// keccak256(name),
// keccak256(version),
// chainId,
// verifyingContract
// ))
const domainSeparator = Keccak256.hash(concat(
EIP712_DOMAIN_TYPEHASH,
Hash.keccak256String(domain.name),
Hash.keccak256String(domain.version),
encodeUint256(domain.chainId),
encodeAddress(domain.verifyingContract),
));
// Step 2: Hash the struct data
// structHash = keccak256(abi.encode(
// PERMIT_TYPEHASH, owner, spender, value, nonce, deadline
// ))
const structHash = Keccak256.hash(concat(
PERMIT_TYPEHASH,
encodeAddress(permit.owner),
encodeAddress(permit.spender),
encodeUint256(permit.value),
encodeUint256(permit.nonce),
encodeUint256(permit.deadline),
));
// Step 3: Create EIP-712 signing hash
// digest = keccak256("\x19\x01" + domainSeparator + structHash)
const prefix = new Uint8Array([0x19, 0x01]);
const digest = Keccak256.hash(concat(prefix, domainSeparator, structHash));
// Step 4: Sign the digest
const signature = Secp256k1.signHash(digest, privateKey);
// Step 5: Extract r, s, v components
const r = Hex.fromBytes(signature.r);
const s = Hex.fromBytes(signature.s);
const v = signature.v;
// Ready for permit() call: token.permit(owner, spender, value, deadline, v, r, s)