DEV Community

Cardumen
Cardumen

Posted on

Tutorial: Midnight Wallet SDK - Guía Completa para Desarrolladores

Índice

Introducción
Instalación
Conceptos Clave
Crear Instancias de Billetera
Gestión de Direcciones
Trabajar con Transacciones
Conectar con DApps
Ejemplos Prácticos

Introducción
Midnight Wallet SDK es un conjunto de herramientas que permite integrar funcionalidad de billetera en aplicaciones blockchain. El SDK proporciona abstracciones de alto nivel para gestionar operaciones comunes como:

Derivación de claves - Generar claves criptográficas a partir de una semilla
Balance de tokens - Obtener el saldo de tokens desde datos de la cadena
Gestión de monedas - Administrar monedas y datos para pruebas de conocimiento cero
Conexión con indexadores - Sincronizar estado con servidores indexadores
Creación de transacciones - Preparar y enviar transferencias de tokens

Paquetes Principales
PaqueteDescripción@midnight-ntwrk/wallet-apiInterfaces de alto nivel para billeteras@midnight-ntwrk/walletImplementación de wallet-api con herramientas adicionales@midnight-ntwrk/zswapConstrucción de bloques de Zswap@midnight-ntwrk/wallet-sdk-hdSoporte para billeteras jerárquico-determinísticas (HD)@midnight-ntwrk/wallet-sdk-address-formatSoporte para formato de dirección Bech32m

Instalación
Requisitos Previos

Node.js instalado
Yarn o npm como gestor de paquetes

Instalación del SDK
Usa Yarn para instalar el paquete:
bashyarn add @midnight-ntwrk/wallet/
Reemplaza con la versión requerida según la matriz de compatibilidad de versiones.
⚠️ Importante: A partir de la versión 4.0.0 hay cambios significativos. Consulta la matriz de compatibilidad para asegurar que usas la versión correcta.
Información Crítica sobre v4.0.0

Cambio importante: Las claves secretas ya no se incluyen en el estado serializado de la billetera
Restauración: Si restauras una billetera desde una instantánea, debes proporcionar la semilla original
Seguridad: Asegúrate de gestionar datos sensibles de forma segura

Conceptos Clave
Semilla de Billetera (Wallet Seed)
La semilla es un valor aleatorio (usualmente 256 bits) que se usa para derivar todas las claves criptográficas de tu billetera. Es esencial para:

Recuperar una billetera
Generar direcciones determinísticas
Restaurar estado desde instantáneas

Billeteras HD (Jerárquico-Determinísticas)
El SDK utiliza derivación BIP-32 siguiendo la ruta:
m / 44' / 2400' / account' / role / index
Donde:

account - Número de cuenta (recomendación BIP-44)
role - Tipo de rol (3 para Zswap)
index - Índice específico (recomendación BIP-44)

Ciclo de Vida de la Billetera

  1. Crear instancia → 2. Iniciar sincronización → 3. Usar billetera → 4. Cerrar NetworkId (Identificador de Red) Define la red blockchain objetivo:

NetworkId.TestNet - Red de prueba
NetworkId.MainNet - Red principal
NetworkId.DevNet - Red de desarrollo

Crear Instancias de Billetera
Paso 1: Importar el Constructor
javascriptimport { WalletBuilder } from '@midnight-ntwrk/wallet';
import { NetworkId } from '@midnight-ntwrk/zswap';
Paso 2: Generar una Semilla
Para testing, puedes generar una semilla aleatoria:
javascriptimport { generateRandomSeed } from '@midnight-ntwrk/wallet-sdk-hd';

const semillaAleatoria = generateRandomSeed();
console.log('Semilla generada:', semillaAleatoria);
Para producción, usa derivación HD:
javascriptimport {
generateRandomSeed,
HDWallet,
Roles,
} from "@midnight-ntwrk/wallet-sdk-hd";

const semilla = generateRandomSeed();

function derivarSemillaDerivacion(semilla) {
const billetera = HDWallet.fromSeed(semilla);

if (billetera.type != "seedOk") {
throw new Error("Error inicializando HD Wallet");
}

const zswapKey = billetera.hdWallet
.selectAccount(0)
.selectRole(Roles.Zswap)
.deriveKeyAt(0);

if (zswapKey.type === "keyDerived") {
return zswapKey.key;
} else {
throw new Error("Error derivando clave");
}
}

const semillaFinal = derivarSemillaDerivacion(semilla);
console.log('Semilla derivada:', semillaFinal);
Paso 3: Construir la Instancia
Opción A: Desde una Semilla Específica
javascriptimport { WalletBuilder } from '@midnight-ntwrk/wallet';
import { NetworkId } from '@midnight-ntwrk/zswap';

