skip to content
Rob The Writer

Axelar-Solana: A Cross-Chain Protocol on a 1232-Byte Budget

/ 19 min read

Cross-chain messaging sounds like a routing problem until you try to land the proof on Solana.

A destination chain does not trust a relayer’s word that “validators approved this payload.” It needs cryptographic evidence: which validators signed, over what exact bytes, with enough combined weight to meet the hub chain’s threshold. On Ethereum-shaped chains, that evidence routinely ships in a single large transaction. On Solana, the wire format caps every transaction at 1,232 bytes of serialized data, and the runtime caps how much hashing and verification a program may perform in one execution.

The project I joined was a port of Axelar’s general message passing Gateway to Solana: same security story as the EVM Gateway, same validator quorum, same integration contract — but a chain whose constraints force a different transaction shape. The arithmetic is unforgiving. Twenty-seven ECDSA signatures at 65 bytes each are 1,755 bytes of signatures alone, before message headers, account metas, or application payload. The signatures do not fit. Nothing else gets a chance to try.

This post is Part 2 of a five-year blockchain retrospective. Part 1 covered the builder era on EVM chains. Here the constraint is physical: what happens when a hub-and-spoke interoperability protocol meets a packet-sized transaction model.

A cross-chain message, step by step

Before the byte math, it helps to name the objects moving through the system.

Imagine a contract on Ethereum that wants to call a program on Solana. The application does not talk to Solana directly. It calls its local Gateway, which records destination chain, destination address, and payload (or payload hash). Axelar validators observe that event on the source chain, agree on Axelar that the event is real, and sign a batch digest that commits to the messages they are approving. An off-chain relayer then submits transactions on Solana so the Solana Gateway can verify those signatures and mark the message approved. Only then may the Solana application run its execute handler with the full payload bytes.

Simplified GMP lifecycle: application on chain A, source Gateway, Axelar validators, relayer and destination Gateway on chain B, destination application

The security invariant is the same on every chain: the destination only trusts validator signatures over a digest, not the relayer’s narration of what happened. The relayer is a courier. It can stall or waste fees; it cannot mint fake approvals without breaking cryptography.

Key concepts
Transaction

The atomic unit of state change on a blockchain: a signed instruction to update state. Validators gossip it, schedule it, execute it, and either commit the result or revert the whole thing. Every chain imposes limits; on Solana the serialized transaction size is a hard 1,232-byte ceiling, not a gas-priced soft limit.

Solana

A high-throughput layer-1 with explicit account lists per transaction, parallel scheduling where accounts do not alias, and programs compiled to SVM bytecode. Storage for program state lives in accounts, often at PDAs. Fees combine a base charge per signature with metered compute units for on-chain work.

EVM (Ethereum Virtual Machine)

The deterministic runtime behind Ethereum and most “EVM-compatible” chains: stack machine, 256-bit words, gas-metered execution, and a large tooling ecosystem (ABI encoding, JSON-RPC, explorers). Cross-chain gateways on EVM chains typically assume calldata and gas can scale with proof size.

What Axelar is responsible for

Axelar is a proof-of-stake hub chain whose job is authenticated messaging between external chains. An application on chain A calls a Gateway contract; Axelar validators observe that intent, reach agreement on Axelar itself, and produce a multisig attestation; a relayer delivers that attestation to chain B’s Gateway; the destination Gateway verifies signatures and lets the target contract execute the payload.

That division of labor matters when porting:

RoleResponsibility
Source GatewayAccept callContract, emit the cross-chain intent (including payload hash).
Axelar validatorsAgree that the source event happened and sign a batch digest.
Destination GatewayVerify the proof, mark messages approved, prevent replay on execution.
RelayerSubmit destination-chain transactions; cannot forge approvals without validator signatures.
Destination appRun business logic once validateMessage succeeds.

General message passing is Axelar’s name for arbitrary contract calls across chains — not only token bridges. Tokens are one payload shape; the protocol cares about authenticated bytes and destination addresses.

On EVM, the destination contract usually receives the payload in the same transaction that records approval. On Solana, approval and payload delivery are different phases: approvals are proved in many small transactions; the raw payload is uploaded afterward into a dedicated account (see After approval).

Axelar-Solana cross-chain architecture: EVM chains, Axelar validators, off-chain Relayer, and the Solana Gateway with chunked uploads, Merkle-proved messages, and per-signature verification transactions

