Wallet Declaration (ACAM)
Central Bank of Brazil reporting of international virtual asset transfers, and the wallet holder declaration it requires on deposits.
Mandatory from November 15, 2026
Brazilian regulation requires every international transfer of virtual assets to be reported to
the Central Bank of Brazil (BCB). This report is called ACAM. From November 15, 2026, a
deposit that sends crypto to a wallet we cannot identify is refused unless it carries a
walletDeclaration. The requirement is being enabled account by account before that date; ask
support when it will be enabled on yours.
What changes
When a deposit delivers crypto on-chain, we must report who holds the destination wallet and in which country. A holder outside Brazil makes the transfer international, and an international transfer also needs a purpose code describing why the money is moving.
Most deposits need nothing new. You only send the declaration when the destination is an external wallet we have not seen before on your account.
| Destination | What you send |
|---|---|
| A Hodle wallet of your account or of your subaccount's holder | Nothing. We already know the holder. |
| An external wallet you already declared (same address and network) | Nothing. The saved declaration is reused. |
| A new external wallet whose holder is in Brazil | walletDeclaration without purposeCode. |
| A new external wallet whose holder is outside Brazil | walletDeclaration with purposeCode. |
Pix legs (BRL in, BRL out) never need a declaration or a purpose code. Payouts to a Pix key or a Pix QR code are domestic.
The walletDeclaration object
Send it in the body of POST /api/deposit/asset.
| Field | Type | Required | Description |
|---|---|---|---|
isSelfCustody | boolean | Yes | Must be true. The holder controls the wallet's private keys. Deliveries to custodial wallets (an exchange deposit address, for example) are not accepted. |
counterpartyName | string | Yes | Full name or legal name of the wallet holder, 1 to 100 characters. |
counterpartyCountry | string | Yes | ISO 3166-1 alpha-2 code, uppercase. A person's country of residence or a company's country of incorporation. BR makes the transfer domestic. |
reason | string | Yes | Why the money is sent to this wallet, in your own words, 3 to 500 characters. |
purposeCode | string | Cond. | Required when counterpartyCountry is not BR. Must be valid for the amount band (below). Omit it for BR. |
The declaration is stored per account, address and network. Later deposits to the same wallet reuse it, so you send it once. Sending it again replaces the saved one.
Purpose codes and amount bands
The accepted purpose codes depend on the operation's value in USD:
| Band | Value of the operation |
|---|---|
| Up to USD 50,000 | USD 50,000 or less |
| Above USD 50,000 | More than USD 50,000 |
For buying virtual assets, use:
| Purpose | Up to USD 50,000 | Above USD 50,000 |
|---|---|---|
| Purchase of virtual assets | 91992 | 12186 |
A code that does not belong to the band of the deposit is refused. For other purposes, ask support for the list of accepted codes.
Example
A BRLA deposit to an external wallet held by a company incorporated in the United States:
curl --request POST \
--url https://api.hodle.com.br/api/deposit/asset \
--header "Authorization: Bearer $API_KEY" \
--header "Content-Type: application/json" \
--data '{
"value": 50000,
"asset": "BRLA",
"network": "polygon",
"address": "0x4444444444444444444444444444444444444444",
"externalId": "order-3001",
"walletDeclaration": {
"isSelfCustody": true,
"counterpartyName": "Example Holdings LLC",
"counterpartyCountry": "US",
"reason": "Purchase of virtual assets for the company treasury",
"purposeCode": "91992"
}
}'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: 50000,
asset: 'BRLA',
network: 'polygon',
address: '0x4444444444444444444444444444444444444444',
externalId: 'order-3001',
walletDeclaration: {
isSelfCustody: true,
counterpartyName: 'Example Holdings LLC',
counterpartyCountry: 'US',
reason: 'Purchase of virtual assets for the company treasury',
purposeCode: '91992',
},
}),
})
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": 50000,
"asset": "BRLA",
"network": "polygon",
"address": "0x4444444444444444444444444444444444444444",
"externalId": "order-3001",
"walletDeclaration": {
"isSelfCustody": True,
"counterpartyName": "Example Holdings LLC",
"counterpartyCountry": "US",
"reason": "Purchase of virtual assets for the company treasury",
"purposeCode": "91992",
},
},
)
data = res.json()For a holder in Brazil, drop purposeCode and send "counterpartyCountry": "BR".
The response is the same as any other deposit. Nothing about the declaration is returned.
Errors
A missing or invalid declaration is refused with 400 and errorCode: "DEPOSIT_FAILED" before any
Pix code is created, so no charge is left behind:
error | Cause |
|---|---|
Informe a finalidade, o titular e o país e confirme a autocustódia da carteira externa. | New external wallet without walletDeclaration. |
País do titular inválido. | counterpartyCountry is not a valid country code. |
Selecione a finalidade da transferência internacional. | Holder outside Brazil without purposeCode. |
Finalidade inválida. | purposeCode is not an accepted code. |
Selecione uma finalidade válida para a faixa de valor desta transferência. | purposeCode does not belong to the amount band of this deposit. |
Fix the declaration and repeat the request with the same externalId.
Your responsibility
The declaration is reported to the Central Bank under your account. Collect it from your customer, or state it yourself when you are the holder, and keep it true. When the holder or the purpose of a saved wallet changes, send a new declaration.