Smart Contract
The Veylent smart contract is the on-chain component of Veylent. It records which block each anchor ID was anchored in, and emits events that serve as the immutable proof of signing.
Contract Interface
event DocumentAnchored(
bytes16 indexed anchorId,
bytes32 indexed compositeHash,
address indexed signer,
bytes16 previousAnchorId,
uint256 timestamp
);
address public immutable trustedRelayer;
mapping(bytes16 => uint256) public anchorBlock;
error UnauthorizedRelayer(address caller);
error InvalidAnchorId();
error AlreadyAnchored(bytes16 anchorId);
error UnknownPreviousAnchor(bytes16 previousAnchorId);
function anchorDocument(
bytes16 anchorId,
bytes32 compositeHash,
bytes16 previousAnchorId
) external onlyRelayer;
anchorDocument Function
Records a document signature on-chain. Only callable by the trusted relayer address set at deployment.
Parameters:
anchorId-- Random 16-byte ID chosen by the signing app and printed in the signature's QR codecompositeHash-- SHA-256 of the signing record JSON (includes anchor ID, document hash, signer info, timestamp, salt)previousAnchorId-- Anchor ID of the previous signature on the same document (zero for the first signature)
Behavior:
- Reverts with
UnauthorizedRelayerif the caller is not the trusted relayer - Reverts with
InvalidAnchorIdifanchorIdis zero - Reverts with
AlreadyAnchoredifanchorIdwas anchored before - Reverts with
UnknownPreviousAnchorifpreviousAnchorIdis non-zero and was never anchored - Stores
anchorBlock[anchorId] = block.number - Emits a
DocumentAnchoredevent with the provided data,_msgSender(), andblock.timestamp
Because an ID can be anchored only once and a previous anchor must already exist, a signing history can only be extended. It cannot be forked onto made-up predecessors.
anchorBlock Mapping
anchorBlock(anchorId) returns the block number the anchor ID was anchored in, or zero if never. Anyone can use it to find an anchor's event with a single-block log query, without relying on the Veylent server's database.
Access Control
The contract restricts who can anchor documents via an onlyRelayer modifier. The trustedRelayer address is set as an immutable in the constructor, meaning it cannot be changed after deployment. This prevents unauthorized parties from polluting the contract with fake anchors.
If the relayer key needs to be rotated, a new contract must be deployed. Old signatures stay verifiable on the old contract.
DocumentAnchored Event
The event serves as the permanent record. Event fields:
| Field | Type | Indexed | Purpose |
|---|---|---|---|
anchorId | bytes16 | Yes | Identifies the signature (as printed in its QR code) |
compositeHash | bytes32 | Yes | Commits to the signing record |
signer | address | Yes | Blockchain address that submitted the tx (the relayer) |
previousAnchorId | bytes16 | No | Links to previous signature (chain) |
timestamp | uint256 | No | Block timestamp at anchoring |
Signature Chaining
When a document is signed multiple times, each signature references the previous one by anchor ID:
Signature 1 Signature 2 Signature 3
anchorId = A1 anchorId = A2 anchorId = A3
compositeHash = H1 compositeHash = H2 compositeHash = H3
previousAnchorId = 0x000...0 previousAnchorId = A1 previousAnchorId = A2
The verification API walks this chain backwards, from the scanned signature to the first, to present the complete signing history.
Development Setup
The project uses Hardhat for local blockchain development:
# Start local blockchain
npx hardhat node
# Deploy contract
npx hardhat run scripts/deploy.ts --network localhost
Gas Costs
Each anchor writes one storage slot (anchorBlock[anchorId]) and emits one event. The event data lives in transaction logs, not contract storage. The storage slot is what lets anyone find the event without an index.
Verification Security
The verification API enforces two checks to ensure anchors are authentic:
- Contract address pinning -- The API reads
anchorBlockand queries events only on the officialVEYLENT_CONTRACT_ADDRESS. Events from copycat deployments are never seen. - Relayer restriction -- The
onlyRelayermodifier on-chain ensures only the trusted relayer can write to the contract, preventing unauthorized anchors at the source.
Together, these prevent both copycat contracts and unauthorized writes to the official contract.
Upgradeability
The current contract is not upgradeable. Once deployed, the contract code is immutable. This is intentional -- it means no one can change the rules after deployment, which strengthens the trust model.
If a new version is needed, a new contract is deployed and the API is updated to point to it. Old signatures remain verifiable on the old contract.