const billetera = await WalletBuilder.build(
'https://indexer.testnet-02.midnight.network/api/v1/graphql', // URL del Indexador
'wss://indexer.testnet-02.midnight.network/api/v1/graphql/ws', // WebSocket del Indexador
'http://localhost:6300', // URL del Servidor de Pruebas
'https://rpc.testnet-02.midnight.network', // URL del Nodo
'0000000000000000000000000000000000000000000000000000000000000000', // Semilla
NetworkId.TestNet, // Red
'error' // Nivel de Log (opcional)
);
Opción B: Con Semilla Aleatoria (Testing)
javascriptimport { WalletBuilder } from '@midnight-ntwrk/wallet';
import { NetworkId } from '@midnight-ntwrk/zswap';
import { generateRandomSeed } from '@midnight-ntwrk/wallet-sdk-hd';

const semillaAleatoria = generateRandomSeed();

const billetera = await WalletBuilder.build(
'https://indexer.testnet-02.midnight.network/api/v1/graphql',
'wss://indexer.testnet-02.midnight.network/api/v1/graphql/ws',
'http://localhost:6300',
'https://rpc.testnet-02.midnight.network',
semillaAleatoria, // Semilla aleatoria
NetworkId.TestNet,
'error'
);
Paso 4: Iniciar la Sincronización
javascript// Inicia la sincronización con la cadena
billetera.start();

console.log('Billetera iniciada y sincronizando...');
Paso 5: Cerrar la Billetera
javascript// Cuando termines, cierra la billetera adecuadamente
await billetera.close();

console.log('Billetera cerrada y recursos liberados');

Instantáneas de Estado (State Snapshots)
Las instantáneas permiten guardar y restaurar el estado de una billetera rápidamente, ideal para extensiones de navegador o aplicaciones móviles.
Crear una Instantánea
javascriptconst estadoSerializado = await billetera.serialize();

console.log('Instantánea creada:', estadoSerializado);
Restaurar desde una Instantánea
⚠️ Importante: Debes proporcionar la semilla original para restaurar.
javascriptconst billetera_restaurada = await WalletBuilder.restore(
'https://indexer.testnet-02.midnight.network/api/v1/graphql',
'wss://indexer.testnet-02.midnight.network/api/v1/graphql/ws',
'http://localhost:6300',
'https://rpc.testnet-02.midnight.network',
'0000000000000000000000000000000000000000000000000000000000000000', // DEBES proporcionar la semilla
estadoSerializado,
'error'
);

billetera_restaurada.start();

Gestión de Direcciones
Formato Bech32m
Midnight utiliza direcciones en formato Bech32m con el prefijo personalizado mn_.
La estructura del prefijo es:
mn__
Ejemplos:

mn_shield-addr_test1... - Dirección de pago en TestNet
mn_shield-addr_test1... - Dirección escudada en TestNet (ESK para indexador)

Codificar una Dirección
javascriptimport {
ShieldedAddress,
ShieldedCoinPublicKey,
ShieldedEncryptionPublicKey,
MidnightBech32m,
} from "@midnight-ntwrk/wallet-sdk-address-format";
import { NetworkId } from "@midnight-ntwrk/zswap";
import { Buffer } from "buffer";

// Crear claves públicas
const claveCoinPublica = new ShieldedCoinPublicKey(
Buffer.from(
"064e092a80b33bee23404c46cfc48fec75a2356a9b01178dd6a62c29f5896f67",
"hex"
)
);

const claveEncriptacionPublica = new ShieldedEncryptionPublicKey(
Buffer.from(
"0300063c7753854aea18aa11f04d77b3c7eaa0918e4aa98d5eaf0704d8f4c2fc272899efbb8a71275f2a1aedd29f879021e0962b4730f9b47e1a",
"hex"
)
);

// Crear dirección
const direccion = new ShieldedAddress(claveCoinPublica, claveEncriptacionPublica);

// Codificar en Bech32m
const direccionCodificada = ShieldedAddress.codec
.encode(NetworkId.TestNet, direccion)
.asString();

console.log('Dirección codificada:', direccionCodificada);
// Salida: mn_shield-addr_test1qe8qj25qkva7ug6qf3rvl3y0a366ydt2nvq30rwk5ckznavfdansxqqx83m48p22agv25y0sf4mm83l25zgcuj4f34027pcymr6v9lp89zv7lwu2wyn472s6ahfflpusy8sfv268xrumgls62hqz4u
Decodificar una Dirección
javascriptconst direccionCodificada = "mn_shield-addr_test1qe8qj25qkva7ug6qf3rvl3y0a366ydt2nvq30rwk5ckznavfdansxqqx83m48p22agv25y0sf4mm83l25zgcuj4f34027pcymr6v9lp89zv7lwu2wyn472s6ahfflpusy8sfv268xrumgls62hqz4u";

