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.
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.
┌───────────────────────────────────────────────────────────┐
│ 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 │
└───────────────────────────────────────────────────────────┘
When SOLANA_FEE_PAYER_<CHAIN_ID> is configured, the server acts as
fee payer. The 2-step workflow:
feePayer, no
signature)feePayer → signs with the fee payer keypairpartialSign(userKeypair) → sends the fully signed
transactionSecurity: 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.
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 |
{
"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 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
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 |
| Input Format | Size | Method |
|---|---|---|
| Seed (from HKDF) | 32 bytes | Keypair.fromSeed(seed) |
| Full secret key | 64 bytes | Keypair.fromSecretKey(secretKey) |
Common error: UsingKeypair.fromSecretKey()with a 32-byte seed throws "bad secret key size". Always usefromSeed()for 32-byte HKDF outputs.
| 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) |