Borrow USDC against native Bitcoin collateral on Solana - No bridges, No wBTC.
A Solana Anchor program that lets a user lock real, native BTC as collateral and borrow USDC against it, with an Ika dWallet acting as the cryptographic owner of the on-Bitcoin UTXO. Liquidation flows authorize the dWallet via on-chain CPI to release that BTC back into the protocol's possession, all without ever wrapping or bridging the asset.
Solana DeFi treats Bitcoin as a second-class citizen. Every BTC-collateralized position today is either:
- A wrapped IOU (wBTC, tBTC, sBTC): the user trusts a custodian or a bridge, and a bridge exploit zeros the position.
- A bridge-LP claim: the user owns a synthetic that drifts from BTC under stress, and reconciliation depends on liveness of the bridge.
Native BTC has no smart-contract layer, so there has been no way to write a Solana program that directly controls a Bitcoin UTXO. Margin and lending against BTC therefore live behind a wrapper, and the wrapper is the weakest link.
BTC Margin Vault uses Ika's dWallet primitive as the on-chain handle to a real Bitcoin address. A dWallet is a programmable, transferable account whose signing authority is shared between the user and the Ika MPC network via 2PC-MPC - meaning a Solana program can authorize a Bitcoin signature without ever holding the private key, and without anyone ever holding the full key.
The vault flow:
- The user generates a dWallet (Bitcoin-controlling address) through Ika and sends BTC to it.
initialize_vaultrecords thedwallet_idand the dollar value of the collateral on Solana.borrow_usdclets the user borrow up to a 70% LTV against that collateral.- If the position falls below 85% health,
liquidateissues aapprove_messageCPI to the Ika dWallet program — telling the MPC network it is authorized to co-sign a BTC transaction that releases the collateral to the protocol.
No wrapping. No bridge. The BTC stays on Bitcoin the entire time; only the authority to move it ever crosses chains.
This is the integration that makes the whole design work. Walk through what happens during liquidation:
-
The vault holds a
dwallet_id— aPubkeyregistered atinitialize_vault(and updatable viaregister_dwallet). It points at an Ika dWallet account that, in turn, controls a real Bitcoin address. -
The vault program is itself a Solana program. It cannot sign Bitcoin transactions. What it can do is sign on-chain instructions to the Ika dWallet program using a PDA derived from its own program ID — the CPI authority PDA, seeded by
[b"__ika_cpi_authority"](a constant exported byika-dwallet-anchor). -
When
liquidatefires and the LTV is genuinely above the 85% threshold, the handler builds aDWalletContextand invokesapprove_message:ctx.approve_message( coordinator, message_approval, dwallet, payer, system_program, message_digest, // hash of the BTC tx that releases collateral message_metadata_digest, user_pubkey, // Schnorr/EdDSA pubkey held by the user signature_scheme, message_approval_bump, )?;The CPI is
invoke_signedwith the program's CPI authority PDA, so the Ika program sees the call as authorized by this program, not by a user-held key. -
The Ika program creates a
MessageApprovalPDA. This PDA is the on-chain signal to the off-chain Ika MPC network: "the dWallet's program-side authority has approved this exact BTC transaction." The Ika network watches for these approvals, runs 2PC-MPC, and produces a Schnorr signature over the BTC transaction. -
The signed BTC transaction is broadcast to the Bitcoin network by any client — the MPC signature is the only thing that was missing, and now it exists.
The crucial security property: even though liquidate can be called by anyone (a liquidator), the Solana program enforces the LTV check before the CPI fires. The Ika CPI authority will refuse to authorize any signing the program does not explicitly request, and the program will not request signing unless the LTV ratio actually exceeds 85%. The on-chain LTV check and the off-chain BTC release are therefore atomically gated.
┌─────────────────────────┐
│ vault owner │
└────────────┬────────────┘
│ initialize / deposit / borrow / repay
▼
┌──────────────────────────────────────────────────────────┐
│ btc-margin-vault (Solana / Anchor) │
│ │
│ VaultState PDA [b"vault", owner.key()] │
│ ├─ dwallet_id │
│ ├─ btc_collateral_usd │
│ ├─ usdc_borrowed │
│ └─ is_active │
│ │
│ CPI authority PDA [b"__ika_cpi_authority"] │
│ └─ signs Ika CPIs on behalf of this program │
└──────────────────────────────┬───────────────────────────┘
│ liquidate(): approve_message CPI
│ invoke_signed via CPI authority PDA
▼
┌──────────────────────────────────────────────────────────┐
│ Ika dWallet program │
│ 87W54kGYFQ1rgWqMeu4XTPHWXWmXSQCcjm8vCTfiq1oY │
│ │
│ MessageApproval PDA ◄─── the on-chain "approved" flag │
└──────────────────────────────┬───────────────────────────┘
│ watched off-chain
▼
┌──────────────────────────────────────────────────────────┐
│ Ika MPC network (2PC-MPC, threshold signature) │
│ produces a Schnorr signature over the BTC tx │
└──────────────────────────────┬───────────────────────────┘
│ signed BTC tx broadcast
▼
┌────────────────────┐
│ Bitcoin network │
│ (native UTXO) │
└────────────────────┘
| Instruction | Args | Behavior |
|---|---|---|
initialize_vault |
dwallet_id: Pubkey |
Creates the VaultState PDA for the calling owner, binds it to a dWallet, sets balances to zero, marks active. |
register_dwallet |
dwallet_id: Pubkey |
Owner-only. Rebinds the vault to a (possibly new) dWallet. Refuses on inactive vaults. |
deposit_collateral |
amount_usd: u64 |
Records additional BTC collateral (in USD terms) against the vault. Caller-trusted USD valuation; an oracle would slot in here in production. |
borrow_usdc |
amount: u64 |
Borrows USDC if and only if the resulting LTV stays at or below LTV_THRESHOLD (70%). Reverts with InsufficientCollateral otherwise. |
repay_usdc |
amount: u64 |
Reduces usdc_borrowed by amount (saturating). |
liquidate |
vault: Pubkey, message_digest: [u8;32], message_metadata_digest: [u8;32], user_pubkey: [u8;32], signature_scheme: u16, message_approval_bump: u8 |
Permissionless. If LTV > LIQUIDATION_THRESHOLD (85%), invokes the Ika dWallet approve_message CPI to authorize releasing the BTC, then zeroes the vault and marks it inactive. |
| Name | Value | Meaning |
|---|---|---|
LTV_THRESHOLD |
70 |
Maximum LTV percentage allowed at borrow time. |
LIQUIDATION_THRESHOLD |
85 |
LTV percentage above which liquidate will fire the Ika CPI. |
VAULT_SEED |
b"vault" |
Seed prefix for the vault PDA (combined with owner.key()). |
IKA_CPI_AUTHORITY_SEED |
b"__ika_cpi_authority" |
Re-exported from ika-dwallet-anchor; seed for this program's CPI authority PDA. |
Requirements:
- Rust toolchain pinned to
1.89.0(seerust-toolchain.toml) - Anchor CLI
1.0.1 - A Solana SBF toolchain (Anchor will fetch this on first build)
# Build the program
anchor build
# Run the test suite (LiteSVM-based, in-process; no validator required)
cargo test --package btc-margin-vault --testsExpected output: 8 passed; 0 failed; 1 ignored. The single ignored test (liquidate_above_threshold_succeeds) exercises the full Ika CPI on the success path and is gated until an Ika program fixture is wired into the LiteSVM harness.
| Item | Value |
|---|---|
btc-margin-vault program ID |
6UVY7nskmCBaeKS22E6oh5CkS9TcdcmazHzLAme4YNfB |
| Ika dWallet devnet program ID | 87W54kGYFQ1rgWqMeu4XTPHWXWmXSQCcjm8vCTfiq1oY |
| Solana cluster | devnet (https://api.devnet.solana.com) |
| Ika dWallet gRPC endpoint | https://pre-alpha-dev-1.ika.ika-network.net:443 |
| Ika SDK | ika-dwallet-anchor (pinned, pre-alpha) |