Add ERC7739 and ERC7739Utils (#5664)
This commit is contained in:
118
test/helpers/erc7739.js
Normal file
118
test/helpers/erc7739.js
Normal file
@ -0,0 +1,118 @@
|
||||
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,
|
||||
};
|
||||
Reference in New Issue
Block a user