Key concepts
Axelar

Cosmos-family hub chain specialized for cross-chain routing. Validators run external-chain watchers, sign approved message batches, and maintain gateway contracts on connected networks. Topology is hub-and-spoke: chains do not verify each other directly; they verify Axelar’s threshold signatures at their local Gateway.

GMP (General Message Passing)

Cross-chain contract calls with validator-attested payloads. Source contract specifies destination chain, destination address, and bytes; destination contract receives the same bytes only if the Gateway accepts the multisig proof.

EVM Gateway

Axelar’s reference destination contract on EVM chains. It stores verifier sets, verifies secp256k1 signatures over a payload digest, records approved messages, and exposes validateMessage / execute to applications. The critical convenience for EVM integrators is batch verification in one transaction.

Quorum

Minimum signing weight required to approve a batch. Axelar’s Amplifier integration guide assumes a ~⅔ threshold: with 40 signers in the set, implementations must verify at least 27 signatures. That number is what collides with Solana’s packet limit.

I joined several months into the effort. The schedule was late, the integration surface was large, and the uncomfortable fact was already visible: the EVM Gateway’s “one transaction, whole proof” design was not a small porting detail. It was the wrong shape for Solana.

A note on the source

The code for this work is open source. The old Eiger-era links are stale now, because that project location became archival after the handoff: the Solana Gateway lives in Equilibrium’s axelar-amplifier-solana repository, while the Axelar-side Amplifier code I reference below is now easiest to find through Common Prefix’s axelar-amplifier fork. So if you click through and the org names differ from the company names in the story, that is why. Open source is great, but GitHub archaeology is still archaeology.

Solana basics for EVM readers

If you have only used Solidity chains, three Solana mechanics explain why the Gateway could not be a straight port.

Accounts, not implicit storage. Every instruction declares the full list of accounts it may read or write. Think of them as explicitly passed storage slots. If the relayer omits an account the program needs, the transaction fails before your logic runs.

Programs cannot sign; PDAs can. There is no msg.sender on Solana. A program id is never a transaction signer. Programs act through program derived addresses (PDAs) — deterministic addresses they own — and sign cross-program invocations with invoke_signed. The Gateway uses PDAs for config, per-message state, signature-verification sessions, and payload buffers.

Cross-program invocation (CPI). When the Gateway calls an application’s execute, or when an application calls validate_message on the Gateway, that is a CPI with its own account list. Integrators implementing axelar-executable must encode not only application bytes but also the account metas the relayer needs on Solana — unlike EVM, where calldata alone is enough.

Sending messages from Solana (outbound)

Outbound GMP is symmetric in trust but different in shape. A Solana program CPIs into the Gateway’s call_contract (or call_contract_offchain_data when the payload is too large to fit in instruction data). Validators’ ampd process watches Solana logs for the ContractCall event, confirms it against RPC, and drives the same Amplifier signing flow. Large outbound payloads may be passed to the relayer off-chain while only the hash lands on-chain — mirroring the inbound chunking problem in reverse. See the Gateway sending flow in the integration docs.

Two ceilings: bytes and compute

The EVM Gateway’s approveMessages path is intentionally monolithic. It receives the full Proof — weighted signer set plus every signature — hashes the message batch into a payload digest, recovers signers with secp256k1 ECDSA, accumulates weight against the threshold, and writes approvals. One call, one state transition, one clear atomic story.

EVM chains tolerate that shape because transaction size is primarily an economic limit. Larger calldata costs more gas, but the protocol does not reject a 10 KB transaction on size alone.

Solana is different. Two independent limits apply to every transaction:

LimitWhat it boundsTypical failure mode
Serialized size (1,232 bytes)Entire transaction: signatures array + message (header, account keys, blockhash, instructions)Transaction rejected before execution
Compute unitsWork performed inside program instructions (hashing, account updates, custom logic)Transaction executes then aborts with compute budget exceeded

After reserving space for the message header, account addresses, recent blockhash, and the transaction’s own Ed25519 signer signatures, only a few hundred bytes remain for instruction data in a typical Gateway transaction. That is the workable “payload window,” not the full 1,232.

Typical allocation of a 1,232-byte Solana transaction: most space goes to headers and account keys; Axelar authentication data alone exceeds the entire cap

