Skip to main content

Account Management

Learn how to generate, import, and manage Convex accounts and Ed25519 key pairs.

Overview

Every Convex account has:

  • Address - A unique identifier (e.g., #1678)
  • Ed25519 Seed - 32-byte private seed (keep this secret!)
  • Public Key - Derived from the seed (safe to share)
  • Balance - Amount of Convex Coins owned
  • Sequence - Transaction counter

Generating New Key Pairs

Random Generation

Generate a new random key pair:

import { KeyPair } from '@convex-world/convex-ts';

// Generate random key pair
const keyPair = KeyPair.generate();

// Access the keys
console.log('Public key:', keyPair.publicKeyHex);
console.log('Seed:', keyPair.privateKeyHex); // Keep secret!

// Access as bytes
const publicBytes: Uint8Array = keyPair.publicKey;
const seedBytes: Uint8Array = keyPair.privateKey;
Keep Seeds Secret

Never commit seeds to version control, share them publicly, or store them unencrypted. Anyone with your seed can control your account.

From Existing Seed

If you already have an Ed25519 seed:

import { KeyPair } from '@convex-world/convex-ts';

// From hex string
const keyPair = KeyPair.fromSeed('your-32-byte-seed-hex');

// From bytes
const seedBytes = new Uint8Array(32); // Your seed
const keyPair = KeyPair.fromSeed(seedBytes);

// Public key is automatically derived
console.log('Public key:', keyPair.publicKeyHex);

Setting Up Your Account

Basic Setup

import { Convex, KeyPair } from '@convex-world/convex-ts';

const convex = new Convex('https://peer.convex.live');

// Load your key pair
const keyPair = KeyPair.fromSeed(process.env.CONVEX_SEED!);

// Set your account
convex.setAccount('#1678', keyPair);

// Now you can transact
const info = await convex.getAccountInfo();
console.log('Balance:', info.balance);

Separate Signer and Address

For advanced use cases, you can set the signer and address separately:

// Set signer first
convex.setSigner(keyPair);

// Then set address
convex.setAddress('#1678');

// Or switch addresses with the same signer
convex.setAddress('#9999');

This is useful when one signer controls multiple accounts.

Account Information

Get Account Details

const info = await convex.getAccountInfo();

console.log('Address:', info.address);
console.log('Balance:', info.balance / 1_000_000_000, 'coins');
console.log('Sequence:', info.sequence);
console.log('Public key:', Buffer.from(info.publicKey).toString('hex'));

Check Balance

// Your account balance
const info = await convex.getAccountInfo();
const balanceInCoins = info.balance / 1_000_000_000;

// Any account balance (via query)
const result = await convex.query('(balance #123)');
const balance = Number(result.value);

Key Pair Formats

Hex Format

const keyPair = KeyPair.generate();

// Get keys as hex strings
const publicKeyHex = keyPair.publicKeyHex;
const seedHex = keyPair.privateKeyHex;

// Restore from hex
const restored = KeyPair.fromSeed(seedHex);

Bytes Format

const keyPair = KeyPair.generate();

// Get keys as Uint8Array
const publicKey: Uint8Array = keyPair.publicKey; // 32 bytes
const seed: Uint8Array = keyPair.privateKey; // 32 bytes

// Restore from bytes
const restored = KeyPair.fromSeed(seed);

Environment Variables

Best Practice Storage

Store credentials in environment variables:

// .env file (never commit this!)
CONVEX_SEED=abc123...
CONVEX_ADDRESS=#1678
CONVEX_PEER=https://peer.convex.live
// app.ts
import { Convex, KeyPair } from '@convex-world/convex-ts';

const convex = new Convex(process.env.CONVEX_PEER!);
const keyPair = KeyPair.fromSeed(process.env.CONVEX_SEED!);
convex.setAccount(process.env.CONVEX_ADDRESS!, keyPair);

Using dotenv

npm install dotenv
import 'dotenv/config';
import { Convex, KeyPair } from '@convex-world/convex-ts';

const convex = new Convex(process.env.CONVEX_PEER!);
const keyPair = KeyPair.fromSeed(process.env.CONVEX_SEED!);
convex.setAccount(process.env.CONVEX_ADDRESS!, keyPair);

Key Pair Validation

Verify Seed Length

function loadKeyPair(seedHex: string): KeyPair {
const seedBytes = Buffer.from(seedHex, 'hex');

if (seedBytes.length !== 32) {
throw new Error(`Invalid seed length: expected 32 bytes, got ${seedBytes.length}`);
}

return KeyPair.fromSeed(seedBytes);
}

Verify Key Derivation

const keyPair = KeyPair.fromSeed(seed);

// Public key should always be 32 bytes
if (keyPair.publicKey.length !== 32) {
throw new Error('Invalid public key length');
}

// Verify deterministic derivation
const keyPair2 = KeyPair.fromSeed(seed);
if (keyPair.publicKeyHex !== keyPair2.publicKeyHex) {
throw new Error('Non-deterministic key derivation');
}

Multiple Accounts

Managing Multiple Key Pairs

class AccountManager {
private accounts = new Map<string, KeyPair>();

addAccount(address: string, keyPair: KeyPair) {
this.accounts.set(address, keyPair);
}

getKeyPair(address: string): KeyPair | undefined {
return this.accounts.get(address);
}

async useAccount(convex: Convex, address: string) {
const keyPair = this.accounts.get(address);
if (!keyPair) {
throw new Error(`No key pair for ${address}`);
}
convex.setAccount(address, keyPair);
}
}

// Usage
const manager = new AccountManager();
manager.addAccount('#1678', KeyPair.fromSeed(seed1));
manager.addAccount('#9999', KeyPair.fromSeed(seed2));

await manager.useAccount(convex, '#1678');
await convex.transfer('#456', 1000000);

await manager.useAccount(convex, '#9999');
await convex.transfer('#789', 2000000);

Multi-Signature Patterns

// Multiple signers for one account (requires smart contract)
const signers = [
KeyPair.fromSeed(seed1),
KeyPair.fromSeed(seed2),
KeyPair.fromSeed(seed3)
];

// Collect signatures
const message = 'transfer 1000000 to #456';
const signatures = await Promise.all(
signers.map(kp => sign(message, kp.privateKey))
);

// Submit multi-sig transaction
const result = await convex.transact({
data: {
message,
signatures: signatures.map(sig => Buffer.from(sig).toString('hex'))
}
});

Security Best Practices

✅ Do

  • Generate seeds with cryptographically secure random number generators
  • Store seeds in environment variables or encrypted keystores
  • Use different accounts for different purposes (hot/cold wallets)
  • Back up seeds securely (encrypted, offline)
  • Verify public key derivation is deterministic
  • Use hardware wallets for high-value accounts

❌ Don't

  • Hardcode seeds in source code
  • Commit seeds to version control
  • Share seeds via insecure channels (email, chat)
  • Store seeds in browser localStorage without encryption
  • Reuse seeds across different networks without understanding implications
  • Generate seeds with weak random number generators (Math.random())

Backup and Recovery

Mnemonic Seeds (BIP39)

For better user experience, consider using BIP39 mnemonics:

import * as bip39 from 'bip39';

// Generate mnemonic
const mnemonic = bip39.generateMnemonic();
console.log('Backup phrase:', mnemonic);
// "witch collapse practice feed shame open despair creek road again ice least"

// Derive seed from mnemonic
const seed = bip39.mnemonicToSeedSync(mnemonic);
const ed25519Seed = seed.subarray(0, 32); // Use first 32 bytes

// Create key pair
const keyPair = KeyPair.fromSeed(ed25519Seed);

Paper Backup

// Generate QR code for backup
import QRCode from 'qrcode';

const keyPair = KeyPair.generate();

// Create QR code of seed (for paper backup)
const qrCode = await QRCode.toDataURL(keyPair.privateKeyHex);

// Display or print QR code
console.log('Scan this QR code to restore your account:');
console.log(qrCode);

Account Creation

On-Chain Account Creation

Creating a new account on-chain (requires an existing funded account):

// From an existing account, fund a new account
const newKeyPair = KeyPair.generate();
const newPublicKey = newKeyPair.publicKeyHex;

const result = await convex.transact(`
(create-account ${newPublicKey})
`);

console.log('New account created:', result.result);

Next Steps

See Also