EIP-712 Signing
CRE Connect authorizes every Operation with an EIP-712 typed-data signature. The Smart Account verifies the signature on-chain before any transaction in the Operation runs. With EIP-712, wallet UIs and key-management systems can show a human-readable approval prompt instead of an opaque hash.
Domain
The EIP-712 domain is constant across CRE Connect:
Field | Value | Source |
|---|---|---|
name | CLLSmartAccount | transact/types constant EIP712DomainName. |
version | 1 | transact/types constant EIP712DomainVersion. |
chainId | The numeric chain ID, derived from the CCIP chain selector passed to the SDK. | Resolved with GetChainIDFromSelector(selector). EVM-only. |
verifyingContract | The Smart Account address (Operation.Account). | Built by SmartAccountEIP712Domain(chainId, account). |
Because verifyingContract is the Smart Account itself, two wallets on the same chain produce different domain hashes. A signature for wallet A is unusable on wallet B even if both contracts speak the same protocol. This is what makes the EIP-712 binding per-account.
Typed-data schema
The primary type is Operation. It references one dependent type, Transaction:
Operation {
id uint256
account address
deadline uint256
transactions Transaction[]
}
Transaction {
to address
value uint256
data bytes
}
These are the same fields documented in Operations. EIP-712 hashing follows the standard rules:
bytesandstringfields are hashed withkeccak256before being included.- Dynamic arrays are hashed by concatenating the hashes of each element and
keccak256-ing the result. - The struct hash is
keccak256(typeHash ‖ encoded fields). - The final digest is
keccak256("\x19\x01" ‖ domainSeparator ‖ structHash).
The SDK builds this digest with go-ethereum's apitypes.TypedDataAndHash, so the bytes are bit-for-bit compatible with any EIP-712 implementation in Solidity, Ethers, viem, or rust-ethereum.
Hashing pipeline (in code)

Most application code does not call the Handler directly. Use the high-level entry points on the unified client:
client.Transact.HashOperation(op, chainSelector): returns the 32-byte digest without signing. Use this for draft operations and external approval workflows.client.Transact.SignOperation(ctx, op, signer, chainSelector): returns the 32-byte digest and the signature.client.Transact.ExecuteOperation(ctx, channelID, signer, op, chainSelector): signs and submits in one call.client.Transact.ExecuteTransactions(ctx, channelID, signer, executorAccount, txs, deadline, chainSelector): convenience wrapper that builds theOperationfor you.
Draft operations use the same digest. In the draft flow, CRE Connect stores the unsigned operation first, then your application sends the digest and signature when it finalizes the draft.
The lower-level handler is exposed in transact/eip712 if you need the digest without signing or want to drive the pipeline yourself:
eip712.Handler.HashOperation(op, chainSelector): returns the 32-byte digest, no signing.eip712.Handler.SignOperation(ctx, op, signer, chainSelector): computes the digest and asks the suppliedSignerto sign it.
Signers
A signer.Signer is anything that produces a 65-byte ECDSA signature over a 32-byte hash. The interface lives in github.com/smartcontractkit/crec-sdk/transact/signer:
import "github.com/smartcontractkit/crec-sdk/transact/signer"
type Signer interface {
Sign(ctx context.Context, hash []byte) ([]byte, error)
}
Some signer adapters (for example, Fireblocks) also implement signer.TypedDataSigner, which lets the adapter render a typed-data prompt to a human approver:
type TypedDataSigner interface {
SignTypedData(ctx context.Context, typedData *TypedData) ([]byte, error)
}
The SDK ships five built-in adapters and supports any custom implementation:
| Adapter | Package | Notes |
|---|---|---|
| Local (ECDSA) | transact/signer/local | In-process key. Recommended for local development only. |
| AWS KMS | transact/signer/kms | KMS-managed ECC_SECG_P256K1 key. |
| HashiCorp Vault | transact/signer/vault | Vault Transit secrets engine, ECDSA secp256k1 key. |
| Fireblocks | transact/signer/fireblocks | Implements both Signer and TypedDataSigner. |
| Privy | transact/signer/privy | Privy wallet via REST API. |
| Custom | any package | Any type implementing the Signer interface. |
See Signers for runnable setup guides.
Authorization vs execution
EIP-712 signing only authorizes the Operation. It does not pay gas, and it does not put the Operation on-chain by itself. Execution still goes through the Chainlink DON, which signs and broadcasts the underlying transaction on your Smart Account's behalf (see Account Abstraction & Gas Sponsorship).
This separation has two practical consequences:
- The signing key never holds gas. A KMS- or Vault-managed key with no on-chain presence is sufficient.
- The on-chain
tx.originis the DON's writer, not the user. Authorization is anchored on the EIP-712-recovered signer address compared against the wallet's allow-list, not on the message sender. See Smart Accounts for the contract-level model.
Related
- Operations & Transactions: what gets signed.
- Draft Operations: deferred signing with the same EIP-712 digest.
- Build and Sign Operations: the SDK call flow.
- Signing Transparency: render the typed data for human approvers.
- Smart Accounts: the on-chain verifier of the signature.
- EIP-712: Typed structured data hashing and signing: the canonical Ethereum specification.