Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 0 additions & 18 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -249,22 +249,6 @@ SOROBAN_BILLING_RPC_TIMEOUT_MS=5000
# Stellar secret key used to submit fee-bumped transactions. Default: unset; fee bumping fails closed when missing.
FEE_BUMPER_SECRET_KEY=

# -----------------------------------------------------------------------------
# Revenue settlement payouts (Soroban revenue pool)
# -----------------------------------------------------------------------------
# Deployed revenue pool contract invoked as distribute(caller, to, amount).
# Testnet example: CC5KINWRSLKSSQG4ZOTP3EZIOUVC6ER7HL5GZALYY4LVE6ZIMX4PMCSE
REVENUE_POOL_CONTRACT_ID=CC5KINWRSLKSSQG4ZOTP3EZIOUVC6ER7HL5GZALYY4LVE6ZIMX4PMCSE
# Stellar secret key (S...) of the settlement signer / revenue-pool caller.
# Required to submit payouts; never commit the real value.
SETTLEMENT_SIGNER_SECRET_KEY=
# Developer payout destinations are stored per-profile as `payout_address`
# (validated Ed25519 public key or contract id). Developers without one are
# skipped and their events remain unsettled until a payout address is set.
# Validation: StrKey.isValidEd25519PublicKey (G...) or StrKey.isValidContract (C...).
# A settlement is only marked completed after getTransaction returns SUCCESS;
# a submitted-but-unconfirmed payout keeps status 'submitted' and stays pending.

# -----------------------------------------------------------------------------
# Horizon (optional — set HORIZON_ENABLED=true to activate)
# -----------------------------------------------------------------------------
Expand All @@ -273,8 +257,6 @@ HORIZON_URL=https://horizon-testnet.stellar.org
HORIZON_TIMEOUT=2000
SETTLEMENT_STATUS_SYNC_INTERVAL_MS=60000
SETTLEMENT_STATUS_SYNC_TIMEOUT_MS=5000
# Deadline (ms) for polling getTransaction after a payout is submitted.
SETTLEMENT_CONFIRM_TIMEOUT_MS=30000
REVENUE_LEDGER_INDEXER_INTERVAL_MS=30000
REVENUE_LEDGER_INDEXER_BATCH_SIZE=500

Expand Down
2 changes: 1 addition & 1 deletion docs/billing-idempotency.md
Original file line number Diff line number Diff line change
Expand Up @@ -745,7 +745,7 @@ original request identifier. Do not generate a new identifier to retry a charge.

The lease prevents stale database writes, but cannot cancel an external side
effect after a disconnect or crash. Billing's persistent request identifier and
Soroban idempotency remain necessary. Apply migration `0027_idempotency_lease`
Soroban idempotency remain necessary. Apply migration `0028_idempotency_lease`
and drain old application instances before enabling takeover: old binaries do
not fence writes. Existing completed JSON records remain replayable. On rollback,
roll back the application before removing the lease column; new response-format
Expand Down
4 changes: 0 additions & 4 deletions docs/error-codes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,6 @@ error_codes:
section: Billing request
description: The requested billing record was not found

- code: BILLING_REQUEST_ID_CONFLICT
section: Billing request
description: The requestId was already used for a different deduction (different user, amount, API or endpoint)

# Billing forecast
- code: FORECAST_DATA_UNAVAILABLE
section: Billing forecast
Expand Down
2 changes: 1 addition & 1 deletion docs/guides/SETTLEMENT_STORE_DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ Any reintroduction of float arithmetic in these files is a build-time error.
- **Bounded Attempts**: `SETTLEMENT_MAX_RETRIES` defaults to 5; after the cap the row becomes `failed` and records the failure reason.
- **Backoff**: Retry delays use equal jitter over exponential intervals starting at 30 seconds, capped at 30 minutes.
- **Pending Query Index**: `idx_settlements_retry_after` is partial on rows with `status = 'retryable'`.
- **Migration**: `migrations/0026_settlement_retry_policy.sql` adds the retry state; its `.down.sql` rollback restores the original status constraint.
- **Migration**: `migrations/0031_settlement_retry_policy.sql` adds the retry state; its `.down.sql` rollback restores the original status constraint.

