Setup SDK Client#

In this tutorial we will show how to configure the client and key managers for use in the remaining tutorials.

Prerequisites#

Code#

Save the following two modules side-by-side — setupDashClient-core.mjs and setupDashClient.mjs — in the same directory. Later Node tutorials import from setupDashClient.mjs; browser apps can import from setupDashClient-core.mjs directly.

Important

After saving, open setupDashClient.mjs (the wrapper) and set your mnemonic in the clientConfig section near the top of the file — either edit the value directly or create a .env file with PLATFORM_MNEMONIC. The core file has no configuration to edit.

Browser-safe core#

setupDashClient-core.mjs holds everything that works in any JS runtime: the SDK client factory, DIP-13 path helper, and the two key manager classes. Browser apps import from this file directly.

setupDashClient-core.mjs#
/* eslint-disable max-classes-per-file */
//
// Browser-safe core of setupDashClient.
//
// This file contains the parts of setupDashClient that work in any JS runtime:
//   - createClient(network)
//   - dip13KeyPath(network, identityIndex, keyIndex)
//   - IdentityKeyManager
//   - AddressKeyManager
//   - KEY_SPECS
//
// The Node-only convenience layer (dotenv loading, process.env-driven
// clientConfig, and the setupDashClient() wrapper) lives in setupDashClient.mjs
// alongside this file. Both Node tutorials and browser apps import the same
// IdentityKeyManager from here so there is no copy-paste drift.
//
import {
  EvoSDK,
  IdentityPublicKeyInCreation,
  IdentitySigner,
  KeyType,
  PlatformAddressSigner,
  PrivateKey,
  Purpose,
  SecurityLevel,
  wallet,
} from '@dashevo/evo-sdk';
import { PLATFORM_VERSION_OVERRIDE } from './platformVersion.mjs';

/** @typedef {import('@dashevo/evo-sdk').Identity} Identity */
/** @typedef {import('@dashevo/evo-sdk').IdentityPublicKey} IdentityPublicKey */
/** @typedef {import('@dashevo/evo-sdk').PlatformAddress} PlatformAddress */
/** @typedef {import('@dashevo/evo-sdk').PlatformAddressInfo} PlatformAddressInfo */
/** @typedef {import('@dashevo/evo-sdk').NetworkLike} NetworkLike */

/**
 * @typedef {Object} DerivedKeyEntry
 * @property {number} keyId
 * @property {string} privateKeyWif
 * @property {string} [publicKey] - Only present via createForNewIdentity()
 */

/**
 * @typedef {Object} AddressEntry
 * @property {PlatformAddress} address
 * @property {string} bech32m
 * @property {string} privateKeyWif
 * @property {string} path
 */

// ⚠️ Tutorial helper — holds WIFs in memory for convenience.
// Do not use this pattern as-is for production key management.

// ---------------------------------------------------------------------------
// Browser-safe hex → bytes (replaces Buffer.from(hex, 'hex'))
// ---------------------------------------------------------------------------

/**
 * Decode a hex string to a Uint8Array. Browser-safe replacement for
 * Buffer.from(hex, 'hex'). Throws on odd-length or non-hex input.
 *
 * @param {string} hex
 * @returns {Uint8Array}
 */
function hexToBytes(hex) {
  if (typeof hex !== 'string' || hex.length % 2 !== 0) {
    throw new Error('hexToBytes: expected even-length hex string');
  }
  const out = new Uint8Array(hex.length / 2);
  for (let i = 0; i < out.length; i += 1) {
    const offset = i * 2;
    const chunk = hex.slice(offset, offset + 2);
    if (!/^[0-9A-Fa-f]{2}$/.test(chunk)) {
      throw new Error(`hexToBytes: invalid hex at offset ${offset}`);
    }
    out[i] = parseInt(chunk, 16);
  }
  return out;
}

// ---------------------------------------------------------------------------
// DIP-13 key path
// ---------------------------------------------------------------------------