// Parsear
const direccionParseada = MidnightBech32m.parse(direccionCodificada);

// Decodificar
const direccionDecodificada = ShieldedAddress.codec.decode(
NetworkId.TestNet,
direccionParseada
);

console.log('Clave pública de coin:', direccionDecodificada.coinPublicKeyString());
console.log('Clave pública de encriptación:', direccionDecodificada.encryptionPublicKeyString());

Trabajar con Transacciones
Ciclo de Vida de una Transacción

  1. Preparar transacción sin prueba ↓
  2. Calcular pruebas de conocimiento cero ↓
  3. Enviar transacción comprobada Acceder al Estado de la Billetera El estado se proporciona a través de un observable de RxJS: javascriptimport { Observable } from 'rxjs';

// Suscribirse a cambios de estado
billetera.state().subscribe((estado) => {
console.log('Estado actual:', estado);
console.log('Saldo:', estado.coins);
console.log('Historial de transacciones:', estado.transactionHistory);
});
Paso 1: Preparar una Transferencia
javascriptimport { nativeToken } from '@midnight-ntwrk/zswap';

// ⚠️ IMPORTANTE: Asegúrate que la billetera haya sincronizado completamente
// antes de preparar una transacción. De lo contrario, podrías intentar gastar
// una moneda que ya fue gastada pero tu billetera aún no lo sabe.

const recetaTransferencia = await billetera.transferTransaction([
{
amount: 1n, // Cantidad en satoshis (BigInt)
type: nativeToken(), // Tipo de token (tDUST)
receiverAddress: 'mn_shield-addr_test1kj...' // Dirección del receptor
}
]);

console.log('Receta de transferencia preparada');
Paso 2: Balancear una Transacción Existente
Balancear una transacción significa ajustarla para cubrir las comisiones y equilibrar entradas/salidas:
javascriptconst transaccionBalanceada = await billetera.balanceTransaction(
transaccionExistente,
[] // newCoins (opcional) - para monedas nuevas creadas por DApps
);

console.log('Transacción balanceada');
Paso 3: Generar Pruebas de Conocimiento Cero
Este paso es computacionalmente intensivo y puede tomar varios segundos:
javascriptimport { TRANSACTION_TO_PROVE } from '@midnight-ntwrk/wallet-api';

// Crear la receta (wrapper) de la transacción
const receta = {
type: TRANSACTION_TO_PROVE,
transaction: recetaTransferencia
};

// Generar pruebas (⚠️ Esto es MUY lento - puede tomar decenas de segundos)
console.time('Generación de pruebas');
const transaccionComprobada = await billetera.proveTransaction(receta);
console.timeEnd('Generación de pruebas');

console.log('Pruebas generadas exitosamente');
Paso 4: Enviar la Transacción
javascript// La transacción debe estar:
// 1. Balanceada (entradas ≥ salidas + comisiones)
// 2. Comprobada (pruebas generadas)

const transaccionEnviada = await billetera.submitTransaction(
transaccionComprobada
);

console.log('Transacción enviada:', transaccionEnviada);
console.log('Hash de transacción:', transaccionEnviada.id);

Conectar con DApps
Las aplicaciones descentralizadas se conectan a la billetera a través del API de DApp Connector. La billetera se inyecta en el contexto del navegador, permitiendo que las DApps soliciten operaciones como:

Obtener dirección del usuario
Firmar transacciones
Enviar tokens
Ejecutar contratos inteligentes

Consulta:

DApp Connector Overview
Wallet Provider para más detalles técnicos

Ejemplos Prácticos
Ejemplo 1: Transferencia Básica de tDUST
Este ejemplo crea una billetera, sincroniza con la cadena y envía 1 tDUST a otra dirección:
javascriptimport { WalletBuilder } from '@midnight-ntwrk/wallet';
import { NetworkId, nativeToken } from '@midnight-ntwrk/zswap';

