Spark & DePix

On-ramp a PIX straight into Spark — DePix as a Spark token, or BTC swapped on the DePix/BTC pool with Hodle's Flashnet key.

Spark is a Bitcoin layer-2 where balances live in leaves signed by the wallet owner, not in on-chain UTXOs. Hodle gives every account a self-custodial Spark wallet, and this page covers the two on-ramps that end there:

  • DEPIX on spark — a PIX becomes DePix, a BRL-pegged token, delivered to a Spark address.
  • BTC on spark — the same PIX, but Hodle immediately swaps the DePix for sats on the DePix/BTC pool and forwards them to you.

Both are on-ramp only. There is no Spark off-ramp: the provider settles redemptions on Liquid, never on Spark. Sats that land on Spark leave through Lightning or an on-chain exit; DePix that lands on Spark leaves only through the swap below.

Before you start: find your Spark address

A Spark address is bech32m and starts with spark1. Read it from Wallet Get:

POST /api/wallet/get
{
  "success": true,
  "data": {
    "addresses": {
      "spark": "spark1pgss92n427yh3ghz5dq0j93ph3p9lrmhy7enrus02uusmd6ktlx23t88en8f8j",
      "sparkLightning": "[email protected]"
    }
  }
}

addresses.spark is the Spark address — the one this page uses. addresses.sparkLightning is the Lightning address of the same wallet, for the Lightning ↔ PIX flow.

addresses.spark is absent until the wallet has registered a Spark identity, which happens the first time its owner unlocks it in the app. Until then both on-ramps answer 400 with NO_SPARK_WALLET, unless you pass an explicit address.

Flow 1 — DePix on Spark

POST /api/deposit/asset          you get a PIX QR code
  { asset: "DEPIX", network: "spark" }
        |
   customer pays the PIX
        |
   provider mints DePix to your spark1… address
        |
   webhook DEPOSIT_ASSET_SUCCESS
curl --request POST \
  --url https://api.hodle.com.br/api/deposit/asset \
  --header "Authorization: Bearer $API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "value": 5000,
    "asset": "DEPIX",
    "network": "spark",
    "externalId": "my-order-123"
  }'
const res = await fetch('https://api.hodle.com.br/api/deposit/asset', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.HODLE_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    value: 5000,
    asset: 'DEPIX',
    network: 'spark',
    externalId: 'my-order-123',
  }),
})
const data = await res.json()
import os, requests

res = requests.post(
    "https://api.hodle.com.br/api/deposit/asset",
    headers={
        "Authorization": f"Bearer {os.environ['HODLE_API_KEY']}",
        "Content-Type": "application/json",
    },
    json={
        "value": 5000,
        "asset": "DEPIX",
        "network": "spark",
        "externalId": "my-order-123",
    },
)
data = res.json()
200 OK
{
  "success": true,
  "externalId": "my-order-123",
  "qrCode": "00020101021226910014br.gov.bcb.pix…",
  "address": "spark1pgss92n427yh3ghz5dq0j93ph3p9lrmhy7enrus02uusmd6ktlx23t88en8f8j",
  "fee": 199,
  "fxRateAtTx": 408163.26
}

Omit address and the DePix goes to the account's own Spark wallet. Pass address to send it to any other spark1… address.

Flow 2 — BTC on Spark

Same PIX, but the DePix never reaches you. It is delivered to Hodle's master Spark wallet, swapped for sats on the DePix/BTC pool with Hodle's own Flashnet key, and the sats are forwarded to your Spark address. You sign nothing and you never hold the token.

POST /api/deposit/asset
  { asset: "BTC", network: "spark" }
        |
   customer pays the PIX
        |
   provider mints DePix to Hodle's master Spark wallet
        |
   Hodle swaps DePix -> BTC on the pool  (Hodle's Flashnet key)
        |
   Hodle transfers the sats to your spark1… address
        |
   webhook DEPOSIT_ASSET_SUCCESS
curl --request POST \
  --url https://api.hodle.com.br/api/deposit/asset \
  --header "Authorization: Bearer $API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "value": 5000,
    "asset": "BTC",
    "network": "spark",
    "externalId": "my-order-124"
  }'

The response has the same shape as flow 1. The swap runs after the PIX is paid, so the 200 tells you the charge exists, not that the sats moved. The DEPOSIT_ASSET_SUCCESS webhook is what confirms delivery:

webhook DEPOSIT_ASSET_SUCCESS
{
  "success": true,
  "asset": "BTC",
  "network": "spark",
  "externalId": "my-order-124",
  "result": "b0a922dcbf343034e53c2b619bf735fbb71ec12756027deb0cbb1d0552a56825",
  "amountSats": 9800,
  "depixTxId": "307cb5a81592ddc660415aee1ea77c816b86f1a81a0920909dd7cdb07fbcffbf",
  "swapRequestId": "01a088a0-c81f-7d52-827d-148ebb72d7b9"
}

result is the Spark transfer id — the sats in your wallet. swapRequestId and depixTxId are there so you can audit both legs.

The pool

The swap runs on a single V3 concentrated-liquidity pool hosted by the DePix issuer.

FieldValue
Pool0354b3225e9239f2ccf45113fcad5a8ff1526ffd2ec893bcfc2828e3627d334b98
DePix tokenbtkn1sdqgp9904d620fmgs6p9669fghzfq06hvpqmy2wg8c9xtw4qms3qpf4aty
Pool fee1.40% (1.00% LP + 0.40% host)
Decimals8

It is the only pool that holds DePix, so BTC is the only asset DePix can be swapped into. Anything else is a second hop from BTC.

Costs and minimums

CostAmount
Provider costR$ 0.99 fixed per deposit, no network fee
Hodle feeR$ 1.00 + 2% (unless your account has custom fees)
Pool fee1.40%, on the BTC flow only
Minimumthe swap needs an output of at least 400 sats, so small tickets are refused with INSUFFICIENT_AMOUNT

At a price around 250 sats per DePix, that 400-sat floor is roughly R$ 2 of DePix reaching the pool — but the floor is on the output, so it moves with the BTC price. Size tickets with headroom.

Getting the sats out

Sats delivered by flow 2 are Spark-native — leaves in your wallet, not an on-chain UTXO. Two exits:

  • Lightning — no minimum beyond the invoice itself. This is the realistic exit for small tickets.
  • On-chain — a Spark exit that pays L1 fees. Hodle refuses exits below 10,000 net sats, because below that the fee eats the withdrawal.

Errors

400 — the wallet has no Spark identity yet
{ "success": false, "error": "…", "errorCode": "NO_SPARK_WALLET" }
400 — address is not a valid spark1… address
{ "success": false, "error": "Endereço Spark inválido", "errorCode": "INVALID_SPARK_ADDRESS" }
400 — ticket too small for the pool
{ "success": false, "error": "…", "errorCode": "INSUFFICIENT_AMOUNT" }

If the PIX is paid but the swap fails, the charge stays pending and nothing is lost: the DePix sits on Hodle's master wallet and the settlement is retried. The DEPOSIT_ASSET_SUCCESS webhook only fires once the sats actually land.