Quickstart
This is the shortest real path from nothing to an agent that can make an autonomous payment. It is grounded in Tidal's accepted developer-platform and SDK contract. Because the platform is still being built, some specifics are marked Draft — to be finalized; the shape is accurate, but treat version numbers, availability, and any exact wire detail as provisional until the SDK ships.
Draft — to be finalized. The SDK package, its entry points, and the initialization and payment surfaces below come directly from the accepted contract. There is not yet a publicly accepted hosted staging target, and part of the payment surface currently ships as smaller building blocks (noted in step 5). Start against sandbox / Testnet; Mainnet is gated behind a separate review.
Before you start
You will do all of this from a server-side backend. The Tidal agent SDK's signing and credential surface is server-only on purpose — it must never end up in a browser, extension, or React Native bundle. You will need Node.js, and for anything beyond local development, a cloud key store (AWS KMS or GCP KMS) to hold the agent's private key.
1. Create a developer account and credentials
Onboard your developer organization and an application client in the Tidal developer portal
(portal.t54.ai), and create an environment-scoped API credential for the sandbox environment.
Sandbox and Mainnet are separate security domains — credentials, agent keys, and domain bindings are
never shared across them.
The credential has three parts, and the split matters:
apiKey— a public identifier for your application (prefixt54_pk_). It identifies your app but never proves user consent and never authorizes a payment by itself.apiSecret— an Ed25519 request-signing private key (prefixt54_sk_), not a bearer token. Your backend generates it locally; the portal only ever receives the public half.kid— a key-version identifier (prefixt54_kid_) naming which key version signs a request.
Because apiSecret is a signing key that stays on your side, the portal cannot display, recover, or
store it. Keep it in your secret manager; every authenticated request to Tidal is signed with it.
2. Install the SDK
npm install @xrpl-agent-wallet/agent-wallet-sdk
The package exposes distinct entry points by runtime. You will use the server entry point and one keystore adapter:
| Entry point | Use |
|---|---|
@xrpl-agent-wallet/agent-wallet-sdk/server | The authenticated client: init, createAgent, authorization, and payment. Server-side only. |
@xrpl-agent-wallet/agent-wallet-sdk/keystore/aws-kms | Agent key in AWS KMS (secp256k1). Production-eligible. |
@xrpl-agent-wallet/agent-wallet-sdk/keystore/gcp-kms | Agent key in GCP KMS (Ed25519). Ships fail-closed until an accepted live vector — see note. |
@xrpl-agent-wallet/agent-wallet-sdk/keystore/encrypted-file | Local encrypted file, development only, refuses Mainnet. |
@xrpl-agent-wallet/agent-wallet-sdk/public | Browser/extension/RN-safe types and link helpers. No secrets. |
Draft — to be finalized. The GCP (Ed25519) adapter is specified but ships fail-closed until a live mixed-algorithm Testnet signing vector is independently accepted. For a first integration, use
encrypted-filelocally and plan foraws-kmsin production.
3. Initialize the server SDK
Initialization validates your credential locally, proves the app and environment binding with a signed challenge, and freezes your key store's capabilities. It refuses to expose any key-generation or payment method until those checks pass — there is no anonymous or degraded mode.
import { init } from "@xrpl-agent-wallet/agent-wallet-sdk/server";
import { encryptedFileKeyStore } from "@xrpl-agent-wallet/agent-wallet-sdk/keystore/encrypted-file";
const sdk = await init({
apiKey: process.env.TIDAL_API_KEY!, // t54_pk_...
apiSecret: process.env.TIDAL_API_SECRET!, // t54_sk_... (never logged)
keyStore: encryptedFileKeyStore({ /* dev-only config */ }),
environment: "sandbox",
});
4. Create an agent
createAgent registers an agent under your opaque identifiers. The key is created in your key
store; only the public key and opaque references ever reach Tidal. Calls are idempotent on
(org, app, environment, userId, agentId): the same tuple with the same public key returns the
existing record, and a retry after local key creation reuses the same key rather than minting a new
one.
const agent = await sdk.createAgent("user-42", "research-agent");
// agent.record holds the immutable directory record (public key, XRPL account, generation, ...)
userId and agentId are yours to choose, but they must be opaque — no emails, names, or secret
material. To resume an existing agent later without side effects, use openAgent(userId, agentId),
which fails closed if the local key record and the Tidal directory record do not match exactly.
5. Request authorization, then pay
Your app asks the user to authorize the agent within limits. requestAuthorization returns a public
App Link / QR payload — routing handles only, never a secret — that you present to the user; they
approve it in their Tidal wallet, funding and provisioning the budget account.
const authRequest = await agent.requestAuthorization({
limits: { perPayment: "1", daily: "10", lifetime: "100" }, // shape illustrative
currency: "RLUSD",
expiry: "2026-12-31T00:00:00Z",
});
// Present authRequest's link/QR to the user. Wait for approval (poll or webhook).
Once the authorization is active, the agent pays. Every payment is idempotent: you supply a unique,
high-entropy idempotencyKey, and the SDK never silently mints a new one on retry.
const receipt = await agent.pay({
destination: "rMerchantXRPLAddress...",
amount: { currency: "RLUSD", value: "0.50" }, // shape illustrative
idempotencyKey: myUniqueKey,
});
Under the hood, pay() runs the full lifecycle: it obtains the policy decision, has your key store
sign the agent's half over an exact frozen transaction, requests Tidal's co-signature (granted only
on approval), assembles the two-of-two payment, submits it once, and reconciles finality before
returning the bound receipt. The integration guide walks that sequence in
full.
Draft — to be finalized. The single-call
agent.pay()state machine is the target surface. The current server SDK ships the smaller reviewed building blocksagent.initiatePayment(input)andagent.getPayment(operationId)— initiation makes one payment-init call and never retries internally; the read polls the operation. If a challenge is required, initiation surfaces achallenge_pendingstate you resolve before continuing. Build againstinitiatePayment/getPaymenttoday and adoptpay()when it lands.
Handling the common outcomes
Payments are fail-closed, and the SDK exports typed errors so you can react precisely rather than guessing from a status code. The ones you will meet first:
PolicyDeniedError— the payment broke a limit or policy rule; it was never signed.ChallengePendingError— policy needs the user to answer a challenge; resolve, then resume the same operation.InsufficientBudgetError— the budget account cannot cover it above reserves and fees.IdempotencyConflictError— the same idempotency key was reused with a different request.ReconciliationRequiredError— submission was ambiguous; reconcile by the bound transaction ID rather than resubmitting.
Full list and HTTP mapping are in the integration guide.
Read next
- Concepts — the objects and authorities you just used, explained.
- Integration guide — the payment lifecycle and idempotency in detail.
- Security model — why this is safe to hand a key to.