### 1. Data Persistence Invariants
- **Settlement Immutability**: Once created, settlement core fields (`id`, `developerId`, `amount`, `created_at`) never change
Expand Down
57 changes: 11 additions & 46 deletions docs/network-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,69 +26,35 @@ STELLAR_NETWORK=mainnet
```bash
STELLAR_TESTNET_HORIZON_URL=https://horizon-testnet.stellar.org
SOROBAN_TESTNET_RPC_URL=https://soroban-testnet.stellar.org
# Vault and settlement contract ids must be valid StrKey contract ids (C...).
STELLAR_TESTNET_VAULT_CONTRACT_ID=CBJZLUKQBWDMZPKNJAVONZ7GCCWADSETRXOPN5Z3XWAUNI4F2R7XDYQ
STELLAR_TESTNET_SETTLEMENT_CONTRACT_ID=CB4BUGCX5LFS5KYWAO2J4ZQTALOQSGXFFAOVKKGAS32OFZLU7O7YYWGHS
STELLAR_TESTNET_VAULT_CONTRACT_ID=CC...TESTNET_VAULT
STELLAR_TESTNET_SETTLEMENT_CONTRACT_ID=CC...TESTNET_SETTLEMENT
```

### Mainnet

```bash
STELLAR_MAINNET_HORIZON_URL=https://horizon.stellar.org
SOROBAN_MAINNET_RPC_URL=https://soroban-mainnet.stellar.org
# Vault and settlement contract ids must be valid StrKey contract ids (C...).
STELLAR_MAINNET_VAULT_CONTRACT_ID=CBUNJVCNRWCGWYNWYNWYNWYNWYNWYNWYNWYNWYNWYNW
STELLAR_MAINNET_SETTLEMENT_CONTRACT_ID=CB4BU4GCX5LFS5KYWAO2J4ZQTALOQSGXFFAOVKKGAS32OFZLU7O7YYWGHS
STELLAR_MAINNET_VAULT_CONTRACT_ID=CC...MAINNET_VAULT
STELLAR_MAINNET_SETTLEMENT_CONTRACT_ID=CB...MAINNET_SETTLEMENT
```

## Behavior Guarantees

- Deposit transaction building uses the active network Horizon URL.
- Deposit preparation rejects requests for a different network than the active configuration.
- Vault queries without an explicit `network` use `STELLAR_NETWORK` (then `SOROBAN_NETWORK`) rather than implicitly selecting testnet.
- `GET /api/vault/balance` refreshes snapshots from the configured vault's read-only Soroban views when older than `VAULT_SNAPSHOT_MAX_AGE_MS (default `60000`). A failed REC read returns the prior snapshot with `stale: true`.
- `GET /api/vault/balance` refreshes snapshots from the configured vault's read-only Soroban views when older than `VAULT_SNAPSHOT_MAX_AGE_MS` (default `60000`). A failed RPC read returns the prior snapshot with `stale: true`.
- `GET /api/vault/onchain` returns the live vault view state and ledger; it performs simulations only and never signs or submits transactions.
- Soroban settlement client resolves RPC URL and settlement contract ID from the active network.
- If a settlement contract ID is missing for the active network, the Soroban client fails fast.
- Stellar Horizon and Soroban RPC endpoints are validated at runtime before config export.
- Remote Stellar endpoints must use `https://`; plain `http://` is only allowed for localhost-based development endpoints.
- Stellar endpoint URLs must not include embedded credentials, query strings, or URL fragments.

## StrKey Validation

Contract ids, account keys and secret keys are validated at startup using Stellar StrKey checksum rules. Invalid values fail the environment parse before the server listens.

| Variable class | Expected format | Example prefix |
| --- | --- | --- |
| Contract id | StrKey contract (C...) | `CBJZLUKQX.XDYQ` |
| Public account key | StrKey Ed25519 public key (G...) | `GAAAA...` |
| Secret key | StrKey Ed25519 secret seed (S...) | `SAAAA...` |

