Integration
Relayer: FairyPort
FairyPort is the production-grade Go relayer that bridges Stellar and Fairyring. It monitors events on both chains, optionally pre-verifies ZK proofs, and submits cross-chain transactions.
Internal architecture
| Component | Responsibility | Key files |
|---|---|---|
| Event Monitors | Real-time WebSocket subscriptions with auto-reconnect; HTTP catchup on startup | internal/stellar/ws/ws.go, internal/fairy/ws/ws.go |
| Event Registries | Parse and type-validate blockchain events from both chains | internal/stellar/events/wasm.go, internal/fairy/events/wasm.go |
| Event Queues | Bounded channels (capacity 1024 each) decoupling receipt from processing | internal/app/app.go |
| Stellar Dispatcher | Convert Stellar events into Fairyring ExecuteMsg; normalize and deduplicate | internal/app/dispatcher_stellar.go |
| CW Dispatcher | Convert Fairyring wasm events into Soroban callback invocations | internal/app/dispatcher_cw.go |
| Off-chain ZKP Verifier | Rust FFI library called via CGO; verifies transfer and withdraw proofs before relay | internal/zkpffi/ffi.go, native/zkp_verify_ffi/ |
| Transactors | Sign with relayer private key and broadcast to each chain | internal/app/tx_stellar.go, internal/app/tx_cosmos.go |
| Pending Poller | Periodically query Soroban for pending actions; re-trigger stale actions | internal/app/pending_poller.go |
| Checkpoint Store | Atomic LevelDB writes for last processed ledger/block height and pending state | internal/app/checkpoint.go, internal/store/store.go |
| Retry Manager | Track retry counts with configurable backoff; dead-letter on exhaustion | internal/app/retry_cap.go, internal/app/failed_tx_log.go |
Building from source
FairyPort is a Go binary that links against a Rust FFI library for ZKP verification. Both must be compiled.
# Prerequisites: Go 1.22.10+, Rust 1.70+, Cargo, C build tools
# Clone and build (Rust FFI library + Go binary)
git clone https://github.com/Fairblock/stellar-repo
cd stellar-repo/fairyport
make install
# Verify installation
fairyport --version
ls -la native/lib/ # should contain libzkp_verify_ffi.{so,dylib}The make install target compiles the Rust library (libzkp_verify_ffi) into native/lib/ and then links it into the Go binary via CGO. The pre-compiled library is included for macOS and Linux.
Configuration
Copy configs/configs.example.yaml as your starting point.
config:
fairyring:
chain_id: fairyring_devnet
bech32: fairy
contract_address: fairy1... # CosmWasm contract address
relayer_account_priv_key_hex: 0x... # 64 hex chars; keep secret
rpc_endpoint:
ip: 127.0.0.1
port: 26657
protocol: http
grpc_endpoint:
ip: 127.0.0.1
port: 9090
protocol: ""
websocket_endpoint:
ip: 127.0.0.1
port: 26657
protocol: ws
fee_denom: ufair
fee_amount: 0
gas_limit: 1500000
events:
contracts:
- fairy1...
pep_events_only: false
start_height: -1 # -1 = start from latest
stellar:
rpc_endpoint: https://soroban-rpc.testnet.stellar.org
contract_address: CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
network_passphrase: "Test SDF Network ; September 2015"
relayer_private_key: SXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
start_ledger: -1 # -1 = start from latest
metrics:
enabled: true
listen_addr: :9102
log:
level: info # debug | info | warn | errorEnvironment variable overrides
Every config field can be overridden with an environment variable using the FAIRYPORT_ prefix. Prefer environment variables for secrets in production.
# Fairyring config overrides
export FAIRYPORT_CONFIG_FAIRYRING_CHAIN_ID=fairyring_devnet
export FAIRYPORT_CONFIG_FAIRYRING_CONTRACT_ADDRESS=fairy1...
export FAIRYPORT_CONFIG_FAIRYRING_RELAYER_ACCOUNT_PRIV_KEY_HEX=0x...
export FAIRYPORT_CONFIG_FAIRYRING_RPC_ENDPOINT_IP=127.0.0.1
export FAIRYPORT_CONFIG_FAIRYRING_RPC_ENDPOINT_PORT=26657
export FAIRYPORT_CONFIG_FAIRYRING_GRPC_ENDPOINT_IP=127.0.0.1
export FAIRYPORT_CONFIG_FAIRYRING_GRPC_ENDPOINT_PORT=9090
export FAIRYPORT_CONFIG_FAIRYRING_WEBSOCKET_ENDPOINT_IP=127.0.0.1
export FAIRYPORT_CONFIG_FAIRYRING_FEE_DENOM=ufair
export FAIRYPORT_CONFIG_FAIRYRING_GAS_LIMIT=1500000
# Stellar config overrides
export FAIRYPORT_CONFIG_STELLAR_RPC_ENDPOINT=https://soroban-rpc.testnet.stellar.org
export FAIRYPORT_CONFIG_STELLAR_CONTRACT_ADDRESS=CAAAA...
export FAIRYPORT_CONFIG_STELLAR_NETWORK_PASSPHRASE="Test SDF Network ; September 2015"
export FAIRYPORT_CONFIG_STELLAR_RELAYER_PRIVATE_KEY=SXXX...
# Observability
export FAIRYPORT_CONFIG_METRICS_ENABLED=true
export FAIRYPORT_CONFIG_METRICS_LISTEN_ADDR=:9102
export FAIRYPORT_CONFIG_LOG_LEVEL=infoRunning the relayer
# Run with default config path (fairyport.config.yaml in CWD)
fairyport run
# Run with explicit config path
fairyport run --config /etc/fairyport/config.yaml
# Sync mode: replay events between two heights without continuous operation
fairyport sync --from-height 1000 --to-height 2000Startup & recovery sequence
Load checkpoint store
Opens or creates LevelDB database. Reads last_stellar_ledger and last_cw_height from persistent storage.
Query current chain heights
Fetches the latest ledger number from the Stellar RPC and the latest block height from Fairyring.
Catchup replay
Replays all events from the checkpoint to the current height in background. Uses HTTP queries (not WebSocket) to fill the gap. Checkpoints update atomically after each batch.
WebSocket subscription
Once caught up, subscribes to live events via WebSocket on both chains. Auto-reconnects on disconnection.
Continuous processing
Dispatches events through the queue → verifier (optional) → broadcaster → confirmation poller pipeline.
Checkpoint store layout
Persisted to LevelDB at fairyport-checkpoints-{chainid}-{stellaraddr}.leveldb.
# LevelDB checkpoint keys
checkpoint/stellar/last_ledger → int64 string
checkpoint/cw/last_height → int64 string
# Pending action deduplication keys (prevent duplicate submissions)
pending/forwarded/{kind}/{owner}/{txid} → timestamp string
pending/retry_count/{kind}/{owner}/{txid} → integer string
pending/dead/{kind}/{owner}/{txid} → int64 string (dead-letter marker)
# kind values: create | deposit | transfer | apply | withdrawHealth monitoring
Prometheus metrics are exposed at the configured listen_addr.
# Prometheus metrics endpoint
curl -s http://localhost:9102/metrics | grep fairyport_
# Key metrics to watch:
# fairyport_events_received_total{chain="stellar"}
# fairyport_events_received_total{chain="fairyring"}
# fairyport_transactions_submitted_total{chain="stellar",status="ok"}
# fairyport_transactions_submitted_total{chain="fairyring",status="ok"}
# fairyport_retry_count_total
# fairyport_dead_letter_total
# Check checkpoint state
ls -lh fairyport-checkpoints-*.leveldb/
# Inspect dead-lettered transactions
jq length failed_txs.json 2>/dev/null || echo "0 failed"
# Process status
ps aux | grep fairyport | grep -v grepError handling & recovery
Network timeout / transient error
Automatic retry with exponential backoff. Retry count tracked per (kind, owner, txid) in LevelDB.
Retry exhaustion
Transaction marked dead-lettered in LevelDB and appended to failed_txs.json for manual operator review.
Invalid ZK proof (off-chain mode)
Error callback sent directly to the Stellar contract; action discarded. No state mutation on Fairyring.
Relayer restart
Checkpoint loaded from LevelDB; catchup replay fills the gap. Duplicate tx_ids are handled idempotently by Fairyring.
Corrupt checkpoint database
Back up and delete the .leveldb directory. FairyPort will replay from config start_height or from genesis.
Pending action timeout
The Pending Poller queries Soroban for accounts with pending_action=true and re-triggers relay if no confirmation has arrived after a configured timeout.
Security guidelines
Inject private keys via environment variables, not config files, in production
Restrict config file permissions: chmod 600 fairyport.config.yaml
Private keys are never logged; sensitive fields are sanitized from error messages
Checkpoints only update after confirmed onchain finality no state regression risk
The relayer does not hold user funds; it only signs cross-chain coordination messages
Run multiple independent FairyPort instances for liveness redundancy
Testing
Three integration test scripts are provided in fairyport/scripts/:
test_end2end.pyFull Stellar → Fairyring → Stellar flow with off-chain verification. Covers create, deposit, transfer, apply, withdraw. Also exercises relay catchup by submitting while relayer is offline.
test_end2end_onchainverify.pySame end-to-end flow but with use_offchain_verify=false (onchain ZKP verification via Fairyring).
test_sync.pyCreates an account, directly deposits into Fairyring, runs fairyport sync, and verifies the Stellar mirror state updates.
