# FreedomSwap API

FreedomSwap provides one-time quoted swaps and destination-bound permanent
conversion routes. Integrate from a trusted backend. Never put either partner
credential in a browser, desktop application, mobile application, extension, or
public repository.

## Public resources

- Base URL: `https://xchgo.com/api/v1`
- HTML guide: https://xchgo.com/developers
- OpenAPI 3.1: https://xchgo.com/api/v1/openapi.json
- Node.js SDK: https://xchgo.com/sdk/freedomswap-client.mjs
- Support: support@xchgo.com

## Credentials

Each partner receives exactly two static backend credentials:

1. `FREEDOMSWAP_KEY_ID`
2. `FREEDOMSWAP_SHARED_SECRET`

Set `FREEDOMSWAP_API_AUDIENCE=https://xchgo.com`. The SDK signs the method,
path, exact JSON body, audience, timestamp, nonce, partner user ID, and swap
idempotency key. The `userId` is your stable, opaque customer identifier; it is
request context, not another credential.

```js
// curl -O https://xchgo.com/sdk/freedomswap-client.mjs
import {
  createFreedomSwapClient,
  createIdempotencyKey,
} from "./freedomswap-client.mjs";

const freedomSwap = createFreedomSwapClient({
  baseUrl: "https://xchgo.com/api/v1",
  audience: "https://xchgo.com",
  keyId: process.env.FREEDOMSWAP_KEY_ID,
  sharedSecret: process.env.FREEDOMSWAP_SHARED_SECRET,
  userId: authenticatedUser.id,
});
```

## One-time swap

1. Load `GET /assets` and source-scoped `GET /pairs` guidance.
2. Request `POST /quotes` with the exact asset, network, amount, rate type,
   and refund policy.
3. Display the returned quote and create the swap before `expiresAt`.
4. Generate and persist an idempotency key before calling `POST /swaps`.
5. Poll `GET /swaps/{id}` until `depositAddress` is non-null. Only then show
   the exact address and `depositMemo`, if present.
6. Track the swap by partner authentication or give the customer device only
   its swap-scoped access token.

One-time exchange costs vary with the selected venue and are already reflected in
the quoted receive amount. Pair guidance therefore does not publish a universal
fee percentage. Use the quote response rather than estimating costs from pair
guidance; the sending wallet may charge its own network fee separately.

```js
const request = {
  sourceAsset: "BTC",
  sourceNetwork: "BTC",
  destinationAsset: "ETH",
  destinationNetwork: "ETH",
  sourceAmount: "0.01",
  rateType: "best",
  refundMode: "sender",
};

const { quote } = await freedomSwap.quote(request);
const idempotencyKey = createIdempotencyKey();
// Persist idempotencyKey with this pending checkout before the network call.

const { order, accessToken } = await freedomSwap.createSwap({
  ...request,
  rateType: quote.rateType,
  destinationAddress: customer.ethAddress,
  quoteId: quote.id,
}, { idempotencyKey });
```

If the create response is lost, retry the identical body with the same
`Idempotency-Key`. The original response is replayed. The same key with a
different body returns HTTP 409. Never generate a replacement key merely because
a response was uncertain.

HTTP 202 means creation is queued; it does not authorize a deposit. Wait for a
non-null `depositAddress`.

## Permanent routes

A permanent route is reusable and bound to one verified settlement wallet. The
backend HMAC authenticates the partner, but does not replace wallet ownership
proof.

1. Call `POST /permanent-routes/challenges` with the fUSD or ZANO settlement
   target.
2. Have that Zano wallet sign the exact returned `challenge`.
3. Call `POST /permanent-routes` with `challengeId`, `message`, the
   64-hex-character public spend key, and the 128-hex-character signature.
4. Display only non-empty entries from `addresses` and only while the returned
   route is active.
5. Call `POST /permanent-routes/{id}/expect-deposit` immediately before an
   expected payment, then poll `GET /permanent-routes/{id}/deposits`.

ETH, USDT on Ethereum, and USDC on Ethereum may intentionally use the same
Ethereum address. They remain separate assets for accounting and accumulation.

Use the returned `min_swap_usd`; it is currently $10 for fUSD routes. A smaller
deposit remains at the assigned address and accumulates only with later deposits
of the same asset. Read `accumulated_usd`, `remaining_usd`, and `valued_at`
to explain this to the customer. Do not send on a paused route.

Use the returned `fee` and `max_auto_swap_usd` as well. Current fUSD routes
deduct a 1% service fee from the fUSD output and retain bounded Ethereum execution
gas from the source amount. A value above `max_auto_swap_usd` is not eligible
for automatic processing; a published address cannot reject an unsolicited transfer.

## Status contract

Public status values are `waiting`, `confirming`, `exchanging`, `sending`,
`accumulating`, `finished`, `refunded`, `failed`, `expired`, `overdue`,
and `review`.

Do not infer behavior from the name alone. Use:

- `terminal`: whether the receipt can still transition;
- `actionRequired`: `none`, `customer`, or `operator`;
- `retryable`: whether repeating the same API action is useful and safe;
- `reasonCode`: stable customer-facing reason category.

A `review` response is not always terminal. Continue polling when
`terminal=false`. The SSE stream closes only when `terminal=true`.

## Errors and retry policy

Errors use `{ "error": { "code": "...", "message": "..." } }`.

- 400: correct the input; do not retry it unchanged.
- 401/403: check backend credentials, audience, user context, or ownership proof.
- 409: request a fresh quote unless it is an idempotency-in-progress response;
  for an uncertain create, retain the original body and key.
- 429: wait for `Retry-After`, then use exponential backoff with jitter.
- 500/502/503: retry safe reads with backoff. Never invent a quote, address,
  memo, status, or settlement result.

## Security rules

- Keep both partner credentials in a backend secret manager.
- Never log the shared secret, signed authentication headers, swap access token,
  wallet private key, or recovery phrase.
- Persist an idempotency key before the corresponding swap-create request.
- Treat asset plus network as one identifier.
- Display deposit addresses and memos exactly as returned.
- Check `availability.acceptingNewRoutes` before starting a one-time swap and
  handle permanent-route 503 responses separately.
- Use the OpenAPI document as the machine-readable request and response contract.