Validated variables:
- `STELLAR_TESTNET_VAULT_CONTRACT_ID`, `STELLAR_TESTNET_SETTLEMENT_CONTRACT_ID`, `STELLAR_MAINNET_VAULT_CONTRACT_ID`, `STELLAR_MAINNET_SETTLEMENT_CONTRACT_ID`, and the `SOROBAN_TESTNET_VAULT_CONTRACT_ID` / `SOROBAN_TESTNET_SETTLEMENT_CONTRACT_ID` aliases
- `SOROBAN_BILLING_CONTRACT_ID` (Contract id)
- `SOROBAN_BILLING_SOURCE_ACCOUNT` (G-address)
- `SOROBAN_BILLING_BACKEND_SECRET_KEY` (S-seed)

Validation error messages never include the value of a secret key; they report the variable name and the failing rule only.

## Soroban Billing Network Settings

`SOROBAN_BILLING_NETWORK_PASSPHRASE` and `SOROBAN_BILLING_RPC_URL` are optional.

- When unset, they are derived from the active network (`config.stellar.networkPassphrase` and the active network RPC URL).
- When set, the passphrase must match the active network or validation fails with a clear mismatch message.
- The billing RPC URL falls back to the active network RPC URL. The `http://localhost:8000` fallback is only available in `development` and `test`; in production an RPC URL is required.

## Consistency Checks

- If both `SOROBAN_BILLING_CONTRACT_ID` and the active `*_VAULT_CONTRACT_ID` are set, they must be equal. If they differ, the billing id is treated as an explicit override and a warning is logged once at startup.
- When `SOROBAN_BILLING_SOURCE_ACCOUNT` and `SOROBAN_BILLING_BACKEND_SECRET_KEY` are both set, the public key derived from the secret must equal the source account.
- Production with the testnet passphrase requires an explicit `ALLOW_TESTNET_IN_PRODUCTION=true`. The demo deployment sets it deliberately.

## Network Match Rules for Deposit Preparation

The deposit flow enforces the active network at the controller layer. `DepositController` compares the `Network` field of the incoming `POST /api/vault/deposit/prepare` body against `config.stellar.network` and rejects any mismatch before touching Horizon or Soroban.
The deposit flow enforces the active network at the controller layer. `DepositController` compares the `Network` field of the incoming `Post /api/vault/deposit/prepare` body against `config.stellar.network and rejects any mismatch before touching Horizon or Soroban.

- Allowed values are the active network only (`testnet` or `mainnet`).
- A mismatch returns HTTP 400 with an `INVALID_NETWORK` error code and a message identifying the expected network.
Expand All @@ -113,18 +79,17 @@ For contract IDs, these aliases are also accepted:
- `SOROBAN_TESTNET_VAULT_CONTRACT_ID`
- `SOROBAN_MAINNET_VAULT_CONTRACT_ID`
- `SOROBAN_TESTNET_SETTLEMENT_CONTRACT_ID`
- `SOROBAN_MAINNET_SETTLEMENT_CONTRACT_ID`
- `SOROBAN_MAINNET_SETTLEMENT_CONTRACT_ID`

## Testnet vault deployment

The testnet deployment documented by Callora-Contracts uses:

| Contract | ID |
| --- | --- |
| Vault | `CBJZLUKQBWDMZPKNJAVONZ7GCCWADSETRWOPN5Z3XWAUNI4F2R7XDYQ` |
| Settlement | `CB4BUGCX5LFS5KYWAO2JIZQTALOQSGXFFAOVKKGAS32OFZLU7O7YYWGHS` |
| Revenue pool | `CC5KINWRBWDMZPKNJAVONZ7GCCWADSETRXOPN5Z3XWAUNI4F2R7XDYQ` |
| USDC SAC | `CAC7CFZ4L3LTNZCI4C3XI6UF7AHS3PYTTJ5QEG2PSLRGBGMWRQOZOT5` |
|---|---|
| Vault | `CBJZLUKQBWDMZPKNJAVONZ7GCCWADSETRXWOPN5Z3XWAUNI4F2R7XDYQ` |
| Settlement | `CB4BUGCX5LFS5KYWAO2J4ZQTALOQSGXFFAOVKKGAS32OFZLU7O7YYGHS` |
| USDC SAC | `CAC7CFZ42L3LTTZCI4C3XI6UF7AHS2PYTNJ5QEG2PSLRGDGMWRQOZOT5` |

The testnet RPC is `https://soroban-testnet.stellar.org` and its passphrase is `Test SDF Network ; September 2015`. Set `STELLAR_TESTNET_VAULT_CONTRACT_ID` to enable contract-aware health checks and to select the deployed vault for public demo reads when `DEMO_MODE=true`.

