Wallet Keys

Fetch the protectedSymmetricKey needed for wallet payouts.

POST /api/wallet/keys

Returns the selected wallet's walletId, protectedSymmetricKey and the owner's email. Use that wallet's key in transfers and payouts.

Cache keys per walletId, together with the owner's email. Refetch after that wallet's PIN-protected key changes.

Requires the WALLET_PAYOUT_API feature flag. Without it the endpoint responds 403.

Create a wallet with the platform PIN

The account holder first sets a six-digit wallet PIN on the Hodle platform. Wallet creation through the API requires that same walletPin. The server verifies it by unlocking an existing protected wallet belonging to the owner, then creates fresh wallet keys encrypted under that PIN. The PIN is not stored in plaintext.

POST /api/wallet/create
{
  "network": "polygon",
  "walletPin": "123456",
  "protectedSymmetricKey": "<existing owner wallet protectedSymmetricKey>"
}

protectedSymmetricKey is optional on creation: it selects the existing owner wallet used to verify the PIN. If omitted, the server selects a protected reference wallet. It is not a replacement for the PIN, and a key from another owner is rejected.

The response contains data.id, data.address, data.network, data.balance and the new wallet's data.protectedSymmetricKey. EVM responses also include data.chainId. Save the returned id as walletId. Each successful creation creates a new wallet, including successive calls for the same network; keep its ID instead of calling create again to retrieve it.

  • Missing or malformed walletPin: 400, with no wallet created.
  • No platform PIN established: 400. In sandbox, the first protected EVM wallet can be established with the supplied PIN.
  • Wrong platform PIN: 403. Three failures lock further attempts for 24 hours (429).
  • Supported creation networks: polygon, base, solana. Solana keys are encrypted with the wallet's symmetric key. Solana creation is unavailable in sandbox; EVM creation uses Base Sepolia (chainId: 84532).

For a customer wallet, also pass its subAccountId. A reference subaccount inherits its owner's PIN; a legacy subaccount with its own user uses that user's PIN and email.

Reference the new wallet

POST /api/wallet/keys or POST /api/wallet/get
{ "walletId": "6a4d263d4cc67674bf72a3c9" }

Pass the same walletId in /api/wallet/transfer or /api/wallet/payout, alongside walletPin and the selected wallet's protectedSymmetricKey. For subaccount wallets also supply fromSubAccountId on transfers, or subAccountId on reads and payouts. The wallet must belong to that owner and scope. A missing, removed or foreign wallet returns an error; it never falls back to another wallet.

Without walletId, existing default selection applies. In sandbox, an explicit walletId selects the PIN-protected Safe on Base Sepolia for transfers and payout debits. The sandbox mints test tokens and transfers them from that Safe in one sponsored UserOperation; only the PIX leg is simulated. Without walletId, the existing application test wallet remains the settlement source.

For the signature-based payout flow, send walletId when preparing the payout. The returned payoutIntentId binds the wallet; omit walletId when submitting the signature.

PIN format

Wallet creation requires exactly six digits. Use the same platform PIN to unlock the resulting wallet for transfers and payouts.

Rate limit

1 request per minute per API key. Hitting the limit responds 429 with a Retry-After header.

Request

The API key identifies the owner. Send walletId to select a wallet and subAccountId when it belongs to a customer subaccount. Both fields are also accepted as query parameters. An empty body selects the default wallet.

curl --request POST \
  --url https://api.hodle.com.br/api/wallet/keys \
  --header "Authorization: Bearer $API_KEY" \
  --header "Accept: application/json"
const res = await fetch('https://api.hodle.com.br/api/wallet/keys', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.HODLE_API_KEY}`,
    Accept: 'application/json',
  },
})
const data = await res.json()
import os, requests

res = requests.post(
    "https://api.hodle.com.br/api/wallet/keys",
    headers={
        "Authorization": f"Bearer {os.environ['HODLE_API_KEY']}",
        "Accept": "application/json",
    },
)
data = res.json()

Response

200 OK
{
  "success": true,
  "data": {
    "walletId": "6a4d263d4cc67674bf72a3c9",
    "protectedSymmetricKey": "AoofiKHyVRLvdrknnXzoIh1Gd1YTwLaOBn4ibm103a4dpwHZA36dU9DiiZdDvQmNn...",
    "email": "user@example.com"
  }
}
FieldTypeDescription
data.walletIdstringID of the wallet whose key is returned.
data.protectedSymmetricKeystringPass this in POST /api/wallet/payout body.
data.emailstringThe user's email — keep alongside the key in your records.

Errors

403 Forbidden — feature flag disabled
{ "success": false, "error": "WALLET_PAYOUT_API feature flag is not enabled for this user" }
404 Not Found — wallet missing
{ "success": false, "error": "Wallet not found" }
404 Not Found — wallet has no PIN-protected key
{ "success": false, "error": "Wallet has no protected symmetric key" }

This wallet has no usable PIN-protected key. Select a protected wallet by walletId or contact support for recovery of existing funds.

429 Too Many Requests
{ "success": false, "error": "Too many requests. Retry in 47 seconds" }

When to refetch

Almost never. Re-fetch only if:

  • You don't have the value cached yet for this wallet.
  • A previous payout returned Invalid PIN despite the user typing correctly — could mean the user reset their PIN and your cache is stale.