119 lines
3.3 KiB
JavaScript
119 lines
3.3 KiB
JavaScript
const { ethers } = require('hardhat');
|
|
const { formatType } = require('./eip712');
|
|
|
|
const PersonalSign = formatType({ prefixed: 'bytes' });
|
|
const TypedDataSign = contentsTypeName =>
|
|
formatType({
|
|
contents: contentsTypeName,
|
|
name: 'string',
|
|
version: 'string',
|
|
chainId: 'uint256',
|
|
verifyingContract: 'address',
|
|
salt: 'bytes32',
|
|
});
|
|
|
|
class ERC7739Signer extends ethers.AbstractSigner {
|
|
#signer;
|
|
#domain;
|
|
|
|
constructor(signer, domain) {
|
|
super(signer.provider);
|
|
this.#signer = signer;
|
|
this.#domain = domain;
|
|
}
|
|
|
|
static from(signer, domain) {
|
|
return new this(signer, domain);
|
|
}
|
|
|
|
get signingKey() {
|
|
return this.#signer.signingKey;
|
|
}
|
|
|
|
get privateKey() {
|
|
return this.#signer.privateKey;
|
|
}
|
|
|
|
async getAddress() {
|
|
return this.#signer.getAddress();
|
|
}
|
|
|
|
connect(provider) {
|
|
this.#signer.connect(provider);
|
|
}
|
|
|
|
async signTransaction(tx) {
|
|
return this.#signer.signTransaction(tx);
|
|
}
|
|
|
|
async signMessage(message) {
|
|
return this.#signer.signTypedData(this.#domain, { PersonalSign }, ERC4337Utils.preparePersonalSign(message));
|
|
}
|
|
|
|
async signTypedData(domain, types, value) {
|
|
const { allTypes, contentsTypeName, contentsDescr } = ERC4337Utils.getContentsDetail(types);
|
|
|
|
return Promise.resolve(
|
|
this.#signer.signTypedData(domain, allTypes, ERC4337Utils.prepareSignTypedData(value, this.#domain)),
|
|
).then(signature =>
|
|
ethers.concat([
|
|
signature,
|
|
ethers.TypedDataEncoder.hashDomain(domain), // appDomainSeparator
|
|
ethers.TypedDataEncoder.hashStruct(contentsTypeName, types, value), // contentsHash
|
|
ethers.toUtf8Bytes(contentsDescr),
|
|
ethers.toBeHex(contentsDescr.length, 2),
|
|
]),
|
|
);
|
|
}
|
|
}
|
|
|
|
class ERC4337Utils {
|
|
static preparePersonalSign(message) {
|
|
return {
|
|
prefixed: ethers.concat([
|
|
ethers.toUtf8Bytes(ethers.MessagePrefix),
|
|
ethers.toUtf8Bytes(String(message.length)),
|
|
typeof message === 'string' ? ethers.toUtf8Bytes(message) : message,
|
|
]),
|
|
};
|
|
}
|
|
|
|
static prepareSignTypedData(contents, signerDomain) {
|
|
return {
|
|
name: signerDomain.name ?? '',
|
|
version: signerDomain.version ?? '',
|
|
chainId: signerDomain.chainId ?? 0,
|
|
verifyingContract: signerDomain.verifyingContract ?? ethers.ZeroAddress,
|
|
salt: signerDomain.salt ?? ethers.ZeroHash,
|
|
contents,
|
|
};
|
|
}
|
|
|
|
static getContentsDetail(contentsTypes, contentsTypeName = Object.keys(contentsTypes).at(0)) {
|
|
// Examples values
|
|
//
|
|
// contentsTypeName B
|
|
// typedDataSignType TypedDataSign(B contents,...)A(uint256 v)B(Z z)Z(A a)
|
|
// contentsType A(uint256 v)B(Z z)Z(A a)
|
|
// contentsDescr A(uint256 v)B(Z z)Z(A a)B
|
|
const allTypes = { TypedDataSign: TypedDataSign(contentsTypeName), ...contentsTypes };
|
|
const typedDataSignType = ethers.TypedDataEncoder.from(allTypes).encodeType('TypedDataSign');
|
|
const contentsType = typedDataSignType.slice(typedDataSignType.indexOf(')') + 1); // Remove TypedDataSign (first object)
|
|
const contentsDescr = contentsType + (contentsType.startsWith(contentsTypeName) ? '' : contentsTypeName);
|
|
|
|
return {
|
|
allTypes,
|
|
contentsTypes,
|
|
contentsTypeName,
|
|
contentsDescr,
|
|
};
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
ERC7739Signer,
|
|
ERC4337Utils,
|
|
PersonalSign,
|
|
TypedDataSign,
|
|
};
|