ar.io Logoar.io Documentation

Solana Migration for Operators

Overview

The ar.io network has migrated protocol execution from AO to Solana. If you're an existing gateway operator, this guide covers everything you need to change. Your gateway will continue serving Arweave data uninterrupted — the changes affect how your node interacts with the protocol layer (staking, observations, rewards).

Complete these steps before the cutover date to ensure uninterrupted reward eligibility. Gateways that fail 30 consecutive epochs will be pruned with 100% of minimum stake slashed.

What Changed

Before (AO)After (Solana)
Arweave wallet (RSA JWK)Solana keypair (Ed25519)
AO Compute Unit for state readsSolana RPC for state reads
AO_CU_URL, AO_MU_URL, etc.SOLANA_RPC_URL
Observations submitted via AO messagesObservations submitted as Solana transactions
Observer wallet pays in AR/Turbo creditsObserver wallet pays in SOL
Rewards distributed atomicallyRewards distributed via cranker pipeline
Observer address not uniqueObserver address must be unique per gateway
Minimum stake = 10,000 ARIOMinimum stake = 20,000 ARIO

What Didn't Change

  • Your gateway still serves Arweave data (caching, indexing, ArNS resolution)
  • Docker-based deployment workflow is the same
  • Epoch duration remains 24 hours
  • Staking, delegation, and reward mechanics are functionally equivalent

Step-by-Step Migration

Map Your Address

Before the cutover, register your Solana wallet address using the ar.io claim app. This maps your existing Arweave address to your new Solana address so your stake, delegation, and gateway registration carry over automatically.

Generate a Solana Keypair

Create a new Solana keypair for your gateway and observer:

# Install Solana CLI tools
sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"

# Generate gateway operator keypair
solana-keygen new --outfile operator-keypair.json

# Generate observer keypair (must be unique — not shared with any other gateway)
solana-keygen new --outfile observer-keypair.json

Your observer address must be unique across all gateways. If another gateway is already using the same observer address, your registration will fail. This is enforced onchain by the ObserverLookup PDA.

Fund Your Wallets with SOL

Your observer wallet needs SOL for submitting observation transactions. Typical costs are less than 0.01 SOL per transaction.

# Check your observer address
solana-keygen pubkey observer-keypair.json

# Send SOL to it from an exchange or existing wallet

We recommend keeping at least 0.5 SOL in the observer wallet to cover several months of observation submissions.

Update Environment Variables

Edit your .env file with the new Solana configuration:

# Operator + observer addresses (Solana pubkeys, base58)
AR_IO_WALLET=<your-solana-operator-pubkey>
OBSERVER_WALLET=<your-solana-observer-pubkey>

# Solana RPC endpoint (see Callout below — use a premium provider in production)
SOLANA_RPC_URL=https://your-rpc-provider.com

# Keypair file paths inside the container (mounted from ./wallets/)
SOLANA_KEYPAIR_PATH=/app/wallets/operator-keypair.json
OBSERVER_KEYPAIR_PATH=/app/wallets/observer-keypair.json

# The four ar.io Solana programs (canonical mainnet)
ARIO_CORE_PROGRAM_ID=73YoECm6NKXpVRoe5f1Q9BcP5DJGPFUjnFy6AxBE5Nvh
ARIO_GAR_PROGRAM_ID=89fNiiwgpFSPHKuqfNUkgYTYjtAJAhyqHjXmgXeppGpf
ARIO_ARNS_PROGRAM_ID=2yCUx5edFvUrkibYaUa2ZXWyx9kuJkS8CwyzsgHPWdZZ
ARIO_ANT_PROGRAM_ID=2MWexMHfMhGJwMHv9Qm9YAVCqjUFUJwDJAysW4oCUGk5

Remove the old AO variables (they are no longer used):

# DELETE THESE from your .env:
# AO_CU_URL=...
# NETWORK_AO_CU_URL=...
# ANT_AO_CU_URL=...
# AO_MU_URL=...
# AO_GATEWAY_URL=...
# AO_GRAPHQL_URL=...
# HTTPSIG_UPLOAD_ATTESTATION=...
# WALLETS_PATH=...

The default public Solana RPC (api.mainnet-beta.solana.com) is rate-limited. Under any real load your observer and cranker will start dropping transactions, and the resulting timeouts cause most observation cycles to miss their submission window. Use a dedicated RPC provider in production: Helius, Triton, or QuickNode.

