IBEx.Fi supports Bitcoin with two address types derived from the same master secret: P2WPKH (SegWit) and P2TR (Taproot). Transactions are built and signed client-side using PSBTs — the server never holds Bitcoin private keys.
From the user's master bytes (PRF or sealed envelope), two Bitcoin addresses are derived via HKDF:
| Type | Info Label | Address Format | Example |
|---|---|---|---|
| P2WPKH (SegWit v0) | bitcoin:global |
bech32 — bc1q... |
bc1qxy2kgdygjr... |
| P2TR (Taproot v1) | bitcoin:taproot |
bech32m — bc1p... |
bc1pxy2kgdygjr... |
// P2WPKH derivation
sk = HKDF(SHA-256, masterBytes, "ibexfi:derivation:v1", "bitcoin:global", 32)
pub = secp256k1.getPublicKey(sk, true) // 33 bytes compressed
hash = RIPEMD-160(SHA-256(pub)) // 20 bytes
addr = bech32("bc", 0, hash) // bc1q...
// Taproot derivation
sk = HKDF(SHA-256, masterBytes, "ibexfi:derivation:v1", "bitcoin:taproot", 32)
P = secp256k1.getPublicKey(sk).x // 32-byte x-only pubkey
t = taggedHash("TapTweak", P) // BIP-341 tweak
Q = P + t·G // tweaked public key
addr = bech32m("bc", 1, Q.x) // bc1p...
┌───────────────────────────────────────────────────────────────┐
│ Client │
│ │
│ 1. GET /v1.2/safes/bitcoin/utxos?address=bc1q... │
│ 2. GET /v1.2/safes/bitcoin/fees │
│ 3. POST /v1.2/safes/bitcoin/send/prepare (or BITCOIN_SEND) │
│ ← Server selects UTXOs, estimates fees, computes change │
│ 4. Client builds PSBT from "prepared" response │
│ 5. Client signs PSBT locally │
│ 6. POST /v1.2/safes/bitcoin/tx/broadcast │
│ ← Server broadcasts raw tx → returns txid │
└───────────────────────────────────────────────────────────────┘
| Endpoint | Method | Purpose |
|---|---|---|
/v1.2/safes/bitcoin/info |
GET | Bitcoin network info (getblockchaininfo) |
/v1.2/safes/bitcoin/fees |
GET | Fee estimation — fast / standard / slow (sat/vB) |
/v1.2/safes/bitcoin/utxos |
GET | List UTXOs for an address (scantxoutset) |
/v1.2/safes/bitcoin/send/prepare |
POST | Prepare a send — UTXO selection, fee calc, change |
/v1.2/safes/bitcoin/tx/broadcast |
POST | Broadcast a signed raw transaction (hex) |
All endpoints require JWT authentication via Authorization: Bearer <token>.
GET /v1.2/safes/bitcoin/fees
Response:
{
"feeRateSatVb": {
"fast": 22, // ~1 block confirmation
"standard": 12, // ~6 blocks
"slow": 8 // ~12 blocks
}
}
POST /v1.2/safes/bitcoin/send/prepare
Body:
{
"from": "bc1q...",
"to": "bc1q...",
"amountSat": 10000,
"sendAll": false,
"feeProfile": "standard",
"network": "mainnet"
}
Response:
{
"from": "bc1q...",
"to": "bc1q...",
"amountSat": 10000,
"feeSat": 1234,
"inputsUsed": [
{ "txid": "...", "vout": 0, "value": 22345, "scriptPubKey": "0014..." }
],
"outputs": [{ "address": "bc1q...", "value": 10000 }],
"change": { "address": "bc1q...", "value": 11111 }
}
Note: When sendAll: true, the entire balance minus fees is sent
and no change output is included.
The sender's UTXOs fund both the transfer and the network fee.
feeSat = sum(inputs) − sum(outputs).
Neither A's nor B's private keys are exposed to the server. Use SIGHASH_ALL
and let the wallet paying the business amount sign last.
Alternatively, use the standard POST /v1.2/safes/operations endpoint with a
BITCOIN_SEND operation. This wraps the prepare flow with a WebAuthn challenge:
POST /v1.2/safes/operations
{
"safeAddress": "0xSafe...",
"operations": [{
"type": "BITCOIN_SEND",
"network": "mainnet",
"from": "bc1q...",
"to": "bc1q...",
"amountSat": 10000,
"feeProfile": "standard"
}]
}
Response:
{
"credentialRequestOptions": { /* WebAuthn challenge */ },
"prepared": {
"from": "bc1q...",
"to": "bc1q...",
"amountSat": 10000,
"feeSat": 1234,
"inputsUsed": [...],
"outputs": [...],
"change": {...}
}
}
Use the prepared object to build your PSBT client-side, then broadcast via
/v1.2/safes/bitcoin/tx/broadcast.
inputsUsed[i], add a PSBT input with
txid, vout, and witness data
to output + optional changefromPOST /v1.2/safes/bitcoin/tx/broadcast with {"rawtx":
"..."}Refer to your PSBT library docs (bitcoinjs-lib, etc.) for exact input fields per type.
| Aspect | EVM (Safe) | Solana | Bitcoin |
|---|---|---|---|
| Model | Account-based | Account-based | UTXO-based |
| Signing location | Authenticator (WebAuthn) | Client-side | Client-side (PSBT) |
| Gas / fees | Paymaster (gasless) | Fee payer (2-step) | Wallet-paid or collaborative PSBT |
| Address types | 0x… (one) | Base58 (one) | P2WPKH + Taproot (two) |
| Server signs? | Yes (Safe UserOp) | Fee payer only | Never |
| Status | Cause |
|---|---|
| 400 | Missing fields, no UTXOs, insufficient funds |
| 401 | Missing or invalid JWT |
| 500 | RPC failure, broadcast error |
| 501 | /psbt/build — reserved, not yet implemented |