Peer Documentation

website·Indexed 33 days ago

https://docs.peer.xyz/

Developer guides for the Peer ecosystem, including using @zkp2p/sdk for onramp and intent fulfillment flows with Buyer TEE routing, and building vaults to manage deposit rates via EscrowV2.

Tokens
2.4K
Snippets
11
Records
13
Agent score
15%

What's inside Peer

  1. Select and validate payment metadata rows

    When receiving metadata from the extension, select the correct payment row by matching expected values (amount, currency, recipient, or paymentId). Use row.originalIndex for the provider index rather than the array index of a filtered UI list.
    function selectPaymentRow(
      rows: PeerMetadataRow[],
      expected: { amount?: string; currency?: string; paymentId?: string; recipient?: string; },
    ): PeerMetadataRow | null {
      const visibleRows = rows.filter((row) => !row.hidden && isBuyerTeeParams(row.params));
    
      if (expected.paymentId) {
        const byPaymentId = visibleRows.find((row) => row.paymentId === expected.paymentId);
        if (byPaymentId) return byPaymentId;
      }
    
      return visibleRows.find((row) =>
        (!expected.amount || row.amount === expected.amount) &&
        (!expected.currency || row.currency === expected.currency) &&
        (!expected.recipient || row.recipient === expected.recipient)
      ) ?? null;
    }
  2. Implement headless Buyer TEE capture flow

    The full flow requires registering a metadata listener via onMetadataMessage before calling authenticate. Once a matching row is found, use Zkp2pClient.fulfillIntent() to verify the proof and fulfill the intent on-chain.
    // 1. Register listener
    peerSdk.onMetadataMessage(async (message) => {
      const selectedRow = selectPaymentRow(message.metadata, expectedPayment);
      const proof = buildBuyerTeeProof(selectedRow, message.buyerTeeCapture, config);
      
      await client.fulfillIntent({
        intentHash,
        proof,
        attestationServiceUrl,
      });
    });
    
    // 2. Start capture
    await peerSdk.authenticate({
      actionType: config.actionType,
      attestationActionType: config.attestationActionType ?? config.actionType,
      attestationServiceUrl,
      captureMode: "buyerTee",
      platform: config.platform,
    });
  3. Build Buyer TEE proof parameters

    Construct the BuyerTeePaymentProofInput using the selected metadata row and the encrypted session material from the capture. If includeMetadataIndex is enabled in the config, the row.originalIndex must be added to the params object.
    function buildBuyerTeeProof(
      row: PeerMetadataRow,
      capture: PeerBuyerTeePaymentCapture | null | undefined,
      config: BuyerTeePlatformConfig,
    ): BuyerTeePaymentProofInput {
      return {
        proofType: "buyerTee",
        encryptedSessionMaterial: capture!.encryptedSessionMaterial,
        params: {
          ...row.params,
          ...(config.includeMetadataIndex ? { index: row.originalIndex } : {}),
        },
        actionPlatform: config.actionPlatform,
        actionType: config.attestationActionType ?? config.actionType,
      };
    }
  4. Submit Batch Rate Updates

    Update vault minimum rates using setVaultMinRatesBatch. Currency and rate arrays must be grouped by the payment-method index. It is recommended to persist the transaction hash and wait for the receipt before scheduling subsequent updates to avoid redundant submissions.
    import type { Hex, PublicClient } from "viem";
    
    declare const publicClient: PublicClient;
    declare const paymentMethods: Hex[];
    declare const currencies: Hex[][];
    declare const rates: bigint[][];
    
    const hash = await client.setVaultMinRatesBatch({
      rateManagerId,
      paymentMethods,
      currencies,
      rates,
    });
    
    const receipt = await publicClient.waitForTransactionReceipt({ hash });
    if (receipt.status !== "success") throw new Error("Rate update reverted");
  5. Initialize the Peer SDK Client

    To interact with Peer vaults, use the @zkp2p/sdk and viem. The SDK requires a WalletClient for transaction approvals and must be configured for the Base chain (ID 8453). Ensure you use the stable SDK version to maintain compatible dependency pins for contracts, indexer schemas, and attestations.
    npm install @zkp2p/sdk@latest viem
    import { Zkp2pClient } from "@zkp2p/sdk";
    import type { WalletClient } from "viem";
    
    declare const walletClient: WalletClient;
    
    const client = new Zkp2pClient({
      walletClient,
      chainId: 8453,
      runtimeEnv: "production",
      baseApiUrl: "https://api.zkp2p.xyz",
    });
  6. Verify Peer extension state and version

    Before starting capture, ensure the Peer extension is installed, connected, and at least version 0.6.3. Use peerSdk.getState(), peerSdk.requestConnection(), and peerSdk.getVersion() to validate the environment.
    async function ensurePeerReady() {
      const state = await peerSdk.getState();
    
      if (state === "needs_install") {
        peerSdk.openInstallPage();
        throw new Error("Peer extension 0.6.3 is required");
      }
    
      if (state === "needs_connection") {
        const approved = await peerSdk.requestConnection();
        if (!approved) throw new Error("Peer extension connection was not approved");
      }
    
      const version = await peerSdk.getVersion();
      // Logic to check if version >= 0.6.3
      if (!isPeerExtension063OrNewer(version)) {
        throw new Error(`Peer extension 0.6.3 or newer is required; found ${version}`);
      }
    }
  7. Initialize the Peer Extension SDK

    To integrate Buyer TEE capture, use the @zkp2p/sdk (version 0.5.0 or newer). Initialize the SDK using createPeerExtensionSdk with the current window object.
    import { createPeerExtensionSdk, Zkp2pClient } from "@zkp2p/sdk";
    
    const peerSdk = createPeerExtensionSdk({ window });
  8. Manage Vault Rates and Delegations via Contracts

    Vault operations are split between RateManagerV1 (creation and rate settings) and EscrowV2 (delegation and floors).

    Key Technical Details:

    • USDC Decimals: 6 decimals.
    • Rate Decimals: 18 decimals (fiat per USDC). Example: 750500000000000000 represents 0.7505 GBP per USDC.
    • Identifiers: Payment methods and currency codes are bytes32 hashes. Use SDK methods getPaymentMethodsCatalog() and resolveFiatCurrencyBytes32() to retrieve these.
    • Fees: The maximum fee set during createRateManager is immutable.
    JobContract call
    Create a vaultRateManagerV1.createRateManager(config)
    Set one rateRateManagerV1.setRate(rateManagerId, paymentMethod, currencyCode, rate)
    Set several ratesRateManagerV1.setRateBatch(rateManagerId, paymentMethods, currencyCodes, rates)
    Delegate a depositEscrowV2.setRateManager(depositId, rateManager, rateManagerId)
    Clear delegationEscrowV2.clearRateManager(depositId)
    Set a fixed floorEscrowV2.setCurrencyMinRate(depositId, paymentMethod, currencyCode, rate)
  9. Query Vault State via SDK Indexer

    Use the SDK's indexer to retrieve historical and aggregate data for rate managers. Note that indexer results may lag behind on-chain transactions; always confirm via transaction receipts for real-time state.
    const managers = await client.indexer.getRateManagers();
    
    declare const rateManagerId: `0x${string}`;
    const detail = await client.indexer.getRateManagerDetail(rateManagerId);
    const delegations = await client.indexer.getRateManagerDelegations(rateManagerId);
    const daily = await client.indexer.getManagerDailySnapshots(rateManagerId);
  10. Directly verify Buyer TEE payments (Preflight)

    For custom transaction builders, you can bypass client.fulfillIntent() and call the attestation service endpoint directly via POST. This requires the encryptedSessionMaterial, params, and the full intent object (obtained via client.getFulfillIntentInputs).
    // POST {attestationServiceUrl}/buyer/verify/{actionPlatform}/{actionType}
    const response = await fetch(
      `${attestationServiceUrl}/buyer/verify/${encodeURIComponent(config.actionPlatform)}/${encodeURIComponent(config.attestationActionType ?? config.actionType)}`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          encryptedSessionMaterial,
          params,
          chainId,
          intent: { ...intentInputs, timestampBufferMs: "300000" },
        }),
      }
    );
  11. Understand Peer Rate Resolution Logic

    When processing new intents, EscrowV2 determines the applicable rate using the following priority logic:

    1. If the configured oracle has an invalid or stale spread rate $ ightarrow$ return 0.
    2. escrowFloor = $\max(\text{fixedFloor}, \text{oracleSpreadRate})$.
    3. If escrowFloor == 0 $ ightarrow$ return 0.
    4. If no manager is configured $ ightarrow$ return escrowFloor.
    5. If the manager call reverts $ ightarrow$ return escrowFloor.
    6. If managerRate == 0 $ ightarrow$ return 0.
    7. Final Rate = $\max(\text{managerRate}, \text{escrowFloor})$.

    Critical Caveats:

    • A result of 0 disables that specific pair for new intents.
    • A zero manager rate disables the pair even if a fixed floor exists.
    • A positive manager rate cannot make a deposit tradable if there is no usable escrow floor.
  12. Configure Buyer TEE routing and platform settings

    Buyer TEE routing requires a BuyerTeePlatformConfig object. A critical constraint is the includeMetadataIndex flag: it must be set to true only for Venmo, Cash App, Revolut, and Zelle. It must be omitted (or false) for PayPal, Wise, Monzo, or Chime.
    type BuyerTeePlatformConfig = {
      actionPlatform: string;
      actionType: string;
      attestationActionType?: string;
      includeMetadataIndex?: boolean;
      platform: string;
    };
    
    const config: BuyerTeePlatformConfig = {
      actionPlatform: "venmo",
      actionType: "transfer_venmo",
      includeMetadataIndex: true, // Only for Venmo, Cash App, Revolut, Zelle
      platform: "venmo",
    };
    
    const attestationServiceUrl = "https://attestation-service.zkp2p.xyz";