Skip to main content

Stellar & Ripple

Stellar and Ripple require trustlines before an address can receive non-native tokens. Both networks also use memo/tag fields for deposit identification at shared addresses.

Trustlines

On Stellar and Ripple, an account must explicitly trust a non-native asset before it can hold or receive it. Custody exposes this as:

  • a write endpoint that initiates a changeTrust custody transaction (subject to your vault’s normal approval workflow);
  • a read endpoint that returns the assets the on-chain account currently trusts.

Without a trustline, incoming token transfers are rejected by the network — open the trustline before sharing the deposit address with a sender for that token.

Authentication: all calls require a valid JWT (same scheme as the rest of the Custody API).

List trustlines for an address

Request

curl -H "Authorization: Bearer $TOKEN" \
https://api.carabaas.com/api/v1/addresses/{addressId}/trustlines
MethodGET
Path/api/v1/addresses/{addressId}/trustlines
Path parameteraddressId — Custody address resource id

No query parameters.

Authorization

The caller must satisfy either:

  • manageVaults permission on the address’s organization, or
  • read permission on the address’s vault.

Success response

  • HTTP 200
  • Body shape: { "data": [ "…", "…" ] } — an array of strings.
  • Each string is a Custody asset identifier for a token the account currently trusts. The format mirrors the asset ids you pass elsewhere in the API (a chain-scoped prefix plus a _t… segment that encodes the token / issuer / contract reference).

Example response (illustrative)

{
"data": [
"c148_tUSDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN",
"c148_tETH:GBVOL67TMUQBGL4TZYNMY3ZQ5WGQYFPFD5VJRWXR72VA33VFNL225PL5"
]
}

The list endpoint reflects current ledger state as observed by Custody’s indexer — it does not include in-flight changeTrust transactions that have not yet been confirmed on-chain. Re-poll after a changeTrust reaches a confirmed status.

Error responses

HTTPWhen
400The address is not on a Stellar/Ripple network, or the network is a virtual/test pseudo-network.
403Caller lacks both manageVaults (org) and read (vault) permissions.
404addressId does not exist in your organization scope.

Create a trustline

warning

Open the trustline before sharing the deposit address with a sender for that token. Without it, the transfer will fail on-chain and may incur a network-level rejection.

Request

curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"orderId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"addressId": "8uPDmg3KsbUfsB8dx",
"network": "stellar-mainnet",
"asset": "c148_tUSDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN"
}' \
https://api.carabaas.com/api/v1/transactions/trustline
MethodPOST
Path/api/v1/transactions/trustline
BodyJSON (see below)

JSON body fields

FieldRequiredDescription
orderIdYesA valid UUID string. Used as an idempotency key: repeating the same orderId does not create a second transaction (see Idempotency).
addressIdYesCustody address resource id — the Stellar or Ripple account that should gain the trustline. Must belong to an active vault that has a wallet attached.
networkYesCustody network code for a Stellar or Ripple ledger (e.g. stellar-mainnet, ripple-mainnet). Virtual/test pseudo-networks are not accepted.
assetYesCustody asset identifier for the token to trust. Must be a token-class asset configured for that network — i.e. an issued token (Stellar SAC / Ripple issued asset), not the network’s native coin. The id includes a _t… segment carrying the token/issuer/contract reference.
feePriorityNoOne of low, medium, high. If omitted, the API applies its default fee behaviour for that network.

Authorization

The caller must have initiate permission on the vault owning addressId. Without it the request is rejected with 403 before any transaction is created.

Idempotency and response codes

The JSON envelope follows the usual API pattern: { "data": { …transaction… } }. The HTTP status code distinguishes create vs replay — read it explicitly in addition to the body:

HTTPMeaning
201A new trustline transaction was created for this orderId.
200A transaction with this orderId already exists (idempotent retry); the same transaction object is returned.

Replays return the existing transaction regardless of its current status (pending, approved, broadcast, confirmed, failed, …), so data.status is the canonical signal for progress.

The returned transaction object has type: "changeTrust" and amount: "0" (a trustline carries no value transfer). It then moves through the same lifecycle as any other custody transaction: pending → approved (once vault quorum is met) → signing → broadcast → confirmed (or failed on rejection). Subscribe via your usual transaction-event channel (webhooks / event stream) to track it; do not poll the trustline list until status is confirmed.

