Sep31Service

class Sep31Service(val serviceUrl: String, httpClient: HttpClient? = null, httpRequestHeaders: Map<String, String>? = null)

SEP-31 Cross-Border Payments service client.

Provides the Sending Anchor side of SEP-31 by talking to a Receiving Anchor's DIRECT_PAYMENT_SERVER. Wraps the five HTTP endpoints defined by SEP-0031:

  • GET /info — discover supported assets, limits, and KYC requirements.

  • POST /transactions — initiate a cross-border payment and receive on-chain instructions.

  • GET /transactions/:id — fetch the current state of a transaction.

  • PUT /transactions/:id/callback — register a callback URL for status notifications.

  • PATCH /transactions/:id — update legacy per-transaction fields (deprecated; use SEP-12 instead).

Security posture

  • HTTPS enforcement. The constructor rejects non-HTTPS serviceUrl values, the companion Companion.fromDomain re-validates the resolved DIRECT_PAYMENT_SERVER, and putTransactionCallback re-validates the supplied callback URL before any network call. http:// is allowed only for the three loopback authorities (localhost, 127.0.0.1, [::1], each optionally with a port) so callers can integrate against a local Anchor Platform without standing up a TLS proxy; every other host must use HTTPS. Validation errors do not echo the offending input to avoid log-injection.

  • Path-segment validation. Every transaction id interpolated into a URL is percent-decoded once and matched against the RFC 3986 pchar allow-list (A-Z, a-z, 0-9, -, ., _, ~, !, $, &, ', (, ), *, +, ,, ;, =, :, @). The substring .. is additionally rejected to prevent path-traversal attempts, which is stricter than what RFC 3986 alone requires.

  • Redirect handling. The default HTTP client used by this service sets followRedirects = false. The anchor URL is authoritative; a redirect indicates misconfiguration or an attack. A caller-supplied httpClient is used as-is — the caller is responsible for redirect policy on the client they pass in.

  • Response body caps. Bodies are size-capped before decoding (2 MB for /info, 256 KB for transaction responses). If the response advertises a Content-Length larger than the cap, or streams more bytes than the cap, Sep31InvalidResponseException is raised before any body content is materialized.

  • Content-Type allow-list. Responses must declare application/json or application/problem+json (RFC 7807). Other types raise Sep31InvalidResponseException before body read; this defends against anchors returning HTML error pages that would otherwise reach the error-message extractor.

  • No logging plugin by default. This service does not install Ktor's Logging plugin. If a caller supplies a logging-enabled httpClient, the caller is responsible for redaction. JWT bearer tokens, PII (KYC fields, customer ids, refund memos, names), and full request bodies must be filtered upstream.

  • TLS baseline. TLS minimum version and certificate pinning follow the platform Ktor client defaults. Production deployments that require pinning or a higher TLS floor should construct an HttpClient with their engine's pinning policy and pass it via the httpClient constructor parameter.

Typical workflow

// 1. Initialize from the receiving anchor's domain.
val sep31 = Sep31Service.fromDomain("anchor.example.org")

// 2. Discover available assets.
val info = sep31.info(jwt = jwtToken)
val usdc = info.receiveAssets["USDC"] ?: error("USDC not supported")

// 3. Initiate a transaction.
val request = Sep31PostTransactionsRequest(
amount = 100.0,
assetCode = "USDC",
fundingMethod = "SWIFT",
senderId = "11111111-1111-1111-1111-111111111111",
receiverId = "22222222-2222-2222-2222-222222222222"
)
val post = sep31.postTransactions(request, jwtToken)

// 4. Poll for status.
val transaction = sep31.getTransaction(post.id, jwtToken)
println("Status: ${transaction.status}")

See also:

Parameters

httpClient

Optional caller-supplied HTTP client. When null, the service constructs a Ktor client with followRedirects = false, content negotiation, and a 30 s request timeout. A caller-supplied client is used verbatim and never closed by this service.

httpRequestHeaders

Optional headers attached to every outbound request in addition to Authorization and Content-Type.

Throws

if serviceUrl is neither HTTPS nor HTTP targeting a loopback authority.

Constructors

Link copied to clipboard
constructor(serviceUrl: String, httpClient: HttpClient? = null, httpRequestHeaders: Map<String, String>? = null)

Types

Link copied to clipboard
object Companion

Properties

Link copied to clipboard

Base URL of the Receiving Anchor's DIRECT_PAYMENT_SERVER. Must be HTTPS, or http:// against the loopback authorities localhost, 127.0.0.1, or [::1] for local-development integration.

Functions

Link copied to clipboard

Fetches the current state of a previously-initiated transaction.

Link copied to clipboard
suspend fun info(jwt: String, lang: String? = null): Sep31InfoResponse

Fetches the Receiving Anchor's supported assets, limits, and KYC requirements.

Link copied to clipboard

Updates legacy per-transaction fields on a pending transaction.

Link copied to clipboard

Initiates a SEP-31 cross-border payment.

Link copied to clipboard
suspend fun putTransactionCallback(id: String, callbackUrl: String, jwt: String)

Registers a callback URL the Receiving Anchor will invoke when the transaction status changes.