Skip to content

3.1 The Relayer/Bridge Layer Design

The Relayer (or Adapter) layer functions as the crucial secure boundary between the private Canton policy plane and the public execution environment. This component is an external application designed to transport verifiable action approvals (the signed Intents) securely to the public chains.

Architecture Overview

┌───────────────────────────────────────────┐
│        Canton Ledger API                  │
│  • ExecutionIntent contracts              │
│  • Cryptographic signatures               │
└─────────────────┬─────────────────────────┘

                  │ Monitor new intents

┌─────────────────▼─────────────────────────┐
│           Relayer Service                 │
│                                           │
│  ┌─────────────────────────────────────┐ │
│  │  1. Intent Consumption              │ │
│  │  2. Signature Verification          │ │
│  │  3. Transaction Preparation         │ │
│  │  4. Custody Integration             │ │
│  │  5. Submission                      │ │
│  │  6. State Reporting                 │ │
│  └─────────────────────────────────────┘ │
└─────────────────┬─────────────────────────┘

        ┌─────────┴──────────┐
        │                    │
┌───────▼─────┐      ┌───────▼─────┐
│  Ethereum   │      │   Solana    │
│  Arbitrum   │      │   Polygon   │
│  Optimism   │      │   Avalanche │
└─────────────┘      └─────────────┘

Core Responsibilities

1. Intent Consumption

The Relayer continuously monitors the Canton Ledger API for new, signed ExecutionIntent contracts issued by the Policy Issuer Party.

typescript
// Pseudocode
async function monitorCantonIntents() {
  const stream = cantonLedger.streamTransactions({
    templateId: 'Vault.Policy:ExecutionIntent',
    parties: [policyIssuerParty]
  });

  for await (const event of stream) {
    if (event.type === 'created') {
      await processIntent(event.payload);
    }
  }
}

Features:

  • Real-time event streaming
  • Reliable delivery guarantees
  • Idempotency handling
  • Error recovery

2. Verification

The Relayer verifies the cryptographic signature (cantonSig) on the intentPayload.

typescript
async function verifyIntent(intent: ExecutionIntent): Promise<boolean> {
  // 1. Recover public key from signature
  const publicKey = recoverPublicKey(
    intent.cantonSignature,
    hash(intent.payload)
  );

  // 2. Verify against Canton Topology
  const authorizedKeys = await canton.getAuthorizedKeys(
    intent.issuer
  );

  // 3. Confirm authorization
  return authorizedKeys.includes(publicKey);
}

Verification includes:

  • Confirming the signing party (Issuer) is recognized
  • Validating authorization within Canton's Topology Management system
  • Checking signature cryptographic validity
  • Verifying intent hasn't expired

3. Transaction Preparation

The Relayer translates the generic, serialized payload (e.g., JSON) into a blockchain-specific transaction format.

typescript
function prepareTransaction(
  intent: ExecutionIntent,
  targetChain: Chain
): Transaction {
  // Encode intent into chain-specific format
  const calldata = encodeExecuteIntent(
    intent.payload,
    intent.cantonSignature,
    intent.proof
  );

  return {
    to: vaultContracts[targetChain],
    data: calldata,
    value: 0,
    chainId: targetChain.id,
    gasLimit: estimateGas(calldata)
  };
}

Chain-Specific Encoding:

  • EVM: ABI-encoded calldata
  • Solana: Transaction instruction format
  • Cosmos: Protobuf messages
  • Other: Custom encodings

4. Submission

The Relayer initiates the signature process (Pattern 1, 2, or 3) and broadcasts the finalized, signed transaction to the target public chain.

typescript
async function submitTransaction(
  tx: Transaction,
  pattern: BridgePattern
): Promise<TransactionReceipt> {
  switch (pattern) {
    case 'SignedIntentRelay':
      // Pattern 1: Relayer co-signs
      return await relayerWallet.sendTransaction(tx);

    case 'ProofPublication':
      // Pattern 2: Publish proof first
      await publishProof(tx.proof);
      return await relayerWallet.sendTransaction(tx);

    case 'MPCSigned':
      // Pattern 3: MPC signs directly
      return await mpcService.signAndBroadcast(tx);
  }
}

5. State Reporting

After execution, the Relayer monitors the public chain for transaction receipts and execution events.

typescript
async function reportExecution(
  intentId: string,
  receipt: TransactionReceipt
): Promise<void> {
  const executionReport = {
    intentId,
    txHash: receipt.transactionHash,
    success: receipt.status === 1,
    gasUsed: receipt.gasUsed,
    blockNumber: receipt.blockNumber,
    logs: parseExecutionLogs(receipt.logs)
  };

  // Report back to Canton
  await cantonLedger.exercise({
    templateId: 'Vault.Policy:ExecutionIntent',
    contractId: intentId,
    choice: 'RecordExecution',
    argument: executionReport
  });
}

