diff --git a/packages/lib-sourcify/src/lib/types.ts b/packages/lib-sourcify/src/lib/types.ts index 545f57aff..d1173cc80 100644 --- a/packages/lib-sourcify/src/lib/types.ts +++ b/packages/lib-sourcify/src/lib/types.ts @@ -76,6 +76,7 @@ export interface Match { create2Args?: Create2Args; libraryMap?: StringMap; contextVariables?: ContextVariables; + creatorTxHash?: string; } export type Status = diff --git a/packages/lib-sourcify/src/lib/verification.ts b/packages/lib-sourcify/src/lib/verification.ts index 025148516..5d082d5e8 100644 --- a/packages/lib-sourcify/src/lib/verification.ts +++ b/packages/lib-sourcify/src/lib/verification.ts @@ -301,6 +301,7 @@ export async function matchWithCreationTx( recompiledCreationBytecode ); match.abiEncodedConstructorArguments = abiEncodedConstructorArguments; + match.creatorTxHash = creatorTxHash; } } /** diff --git a/packages/lib-sourcify/test/utils.ts b/packages/lib-sourcify/test/utils.ts index d45a6f860..202d355d9 100644 --- a/packages/lib-sourcify/test/utils.ts +++ b/packages/lib-sourcify/test/utils.ts @@ -17,7 +17,7 @@ import { expect } from 'chai'; /** * Function to deploy contracts from provider unlocked accounts * - * @returns the address of the deployed contract + * @returns the address of the deployed contract and the creator tx hash */ // TODO: ABI type definition export async function deployFromAbiAndBytecode( diff --git a/src/server/controllers/VerificationController-util.ts b/src/server/controllers/VerificationController-util.ts index ae430ab1c..cc40e4a97 100644 --- a/src/server/controllers/VerificationController-util.ts +++ b/src/server/controllers/VerificationController-util.ts @@ -162,6 +162,7 @@ export type ContractMeta = { abiEncodedConstructorArguments?: string; msgSender?: string; }; + creatorTxHash?: string; status?: Status; statusMessage?: string; storageTimestamp?: Date; @@ -387,7 +388,8 @@ export const verifyContractsInSession = async ( continue; } - const { address, chainId, contract, contextVariables } = contractWrapper; + const { address, chainId, contract, contextVariables, creatorTxHash } = + contractWrapper; // The session saves the CheckedContract as a simple object, so we need to reinstantiate it const checkedContract = new CheckedContract( @@ -410,7 +412,8 @@ export const verifyContractsInSession = async ( checkedContract, chainId as string, address as string, - contextVariables + contextVariables, + creatorTxHash ); // Send to verification again with all source files. if (match.status === "extra-file-input-bug") { diff --git a/src/server/controllers/VerificationController.ts b/src/server/controllers/VerificationController.ts index 6eaae9a48..860b401af 100644 --- a/src/server/controllers/VerificationController.ts +++ b/src/server/controllers/VerificationController.ts @@ -132,7 +132,8 @@ export default class VerificationController contract, req.body.chain, req.addresses[0], // Due to the old API taking an array of addresses. - req.body.contextVariables + req.body.contextVariables, + req.body.creatorTxHash ); // Send to verification again with all source files. if (match.status === "extra-file-input-bug") { @@ -144,7 +145,8 @@ export default class VerificationController contractWithAllSources, req.body.chain, req.addresses[0], // Due to the old API taking an array of addresses. - req.body.contextVariables + req.body.contextVariables, + req.body.creatorTxHash ); if (tempMatch.status === "perfect") { await this.repositoryService.storeMatch(contract, tempMatch); @@ -230,6 +232,7 @@ export default class VerificationController contractWrapper.address = receivedContract.address; contractWrapper.chainId = receivedContract.chainId; contractWrapper.contextVariables = receivedContract.contextVariables; + contractWrapper.creatorTxHash = receivedContract.creatorTxHash; if (isVerifiable(contractWrapper)) { verifiable[id] = contractWrapper; } @@ -446,14 +449,19 @@ export default class VerificationController const contract: CheckedContract = checkedContracts[0]; - const result = await verifyCreate2( + const match = await verifyCreate2( contract, deployerAddress, salt, create2Address, abiEncodedConstructorArguments ); - res.send({ result: [result] }); + + if (match.status) { + await this.repositoryService.storeMatch(contract, match); + } + + res.send({ result: [match] }); }; private sessionVerifyCreate2 = async ( @@ -495,6 +503,10 @@ export default class VerificationController contractWrapper.storageTimestamp = match.storageTimestamp; contractWrapper.address = match.address; + if (match.status) { + await this.repositoryService.storeMatch(contract, match); + } + res.send(getSessionJSON(session)); }; @@ -563,6 +575,11 @@ export default class VerificationController ...req.body.contextVariables, }) ), + body("creatorTxHash") + .optional() + .custom( + (creatorTxHash, { req }) => (req.body.creatorTxHash = creatorTxHash) + ), this.safeHandler(this.legacyVerifyEndpoint) ); diff --git a/src/server/services/RepositoryService.ts b/src/server/services/RepositoryService.ts index 89d81d92b..c1b591e20 100644 --- a/src/server/services/RepositoryService.ts +++ b/src/server/services/RepositoryService.ts @@ -403,18 +403,22 @@ export default class RepositoryService implements IRepositoryService { match.address, contract.solidity ); - this.storeMetadata( + + // Store metadata + this.storeJSON( matchQuality, match.chainId, match.address, + "metadata.json", contract.metadata ); if (match.abiEncodedConstructorArguments) { - this.storeConstructorArgs( + this.storeTxt( matchQuality, match.chainId, match.address, + "constructor-args.txt", match.abiEncodedConstructorArguments ); } @@ -423,28 +427,41 @@ export default class RepositoryService implements IRepositoryService { match.contextVariables && Object.keys(match.contextVariables).length > 0 ) { - this.storeContextVariables( + this.storeJSON( matchQuality, match.chainId, match.address, + "context-variables.json", match.contextVariables ); } + if (match.creatorTxHash) { + this.storeTxt( + matchQuality, + match.chainId, + match.address, + "creator-tx-hash.txt", + match.creatorTxHash + ); + } + if (match.create2Args) { - this.storeCreate2Args( + this.storeJSON( matchQuality, match.chainId, match.address, + "create2-args.json", match.create2Args ); } if (match.libraryMap && Object.keys(match.libraryMap).length) { - this.storeLibraryMap( + this.storeJSON( matchQuality, match.chainId, match.address, + "library-map.json", match.libraryMap ); } @@ -527,93 +544,40 @@ export default class RepositoryService implements IRepositoryService { } } - private storeMetadata( + private storeJSON( matchQuality: MatchQuality, chainId: string, address: string, - metadata: Metadata + fileName: string, + contentJSON: any ) { this.save( { matchQuality, chainId, address, - fileName: "metadata.json", - }, - JSON.stringify(metadata) - ); - } - - private storeConstructorArgs( - matchQuality: MatchQuality, - chainId: string, - address: string, - abiEncodedConstructorArguments: string - ) { - this.save( - { - matchQuality, - chainId, - address, - source: false, - fileName: "constructor-args.txt", - }, - abiEncodedConstructorArguments - ); - } - - private storeContextVariables( - matchQuality: MatchQuality, - chainId: string, - address: string, - contextVariables: ContextVariables - ) { - this.save( - { - matchQuality, - chainId, - address, - source: false, - fileName: "context-variables.json", - }, - JSON.stringify(contextVariables, undefined, 2) - ); - } - - private storeCreate2Args( - matchQuality: MatchQuality, - chainId: string, - address: string, - create2Args: Create2Args - ) { - this.save( - { - matchQuality, - chainId, - address, - source: false, - fileName: "create2-args.json", + fileName, }, - JSON.stringify(create2Args) + JSON.stringify(contentJSON) ); } - private storeLibraryMap( + private storeTxt( matchQuality: MatchQuality, chainId: string, address: string, - libraryMap: StringMap + fileName: string, + content: string ) { - const indentationSpaces = 2; this.save( { matchQuality, chainId, address, source: false, - fileName: "library-map.json", + fileName, }, - JSON.stringify(libraryMap, null, indentationSpaces) + content ); } diff --git a/src/server/services/VerificationService.ts b/src/server/services/VerificationService.ts index be661bcce..b33e4cc7c 100644 --- a/src/server/services/VerificationService.ts +++ b/src/server/services/VerificationService.ts @@ -45,6 +45,7 @@ export default class VerificationService implements IVerificationService { } catch (err) { // Find the creator tx if it wasn't supplied and try verifying again with it. if ( + !creatorTxHash && err instanceof Error && err.message === "The deployed and recompiled bytecode don't match." ) { diff --git a/test/helpers/helpers.js b/test/helpers/helpers.js index 28e6e5c14..44d7a5237 100644 --- a/test/helpers/helpers.js +++ b/test/helpers/helpers.js @@ -16,6 +16,57 @@ async function deployFromAbiAndBytecode(web3, abi, bytecode, from, args) { return contractResponse.options.address; } +/** + * Creator tx hash is needed for tests. This function returns the tx hash in addition to the contract address. + * + * @returns The contract address and the tx hash + */ +async function deployFromAbiAndBytecodeForCreatorTxHash( + web3, + abi, + bytecode, + from, + args +) { + // Deploy contract + const contract = new web3.eth.Contract(abi); + const deployment = contract.deploy({ + data: bytecode, + arguments: args || [], + }); + const gas = await deployment.estimateGas({ from }); + + // If awaited, the send() Promise returns the contract instance. + // We also need the tx hash so we need two seperate event listeners. + const sendPromiEvent = deployment.send({ + from, + gas, + }); + + const txHashPromise = new Promise((resolve, reject) => { + sendPromiEvent.on("transactionHash", (txHash) => { + resolve(txHash); + }); + sendPromiEvent.on("error", (error) => { + reject(error); + }); + }); + + const contractAddressPromise = new Promise((resolve, reject) => { + sendPromiEvent.on("receipt", (receipt) => { + if (!receipt.contractAddress) { + reject(new Error("No contract address in receipt")); + } else { + resolve(receipt.contractAddress); + } + }); + sendPromiEvent.on("error", (error) => { + reject(error); + }); + }); + + return Promise.all([contractAddressPromise, txHashPromise]); +} /** * Function to deploy contracts from an external account with private key */ @@ -86,6 +137,7 @@ async function callContractMethodWithTx( module.exports = { deployFromAbiAndBytecode, + deployFromAbiAndBytecodeForCreatorTxHash, deployFromPrivateKey, waitSecs, callContractMethod, diff --git a/test/server.js b/test/server.js index 4197093d0..b14ab53c5 100644 --- a/test/server.js +++ b/test/server.js @@ -23,7 +23,11 @@ const MAX_SESSION_SIZE = require("../dist/server/controllers/VerificationController-util").MAX_SESSION_SIZE; const GANACHE_PORT = 8545; const StatusCodes = require("http-status-codes").StatusCodes; -const { waitSecs, callContractMethodWithTx } = require("./helpers/helpers"); +const { + waitSecs, + callContractMethodWithTx, + deployFromAbiAndBytecodeForCreatorTxHash, +} = require("./helpers/helpers"); const { deployFromAbiAndBytecode } = require("./helpers/helpers"); chai.use(chaiHttp); @@ -352,16 +356,28 @@ describe("Server", function () { .post("/session/verify/create2") .send({ deployerAddress: "0xd9145CCE52D386f254917e481eB44e9943F39138", - salt: 12345, + salt: 12344, abiEncodedConstructorArguments: "0x0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4", clientToken: clientToken || "", - create2Address: "0x801B9c0Ee599C3E5ED60e4Ec285C95fC9878Ee64", + create2Address: "0x65790cc291a234eDCD6F28e1F37B036eD4F01e3B", verificationId: verificationId, }) .end((err, res) => { assertAllFound(err, res, "perfect"); - done(); + chai + .request(server.app) + .get("/check-all-by-addresses") + .query({ + chainIds: "0", + addresses: ["0x65790cc291a234eDCD6F28e1F37B036eD4F01e3B"], + }) + .end((err, res) => { + chai.expect(err).to.be.null; + chai.expect(res.body).to.have.a.lengthOf(1); + chai.expect(res.body[0].chainIds).to.have.a.lengthOf(1); + done(); + }); }); }); @@ -395,7 +411,19 @@ describe("Server", function () { }) .end((err, res) => { assertAPIAllFound(err, res, "perfect"); - done(); + chai + .request(server.app) + .get("/check-all-by-addresses") + .query({ + chainIds: "0", + addresses: ["0x801B9c0Ee599C3E5ED60e4Ec285C95fC9878Ee64"], + }) + .end((err, res) => { + chai.expect(err).to.be.null; + chai.expect(res.body).to.have.a.lengthOf(1); + chai.expect(res.body[0].chainIds).to.have.a.lengthOf(1); + done(); + }); }); }); }); @@ -825,6 +853,67 @@ describe("Server", function () { assertions(null, res, null, address, "partial"); }); + it("should fail to verify a contract with immutables but should succeed with creatorTxHash and save creator-tx-hash.txt", async () => { + const artifact = require("./testcontracts/WithImmutables/artifact.json"); + const [address, creatorTxHash] = + await deployFromAbiAndBytecodeForCreatorTxHash( + localWeb3Provider, + artifact.abi, + artifact.bytecode, + accounts[0], + [999] + ); + + const metadata = require("./testcontracts/WithImmutables/metadata.json"); + const sourcePath = path.join( + "test", + "testcontracts", + "WithImmutables", + "sources", + "WithImmutables.sol" + ); + const sourceBuffer = fs.readFileSync(sourcePath); + + const res1 = await chai + .request(server.app) + .post("/") + .send({ + address: address, + chain: defaultContractChain, + files: { + "metadata.json": JSON.stringify(metadata), + "WithImmutables.sol": sourceBuffer.toString(), + }, + }); + assertBytecodesDontMatch(null, res1); + + // Now pass the creatorTxHash + const res2 = await chai + .request(server.app) + .post("/") + .send({ + address: address, + chain: defaultContractChain, + files: { + "metadata.json": JSON.stringify(metadata), + "WithImmutables.sol": sourceBuffer.toString(), + }, + creatorTxHash: creatorTxHash, + }); + assertions(null, res2, null, address); + assertEqualityFromPath( + creatorTxHash, + path.join( + server.repository, + "contracts", + "full_match", + defaultContractChain, + address, + "creator-tx-hash.txt" + ) + ); + }); + it("should verify a contract created by a factory contract and has immutables without constructor arguments but with msg.sender assigned immutable", async () => { const artifact = require("./testcontracts/FactoryImmutableWithoutConstrArg/Factory3.json"); const factoryAddress = await deployFromAbiAndBytecode( @@ -1573,6 +1662,64 @@ describe("Server", function () { }); }); + it("should fail to verify a contract with immutables but should succeed with creatorTxHash and save creator-tx-hash.txt", async () => { + const artifact = require("./testcontracts/WithImmutables/artifact.json"); + const [address, creatorTxHash] = + await deployFromAbiAndBytecodeForCreatorTxHash( + localWeb3Provider, + artifact.abi, + artifact.bytecode, + accounts[0], + [999] + ); + + const metadata = require("./testcontracts/WithImmutables/metadata.json"); + const metadataBuffer = Buffer.from(JSON.stringify(metadata)); + const sourcePath = path.join( + "test", + "testcontracts", + "WithImmutables", + "sources", + "WithImmutables.sol" + ); + const sourceBuffer = fs.readFileSync(sourcePath); + + const agent = chai.request.agent(server.app); + + const res1 = await agent + .post("/session/input-files") + .attach("files", sourceBuffer) + .attach("files", metadataBuffer); + + let contracts = assertSingleContractStatus(res1, "error"); + + contracts[0].address = address; + contracts[0].chainId = defaultContractChain; + const res2 = await agent + .post("/session/verify-validated") + .send({ contracts }); + + contracts = assertSingleContractStatus(res2, "error"); + contracts[0].creatorTxHash = creatorTxHash; + + const res3 = await agent + .post("/session/verify-validated") + .send({ contracts }); + + assertSingleContractStatus(res3, "perfect"); + assertEqualityFromPath( + creatorTxHash, + path.join( + server.repository, + "contracts", + "full_match", + defaultContractChain, + address, + "creator-tx-hash.txt" + ) + ); + }); + it("should verify a contract created by a factory contract and has immutables", async () => { const deployValue = 12345; diff --git a/test/testcontracts/WithImmutables/artifact.json b/test/testcontracts/WithImmutables/artifact.json new file mode 100644 index 000000000..b591c7466 --- /dev/null +++ b/test/testcontracts/WithImmutables/artifact.json @@ -0,0 +1,55 @@ +{ + "bytecode": "60a060405234801561001057600080fd5b506040516103ca3803806103ca8339818101604052602081101561003357600080fd5b81019080805190602001909291905050508060808181525050506080516103636100676000398061026052506103636000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806357de26a41461004657806379d6348d146100c9578063ced7b2e314610184575b600080fd5b61004e6101a2565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561008e578082015181840152602081019050610073565b50505050905090810190601f1680156100bb5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610182600480360360208110156100df57600080fd5b81019080803590602001906401000000008111156100fc57600080fd5b82018360208201111561010e57600080fd5b8035906020019184600183028401116401000000008311171561013057600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610244565b005b61018c61025e565b6040518082815260200191505060405180910390f35b606060008054600181600116156101000203166002900480601f01602080910402602001604051908101604052809291908181526020018280546001816001161561010002031660029004801561023a5780601f1061020f5761010080835404028352916020019161023a565b820191906000526020600020905b81548152906001019060200180831161021d57829003601f168201915b5050505050905090565b806000908051906020019061025a929190610282565b5050565b7f000000000000000000000000000000000000000000000000000000000000000081565b828054600181600116156101000203166002900490600052602060002090601f0160209004810192826102b857600085556102ff565b82601f106102d157805160ff19168380011785556102ff565b828001600101855582156102ff579182015b828111156102fe5782518255916020019190600101906102e3565b5b50905061030c9190610310565b5090565b5b80821115610329576000816000905550600101610311565b509056fea26469706673582212200f4b79ce4268a474314f15ab80925bd78deed6af2213db6b41d15da4ec81536564736f6c63430007040033", + "abi": [ + { + "inputs": [ + { + "internalType": "uint256", + "name": "a", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "_a", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "read", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "name": "sign", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ] +} diff --git a/test/testcontracts/WithImmutables/metadata.json b/test/testcontracts/WithImmutables/metadata.json new file mode 100644 index 000000000..10a3502ba --- /dev/null +++ b/test/testcontracts/WithImmutables/metadata.json @@ -0,0 +1,95 @@ +{ + "compiler": { + "version": "0.7.4+commit.3f05b770" + }, + "language": "Solidity", + "output": { + "abi": [ + { + "inputs": [ + { + "internalType": "uint256", + "name": "a", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "_a", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "read", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "name": "sign", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "devdoc": { + "kind": "dev", + "methods": {}, + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": {}, + "version": 1 + } + }, + "settings": { + "compilationTarget": { + "contracts/WithImmutables.sol": "WithImmutables" + }, + "evmVersion": "istanbul", + "libraries": {}, + "metadata": { + "bytecodeHash": "ipfs" + }, + "optimizer": { + "enabled": false, + "runs": 200 + }, + "remappings": [] + }, + "sources": { + "contracts/WithImmutables.sol": { + "keccak256": "0xbee62e4af99b4199c5b0982f5da15fc58215a4d547724bc574d516df07f66dc2", + "urls": [ + "bzz-raw://6d83c127e075a149ec14c6af579bc7b24955cdb7578ae7da2f253b7142d267cc", + "dweb:/ipfs/QmW6tdCTV7X5dd5LCKDWedbMmkurQTMi4ePx7LY3DNuLn7" + ] + } + }, + "version": 1 +} \ No newline at end of file diff --git a/test/testcontracts/WithImmutables/sources/WithImmutables.sol b/test/testcontracts/WithImmutables/sources/WithImmutables.sol new file mode 100644 index 000000000..d920e6cdd --- /dev/null +++ b/test/testcontracts/WithImmutables/sources/WithImmutables.sol @@ -0,0 +1,19 @@ +pragma solidity >=0.7.0; + +contract WithImmutables { + uint256 public immutable _a; + + string _name; + + constructor (uint256 a) { + _a = a; + } + + function sign(string memory name) public { + _name = name; + } + + function read() public view returns(string memory) { + return _name; + } +} \ No newline at end of file diff --git a/ui/src/pages/Verifier/CheckedContractsView/CheckedContract/ChainAddressForm/index.tsx b/ui/src/pages/Verifier/CheckedContractsView/CheckedContract/ChainAddressForm/index.tsx index 9df2e9199..feabc1e91 100644 --- a/ui/src/pages/Verifier/CheckedContractsView/CheckedContract/ChainAddressForm/index.tsx +++ b/ui/src/pages/Verifier/CheckedContractsView/CheckedContract/ChainAddressForm/index.tsx @@ -1,3 +1,4 @@ +import { isHexString } from "@ethersproject/bytes"; import { isAddress } from "@ethersproject/address"; import React, { ChangeEventHandler, @@ -48,6 +49,9 @@ const ChainAddressForm = ({ useState(""); const [msgSender, setMsgSender] = useState(""); const [isInvalidMsgSender, setIsInvalidMsgSender] = useState(false); + const [creatorTxHash, setCreatorTxHash] = useState(""); + const [isInvalidCreatorTxHash, setIsInvalidCreatorTxHash] = + useState(false); const [showRawAbiInput, setShowRawAbiInput] = useState(false); const [isInvalidConstructorArguments, setIsInvalidConstructorArguments] = useState(false); @@ -101,6 +105,17 @@ const ChainAddressForm = ({ setIsInvalidMsgSender(false); }; + const handleCreatorTxHashChange: ChangeEventHandler = ( + e + ) => { + const tempHash = e.target.value; + setCreatorTxHash(tempHash); + if (!isHexString(tempHash, 32) && tempHash !== "") { + return setIsInvalidCreatorTxHash(true); + } + setIsInvalidCreatorTxHash(false); + }; + const handleSubmit: FormEventHandler = (e) => { e.preventDefault(); if (!address || !chainId || isInvalidAddress || isInvalidMsgSender) return; @@ -113,6 +128,7 @@ const ChainAddressForm = ({ abiEncodedConstructorArguments, msgSender, }, + creatorTxHash, }).finally(() => setIsLoading(false)); }; @@ -167,11 +183,46 @@ const ChainAddressForm = ({ /> - {/* Constructor arguments, msg.sender etc fields. */} + {/* CreatorTx, Constructor arguments, msg.sender etc fields. */}
-
+ {/* Creator Tx Hash */} +
+
+ + + {isInvalidCreatorTxHash && ( + + Invalid Transaction Hash String + + )} +
+ + +
+
Inputs below will be used to simulate the creation of the contract. This helps us verify contracts created by a factory contract.
If there are other variables your contract makes use of during diff --git a/ui/src/types.ts b/ui/src/types.ts index 09b6bf753..ad6eec949 100644 --- a/ui/src/types.ts +++ b/ui/src/types.ts @@ -69,6 +69,7 @@ export type VerificationInput = { abiEncodedConstructorArguments?: string; msgSender?: string; }; + creatorTxHash?: string; }; export type Create2VerificationInput = {