Citrate JavaScript SDK
citrate-js is the canonical TypeScript SDK for building on Citrate Network. It wraps the chain's JSON-RPC,
the model and inference operations, and the account-abstraction stack into a typed API, so a Node or browser
app can talk to Citrate without hand-rolling calldata. This page is for the developer writing that app.
What it is
The SDK is the typed front door to Citrate Network from JavaScript and TypeScript. The package is named
citrate-js and the version of record is 0.2.0 in package.json. It is the canonical SDK; the other
language SDKs follow it and stay non-canonical until a pilot integrator needs parity.
Four surfaces sit behind one import, and most apps only ever touch the first:
- The client,
CitrateClientandWebSocketClient, for reading chain state, deploying models, and running inference against the chain (src/client/). - The account-abstraction helpers, exported under the
aanamespace, for Citrate Keyring accounts built on Kernel v3 and ERC-4337 v0.7: counterfactual address derivation, passkey and EOA signing, UserOperation building, guardian recovery, and a bundler client (src/aa/). - The cryptography utilities,
CryptoManager,KeyManager, and Shamir secret sharing (src/crypto/). - The optional React hooks (
src/react/hooks.ts), which are not re-exported from the package root.
The mental model is two layers. Reach for CitrateClient when you hold a key and want to read or write the
chain directly. Reach for aa when you want a Citrate Keyring account that a passkey controls and a paymaster
can sponsor, instead of a raw key-pair account. Passkeys are covered under passkeys and
sponsorship under the paymaster.
The aa module is labeled EW-S1 WP-7 in source and points at infrastructure that is live but still moving
(bundler.citrate.ai, auth.citrate.ai). Treat it as in development. One stale detail to know up front: the
exported VERSION constant reads 0.1.1 (src/index.ts), which trails package.json. Use package.json
as the version of record.
How to use it
The package targets Node 16 and newer (engines.node), and runs in modern browsers through the Web Crypto
API.
-
Install the package.
bashnpm install citrate-jsThe runtime dependencies are
ethers ^6.8,axios ^1.7, andeventemitter3 ^5. The React hooks needreact >=16.8andreact-dom >=16.8, which are optional peer dependencies; install them only if you use the hooks. -
Construct a client. The defaults for testnet, chain id
40204, live insrc/utils/constants.ts.typescriptimport { CitrateClient, CHAIN_IDS, DEFAULT_RPC_URLS } from 'citrate-js'; const client = new CitrateClient({ // DEFAULT_RPC_URLS[40204] is ['https://rpc.citrate.ai']. An array // enables fallback across endpoints; a bare string is the single-RPC form. rpcUrl: DEFAULT_RPC_URLS[CHAIN_IDS.TESTNET], // privateKey: process.env.CITRATE_PRIVATE_KEY, // optional; from env, never inline });The constructor validates every RPC URL and any private key before it builds a provider, so a typo fails immediately with a
ValidationErrorrather than at first use. -
Read chain state. Read methods need no key.
typescriptconsole.log(await client.getChainId()); // 40204 console.log((await client.getBalance('0xYourAddress'))); // bigint, wei -
Write to the chain. Methods that send a transaction,
deployModel,inference, andbatchInference, require aprivateKeyin the config. Without one they throw. To send a sponsored write through a Citrate Keyring account instead of a raw key, build a UserOperation with theaahelpers and submit it through the bundler client; the first-app tutorial walks the whole path.
For a step-by-step build, follow build your first app.
Reference
Each item names its export and source path in citrate-sdk-js so a reader can check it against the code.
Client, src/client/CitrateClient.ts
The constructor takes a CitrateClientConfig: rpcUrl as a string or string array, and optional
privateKey, timeout, retries, headers, and ipfsApiUrl. Exported from src/index.ts alongside
WebSocketClient (src/client/WebSocketClient.ts) for streaming and subscriptions.
| Method | Signature | What it does |
|---|---|---|
getRpcUrls() | (): readonly string[] | The current fallback list, primary first. |
getChainId() | (): Promise<number> | The chain id reported by the provider. |
getBalance(address?) | (): Promise<bigint> | Native balance in wei. |
getNonce(address?) | (): Promise<number> | Pending transaction count. |
getAddress() | (): string | undefined | The configured account address, if a key was given. |
deployModel(modelData, config) | (): Promise<ModelDeployment> | Deploy a model artifact. Needs a key. |
inference(request) | (): Promise<InferenceResult> | One inference call. Needs a key. |
batchInference(request) | (): Promise<BatchInferenceResult> | Batched inference. Needs a key. |
getModelInfo(modelId) | (): Promise<ModelInfo> | Model metadata, via citrate_getModel. |
listModels(owner?, limit=100) | (): Promise<ModelInfo[]> | List models, via citrate_listModels. |
purchaseModelAccess(modelId, amount) | (): Promise<string> | Disabled, fails closed. See failure modes. |
Account abstraction, src/aa/
Imported as a namespace, import { aa } from 'citrate-js'; the module index is src/aa/index.ts. The flow
it documents is: derive a userId, predict the address, enroll a validator, then build, sign, and send a
UserOperation. The market side of this is covered in the marketplace SDK.
- Address derivation (
src/aa/address.ts):uuidToUserId,accountIdToAaUserId,predictWalletAddress,erc1967MinimalInitCodeHash, andAddressPredictionError. The derivation is the cross-surface seam that gives one user the same account address on every device.uuidToUserIdiskeccak256(utf8(lowercase uuid));predictWalletAddress(factory, implementation, userId)returns the CREATE2 address the factory deploys the Kernel proxy to. - Kernel v3 encoding (
src/aa/kernel.ts): nonce helpersrootValidatorNonce,validatorNonceKey, andcomposeNonce;encodeExecuteSingleandencodeExecuteBatch; module managementencodeInstallModule,encodeUninstallModule, andencodeChangeRootValidator; and the install-data builderskernelInitializeCalldata,webauthnInstallData,ecdsaInstallData, andguardianInstallData. The guardian builder enforces a count in [2, 7] and a threshold in [1, count]. - UserOperation building (
src/aa/userop.ts):buildPackedUserOp,getUserOpHash,encodeDeployFor,packInitCode,packCitratePaymasterAndData,toRpcUserOperation, and the gas-packing helperspackAccountGasLimitsandpackGasFees.getUserOpHashis verified against the live EntryPoint v0.7 on chain 40204. - Passkey signing (
src/aa/webauthn.ts):signUserOpWithPasskeydrives the browser'snavigator.credentials.get(); the pure encodersencodeWebauthnValidatorSignature,parseDerEcdsaSignature,normalizeP256S, andbase64UrlEncodeare testable without a browser. The validator rejects high-s signatures, sonormalizeP256Sfoldssinto the lower half of the curve order. - EOA signing (
src/aa/eoa.ts):signUserOpWithEoa(signer, userOpHash)signs with any ethersSignerand produces the EIP-191 shape the ECDSA validator accepts. - Guardian recovery (
src/aa/recovery.ts):guardianRecoveryDigest,packGuardianSignatures,buildRotateSignerCall, andRecoveryError. The recovery op rotates the account's root validator toward a fresh passkey, validated by M-of-N guardian signatures bound to the account. - Bundler client (
src/aa/bundler.ts):BundlerClientwithsendUserOperation,estimateUserOperationGas,getUserOperationReceipt,waitForUserOperationReceipt,supportedEntryPoints, andchainId. The default endpoint isCITRATE_BUNDLER_URL,https://bundler.citrate.ai/rpc, a public RPC, not a secret. Errors surface the bundler'sAAxxcodes throughBundlerRpcError. - Types (
src/aa/types.ts):PackedUserOperation,RpcUserOperation,UserOperationReceipt,CitrateAaConfig, and thePaymasterCategoryenum (Standard,Recovery,FirstOp).
Cryptography, src/crypto/
CryptoManager(src/crypto/CryptoManager.ts): SHA-256 hashing, AES-256-GCM, and PBKDF2 over the Web Crypto API. The default work factor isPBKDF2_DEFAULT_ITERATIONS, 600,000 iterations.KeyManager(src/crypto/KeyManager.ts): key handling and model encryption, returningEncryptedModelResult. Encryption ECDH-wraps the symmetric key to the recipient and never ships it raw.- Shamir secret sharing (
src/crypto/FiniteField.ts):splitSecretBytes,reconstructSecretBytes,GF256, andShamirSecretSharing. Share coefficients are drawn from a cryptographic RNG and fail closed if none is available.
React hooks, src/react/hooks.ts
The hooks are not re-exported from the package root, because React is an optional peer dependency. Import them from the build path; each hook throws if React is not installed.
import { useCitrateClient } from 'citrate-js/dist/react/hooks';The hooks are useCitrateClient, useModelDeployment, useInference, useModelInfo, and useModelList.
Constants and errors
src/utils/constants.ts:CHAIN_IDS(MAINNET: 1,TESTNET: 40204),DEFAULT_RPC_URLS,DEFAULT_WS_URLS,PRECOMPILE_ADDRESSES,GAS_LIMITS,TIMEOUTS,MODEL_LIMITS,ENCRYPTION,EVENTS, andAPI_ENDPOINTS.src/errors/CitrateError.ts:CitrateError,ModelNotFoundError,InsufficientFundsError, andValidationError.
Design rationale
The client holds the RPC fallback list and rotates through it on transport errors rather than wrapping
ethers' automatic failover, which would double the connection budget. The trade is a little manual rotation
in exchange for a predictable connection count. The aa helpers are written as pure functions: chain reads,
nonces and deploy status, are passed in as arguments, so the same code that runs in a browser against
auth.citrate.ai and the bundler also runs against pinned test vectors. Address derivation is duplicated
byte-for-byte across the JavaScript, identity-TS, and Rust implementations on purpose, because the address a
user controls must be identical no matter which surface computes it.
Failure modes
This is the surface where a mistake costs money or leaks data, so several methods fail closed.
- A write method called without a configured key throws rather than silently doing nothing.
deployModel,inference, andbatchInferenceall requireprivateKeyin the config. - When a model deployment or inference asks for encryption but no key manager is configured, the call throws rather than uploading in plaintext. There is no silent downgrade onto public calldata.
purchaseModelAccessis disabled and throws. The canonical precompile table has no access-purchase operation; the earlier implementation routed buyer funds to the verification precompile, where access was never granted and value was never credited. It stays closed until a node-confirmed precompile exists.- Shamir share generation throws if no cryptographic RNG is present, because the scheme's secrecy depends on unpredictable coefficients.
- A bundler rejection surfaces as a
BundlerRpcErrorcarrying theAAxxcode verbatim, for exampleAA21for an unfunded prefund orAA31for a paymaster deposit too low, so the caller can act on the real cause.
Access and canon
Public. This is open SDK reference a developer needs to build on Citrate Network, and it carries no secrets.
Private keys, mnemonics, and bundler API keys are never inline; they come from the caller's environment
(config.privateKey, BundlerClientOptions.apiKey). The hostnames named here, rpc.citrate.ai,
bundler.citrate.ai, and auth.citrate.ai, are public production endpoints already shipped as defaults in
the source, not credentials. The chain on testnet is identity-checked through CLEAR for node operators; the
SDK itself holds no such data.
Source and verification
- Source repo:
citrate-sdk-js, packagecitrate-js@0.2.0(package.json). - Audited against SHA:
bc5a830. - Audited paths:
src/index.ts,src/client/CitrateClient.ts,src/client/WebSocketClient.ts,src/aa/{index,address,kernel,userop,webauthn,eoa,recovery,bundler,types}.ts,src/crypto/{CryptoManager,KeyManager,FiniteField}.ts,src/react/hooks.ts,src/utils/constants.ts, andsrc/errors/CitrateError.ts. - Status: Implemented (pre-audit). The client and cryptography surfaces are built and run against testnet 40204.
The
aamodule is Implemented but in development (EW-S1 WP-7), depends on still-moving bundler and auth infrastructure, and the exportedVERSIONconstant is stale relative topackage.json.