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.enabled2. 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 widget5. 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