erc7739.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. const { ethers } = require('hardhat');
  2. const { formatType } = require('./eip712');
  3. const PersonalSign = formatType({ prefixed: 'bytes' });
  4. const TypedDataSign = contentsTypeName =>
  5. formatType({
  6. contents: contentsTypeName,
  7. name: 'string',
  8. version: 'string',
  9. chainId: 'uint256',
  10. verifyingContract: 'address',
  11. salt: 'bytes32',
  12. });
  13. class ERC7739Signer extends ethers.AbstractSigner {
  14. #signer;
  15. #domain;
  16. constructor(signer, domain) {
  17. super(signer.provider);
  18. this.#signer = signer;
  19. this.#domain = domain;
  20. }
  21. static from(signer, domain) {
  22. return new this(signer, domain);
  23. }
  24. get signingKey() {
  25. return this.#signer.signingKey;
  26. }
  27. get privateKey() {
  28. return this.#signer.privateKey;
  29. }
  30. async getAddress() {
  31. return this.#signer.getAddress();
  32. }
  33. connect(provider) {
  34. this.#signer.connect(provider);
  35. }
  36. async signTransaction(tx) {
  37. return this.#signer.signTransaction(tx);
  38. }
  39. async signMessage(message) {
  40. return this.#signer.signTypedData(this.#domain, { PersonalSign }, ERC4337Utils.preparePersonalSign(message));
  41. }
  42. async signTypedData(domain, types, value) {
  43. const { allTypes, contentsTypeName, contentsDescr } = ERC4337Utils.getContentsDetail(types);
  44. return Promise.resolve(
  45. this.#signer.signTypedData(domain, allTypes, ERC4337Utils.prepareSignTypedData(value, this.#domain)),
  46. ).then(signature =>
  47. ethers.concat([
  48. signature,
  49. ethers.TypedDataEncoder.hashDomain(domain), // appDomainSeparator
  50. ethers.TypedDataEncoder.hashStruct(contentsTypeName, types, value), // contentsHash
  51. ethers.toUtf8Bytes(contentsDescr),
  52. ethers.toBeHex(contentsDescr.length, 2),
  53. ]),
  54. );
  55. }
  56. }
  57. class ERC4337Utils {
  58. static preparePersonalSign(message) {
  59. return {
  60. prefixed: ethers.concat([
  61. ethers.toUtf8Bytes(ethers.MessagePrefix),
  62. ethers.toUtf8Bytes(String(message.length)),
  63. typeof message === 'string' ? ethers.toUtf8Bytes(message) : message,
  64. ]),
  65. };
  66. }
  67. static prepareSignTypedData(contents, signerDomain) {
  68. return {
  69. name: signerDomain.name ?? '',
  70. version: signerDomain.version ?? '',
  71. chainId: signerDomain.chainId ?? 0,
  72. verifyingContract: signerDomain.verifyingContract ?? ethers.ZeroAddress,
  73. salt: signerDomain.salt ?? ethers.ZeroHash,
  74. contents,
  75. };
  76. }
  77. static getContentsDetail(contentsTypes, contentsTypeName = Object.keys(contentsTypes).at(0)) {
  78. // Examples values
  79. //
  80. // contentsTypeName B
  81. // typedDataSignType TypedDataSign(B contents,...)A(uint256 v)B(Z z)Z(A a)
  82. // contentsType A(uint256 v)B(Z z)Z(A a)
  83. // contentsDescr A(uint256 v)B(Z z)Z(A a)B
  84. const allTypes = { TypedDataSign: TypedDataSign(contentsTypeName), ...contentsTypes };
  85. const typedDataSignType = ethers.TypedDataEncoder.from(allTypes).encodeType('TypedDataSign');
  86. const contentsType = typedDataSignType.slice(typedDataSignType.indexOf(')') + 1); // Remove TypedDataSign (first object)
  87. const contentsDescr = contentsType + (contentsType.startsWith(contentsTypeName) ? '' : contentsTypeName);
  88. return {
  89. allTypes,
  90. contentsTypes,
  91. contentsTypeName,
  92. contentsDescr,
  93. };
  94. }
  95. }
  96. module.exports = {
  97. ERC7739Signer,
  98. ERC4337Utils,
  99. PersonalSign,
  100. TypedDataSign,
  101. };