Matrix logo

Deployer

The deployer plans and executes intent-based contract deployments with idempotency guarantees. It handles both plain contract creation and CREATE2 deterministic deployments, wit...

Source file: internal/deployer/deployer.go

The deployer plans and executes intent-based contract deployments with idempotency guarantees. It handles both plain contract creation and CREATE2 deterministic deployments, with on-chain confirmation and registry write-back.


Design decisions

Idempotency by key, not by state

Deployments are keyed by idempotency_key + chain_id. Before broadcasting, the deployer checks the registry for an existing record. If found and confirmed on-chain (bytecode > 0 at the address), it returns the existing deployment with Existing: true. This makes retries safe: the same key always returns the same address.

CREATE2 support

CREATE2 deployments use a deterministic factory pattern. The deployer:

  1. Computes the deterministic address from deployer (factory), salt (32 bytes), and keccak256(initCode)
  2. Checks if code already exists at that address
  3. If not, builds a factory call: to = deployer, data = salt ++ initCode
  4. After broadcast, uses the precomputed create2Addr as the deployment address (not the receipt's ContractAddress, which is the factory call's receipt)
func computeCreate2(deployerHex, saltHex string, initCode []byte) (common.Address, error)

On-chain confirmation

Before returning an existing deployment, the deployer verifies the contract is actually on-chain:

func (d *Deployer) confirmOnChain(ctx context.Context, chainID, address string) (bool, error)

This calls eth_getCode and checks len(code) > 0. This prevents returning stale registry entries from reverted deployments or chain resets.

Constructor arg packing

Constructor arguments are ABI-encoded and appended to the creation bytecode:

func packConstructor(art registry.ArtifactRecord, args json.RawMessage) ([]byte, error)
  • Decodes bytecode from hex
  • If args present and not null: calls abienc.PackConstructorArgs(art.ABI, args)
  • Returns bytecode ++ packedArgs

Wallet integration

The deployer delegates signing and broadcasting to the wallet gate:

  1. Wallet.Authorize(capabilityToken, spendCap, chainID) — policy check
  2. Wallet.Sign(ctx, client, intent, policy) — sign or delegate
  3. If RawTx returned: client.SendRawTransaction(ctx, res.RawTx) — broadcast
  4. If TxHash returned: signer already broadcast (embedded wallet mode)
  5. client.WaitReceipt(ctx, txHash) — poll for confirmation

Registry write-back

After successful deployment, the record is written to the registry:

registry.DeploymentRecord{
    IdempotencyKey: key,
    ChainID:        chainKey,
    Contract:       contract,
    Address:        address,
    TxHash:         txHash,
    Confirmed:      txHash != "" || existing,
    ProjectID:      projectID,
}

Deployment flow

DeployRequest{idempotency_key, chain_id, contract, constructor_args, create2?}
    │
    ▼
Check registry for existing deployment by key+chain
    │
    ├── Found + confirmed on-chain → return Existing: true
    │
    └── Not found / not confirmed
        │
        ▼
    Resolve artifact from registry (projectID:contract)
        │
        ▼
    Resolve chain profile
        │
        ▼
    Pack constructor args + bytecode
        │
        ▼
    Build TxIntent (plain or CREATE2)
        │
        ▼
    Authorize via wallet policy gate
        │
        ▼
    Sign + broadcast
        │
        ▼
    Wait for receipt
        │
        ▼
    Record in registry
        │
        ▼
    Return DeployResponse

Error codes

CodeRetryMeaning
DEPLOY_FAILEDvariesDeployment planning or broadcast failed
ARTIFACT_NOT_FOUNDnoCompile first or wrong project_id
WALLET_NOT_CONFIGUREDnoNo signer configured
WALLET_POLICY_DENIEDnoSpend cap or allow-list rejected
CHAIN_NOT_FOUNDnoUnknown chain_id
CHAIN_RPC_FAILEDyesRPC dial or transport error

Modifying the deployer

What to changeWhere
Add deployment metadatapkg/types/deploy.goDeployRequest/DeployResponse
Change CREATE2 formulainternal/deployer/deployer.gocomputeCreate2
Add proxy deploymentNew method — UUPS/transparent proxy pattern
Change confirmation logicinternal/deployer/deployer.goconfirmOnChain
Add multi-chain deployinternal/deployer/deployer.go — loop over chain IDs