The off-ramp intent is the core primitive of Stellar Intel. A user expresses what they want — "withdraw this USDC to this corridor" — signs it with their Stellar key, and the server verifies the signature before routing it. The user never hands over a key; they sign a canonical payload.
Source of truth: types/intent.ts,
lib/intent/, and the route
app/api/intent/offramp/route.ts.
The inner object describing one off-ramp operation
(OfframpIntentSchema in types/intent.ts):
| Field | Type | Rule |
|---|---|---|
anchorId |
string | Non-empty; an id from constants/anchors.ts. |
corridorId |
string | Non-empty; a corridor id (e.g. usdc-ngn). |
amount |
string | Positive decimal, ≤ 7 dp (/^\d+(\.\d{1,7})?$/). |
publicKey |
string | Stellar public key (G…, 56 chars). |
The wire format the server accepts (SignedIntentEnvelopeSchema). Construction:
- Canonicalize the
intent— keys sorted recursively, thenJSON.stringify(seedocs/CANONICAL_JSON.mdandlib/intent/). - Hash the canonical bytes — SHA-256 →
hash(lowercase hex, 64 chars). - Sign the canonical JSON bytes — Ed25519 via Freighter →
signature(base64). - Include the matching Stellar
publicKeyreturned by Freighter.
The schema enforces envelope.publicKey === intent.publicKey. The server
recomputes the canonical hash and verifies the Ed25519 signature before doing any
routing — a forged or tampered intent is rejected at the boundary.
lib/intent/replay.ts guards against a previously-signed envelope being
re-submitted. Treat each signed envelope as single-use.
POST /api/intent/offramp
Content-Type: application/json
<SignedIntentEnvelope>
- 200 — signature verified, intent accepted for routing.
- 400 — schema validation failed (bad amount, key mismatch, malformed hash).
- 401/422 — signature did not verify, or a replay was detected.
curl -sX POST https://stellar-intel.vercel.app/api/intent/offramp \
-H 'content-type: application/json' \
-d @signed-intent.jsontypes/intent.ts also exports IntentV1Schema / IntentV1 (re-exported from
lib/intent/schema.ts), the richer canonical intent the router consumes. The
off-ramp envelope above is the v1 path wired into the UI today; IntentV1 is the
shape the multi-anchor router (see docs/ROADMAP.md, Wave 1.2 /
v2.2) scores and splits across anchors.
docs/CANONICAL_JSON.md— the exact canonicalization rules.docs/ARCHITECTURE.md— where the intent sits in the flow.docs/ANCHOR_REPUTATION.md— how outcomes feed scoring.
{ "intent": { "anchorId": "cowrie", "corridorId": "usdc-ngn", "amount": "100", "publicKey": "GAB…", }, "hash": "<64-char lowercase hex sha-256 of canonical intent>", "signature": "<base64 ed25519 signature>", "publicKey": "GAB…", }