Skip to main content

Wallets (TypeScript)

In this tutorial we’ll review how to create, save, recreate, and use a wallet. When you perform a transaction, the SDK signs the transaction and sends it to the network. This cryptographic seal lets nodes detect any modifications to your data and provides traceability to your wallet’s activity, because all of your transactions are linked to your wallet.

info

A wallet is used to create and sign transactions; the wallet also has a unique address. Nodes validate the signature and the wallet’s existence on-chain for those transactions, so it’s a requirement to use valid wallets when creating transactions.

info

This tutorial assumes you already have a ULedger blockchain set up and a node endpoint available.

In ULedger, a wallet represents an on-chain identity:

  • It holds a keypair (public/private key).
  • It has a unique address derived from the public key.
  • It can be granted permissions via authGroups.
  • It’s used to sign transactions that the network can verify.

A wallet also has a key type (keyType) that defines what cryptographic algorithm it uses. In the TypeScript SDK, keyType is a typed enum (KeyType), so you should pass enum values (not raw strings) when selecting a key type.

Supported key types:

  • KeyType.Secp256k1 (default)
  • KeyType.ED25519
  • KeyType.MlDSA87
  • KeyType.BLS12377

In this guide you’ll learn how to:

  1. Generate a new wallet + mnemonic (recommended).
  2. Restore a wallet from an existing mnemonic (when you already have one).
  3. Save the wallet to a .ukey file.
  4. Load a wallet back from disk.
  5. Recreate a wallet from existing keys or JSON.
  6. (Then) use it in a transaction session.

1. Prerequisites

  • Node.js >= 16
  • Install the SDK:
npm i @uledgerinc/typescript-sdk

In your TypeScript code you’ll mainly use:

import { getULedgerSDK } from "@uledgerinc/typescript-sdk";

Why getULedgerSDK()? It loads the WASM backend bundled inside this package (best default for Node).


2. Wallet structure (what’s in a wallet?)

In TypeScript, wallet types mirror the Go implementation and include a keyType: KeyType field (not a free-form string).

A full wallet payload typically contains:

  • address: string
  • enabled: boolean
  • parent: string
  • authGroups: Record<string, UL_AuthPermission>
  • keyType: KeyType
  • publicKeyHex: string
  • privateKeyHex?: string
  • mnemonic?: string

KeyType is an enum, for example: KeyType.Secp256k1 (string value "secp256k1").


The safest and recommended way to create a wallet is to let the SDK generate a cryptographically-secure BIP-39 mnemonic for you.

The SDK supports common entropy values: 128, 160, 192, 224, 256 bits.

We recommend 256 bits (24 words). In examples below we set it explicitly to 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);

⚠️ Treat the returned mnemonic as a high-security secret. Store it offline if possible.


4. Restore a wallet from an existing mnemonic (when you already have one)

If you already have a valid BIP-39 mnemonic (for example, generated earlier by the SDK), you can recreate the same 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 conceptually:

  • Validates the mnemonic (BIP-39).
  • Derives a seed from the mnemonic (+ optional passphrase).
  • Creates a keypair for the selected key type (e.g. secp256k1).
  • Computes the address from the public key.
  • Returns a wallet capable of signing.

💡 The mnemonic + passphrase is enough to recreate the same wallet later. Losing it means losing access to the underlying keypair.


5. Save a wallet to a .ukey file

Unlike the Go SDK, the TS SDK doesn’t ship a SaveToFile helper—so you persist using Node’s fs and the wallet’s JSON export.

Helper: save a .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
}

Usage:

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);

Example .ukey file

{
"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"
}

A few details worth calling out:

  • keyType is stored as a string (e.g. "secp256k1").
  • mnemonic can be included for convenience and recovery; omit it if you don’t want mnemonics in files.
  • privateKeyHex is optional — include only if you truly need it.

6. Load a wallet from a .ukey file

To reconstruct a wallet from a .ukey, read the mnemonic or JSON and pass it to the 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);

Behavior matches the intent of the Go flow:

  • If mnemonic is present → restore from mnemonic (mnemonic must be valid BIP-39).
  • Else if private key is present → rebuild from hex keys.
  • Else → you may end up with a wallet that cannot sign.

7. Load from raw JSON (string)

If your wallet is already a raw JSON string (from a CLI, an API, etc.), use:

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. Import an existing keypair (public/private hex)

If you already have hex-encoded keys, use 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. Next steps: Register wallets

Everything we’ve done so far lives locally:

  • We generated a keypair (and mnemonic).
  • We saved it to a .ukey file.
  • We loaded it back into a wallet object.

At this point, the wallet may not be registered on any blockchain yet. It’s a cryptographic identity that can be used to sign transactions, but nodes typically need to see the wallet’s public key and metadata on-chain before accepting it.

To make a blockchain aware of this wallet (public key, parent, auth groups), you send a wallet-registration transaction (e.g. TX_CREATE_WALLET):

  • Payload includes wallet public key, key type, parent, and auth groups.
  • Transaction is signed and submitted like any other transaction.
  • Once included in a block, nodes can validate this wallet when it signs future transactions.

Continue to: Register & Create Transactions (TypeScript) to build and send the wallet registration transaction.