Making a Withdrawal
Withdrawals move USDC from the Lateo pool to any Stellar address. A Groth16 proof with nullifiers is generated and verified on-chain — the pool contract atomically verifies the proof, marks nullifiers as spent, and transfers USDC.
Via Dashboard
- Click Withdraw in the Agent Dashboard
- Enter amount and destination address
- The proxy generates a ZK proof with nullifiers from your unspent notes
- The operator signs and submits
transact()with negativeext_amount - USDC arrives at the destination after on-chain confirmation
No Freighter signing is required for withdrawals — the operator signs because the pool contract transfers from itself.
Via API
curl -X POST http://localhost:3002/api/withdraw \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <jwt>" \
-d '{
"amount": "1.0000000",
"destination": "GDEST..."
}'Response:
{
"status": "confirmed",
"txHash": "dcb61c18...",
"amount": "1.0000000",
"destination": "GDEST...",
"zkProof": { "generated": true, "onChain": true }
}Via MCP
lateo_withdraw(amount: "1", destination: "GDEST...")What happens on-chain
The transact() call with negative ext_amount:
- Verifies the Groth16 proof (BN254 pairing check)
- Verifies nullifiers have NOT been previously spent (double-spend protection)
- Marks nullifiers as spent (stored permanently on-chain)
- Transfers USDC from pool to the specified recipient
- Inserts change commitments (if withdrawal < total input notes)
The transaction is atomic — if any step fails, no USDC moves and no nullifiers are marked.
Key difference from deposits
| Deposit | Withdrawal | |
|---|---|---|
| Who signs | User (Freighter) | Operator |
| ext_amount | Positive | Negative |
| USDC direction | User → Pool | Pool → Recipient |
| Nullifiers | None (dummy inputs) | Real (prevents double-spend) |
| Change note | None | Created if partial withdrawal |
Last updated on