Error responses

All validation errors return HTTP 400 with a descriptive message. Common cases:

CauseMessage contains
network is not Stellar/RippleNetwork ... is not allowed for this operation
network is a virtual/test pseudo-networkVirtual networks are not supported for this operation
asset is not configured for that networkAsset ... is not supported
asset is the native coin or otherwise not a trustline-eligible tokenAsset type ... is not allowed for this operation
asset lacks token/contract metadataAsset ... has no contract information
Vault is paused, archived, or has no wallet attachedVault is not active
Caller lacks initiate on the vaultAccess denied (HTTP 403)
addressId does not exist(HTTP 404)

Schema-level violations (missing field, malformed UUID, wrong types) are rejected by request validation before any of the above and surface as a generic 400 with a Zod-style error.

Trustline requirements

NetworkNative assetTrustline reserveNotes
StellarXLM0.5 XLM reserve per trustlineLocked as base reserve
RippleXRP2 XRP reserve per trustlineOwner reserve increase

Make sure the address holds enough native balance to cover the trustline reserve plus network fees before creating a trustline; otherwise the on-chain submission stage of the transaction will fail even though the API call succeeds.

Integration notes

  1. Fresh UUIDs, careful retries — Generate a fresh UUID per logical “create trustline” business action. On timeouts/network errors reuse the same orderId so retries don’t spawn duplicates: the server treats the retry as a no-op and returns the existing transaction with HTTP 200.
  2. Trustline ≠ balance — Opening a trustline does not fund the account; deposits/transfers still happen per your product flow.
  3. Approvals required — Like any custody transaction, a changeTrust may need vault-quorum approvals before signing. Wire it through the same approval/notification path you already use for transfers.
  4. Read-after-write — After a changeTrust reports confirmed, refresh GET …/trustlines. Before that, the list reflects current ledger state, not in-flight operations.
  5. Asset identifiers are stable — The string returned by the list endpoint is the same identifier you pass to other Custody endpoints (transfers, balances), so you can pipe it through without parsing.
  6. Stellar / Ripple only — Both endpoints reject other networks with 400. Your client doesn’t need to special-case the chain beyond passing the right network code.

Memo-Based Deposits

Both Stellar and Ripple support deposit identification using memos/tags. This allows multiple customers to deposit to a shared address — the memo field identifies which customer the deposit belongs to.

Create a Memo

curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"addressId": "hN8qR2tVwX4yZ6aBcDeF3g",
"memo": "customer-12345",
"name": "Customer 12345 Deposits"
}' \
https://api.carabaas.com/api/v1/memos

How It Works

  1. Create a single Stellar/Ripple address in your vault
  2. Assign unique memos for each customer via the Memos API
  3. Share the address + memo with the customer
  4. Incoming deposits include the memo in the webhook payload — match it to the customer

Sending with Memo

When sending to an address that requires a memo:

curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"orderId": "stellar-tx-001",
"vaultId": "kR7mNpX2wQvL9sYhBjD4eT",
"destination": "GRecipientAddress...",
"network": "stellar-mainnet",
"asset": "XLM",
"amount": "100.00",
"options": {
"memo": "payment-ref-456"
}
}' \
https://api.carabaas.com/api/v1/transactions

Ripple Destination Tag

Ripple uses integer destination tags instead of text memos:

{
"options": {
"memo": "123456"
}
}

Stellar Token Transfers

Transfer Stellar assets (after trustline is established):

curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"orderId": "stellar-usdc-001",
"vaultId": "kR7mNpX2wQvL9sYhBjD4eT",
"destination": "GRecipient...",
"network": "stellar-mainnet",
"asset": "USDC",
"amount": "500.00"
}' \
https://api.carabaas.com/api/v1/transactions

Deposit Flow for Stellar/Ripple

1. Create address (or use shared address)
2. Create trustline for each token you want to receive
3. Create memo for each customer (if using shared address)
4. Share address + memo with customer
5. Subscribe to incoming webhook stream
6. Match deposits by address + memo

See Also