Quickstart
Give your users a private balance funded in local currency, with a few HTTP calls.
This is the flow Shield's own web follows. You need the stellar-private-payments browser SDK (0.2.1) for the keys, and Freighter or any SEP-43 wallet for the signatures.
1. Read the deployment
const info = await fetch(`${SHIELD}/v1/info`).then((r) => r.json());
// info.network_passphrase, info.pool.contract_id, info.modes.ramp.enabled2. Derive the user's pool keys
The wallet signs once to derive them. They stay in the browser.
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. Onboard with a signature
Sign this exact text with the account (SEP-53, Freighter's signMessage), with keys in lowercase hex. Without it, 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
}),
});Poll GET /v1/users/{account} while asp_status is pending, until it is allowed.
4. Create a ramp intent
// 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. Follow it to completed
Poll every 2–3 seconds. The three hashes appear as each step lands 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. Show the private balance
const pool = await account.pool({ poolContract: info.pool.contract_id });
await client.sync();
const stroops = await pool.balance(); // bigint, 7 decimals