async function transferirTDUST() {
try {
// 1. Crear billetera
console.log('📱 Creando billetera...');
const billetera = await WalletBuilder.build(
'https://indexer.testnet-02.midnight.network/api/v1/graphql',
'wss://indexer.testnet-02.midnight.network/api/v1/graphql/ws',
'http://localhost:6300',
'https://rpc.testnet-02.midnight.network',
'0000000000000000000000000000000000000000000000000000000000000000',
NetworkId.TestNet
);

// 2. Iniciar sincronización
console.log('🔄 Iniciando sincronización...');
billetera.start();

// Esperar a que sincronice (en producción, implementa mejor lógica de espera)
await new Promise(resolve => setTimeout(resolve, 5000));

// 3. Preparar transferencia
console.log('💸 Preparando transferencia...');
const recetaTransferencia = await billetera.transferTransaction([
  {
    amount: 1n,
    type: nativeToken(),
    receiverAddress: 'mn_shield-addr_test1kjwksfp8x2tachehsfvufsdl35ljg5cxzdcysjdn6ntadspyxn3qxqrxypgjm055c2azrpuyn7un0ge2vm25vkfv38d24rj3ewcku5wmdc94gjr9'
  }
]);

// 4. Generar pruebas
console.log('⚙️ Generando pruebas de conocimiento cero (esto puede tomar un tiempo)...');
const transaccionComprobada = await billetera.proveTransaction(recetaTransferencia);

// 5. Enviar transacción
console.log('📤 Enviando transacción...');
const resultado = await billetera.submitTransaction(transaccionComprobada);

console.log('✅ ¡Transacción enviada exitosamente!');
console.log('ID de transacción:', resultado.id);

// 6. Cerrar billetera
await billetera.close();
console.log('🔐 Billetera cerrada');
Enter fullscreen mode Exit fullscreen mode

} catch (error) {
console.error('❌ Error:', error.message);
}
}

// Ejecutar
transferirTDUST();
Ejemplo 2: Guardar y Restaurar Billetera
javascriptimport { WalletBuilder } from '@midnight-ntwrk/wallet';
import { NetworkId } from '@midnight-ntwrk/zswap';
import { generateRandomSeed } from '@midnight-ntwrk/wallet-sdk-hd';
import * as fs from 'fs';

async function guardarYRestaurarBilletera() {
const ruutaArchivo = './billetera_snapshot.json';
const semilla = generateRandomSeed();

try {
// 1. Crear billetera original
console.log('📱 Creando billetera original...');
const billetera = await WalletBuilder.build(
'https://indexer.testnet-02.midnight.network/api/v1/graphql',
'wss://indexer.testnet-02.midnight.network/api/v1/graphql/ws',
'http://localhost:6300',
'https://rpc.testnet-02.midnight.network',
semilla,
NetworkId.TestNet
);

billetera.start();

// Esperar sincronización
await new Promise(resolve => setTimeout(resolve, 3000));

// 2. Guardar instantánea
console.log('💾 Guardando instantánea...');
const estadoSerializado = await billetera.serialize();
fs.writeFileSync(ruutaArchivo, JSON.stringify({
  estado: estadoSerializado,
  semilla: Buffer.from(semilla).toString('hex')
}, null, 2));

await billetera.close();
console.log('✅ Instantánea guardada en:', ruutaArchivo);

// 3. Restaurar desde instantánea
console.log('📂 Leyendo instantánea...');
const datos = JSON.parse(fs.readFileSync(ruutaArchivo, 'utf-8'));

console.log('🔄 Restaurando billetera...');
const billetera_restaurada = await WalletBuilder.restore(
  'https://indexer.testnet-02.midnight.network/api/v1/graphql',
  'wss://indexer.testnet-02.midnight.network/api/v1/graphql/ws',
  'http://localhost:6300',
  'https://rpc.testnet-02.midnight.network',
  Buffer.from(datos.semilla, 'hex'),
  datos.estado
);

billetera_restaurada.start();
console.log('✅ Billetera restaurada exitosamente');

// Verificar estado
billetera_restaurada.state().subscribe((estado) => {
  console.log('Estado restaurado - Monedas:', estado.coins);
});

await billetera_restaurada.close();
Enter fullscreen mode Exit fullscreen mode

} catch (error) {
console.error('❌ Error:', error.message);
}
}

guardarYRestaurarBilletera();
Ejemplo 3: Monitorear Estado en Tiempo Real
javascriptimport { WalletBuilder } from '@midnight-ntwrk/wallet';
import { NetworkId } from '@midnight-ntwrk/zswap';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';

