◎ Solana Integration

IBEx.Fi supports Solana as a first-class chain alongside EVM. Solana wallets are derived from the same master secret as all other chains, using Ed25519. Transactions can be fee-sponsored by the platform so users never need to hold SOL for gas.

How Solana Wallets Are Derived

At sign-up, the server derives a Solana keypair from the user's master bytes (PRF or sealed envelope):

// 1. HKDF-SHA256 derivation
salt = "ibexfi:derivation:v1"
info = "solana:global"
seed = HKDF(SHA-256, masterBytes, salt, info, 32)   // 32-byte Ed25519 seed

// 2. Ed25519 key generation
keypair = Keypair.fromSeed(seed)
address = keypair.publicKey.toBase58()               // e.g. "7xKXt..."

The Solana address is stored in Signer.data.derivation.solanaAddress and registered with BCReader for balance/transaction tracking.

Architecture

┌───────────────────────────────────────────────────────────┐
│  Client (dApp / SDK)                                      │
│                                                           │
│  1. Build transaction (unsigned, no feePayer set)          │
│  2. Serialize → base64                                    │
│  3. POST /{chainId}/rpc  sendTransaction (unsigned)       │
│     ← Server adds fee payer + fee payer signature         │
│  4. partialSign(userKeypair)                              │
│  5. POST /{chainId}/rpc  sendTransaction (fully signed)   │
│     ← Server broadcasts → tx signature                   │
└───────────────────────────────────────────────────────────┘

Fee Sponsorship (Gasless)

When SOLANA_FEE_PAYER_<CHAIN_ID> is configured, the server acts as fee payer. The 2-step workflow:

  1. Step 1: Client sends an unsigned transaction (no feePayer, no signature)
  2. Server detects it is unsigned → sets feePayer → signs with the fee payer keypair
  3. Server returns the fee-payer-signed transaction (base64)
  4. Step 2: Client calls partialSign(userKeypair) → sends the fully signed transaction
  5. Server broadcasts to the Solana network → returns the transaction signature
Security: The server never accesses the client's private key. The fee payer only pays for gas; it cannot move user funds. Both signatures are required.

Operation Types

Solana operations are submitted via the standard POST /v1.2/safes/operations endpoint using string-type operation codes:

Operation Purpose
SOLANA_PREPARE Build a transaction from instructions + recent blockhash
SOLANA_SIGN Sign a base64 message with the user's Ed25519 key
SOLANA_SEND Submit a signed transaction for broadcast
SOLANA_CONFIRM Wait for confirmation at a given commitment level
SOLANA_STATUS Check the status of a submitted transaction

SOLANA_PREPARE Request

{
  "safeAddress": "0xYourEvmSafe",
  "chainId": 1399811149,
  "operations": [{
    "type": "SOLANA_PREPARE",
    "instructions": ["<base64-instruction>", "..."],
    "payer": "<base58-public-key>",
    "recentBlockhash": "<blockhash>",
    "lastValidBlockHeight": 265000000,
    "computeUnitPriceMicroLamports": 1000,
    "computeUnitLimit": 800000,
    "sponsored": true
  }]
}

SPL Token Transfers

SPL Token transfers follow the same 2-step fee sponsorship flow:

import { createTransferInstruction, getAssociatedTokenAddress } from '@solana/spl-token';

// Get associated token accounts
const fromATA = await getAssociatedTokenAddress(tokenMint, userPublicKey);
const toATA   = await getAssociatedTokenAddress(tokenMint, recipientPublicKey);

// Build transfer instruction
const transaction = new Transaction().add(
  createTransferInstruction(fromATA, toATA, userPublicKey, amount)
);
transaction.recentBlockhash = blockhash;
// DO NOT set feePayer or sign — send unsigned to server

Checking Balances & Transactions

Use the existing BCReader endpoints with the Solana chain ID:

Endpoint Purpose
GET /v1.2/bcreader/balances?blockchainId=<SOLANA_ID>&address=<PUBKEY> Native SOL + SPL token balances
GET /v1.2/bcreader/transactions?blockchainId=<SOLANA_ID>&signature=<SIG> Transaction status and details

Keypair Management (Client-Side)

Input Format Size Method
Seed (from HKDF) 32 bytes Keypair.fromSeed(seed)
Full secret key 64 bytes Keypair.fromSecretKey(secretKey)
Common error: Using Keypair.fromSecretKey() with a 32-byte seed throws "bad secret key size". Always use fromSeed() for 32-byte HKDF outputs.

Key Differences vs EVM

Aspect EVM (Safe / EOA) Solana
Curve secp256k1 Ed25519
Address format 0x… (20 bytes, hex) Base58 (32 bytes public key)
Smart wallet Safe contract or EIP-7702 Native EOA (no smart contract)
Gas sponsorship ERC-4337 Paymaster Fee payer (2-step flow)
Token standard ERC-20 SPL Token
Signing Server-side (WebAuthn) Client-side (partialSign)

See Also