DePix swap

Swap your existing DePix on Spark into BTC in a Spark wallet or a payment to a Lightning address, using Flashnet.

Use POST /api/wallet/swap to spend DePix already held in your Hodle Spark wallet. Hodle exchanges it on the DePix/BTC Flashnet pool and delivers BTC to a Spark address or pays a Lightning address. This operation does not create a PIX charge and cannot spend DePix held on Liquid.

Prerequisites

  • A production API key and an account with swaps enabled (SWAP). Spark DePix swaps are unavailable in sandbox.
  • An initialized Spark wallet with spendable DePix. Unlock the wallet once in the app to register its Spark identity.
  • The wallet PIN and its protectedSymmetricKey, available through Wallet Keys. The PIN must unlock the wallet selected by subAccountId, when supplied.

A supplied destination changes where BTC is delivered. It does not select the wallet to debit or authorize spending from an arbitrary Spark address.

POST /api/wallet/swap

The API verifies wallet authorization, records the request, and returns 202 Accepted. The worker checks the pool output before transferring DePix. Follow the swapId to learn whether delivery completed.

DePix to your own Spark BTC wallet
const response = await fetch('https://api.hodle.com.br/api/wallet/swap', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.HODLE_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    fromAsset: 'DEPIX',
    toAsset: 'BTC',
    network: 'spark',
    amount: '25.00',
    clientRequestId: 'depix-order-123',
    minAmountOutSats: 400,
    maxSlippageBps: 100,
    walletPin: process.env.HODLE_WALLET_PIN,
    protectedSymmetricKey: process.env.HODLE_PROTECTED_SYMMETRIC_KEY,
  }),
})
const swap = await response.json()

Omit address to receive BTC in the selected wallet's Spark address, or pass another valid mainnet spark1… address.

202 Accepted — illustrative response
{
  "success": true,
  "swapId": "f3cda087-85f9-4e17-a37a-208e58231fe1",
  "clientRequestId": "depix-order-123",
  "status": "PENDING",
  "fromAsset": "DEPIX",
  "toAsset": "BTC",
  "amountIn": "2500000000",
  "network": "spark",
  "destination": "spark1pgss92n427yh3ghz5dq0j93ph3p9lrmhy7enrus02uusmd6ktlx23t88en8f8j",
  "minAmountOutSats": 400,
  "maxSlippageBps": 100,
  "maxLightningFeeSats": 0
}

amount is a decimal string in whole DePix. amountIn is an integer string in raw token units: 1 DePix = 100,000,000 units. BTC amounts in the response and tracking legs are satoshis.

Request fields

FieldRequiredDescription
fromAssetYesDEPIX. The source network is Spark.
toAssetYesBTC.
networkRecommendedDelivery network: spark or lightning. Omission retains the Lightning default. Always send it explicitly in new integrations.
amountYesPositive decimal string with up to 8 decimal places; at most 10,000 DePix. Scientific notation is rejected.
clientRequestIdYesUnique per owner; 1–100 letters, digits, _, or -. Reuse the same ID when retrying the same request.
walletPinYesPIN for the selected wallet. Never log it. Three invalid attempts lock the wallet for 24 hours.
protectedSymmetricKeyYesEncrypted key envelope from Wallet Keys. Never log it.
subAccountIdNoA sub-account owned by the API-key holder. Its wallet is debited; the owner's default wallet is not a fallback.
addressNoA mainnet Spark address for spark, or user@domain for lightning. Defaults to the selected wallet's address for that network.
minAmountOutSatsNoMinimum BTC delivered to the requested destination. Integer, at least 400; defaults to 400. For Lightning, applies after reserving the fee allowance.
maxSlippageBpsNoInteger from 0 to 1,000; defaults to 100 (1%). Passed to Flashnet along with the minimum output.
maxLightningFeeSatsNoInteger from 0 to 10,000; defaults to 10 for Lightning, ignored for Spark. Maximum routing allowance, not a fee charged in full.
referenceNoYour label for reconciliation. It is not an idempotency key.

Lightning delivery

Use the same endpoint and credentials with these fields:

{
  "fromAsset": "DEPIX",
  "toAsset": "BTC",
  "network": "lightning",
  "amount": "25.00",
  "clientRequestId": "depix-lightning-124",
  "address": "[email protected]",
  "minAmountOutSats": 400,
  "maxSlippageBps": 100,
  "maxLightningFeeSats": 10,
  "walletPin": "<wallet PIN>",
  "protectedSymmetricKey": "<encrypted key envelope>"
}

Hodle first transfers the entire BTC swap output to your Spark wallet. That wallet then pays an invoice obtained from the Lightning address for swap output minus maxLightningFeeSats. Only the actual routing fee is spent; the unused allowance stays in your Spark wallet.

