@cef-ai/vault-sdk
v5.7.0
Published
Client SDK for apps talking to a user's CEF Vault from outside the Agent Runtime: chat UIs, Slack bots, data pipelines, integrations.
Maintainers
Keywords
Readme
@cef-ai/vault-sdk
Client SDK for apps talking to a user's CEF Vault from outside the Agent Runtime: chat UIs, Slack bots, data pipelines, integrations.
Install
pnpm add @cef-ai/vault-sdkQuickstart
Browser app, wallet already registered as a gateway proxy on-chain (the
common case once onboarding has run once via @cef-ai/account):
import { VaultSDK, type EnsureProgressEvent } from "@cef-ai/vault-sdk";
const sdk = new VaultSDK({
endpoint: "https://vault.cere.io",
garEndpoint: "https://gar.cere.io",
chainUrl: "wss://rpc.cere.network", // the vault-delegate grant step
s3GatewayAuthInfoUrl: "https://ddc-s3-gateway.compute.dev.ddcdragon.com/auth/info",
// Override to point at the prod marketplace; defaults to the dev cluster URL.
marketplaceEndpoint: "https://agent-marketplace.compute.dev.ddcdragon.com",
signer: mySigner, // any `@cef-ai/signer` Signer
});
const vault = await sdk.vault.ensure({
onProgress: (e: EnsureProgressEvent) => console.log(e.kind),
});
await vault.scope("default").publish({
type: "user_message",
context: "conv-42",
payload: { text: "Hi" },
});Server-side (pre-provisioned wallet, skip onboarding):
import { VaultSDK, CereWallet } from "@cef-ai/vault-sdk";
const signer = await CereWallet.fromMnemonic(process.env.WALLET_MNEMONIC!);
const sdk = new VaultSDK({
endpoint: "https://vault.cere.io",
garEndpoint: "https://gar.cere.io",
chainUrl: "wss://rpc.devnet.cere.network/ws",
s3GatewayAuthInfoUrl: "https://ddc-s3-gateway.compute.dev.ddcdragon.com/auth/info",
signer,
});
const vault = await sdk.vault.ensure({ onboard: false });Onboarding
vault-sdk depends on no @cere-ddc-sdk/* package and stays chain-free at
module load — the chain stack (@cef-ai/account → @cef-ai/chain) is
loaded lazily, only when vault.ensure() actually runs the vault-delegate
grant step. vault.ensure() also runs a lightweight, HTTP-only onboarding
status check by default:
- Status check —
GET /auth/onboarding/status(404 means prod, skip the rest of this section entirely). - Bootstrap (devnet only) — if
faucet_eligibleorddc_seed_eligible,POST /auth/onboarding/bootstrapto drip CERE and seed the DDC deposit. - If the gateway reports the wallet is still not a registered proxy
(
gateway_is_proxy: false),vault.ensure()throwsOnboardingRequiredErrorinstead of trying to submit the on-chain extrinsic itself.
Registering the gateway as a proxy is now done externally, via
@cef-ai/account's provisioning.ensure:
import { provisioning } from "@cef-ai/account";
await provisioning.ensure({
signer: myChainSigner, // a `@cef-ai/signer` Signer
chainUrl: "wss://rpc.devnet.cere.network/ws",
gateway: gatewaySs58Address,
});
// Now retry — the gateway will see the proxy and `ensure()` will proceed.
const vault = await sdk.vault.ensure({ onProgress: (e) => console.log(e.kind) });onProgress receives an EnsureProgressEvent for each stage:
| Kind | Meaning |
| --- | --- |
| inspecting-wallet | Gateway status probe is in flight. |
| funding-wallet | Devnet auto-fund (CERE drip + DDC seed deposit); never fires on prod. |
| gateway-authorization-required | Onboarding required; carries status: OnboardingStatus. ensure() throws OnboardingRequiredError right after this fires. |
| authorizing-vault | Granting vault-api write delegation: ensureVaultDelegate checks/submits the on-chain NonTransfer proxy. Only fires on the default (tokenless) path. |
| provisioning-vault | Calling POST /api/v1/vaults to claim the record. |
One error can surface from the pipeline:
OnboardingRequiredError— the gateway reports the wallet still needs the on-chain proxy step (whetheronboardwas left at its default or set tofalse). Run@cef-ai/account'sprovisioning.ensureexternally, then retryensure().
Write delegation (the claim's authorization)
By default vault.ensure() claims the vault tokenless: after the
onboarding check it grants vault-api's delegate key an on-chain
NonTransfer proxy (@cef-ai/account's ensureVaultDelegate, which
discovers the key from GET {endpoint}/api/v1/delegate-info and skips
submission when the grant already exists), then POSTs
{name, label: "vault"} — vault-api routes that shape through its proxy
saga. This needs a signer and a chainUrl (constructor or per call);
without a chainUrl the wallet path fails with a config error.
opts.delegationToken is the legacy escape hatch: when passed, the grant
step is skipped entirely and the token-bearing claim body of the old flow
is sent instead (vault-api accepts both shapes). Nothing in this SDK mints
DDC delegation tokens any more — the @cef-ai/account native builder was
removed with the migration; token-holding callers bring their own.
Connecting an agent
vault.agents.connect({ agentId, scope, settings }) is the high-level entry
point. It builds the v2 agreement payload, signs it with your wallet, POSTs
the signed envelope to the Global Agent Registry (GAR), and then calls
vault-api POST /agents with the resulting signature — all internally. The
agent-service pubkey the GAR agreement is keyed on is derived from the
agentId prefix (<agentServicePubkey>:<alias>); pass agentServicePubkey
explicitly to override. version/manifestCid are not sent — the vault-api
resolves the manifest from the DDC registry itself, so no prior getAgent
call is required to connect.
// Optional: fetch the display card to render before connecting.
const { agentId, card } = await sdk.marketplace.getAgent("personal-assistant");
console.log(card.name, card.description);
const handle = await vault.agents.connect({
agentId,
scope: "default",
settings: { language: "en" },
});
console.log(handle.agentId, handle.status);Requires garEndpoint and a signer on VaultSDKConfig. If you
already have a pre-signed agreementSignature (e.g. from a custodial flow),
drop down to the low-level Agents.connect(vaultId, ConnectAgentRequest)
exported from @cef-ai/vault-sdk/internal.
Surface
| Method | Maps to |
| --- | --- |
| sdk.vault.ensure({ onboard?, onProgress?, chainUrl?, delegationToken? }) | onboarding check + on-chain vault-delegate grant + POST /api/v1/vaults (tokenless by default) |
| sdk.vault.current() | GET /api/v1/vaults |
| sdk.vault.delegations.grant(vaultId, { vaultBucketId, registryBucketId, expiresAt? }) — owner only; mints the vault and registry delegation tokens with the configured signer (two signatures) and sends them; re-run to renew before delegationsExpireAt | GET /api/v1/delegate-info + PUT /api/v1/vaults/:v/delegations |
| sdk.vault.delegations.put(vaultId, { vaultDelegationToken, registryDelegationToken }) — tokens minted with mintDelegationTokens(...) | PUT /api/v1/vaults/:v/delegations |
| vault.scopes.{list,get,create,update,delete} | /api/v1/vaults/:v/scopes |
| vault.agents.connect({ agentId, scope, settings }) (signs GAR internally) | POST {garEndpoint}/api/v1/agreements + POST /api/v1/vaults/:v/agents |
| vault.agents.{list,get} → AgentConnectionHandle | /api/v1/vaults/:v/agents |
| handle.update(settings) / handle.disconnect() | PATCH / DELETE |
| handle.setCeiling({ gpuUnits?, a2aTokens? }) — spend ceiling; gpuUnits = platform-metered, a2aTokens = agent self-reported (integer); absent/0 = no ceiling, {} clears both; read back as handle.ceiling (absent = none) | PUT /agents/:a/ceiling |
| handle.cubby(alias).query(...) / .exec(...) | /scopes/:s/cubbies/:alias/{query,exec} |
| handle.jobs.list({ state?, cursor?, limit? }) | /agents/:a/jobs |
| vault.scope(s).publish(input) | POST /scopes/:s/events |
| vault.scope(s).subscribe(filter, handler, opts?) | poll-backed long-poll over /streams/:c/events |
| vault.scope(s).subscribeAll(filter, handler, { refreshIntervalMs? }) | fans out across streams.list(); refreshes every 30 s by default |
| vault.scope(s).streams.list() / .get(c) / .stream(c).events.list() | /scopes/:s/streams[/:c[/events]] |
| vault.jobs.list({ state? }) / .get(jobId).activities.list() | /jobs[/...] |
| handle.jobs.get(jobId).activities.subscribe({ since? }, handler) | poll-backed activity tail with seen-id dedupe |
| sdk.marketplace.getAgent(id, version?) → { agentId, agentServicePubkey, version, card } | public GET /api/v1/marketplace/agents/:id[/versions/:v] |
| sdk.marketplace.list({ limit?, cursor?, query? }) | public GET /api/v1/marketplace/agents |
| sdk.health.live() / .ready() | public GET /health / /ready |
VaultSDKConfig knobs
| Field | Description |
| --- | --- |
| endpoint: string | Base URL for the vault API (wallet-authenticated). |
| chainUrl?: string | Cere node WS URL. Required on vault.ensure()'s wallet path (signer + no delegationToken): drives the on-chain vault-delegate grant. Also settable per call. |
| garEndpoint?: string | Base URL for the Global Agent Registry. Required for vault.agents.connect. |
| marketplaceEndpoint?: string | Base URL for the agent marketplace API (separate host from the vault API; reads are unauthenticated). Default: dev cluster URL (https://agent-marketplace.compute.dev.ddcdragon.com). |
| s3GatewayAuthInfoUrl?: string | Gateway /auth/info URL; its dirname is the gateway base the onboarding pipeline talks to. Default: dev cluster gateway. |
| signer?: Signer / auth?: AuthProvider | Authenticated calls. Mutually exclusive — auth overrides signer. Signer is @cef-ai/signer's canonical interface. |
| fetch?: typeof fetch | Inject a custom fetch for tests / non-browser runtimes. |
| timeoutMs?: number | Per-request timeout (default 30 s). |
Scope.metadata: Record<string, unknown> is a free-form, server-stored map.
vault.scopes.update(name, { metadata }) is a partial patch — server
semantics are shallow-merge per top-level key (keys in the patch overwrite,
others are preserved). The SDK whitelists the request body so only the keys
you supply reach the wire.
Pluggable auth
VaultSDK accepts either a signer (default → WalletAuthProvider) or a
fully custom auth: AuthProvider:
new VaultSDK({ endpoint, auth: new BearerAuthProvider(token) });Ships with WalletAuthProvider and NoAuth. Implement AuthProvider
yourself to plug in delegation tokens, trusted-headers, or anything else.
Signers
The SDK consumes a @cef-ai/signer.Signer — the canonical, chain-free signer
interface shared across the SDK suite: { type, address, publicKey: Uint8Array,
isReady(), sign(bytes, intent?) }. vault-sdk 3.0.0 retired its bespoke Wallet
interface; pass any Signer directly.
KeypairWallet.fromSeed(bytes)— raw 32-byte ed25519 seed (server use); implementsSigner.CereWallet.fromMnemonic(mnemonic, type?)— wrapsUriSigner; implementsSigner.CereWallet.fromKeystore(json, passphrase, type?)— Cere/Polkadot JSON keystore; implementsSigner.
Browser apps using the CEF wallet pass embedWallet.asSigner() straight into
new VaultSDK({ signer }) — it already satisfies Signer, so no adapter is
needed (this replaces the old 3-method Wallet shim).
The UriSigner / JsonSigner classes powering CereWallet are vendored
from @cere-activity-sdk/signers
(Apache-2.0); see src/wallet/cere-internal/README.md for attribution and
divergences. We dropped the runtime dep because the upstream package is
neglected; the underlying @polkadot/keyring, @polkadot/util and
@polkadot/util-crypto are pulled in directly.
Browser-extension and CereWalletNative adapters live in their own packages.
Lower-level access
@cef-ai/vault-sdk/internal re-exports Vaults, Scopes, Agents,
Events, Browse, Cubbies, Health, Marketplace, plus
VaultHttpClient and endpoints.
Onboarding primitives (runOnboarding, pollUntilProxyReady,
getOnboardingStatus, runBootstrap, signerToOnboardingSigner) live
under their respective files in src/internal/ for advanced consumers that
need to drive the HTTP-only status/bootstrap pipeline piecewise. Use them
only when the fluent surface isn't enough. On-chain proxy registration is
not part of this SDK at all — see @cef-ai/account's provisioning.ensure
(gateway proxy) and ensureVaultDelegate (vault-api write delegate).
Spec
See ../../specs/platform/03-components/vault-sdk.md (internal
sibling repo) for the design contract.
