@ensc/sdk
v0.1.2
Published
Official ENSC API client SDK — typed, server-side, with automatic Ed25519 request signing
Downloads
240
Maintainers
Readme
@ensc/sdk
Official client SDK for the ENSC API — typed end to end, server-side, with automatic Ed25519 request signing.
Install
npm install @ensc/sdk
# the web3 helper (optional) also needs viem:
npm install @ensc/sdk viemRequires Node 18+ (or any modern runtime with fetch — Workers, Deno, Bun).
Server-side only
The SDK is built for backend use. Your API key and signing key are secrets — they must never reach a browser bundle. EnscClient refuses to construct in a browser context. Do not ship it to end users.
Authentication: two secrets and one identifier
The SDK needs three things, down from the old SDK's three secrets:
| Value | Secret? | Purpose |
| --- | --- | --- |
| apiKey | yes | Authorization: Bearer. Identifies the merchant, carries env + scopes. |
| signingPrivateKey | yes | Ed25519 private key (base64url). Signs mutating requests. Reads don't need it. |
| signingKeyId | no | The id of the registered public key. Sent as X-ENSC-Key-Id. |
Plus merchantId (mrc_…) — not a secret, but required because it's part of the canonical string every signed request is built from.
There is no payload encryption. Mutating requests are authenticated by an Ed25519 signature over a canonical representation of the request; nothing in the body is encrypted client-side.
Quick start
import { EnscClient } from '@ensc/sdk';
const ensc = new EnscClient({
apiKey: process.env.ENSC_API_KEY!,
merchantId: process.env.ENSC_MERCHANT_ID!,
signingPrivateKey: process.env.ENSC_SIGNING_PRIVATE_KEY, // for writes
signingKeyId: process.env.ENSC_SIGNING_KEY_ID, // for writes
});
// Read — needs only apiKey + merchantId
const balance = await ensc.balance.get({
account: '0x…',
chain: 'lisk',
asset: 'ENSC',
});
// Write — needs the signing key; signed automatically
const { unsignedTransaction, paymentIntentId } = await ensc.mint.create({
recipient: '0x…',
amount: '100',
chain: 'lisk',
});First-time setup: register a signing key
Generate the keypair locally — the private half never leaves your process:
import { EnscClient } from '@ensc/sdk';
const ensc = new EnscClient({ apiKey, merchantId }); // reads work without a signing key
const { registration, keypair } = await ensc.signingKeys.generate({
env: 'live',
name: 'production-server',
});
// Store keypair.privateKey as ENSC_SIGNING_PRIVATE_KEY
// Store registration.id as ENSC_SIGNING_KEY_ID
// The API only ever saw keypair.publicKey.The web3 flow: unsigned transactions
ENSC never holds a wallet key and never broadcasts. mint, redeem, transfer, and approve return an unsigned transaction for you to sign with your own wallet key and submit to the chain. The ENSC indexer reconciles the on-chain event afterward.
const { unsignedTransaction } = await ensc.mint.create({
recipient: '0x…', amount: '100', chain: 'lisk',
});
// Option A — sign + broadcast with the optional helper (needs `viem`)
import { signAndBroadcast } from '@ensc/sdk/web3';
const txHash = await signAndBroadcast(unsignedTransaction, walletPrivateKey, {
rpcUrl: 'https://rpc…',
});
// Option B — sign here, broadcast through your own infra
import { signTransaction } from '@ensc/sdk/web3';
const signed = await signTransaction(unsignedTransaction, walletPrivateKey);walletPrivateKey is your on-chain EOA key — a separate secret from the ENSC API keys. It never touches the ENSC API and is never stored by the SDK; pass it per call. Do not put it in EnscClient config.
redeem returns an unsigned transaction plus settlement details; after broadcasting it, call ensc.withdraw.create({ pvRef, txHash }) to fire the fiat payout.
Verifying webhooks
ENSC signs every webhook delivery with Ed25519. Verify before trusting the payload — pass the exact raw body you received:
import { EnscClient } from '@ensc/sdk';
// In your webhook route:
const event = EnscClient.constructEvent({
body: rawRequestBody, // string or bytes, exactly as received
headers: request.headers, // Headers instance or a plain record
publicKey: ENSC_WEBHOOK_PUBLIC_KEY,
});
// reaching here means the signature is validconstructEvent throws EnscError('ENSC_INVALID_SIGNATURE') on failure. For non-throwing checks use EnscClient.verifyWebhookSignature(...), which returns { valid, reason? }.
Error handling
Every failure — including network errors — throws a single EnscError type, with the same code taxonomy the API uses.
import { EnscError, isEnscError, isEnscErrorCode } from '@ensc/sdk';
try {
await ensc.mint.create({ … });
} catch (err) {
if (isEnscErrorCode(err, 'ENSC_MINT_LIMIT_EXCEEDED')) {
// handle the specific case
} else if (isEnscError(err)) {
console.error(err.code, err.status, err.message, err.details);
}
}Transient failures (network errors, 5xx) are retried automatically (maxRetries, default 2); 4xx is never retried. Mutating requests carry a stable idempotency key across retries, so a transparently retried POST cannot double-execute.
Chains
chain is a string at the API boundary. The SDK exports a ChainSlug union and KNOWN_CHAINS for autocomplete, but any string is accepted — an unknown or disabled chain comes back as ENSC_INVALID_CHAIN.
API surface
| Area | Resource | Methods |
| --- | --- | --- |
| Web3 | mint redeem transfer approve | create |
| Web3 | withdraw virtualAccounts | create |
| Reads | balance mintLimit | get |
| Reads | verifyPayout | verify, verifyWithdraw |
| Reads | events | list, get |
| Management | apiKeys | create, list, rotate, revoke |
| Management | signingKeys | register, generate, list, revoke |
| Management | origins | create, list, remove |
| Management | webhookEndpoints | create, list, get, update, remove, sendTest |
Verifying this package
@ensc/sdk is published only by GitHub Actions, from sdk-v* release tags, using npm trusted publishing (OIDC) — there is no long-lived npm token. Every published version is registry-signed; run npm audit signatures after installing to verify it. A legitimate release has exactly two runtime dependencies — @noble/curves and @noble/hashes — an optional viem peer, and no install scripts; anything else is a red flag. (A provenance attestation is planned — it requires publishing from a public repo.) For full consumer guidance — pinning, release cooldowns, --ignore-scripts — and to report a suspected compromise, contact [email protected].
License
MIT