Expand Down
27 changes: 0 additions & 27 deletions docs/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1253,18 +1253,6 @@
"requestId": "req-abc123"
}
},
"requestIdConflict": {
"summary": "requestId already used for a different deduction",
"value": {
"success": false,
"error": {
"code": "BILLING_REQUEST_ID_CONFLICT",
"message": "requestId already used for a different deduction"
},
"requestId": "req-abc123",
"timestamp": "2026-07-28T10:00:00.000Z"
}
},
"reconciliationRequired": {
"summary": "Previous deduction attempt pending reconciliation",
"value": {
Expand Down Expand Up @@ -1354,20 +1342,6 @@
"application/json": {
"schema": {
"$ref": "#/components/schemas/BillingBulkDeductErrorResponse"
},
"examples": {
"requestIdConflict": {
"summary": "A bulk item reuses a requestId with different deduction fields",
"value": {
"success": false,
"error": {
"code": "BILLING_REQUEST_ID_CONFLICT",
"message": "requestId already used for a different deduction"
},
"requestId": "req-abc123",
"timestamp": "2026-07-28T10:00:00.000Z"
}
}
}
}
}
Expand Down Expand Up @@ -8376,7 +8350,6 @@
"IDEMPOTENCY_CONFLICT",
"IDEMPOTENCY_RESPONSE_TOO_LARGE",
"IDEMPOTENCY_IN_PROGRESS",
"BILLING_REQUEST_ID_CONFLICT",
"SIMULATION_FAILED",
"INVALID_AUTH_HEADER",
"MISSING_TOKEN",
Expand Down
11 changes: 0 additions & 11 deletions docs/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,6 @@ This document describes the core tables that power the Callora API marketplace,

## Tables

### `usage_events` and `usage_event_request_ids`

`usage_events` is hash partitioned by `developer_id`; its per-partition primary
key includes that partition key, so it cannot enforce global request ID
uniqueness. Every writer reserves `request_id` in `usage_event_request_ids`
within the same transaction before inserting the event. The registry primary
key provides the global idempotency guarantee and its unique `usage_event_id`
is the target for `revenue_ledger.usage_event_id`. The partitioned parent also
contains `api_key VARCHAR(255)` and `status_code INTEGER NOT NULL DEFAULT 200`
for persistent usage-store reads and writes.

### `apis`

Stores the top-level API products created by developers.
Expand Down
10 changes: 0 additions & 10 deletions migrations/0026_add_developer_payout_address.sql

This file was deleted.

32 changes: 0 additions & 32 deletions migrations/0027_fee_abstraction_quotes.sql

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
-- Rollback: 0026_usage_events_deduction_status
-- Rollback: 0027_usage_events_deduction_status
-- destructive-approved: #1435

DROP INDEX IF EXISTS idx_usage_events_status_created;
Expand Down
10 changes: 0 additions & 10 deletions migrations/0027_usage_events_request_fingerprint.sql

This file was deleted.

16 changes: 0 additions & 16 deletions migrations/0028_usage_event_request_ids.down.sql

This file was deleted.

54 changes: 0 additions & 54 deletions migrations/0028_usage_event_request_ids.sql

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
-- migration. GET /api/billing has been rewritten to serve from usage_events,
-- so this index is dead. Drop it if it somehow exists.
--
-- destructive-approved: #billing-history
-- destructive-approved: #1532
DROP INDEX IF EXISTS idx_billing_requests_lookup_hot;
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
-- Rollback: 0026_usage_events_amount_units
-- Rollback: 0030_usage_events_amount_units
-- destructive-approved: #1276

BEGIN;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
-- destructive-approved: #1547
-- Add bounded retry state for settlement reconciliation.
ALTER TABLE settlements
ADD COLUMN retry_after TIMESTAMP NULL,
Expand All @@ -13,4 +14,4 @@ ALTER TABLE settlements

CREATE INDEX idx_settlements_retry_after
ON settlements(retry_after)
WHERE status = 'retryable';
WHERE status = 'retryable';
Loading
Loading