Overview
These docs follow the architecture described in How Stablecoins Work. Code targets xrpl.js v4+, ethers v6 and Solidity ^0.8.24. The XRPL examples use Testnet. Change the endpoint only after completing the security checklist.
Keys. Never hard-code seeds or private keys. Examples read them from environment variables. In production, issuer and admin keys belong in HSMs or multisig custody, never on application servers.
Quickstart
The public API needs no authentication. Start by reading live pilot telemetry:
curl https://stablecoins.unykorn.ai/api/pilot{
"xrpl": { "issuer": "rfoXVXYvBjwS2bTVvEZdZmg6G1d78fBdzr", "currency": "AET",
"obligations": 50010, "ledgerIndex": 107045078, "flags": { "defaultRipple": true, ... } },
"polygon": { "token": "0xA0B7C74C4025ef06056DF0127DB5F6AA26b80481",
"totalSupply": 50010, "decimals": 6, "blockNumber": 76543210 },
"status": "PILOT_NOT_A_REGULATED_STABLECOIN",
"fetchedAt": "2026-09-17T12:00:00.000Z"
}
Install the SDKs used below:
npm install xrpl ethers
Issue a token on XRPL
XRPL issuance takes four transactions: configure the issuer, have the holder open a trust line, authorize the line if required, and pay from the issuer. The issuer's outstanding balance is the circulating supply.
1 · Configure the issuer (before any trust lines exist)
import xrpl from "xrpl";
const client = new xrpl.Client("wss://s.altnet.rippletest.net:51233");
await client.connect();
const issuer = xrpl.Wallet.fromSeed(process.env.ISSUER_SEED);
const { AccountSetAsfFlags } = xrpl;
// Clawback MUST be enabled while the issuer has no trust lines or other objects.
for (const flag of [
AccountSetAsfFlags.asfAllowTrustLineClawback,
AccountSetAsfFlags.asfRequireAuth, // permissioned distribution (optional)
AccountSetAsfFlags.asfDefaultRipple, // lets holders pay each other
]) {
await client.submitAndWait(
{ TransactionType: "AccountSet", Account: issuer.address, SetFlag: flag },
{ wallet: issuer }
);
}
await client.submitAndWait({
TransactionType: "AccountSet", Account: issuer.address,
Domain: xrpl.convertStringToHex("issuer.example.com"),
}, { wallet: issuer });
2 · Holder opens a trust line
const CURRENCY = "USX";
const holder = xrpl.Wallet.fromSeed(process.env.HOLDER_SEED);
await client.submitAndWait({
TransactionType: "TrustSet",
Account: holder.address,
LimitAmount: { currency: CURRENCY, issuer: issuer.address, value: "10000000" },
}, { wallet: holder });
3 · Issuer authorizes the line (RequireAuth only)
await client.submitAndWait({
TransactionType: "TrustSet",
Account: issuer.address,
LimitAmount: { currency: CURRENCY, issuer: holder.address, value: "0" },
Flags: xrpl.TrustSetFlags.tfSetfAuth,
}, { wallet: issuer });
4 · Mint by paying from the issuer
// Only after the custodian confirms the matching fiat funding.
const res = await client.submitAndWait({
TransactionType: "Payment",
Account: issuer.address,
Destination: holder.address,
Amount: { currency: CURRENCY, issuer: issuer.address, value: "250000" },
Memos: [{ Memo: {
MemoType: xrpl.convertStringToHex("FUNDING_REF"),
MemoData: xrpl.convertStringToHex("WIRE-2026-09-17-000123"),
}}],
}, { wallet: issuer });
console.log(res.result.meta.TransactionResult); // tesSUCCESS
Redemption is the reverse: the holder pays tokens back to the issuer, which removes them from supply, and the issuer then sends the fiat wire.
Freeze, authorization & clawback
// Freeze one holder's trust line
await client.submitAndWait({
TransactionType: "TrustSet", Account: issuer.address,
LimitAmount: { currency: CURRENCY, issuer: holder.address, value: "0" },
Flags: xrpl.TrustSetFlags.tfSetFreeze,
}, { wallet: issuer });
// Claw back tokens (note: Amount.issuer is the HOLDER's address)
await client.submitAndWait({
TransactionType: "Clawback", Account: issuer.address,
Amount: { currency: CURRENCY, issuer: holder.address, value: "1000" },
}, { wallet: issuer });
// Emergency: freeze every trust line
await client.submitAndWait({
TransactionType: "AccountSet", Account: issuer.address,
SetFlag: xrpl.AccountSetAsfFlags.asfGlobalFreeze,
}, { wallet: issuer });
Irreversible flag. asfNoFreeze permanently gives up freeze powers. A regulated issuer should never set it.
Monitor circulating supply
const { result } = await client.request({
command: "gateway_balances",
account: issuer.address,
hotwallet: [process.env.HOT_WALLET], // exclude operational float
ledger_index: "validated",
});
console.log(result.obligations); // { USX: "250000" } -> reconcile vs. reserves
Deploy & assign roles (EVM)
The UnyKorn contracts use a transparent upgradeable proxy with separate owner, master minter, minter, compliance and pauser roles. See the role matrix. Sources are served at /contracts/<Name>.sol.
import { ethers } from "ethers";
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const owner = new ethers.Wallet(process.env.OWNER_KEY, provider); // multisig in prod
const abi = [
"function configureMinter(address minter, uint256 quota)",
"function mint(address to, uint256 amount) returns (bool)",
"function burn(uint256 amount)",
"function freeze(address target, string reason)",
"function wipeAccount(address target)",
"function setPaused(bool paused)",
"function totalSupply() view returns (uint256)",
];
const token = new ethers.Contract(process.env.TOKEN, abi, owner);
const units = (n) => ethers.parseUnits(n, 6);
// Master minter grants a bounded quota to a treasury minter
await (await token.configureMinter(process.env.MINTER, units("1000000"))).wait();
// Minter issues against a confirmed funding event
const minter = new ethers.Wallet(process.env.MINTER_KEY, provider);
await (await token.connect(minter).mint(process.env.CUSTOMER, units("250000"))).wait();
// Compliance officer freezes a sanctioned address with a reason code
const compliance = new ethers.Wallet(process.env.COMPLIANCE_KEY, provider);
await (await token.connect(compliance).freeze(process.env.SUSPECT, "OFAC-SDN-MATCH")).wait();
Reserve-gated minting
ReserveAttestationOracle publishes threshold-signed reserve attestations. The token refuses to mint when the attestation is stale or the new supply would exceed attested reserves minus a risk buffer.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
interface IReserveAttestationOracle {
function verifyMintFeasibility(uint256 currentSupply, uint256 mintAmount)
external view returns (bool feasible, uint256 headroom);
function isAttestationFresh() external view returns (bool);
}
abstract contract ReserveGatedMint {
IReserveAttestationOracle public oracle;
uint256 public totalSupply;
error StaleAttestation();
error InsufficientReserves(uint256 headroom);
modifier reserveBacked(uint256 amount) {
if (!oracle.isAttestationFresh()) revert StaleAttestation();
(bool ok, uint256 headroom) = oracle.verifyMintFeasibility(totalSupply, amount);
if (!ok) revert InsufficientReserves(headroom);
_;
}
}
Upgrades to the oracle address go through a timelock (initiateOracleUpgrade then executeOracleUpgrade), so holders get notice before the trust source changes.
HTTP 402 payments
HTTP 402 lets a server price a request and a client (often an AI agent) pay in stablecoins without accounts or API keys. The flow:
Request
The client calls a paid resource.
402 challenge
The server responds 402 Payment Required with amount, asset, network, pay-to address, nonce and expiry.
Pay & retry
The client settles on-chain, or signs an authorization for a facilitator, then retries with payment proof in a header.
Verify & serve
The server verifies the receipt, checks for replay and expiry, and returns 200 with a settlement receipt.
Try the sandbox endpoint. It issues a real challenge but does not verify payments on chain:
# 1. Get a challenge
curl -i https://stablecoins.unykorn.ai/api/x402/demo
# 2. Retry with a (sandbox) proof
curl -i https://stablecoins.unykorn.ai/api/x402/demo \
-H "X-Invoice-Id: INV-..." \
-H "X-Payment-Proof: 0x<64 hex chars>"
For interoperable agent payments, follow the x402 specification. Header names and payload formats are versioned by the x402 Foundation.
ISO 20022 mapping
Put the on-chain reference in the remittance information of the fiat leg, so bank reconciliation can match cash to token events.
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pacs.008.001.10">
<FIToFICstmrCdtTrf>
<CdtTrfTxInf>
<PmtId><EndToEndId>MINT-2026-09-17-000123</EndToEndId></PmtId>
<IntrBkSttlmAmt Ccy="USD">250000.00</IntrBkSttlmAmt>
<Dbtr><Nm>Client Corp</Nm><Id><OrgId><LEI>00000000000000000000</LEI></OrgId></Id></Dbtr>
<Cdtr><Nm>Issuer Reserve Trust</Nm></Cdtr>
<RmtInf><Strd><AddtlRmtInf>DTI:XXXXXXXXX;CHAIN:XRPL;TX:8DEE18D1...</AddtlRmtInf></Strd></RmtInf>
</CdtTrfTxInf>
</FIToFICstmrCdtTrf>
</Document>
The LEI and DTI values above are placeholders. The Lab's ISO studio generates full pacs.008 and camt.053 payloads.
REST API reference
Base URL https://stablecoins.unykorn.ai. All responses are JSON, and CORS is open for GET requests. Responses are cached for up to 60 seconds.
Service status and version.
Live pilot telemetry read directly from XRPL mainnet (gateway_balances, account_info) and Polygon (totalSupply).
Dated market and regulatory snapshot used on this site, with source URLs.
Solidity sources available at /contracts/{name}.sol.
HTTP 402 sandbox. Without a proof it returns a 402 challenge; with X-Payment-Proof it returns a sandbox receipt.
Read-only deployment. Endpoints that mint addresses, move internal balances or touch key material are disabled on this public site and return 403.
Security checklist
- Issuer, owner and proxy-admin keys in HSM or multisig. XRPL issuer master key disabled after setting a regular key or signer list.
- Minter quotas sized to confirmed funding. Mint and burn events reconciled against custodian statements daily.
- Timelock on upgrades and oracle changes. Upgrade and freeze events published to a monitored channel.
- Independent audit before mainnet, verified source on explorers, and a live bug bounty.
- Freeze and clawback enabled before first issuance. Never set
NoFreeze. - Sanctions screening on primary transfers, Travel Rule messaging between VASPs, and a documented legal basis for every freeze or wipe.
- Monthly reserve attestations by an independent accountant, with mints halted automatically on stale attestations.