/**
 * Build a DIP-13 identity key derivation path.
 * Returns the full 7-level hardened path:
 *   m/9'/{coin}'/5'/0'/0'/{identityIndex}'/{keyIndex}'
 * @param {string} network
 * @param {number} identityIndex
 * @param {number} keyIndex
 * @returns {Promise<string>}
 */
export async function dip13KeyPath(network, identityIndex, keyIndex) {
  const base =
    network === 'testnet'
      ? await wallet.derivationPathDip13Testnet(5)
      : await wallet.derivationPathDip13Mainnet(5);
  return `${base.path}/0'/0'/${identityIndex}'/${keyIndex}'`;
}

// ---------------------------------------------------------------------------
// SDK client helpers
// ---------------------------------------------------------------------------

export { PLATFORM_VERSION_OVERRIDE };

/**
 * Create and connect an EvoSDK client for the selected network.
 *
 * @param {string} [network='testnet']
 * @returns {Promise<EvoSDK>}
 */
export async function createClient(network = 'testnet') {
  const factories = /** @type {Record<string, () => EvoSDK>} */ ({
    testnet: () =>
      EvoSDK.testnetTrusted({ version: PLATFORM_VERSION_OVERRIDE }),
    mainnet: () =>
      EvoSDK.mainnetTrusted({ version: PLATFORM_VERSION_OVERRIDE }),
    local: () => EvoSDK.localTrusted({ version: PLATFORM_VERSION_OVERRIDE }),
  });

  const factory = factories[network];
  if (!factory) {
    throw new Error(
      `Unknown network "${network}". Use: ${Object.keys(factories).join(', ')}`,
    );
  }

  const sdk = /** @type {EvoSDK} */ (factory());
  await sdk.connect();
  return sdk;
}

// ---------------------------------------------------------------------------
// IdentityKeyManager
// ---------------------------------------------------------------------------

/** Key specs for the 5 standard identity keys (DIP-9). */
export const KEY_SPECS = [
  {
    keyId: 0,
    purpose: Purpose.AUTHENTICATION,
    securityLevel: SecurityLevel.MASTER,
  },
  {
    keyId: 1,
    purpose: Purpose.AUTHENTICATION,
    securityLevel: SecurityLevel.HIGH,
  },
  {
    keyId: 2,
    purpose: Purpose.AUTHENTICATION,
    securityLevel: SecurityLevel.CRITICAL,
  },
  {
    keyId: 3,
    purpose: Purpose.TRANSFER,
    securityLevel: SecurityLevel.CRITICAL,
  },
  {
    keyId: 4,
    purpose: Purpose.ENCRYPTION,
    securityLevel: SecurityLevel.MEDIUM,
  },
];

/**
 * Manages identity keys and signing for write operations.
 *
 * Mirrors the old js-dash-sdk pattern where `setupDashClient()` hid all
 * wallet/signing config. Construct once, then call getAuth(), getTransfer(),
 * or getMaster() to get a ready-to-use { identity, identityKey, signer }.
 *
 * Keys are derived from a BIP39 mnemonic using standard DIP-9 paths
 * (compatible with dash-evo-tool / Dash wallets):
 *   Key 0 = MASTER (identity updates)
 *   Key 1 = HIGH auth (documents, names)
 *   Key 2 = CRITICAL auth (contracts, documents, names)
 *   Key 3 = TRANSFER (credit transfers/withdrawals)
 *   Key 4 = ENCRYPTION MEDIUM (encrypted messaging/data)
 */
class IdentityKeyManager {
  /**
   * @param {EvoSDK} sdk
   * @param {string|null|undefined} identityId
   * @param {Record<string, DerivedKeyEntry>} keys
   * @param {number} identityIndex
   */
  constructor(sdk, identityId, keys, identityIndex) {
    this.sdk = sdk;
    this.id = identityId;
    this.keys = keys; // { master, auth, authHigh, transfer, encryption }
    this.identityIndex = identityIndex ?? 0;
  }

