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 |
|---|---|---|
| CommonJS |
|
| ESM only | Must use |
If you try to require() the anchor package, you'll get:
Error [ERR_REQUIRE_ESM]: require() of ES Module not supportedFix: 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_EXISTSThis 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_PREVIOUSSubsequent blocks: use the hash of the last published block
When building multiple blocks in sequence, chain them:
block2.previous = block1.hashBlocks 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.