EVM monolith vs Solana pipeline

The architectural fork is not “Rust instead of Solidity.” It is how many transactions carry the proof.

Side by side: EVM Gateway approves a batch in one transaction; Solana Gateway uses init session, per-signature verification, per-message approval, chunked payload upload, then execute

StageEVM Gateway (reference)Solana Gateway (shipped shape)
Proof deliverySingle approveMessages calldata blobExecuteData streamed as many txs
Signature checksAll in one contract executionOne verify_signature per validator signature
Message approvalAll messages in same callOne approve_message per GMP message
Application payloadPassed as call argumentsChunked into Message Payload PDA (~800 B per tx)
Upper bound (integration)> 1 MB on Ethereum10 KB payload PDA limit on Solana

Size budget vs integration minimums

Axelar’s Amplifier integration limits state what a serious Gateway must support. The table below is a napkin layout of authentication data only — not the application payload, not Borsh padding, not account metas:

PieceCount (minimum)Per-itemSubtotal
Signatures2765 bytes (secp256k1)1,755 bytes
Signer addresses4020 bytes800 bytes
One message header1~150 bytes~150 bytes
Subtotal~2,700 bytes

That is roughly 2.2× the entire transaction budget before the cross-chain payload. Signatures alone exceed the cap by ~40%.

The same integration document recommends 16 KB minimum cross-chain message size (64 KB recommended). Payload size is a separate axis from proof size, but both must eventually land on-chain. A port that only solves signatures still fails if it cannot commit multi-kilobyte application data.

Compute budget vs batch verification

Even a design that stuffed proof bytes into a PDA would still need to verify them. Hashing the verifier set, hashing messages into the digest, and recovering dozens of ECDSA signatures inside one program invocation overshot Solana’s per-transaction compute budget in our measurements.

Key concepts
Compute-unit budget

Per-transaction CPU allowance inside the SVM. Hashing, account serialization, and custom verification logic consume units; exceeding the budget aborts the transaction. Independent from the 1,232-byte wire limit — a transaction can be small and still too expensive to run.

Signature verification

On-chain proof that a secp256k1 signature recovers to an authorized verifier address over the exact digest validators signed. Cheap as a concept; expensive when multiplied by quorum size inside one execution.

secp256k1

Elliptic curve used by Bitcoin and Ethereum for ECDSA. Axelar validator signatures are recoverable secp256k1, 65 bytes each (r, s, recovery id). The byte cost is fixed; the count is set by security policy (~27 minimum at launch assumptions).

The EVM reference was not “a little over budget.” It violated both ceilings, by different factors, for different reasons.

First approach: store the proof, verify in one shot

The codebase I inherited tried the direct translation: put the full ExecuteData bundle in a PDA so the 1,232-byte limit applied only to the pointer transaction, then run digest reconstruction and signature recovery in one Gateway invocation against the stored blob.

That removed the size problem and exposed the compute problem. Measured limits from that architecture looked like:

ParameterMeasured (PDA path)Integration expectation
Signers verified per tx527 minimum
Messages per batch3many (batched approvals)
Payload635 bytes16 KB minimum (64 KB recommended)
Account references20higher for real batches

Wrong on every security-relevant axis — not a tuning issue, a shape issue.

Key concepts
PDA (Program Derived Address)

Address derived deterministically from a program id and seeds; the program signs for it without a private key. Used for persistent Gateway state, chunk buffers, and progress counters across the Merkle upload sequence.

ExecuteData

Axelar’s packaged proof: verifier set, signatures, payload digest material, and the data needed to reconstruct what validators signed. On EVM this arrives in one approveMessages call. On Solana it became a Merkleized stream.

The rkyv detour

In parallel, we invested in rkyv for zero-copy deserialization of dynamically sized structs — fewer allocations, less decode work, lower compute per byte touched. For a while it looked like the right lever: if the bottleneck were deserialization cost inside one fat transaction, zero-copy might recover enough headroom.

It did not change the binding constraints. The proof still could not fit in 1,232 bytes. Even with cheaper parsing, we could not verify a 27-signature quorum in one execution, and the native precompile path could not verify more than eight secp256k1 signatures per transaction anyway. When the client’s final quorum and payload requirements landed, the optimization axis was clearly wrong. We removed rkyv and redesigned around Merkle streaming.