  get identityId() {
    return this.id;
  }

  /**
   * Create an IdentityKeyManager from a BIP39 mnemonic.
   * Derives all standard identity keys using DIP-9 paths.
   *
   * @param {object} opts
   * @param {EvoSDK} opts.sdk - Connected EvoSDK instance
   * @param {string} [opts.identityId] - Identity ID. If omitted, auto-resolved
   *   from the mnemonic by looking up the master key's public key hash on-chain.
   * @param {string} opts.mnemonic - BIP39 mnemonic
   * @param {string} [opts.network='testnet'] - 'testnet' or 'mainnet'
   * @param {number} [opts.identityIndex=0] - Which identity derived from this mnemonic
   * @returns {Promise<IdentityKeyManager>}
   */
  static async create({
    sdk,
    identityId,
    mnemonic,
    network = 'testnet',
    identityIndex = 0,
  }) {
    const derive = async (/** @type {number} */ keyIndex) =>
      wallet.deriveKeyFromSeedWithPath({
        mnemonic,
        path: await dip13KeyPath(network, identityIndex, keyIndex),
        network,
      });

    const [masterKey, authHighKey, authKey, transferKey, encryptionKey] =
      await Promise.all([
        derive(0), // MASTER
        derive(1), // HIGH auth
        derive(2), // CRITICAL auth
        derive(3), // TRANSFER
        derive(4), // ENCRYPTION MEDIUM
      ]);

    let resolvedId = identityId;
    if (!resolvedId) {
      const privateKey = PrivateKey.fromWIF(masterKey.toObject().privateKeyWif);
      const pubKeyHash = privateKey.getPublicKeyHash();
      const identity = await sdk.identities.byPublicKeyHash(pubKeyHash);
      if (!identity) {
        throw new Error(
          'No identity found for the given mnemonic (key 0 public key hash)',
        );
      }
      resolvedId = identity.id.toString();
    }

    return new IdentityKeyManager(
      sdk,
      resolvedId,
      {
        master: { keyId: 0, privateKeyWif: masterKey.toObject().privateKeyWif },
        authHigh: {
          keyId: 1,
          privateKeyWif: authHighKey.toObject().privateKeyWif,
        },
        auth: { keyId: 2, privateKeyWif: authKey.toObject().privateKeyWif },
        transfer: {
          keyId: 3,
          privateKeyWif: transferKey.toObject().privateKeyWif,
        },
        encryption: {
          keyId: 4,
          privateKeyWif: encryptionKey.toObject().privateKeyWif,
        },
      },
      identityIndex,
    );
  }

  /**
   * Find the first unused DIP-9 identity index for a mnemonic.
   * Scans indices starting at 0 until no on-chain identity is found.
   *
   * @param {EvoSDK} sdk - Connected EvoSDK instance
   * @param {string} mnemonic - BIP39 mnemonic
   * @param {string} [network='testnet'] - 'testnet' or 'mainnet'
   * @returns {Promise<number>} The first unused identity index
   */
  static async findNextIndex(sdk, mnemonic, network = 'testnet') {
    /* eslint-disable no-await-in-loop */
    for (let i = 0; ; i += 1) {
      const path = await dip13KeyPath(network, i, 0);
      const key = await wallet.deriveKeyFromSeedWithPath({
        mnemonic,
        path,
        network,
      });
      const privateKey = PrivateKey.fromWIF(key.toObject().privateKeyWif);
      const existing = await sdk.identities.byPublicKeyHash(
        privateKey.getPublicKeyHash(),
      );
      if (!existing) return i;
    }
    /* eslint-enable no-await-in-loop */
  }

