Inicio rápido

Dale a tus usuarios un saldo privado fondeado en moneda local, con unas pocas llamadas HTTP.

Es el mismo flujo que sigue la web de Shield. Necesitas el SDK de navegador stellar-private-payments (0.2.1) para las llaves, y Freighter o cualquier wallet SEP-43 para las firmas.

1. Lee el despliegue

const info = await fetch(`${SHIELD}/v1/info`).then((r) => r.json());
// info.network_passphrase, info.pool.contract_id, info.modes.ramp.enabled

2. Deriva las llaves del pool del usuario

La wallet firma una vez para derivarlas. Se quedan en el navegador.

import init, { Client, Storage } from "stellar-private-payments";

await init();
const client = await Client.new({ rpcUrl, storage: await Storage.open(), contractConfig, circuitsBaseUrl, circuitLock });
const account = await client.account({ networkPassphrase, userAddress }, signer); // e.g. Freighter
const keys = await account.derivePrivacyKeys(); // the wallet signs once
const aspLeaf = await account.deriveAspUserLeaf();

3. Registra al usuario con una firma

La cuenta firma exactamente este texto (SEP-53, signMessage de Freighter), con las llaves en hex en minúsculas. Sin la firma, el API responde 400 invalid_signature.

const message = [
  "Lendara Shield onboarding",
  "network: testnet",
  `account: ${userAddress}`,
  `note_public_key: ${keys.notePublicKey.toLowerCase()}`,
  `encryption_public_key: ${keys.encryptionPublicKey.toLowerCase()}`,
  `asp_leaf: ${aspLeaf.toLowerCase()}`,
].join("\n");
const { signedMessage } = await signMessage(message, { address: userAddress, networkPassphrase });

await fetch(`${SHIELD}/v1/onboarding`, {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({
    stellar_account: userAddress,
    note_public_key: keys.notePublicKey.toLowerCase(),
    encryption_public_key: keys.encryptionPublicKey.toLowerCase(),
    asp_leaf: aspLeaf.toLowerCase(),
    signature: signedMessage,
    scenario: "approved", // sandbox only
  }),
});

Consulta GET /v1/users/{account} mientras asp_status sea pending, hasta que quede allowed.

4. Crea un intent de rampa

// Only the account's owner can start a deposit: it signs the amount and a single-use id.
const request = { fiat_currency: "CRC", amount: "5000", request_id: crypto.randomUUID(), issued_at: Math.floor(Date.now() / 1000) };
const text = [
  "Lendara Shield intent",
  "network: testnet",
  `account: ${userAddress}`,
  `fiat_currency: ${request.fiat_currency}`,
  `amount: ${request.amount}`,
  `request_id: ${request.request_id}`,
  `issued_at: ${request.issued_at}`,
].join("\n");
const signed = await signMessage(text, { address: userAddress, networkPassphrase });

const intent = await fetch(`${SHIELD}/v1/intents`, {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ mode: "ramp", stellar_account: userAddress, ...request, signature: signed.signedMessage }),
}).then((r) => r.json());

window.open(intent.interactive_url, "anchor", "popup"); // the anchor's SEP-24 widget

5. Síguelo hasta completed

Consulta cada 2 o 3 segundos. Los tres hashes aparecen a medida que cada paso entra on-chain.

const FINAL = ["completed", "rejected", "expired", "error"];
let current = intent;
while (!FINAL.includes(current.status)) {
  await new Promise((r) => setTimeout(r, 2500));
  current = await fetch(`${SHIELD}/v1/intents/${intent.id}`).then((r) => r.json());
}
// current.transactions: { intake_payment, pool_deposit, pool_transfer }

6. Muestra el saldo privado

const pool = await account.pool({ poolContract: info.pool.contract_id });
await client.sync();
const stroops = await pool.balance(); // bigint, 7 decimals