Skip to main content

Create protection

Step 1: prepare the transaction​

curl -X POST $BFX/api/v2/hedges/prepare \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{ \"quote\": $QUOTE }"
{
"transactions": [
{
"chain": "stellar-testnet",
"family": "stellar",
"label": "Buy protection",
"payload": { "kind": "stellar", "networkPassphrase": "Test SDF Network ; September 2015", "xdr": "AAAA..." }
}
]
}

The quote must have been issued to the same user (UNAUTHORIZED otherwise). To buy for a business account, send X-BFX-Organisation with both the quote and this request, so the quote is bound to the business account.

Step 2: sign the authorisation with the passkey​

The transaction's authorisation entries for the user's smart wallet must be signed by their passkey. In the browser:

import { PasskeyKit } from "passkey-kit";
import { xdr, Address } from "@stellar/stellar-sdk";

async function signAuth(txXdr: string, credentialId: string, wallet: string, cfg: { rpcUrl: string; networkPassphrase: string; walletWasmHash: string }) {
const kit = new PasskeyKit(cfg);
await kit.connectWallet({ keyId: credentialId, getContractId: async () => wallet });

const envelope = xdr.TransactionEnvelope.fromXDR(txXdr, "base64");
const invoke = envelope.v1().tx().operations()[0].body().invokeHostFunctionOp();
const signed = [];
for (const entry of invoke.auth()) {
const c = entry.credentials();
const kind = c.switch().name;
const addr = kind === "sorobanCredentialsAddressV2" ? c.addressV2().address()
: kind === "sorobanCredentialsAddress" ? c.address().address() : null;
const ours = addr !== null && Address.fromScAddress(addr).toString() === wallet;
signed.push(ours ? await kit.signAuthEntry(entry) : entry);
}
invoke.auth(signed);
return envelope.toXDR("base64");
}

The user confirms with their device.

Signing for a business account​

A business account is an OpenZeppelin smart account, so its authorisation entries are signed differently: the passkey signs OpenZeppelin's auth digest, which binds the account and the context rule ids the member claims. A purchase has two contexts (the pool call and the USDC premium transfer inside it), so an admin claims the admins' rule for both and an operator claims the operators' pool rule, then the operators' USDC rule. Rule ids come from GET /api/organisations/:id. The BlockFinaX app's implementation shows the whole flow, using computeEntryAuthDigest from Smart Account Kit.

Step 3: relay it​

curl -X POST $BFX/api/v2/transactions/relay \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "chain": "stellar-testnet", "xdr": "<signed xdr>" }'
{ "chain": "stellar-testnet", "state": "CONFIRMED", "hash": "..." }

BlockFinaX checks the call is on the allowlist and authorised by the user's wallet, then submits it and pays the fee.

Step 4: confirm​

curl $BFX/api/v2/hedges -H "Authorization: Bearer $TOKEN"

The new position appears with status: "active".

If it fails​

Contract errorCauseDo
QuoteExpired (19)More than 120 s since signingRequest a new quote
QuoteReused (20)Quote already usedRequest a new quote
InsufficientCapacity (17)Free capital changed since the quoteRequest a new quote, or reduce size
SlippageExceeded (18)Past the deadline, or cost above max_costRequest a new quote
PoolNotAccepting (9), TenorNotOffered (10)Pool changed since the quoteRequest a new quote