Developer docs

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 (prefix t54_pk_). It identifies your app but never proves user consent and never authorizes a payment by itself.
  • apiSecret — an Ed25519 request-signing private key (prefix t54_sk_), not a bearer token. Your backend generates it locally; the portal only ever receives the public half.
  • kid — a key-version identifier (prefix t54_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 pointUse
@xrpl-agent-wallet/agent-wallet-sdk/serverThe authenticated client: init, createAgent, authorization, and payment. Server-side only.
@xrpl-agent-wallet/agent-wallet-sdk/keystore/aws-kmsAgent key in AWS KMS (secp256k1). Production-eligible.
@xrpl-agent-wallet/agent-wallet-sdk/keystore/gcp-kmsAgent key in GCP KMS (Ed25519). Ships fail-closed until an accepted live vector — see note.
@xrpl-agent-wallet/agent-wallet-sdk/keystore/encrypted-fileLocal encrypted file, development only, refuses Mainnet.
@xrpl-agent-wallet/agent-wallet-sdk/publicBrowser/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-file locally and plan for aws-kms in 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 blocks agent.initiatePayment(input) and agent.getPayment(operationId) — initiation makes one payment-init call and never retries internally; the read polls the operation. If a challenge is required, initiation surfaces a challenge_pending state you resolve before continuing. Build against initiatePayment / getPayment today and adopt pay() 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