Retail accounts from account-service. Use Use on a row to set session
context for wallet and dev flows, or create a new account.
Transfer365 Mobile Registration
Register an E.164 phone on a bank account (account-service) and seed identity-service keys
for email/SMS notifications. Request SMS OTP, then confirm manually.
1. Resolve Asset GET /api/coin/resolve
2. Create / Get Crypto Account POST /api/wallet/crypto-accounts
Idempotent — safe to call again for the same (owner, asset); returns the existing account. Reference Account ID is any EXISTING account belonging to the target user (e.g. their fiat checking account — shown top-right as "D124f0ee…56f81c" style IDs) — the owner is resolved from it server-side. It is not the crypto account itself, which doesn't exist yet.
All accounts (fiat + one per crypto asset) belonging to the same owner as the Reference Account ID above — reflects the ADR-0933 one-account-per-asset model. Click a crypto row to fill Account ID for later steps.
3. Get / Create Deposit Address POST /api/wallet/deposits/address
List Deposit Addresses
GET /api/wallet/deposits/addresses
4. List Deposits GET /api/wallet/deposits
5. Create Withdrawal POST /api/wallet/withdrawals
Fireblocks vault withdrawals are sourced from this vault. Ledger ffffffff…ffff is the treasury ledger mirror.
On-chain address optional: set BACKOFFICE_TREASURY_DEPOSIT_ADDRESS in
backoffice env (same process as FIREBLOCKS_TREASURY_VAULT_ID).
Company aggregate custody (debit-normal). Withdrawal balance checks use this ledger account.
6. List Withdrawals GET /api/wallet/withdrawals
Withdrawal trace across services/databases
End-to-end evidence for one withdrawal: wallet record, Fireblocks history, transaction-service records, ledger entries, ledger balances, and account balances.
List Swaps GET /api/wallet/swaps
Create Swap POST /api/wallet/swaps
Get Swap Quote GET /api/wallet/swaps/quote
Binance Orders GET /api/swap/orders
Ledger Entries GET /api/ledger
Shows one row per (account, coin, reserve) that has ledger entries.
Leave filter empty to include every account (e.g. operational
00000000…).
Transaction service — transactions table
Direct read from the transaction-service Postgres database (same rows the service
persists). Use TRANSACTION_DATABASE_URL if your DB name differs from
transaction_service.
transactions.Project databases — tables and columns
Full schema read from configured backoffice DB connections. Column names are shown exactly as they exist in Postgres (no display formatting).
Table data preview for testing
Load sample rows from any table using its real identifier names. This helps verify migrations and data shape quickly during manual testing.
List Coins GET /api/coin/list
Get Coin GET /api/coin/get
Resolve Asset/Listing ID GET /api/coin/resolve
Leave Network empty to resolve the asset-level coins.id (FIAT or CRYPTO,
via ResolveAssetId). Set Network to resolve the rail-level
coin_listings.id for that (code, network) pair instead (CRYPTO-only, via
ResolveListingId).
Resolve by Slug GET /api/coin/resolve-by-slug
Admin: Save Coin (Create or Update) POST /api/coin/admin/update
Uses name + network as key: updates existing coin when found, creates a new one when not
found.
Optional fields (expand)
Fireblocks webhook log
Rows come from webhook_events in the
wallet-service database — every payload received on
POST /webhooks/fireblocks is stored for audit and replay (see
wallet-service persist_webhook_event).
User Management
Create passwordless users, list existing users, and manage MFA via user-service (port 50058). Production requires email/SMS OTP; dev can use the one-shot bypass.
Create Passwordless User
Production flow — request OTP from email, then confirm:
Dev one-shot (uses OTP bypass 000000 unless OTP field is set):
User Actions
Register Phone on User (SMS notifications)
Sets phone_number on user-service (not account-service). Request SMS OTP, then confirm manually.
Register PIN
Creates a transaction PIN via whatsapp-service using the bcr-bot JWT (WHATSAPP_CONSOLE_PRIVATE_KEY_PEM).
User List (from DB)
Account Management (Admin)
BackofficeAccountService (account-service :50052) — list, inspect, lock/suspend/reset PIN on accounts.
List Accounts
Account Actions
Reset PIN
Special Accounts
All Transactions (Backoffice)
BackofficeTransactionService — list, review, approve, cancel transactions. Review/Approve/Cancel require OTP (use "000000" in dev).
Account Transactions
Review Transaction
Approve Transaction
Cancel Transaction
Treasury & Operational
Treasury Transactions
Requires dev operator permissions. If you get "permission denied", go to and click "Grant All Permissions to Dev Operator".
Operational Transactions
BCR Institutions (config-service)
BCR participant banks for LBTR. config-service port 50060.
Create Institution
Get / Update Institution
Services Health
Functional readiness, not just reachability:
gRPC services answer grpc.health.v1.Health/Check (SERVING), databases run a
SELECT/grant probe, Kafka returns cluster metadata, Redis replies to PING.
Ledger is a Kafka worker (process + migration + Kafka + ledger-db). Auto-refreshes every 15 s.
gRPC Services
Databases
Infrastructure
Dev Operator Permissions
Grant all backoffice permissions to the dev operator (00000000-0000-0000-0000-000000000001).
Required to access Treasury, Operational, and other permission-gated endpoints.
Safe to run multiple times — auth-service is idempotent on grants.
Operational Flags
The 14 DIRECTIONAL kill switches of the ADR-0413 registry — money rails,
identity paths and the two notification channels. Changes are persisted to the
user_service DB (global_feature_flags table) and the
WHOLE 14-key snapshot is republished to Kafka topic
operational.flags with key global, which is what the
services actually gate on.
Auto-refreshes every 30 seconds.
loading…
LBTR / BCR Mock Producer
Injects messages into Kafka to simulate the BCR network. transaction-service consumes
lbtr.transfer.received and creates transactions.
Kafka broker: localhost:9093
· BCR REST mock:
http://127.0.0.1:8088/api/bcr
① Inject Incoming Transfer (BCR → us)
Publishes to lbtr.transfer.received. transaction-service creates a new
transaction and credits the account.
② Inject Status Update (BCR → us)
Publishes to
lbtr.status.incoming. Simulates BCR confirming or returning a transaction.
Quick Scenarios
③ Track Resulting Transaction & All State Changes
After injecting a transfer, see the created transaction, its status progression, ledger entries (debit/credit), and account balance changes. Auto-refreshes on status inject.
④ BCR Gateway — Outgoing Instructions Received
When lbtr-service processes an outgoing payment (e.g. a LBTR transfer our users send), it
calls the BCR Java gateway at
POST /api/lbtr/instrucciones. In dev, the backoffice mocks that gateway — all instructions
land here.
Transfer365 Lab
Stepper · saldos origem/destino · Externo↔eNorQuem envia o pacs.008 para a eNor (lado BCR).
▾ Aguardando confirmação na rede (mock)
Tesouraria (backoffice) e envios da carteira: aprovados com saldo em reserva e pacs.008 em
input/, sem resposta BCR final. Confirmar na rede arquiva o pacs.008; depois
aplique a resposta no stepper (aba Avançado ou Respostas BCR).
▸ Scenarios (one-click flows)
▸ PACS message tester
Dry run — valida XSD (mesma regra do
transfer365-service); nada é gravado em output/
▸ Advanced tools
Transfer365 Mock Producer
Injects messages into Kafka to simulate the Transfer365 network. transaction-service consumes
transfer365.received and creates transactions.
Kafka broker: localhost:9093
Inject Received Transfer (transfer365.received)
Simulates Transfer365 sending an incoming payment. transaction-service will process and credit the account.
Inject Status Update (transfer365.received.status)
Updates the transaction status in
transaction-service via Kafka.
transfer365-service will log an error for
mock-injected transactions because they bypass its internal file engine (redb has no record). This is
harmless — transaction-service correctly processes the status update.
Quick Scenarios
③ Track Resulting Transaction & All State Changes
After injecting a transfer above, see the transaction created, its current status, ledger entries, and account balance. Auto-refreshes when you inject a status update.
Simulador de transferências
Ferramenta de testes (não é o sistema real do cliente). Serve para simular pagamentos que chegam do banco ou que a eNor envia, e ver se tudo foi processado certo.
Caminho recomendado: aba Simulador (batch BCR) · testes manuais em Fazer um teste · checklist em Relatórios de teste.
Resumo: quantos pagamentos estão em cada etapa. Número grande = quantidade de arquivos. Use Atualizar no topo.
Últimos arquivos:
Simulador BCR unificado — cria transações reais (depósito + reserva + operacional),
aplica respostas BCR (pacs.002 / pacs.004) e gera relatório pass/fail para todos os cenários da matriz.
Pré-requisitos: stack local (transfer365-service, Kafka, transaction/ledger-service), seed dev,
T365_APP_DIR igual no console e no serviço.
Ledger global (diagnóstico avançado)
Não é saldo da conta. Conta operacional global (00000000-…) é débito-normal:
saldo bruto negativo = fundos já implantados nas contas (esperado após top-ups e liquidações).
Reserva espelho acompanha o depósito reservado da conta origem.
Depósito = account_balances (igual ao header/Lab). Disponível + reservado = total.
Configuração
Progresso
Pronto
Plano de cobertura Transfer365 — lista todos os cenários que devem ser executados para validar o simulador (inbound, outbound, BCR, fila pendente, regressões). Marque o status de cada execução e exporte o checklist (JSON, CSV, Markdown ou PDF). O Transfer365 Lab continua a gravar a sessão com XML; use a aba Simulador para batch automático ou marque manualmente aqui.
Receber (banco → eNor)
Crédito inbound (pacs.008 simulado).
Conta eNor que será creditada.
Enviar (eNor → destino)
Obrigatório: dry run lib-transfer-validate antes do envio
real.
Conta eNor que envia (reserva saldo).
Ainda não há mensagens. Rode um dos testes acima.
Pedido e resposta
Situação: aguardando teste
XML manual na aba .
output/ (XSD);
outbound simula resposta do banco (pacs.002/004). Envio eNor (initiate/confirm) só na
aba Fazer um teste com lib-transfer-validate.
Inbound — banco → eNor
Passos 1→2→3 · validação XSD (não é lib-transfer-validate).
Escolha o tipo e preencha os campos; depois gere o XML.
Outbound — resposta do banco
Passos 1→2→3 · após envio eNor na aba Fazer um teste.
Resposta do banco sem colar XML (cenário + MsgId).
Pedido e resposta
Situação: aguardando
Configure o que o BCR devolve ao eNor: pacs.002 (grupo ou liquidação), pacs.004 (devolução) ou atalho
Kafka.
Os XML gerados vão para output/. No fluxo tesouraria/carteira pendente, use
Avançado
(confirmar na rede → stepper abaixo da fila).
Escolha o formato ISO ou um preset nomeado.
Para backend / API externa: o simulador T365 no console expõe os mesmos fluxos via
HTTP.
Em produção a sua API chama transaction-service (gRPC) com sessão real; em
dev use estas rotas do console
(cargo run -p console → localhost:8088).
1 · Catálogo de bancos (bank_code)
O bank_code no validate/initiate é o id desta
lista — não o BIC.
curl -sS "$BASE/api/institutions" | jq .
2 · Dry run — lib-transfer-validate (obrigatório antes do envio)
Equivalente ao botão « Validar transferência » / Passo 1 do E2E. Substitua
ACCOUNT_UUID, conta destino e bank_code.
curl -sS -X POST "$BASE/api/discovery/transfer/validate" \
-H "Content-Type: application/json" \
-d '{
"account_id": "ACCOUNT_UUID",
"coin_id": "00000000-0000-0000-0000-000000000000",
"amount": 10000,
"counterpart_type": "external",
"name": "Beneficiary Test",
"customer_type": 1,
"account_type": 2,
"account_number": "1010000007",
"bank_code": 6,
"document": "041234567"
}' | jq .
3 · Initiate + confirm (envio real no ledger)
Mesmo body do validate + description e channel.
Guarde transaction_id da resposta do initiate.
curl -sS -X POST "$BASE/api/discovery/transfer/initiate" \
-H "Content-Type: application/json" \
-d '{
"account_id": "ACCOUNT_UUID",
"coin_id": "00000000-0000-0000-0000-000000000000",
"amount": 10000,
"counterpart_type": "external",
"name": "Beneficiary Test",
"customer_type": 1,
"account_type": 2,
"account_number": "1010000007",
"bank_code": 6,
"document": "041234567",
"description": "curl test outbound",
"channel": 1
}' | jq .
curl -sS -X POST "$BASE/api/discovery/transfer/confirm" \
-H "Content-Type: application/json" \
-d '{
"account_id": "ACCOUNT_UUID",
"transaction_id": "TX_UUID_FROM_INITIATE",
"pin": "000000"
}' | jq .
4 · File gateway — injetar XML (BCR → eNor)
Validação XSD: /api/simulator/t365/validate. Envio:
/api/simulator/t365/submit (grava em output/).
curl -sS -X POST "$BASE/api/simulator/t365/validate" \
-H "Content-Type: application/json" \
-d '{"xml":"<Document xmlns=\"urn:iso:std:iso:20022:tech:xsd:pacs.008.001.07\">...</Document>"}' | jq .
curl -sS -X POST "$BASE/api/simulator/t365/submit" \
-H "Content-Type: application/json" \
-d '{"xml":"...","processing_mode":"ift","dry_run":false}' | jq .
5 · Resposta do banco (pacs.002) após outbound T365
curl -sS -X POST "$BASE/api/simulator/t365/respond" \
-H "Content-Type: application/json" \
-d '{"scenario":"liquidate","original_msg_id":"MSGID_FROM_INPUT","amount":10000}' | jq .
Variável BASE (copiar/colar no terminal)
export BASE="http://localhost:8088"
Erro « Invalid bank code »? O bank_code não existe no catálogo ou
config-service está parado.
Confira com o passo 1. Conta origem: UUID em GET /api/accounts (aba Accounts) — o console
cria sessão dev automaticamente para discovery.
Só para técnicos: formulários detalhados para criar cada tipo de mensagem bancária. Se não tiver certeza, use a aba Fazer um teste.
Aguardando confirmação na rede (mock)
Transferências outgoing aprovadas com saldo em reserva e pacs.008 em
input/, mas ainda sem resposta BCR final. Passo 1: Confirmar na rede
(arquiva o pacs.008). Passo 2: escolha a resposta no stepper abaixo (liquidação, RJCT, ACCP, etc.)
e
Aplicar resposta.
Lista de arquivos que o sistema guardou durante os testes. Pode clicar para ver o conteúdo ou apagar pastas antigas.
Lista de arquivos
Registo do sistema (técnicos)
Últimas linhas do diário interno — útil se algo falhou e a equipa técnica pedir detalhe.
Passos do pagamento
Mostra, passo a passo, se o dinheiro foi creditado e confirmado. Atualiza sozinha após um teste.
—
Detalhe da conta e do saldo
Depois de simular um pagamento recebido, informe o número da conta (ou deixe preencher sozinho) e aperte « Ver detalhes » para ver transação e saldo.
BCR Gateway Mock
This backoffice replaces the external Java lbtr-gateway app.
lbtr-service calls POST /api/bcr/lbtr/instrucciones here when processing
outgoing payments.
Click ↓ JSON on any row to download the exact payload sent to the gateway.
BCR Test Environment Reference (from docs/BCR_CONTEXT.md — for lbtr-gateway Java app, not this mock)
SOAP Endpoints (test)
| Instrucciones | lbtrdes.bcr.gob.sv/lbtr-ws/InstruccionServiceSoapHttpPort |
| Lote | lbtrdes.bcr.gob.sv/lbtr-ws/LoteServiceSoapHttpPort |
| SPM (T365) | lbtrdes.bcr.gob.sv/lbtr-ws/ServicioPagoServiceSoapHttpPort |
| Server IP | 192.168.11.56 |
| Notif server | notificacioneslbtrdes.bcr.gob.sv (192.168.11.69) |
⚠ BCR internal network only — VPN / dedicated line required
WS-Security & Certificates
| Protocol | SOAP/WSDL Document/Wrapped |
| Security | OASIS WS-Security V1.2 · X.509 |
| Policy | Wssp1.2-2007-Wss1.1-X509-Basic256Sha256 |
| Keystore | lbtrcliente.jks (pw: lbtrcliente) |
| Truststore | lbtrcliente_truststore.jks |
| Key alias | lbtrcliente |
TraceableReference format (25 chars)
YYYYMMDD{entity:03}{correlative:05}{internal_ref:09}
Entity 089 = our participant. Date must
match today — BCR rejects mismatches.
Auto-generated in LBTR mock above. Implemented in
lib-bcr::lbtr::types::TraceableReference.
Full specs → docs/BCR_CONTEXT.md
in repo · Also see dev/console/TEAM_REVIEW_BACKLOG.md §8
Create Internal Transfer
Transfer between two eNor accounts via
transaction-service gRPC — InitiateTransfer → ConfirmTransfer.
The recipient is resolved by transfer key (phone, email, or account UUID) via the
identity/keys service.
External Transfer (Outbound)
Send from an eNor account to an external bank via
InitiateTransfer → ConfirmTransfer. Confirm triggers ledger debit → outbox →
Transfer365/LBTR routing.
Quick Setup — Transfer365 ready
Creates a passwordless user, verifies email, opens a CHECKING account (SV) — and optionally
a SAVINGS account to test multiple account_type values.
Funds USD (00000000-0000-0000-0000-000000000000), runs KYC, and pre-fills
Transfer365 / External Transfer fields. Leave email blank to auto-generate a unique address.
Setup Parameters
WhatsApp / BCR onboarding (E2E)
Stepped wizard aligned with
bcr-bot onboarding: KYC → Accept contract → Create PIN. Step 4 uses
AcceptContract (creates the bank account). Uses whatsapp-service gRPC via console
JWT. See docs/adr/ADR-0349.md.
Step 1: Session pending
Issue API token and read
has_session, has_auth, kyc_done, account_exists,
has_pin.
Step 2: Login OTP pending
Same as bot Path A/B:
CreateVerification(otp) with context_id = login:{phone}. Enter the SMS OTP below.
Step 3: KYC (Sumsub) pending
Runs when
has_auth && !kyc_done. Bot calls StartKYC then polls
CheckKycStatus.
Step 4: Contract signing pending
Accept (ADR-0096):
CreateVerification(contract_sign) → compliance web → user signs → account created. No
separate “Accept contract” button (legacy removed). Refuse below skips the web page (dev
shortcut).
Step 5: Create PIN pending
Bot uses
CreateVerification(pin_creation) with pin_creation:onboarding:{phone}, then
compliance / VerifyDevice.
Advanced: raw verification
Test hygiene (RUN_MODE=dev on whatsapp-service)
{}
Signup Web
Join eNor Securities in a few simple steps.
Create account
Enter your email to receive a verification code.
Choose a password
Create a strong password for your account.
Almost there
Enter your name and accept the terms to finish.
Account created!
Complete onboarding below (KYC + PIN), same as the web dashboard.
Ferramentas de desenvolvedor
Crédito de saldo (dev): informe
account_id ou telefone.
Swap / Transfer365 via customer-web
(/api/web/*) — requer sessão ativa acima.
Swap
Transfer365
Counterpart (external bank recipient)
{}
Fund Treasury Deposit Account
Credits the backoffice deposit-account ledger mirror
(ffffffff-ffff-ffff-ffff-ffffffffffff) via Kafka
ledger.transactions. Same balance shown at
ops-web /admin/banking/deposit-account. Dev-only; requires
ledger-service and account-service running.
Current balance
Add Balance (AccountTestService)
Directly credit an account without going through the full transfer flow. Dev-only service, not available in production.
After adding balance
Verify the balance was credited in the account service DB:
QR Code Testing (QrCodeService on transaction-service)
Generate QR
Get QR Details
Scan / Initiate Payment
Outbox Health
Shows pending entries in each service's
outbox table — a non-zero count means Kafka is behind or blocked.
Scheduled Payments (TransactionService)
KYC Management (UserService)
Get and set KYC levels for users. Requires a valid user session (user_id + auth-service running).
Get KYC Level
Finish KYC (Mark Verified — operator)
Operator verification through the authorized
BackofficeKyc MarkVerified RPC (RBAC KYC_APPROVE + console TOTP). The old
FinishKyc path is mTLS-locked to whatsapp-service and could only 403 from here.
Permission Profiles (BackofficePermissionService on auth-service)
Create Profile
Assign / Remove Profile from User
KYC Compliance (BackofficeKycService) — paridade com ops-web
/backoffice/kyc/*. Lookup por user_id ou account_id.
Atalhos de onboarding continuam em User Service → KYC.
Account
Preenchido a partir da sessão ativa quando disponível; o Dev account acima também preenche este campo. As ações de compliance abaixo usam este user_id.
Mark Verified (manual)
Operator verification without Sumsub — same as ops-web mark-verified. Use when status is not yet verified.
Change KYC Level
After verification — adjust tier only (does not change status). Nível 4 = EDD (Enhanced Due Diligence) — destrava os limites custom abaixo.
Release Limit Block
Only when user is limit-blocked.
Custom Limits (USD) — EDD
Requer KYC nível 4 (EDD) — o servidor rejeita se o user não estiver no nível 4. É o envelope que destrava as chaves money no gateway.
Declared Financial
Transactional Override
Compliance History
Contract Management (BackofficeComplianceService on compliance-service)
Upload Contract PDF
Select a PDF file — it will be base64 encoded and uploaded.
Activate Contract Version
Sets this contract as the current active version users must accept.
🔗 Fireblocks Webhook Simulator
Sends a properly HMAC-signed Fireblocks
webhook to wallet-service. Set FIREBLOCKS_WEBHOOK_SECRET env var in both services to match.
🔍 Global UUID Search
Searches every key table across wallet, transaction, account, ledger, and swap DBs simultaneously.
🕵️ Multi-Service Tracer
One UUID — full picture across transactions, ledger, deposits, withdrawals, accounts, and swaps.
🔐 Session Manager
🛡 Permission Debugger
Check if a user is authorized for a specific permission.
⚖️ Balance Reconciliation
Compares account-service reported balances vs. sum of ledger entries for an account.
📦 Migration Status
Shows applied migrations per service DB.
💸 Internal Transfer Flow
Preview → Initiate → Confirm a transfer on behalf of any account. Server mints a dev session automatically.
Notification overrides (optional)
When empty, the service uses the account owner's profile data.
📡 Kafka Activity Monitor
Shows the 10 most recently updated rows in each service's core tables — a proxy for live Kafka activity.
📜 Service Log Viewer
Tails the last N lines from
/tmp/<service>.log
Criar API key (mint)
O par Ed25519 é gerado no seu browser. A seed privada
nunca sai do browser — é mostrada uma única vez no fim.
Usa a conta ativa (botão USE na aba de contas)
(WhatsApp POST /api/whatsapp/api-keys · CreateApiKey).
Staff nunca mint.
A PoP (proof-of-possession) é assinada sobre o account_id (UUID)
da conta ativa, resolvido no servidor a partir do account_number.
Limites de valor por chave. Todos os valores são em USD.
Preencha por-tx e/ou 24h (não existe semana/mês/ano).
asset vazio = qualquer (aplica ao movimento inteiro).
Os tetos são aplicados após a criação da chave — se um teto
falhar, a chave continua criada (o resultado por linha é mostrado abaixo).
Keys da conta
Só metadados — nunca secretos. GET /api/whatsapp/api-keys ·
ListApiKeys. Usa a conta ativa (botão
USE na aba de contas).
API Management (backoffice)
Painel do OPERADOR — dirige a borda REST do backoffice-service
(/backoffice/gateway/*). Leituras não pedem TOTP; mutações
pedem o TOTP do operador e passam pelo fluxo de quatro-olhos
(iniciador ≠ revisor ≠ aprovador). Um só operador não se auto-aprova.
Aprovações pendentes (quatro-olhos)
GET /backoffice/gateway/config-changes?status=open. Aprovar/
rejeitar pede TOTP; o aprovador precisa ser distinto do iniciador/revisor.
Minhas chaves — gerir
Usa a conta ativa (botão USE na aba de contas). Escolha uma chave para editar config, tetos, rodar (rotate), revogar ou ver uso.