  /**
   * Create an IdentityKeyManager for a new (not yet registered) identity.
   * Derives keys and stores public key data needed for identity creation.
   * If identityIndex is omitted, auto-selects the next unused index.
   *
   * @param {object} opts
   * @param {EvoSDK} opts.sdk - Connected EvoSDK instance
   * @param {string} opts.mnemonic - BIP39 mnemonic
   * @param {string} [opts.network='testnet'] - 'testnet' or 'mainnet'
   * @param {number} [opts.identityIndex] - Identity index (auto-scanned if omitted)
   * @returns {Promise<IdentityKeyManager>}
   */
  static async createForNewIdentity({
    sdk,
    mnemonic,
    network = 'testnet',
    identityIndex,
  }) {
    const idx =
      identityIndex ??
      (await IdentityKeyManager.findNextIndex(sdk, mnemonic, network));
    const derive = async (/** @type {number} */ keyIndex) =>
      wallet.deriveKeyFromSeedWithPath({
        mnemonic,
        path: await dip13KeyPath(network, idx, keyIndex),
        network,
      });

    const derivedKeys = await Promise.all(
      KEY_SPECS.map((spec) => derive(spec.keyId)),
    );

    const keys = {
      master: {
        keyId: 0,
        privateKeyWif: derivedKeys[0].toObject().privateKeyWif,
        publicKey: derivedKeys[0].toObject().publicKey,
      },
      authHigh: {
        keyId: 1,
        privateKeyWif: derivedKeys[1].toObject().privateKeyWif,
        publicKey: derivedKeys[1].toObject().publicKey,
      },
      auth: {
        keyId: 2,
        privateKeyWif: derivedKeys[2].toObject().privateKeyWif,
        publicKey: derivedKeys[2].toObject().publicKey,
      },
      transfer: {
        keyId: 3,
        privateKeyWif: derivedKeys[3].toObject().privateKeyWif,
        publicKey: derivedKeys[3].toObject().publicKey,
      },
      encryption: {
        keyId: 4,
        privateKeyWif: derivedKeys[4].toObject().privateKeyWif,
        publicKey: derivedKeys[4].toObject().publicKey,
      },
    };

    return new IdentityKeyManager(sdk, null, keys, idx);
  }

  /**
   * Build IdentityPublicKeyInCreation objects for all 5 standard keys.
   * Only works when public key data is available (via createForNewIdentity).
   *
   * @returns {IdentityPublicKeyInCreation[]}
   */
  getKeysInCreation() {
    return KEY_SPECS.map((spec) => {
      const key = Object.values(this.keys).find((k) => k.keyId === spec.keyId);
      if (!key?.publicKey) {
        throw new Error(
          `Public key data not available for key ${spec.keyId}. Use createForNewIdentity().`,
        );
      }
      const pubKeyData = hexToBytes(key.publicKey);
      return new IdentityPublicKeyInCreation({
        keyId: spec.keyId,
        purpose: spec.purpose,
        securityLevel: spec.securityLevel,
        keyType: KeyType.ECDSA_SECP256K1,
        data: pubKeyData,
      });
    });
  }

  /**
   * Build an IdentitySigner loaded with all 5 key WIFs.
   * Useful for identity creation where all keys must sign.
   *
   * @returns {IdentitySigner}
   */
  getFullSigner() {
    const signer = new IdentitySigner();
    Object.values(this.keys).forEach((key) => {
      signer.addKeyFromWif(key.privateKeyWif);
    });
    return signer;
  }

  /**
   * Fetch identity and build { identity, identityKey, signer } for a given key.
   * @param {string} keyName - One of: master, auth, authHigh, transfer, encryption
   * @returns {Promise<{ identity: Identity, identityKey: IdentityPublicKey | undefined, signer: IdentitySigner }>}
   */
  async getSigner(keyName) {
    if (!this.id) {
      throw new Error(
        'Identity ID is not set. Use IdentityKeyManager.create() for an existing identity, ' +
          'or create/register the identity first and then set the ID.',
      );
    }
    const key = /** @type {Record<string, DerivedKeyEntry>} */ (this.keys)[
      keyName
    ];
    if (!key) {
      throw new Error(
        `Unknown key "${keyName}". Use: ${Object.keys(this.keys).join(', ')}`,
      );
    }
    const identity = await this.sdk.identities.fetch(this.id);
    if (!identity) {
      throw new Error(`Identity "${this.id}" not found on-chain.`);
    }
    const identityKey = identity.getPublicKeyById(key.keyId);
    const signer = new IdentitySigner();
    signer.addKeyFromWif(key.privateKeyWif);
    return { identity, identityKey, signer };
  }

