Twin Documentation
  • Overview
  • Consumer
  • Developer
  • Operations
Arquitectura
Flujos Técnicos
Base de Datos
Local SetupConventions
powered by Zudoku
Developer

Conventions

Coding standards and team agreements for the Twin platform.

Language

ContextLanguageExample
Backend error messagesEnglishthrow Errors.badRequest('Token not found')
Frontend UI textSpanish"Token no encontrado"
Code (variables, comments)Englishconst walletBalance = ...

If the frontend needs to display a backend error, translate it on the frontend side (e.g., via a translation map). Never translate throw messages in backend code.

Amount Conversion

Never use parseFloat * Math.pow(10, decimals) for converting token amounts. This causes overflow and scientific notation for large numbers.

Use string-based conversion instead:

Code
// Correct -- string manipulation function formatAmountToBaseUnits(amount: string, decimals: number): string { const [integer, decimal = ''] = amount.split('.'); const padded = decimal.padEnd(decimals, '0').slice(0, decimals); return (integer + padded).replace(/^0+/, '') || '0'; } // Wrong -- floating point const baseUnits = parseFloat(amount) * Math.pow(10, decimals); // OVERFLOW

Backend schemas should validate base-unit amounts with pattern: '^[0-9]+$' (digits only).

Route Registration Order

Fastify matches routes in registration order. Static routes must be registered before parameterized routes:

Code
// Correct app.get('/resource/history', historyHandler); app.get('/resource/active', activeHandler); app.get('/resource/:id', getByIdHandler); // Wrong -- /:id captures "history" and "active" as params app.get('/resource/:id', getByIdHandler); app.get('/resource/history', historyHandler);

CRUD vs Operations Paths

TypeSidebar SectionURL PatternPurpose
CRUDConfiguracion/wallets/<entity>Create, edit, delete wallets
OperationsOperaciones/operations/<entity>Execute actions, view data, transaction history

Exception: OpsToken CRUD is under /operations/tokens (tokens are reference entities, not wallets).

Route Permissions

When adding a new endpoint to the dashboard API, register it in 2 places:

  1. Route file -- The Fastify handler in packages/dashboard/src/routes/
  2. Permissions config -- packages/shared/src/config/routes-permissions.ts (HTTP method + allowed roles)

Without the permissions entry, the checkPermissions middleware returns 403 Forbidden.

Cron Job Registration

New cron jobs must be registered in 3 places:

  1. apps/cron-service/src/jobs/cron-keys.ts -- Enum key
  2. apps/cron-service/src/jobs/manager.ts -- Import + jobHandlers map entry
  3. packages/dashboard/src/cron-handlers.ts -- Handler for on-demand execution from dashboard UI

BitGo Coin Naming

Wallets operating with ERC20 tokens use the full coin identifier including the token suffix:

ContextCoin FormatExample
Token transfer (sendMany)basechain:tokenbaseeth:argt, polygon:argt
Contract call (sendContractCall)Base chain onlybaseeth, polygon
GET endpointsBase chain onlybaseeth, polygon

Use getBaseCoin() in bitgo-express.client.ts to strip the token suffix for GET requests.

Objection.js Gotcha: undefined vs null

Objection.js ignores undefined values in patch() calls. This is by design -- undefined means "don't change this field."

To explicitly clear a field, use null:

Code
// Wrong -- lastError is NOT cleared await CronJob.query().findById(id).patch({ lastError: undefined }); // Correct -- lastError is set to NULL in DB await CronJob.query().findById(id).patch({ lastError: null });

This caused the cron lastError persistence bug -- errors were never cleared because the success path patched with undefined.

Frontend Error Handling

Never use empty catch {} blocks -- they silently discard error messages from the backend.

Code
// Wrong -- error message is lost try { await api.post('/endpoint', data); } catch {} // Correct -- extract and display the backend message try { await api.post('/endpoint', data); } catch (err: any) { const message = err?.response?.data?.message || 'Error desconocido'; dispatch(showError(message)); }

Git Workflow

  • Feature branches off main (backend, dashboard, platform)
  • Bridge contracts use master branch
  • Pull requests require review before merge
  • Commit messages in English, concise, imperative mood
Last modified on May 12, 2026
Local Setup
On this page
  • Language
  • Amount Conversion
  • Route Registration Order
  • CRUD vs Operations Paths
  • Route Permissions
  • Cron Job Registration
  • BitGo Coin Naming
  • Objection.js Gotcha: undefined vs null
  • Frontend Error Handling
  • Git Workflow
TypeScript
TypeScript
TypeScript
TypeScript