@dropless-xrpl/sdk
v0.1.2
Published
Official TypeScript SDK for the dropless XRPL sponsorship service (XLS-68).
Maintainers
Readme
@dropless-xrpl/sdk
Official TypeScript SDK for the dropless XRPL sponsorship service (XLS-68).
- Talks to every public sponsor-api endpoint with typed request/response
- Retries with jitter on 429 / 5xx / network errors, honours
Retry-After - First-class adapters for xrpl.js
Walletand xrpl-connect providers - Zero required dependencies - signer libraries are optional peer deps
- Works in Node 18+, modern browsers, Cloudflare Workers, Bun, Deno
Install
npm install @dropless-xrpl/sdk
# and, for signer helpers:
npm install xrpl ripple-binary-codecQuick start
import { createClient } from "@dropless-xrpl/sdk";
const dropless = createClient({
baseUrl: "https://api.dropless.xyz",
apiKey: process.env.DROPLESS_API_KEY!,
defaultNetwork: "testnet",
});
// Mode A - sponsor creates an XRPL account for a user.
const res = await dropless.sponsorCreateAccount({
destination: "rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH",
});
console.log(res.submit_tx_hash, res.credits_remaining_bucket);Configuration
createClient({
baseUrl: "https://api.dropless.xyz", // required
apiKey: "dpl_live_...", // required
defaultNetwork: "testnet", // optional
timeoutMs: 30_000, // per-attempt timeout, default 30s
retry: {
// default: 3 retries, 200ms base, 5s cap
maxAttempts: 5,
baseMs: 250,
capMs: 10_000,
},
fetch: customFetch, // inject undici / a mock
userAgent: "myapp/1.0", // default: dropless-sdk-js/<version>
});Signers
xrpl.js Wallet
import { Wallet } from "xrpl";
import { decode } from "ripple-binary-codec";
import { xrplWalletSigner } from "@dropless-xrpl/sdk/signers/xrpl";
const wallet = Wallet.fromSeed(userSeed);
const signer = xrplWalletSigner({ wallet, decode });xrpl-connect
import { XrplConnect } from "xrpl-connect";
import { decode } from "ripple-binary-codec";
import { xrplConnectSigner } from "@dropless-xrpl/sdk/signers/xrpl-connect";
const connect = new XrplConnect({ network: "testnet" });
await connect.connect("xaman");
const signer = xrplConnectSigner({ provider: connect.provider, decode });Bring your own
import type { Signer, TxJson, SignedTxJson } from "@dropless-xrpl/sdk";
const signer: Signer = {
address: "rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH",
async sign(tx: TxJson): Promise<SignedTxJson> {
return await mySigner(tx);
},
};High-level flows
Mode B - co-sign then submit
import { cosignAndSign } from "@dropless-xrpl/sdk";
import { Client } from "xrpl";
const xrpl = new Client("wss://s.altnet.rippletest.net:51233");
await xrpl.connect();
const unsignedTx = await xrpl.autofill({
TransactionType: "TrustSet",
Account: userAddress,
Sponsor: sponsorAddress,
SponsorFlags: 2, // spfSponsorReserve
LimitAmount: { currency: "USD", issuer: gatewayAddress, value: "1000" },
});
const { signedTx } = await cosignAndSign({
client: dropless,
signer,
unsignedTx,
});
// signedTx is now valid XRPL JSON with both signatures.
const { result } = await xrpl.submitAndWait(signedTx);Release a sponsored account
import { signAndReleaseAccount } from "@dropless-xrpl/sdk";
const accountDelete = await xrpl.autofill({
TransactionType: "AccountDelete",
Account: sponseeAddress,
Destination: sponsorAddress, // must equal the sponsor
});
const res = await signAndReleaseAccount({
client: dropless,
signer,
accountDelete,
});
console.log("refunded", res.credits_refunded, "credits");Dissolve any owner-object
import { signAndReleaseDissolution } from "@dropless-xrpl/sdk";
const dissolveLine = await xrpl.autofill({
TransactionType: "TrustSet",
Account: sponseeAddress,
LimitAmount: { currency: "USD", issuer: gatewayAddress, value: "0" },
});
await signAndReleaseDissolution({
client: dropless,
signer,
dissolutionTx: dissolveLine,
});Error handling
Every non-2xx response becomes a DroplessError (or subclass) with a
code, status, traceId, and message:
import {
DroplessError,
RateLimitedError,
InsufficientCreditsError,
InsufficientSponsorReserveError,
PolicyDeniedError,
UnauthenticatedError,
} from "@dropless-xrpl/sdk";
try {
await dropless.sponsorCreateAccount({ destination });
} catch (err) {
if (err instanceof RateLimitedError) {
// Automatic retries already exhausted; back off further.
} else if (err instanceof InsufficientCreditsError) {
// err.bucket === "account_reserve" | "other_reserve"
} else if (err instanceof InsufficientSponsorReserveError) {
// Top up the sponsor address on-chain.
} else if (err instanceof PolicyDeniedError) {
// The request didn't match any enabled policy.
} else if (err instanceof UnauthenticatedError) {
// Bad API key or revoked.
} else if (err instanceof DroplessError) {
console.error(err.code, err.status, err.traceId);
}
}Retries
By default the SDK retries 429, 5xx, and transport errors up to 3
times with decorrelated jitter (AWS backoff). On 429 the server-sent
Retry-After overrides the jitter. Everything else - 400, 401,
402 (insufficient_credits), 403, 422 - surfaces immediately as
a typed error.
Disable retries entirely with retry: { maxAttempts: 0 }.
Development
npm install
npm run build # tsup → dist/*.{js,cjs,d.ts}
npm run type-check
npm run lintLicense
MIT