  /**
   * CRITICAL auth (key 2) — contracts, documents, names.
   * @returns {Promise<{ identity: Identity, identityKey: IdentityPublicKey | undefined, signer: IdentitySigner }>}
   */
  async getAuth() {
    return this.getSigner('auth');
  }

  /**
   * HIGH auth (key 1) — documents, names.
   * @returns {Promise<{ identity: Identity, identityKey: IdentityPublicKey | undefined, signer: IdentitySigner }>}
   */
  async getAuthHigh() {
    return this.getSigner('authHigh');
  }

  /**
   * TRANSFER — credit transfers, withdrawals.
   * @returns {Promise<{ identity: Identity, identityKey: IdentityPublicKey | undefined, signer: IdentitySigner }>}
   */
  async getTransfer() {
    return this.getSigner('transfer');
  }

  /**
   * ENCRYPTION MEDIUM — encrypted messaging/data.
   * @returns {Promise<{ identity: Identity, identityKey: IdentityPublicKey | undefined, signer: IdentitySigner }>}
   */
  async getEncryption() {
    return this.getSigner('encryption');
  }

  /**
   * MASTER — identity updates (add/disable keys).
   * @param {string[]} [additionalKeyWifs] - WIFs for new keys being added
   * @returns {Promise<{ identity: Identity, identityKey: IdentityPublicKey | undefined, signer: IdentitySigner }>}
   */
  async getMaster(additionalKeyWifs) {
    const result = await this.getSigner('master');
    if (additionalKeyWifs) {
      additionalKeyWifs.forEach((wif) => result.signer.addKeyFromWif(wif));
    }
    return result;
  }
}

// ---------------------------------------------------------------------------
// AddressKeyManager
// ---------------------------------------------------------------------------

/**
 * Manages platform address keys and signing for address operations.
 *
 * Parallel to IdentityKeyManager but for platform address operations.
 * Derives BIP44 keys from a mnemonic and provides ready-to-use
 * PlatformAddressSigner instances.
 *
 * Platform addresses are bech32m-encoded L2 addresses (tdash1... on testnet)
 * that hold credits directly, independent of identities.
 */
class AddressKeyManager {
  /**
   * @param {EvoSDK} sdk
   * @param {AddressEntry[]} addresses
   * @param {string} network
   */
  constructor(sdk, addresses, network) {
    this.sdk = sdk;
    this.addresses = addresses; // [{ address, bech32m, privateKeyWif, path }]
    this.network = network;
  }

  /** The first derived address (index 0). */
  get primaryAddress() {
    return this.addresses[0];
  }