War story: optimizing the wrong variable

The rkyv work was technically sound and materially faster for the shapes it handled. The failure mode was architectural misclassification: we treated Solana’s limits as a performance problem when they were a protocol packaging problem. Serialization efficiency does not shrink a 27-signature proof below a packet cap, and it does not multiply the precompile signature budget. The lesson I took into later chain work: identify which limit is structural before investing in micro-optimizations.

Key concepts
rkyv

Rust serialization framework for in-place access to archived structs. Strong fit when wire layout is stable and hot paths deserialize repeatedly. Wrong fit when the wire shape itself must change from one monolithic batch to many proved fragments.

Zero-copy deserialization

Reading typed fields directly from a byte buffer without building a separate owned struct. Saves allocation and copy cost; requires careful layout, alignment, and version discipline. Cannot overcome hard packet or quorum-per-transaction limits.

Merkle streaming: same security, different transaction count

The design we shipped keeps Axelar’s security story — validators sign one payload digest; the Gateway only approves what that digest commits to — but changes how the destination chain learns the proof.

streaming signature0/8 verified
Relayer sends one leaf plus its Merkle proof. The Gateway checks the same root every time.
ExecuteData Merkle tree
root
h1
h2
p1
p2
p3
p4
sig_01
sig_02
sig_03
sig_04
msg_A
msg_B
chunk_0
chunk_1
Solana Gateway
committed root
0x8f...root
verified items
0/8
sig_01
leaf + proof
sig_01
proof: h02 + h03-04 + h05-08
The root never changes. Only the transaction count does.

What validators sign (ExecuteData)

Off-chain, the Multisig Prover collects signatures and returns ExecuteData: the verifier set that signed, every signature with Merkle proofs, and the payload digest validators attested to. The digest is a keccak commitment over the message batch, domain separator, and signing verifier-set metadata — the cross-chain equivalent of “what exactly did the quorum agree to?”

The encoding crate Merkelizes that bundle so each piece is provable without shipping the whole set in one transaction:

Merkle treeCommits toUsed when
Verifier set treeAuthorized signers and weightsProving a signature belongs to the set that signed the digest
Message batch treeGMP messages in the batchProving a message was part of what validators signed
Payload digestThe signed batch hashTying every on-chain step to validator signatures

On EVM, the contract reconstructs those hashes from full calldata. On Solana, the relayer brings one leaf at a time plus a short proof.

Relayer pipeline on Solana

Merkle trees commit to a set with a single root. Any leaf is provable with O(log N) sibling hashes. The off-chain Relayer submits a sequence of small Solana transactions (documented in the project’s Gateway guide):

StepOn-chain instruction (conceptual)Transactions
Open verification sessioninitialize_payload_verification_session1
Check each validator signatureverify_signature + Merkle proof1 per signature (≥ 27)
Record each approved messageapprove_message + Merkle proof1 per message
Deliver application bytesinitialize / write / commit payload chunks1 + ⌈payload / ~800 B⌉
Run application logicDestination program + validate_message CPI1 (+ close payload account)

Relayer pipeline: verification session, per-signature and per-message transactions, chunked payload upload, then execute with validate_message CPI

Key PDAs involved (each is a small on-chain state machine, not a single storage slot):

PDARole
Gateway configEpoch, verifier-set hashes, rotation delay
Signature verification sessionAccumulates verified weight until quorum
Incoming messagePer-message approved / executed status (command_id seed)
Message payloadHolds chunked raw bytes until execute (up to 10 KB)

A progress PDA accumulates verified weight across signature transactions until quorum matches the EVM contract’s threshold check.

After approval: payloads and execution

Message approval only proves that validators authorized a payload hash. The application still needs the raw bytes.

Because instruction data stays in the hundreds of bytes, the relayer chunks the payload into a Message Payload PDA (~800 bytes per write), commits the hash, and only then calls the destination program. The program uses a signing PDA to CPI validate_message on the Gateway (marking the message executed, like validateMessage on EVM) and then runs application logic. Closing the payload PDA refunds most rent to the relayer.

This split — approve hash first, upload body second, execute last — is the main behavioral difference a casual reader should remember when comparing to “one big execute on Ethereum.”

How the data was Merkelised: signatures, messages, and chunked payload slices all hash up to a single root the Gateway commits to, so each piece can be streamed in its own small transaction and proved against the root on arrival

