Create your first SDK client
Learn the Core SDK by bootstrapping a client, completing email OTP, and reading an Account.
This tutorial builds a small Node script with the Core SDK client. You will bootstrap from a publishable key, complete email OTP, and read the signed-in user's logical Account.
Before you start
You need Node.js, a Capxul publishable key, and access to the email address used for the tutorial. Create an empty project, then install the current alpha as an exact dependency:
npm init -y
npm install --save-exact @capxul/sdk@alpha tsxSet CAPXUL_PUBLISHABLE_KEY and CAPXUL_TUTORIAL_EMAIL in the environment that
will run the script. The publishable key is safe to use in a client, but it is
locked to the application origins configured when it was created.
1. Create the client
import { createCapxulClient } from "@capxul/sdk";
const created = await createCapxulClient({
publishableKey: process.env.CAPXUL_PUBLISHABLE_KEY!,
requirement: "counterfactual",
});
if (!created.ok) {
console.error(created.error.code, created.error.message);
process.exit(1);
}
const client = created.value;The publishable key identifies the application and resolves platform
configuration. counterfactual asks the client to prepare a receivable Account
after sign-in without requiring full deployed activation.
2. Request an OTP
const email = process.env.CAPXUL_TUTORIAL_EMAIL!;
const code = process.argv[2];
if (!code) {
const requested = await client.auth.signIn({ email });
if (!requested.ok) throw requested.error;
console.log("Check your email, then run this script again with the one-time code.");
process.exit(0);
}The result is a value. Expected failures such as invalid input or rate limiting
do not need try/catch; narrow on ok.
3. Verify the code
When the script receives a code argument, verify it without requesting a new code:
const verified = await client.auth.verifyOtp({ email, code });
if (!verified.ok) throw verified.error;
console.log(`Signed in until ${new Date(verified.value.expiresAt).toISOString()}`);Successful verification stores the session for this process and starts the configured Account readiness lane in the background.
4. Wait for Account readiness
OTP verification does not wait for Account setup. Poll the public lifecycle until the requested readiness is available, and surface setup failure explicitly:
const readinessDeadline = Date.now() + 60_000;
while (true) {
const lifecycle = await client.account.getLifecycle();
if (!lifecycle.ok) throw lifecycle.error;
if (lifecycle.value.status === "ready") break;
if (lifecycle.value.status === "failed") throw lifecycle.value.error;
if (Date.now() >= readinessDeadline) throw new Error("Account setup timed out");
await new Promise((resolve) => setTimeout(resolve, 500));
}5. Read the Account
const account = await client.accounts.read();
if (!account.ok) throw account.error;
console.log({
accountId: account.value.id,
balance: account.value.balance.value,
currency: account.value.balance.currency,
available: account.value.available.value,
});The public result uses Money, not chain units. Your application does not need
an RPC URL, address, or chain ID for this read.
6. Close owned resources
The public factory owns its production resources. When a short-lived process is done, close the client if a close hook is present:
await client._internal.close?.();_internal is lifecycle substrate, not a domain API. Do not build product
behavior on other members under that namespace.
Run the script once to request the OTP, then run it again with the code as the first argument:
npx tsx first-client.ts
npx tsx first-client.ts 123456What you learned
You used the SDK independently of React, treated failures as typed values, and worked in product vocabulary. Continue with the OTP guide, readiness guide, or client reference.