Examples

Runnable integrations against @rpp402/sdk, read directly from the @rpp402/examples package at build time - this page can't show stale code, because it's the exact file @rpp402/examples' own test suite actually executes.

Basic purchase

runs against the real starter

The canonical single-leg lifecycle -Discovery → Quote → Commerce Session → Payment Intent → Settlement → Receipt -against a real, freshly-spawned copy of the minimal-service starter that rpp402 init produces.

pnpm --filter @rpp402/examples basic-purchase
Source56 lines · TypeScript
TypeScript
import { pathToFileURL } from "node:url";
import { createClient } from "@rpp402/sdk";
import { startDemoService } from "./lib/start-demo-service";

const DEMO_WALLET = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" as const;

/**
 * The canonical single-leg lifecycle: Discovery → Quote → Commerce
 * Session → Payment Intent → Settlement → Receipt, against a real,
 * freshly-spawned copy of packages/private/templates/minimal-service -the exact
 * starter `rpp402 init` produces, not a stand-in for it.
 */
export async function runBasicPurchase() {
  const service = await startDemoService("examplebasicpurchase", 4041);
  try {
    const rpp402 = createClient({ agentWallet: DEMO_WALLET });

    const discovered = await rpp402.discover(`${service.baseUrl}/.well-known/rpp.json`);
    const session = await rpp402.session.create(discovered);

    const [asset] = discovered.supported_assets;
    if (!asset) throw new Error(`${discovered.service.name} published no supported_assets`);

    const quote = await session.quote(discovered, {
      capability: "example.echo",
      params: { hello: "world" },
      settlementAsset: asset,
    });

    const intent = await session.intent({
      asset: quote.price.asset,
      authorization: {
        type: "agentic_account_delegation",
        account_id: "aa_example01",
        delegation_ref: "del_example1",
      },
    });

    const settlement = await session.settle();
    const receipt = await session.receipt();

    return { discovered, session: session.data, quote, intent, settlement, receipt };
  } finally {
    await service.stop();
  }
}

// pathToFileURL, not string concatenation -process.argv[1] is a raw OS
// path (backslashes on Windows), import.meta.url is a proper file:// URL.
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
  const result = await runBasicPurchase();
  console.log(`Discovered ${result.discovered.service.name} (${result.discovered.service.id})`);
  console.log(`Quote:      ${result.quote.price.amount} ${result.quote.price.asset.symbol}`);
  console.log(`Settlement: ${result.settlement.status} -${result.settlement.tx_hash}`);
  console.log(`Receipt:    ${result.receipt.id}, signed by ${result.receipt.signature.signer}`);
}

Multi-leg session

simplified, disclosed below

One Commerce Session holding two legs, settled and receipted atomically -the structural claim at the center of RPP402-000's motivation. Both legs are against the same fixture service, not two independent companies -attaching a leg whose Quote was issued by a different service needs the session host to verify a Quote it never issued itself, which the reference implementation doesn't do yet (see RPP402-003 §Session host). Disclosed, not hidden -see the @rpp402/examplespackage's own docs.

pnpm --filter @rpp402/examples multi-leg-session
Source67 lines · TypeScript
TypeScript
import { fileURLToPath, pathToFileURL } from "node:url";
import { createClient } from "@rpp402/sdk";
import { startFixtureService } from "./lib/start-demo-service";

const DEMO_WALLET = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" as const;
const FIXTURE = fileURLToPath(new URL("./fixtures/two-capability-service.js", import.meta.url));

/**
 * One Commerce Session, two legs, one Settlement, one Receipt with two
 * line items -the structural claim at the center of RPP402-000's
 * motivation. Both legs are against the same fixture service (see
 * src/fixtures/two-capability-service.js for why: cross-service leg
 * attachment needs the session host to verify a Quote it never issued
 * itself, which the reference implementation doesn't do yet). What this
 * *does* prove for real: the Session/Intent/Settlement/Receipt state
 * machine correctly holds and settles more than one leg atomically.
 */
export async function runMultiLegSession() {
  const service = await startFixtureService(FIXTURE, 4043);
  try {
    const rpp402 = createClient({ agentWallet: DEMO_WALLET });

    const discovered = await rpp402.discover(`${service.baseUrl}/.well-known/rpp.json`);
    const session = await rpp402.session.create(discovered);

    const [asset] = discovered.supported_assets;
    if (!asset) throw new Error(`${discovered.service.name} published no supported_assets`);

    const quoteA = await session.quote(discovered, {
      capability: "example.echo",
      params: { leg: "A" },
      settlementAsset: asset,
    });
    const quoteB = await session.quote(discovered, {
      capability: "example.ping",
      params: { leg: "B" },
      settlementAsset: asset,
    });

    const intent = await session.intent({
      asset,
      authorization: {
        type: "agentic_account_delegation",
        account_id: "aa_example02",
        delegation_ref: "del_example2",
      },
    });

    const settlement = await session.settle();
    const receipt = await session.receipt();

    return { discovered, session: session.data, quoteA, quoteB, intent, settlement, receipt };
  } finally {
    await service.stop();
  }
}

if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
  const result = await runMultiLegSession();
  console.log(`Session ${result.session.id} -${result.session.legs.length} legs`);
  console.log(
    `Intent amount: ${result.intent.amount} ${result.intent.asset.symbol} (sum of both legs, exact decimal-string addition)`,
  );
  console.log(
    `Receipt ${result.receipt.id}: ${result.receipt.line_items.length} line items, total ${result.receipt.total.amount} ${result.receipt.total.asset.symbol}`,
  );
}