Common Pitfalls

By AnchorFactory ·

These are actual problems encountered building production applications on Keeta. Read this before you start building.

ESM vs CJS Import Split

The two Keeta packages have different module systems:

Package

Module System

Import Style

@keetanetwork/keetanet-client

CommonJS

require() works

@keetanetwork/anchor

ESM only

Must use import()

If you try to require() the anchor package, you'll get:

Error [ERR_REQUIRE_ESM]: require() of ES Module not supported

Fix: Always use dynamic import() for the anchor SDK:

// WRONG
const { KYC } = require("@keetanetwork/anchor/client");

// CORRECT
const { KYC } = await import("@keetanetwork/anchor/client");

publicKeyString is a Getter Object

// WRONG — returns "[object Object]"
const address = account.publicKeyString;

// CORRECT
const address = account.publicKeyString.get();

This trips up everyone. The address looks like keeta_aab... when correct.

account.sign() Return Type

account.sign() returns a special BufferStorage/ED25519Signature object, NOT a raw ArrayBuffer.

// WRONG — produces all-zero signatures
const sig = Buffer.from(await account.sign(data)).toString('hex');

// CORRECT — use the SDK's native toString
const sig = (await account.sign(data)).toString('hex');

This is the most dangerous gotcha because all-zero signatures don't crash — they just silently fail verification on the other end.

Anchor SDK Needs UserClient, Not Client

The resolver and anchor service clients require a network context that only UserClient carries:

// WRONG — resolver won't work
const client = new Keeta.Client(node);

// CORRECT
const client = await Keeta.UserClient.fromNetwork('test', null, { account });

grantAccess() Before Export

When working with SharableCertificateAttributes, you MUST call grantAccess() before exporting:

const sharable = await SharableCertificateAttributes.fromCertificate(
  cert, intermediates, ['fullName', 'email']
);

// WRONG — throws "This container has no authorized users"
const pem = await sharable.export({ format: 'string' });

// CORRECT
await sharable.grantAccess(recipientAccount);
const pem = await sharable.export({ format: 'string' });

Block Chaining

Each block must reference its previous block hash. Getting this wrong causes:

LEDGER_SUCCESSOR_VOTE_EXISTS

This error means you tried to publish a block whose previous hash doesn't match the current chain head.

Rules:

  • First block ever: use Block.NO_PREVIOUS

  • Subsequent blocks: use the hash of the last published block

  • When building multiple blocks in sequence, chain them: block2.previous = block1.hash

  • Blocks on different account chains are independent

Fee Block Required

Every publish() call requires a generateFeeBlock callback. Publishing without it will fail silently or throw.

await client.publish([block], {
  generateFeeBlock: async (staple) => {
    return feeBuilder.computeFeeBlock(staple);
  }
});

issuerDN Normalization

Certificate issuerDN can be a string, array of objects, or plain object. Always normalize:

function normalizeIssuerDN(issuerDN) {
  if (typeof issuerDN === 'string') return issuerDN;
  if (Array.isArray(issuerDN)) {
    return issuerDN.map(entry => {
      const key = Object.keys(entry)[0];
      return `${key}=${entry[key]}`;
    }).join(', ');
  }
  return Object.entries(issuerDN).map(([k, v]) => `${k}=${v}`).join(', ');
}

Browser / Service Worker Polyfills

The Keeta SDK expects Node.js globals (Buffer, process, crypto) that don't exist in browsers or MV3 service workers. You need polyfills:

// Must be imported BEFORE any Keeta SDK import
import { Buffer } from 'buffer';
globalThis.Buffer = Buffer;
globalThis.process = { env: {} };

For Chrome MV3 service workers, you also need to fake window, document, and navigator.

Bivo Shorthand Asset IDs

The Bivo anchor uses shorthand asset IDs ($USDC, $USD, $EUR) instead of full Keeta token addresses. This is undocumented and causes INTERNAL_ERROR if you pass full addresses.

More in Reference · All documentation