Skip to content
Testnet only: a reference implementation, not a production service.Tokens and bank transfers here are simulated and have no value.

Build an extension

The reference issuer reaches the outside world through four extension points. Each is a TypeScript interface in packages/domain/src/interfaces.ts, and each has a contract suite in packages/contract-tests. Implement the interface, then prove it with the suite before you switch it on. Nothing here runs in the docs build: the code below is a worked example, not a snippet the site executes.

ReserveSource

A ReserveSource reports the value of one pool of reserves that backs the Platform asset. The reserve guard (apps/core/src/modules/reserves/guard.ts) sums every fresh source to compute available reserves, and a value older than maxAgeSeconds counts as zero for mint headroom (CONTRACT §4).

  • readonly id: string — the source's stable identifier. It must equal the source_id on every value the source returns.
  • readonly maxAgeSeconds: number — the freshness window. It must be positive; a value older than it counts as zero.
  • read(): Promise<ReserveValue> — the current value. amount is a non-negative bigint in the denomination currency's minor units, as_of is a real Date no later than now, kind is on_chain or off_chain, and evidence_ref is a string or null.

An implementation must pass the contract suite in packages/contract-tests/src/reserve/index.ts (runReserveSourceContract). Run it with:

pnpm test -- -t 'passes the ReserveSource contract'

SettlementRail

A SettlementRail is the fiat, or cash, leg. It pays out to a registered bank counterparty and reports the rail's own view of the Platform balance so reconciliation can compare it with the ledger (CONTRACT §4).

  • readonly id: string — the rail's stable identifier.
  • payout(req): Promise<{ rail_ref: string }> — pays a registered bank counterparty. It is idempotent on payout_id: the same id returns the same rail_ref and debits the balance once. A non-positive amount is rejected.
  • balance(currency): Promise<{ amount: FiatMinor; as_of: Date }> — the rail's balance for a currency. amount is a non-negative bigint and as_of is a Date.

An implementation must pass the contract suite in packages/contract-tests/src/rail/index.ts (runSettlementRailContract). Run it with:

pnpm test -- -t 'passes the SettlementRail contract'

Signer

A Signer holds the three Platform keys and is the only place a transaction is signed (CONTRACT §7). The orchestrator sends a TransferIntent; the signer rebuilds the transaction from it and checks it against the policy table before signing. It never trusts caller XDR except the fee_bump inner envelope, which it re-checks.

  • sign(intent): Promise<SignedEnvelope> — rebuilds and signs the intent. It throws SignerDenied for any policy failure and never signs a partial or rebuilt intent it cannot verify. Every intent must carry the configured network passphrase (wrong_network), time bounds no wider than 120 seconds (time_bounds_invalid) and a fee within the cap (fee_too_high); anything outside the (account, kind) policy table is op_not_allowed.
  • signReport(body): Promise<{ jws: string; kid: string }> — a detached JWS (EdDSA) over a public report body, signed with the separate report key (CONTRACT §13).

An implementation must pass the contract suite in packages/contract-tests/src/signer/index.ts (runSignerContract). Run it with:

pnpm test -- -t 'passes the Signer contract'

KycProvider

A KycProvider decides whether an organisation may be onboarded. It is the pluggable review boundary: the reference ships a mock, and a real provider replaces it behind the same interface.

  • readonly id: string — the provider's stable identifier.
  • review(application): Promise<{ decision: KybDecision; reason: string | null }> — reviews an application (application_id, org_id and the submitted fields) and returns one of approved, info_requested or rejected. reason is a string or null, and reviewing the same application twice gives the same decision.

An implementation must pass the contract suite in packages/contract-tests/src/kyc/index.ts (runKycProviderContract). Run it with:

pnpm test -- -t 'passes the KycProvider contract'

Worked example: a fixed ReserveSource

Say the treasury enters one off-chain figure by hand. Implement ReserveSource that reads it:

import type { FiatMinor, ReserveSource, ReserveValue } from "@scr/domain";

/** Reports one fixed off-chain figure. */
export class FixedReserveSource implements ReserveSource {
  readonly id: string;
  readonly maxAgeSeconds: number;
  readonly #currency: string;
  readonly #amount: FiatMinor;
  readonly #asOf: Date;

  constructor(opts: { id: string; currency: string; amount: FiatMinor; asOf: Date; maxAgeSeconds: number }) {
    this.id = opts.id;
    this.maxAgeSeconds = opts.maxAgeSeconds;
    this.#currency = opts.currency;
    this.#amount = opts.amount;
    this.#asOf = opts.asOf;
  }

  async read(): Promise<ReserveValue> {
    return {
      source_id: this.id,
      kind: "off_chain",
      asset_or_currency: this.#currency,
      amount: this.#amount,
      as_of: new Date(this.#asOf.getTime()),
      valuation_method: "configured",
      evidence_ref: null,
    };
  }
}

Put it beside its test and run the suite against it:

import { runReserveSourceContract } from "@scr/contract-tests";
import { FixedReserveSource } from "./fixed-reserve-source.ts";

runReserveSourceContract("fixed-reserve-source", () =>
  new FixedReserveSource({
    id: "fixed-treasury",
    currency: "USD",
    amount: 1_000_000n,
    asOf: new Date("2026-10-01T00:00:00Z"),
    maxAgeSeconds: 86_400,
  }),
);

Register it where the guard loads its sources. apps/core/src/modules/reserves/config.ts maps each reserve_sources entry in the reserve configuration to a ReserveSource; point that mapping at your class:

import type { Kysely } from "kysely";
import { FIAT_SCALE, parseAmount, type ReserveSource } from "@scr/domain";
import { currentConfig } from "../config.ts";
import { FixedReserveSource } from "./sources/fixed-reserve-source.ts";

export async function loadReserveSources(db: Kysely<object>, asset_code: string): Promise<ReserveSource[]> {
  const cur = await currentConfig(db);
  return cur.config.reserve_sources
    .filter((s) => s.asset_code === asset_code && s.counts_toward_headroom)
    .map((s) =>
      new FixedReserveSource({
        id: s.id,
        currency: s.currency,
        amount: parseAmount(s.amount, FIAT_SCALE),
        asOf: new Date(s.as_of),
        maxAgeSeconds: s.max_age_seconds,
      }),
    );
}

Then run the suite and, when it passes, enable the source. The interface header is explicit: every implementation must pass runReserveSourceContract before it can be enabled, and changing a signature is a CONTRACT change, not an implementation detail.

Edit on GitHub