Spec — trustless_work_dart + trustless_work_flutter_storage¶
Fecha: 2026-04-15 Autor: Andrés Peña (HabitaNexus / Dojo Coding) Estado: Draft inicial post-brainstorming Versión: 1.0
1. Contexto¶
HabitaNexus es un marketplace de alquiler long-term en Costa Rica construido en Flutter. El escrow digital del depósito de garantía es central a la propuesta de valor: evita estafas, supera el límite de SINPE Móvil (¢100k/día), y habilita custodia no-bancaria. La startup está clasificada 🔴 URGENTE en el análisis de estructura corporativa: no puede operar comercialmente sin entity legal formal + SDK técnico funcional.
El SDK de Trustless Work existe solo en React/TypeScript (@trustless-work/escrow). No hay equivalente Dart/Flutter. Sin él, no hay forma limpia de integrar escrows desde la app móvil de HabitaNexus.
Este spec documenta el diseño del spike que porta el SDK a Dart, vive inicialmente dentro del monorepo, y eventualmente se extrae como paquete OSS mantenido conjuntamente con Trustless Work bajo Dojo Coding como incubador.
2. Goals¶
- Portar el SDK de Trustless Work al ecosistema Dart con paridad a nivel de API gateway y naming de entidades, no de surface SDK.
- Mantener el core Dart puro (reusable desde Flutter mobile, Flutter Web, Jaspr, Dart server, CLI).
- Habilitar wallet embebida transparente al usuario final (HabitaNexus abstrae la blockchain).
- Preparar el camino para publicación en pub.dev bajo verified publisher de Trustless Work.
- Desbloquear integración de escrows en
apps/mobilede HabitaNexus en el menor tiempo posible.
3. Non-Goals¶
- No ser un cliente Soroban directo. El SDK habla al gateway de TW, no a Soroban RPC ni Horizon (excepto para firma XDR local vía
stellar_flutter_sdk). - No replicar 1:1 el surface del SDK React. Los hooks React no tienen análogo idiomático en Dart; se expone funciones puras
Future<T>. - No gestionar fondos fiat. El SDK NO se encarga de on-ramp ni off-ramp colones↔USDC (ver §9).
- No convertir a HabitaNexus en intermediario financiero. La custodia vive on-chain; la app es plataforma de coordinación.
- No incluir disputas, multi-release, indexer queries, o update de escrows en v0.1 (ver roadmap §11).
- No incluir una UI de wallet visual. La wallet es embebida y transparente.
4. Decisiones arquitectónicas¶
| Decisión | Elección | Razonamiento |
|---|---|---|
| Alcance del spike | Esqueleto en packages/, sin publicar a pub.dev aún |
Time-flexible; validar arquitectura antes de commitment con TW |
| Capa de integración | Cliente HTTP 1:1 del API gateway de TW | No reimplementar Soroban; reusar abstracción ya auditada |
| Ownership del repo | packages/ → github.com/DojoCodingLabs/trustless-work-dart → pub.dev bajo Trustless Work verified publisher |
Iteración local, extracción cuando madura, publicación bajo identidad correcta |
| Naming del paquete core | trustless_work_dart |
Sufijo _dart desambigua para devs que lo importan sin contexto |
| Naming del hermano | trustless_work_flutter_storage |
Aisla deps Flutter del core Dart puro |
| Arquitectura del signer | TransactionSigner interface + KeyPairSigner + CallbackSigner en el core; SecureStorageKeyPairSigner en el hermano |
Core reusable desde cualquier contexto Dart |
| Alcance funcional v0.1 | initializeEscrow + fundEscrow + getEscrow + releaseFunds + sendTransaction helper |
Cubre ciclo completo del caso de uso HabitaNexus (single-release) |
| Generación de tipos | Hand-written con freezed + json_serializable |
TW no publica OpenAPI bundle unificado; fragments por endpoint no viables para codegen |
| Licencia | MIT | Matcheo con declaración del SDK React + convención pub.dev |
| Reactividad | Future<T> puro + Stream<EscrowEvent> @experimental (polling) |
HabitaNexus urgente; shape del API público estable al reemplazar implementación en v0.2 |
5. Arquitectura¶
5.1 Diagrama de capas¶
┌─────────────────────────────────────────────────────────────┐
│ API pública: TrustlessWorkClient │
│ initializeEscrow, fundEscrow, getEscrow, releaseFunds, │
│ escrowEvents (Stream, @experimental) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Endpoints (operaciones por dominio) │
│ SingleReleaseDeployer, SingleReleaseOperations, │
│ TransactionHelper, EscrowQueries │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Transversales │
│ HttpClient (dio/http), TransactionSigner abstraction, │
│ TrustlessWorkError sealed class, Result<T, E>, │
│ EscrowEvent sealed class (7 variantes) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Modelos (freezed + json_serializable, hand-written) │
│ Escrow, Milestone, Role, Trustline, Flags, payloads/ │
└─────────────────────────────────────────────────────────────┘
5.2 Estructura de directorios — packages/trustless_work_dart/¶
packages/trustless_work_dart/
├── lib/
│ ├── src/
│ │ ├── client/
│ │ │ ├── trustless_work_client.dart # API pública principal
│ │ │ ├── trustless_work_config.dart # baseUrl, apiKey, network
│ │ │ └── http_client.dart # wrapper sobre dio o http
│ │ ├── signer/
│ │ │ ├── transaction_signer.dart # abstract class
│ │ │ ├── keypair_signer.dart # wallet embebida con stellar_flutter_sdk
│ │ │ └── callback_signer.dart # adapter genérico
│ │ ├── endpoints/
│ │ │ ├── deployer.dart # POST /deployer/single-release
│ │ │ ├── single_release_operations.dart # fund, release
│ │ │ ├── helpers.dart # POST /helper/send-transaction
│ │ │ └── queries.dart # getEscrow — endpoint exacto pendiente de confirmar con TW (ver §13.5)
│ │ ├── models/
│ │ │ ├── escrow.dart # freezed
│ │ │ ├── milestone.dart
│ │ │ ├── role.dart
│ │ │ ├── trustline.dart
│ │ │ ├── flags.dart
│ │ │ └── payloads/
│ │ │ ├── single_release_contract.dart
│ │ │ ├── fund_escrow_payload.dart
│ │ │ └── release_funds_payload.dart
│ │ ├── events/
│ │ │ ├── escrow_event.dart # sealed class, 7 variantes
│ │ │ └── polling_event_stream.dart # @experimental
│ │ └── errors/
│ │ ├── trustless_work_error.dart # sealed class
│ │ └── result.dart # Result<T, E> idiomático
│ └── trustless_work_dart.dart # barrel export
├── test/
│ ├── client_test.dart
│ ├── signer/
│ ├── endpoints/
│ └── integration/
│ └── testnet_e2e_test.dart # end-to-end real testnet
├── example/
│ └── simple_escrow.dart # crear → fondear → consultar → liberar
├── pubspec.yaml
├── analysis_options.yaml
├── README.md # incluye "Qué ES y qué NO ES"
├── CHANGELOG.md
└── LICENSE # MIT
5.3 Estructura de directorios — packages/trustless_work_flutter_storage/¶
packages/trustless_work_flutter_storage/
├── lib/
│ ├── src/
│ │ └── secure_storage_keypair_signer.dart # wallet embebida persistente
│ └── trustless_work_flutter_storage.dart # barrel export
├── test/
├── example/
├── pubspec.yaml # deps: trustless_work_dart + flutter_secure_storage
├── README.md
├── CHANGELOG.md
└── LICENSE # MIT
6. Módulos detallados¶
6.1 TrustlessWorkClient¶
API pública principal. Puntos de entrada para todos los flujos.
class TrustlessWorkClient {
TrustlessWorkClient({
required TrustlessWorkConfig config,
required TransactionSigner signer,
HttpClient? httpClient,
});
Future<Escrow> initializeEscrow(SingleReleaseContract contract);
Future<Escrow> fundEscrow(FundEscrowPayload payload);
Future<Escrow> getEscrow(String contractId);
Future<Escrow> releaseFunds(ReleaseFundsPayload payload);
@experimental
Stream<EscrowEvent> escrowEvents(String contractId, {Duration pollInterval = const Duration(seconds: 15)});
}
6.2 TransactionSigner¶
Abstracción del signing. Delega firma a implementación concreta.
abstract class TransactionSigner {
Future<String> signXdr(String unsignedXdr);
String get publicKey;
}
class KeyPairSigner implements TransactionSigner {
KeyPairSigner(this._keyPair, {required Network network});
final KeyPair _keyPair;
final Network _network;
// implementación con stellar_flutter_sdk
}
class CallbackSigner implements TransactionSigner {
CallbackSigner({
required this.publicKey,
required Future<String> Function(String unsignedXdr) signFn,
});
final Future<String> Function(String) _signFn;
// delega todo al callback
}
6.3 EscrowEvent (sealed class)¶
sealed class EscrowEvent {
String get contractId;
DateTime get observedAt;
}
class Initialized extends EscrowEvent { /* ... */ }
class Funded extends EscrowEvent { /* ... */ }
class MilestoneStatusChanged extends EscrowEvent { /* ... */ }
class MilestoneApproved extends EscrowEvent { /* ... */ }
class Released extends EscrowEvent { /* ... */ }
class DisputeStarted extends EscrowEvent { /* ... */ }
class DisputeResolved extends EscrowEvent { /* ... */ }
v0.1: MilestoneStatusChanged, MilestoneApproved, DisputeStarted, DisputeResolved quedan definidos pero no se emiten (milestones y disputas diferidos). API público estable desde el inicio.
6.4 TrustlessWorkError (sealed class)¶
sealed class TrustlessWorkError implements Exception {
String get message;
}
class BadRequest extends TrustlessWorkError { /* 400 */ }
class Unauthorized extends TrustlessWorkError { /* 401 */ }
class TooManyRequests extends TrustlessWorkError { /* 429 */ }
class ServerError extends TrustlessWorkError { /* 500 con lista de possible errors */ }
class NetworkError extends TrustlessWorkError { /* timeouts, DNS */ }
class SigningError extends TrustlessWorkError { /* error del TransactionSigner */ }
6.5 SecureStorageKeyPairSigner (en el hermano)¶
class SecureStorageKeyPairSigner implements TransactionSigner {
SecureStorageKeyPairSigner({
required FlutterSecureStorage storage,
required String storageKey,
required Network network,
});
static Future<SecureStorageKeyPairSigner> generate({ /* ... */ });
static Future<SecureStorageKeyPairSigner?> load({ /* ... */ });
Future<void> clear();
// Encapsula generación, persistencia, recovery
}
7. Data flow — ejemplo initializeEscrow + fundEscrow¶
HabitaNexus app SDK Dart TW gateway Stellar
──────────────────── ────────── ────────── ───────
initializeEscrow(contract) ──▶ POST /deployer ──▶
(valida +
construye XDR)
{transactionXdr} ◀──
signer.signXdr(xdr)
──(signing local)──▶
{signedXdr}
POST /helper/send ──▶
(submits to Soroban) ──▶
(deploy)
contractId
◀── response ────────────
◀── Escrow ────────
◀── Escrow ────────────
fundEscrow(payload) ──▶ POST /escrow/single-release/fund ──▶
(valida +
construye XDR)
{transactionXdr} ◀──
signer.signXdr(xdr)
POST /helper/send ──▶
(submits transfer) ──▶
(USDC → escrow)
◀── confirmed ───────────
◀── Escrow ────────
◀── Escrow funded ──────
8. Error handling¶
- Cada endpoint devuelve
Future<Escrow>en happy path, lanzaTrustlessWorkError(sealed class) en fallo. - El consumidor puede hacer pattern matching con
switchexhaustivo. Result<T, E>se expone como alternativa opcional para consumidores que prefieran estilo funcional.NetworkErrorincluye retry logic opcional configurable (exponential backoff con jitter).ServerError.500incluye lista de "possible errors" documentada por TW para que consumidor muestre mensaje user-friendly.
9. Dependencias externas al SDK¶
9.1 On-ramp colones → USDC¶
Patrón: WebView embebido (flutter_inappwebview o webview_flutter). No librería Dart. Ver docs/sop-escrow-deposito-garantia.md.
Candidato principal: Onramper. Pendiente de validar via su API que soporte USDC-Stellar + método de pago CR adecuado (ideal SINPE Móvil).
9.2 Kindo (pagos mensuales SINPE)¶
Vive en apps/backend/ (NestJS), no en el SDK Dart. Integración B2B bajo contrato con Prosoft CR. HabitaNexus recibe webhooks de confirmación y actualiza el expediente digital. NO custodia fondos fiat.
9.3 Fee sponsorship¶
Stellar fees son ~$0.0000012/op. Abierto: ¿TW absorbe XLM fees vía FeeBumpTransaction? ¿Espera que la platform (HabitaNexus) sponsoree? Preguntar a Alberto Chaves. v0.1 no incluye fee-bump; potencialmente v0.2.
10. Testing¶
10.1 Unit tests (HTTP mocks)¶
- HTTP client:
package:http(standard library Dart). Consistente constellar_flutter_sdk, cero deps adicionales. - Mocks vía
package:http/testing.dart(MockClient). - Cada endpoint tiene tests de: happy path, bad request, unauthorized, server error, network timeout.
- Signers tests: firma correcta de XDR de testnet, error si XDR malformado.
EscrowEventpolling: emite eventos correctos cuando cambia el estado simulado.
10.2 Integration tests (testnet real)¶
Archivo test/integration/testnet_e2e_test.dart:
1. Genera KeyPair con stellar_flutter_sdk → fondea vía Friendbot.
2. Crea escrow vía initializeEscrow con testnet TW gateway.
3. Fondea escrow con USDC testnet.
4. Consulta getEscrow y valida estado.
5. Ejecuta releaseFunds.
6. Valida que el balance regresó al receiver.
Gated por --tags=integration; no corre en CI default, sí en CI scheduled (diario).
10.3 Static analysis¶
dart analyzesin warnings.dart format --set-exit-if-changed .pasa.pana(pub score) baseline: 130/160 al cierre de HAB-57 (spike). Brechas conocidas: faltaexample/(0/10) y dependencias con constraints debajo del último stable (freezed_annotation ^2.4.4vs 3.0.0,stellar_flutter_sdk ^1.9.0vs 2.0.0 — 0/10), y algunos lints/formato menores surgidos de la versión más nueva del formatter (40/50). Se cierran antes de publish en v0.2.
11. Roadmap¶
v0.1 (spike):
- Alcance funcional descrito en §4.
- Consumible desde apps/mobile vía path dependency.
- Integration test testnet pasa.
v0.2:
- Reemplazar PollingEventStream por implementación híbrida: Horizon SSE (effects classic) + Soroban getEvents con cursor (contract events). API público estable.
- updateEscrow.
- Milestones: changeMilestoneStatus, approveMilestone.
- Disputas: startDispute, resolveDispute.
v0.3:
- Multi-release escrows.
- Indexer queries: getEscrowsFromIndexerByRole, getEscrowsFromIndexerBySigner, getEscrowFromIndexerByContractIds.
- Multiple balance queries: getMultipleEscrowBalances.
v0.4+:
- Migrar tipos a codegen desde OpenAPI bundle (si TW publica).
- Paquetes opcionales: trustless_work_riverpod, trustless_work_bloc.
- Fee-bump transaction support si TW no lo absorbe upstream.
12. Gobernanza¶
- Licencia: MIT (matcheo con declaración del SDK React).
- Incubación: repo inicial bajo
github.com/DojoCodingLabs/trustless-work-dart(Dojo Coding como incubador OSS). - Publicación futura: pub.dev bajo verified publisher de Trustless Work cuando el paquete esté estable y TW acepte co-mantenimiento.
- HabitaNexus es la primera consumer y justificación de negocio, no el owner del paquete.
13. Dependencias bilaterales con Trustless Work¶
Ya pedido a Alberto Chaves vía WhatsApp (2026-04-15):
- Formalizar
LICENSEfile con MIT enTrustless-Work/react-library-trustless-work. El README lo declara, pero no hay file canónico. - Bundle OpenAPI unificado vía
@nestjs/swagger/api-json. Nice-to-have para habilitar codegen en v0.4+.
Pendiente de preguntar en call de alineación:
- Verified publisher en pub.dev bajo
Trustless Work. Aceptación formal vía Slack/email/contrato de colaboración antes de mover el repo agithub.com/Trustless-Work/o publicar. - Fee sponsorship: ¿TW absorbe XLM fees vía
FeeBumpTransaction? ¿Espera que la platform sponsoree? - Endpoint
getEscrow: el research mostró endpoints del indexer (POST /escrows/contracts,GET /escrows) pero no unGETdirecto porcontractIden el gateway principal. Confirmar cuál es la ruta idiomática para "dame el estado actual de un escrow por contractId" y si requiere el indexer como dependencia separada.
14. Verificación de completitud del spec¶
- Contexto y urgencia claros
- Goals y Non-Goals explícitos
- Arquitectura descrita con diagramas
- Módulos con signatures de API
- Data flow ejemplificado
- Error handling definido
- Dependencias externas identificadas (y cuáles NO resuelve el SDK)
- Testing strategy
- Roadmap incremental
- Gobernanza y licencia
15. Archivos a crear después de aprobar el spec¶
packages/trustless_work_dart/con la estructura de §5.2.packages/trustless_work_flutter_storage/con la estructura de §5.3.- Consumir ambos desde
apps/mobile/pubspec.yamlcomo path deps. - Integration test testnet pasando.
16. Referencias¶
escrow-deposito-garantia.md— SOP de negocio con diagrama Mermaid.flujo-arrendamiento.md— flujo general del arrendamiento.- Trustless Work React SDK — referencia de API.
- Trustless Work docs — API reference con OpenAPI fragments.
stellar_flutter_sdk— XDR signing + network primitives.~/Escritorio/lapc506-personal-dogfood/structure-decision.md— urgencia legal.~/.claude/plans/resilient-marinating-token.md— plan del spike aprobado.