Reported Data:

  • Transaction hash
  • Success/failure status
  • Gas usage
  • Slippage metrics
  • Error messages (if any)

High Availability Design

Relayer Cluster

Deploy multiple Relayer instances for fault tolerance:

┌─────────────────────────────────────────┐
│         Load Balancer                   │
└────┬──────────┬──────────┬─────────────┘
     │          │          │
┌────▼────┐ ┌──▼─────┐ ┌──▼─────┐
│Relayer 1│ │Relayer 2│ │Relayer 3│
│ (Active)│ │(Standby)│ │(Standby)│
└─────────┘ └─────────┘ └─────────┘

Features:

  • Leader election (e.g., using Raft or etcd)
  • Automatic failover
  • Duplicate submission prevention
  • State synchronization

Monitoring & Alerts

yaml
alerts:
  - name: IntentProcessingDelay
    condition: queue_depth > 10
    action: page_on_call

  - name: VerificationFailure
    condition: invalid_signature_count > 0
    action: alert_security_team

  - name: ChainExecutionFailure
    condition: failed_tx_rate > 0.05
    action: alert_ops_team

  - name: CantonDisconnection
    condition: canton_connection_lost
    action: immediate_escalation

Security Considerations

Access Control

  • Canton API Access: Secure credentials, rotate regularly
  • MPC Integration: Hardware security module (HSM) protected
  • Chain RPC: Rate limiting and DDoS protection
  • Operational Logs: Encrypted at rest and in transit

Replay Protection

The Relayer must enforce:

  • Intent expiration checking
  • Nonce ordering verification
  • Duplicate submission prevention
  • Transaction idempotency

Error Handling

typescript
async function processIntentWithRetry(
  intent: ExecutionIntent
): Promise<void> {
  const maxRetries = 3;
  let attempt = 0;

  while (attempt < maxRetries) {
    try {
      // Verify intent is still valid
      if (Date.now() > intent.deadline) {
        throw new Error('Intent expired');
      }

      // Process intent
      const tx = prepareTransaction(intent);
      const receipt = await submitTransaction(tx);
      await reportExecution(intent.id, receipt);
      return; // Success

    } catch (error) {
      attempt++;
      if (attempt >= maxRetries) {
        await reportFailure(intent.id, error);
        throw error;
      }
      await sleep(exponentialBackoff(attempt));
    }
  }
}

Performance Optimization

Batching

For high-throughput scenarios, batch multiple intents:

typescript
async function batchIntents(
  intents: ExecutionIntent[]
): Promise<Transaction> {
  // Combine multiple intents into single transaction
  const batchCalldata = encodeMulticall(
    intents.map(i => ({
      target: vaultAddress,
      callData: encodeExecuteIntent(i)
    }))
  );

  return {
    to: multicallContract,
    data: batchCalldata
  };
}

Gas Optimization

  • Dynamic gas price estimation
  • EIP-1559 support with priority fees
  • Transaction replacement (RBF) for stuck transactions
  • Gas limit optimization based on action complexity

Parallel Execution

Process intents for different chains concurrently:

typescript
async function processIntents(
  intents: ExecutionIntent[]
): Promise<void> {
  // Group by target chain
  const byChain = groupBy(intents, i => i.targetChain);

  // Process each chain in parallel
  await Promise.all(
    Object.entries(byChain).map(([chain, chainIntents]) =>
      processChainIntents(chain, chainIntents)
    )
  );
}

Observability

Metrics

Track key performance indicators:

relayer_intents_processed_total
relayer_verification_failures_total
relayer_submission_latency_seconds
relayer_gas_used_total
relayer_chain_execution_success_rate

Tracing

Implement distributed tracing for end-to-end visibility:

Canton Intent Created
  → Relayer Received (span 1)
    → Verification (span 2)
      → Transaction Preparation (span 3)
        → MPC Signing (span 4)
          → Chain Submission (span 5)
            → Confirmation (span 6)
              → Canton Report (span 7)

Logging

Structured logging for audit and debugging:

json
{
  "timestamp": "2024-10-14T12:00:00Z",
  "level": "INFO",
  "component": "relayer",
  "intentId": "intent-123",
  "event": "intent_processed",
  "chain": "ethereum",
  "txHash": "0xabc...",
  "gasUsed": 150000,
  "duration_ms": 2500
}

Canton DeFi - Multichain DeFi Technical Reference