@witnium-tech/witniumchain
v0.10.0
Published
TypeScript SDK for WitniumChain — identity, billing, OAuth, witness writes (v5), key management, and chain-api read surfaces (contracts, witnesses, wallets, transactions, dashboards). Single canonical SDK; supersedes @witnium-tech/accounts-sdk and @witniu
Downloads
1,138
Maintainers
Readme
@witnium-tech/witniumchain
TypeScript SDK for WitniumChain — the canonical single client for both the
accounts service (identity, billing, OAuth, delegated keys, witness writes)
and the chain-api read surfaces (contract info, witness lookup, wallet
balance, dashboards). The two-service backend is hidden behind one
WitniumchainClient class; you give it one OAuth access token and it knows
where each call belongs.
Status
v0.5 — adds OAuth 2.1 + PKCE login helpers on top of the v0.4 surface:
- All 41 accounts routes (five auth modes: session / OAuth / org / admin / signed)
- 9 chain-api read methods, routed to
chainBaseUrl, reusing the same OAuthaccessToken(accounts mints tokens withaud=https://api.witniumchain.com, so they're valid against both services unchanged) - Three layered clients on top:
WitniumchainClient(end-user),WitniumchainOrgClient(org admin),WitniumchainAdminClient(sysadmin) beginOAuthLogin/completeOAuthLogin/refreshAccessToken/signOut— browser-side Authorization-Code + PKCE login flow. Discovery-driven endpoint resolution (/.well-known/openid-configuration), PKCE verifier insessionStorage, access token held in memory only, transparent 401-retry with single-flight refresh-token rotation. Refresh tokens are delivered as an HttpOnlywac_refreshcookie scoped toPath=/token— JavaScript never touches them. Non-browser callers (Node SSR, native apps without a cookie jar) fall back to in-band refresh-token delivery; the SDK handles both.client.mfa.totp.enroll/confirm/disable+client.mfa.recoveryCodes.regenerate— TOTP MFA self-management.enrollreturns the secret + otpauth URL (render as a QR code in your dashboard with any QR lib);confirmvalidates the first code and returns 10 single-use recovery codes (shown ONCE).
What's NOT exposed (by design): chain-api v5 writes — those are proxied by
accounts' v5 surface which adds credit reservation + idempotency + scope
checks. Calling chain-api writes directly would burn credits without
billing them. See docs/PLAN-PHASE-SDK-UNIFIED.md for the routing rules.
Every type comes from the published OpenAPI specs of both services via
openapi-typescript. npm run openapi:check regenerates and fails on
diff; it runs ahead of every npm publish.
Install
npm install @witnium-tech/witniumchainAuth model
The SDK accepts five distinct credentials. Configure whichever you need on the client; methods that require a credential you didn't supply throw at call time.
| Credential | Header / Cookie | Used by |
|---|---|---|
| sessionCookie | Cookie: wac_session=… | /v1/auth/logout, /v1/account/*, /v1/billing/*, /v1/keys/*, /v1/contracts/{pause,unpause}, /v1/oauth/sessions* |
| accessToken | Authorization: Bearer <JWT> | /v1/users/me/delegated-keys/*, /v1/sign, all chain-api reads |
| orgApiKey | Authorization: Bearer wcorg_live_… | /v1/orgs/me/* |
| adminToken | Authorization: Bearer <ADMIN_TOKEN> | /v1/admin/* |
| signedRequest | X-Witnium-Key/Timestamp/Signature | /v1/contracts/{addr}/witnesses/{propose,sign,finalize,revoke} |
Public routes need no credential (/v1/auth/{signup,verify,login,…},
/v1/contracts/provision, GET /v1/contracts/{addr}/witnesses/{id},
/health/*).
Two services, one client
Accounts and chain-api are separate services today (consolidation is a later phase). The SDK hides that:
const client = new WitniumchainClient({
baseUrl: 'https://auth.witniumchain.com', // accounts
chainBaseUrl: 'https://api.witniumchain.com', // chain-api (omit if you only need accounts)
accessToken: oauthBearerJwt,
});
// Writes / billing / auth → accounts (baseUrl)
await client.proposeWitnessV5('0xabc', { ... });
// Reads of on-chain state → chain-api (chainBaseUrl)
const witness = await client.getWitnessV5('0xabc', witnessId);
const balance = await client.getWalletBalance('0xabc');If you call a chain-api method without chainBaseUrl configured, the SDK
throws a clear WitniumchainApiError at call time naming the missing
config — no silent fallback to a wrong base URL.
Examples
End-user signup + login
import { WitniumchainClient } from '@witnium-tech/witniumchain';
const client = new WitniumchainClient({
baseUrl: 'https://auth.witniumchain.com',
});
await client.signup({ email: '[email protected]', password: 'correct horse battery staple' });
// User clicks email link, lands on /verify?token=…
const { provisioningToken } = await client.verifyEmail('the-token-from-the-link');
// Then login (sets the wac_session cookie in a browser; in Node, capture
// from response headers and pass back via `sessionCookie`).
await client.login({ email: '[email protected]', password: '…' });Org admin — create a user
const org = new WitniumchainClient({
baseUrl: 'https://auth.witniumchain.com',
orgApiKey: 'wcorg_live_…',
});
const { userId, provisioningToken } = await org.createOrgUser({
email: '[email protected]',
});
// Forward `provisioningToken` to Bob — he calls /v1/contracts/provision
// with locally-generated owner + signing keypairs.Sysadmin — create an organisation
const sys = new WitniumchainClient({
baseUrl: 'https://auth.witniumchain.com',
adminToken: process.env.ACCOUNTS_ADMIN_TOKEN!,
});
const { organization, apiKey } = await sys.createOrganization({
name: 'Acme Inc.',
email: '[email protected]',
accountType: 'metered',
signupGrantAmount: 100,
});
// `apiKey` is shown ONCE — store it now.Sign in with Witnium (Authorization Code + PKCE)
For a browser SPA (e.g. a Lovable app) that wants to authenticate end-users against a Witnium org. No backend required.
const client = new WitniumchainClient({
baseUrl: 'https://auth.witniumchain.com',
oauthClientId: 'your-registered-client-id',
});
// 1. Kick off login. The SDK generates a PKCE pair, stashes the verifier in
// sessionStorage, and returns the URL to send the user to.
const { authorizationUrl } = await client.beginOAuthLogin({
redirectUri: 'https://your-app.example/callback',
});
window.location.assign(authorizationUrl);
// 2. On the callback page, complete the exchange. Returns the access token
// (the SDK ALSO holds it in memory; subsequent BearerJWT calls Just Work).
const { accessToken, expiresAt } = await client.completeOAuthLogin(
window.location.href,
);
window.history.replaceState({}, '', window.location.pathname); // strip ?code=…
// 3. Use the SDK normally. When the access token expires, the SDK refreshes
// transparently on the next call. You only need to redirect to login if
// refresh itself fails (refresh token revoked or expired).
const { keys } = await client.listDelegatedKeys();
// 4. Sign-out clears in-memory tokens.
client.signOut();OAuth API — delegated-key + sign
const api = new WitniumchainClient({
baseUrl: 'https://auth.witniumchain.com',
accessToken: bearerJwt,
});
const prepared = await api.prepareDelegatedKey({ contractAddress: '0x…' });
// Sign prepared.messageToSign with your owner key locally:
const ownerSignature = await myOwnerSigner.sign(prepared.messageToSign);
const submitted = await api.submitDelegatedKey(prepared.id, { ownerSignature });
// submitted.confirmed === true once the addSigningKey tx mines (~10–15 s).
const { signature } = await api.sign(
{ delegatedKeyId: prepared.id, payload: 'deadbeef…' },
);SDK signed-request (witness propose/sign/finalize)
const sdk = new WitniumchainClient({
baseUrl: 'https://auth.witniumchain.com',
signedRequest: {
publicKeyHex: 'abcd…64-chars…',
sign: async (canonicalMessage) => {
// Sign with your Ed25519 key — e.g. via @noble/ed25519 or a KMS.
// Return the 128-char hex signature.
return await myEd25519Sign(canonicalMessage);
},
},
});
const intent = await sdk.proposeWitness('0x…', {
dataId: '…64-hex…',
requiredSigners: ['…signer-pubkey…'],
});Errors
All non-2xx responses surface as WitniumchainApiError:
import { WitniumchainApiError } from '@witnium-tech/witniumchain';
try {
await client.createOrgUser({ email: '[email protected]' });
} catch (err) {
if (err instanceof WitniumchainApiError) {
console.error(err.status, err.errorLabel, err.message);
// err.body holds the raw parsed body for advanced inspection.
}
}License
MIT.
