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:
DEPIXonspark— a PIX becomes DePix, a BRL-pegged token, delivered to a Spark address.BTConspark— 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:
{
"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_SUCCESScurl --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(){
"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_SUCCESScurl --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:
{
"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.
| Field | Value |
|---|---|
| Pool | 0354b3225e9239f2ccf45113fcad5a8ff1526ffd2ec893bcfc2828e3627d334b98 |
| DePix token | btkn1sdqgp9904d620fmgs6p9669fghzfq06hvpqmy2wg8c9xtw4qms3qpf4aty |
| Pool fee | 1.40% (1.00% LP + 0.40% host) |
| Decimals | 8 |
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
| Cost | Amount |
|---|---|
| Provider cost | R$ 0.99 fixed per deposit, no network fee |
| Hodle fee | R$ 1.00 + 2% (unless your account has custom fees) |
| Pool fee | 1.40%, on the BTC flow only |
| Minimum | the 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
{ "success": false, "error": "…", "errorCode": "NO_SPARK_WALLET" }{ "success": false, "error": "Endereço Spark inválido", "errorCode": "INVALID_SPARK_ADDRESS" }{ "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.