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 reads | Solana RPC for state reads |
AO_CU_URL, AO_MU_URL, etc. | SOLANA_RPC_URL |
| Observations submitted via AO messages | Observations submitted as Solana transactions |
| Observer wallet pays in AR/Turbo credits | Observer wallet pays in SOL |
| Rewards distributed atomically | Rewards distributed via cranker pipeline |
| Observer address not unique | Observer address must be unique per gateway |
| Minimum stake = 10,000 ARIO | Minimum 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.jsonYour 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 walletWe 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=2MWexMHfMhGJwMHv9Qm9YAVCqjUFUJwDJAysW4oCUGk5Remove 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/*.jsonUse 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.json | SOLANA_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 observerDon'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=trueSee 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:
-
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/antshould 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. -
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. -
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.
-
Observer reports are flowing:
https://<your-domain>/ar-io/observer/reports/current -
Gateway listed in the portal: gateways.ar.io
-
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.
| Role | What it signs | Env vars | Fallback |
|---|---|---|---|
| Operator (+ cranker) | join_network, update_gateway_settings, permissionless cranker instructions | SOLANA_KEYPAIR_PATH or SOLANA_PRIVATE_KEY | — (required) |
| Observer | save_observations transactions | OBSERVER_KEYPAIR_PATH or OBSERVER_PRIVATE_KEY | Falls back to operator key |
| Upload | Observer report bundles sent to Turbo | See upload precedence below | Falls back to observer → operator Solana key |
| HTTPSIG signer | RFC 9421 response headers | Uses observer Solana key when set | Auto-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.
| # | Operator | Observer | Upload | Required envs |
|---|---|---|---|---|
| 1 | Solana | = operator | = operator (Solana) | SOLANA_KEYPAIR_PATH |
| 2 | Solana | = operator | Arweave JWK | SOLANA_KEYPAIR_PATH + ARWEAVE_UPLOAD_KEY_FILE |
| 3 | Solana A | Solana B | Solana C | SOLANA_KEYPAIR_PATH + OBSERVER_KEYPAIR_PATH + SOLANA_UPLOAD_KEYPAIR_PATH |
| 4 | Solana A | Solana B | Arweave JWK | SOLANA_KEYPAIR_PATH + OBSERVER_KEYPAIR_PATH + ARWEAVE_UPLOAD_KEY_FILE |
| 5 | Solana A | Solana B | Ethereum | SOLANA_KEYPAIR_PATH + OBSERVER_KEYPAIR_PATH + ETHEREUM_UPLOAD_PRIVATE_KEY_FILE |
Pattern 1 — Single Solana keypair (recommended)
# 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 readyPattern 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=falseUpload 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 → SolanaSignerSetting 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)):
| Format | Example | Source |
|---|---|---|
| JSON array (Solana CLI standard) | [12,34,56,...] — 64 uint8 integers | solana-keygen new --outfile keypair.json |
| base58 secret | 87–88 character base58 string | Phantom "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 2MWexMHfMhGJwMHv9Qm9YAVCqjUFUJwDJAysW4oCUGk5For 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 cachequickly (e.g. ~4s for thousands of records) - Resolver logs report
Base name not found in ArNS names cachefor names that demonstrably exist via the SDK CLI'sget-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 pattern | Cause | Fix |
|---|---|---|
multiple chain groups configured for upload role | Upload 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 role | File-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 keypair | An Arweave JWK or other JSON was placed at the Solana keypair path | Check 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 JWK | A Solana keypair (JSON array) was placed at the Arweave upload slot | Swap the file for your Arweave JWK |
SOLANA_KEYPAIR_PATH not set in Solana mode | The operator key is missing entirely | Set SOLANA_KEYPAIR_PATH or SOLANA_PRIVATE_KEY |
OBSERVER_KEYPAIR_PATH does not match on-chain Gateway.observer_address | The observer key doesn't match what was registered at join_network | Update 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-betaHow is this guide?