async function monitorearBilletera() {
try {
const billetera = await WalletBuilder.build(
'https://indexer.testnet-02.midnight.network/api/v1/graphql',
'wss://indexer.testnet-02.midnight.network/api/v1/graphql/ws',
'http://localhost:6300',
'https://rpc.testnet-02.midnight.network',
'0000000000000000000000000000000000000000000000000000000000000000',
NetworkId.TestNet
);

billetera.start();

// Monitorear cambios de estado con debounce
billetera.state()
  .pipe(
    debounceTime(1000),                    // Esperar 1 segundo sin cambios
    distinctUntilChanged((a, b) =>         // Solo si realmente cambió
      JSON.stringify(a) === JSON.stringify(b)
    )
  )
  .subscribe((estado) => {
    console.log('\n📊 === ESTADO ACTUALIZADO ===');
    console.log('Monedas disponibles:', estado.coins.length);
    console.log('Saldo total:', estado.coins.reduce((sum, c) => sum + c.amount, 0n));
    console.log('Transacciones procesadas:', estado.transactionHistory.length);
    console.log('Última transacción:', 
      estado.transactionHistory[0]?.date || 'Ninguna');
  });

// Mantener proceso activo
console.log('👀 Monitoreando billetera (Ctrl+C para salir)...');
Enter fullscreen mode Exit fullscreen mode

} catch (error) {
console.error('❌ Error:', error.message);
}
}

monitorearBilletera();

Buenas Prácticas y Recomendaciones

  1. Sincronización Completa Antes de Transacciones javascript// ❌ INCORRECTO const receta = await billetera.transferTransaction([...]);

// ✅ CORRECTO
// Esperar a que la billetera esté sincronizada
await new Promise(resolve =>
billetera.state()
.pipe(filter(s => s.isSynced))
.subscribe(() => resolve())
);
const receta = await billetera.transferTransaction([...]);

  1. Gestión Segura de Semillas javascript// ❌ INCORRECTO const semilla = "0000000000..."; // Hardcodeada

// ✅ CORRECTO
const semilla = process.env.WALLET_SEED; // Variables de entorno
// O
const semilla = await obtenerDelAlfiler(); // Sistema seguro de almacenamiento

  1. Manejo de Errores Robusto javascripttry { const transaccion = await billetera.proveTransaction(receta); } catch (error) { if (error.message.includes('insufficient')) { console.error('Saldo insuficiente'); } else if (error.message.includes('timeout')) { console.error('Servidor de pruebas no disponible'); } else { console.error('Error desconocido:', error); } }
  2. Cierre Adecuado de Recursos javascriptasync function operacionConBilletera() { const billetera = await WalletBuilder.build(...); try { billetera.start(); // Tu código aquí } finally { await billetera.close(); // Siempre cerrar } }
  3. Timeouts en Operaciones Largas javascriptimport { timeout } from 'rxjs/operators';

const transaccionConTimeout = billetera.proveTransaction(receta)
.pipe(timeout(60000)); // 60 segundos máximo

Parámetros de Configuración
WalletBuilder.build() - Parámetros Obligatorios
ParámetroTipoDescripciónIndexer URLStringURL del API GraphQL del indexadorIndexer WebSocket URLStringURL WebSocket del indexadorProving Server URLStringURL del servidor de pruebasNode URLStringURL del nodo RPCSeedStringSemilla de 256 bits en hexNetwork IDNetworkIdTestNet, MainNet, etc.
Parámetros Opcionales
ParámetroTipoDefaultDescripciónLog LevelLogLevelwarnerror, warn, info, debugDiscard Transaction HistoryBooleanfalseDescartar historial de transacciones

Troubleshooting
Problema: "Error derivando clave"
Solución: Verifica que la semilla sea de 256 bits (64 caracteres hex)
Problema: Transacción rechazada por "double spend"
Solución: Asegúrate de que la billetera esté completamente sincronizada
antes de preparar la transacción
Problema: Servidor de pruebas no disponible
Solución: Verifica que el servidor de pruebas esté corriendo en localhost:6300
o usa URL correcta en la configuración
Problema: "secret keys are no longer included"
Solución: Proporciona la semilla original al restaurar desde instantánea

Recursos Adicionales

Documentación Oficial: https://docs.midnight.network
GitHub: https://github.com/midnight-ntwrk
API Reference: Consulta la documentación de @midnight-ntwrk/wallet
Ejemplos: Revisa los ejemplos incluidos en el repositorio

Conclusión
El Midnight Wallet SDK proporciona una forma robusta y segura de integrar funcionalidad de billetera en aplicaciones blockchain. Recuerda siempre:

✅ Gestiona las semillas de forma segura
✅ Sincroniza completamente antes de transacciones
✅ Cierra recursos adecuadamente
✅ Implementa manejo robusto de errores
✅ Prueba ampliamente antes de producción

Top comments (0)