Skip to content

Errors

Every failure the SDK raises extends SageSdkError and carries a code. That is deliberate: matching on a code is stable, while matching on a message string breaks the first time someone improves the wording.

The instanceof check is not optional. In TypeScript a caught value is unknown, so reading error.code without narrowing first does not compile — and the narrowing is what tells you the error came from the SDK rather than from your own code or the network stack.

import { SageSdkError } from '@aephia/atlas-kit/client';
try {
await sage.characters.forProfile(profileAddress);
} catch (error) {
if (error instanceof SageSdkError && error.code === 'ACCOUNT_NOT_FOUND') {
// Handle the expected case.
}
}

ACCOUNT_NOT_FOUND — nothing exists at that address on this network. Either the address is wrong, or the RPC is pointed at a different network. This is the most common error, and the second cause is the one people miss.

INVALID_ACCOUNT_OWNER — something exists there, but it belongs to a different program. The address is for something else entirely.

INVALID_DISCRIMINATOR — the account exists and belongs to SAGE, but it is a different account type than this read expected.

INVALID_ENTITY_ID — the value is not a valid address.

MISSING_GAME_CONTEXT — a non-preset cluster was used without supplying a Game address.

RELATIONSHIP_NOT_DISCOVERABLE — the relationship has no derivable address and no discovery provider was configured. See identity for the common case.

PROVIDER_ERROR — the RPC endpoint itself failed. Often rate limiting.

RESOURCE_LIMIT_EXCEEDED — a response exceeded a configured safety bound.

Many reads have a maybe* form that returns undefined instead of throwing:

const standing = await maybeGetFactionStanding(ctx, profileAddress, factionId);

The distinction is intent. Use get* when absence would be a bug in your application, and maybe* when absence is an ordinary outcome. A profile with no faction standing is not an error; a fleet address that resolves to nothing probably is.

Validation happens before decoding: program owner, discriminator, data shape, minimum length. Data that fails becomes a typed error and is never cached as valid state.

This is worth internalising, because the alternative is worse than it sounds. Without it, a truncated or mistaken RPC response becomes a plausible-looking value deep inside your application, and the bug surfaces somewhere unrelated, much later.