Wallets (TypeScript)
En este tutorial revisaremos cómo crear, guardar, recrear y usar un wallet. Cuando realizas una transaction, el SDK firma la transaction y la envía a la red. Este sello criptográfico permite a los nodes detectar cualquier modificación en tus datos y proporciona trazabilidad a la actividad de tu wallet, porque todas tus transactions están vinculadas a tu wallet.
Un wallet se usa para crear y firmar transactions; el wallet también tiene una dirección única. Los nodes validan la firma y la existencia del wallet on-chain para esas transactions, por lo que es un requisito usar wallets válidos al crear transactions.
Este tutorial asume que ya tienes una blockchain ULedger configurada y un endpoint de node disponible.
En ULedger, un wallet representa una identidad on-chain:
- Contiene un keypair (clave pública/privada).
- Tiene una dirección única derivada de la clave pública.
- Se le pueden otorgar permisos mediante
authGroups. - Se usa para firmar transactions que la red puede verificar.
Un wallet también tiene un key type (keyType) que define qué algoritmo criptográfico utiliza. En el TypeScript SDK, keyType es un enum tipado (KeyType), por lo que debes pasar valores del enum (no strings sin formato) al seleccionar un key type.
Key types soportados:
KeyType.Secp256k1(predeterminado)KeyType.ED25519KeyType.MlDSA87KeyType.BLS12377
En esta guía aprenderás cómo:
- Generar un nuevo wallet + mnemonic (recomendado).
- Restaurar un wallet desde un mnemonic existente (cuando ya tienes uno).
- Guardar el wallet en un archivo
.ukey. - Cargar un wallet desde el disco.
- Recrear un wallet desde claves existentes o JSON.
- (Luego) usarlo en una sesión de transaction.
1. Prerrequisitos
- Node.js >= 16
- Instalar el SDK:
npm i @uledgerinc/typescript-sdk
En tu código TypeScript usarás principalmente:
import { getULedgerSDK } from "@uledgerinc/typescript-sdk";
2. Estructura del wallet (¿qué contiene un wallet?)
En TypeScript, los tipos de wallet reflejan la implementación en Go e incluyen un campo keyType: KeyType (no un string de forma libre).
Un payload completo de wallet típicamente contiene:
address: stringenabled: booleanparent: stringauthGroups: Record<string, UL_AuthPermission>keyType: KeyTypepublicKeyHex: stringprivateKeyHex?: stringmnemonic?: string
KeyTypees un enum, por ejemplo:KeyType.Secp256k1(valor string"secp256k1").
3. Generar un nuevo wallet + mnemonic (recomendado)
La forma más segura y recomendada de crear un wallet es dejar que el SDK genere un mnemonic criptográficamente seguro BIP-39 por ti.
El SDK soporta valores de entropía comunes: 128, 160, 192, 224, 256 bits.
Recomendamos 256 bits (24 palabras). En los ejemplos a continuación lo configuramos explícitamente en 256.
import { getULedgerSDK, KeyType } from "@uledgerinc/typescript-sdk";
async function main() {
const sdk = await getULedgerSDK();
const wallet = sdk.generateWallet({
keyType: KeyType.Secp256k1,
entropy: 256, // recommended default (24 words)
password: "", // optional
parent: "", // optional
authGroups: {}, // optional
});
console.log("Address:", wallet.address);
console.log("Mnemonic (store this safely!):", wallet.mnemonic);
}
main().catch(console.error);
⚠️ Trata el mnemonic devuelto como un secreto de alta seguridad. Guárdalo offline si es posible.
4. Restaurar un wallet desde un mnemonic existente (cuando ya tienes uno)
Si ya tienes un mnemonic BIP-39 válido (por ejemplo, generado anteriormente por el SDK), puedes recrear el mismo wallet.
import { getULedgerSDK, KeyType } from "@uledgerinc/typescript-sdk";
async function main() {
const sdk = await getULedgerSDK();
const mnemonic = "<paste your BIP-39 mnemonic here>";
const passphrase = ""; // optional (must match what was used originally)
// Optional but recommended: validate before restoring
if (!sdk.validateMnemonic(mnemonic)) {
throw new Error("Invalid mnemonic: must be valid BIP-39");
}
const wallet = sdk.walletFromMnemonic(mnemonic, {
keyType: KeyType.Secp256k1, // optional (defaults to Secp256k1)
password: passphrase, // optional (used for key derivation)
});
console.log("Wallet address:", wallet.address);
console.log("Can sign:", wallet.canSign);
}
main().catch(console.error);
walletFromMnemonic conceptualmente:
- Valida el mnemonic (BIP-39).
- Deriva una semilla del mnemonic (+ passphrase opcional).
- Crea un keypair para el key type seleccionado (ej.
secp256k1). - Calcula la dirección a partir de la clave pública.
- Devuelve un wallet capaz de firmar.
💡 El mnemonic + passphrase es suficiente para recrear el mismo wallet posteriormente. Perderlo significa perder acceso al keypair subyacente.
5. Guardar un wallet en un archivo .ukey
A diferencia del Go SDK, el TS SDK no incluye un helper SaveToFile, por lo que se persiste usando fs de Node y la exportación JSON del wallet.
Helper: guardar un .ukey (Node.js)
import fs from "node:fs";
import path from "node:path";
import { Wallet } from "@uledgerinc/typescript-sdk";
type SaveOptions = {
includePrivateKey?: boolean; // default false
includeMnemonic?: boolean; // default true
};
export function saveWalletToUKeyFile(
wallet: Wallet,
filePath: string,
opts: SaveOptions = {},
) {
const includePrivateKey = opts.includePrivateKey ?? true;
const includeMnemonic = opts.includeMnemonic ?? true;
const finalPath = filePath.endsWith(".ukey") ? filePath : `${filePath}.ukey`;
const data = wallet.toWalletData(includePrivateKey);
// Optionally remove mnemonic before writing
if (!includeMnemonic) {
// @ts-expect-error wallet data is a plain object
delete data.mnemonic;
}
fs.mkdirSync(path.dirname(finalPath), { recursive: true });
fs.writeFileSync(finalPath, JSON.stringify(data, null, 2), { mode: 0o600 });
fs.chmodSync(finalPath, 0o600); // enforce owner-only permissions on *nix
}
Uso:
import { getULedgerSDK, KeyType } from "@uledgerinc/typescript-sdk";
import { saveWalletToUKeyFile } from "./saveWalletToUKeyFile";
async function main() {
const sdk = await getULedgerSDK();
const wallet = sdk.generateWallet({
keyType: KeyType.Secp256k1,
entropy: 256, // recommended
});
// Best practice: do NOT include the private key in the file if you can avoid it.
saveWalletToUKeyFile(wallet, "wallets/my_wallet.ukey", {
includePrivateKey: false,
includeMnemonic: true,
});
}
main().catch(console.error);
Ejemplo de archivo .ukey
{
"address": "11e4876958847f93a82c39110cc86a2bcdf1adf3f725fa9ad8a70b6f5eae1bf1",
"enabled": true,
"parent": "",
"authGroups": {},
"mnemonic": "kiwi possible help find habit genre math lion alpha page loud arrow ...",
"keyType": "secp256k1",
"publicKeyHex": "0435C4D829A686D05ACE8256CAA73504F69BF5569D3E6122BEC54D64D7C31C8E...",
"privateKeyHex": "2310117A24CD8F086E429D1F52748FEC3EAD5C42E0910187143FE8C63CC0C0C1"
}
Algunos detalles que vale la pena destacar:
keyTypese almacena como string (ej."secp256k1").mnemonicpuede incluirse por conveniencia y recuperación; omítelo si no quieres mnemonics en los archivos.privateKeyHexes opcional — inclúyelo solo si realmente lo necesitas.
6. Cargar un wallet desde un archivo .ukey
Para reconstruir un wallet desde un .ukey, lee el mnemonic o JSON y pásalo al wallet.
import fs from "node:fs";
import { getULedgerSDK, KeyType } from "@uledgerinc/typescript-sdk";
async function main() {
const sdk = await getULedgerSDK();
const raw = fs.readFileSync("wallets/my_wallet.ukey", "utf8");
// get mnemonic from raw
const mnemonic = JSON.parse(raw).mnemonic;
const walletFromMnemonic = sdk.walletFromMnemonic(mnemonic, { keyType: KeyType.Secp256k1}); // password/passphrase if used
const walletFromJson = sdk.walletFromJson(raw); // password if used
console.log("Loaded wallet address:", walletFromMnemonic.address);
console.log("Can sign:", walletFromMnemonic.canSign);
console.log("Loaded wallet address from JSON:", walletFromJson.address);
console.log("Can sign from JSON:", walletFromJson.canSign);
}
main().catch(console.error);
El comportamiento coincide con la intención del flujo en Go:
- Si el mnemonic está presente → restaurar desde el mnemonic (el mnemonic debe ser BIP-39 válido).
- Si hay una clave privada presente → reconstruir desde las claves hex.
- De lo contrario → puedes terminar con un wallet que no puede firmar.
7. Cargar desde JSON sin formato (string)
Si tu wallet ya es un string JSON sin formato (desde una CLI, una API, etc.), usa:
import { getULedgerSDK } from "@uledgerinc/typescript-sdk";
async function main() {
const sdk = await getULedgerSDK();
const raw = `{
"address": "11e4876958847f93a82c39110cc86a2bcdf1adf3f725fa9ad8a70b6f5eae1bf1",
"enabled": true,
"parent": "",
"authGroups": {},
"mnemonic": "kiwi possible help find habit genre math lion alpha page loud arrow ...",
"keyType": "secp256k1",
"publicKeyHex": "0435C4D829A686D0...",
"privateKeyHex": "2310117A24CD8F08..."
}`;
const wallet = sdk.walletFromJson(raw, "");
console.log("Address:", wallet.address);
}
main().catch(console.error);
8. Importar un keypair existente (hex público/privado)
Si ya tienes claves codificadas en hex, usa walletFromHex:
import { getULedgerSDK, KeyType } from "@uledgerinc/typescript-sdk";
async function main() {
const sdk = await getULedgerSDK();
const publicKeyHex = "04f2f0fd15ba3a7f4ba62cd705c4df80...";
const privateKeyHex = "63f6062f2034bcbcc08bae2eaabee8dd...";
const wallet = sdk.walletFromHex(publicKeyHex, privateKeyHex, KeyType.Secp256k1);
console.log("Imported wallet address:", wallet.address);
console.log("Can sign:", wallet.canSign);
}
main().catch(console.error);
9. Próximos pasos: Registrar wallets
Todo lo que hemos hecho hasta ahora existe localmente:
- Generamos un keypair (y mnemonic).
- Lo guardamos en un archivo
.ukey. - Lo cargamos de nuevo en un objeto wallet.
En este punto, el wallet puede no estar registrado en ninguna blockchain aún. Es una identidad criptográfica que puede usarse para firmar transactions, pero los nodes generalmente necesitan ver la clave pública del wallet y sus metadatos on-chain antes de aceptarlo.
Para hacer que una blockchain sea consciente de este wallet (clave pública, parent, auth groups), envías una transaction de registro de wallet (ej. TX_CREATE_WALLET):
- El payload incluye la clave pública del wallet, el key type, el parent y los auth groups.
- La transaction se firma y se envía como cualquier otra transaction.
- Una vez incluida en un bloque, los nodes pueden validar este wallet cuando firme futuras transactions.
Continúa en: Registrar y Crear Transactions (TypeScript) para construir y enviar la transaction de registro del wallet.