Integration guides
How to integrate with Vaeltrix as a Payment Provider, Sending Client, or Observer.
Implementation status: The deployed foundation supports provider registration, templates, stake/coverage/exits, and scoreboard storage. Claim filing, challenges, payouts, the observer service and the public SDK are planned; integration steps for them describe the intended protocol, not runnable deployed functionality. See the technical specification for the status breakdown.
Testnet only
The implemented registration and stake flows target the Vaeltrix Stellar testnet deployment. Planned claim/payout flows will use the same test asset (CB2CFWENJ2M5JXWBGUOKO75QVIFJ2EY5WOIOHR6FFYTXAKGS2S3XOIM4).
The standalone TypeScript examples below are checked in CI for source/display agreement, deadline behaviour, amount boundaries and shared verdict vectors. They are not exported implementations from @vaeltrix/core.
Payment providers: registration & commitments
Anchors and payment processors integrate with Vaeltrix to stake collateral against their offchain payout SLAs and build public reputation on the scoreboard.
1. Derive your provider ID
Your provider_id is derived deterministically from your registered home_domain (in lowercase) and your admin SCAddress XDR bytes:
export function deriveProviderId(homeDomain: string, adminXdrHex: string): string {
const domainTag = Buffer.from("VAELTRIX:PROVIDER:v1", "ascii");
const domainBytes = Buffer.from(homeDomain.toLowerCase(), "utf8");
const adminBytes = Buffer.from(adminXdrHex, "hex");
return createHash("sha256")
.update(Buffer.concat([domainTag, domainBytes, adminBytes]))
.digest("hex");
}2. Verify your home domain
Publish a [VAELTRIX] block in your domain's .well-known/stellar.toml containing PROVIDER_ID, REGISTRY_CONTRACT, COMMITMENTS, and EVENT_SIGNING_KEYS matching the registered provider. The protocol verifier will check this file and execute verify_domain(provider_id) on vaeltrix_registry.
3. Sign completion receipts
When an offchain bank transfer or mobile money credit completes, the anchor's server creates and signs a fixed 131-byte receipt using one of its registered Ed25519 signer keys:
export function buildCompletionReceipt(params: {
networkPassphrase: string;
paymentIdHex: string;
commitmentId: bigint;
amount: bigint;
t0Ms: bigint;
t1Ms: bigint;
pauseMs: bigint;
privateKey: KeyObject;
}): { messageHex: string; signatureHex: string } {
const bytes = Buffer.alloc(131);
bytes.write("VAELTRIX:RECEIPT:v1", 0, "ascii");
const networkId = createHash("sha256").update(params.networkPassphrase).digest();
networkId.copy(bytes, 19);
Buffer.from(params.paymentIdHex, "hex").copy(bytes, 51);
bytes.writeBigUInt64BE(params.commitmentId, 83);
bytes.writeBigUInt64BE(params.amount >> 64n, 91);
bytes.writeBigUInt64BE(params.amount & ((1n << 64n) - 1n), 99);
bytes.writeBigUInt64BE(params.t0Ms, 107);
bytes.writeBigUInt64BE(params.t1Ms, 115);
bytes.writeBigUInt64BE(params.pauseMs, 123);
const signature = sign(null, bytes, params.privateKey);
return {
messageHex: bytes.toString("hex"),
signatureHex: signature.toString("hex"),
};
}This signed receipt is delivered to the observer network or returned in the SEP-31 status endpoint.
Sending clients & wallets: verify coverage
Wallets and remittance apps route payments through anchors that back their speed promises with locked stake.
Check commitment coverage before routing
Before sending a transaction, verify that the commitment is active, unpaused, and within its effective validity window:
export function checkCommitmentCoverage(
commitment: CommitmentCoverage,
paymentTimestampMs: bigint,
): boolean {
if (!commitment.isActive || commitment.isPaused) {
return false;
}
const t0Sec = paymentTimestampMs / 1000n;
if (t0Sec < commitment.effectiveFrom) {
return false;
}
if (commitment.effectiveTo !== null && t0Sec >= commitment.effectiveTo) {
return false;
}
return true;
}Tag your Stellar payment
Include the commitment_id in your SEP-31 transaction metadata or payment memo so observers know which SLA applies to this payment.
Observers: evaluating verdicts & filing claims
Observers independently monitor payment timestamps, verify signatures, and file claims on breaches.
Evaluate payment latency
Calculate the gross elapsed time, deduct any registered carve-out pause duration (pause_ms), and check against target_ms + grace_ms:
export function evaluateVerdict(params: {
t0Ms: bigint;
t1Ms: bigint | null;
nowMs: bigint;
targetMs: bigint;
graceMs: bigint;
pauseMs: bigint;
}): "Pending" | "OnTime" | "LateCompletion" | "NoCompletion" {
if (
[params.t0Ms, params.nowMs, params.targetMs, params.graceMs, params.pauseMs].some(
(value) => value < 0n || value > (1n << 64n) - 1n,
)
) {
throw new RangeError("Timestamp or duration outside u64");
}
const deadline = params.t0Ms + params.targetMs + params.graceMs + params.pauseMs;
if (deadline > (1n << 64n) - 1n) throw new RangeError("Deadline overflow");
if (params.t1Ms === null) {
return params.nowMs > deadline ? "NoCompletion" : "Pending";
}
if (params.t1Ms > (1n << 64n) - 1n || params.t1Ms < params.t0Ms + params.pauseMs) {
throw new RangeError("Invalid completion timeline");
}
const gross = params.t1Ms - params.t0Ms;
const net = gross > params.pauseMs ? gross - params.pauseMs : 0n;
if (net <= params.targetMs + params.graceMs) {
return "OnTime";
}
return "LateCompletion";
}Compute penalty amount
Calculate the penalty owed to the sender based on the template's basis points and maximum penalty cap:
export function calculatePenalty(amount: bigint, bps: number, cap: bigint): bigint {
const penalty = (amount * BigInt(bps)) / 10000n;
return penalty > cap ? cap : penalty;
}If an SLA breach is confirmed (LateCompletion or NoCompletion), the observer or client calls file_claim on vaeltrix_bond.