  /**
   * Create an AddressKeyManager from a BIP39 mnemonic.
   * Derives platform address keys using BIP44 paths.
   *
   * @param {object} opts
   * @param {EvoSDK} opts.sdk - Connected EvoSDK instance
   * @param {string} opts.mnemonic - BIP39 mnemonic
   * @param {string} [opts.network='testnet'] - 'testnet' or 'mainnet'
   * @param {number} [opts.count=1] - Number of addresses to derive
   * @returns {Promise<AddressKeyManager>}
   */
  static async create({ sdk, mnemonic, network = 'testnet', count = 1 }) {
    const addresses = [];

    /* eslint-disable no-await-in-loop */
    for (let i = 0; i < count; i += 1) {
      const pathInfo =
        network === 'testnet'
          ? await wallet.derivationPathBip44Testnet(0, 0, i)
          : await wallet.derivationPathBip44Mainnet(0, 0, i);
      const { path } = pathInfo;
      const keyInfo = await wallet.deriveKeyFromSeedWithPath({
        mnemonic,
        path,
        network,
      });
      const obj = keyInfo.toObject();
      const privateKey = PrivateKey.fromWIF(obj.privateKeyWif);
      const signer = new PlatformAddressSigner();
      const platformAddress = signer.addKey(privateKey);

      addresses.push({
        address: platformAddress,
        bech32m: platformAddress.toBech32m(
          /** @type {NetworkLike} */ (network),
        ),
        privateKeyWif: obj.privateKeyWif,
        path,
      });
    }
    /* eslint-enable no-await-in-loop */

    return new AddressKeyManager(sdk, addresses, network);
  }

  /**
   * Create a PlatformAddressSigner with the primary key loaded.
   * @returns {PlatformAddressSigner}
   */
  getSigner() {
    const signer = new PlatformAddressSigner();
    const privateKey = PrivateKey.fromWIF(this.primaryAddress.privateKeyWif);
    signer.addKey(privateKey);
    return signer;
  }

  /**
   * Create a PlatformAddressSigner with all derived keys loaded.
   * @returns {PlatformAddressSigner}
   */
  getFullSigner() {
    const signer = new PlatformAddressSigner();
    this.addresses.forEach((addr) => {
      const privateKey = PrivateKey.fromWIF(addr.privateKeyWif);
      signer.addKey(privateKey);
    });
    return signer;
  }

  /**
   * Fetch current balance and nonce for the primary address.
   * @returns {Promise<PlatformAddressInfo | undefined>}
   */
  async getInfo() {
    return this.sdk.addresses.get(this.primaryAddress.bech32m);
  }

  /**
   * Fetch current balance and nonce for an address by index.
   * @param {number} index - Address index
   * @returns {Promise<PlatformAddressInfo | undefined>}
   */
  async getInfoAt(index) {
    const entry = this.addresses[index];
    if (!entry) {
      throw new Error(
        `No derived address at index ${index} (count=${this.addresses.length})`,
      );
    }
    return this.sdk.addresses.get(entry.bech32m);
  }
}

export { IdentityKeyManager, AddressKeyManager };

Node wrapper#

setupDashClient.mjs is a thin Node-only wrapper that loads .env, builds clientConfig from process.env, exposes the one-call setupDashClient() helper, and re-exports everything from the core so Node tutorials can import IdentityKeyManager, AddressKeyManager, clientConfig, and friends from a single module.

setupDashClient.mjs#
//
// Node-only convenience wrapper around setupDashClient-core.mjs.
//
// This file adds the bits that only make sense in Node tutorials:
//   - dotenv loading (PLATFORM_MNEMONIC / NETWORK from .env)
//   - clientConfig (network + mnemonic, sourced from process.env)
//   - setupDashClient() — the one-call wrapper used by tutorial scripts
//
// Everything browser-safe (createClient, IdentityKeyManager, AddressKeyManager,
// dip13KeyPath, KEY_SPECS) is re-exported from setupDashClient-core.mjs so a
// Node tutorial that imports from this file sees an identical API surface.
//
// Browser apps should import from setupDashClient-core.mjs directly.
//
import { wallet } from '@dashevo/evo-sdk';

import {
  createClient,
  dip13KeyPath,
  KEY_SPECS,
  IdentityKeyManager,
  AddressKeyManager,
} from './setupDashClient-core.mjs';

// Load .env if dotenv is installed (optional — not needed for tutorials).
// Top-level await requires ESM — .mjs extension ensures this.
// eslint-disable-next-line import/no-extraneous-dependencies
try {
  const { config } = await import('dotenv');
  config();
} catch {
  /* dotenv not installed */
}