Phantom-export keys

If you already have a Phantom-exported base58 secret string for the observer or operator, you can skip the JSON keypair file entirely: set OBSERVER_PRIVATE_KEY=<base58> (or SOLANA_PRIVATE_KEY=<base58>) instead of the *_KEYPAIR_PATH env. Setting both forms for the same role is rejected at startup.

Place Keypair Files

Copy your operator and observer Solana keypair JSON files into the wallets/ directory in your ar-io-node checkout. Docker Compose mounts this directory into /app/wallets/ inside the container, which is where the *_KEYPAIR_PATH env vars point.

cp operator-keypair.json wallets/operator-keypair.json
cp observer-keypair.json wallets/observer-keypair.json
chmod 600 wallets/*.json

Use container paths, not host paths

Env vars must use the in-container path (/app/wallets/...), not the host path (./wallets/...). Docker Compose bind-mounts ${WALLETS_PATH:-./wallets} to /app/wallets inside the container.

Wrong (host path)Right (container path)
SOLANA_KEYPAIR_PATH=./wallets/operator-keypair.jsonSOLANA_KEYPAIR_PATH=/app/wallets/operator-keypair.json

Skip this step entirely if you set OBSERVER_PRIVATE_KEY / SOLANA_PRIVATE_KEY env vars (base58 strings) instead.

Update ar-io-node

Pull the latest Solana-compatible release and recreate the containers:

cd ar-io-node
git pull
docker compose pull
docker compose up -d --force-recreate core envoy observer

Don't use `down -v`

Avoid docker compose down -v here — the -v flag deletes named volumes, which on most setups wipes the sqlite index and chunk data the gateway has spent days/weeks building. Use docker compose up -d --force-recreate <service> instead, which restarts containers in place without touching volumes.

Check your release version at https://<your-gateway>/ar-io/info.

Optional: Enable Epoch Cranking

Your observer can optionally help drive the epoch reward pipeline. This is completely permissionless and costs minimal SOL (~0.000155 SOL per epoch):

# Add to .env
ENABLE_EPOCH_CRANKING=true

See the epoch pipeline docs for details. Running the cranker creates redundancy so the network is never dependent on a single bot.

Verify

After restarting, verify your gateway is operating correctly:

  1. Release + program IDs (cross-check config is what you intended):

    curl -s https://<your-domain>/ar-io/info | jq '{release, wallet, programIds}'

    programIds.core/gar/arns/ant should match the network you're targeting. If they don't, the gateway is still pointed at the old program set, which is usually why a migrated gateway looks inert.

  2. Gateway registration is live on the new network:

    ar.io get-gateway -t solana \
      --rpc-url https://api.mainnet-beta.solana.com \
      --core-program-id 73YoECm6NKXpVRoe5f1Q9BcP5DJGPFUjnFy6AxBE5Nvh \
      --gar-program-id 89fNiiwgpFSPHKuqfNUkgYTYjtAJAhyqHjXmgXeppGpf \
      --arns-program-id 2yCUx5edFvUrkibYaUa2ZXWyx9kuJkS8CwyzsgHPWdZZ \
      --ant-program-id 2MWexMHfMhGJwMHv9Qm9YAVCqjUFUJwDJAysW4oCUGk5 \
      --address <your-operator-pubkey>

    Should return "status": "joined" with the FQDN/stake/settings you set at join time.

  3. ArNS resolution works end-to-end:

    curl -I -H "Host: ardrive.<your-domain>" https://<your-domain>/

    Expect HTTP 200 (or a manifest redirect). 404 here means the cache hasn't hydrated yet — give it a minute and re-check.

  4. Observer reports are flowing: https://<your-domain>/ar-io/observer/reports/current

  5. Gateway listed in the portal: gateways.ar.io

  6. No accumulating failed epochs in the portal's gateway view.

Wallet Roles and Configuration Patterns

The gateway uses up to four distinct wallet roles. Understanding these helps you pick the right configuration for your setup.

RoleWhat it signsEnv varsFallback
Operator (+ cranker)join_network, update_gateway_settings, permissionless cranker instructionsSOLANA_KEYPAIR_PATH or SOLANA_PRIVATE_KEY— (required)
Observersave_observations transactionsOBSERVER_KEYPAIR_PATH or OBSERVER_PRIVATE_KEYFalls back to operator key
UploadObserver report bundles sent to TurboSee upload precedence belowFalls back to observer → operator Solana key
HTTPSIG signerRFC 9421 response headersUses observer Solana key when setAuto-generated standalone Ed25519 key

Setting both the file-path and inline forms for the same role (e.g. SOLANA_KEYPAIR_PATH and SOLANA_PRIVATE_KEY) is rejected at startup as ambiguous. Pick one.

Supported Configurations

These are the five supported wallet setups. Pattern 1 is the recommended default — one key does everything. Pattern 2 is the most common migration path for operators who already have an Arweave JWK.

#OperatorObserverUploadRequired envs
1Solana= operator= operator (Solana)SOLANA_KEYPAIR_PATH
2Solana= operatorArweave JWKSOLANA_KEYPAIR_PATH + ARWEAVE_UPLOAD_KEY_FILE
3Solana ASolana BSolana CSOLANA_KEYPAIR_PATH + OBSERVER_KEYPAIR_PATH + SOLANA_UPLOAD_KEYPAIR_PATH
4Solana ASolana BArweave JWKSOLANA_KEYPAIR_PATH + OBSERVER_KEYPAIR_PATH + ARWEAVE_UPLOAD_KEY_FILE
5Solana ASolana BEthereumSOLANA_KEYPAIR_PATH + OBSERVER_KEYPAIR_PATH + ETHEREUM_UPLOAD_PRIVATE_KEY_FILE
# One key for operator + observer + uploads
SOLANA_KEYPAIR_PATH=/app/wallets/operator-keypair.json
SOLANA_RPC_URL=<your dedicated RPC endpoint>
AR_IO_WALLET=<your Solana pubkey>
OBSERVER_WALLET=<your Solana pubkey>
ENABLE_EPOCH_CRANKING=false   # flip to true when ready

Pattern 2 — Keep existing Arweave JWK for uploads

The most common path for operators migrating from a pre-Solana setup. Your existing Arweave JWK continues signing report bundles while the Solana keypair handles protocol interactions.

SOLANA_KEYPAIR_PATH=/app/wallets/operator-keypair.json
ARWEAVE_UPLOAD_KEY_FILE=/app/wallets/<your-arweave-jwk>.json
SOLANA_RPC_URL=<your dedicated RPC endpoint>
AR_IO_WALLET=<your Solana pubkey>
OBSERVER_WALLET=<your Solana pubkey>
ENABLE_EPOCH_CRANKING=false

Upload Signing Precedence

The gateway picks the first matching upload signer from this list:

1. ARWEAVE_UPLOAD_KEY_FILE     (file)     → ArweaveSigner
2. ARWEAVE_UPLOAD_JWK          (inline)   → ArweaveSigner
3. ETHEREUM_UPLOAD_PRIVATE_KEY_FILE (file) → EthereumSigner
4. ETHEREUM_UPLOAD_PRIVATE_KEY (inline)    → EthereumSigner
5. SOLANA_UPLOAD_KEYPAIR_PATH  (explicit)  → SolanaSigner
6. Fallback: OBSERVER_KEYPAIR_PATH ?? SOLANA_KEYPAIR_PATH → SolanaSigner

Setting upload envs from more than one chain at once (e.g. ARWEAVE_UPLOAD_KEY_FILE plus ETHEREUM_UPLOAD_PRIVATE_KEY) raises a startup error listing every conflicting env. Pick exactly one upload chain.

Key Formats

Solana keypairs come in two common formats. Both encode the same 64-byte secret (seed(32) || pubkey(32)):

FormatExampleSource
JSON array (Solana CLI standard)[12,34,56,...] — 64 uint8 integerssolana-keygen new --outfile keypair.json
base58 secret87–88 character base58 stringPhantom "export private key", browser wallets

Use the JSON file with *_KEYPAIR_PATH env vars, or the base58 string with *_PRIVATE_KEY env vars — never both for the same role.

Troubleshooting

"Observer is restart-looping with 'Epoch 0 PDA not found'"

If the observer logs repeatedly:

error: Continuous observer start() rejected — attempting auto-restart
  ...error: "Epoch 0 PDA not found at <pda> — has prescribe_epoch run yet?"

…the network you're targeting hasn't had its first epoch initialized. The observer needs entropy from epoch[N].prescribed_observers to bootstrap, which doesn't exist until someone calls create_epoch (typically a cranker).

Verify the epoch state directly:

ar.io get-current-epoch -t solana \
  --rpc-url https://api.mainnet-beta.solana.com \
  --core-program-id 73YoECm6NKXpVRoe5f1Q9BcP5DJGPFUjnFy6AxBE5Nvh \
  --gar-program-id 89fNiiwgpFSPHKuqfNUkgYTYjtAJAhyqHjXmgXeppGpf \
  --arns-program-id 2yCUx5edFvUrkibYaUa2ZXWyx9kuJkS8CwyzsgHPWdZZ \
  --ant-program-id 2MWexMHfMhGJwMHv9Qm9YAVCqjUFUJwDJAysW4oCUGk5

For staging-devnet or local devnet, replace the RPC URL and program IDs with that environment's values.

If this returns "Epoch 0 not found", the network is configured but inactive. This is a network-operations state, not a gateway misconfiguration. Wait for an active cranker (yours or another operator's) to bootstrap epoch 0.

"Cranker started but never submits any instructions"

The cranker silently bails out if EpochSettings.enabled is false. This is intentional: operators shouldn't pay SOL fees attempting cranker instructions against a paused network. Confirm with ar.io get-epoch-settings and check whether epochs are enabled on your target network. If enabled is false, no cranker activity is expected.

"ArNS names return 404 but the on-chain record exists"

The gateway hydrates an ArNS names cache at boot, paginating through the on-chain registry. If your SDK pin is significantly older than the deployed ario-arns program, the paginated response shape may not match and the cache hydrates incompletely. Symptoms:

  • Cache hydration logs Successfully hydrated ArNS names cache quickly (e.g. ~4s for thousands of records)
  • Resolver logs report Base name not found in ArNS names cache for names that demonstrably exist via the SDK CLI's get-arns-record --name <name>

Fix: bump @ar.io/sdk in your gateway's image (package.json) to the latest ^4.0.0-solana.* and rebuild.

Wallet Configuration Startup Errors

The gateway validates wallet configuration at startup. Errors are loud and name the offending env:

Error patternCauseFix
multiple chain groups configured for upload roleUpload envs from more than one chain are set (e.g. ARWEAVE_UPLOAD_* and ETHEREUM_UPLOAD_*)Pick one upload chain and remove the others
ambiguous: both ... set for roleFile-path and inline forms for the same role are both set (e.g. SOLANA_KEYPAIR_PATH + SOLANA_PRIVATE_KEY)Use one form per role
material at SOLANA_KEYPAIR_PATH does not look like a Solana keypairAn Arweave JWK or other JSON was placed at the Solana keypair pathCheck you copied the right file — Solana keypairs are a JSON array of 64 integers, not a JWK object
material at ARWEAVE_UPLOAD_KEY_FILE does not look like an Arweave JWKA Solana keypair (JSON array) was placed at the Arweave upload slotSwap the file for your Arweave JWK
SOLANA_KEYPAIR_PATH not set in Solana modeThe operator key is missing entirelySet SOLANA_KEYPAIR_PATH or SOLANA_PRIVATE_KEY
OBSERVER_KEYPAIR_PATH does not match on-chain Gateway.observer_addressThe observer key doesn't match what was registered at join_networkUpdate the key to match, or call update_observer_address on-chain

New Risks to Be Aware Of

Gateway Pruning

Gateways that fail 30 consecutive epochs are automatically pruned from the network. When pruned:

  • 100% of minimum stake (20,000 ARIO) is slashed to the protocol balance
  • Excess operator stake enters the standard 30-day withdrawal queue
  • Delegated stakes enter the standard 30-day withdrawal queue (delegators are not slashed)

See Gateway Pruning for full details.

SOL Balance Monitoring

Keep your observer wallet funded with SOL. If it runs out, your observer cannot submit observations, which leads to failed epochs and eventually pruning. Set up monitoring/alerts for your observer wallet balance.

FAQ

Do I need to re-register my gateway? No. If you mapped your address before cutover, your gateway registration, stake, and delegations are migrated automatically.

What happens to my delegators? Delegations are migrated as-is. Delegators who mapped their addresses will see their stakes in their Solana wallet. Unmapped delegations are held in escrow for claiming.

Can I still use my Arweave wallet for data uploads? Yes. Arweave wallets are still used for uploading data to Arweave via Turbo. The Solana wallet is only for protocol interactions (staking, observations, ArNS).

How do I check my observer's SOL balance?

solana balance <observer-pubkey> --url mainnet-beta

How is this guide?