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 thesource_idon 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.amountis a non-negativebigintin the denomination currency's minor units,as_ofis a realDateno later than now,kindison_chainoroff_chain, andevidence_refis a string ornull.
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 onpayout_id: the same id returns the samerail_refand 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.amountis a non-negativebigintandas_ofis aDate.
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 throwsSignerDeniedfor 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 isop_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_idand the submittedfields) and returns one ofapproved,info_requestedorrejected.reasonis a string ornull, 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.