> ## Documentation Index
> Fetch the complete documentation index at: https://docs.faremeter.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Flex Quickstart

> Create an escrow, register a session key, and pay for a Flex-protected endpoint.

This guide walks through the client side of the Flex flow on Solana: create an escrow, fund it, register a session key, and use `@faremeter/payment-solana/flex/client` to pay for an HTTP resource.

For a complete reference implementation including transaction confirmation, grace-period waits, and full teardown, see [scripts/solana-example/flex-payment.ts](https://github.com/faremeter/faremeter/blob/main/scripts/solana-example/flex-payment.ts) in the faremeter monorepo.

## Prerequisites

* Node.js 18+
* A Solana devnet keypair with some SOL for rent and transaction fees
* Devnet USDC in the keypair's token account ([Circle faucet](https://faucet.circle.com), select Solana devnet)
* The address of a Flex-capable facilitator (the same key the merchant declares as `facilitator` in their requirements)

<Steps>
  <Step title="Install">
    ```bash theme={null}
    pnpm add @faremeter/flex-solana @faremeter/payment-solana @faremeter/fetch @faremeter/info @solana/kit dotenv
    pnpm add -D tsx
    pnpm pkg set type=module
    ```

    Set your wallet path and the facilitator address in `.env`:

    ```bash .env theme={null}
    PAYER_KEYPAIR_PATH=$HOME/.config/solana/id.json
    FACILITATOR_ADDRESS=<facilitator-public-key>
    ```
  </Step>

  <Step title="Set up the RPC, signer, and helpers">
    Create `main.ts`. The `sendInstructions` and `confirmSignature` helpers below are reused throughout the rest of the steps.

    ```typescript main.ts theme={null}
    import "dotenv/config";
    import fs from "node:fs";
    import {
      type Instruction,
      type KeyPairSigner,
      type Signature,
      address,
      appendTransactionMessageInstructions,
      createKeyPairSignerFromBytes,
      createSolanaRpc,
      createTransactionMessage,
      getBase64EncodedWireTransaction,
      pipe,
      setTransactionMessageFeePayerSigner,
      setTransactionMessageLifetimeUsingBlockhash,
      signTransactionMessageWithSigners,
    } from "@solana/kit";
    import { lookupKnownSPLToken } from "@faremeter/info/solana";

    const network = "devnet";
    const rpc = createSolanaRpc("https://api.devnet.solana.com");

    const ownerBytes = JSON.parse(
      fs.readFileSync(process.env.PAYER_KEYPAIR_PATH!, "utf-8"),
    ) as number[];
    const owner = await createKeyPairSignerFromBytes(Uint8Array.from(ownerBytes));

    const facilitatorAddress = address(process.env.FACILITATOR_ADDRESS!);

    const usdc = lookupKnownSPLToken(network, "USDC")!;
    const mint = address(usdc.address);

    async function confirmSignature(sig: Signature) {
      for (let i = 0; i < 60; i++) {
        const { value: statuses } = await rpc.getSignatureStatuses([sig]).send();
        const status = statuses[0];
        if (
          status?.confirmationStatus === "confirmed" ||
          status?.confirmationStatus === "finalized"
        ) {
          if (status.err) {
            throw new Error(`Transaction failed: ${JSON.stringify(status.err)}`);
          }
          return;
        }
        await new Promise((r) => setTimeout(r, 500));
      }
      throw new Error("Transaction confirmation timeout");
    }

    async function sendInstructions(
      feePayer: KeyPairSigner,
      instructions: Instruction[],
    ) {
      const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
      const msg = pipe(
        createTransactionMessage({ version: 0 }),
        (m) => setTransactionMessageFeePayerSigner(feePayer, m),
        (m) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m),
        (m) => appendTransactionMessageInstructions(instructions, m),
      );
      const signedTx = await signTransactionMessageWithSigners(msg);
      const wire = getBase64EncodedWireTransaction(signedTx);
      const sig = await rpc.sendTransaction(wire, { encoding: "base64" }).send();
      await confirmSignature(sig);
    }
    ```
  </Step>

  <Step title="Create the escrow">
    ```typescript main.ts theme={null}
    import {
      getCreateEscrowInstructionAsync,
      getDepositInstructionAsync,
    } from "@faremeter/flex-solana";

    const ESCROW_INDEX = Date.now();
    const REFUND_TIMEOUT_SLOTS = 300; // ~2 minutes
    const DEADMAN_TIMEOUT_SLOTS = 100_000; // ~11 hours
    const MAX_SESSION_KEYS = 10;

    const createIx = await getCreateEscrowInstructionAsync({
      owner,
      index: ESCROW_INDEX,
      facilitator: facilitatorAddress,
      refundTimeoutSlots: REFUND_TIMEOUT_SLOTS,
      deadmanTimeoutSlots: DEADMAN_TIMEOUT_SLOTS,
      maxSessionKeys: MAX_SESSION_KEYS,
    });
    await sendInstructions(owner, [createIx]);

    const escrowAddress = createIx.accounts[1]!.address;
    ```

    <Note>
      The escrow PDA is derived from `[b"escrow", owner, index]`. Pass a unique
      `index` per escrow you want to keep open with the same facilitator.
      `Date.now()` is used here for convenience — production code should track
      indices deliberately.
    </Note>
  </Step>

  <Step title="Deposit funds">
    ```typescript main.ts theme={null}
    const { value: tokenAccounts } = await rpc
      .getTokenAccountsByOwner(owner.address, { mint }, { encoding: "base64" })
      .send();
    const sourceTokenAccount = tokenAccounts[0]!.pubkey;

    const depositIx = await getDepositInstructionAsync({
      depositor: owner,
      escrow: escrowAddress,
      mint,
      source: sourceTokenAccount,
      amount: 50_000n, // smallest unit (USDC is 6 decimals)
    });
    await sendInstructions(owner, [depositIx]);
    ```

    The escrow's vault PDA is created lazily on the first deposit per mint. An escrow can hold up to `MAX_MINTS = 8` distinct token mints.
  </Step>

  <Step title="Register a session key">
    ```typescript main.ts theme={null}
    import type { webcrypto } from "node:crypto";
    import { getAddressFromPublicKey } from "@solana/kit";
    import { getRegisterSessionKeyInstructionAsync } from "@faremeter/flex-solana";

    const sessionKeyPair = (await crypto.subtle.generateKey("Ed25519", true, [
      "sign",
      "verify",
    ])) as webcrypto.CryptoKeyPair;

    const sessionKeyAddress = await getAddressFromPublicKey(
      sessionKeyPair.publicKey,
    );

    const registerIx = await getRegisterSessionKeyInstructionAsync({
      owner,
      escrow: escrowAddress,
      sessionKey: sessionKeyAddress,
      expiresAtSlot: null, // never auto-expires
      revocationGracePeriodSlots: 100, // must be < refund_timeout_slots
    });
    await sendInstructions(owner, [registerIx]);
    ```

    Save the `sessionKeyPair` somewhere durable. You'll use it for every payment authorization until you revoke it. Treat it like any other private key — leaking it lets an attacker sign authorizations, though they still can't drain funds without colluding with the facilitator.
  </Step>

  <Step title="Pay with Faremeter">
    The `@faremeter/payment-solana/flex/client` package exposes `createPaymentHandler`, which plugs into `@faremeter/fetch`'s wrapper. It signs Flex authorizations on demand whenever a server returns a matching `402 Payment Required` response.

    ```typescript main.ts theme={null}
    import { createPaymentHandler } from "@faremeter/payment-solana/flex/client";
    import { wrap } from "@faremeter/fetch";

    const flexHandler = createPaymentHandler({
      network,
      escrow: escrowAddress,
      mint,
      sessionKeyPair,
      sessionKeyAddress,
      rpc,
    });

    const flexFetch = wrap(fetch, { handlers: [flexHandler] });

    const res = await flexFetch("https://api.example.com/protected");
    console.log(res.status, await res.json());
    ```

    Run it:

    ```bash theme={null}
    pnpm tsx main.ts
    ```
  </Step>
</Steps>

## What just happened

The handler:

1. Inspects the server's payment requirements and matches on `(scheme, network, mint)`.
2. Picks a `maxAmount` from the requirements and a fresh `authorizationId`.
3. Builds a serialized authorization using `serializePaymentAuthorization` and signs it with the session key.
4. Returns the signed payload; `@faremeter/fetch` retries the request with the `PAYMENT-SIGNATURE` header set.

The facilitator validates the authorization, holds the funds in memory, lets the request through, then settles the actual amount asynchronously. The client's call returns once the response is ready -- it does **not** wait for on-chain confirmation.

## Cleanup (optional)

Tearing down state cleanly requires coordination with the facilitator (who must co-sign `close_escrow`). The end-to-end sequence is:

1. **Wait for in-flight settlements to land on-chain.** Poll `fetchEscrowAccount` until `pendingCount` reflects everything you expect.
2. **Issue refunds for any pending settlements you don't want finalized.** This is a facilitator-signed instruction (`getRefundInstruction`); merchants typically trigger it via their own tooling.
3. **Revoke the session key, wait out the grace period, then close it** (`getRevokeSessionKeyInstruction` → `getCloseSessionKeyInstruction`).
4. **Close the escrow.** `getCloseEscrowInstruction` needs both the owner and the facilitator as `TransactionSigner`s, plus `mint_count * 2` writable remaining accounts (alternating `vault` PDA + owner-controlled destination per mint).

For a complete reference implementation including transaction confirmation and grace-period waits, see [`flex-payment.ts`](https://github.com/faremeter/faremeter/blob/main/scripts/solana-example/flex-payment.ts) in the faremeter monorepo.

If the facilitator has gone dark, the owner can recover funds without their cooperation. Once `current_slot > last_activity_slot + deadman_timeout_slots`, run `void_pending` for each open pending settlement (escrow owner is allowed to call it under deadman conditions), revoke and close every session key, then call `emergency_close`. `emergency_close` requires `pending_count == 0` AND `session_key_count == 0`, so the cleanup order matters.

## What's next

* [Concepts](/flex/concepts) -- the protocol-level model behind these calls.
* [Facilitator](/flex/facilitator) -- the operator side: hold management and settlement.
* [Program Reference](/flex/program-reference) -- every on-chain instruction, account, and constraint.
* [API Reference](/api/flex-solana.src) -- the full `@faremeter/flex-solana` surface.