/** @typedef {import('@dashevo/evo-sdk').EvoSDK} EvoSDK */

// ⚠️ Tutorial helper — holds WIFs in memory for convenience.
// Do not use this pattern as-is for production key management.

// ###########################################################################
// #  CONFIGURATION — edit these values for your environment               #
// ###########################################################################
// Option 1: Edit the values below directly
// Option 2: Create a .env file with PLATFORM_MNEMONIC and NETWORK

const clientConfig = {
  // The network to connect to ('testnet' or 'mainnet')
  network: process.env.NETWORK || 'testnet',

  // BIP39 mnemonic for wallet operations (identity & address tutorials).
  // Leave as null for read-only tutorials.
  mnemonic: process.env.PLATFORM_MNEMONIC || null,
  // mnemonic: 'your twelve word mnemonic phrase goes here ...',
};

// ---------------------------------------------------------------------------
// setupDashClient — convenience wrapper
// ---------------------------------------------------------------------------

/**
 * @param {{requireIdentity?: boolean, identityIndex?: number}} opts
 * @returns {Promise<{ sdk: EvoSDK, keyManager: IdentityKeyManager | undefined, addressKeyManager: AddressKeyManager | undefined }>}
 */
export async function setupDashClient({
  requireIdentity = true,
  identityIndex = undefined,
} = {}) {
  const { network, mnemonic } = clientConfig;

  if (mnemonic && !(await wallet.validateMnemonic(mnemonic))) {
    throw new Error(
      'PLATFORM_MNEMONIC is not a valid BIP39 mnemonic. ' +
        'Run `node create-wallet.mjs` to generate one.',
    );
  }

  const sdk = await createClient(network);

  let keyManager;
  let addressKeyManager;

  if (mnemonic) {
    addressKeyManager = await AddressKeyManager.create({
      sdk,
      mnemonic,
      network,
    });

    if (requireIdentity) {
      keyManager = await IdentityKeyManager.create({
        sdk,
        mnemonic,
        network,
        identityIndex,
      });
    } else {
      keyManager = await IdentityKeyManager.createForNewIdentity({
        sdk,
        mnemonic,
        network,
        identityIndex,
      });
    }
  }

  return { sdk, keyManager, addressKeyManager };
}

// Re-export everything from the core so existing imports
// (e.g. `import { IdentityKeyManager } from './setupDashClient.mjs'`) keep working.
export {
  createClient,
  dip13KeyPath,
  KEY_SPECS,
  IdentityKeyManager,
  AddressKeyManager,
  clientConfig,
};

What’s Happening#

The setup is split across two files so the same code can run under Node and in the browser without copy-paste drift. Node tutorials import from setupDashClient.mjs (the wrapper); browser apps import from setupDashClient-core.mjs directly.

setupDashClient-core.mjs (browser-safe) provides the substantive pieces:

  • createClient() — creates a connected SDK instance for a given network (testnet, mainnet, or local).

  • IdentityKeyManager — derives 5 standard identity keys from your mnemonic using DIP-9 key paths. Each key serves a specific purpose (authentication, transfers, encryption). Call methods like getAuth() to get { identity, identityKey, signer } — everything the SDK needs to sign and submit a transaction.

  • AddressKeyManager — derives platform address keys from your mnemonic using BIP44 paths. These addresses hold credits on the L2 platform layer and are used for identity creation, top-ups, and credit transfers between addresses.

  • dip13KeyPath() / KEY_SPECS — the DIP-13 path helper and the 5-entry spec table used to build identity public keys.

setupDashClient.mjs (Node wrapper) adds the Node-only conveniences and re-exports everything from core:

  • clientConfig — your network and mnemonic, defined once. Set values directly or use a .env file with NETWORK and PLATFORM_MNEMONIC.

  • setupDashClient() — the main entry point for most tutorials. Connects to the network and creates key managers from clientConfig, returning { sdk, keyManager, addressKeyManager }.