Sign a cyberAgreement
Register a template, create a multi-party agreement, collect EIP-712 signatures and finalize or void it in the CyberAgreementRegistry
cyberSign runs on the CyberAgreementRegistry, which holds agreement templates and the executed contracts made from them. Deals and rounds create their agreements in the same registry.
1. Register a template
A template is a reusable legal document with a field schema. Anyone can register one. Ids are first-come, and a collision reverts TemplateAlreadyExists.
ICyberAgreementRegistry(registry).createTemplate(
templateId, // bytes32, the id you choose
"Series A Stockholder Consent v1", // title
"ipfs://...", // legalContractUri (canonical text)
globalFields, // string[], fields common to the contract
partyFields // string[], fields filled per signing party
);A one-off agreement can skip this step. createStandaloneContractAndSign(title, legalContractUri, globalFields, partyFields, salt, globalValues, parties, partyValues, expiry, signature) derives a template id from the content, creates the template if it does not exist, creates the contract and records the proposer's signature, all in one transaction.
2. Create a contract from the template
bytes32 contractId = ICyberAgreementRegistry(registry).createContract(
templateId,
salt, // uint256
globalValues, // string[]
parties, // address[]
partyValues, // string[][], per-party values
secretHash, // bytes32
finalizer, // address allowed to finalize
expiry // uint256
);createContract returns the id, and the registry emits it in ContractCreated. An eth_call of the same createContract predicts it before you send. When a party must sign before the agreement exists (the proposer of a standalone agreement, for example), compute the id as keccak256(abi.encode(templateId, salt, globalValues, parties, secretHash, finalizer)). For a standalone agreement, use secretHash = 0, finalizer = address(0) and the derived template id.
A registry with the older signature type, whose SIGNATUREDATA_TYPEHASH() returns 0x49ba7af1…0da4, derives the id from four fields only: keccak256(abi.encode(templateId, salt, globalValues, parties)). The separate zkSync Era deployment is one. Both registry types report version() "1", so only the typehash tells them apart.
3. Collect each party's signature
Every signing entry point carries the signer's EIP-712 signature over the agreement content. On a registry with the current type, the typed data is:
Set signer to the party's address even when a delegate produces the signature; the registry accepts the signature for that party only. Read SIGNATUREDATA_TYPEHASH() on the registry before signing. 0xe37d17c3…6f05 means the type above. 0x49ba7af1…0da4, the older type used by the zkSync Era deployment, expects the same struct without signer.
The registry has three signing entry points:
signContract(contractId, partyValues, signature, fillUnallocated, secret)records the caller's own signature.signContractFor(signer, contractId, partyValues, signature, fillUnallocated, secret)relays another party's EIP-712 signature.signContractWithEscrow(escrowSigner, contractId, partyValues, signature, fillUnallocated, secret)applies a pre-escrowed signature. Only the contract's finalizer can call it, so the agreement must name a nonzero finalizer, such as a DealManager or RoundManager (FinalizerNotDefinedotherwise).
When every party has signed, the registry emits ContractFullySigned.
A party can delegate signing authority with setDelegation(delegate, expiry) (expiry == 0 means no expiry) and withdraw it with revokeDelegation(). The delegate signs SignatureData with signer set to the delegating party.
4. Finalize the contract
A contract created with finalizer == address(0) finalizes automatically when the last party signs. Otherwise the finalizer calls:
Void an agreement
voidContractFor(contractId, party, signature) records a party's void request. The signature is an EIP-712 VoidSignatureData(bytes32 contractId,address party) signature by the party or its delegate, and the finalizer can submit a request with no signature. The contract voids when every allocated party requests it (unfilled open slots don't count toward unanimity), when its nonzero expiry has passed, or when the first party requests it while only one signature has been collected.
Check an agreement's status
Read status with hasSigned, allPartiesSigned, isFinalized, isVoided, getContractDetails and getAgreementsForParty.
Function-level detail is in CyberAgreementRegistry, and MetaLeX's template library is in Agreement templates.
Last updated
Was this helpful?