Same invariants:

  • Validators sign exactly one digest for the batch.
  • The Gateway never approves a message not committed by that digest.
  • Replay protection still keys off source chain + message id.
  • A malicious relayer cannot forge signatures; it can only waste fees submitting invalid fragments.

Different operational profile: more transactions, more relayer orchestration, more on-chain state for upload progress — but spec-compliant on Solana’s actual limits.

Key concepts
Merkle trees

Hash tree where leaves are data items and internal nodes hash their children. The root commits to the entire set; a proof is the sibling path from leaf to root. For N leaves, proofs are ~log₂(N) hashes.

Merkle tree: leaves hash pairwise up to a single root; any leaf provable with just the sibling hashes along its path

Payload digest

keccak256 commitment over the batch validators signed: messages, signer set metadata, domain separator, and ordering. Individual messages and signatures are proved against this digest later without re-signing.

Merkle-proved

Carrying a leaf plus sibling hashes so the program recomputes the root and compares it to on-chain state. Enables streaming: the chain never needs the full proof in one packet.

Chunked uploads

Splitting payloads larger than one transaction’s data window into sequential writes to a buffer account, then checking the buffer hash against the digest before approval completes. This is how multi-kilobyte application data survives a 1,232-byte wire limit.

Multisig Prover

Axelar-side service that collects validator signatures on a batch and packages ExecuteData for the destination Gateway. It does not decide message validity; it assembles the evidence chain B must verify.

Off-chain Relayer

Service that submits the Solana transaction sequence, pays fees, handles retries, and orders chunk uploads. Correctness remains on-chain; liveness depends on someone running the relayer well.

Borsh

Binary Object Representation Serializer for Hashing — Solana’s common deterministic encoding. We used it for instruction payloads and account data layouts across Gateway, client, and relayer.

Keccak

Hash family used in EVM ecosystems (Keccak-256). Axelar’s digest rules are keccak-shaped; the Gateway uses Solana’s keccak syscall so digests match other chains without implementing keccak in BPF.

Syscall

Runtime-native operations (hashing, logging, cryptography) callable from programs at lower cost than emulating the same work in bytecode. Keccak via syscall was load-bearing for digest checks inside tight compute budgets.

By the time I handed off, the implementation supported large signer sets (practically up to 256 per quorum configuration), ~10 KB payloads via chunking, and batched message approvals — a different order of magnitude from the five-signer PDA prototype. The client’s downstream requirement was TypeScript bindings for integrators; we had skipped Anchor early for optimization headroom and paid for that later when auto-generated clients became mandatory. Colleagues refactored the public API onto Anchor after my tenure, completed audits, and shipped to Axelar’s public testnet in April 2026.

Key concepts
Anchor

Solana framework with account macros, IDL generation, and TypeScript clients. Default choice for most programs. We initially avoided it for control; the binding cost was integrator tooling, not consensus safety.

TypeScript bindings

Generated clients mirroring program instructions and account layouts. Wallets and application teams depend on them; hand-rolling byte layouts does not scale.

Audit

Third-party security review before production deployment. Necessary signal, not a substitute for protocol-level reasoning about split-transaction state machines.

Testnet

Non-production network for integration testing — same software shapes, no real economic stake. Where relayer sequencing bugs surface before mainnet fees.

Mainnet

Production deployment with permanent state and real value. Split-transaction Gateways fail more expensively here: partial progress, stuck chunks, and relayer race bugs become user-visible incidents.

The theme: limits are part of the protocol

Axelar’s EVM Gateway is elegant because Ethereum’s transaction model amortizes proof size into gas. Solana’s model trades that flexibility for predictable packets and metered execution. Porting between them is not a line-by-line translation of Solidity into Rust; it is a negotiation between what the hub signs and what the destination chain can ingest per packet and per invocation.

The hard part was not implementing Merkle proofs or PDAs. The hard part was accepting that the reference architecture encoded an EVM assumption — monolithic verification — and that Solana’s limits are not bugs to optimize around. They are constraints that change the protocol’s on-chain shape while preserving its off-chain trust model.

The next project in this series had a different kind of limit: not bytes per transaction, but consensus and execution boundaries on a chain that wanted storage reads inside the EVM without pretending they were free. That is Part 3: Irys.