For example, a 2,000-sat swap with a 10-sat allowance pays a 1,990-sat invoice. If the actual routing fee is 3 sats, 7 sats remain in your Spark wallet. A route that needs more than the allowance is not authorized. A Lightning failure does not repeat the DePix swap.

BOLT11 invoices are not accepted as the address input because the final payable amount is known only after the swap. The Lightning service must accept the resulting amount; an invalid or unavailable service can leave the BTC in your Spark wallet without completing the payment.

Track settlement

curl --request GET \
  --url 'https://api.hodle.com.br/api/wallet/swap/f3cda087-85f9-4e17-a37a-208e58231fe1' \
  --header "Authorization: Bearer $HODLE_API_KEY"

For a sub-account swap, append ?subAccountId=YOUR_SUBACCOUNT_ID. An unrelated API key, or a request for the wrong sub-account, receives 404.

Poll every few seconds until a terminal state. A successful POST only confirms acceptance; COMPLETED confirms destination delivery. This endpoint does not emit DEPOSIT_ASSET_SUCCESS; that event belongs to PIX deposits.

StatusMeaning and action
PENDINGRecorded and awaiting the worker.
RUNNINGA worker has claimed the request. Do not create a second swap while it runs.
COMPLETEDSpark transfer, or the final Lightning payment, was confirmed.
FAILEDThe operation stopped before any funds were known to move, such as expired authorization or insufficient output. Inspect error.
STRANDEDFunds moved, or a transfer outcome is uncertain. Inspect the legs and reconcile the existing operation before attempting anything again. For a Lightning failure after BTC delivery, BTC is already in your wallet and may be temporarily locked by the payment.

The response includes legs, each with status, originChain, destinationChain, originTxHash, destinationTxHash, actualAmountOut, and error:

LegMovementEvidence
0Your Spark DePix → Hodle settlement walletoriginTxHash: token transfer ID.
1DePix → BTC on FlashnetswapRequestId: Flashnet request ID; destinationTxHash: pool BTC transfer ID; actualAmountOut: gross sats.
2Settlement wallet → Spark destinationdestinationTxHash: Spark BTC transfer ID. For Lightning, the destination is your own Spark wallet.
3, Lightning onlyYour Spark wallet → Lightning recipientdestinationTxHash: Lightning request ID; actualAmountOut: invoice sats; feeSats: actual routing fee when available.

Confirmed legs remain recorded even when a later step fails. Repeated worker delivery cannot repeat the debit. An interrupted worker can leave RUNNING until reconciliation; a Lightning payment still unconfirmed after the bounded settlement check is reported as STRANDED, never as completed.

Retrying HTTP requests

Retry the identical request with the same clientRequestId after an HTTP timeout. Once the wallet unlock check is free, you receive 200 with the original swapId and current status, and no new job is created. Changing the amount, destination, network, wallet, limits, or reference while reusing the ID returns 409 IDEMPOTENCY_CONFLICT.

A replay of a failed request reports its existing failure; it does not restart settlement. Never use a new ID to recover an uncertain or stranded transfer. Wallet authorization expires after five minutes if the worker has not consumed it.

Costs and errors

The pool fee is included in the BTC output; the current DePix/BTC pool lists a 1.40% fee. There is no PIX deposit fee on this balance swap and no additional Hodle swap markup in this route. Lightning routing fees are paid from the BTC already credited to the user's Spark wallet. See the pool for the token and pool identifiers.

HTTP / codeMeaning
400 INVALID_INPUTMissing request ID, invalid amount, or excessive precision. Invalid limits also return 400.
400 NO_SPARK_WALLETThe selected wallet has no initialized Spark identity or encrypted mnemonic.
400 INVALID_SPARK_ADDRESSInvalid or non-mainnet Spark destination.
400 INVALID_LIGHTNING_DESTINATIONMissing or malformed Lightning address.
400 BOLT11_NOT_SUPPORTEDUse a Lightning address instead of a fixed invoice.
401 INVALID_PINThe PIN/key envelope does not unlock the selected wallet.
403 NOT_ENABLEDSwaps are not enabled for the account.
501Sandbox API keys cannot submit swaps; no live Spark operation runs.
404User, owned sub-account, or scoped swap was not found.
409 IDEMPOTENCY_CONFLICTRequest ID already used with different parameters.
429 RATE_LIMITEDWallet PIN lockout, or another unlock is in progress. Retry a busy unlock later with the same request ID.
503 QUEUE_UNAVAILABLECould not enqueue; the response includes the swap ID.

Pool minimums, spendable balance, and live settlement are checked by the worker. Their failures appear in the tracking response rather than as a synchronous POST rejection.