Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6f4b302
feat(worker): hosted payment mode PoC — delegate signing to a payment…
Nic-dorman Aug 25, 2026
a29959a
feat(worker): relay signed quotes to the payment gateway (V2-926)
Nic-dorman Aug 25, 2026
efa8a6e
feat(worker): authenticate at the payment gateway with a tenant API key
Nic-dorman Aug 25, 2026
d64ab85
feat(worker): stamp payment provenance on uploads — wallet vs hosted …
Nic-dorman Aug 26, 2026
eec2eb2
fix(web): hosted gateway option in transactions type filter (V2-447 s…
Nic-dorman Aug 26, 2026
5bea03a
feat(web): hosted-mode banner on the wallets screen — gateway pays, n…
Nic-dorman Aug 26, 2026
1ad8fcc
feat(web): hosted billing surface — credits in wallets banner, low-ba…
Nic-dorman Aug 27, 2026
03890f0
feat(worker): wallet-less hosted mode + async payment polling (V2-929)
Nic-dorman Aug 27, 2026
4d9e63a
fix(handlers): wallet-less hosted mode — upload gate and wallet-statu…
Nic-dorman Aug 27, 2026
0cb0ec6
fix(worker): send empty tx_hashes object on full-dedup finalize
Nic-dorman Aug 27, 2026
d4013b5
feat(billing): in-app top-up flow — /admin/billing + gateway relay (V…
Nic-dorman Aug 28, 2026
c44432d
feat(web): crypto-free display — fiat across hosted surfaces (V2-1100…
Nic-dorman Aug 28, 2026
34903a1
fix(worker): retry /pay on transport failure — the batch is idempotent
Nic-dorman Aug 28, 2026
ec8d479
feat(billing): credit history — grants and card top-ups in one table …
Nic-dorman Aug 28, 2026
59a482e
feat(hosted): record the gateway's gross debit + itemized network fee…
Nic-dorman Aug 31, 2026
57903fd
feat(hosted): V2-1113 — fee-aware pre-upload estimates and gross max_…
Nic-dorman Sep 1, 2026
042d1d6
feat(hosted): V2-1114 — ≈ GB remaining at current prices on Billing
Nic-dorman Sep 1, 2026
2b2020f
fix(database): apply busy_timeout + synchronous per-connection via DSN
Nic-dorman Sep 1, 2026
2a91d5b
refactor(config): rename payment_mode to payment_backend — who pays, …
Nic-dorman Sep 16, 2026
932ad72
docs(swagger): regenerate — carries the payment_backend wording and t…
Nic-dorman Sep 16, 2026
6362053
build: pin antd 0.13.0 — drop the local antd-go replace, bump .antd-v…
Nic-dorman Sep 16, 2026
fa0f412
fix(database): add the missing postgres migration 017 (gateway_fee_at…
Nic-dorman Sep 16, 2026
a5f2d64
feat(hosted): wallet-less writer boot — wallet encryption key optiona…
Nic-dorman Sep 16, 2026
348c0eb
fix(hosted): address #163 review — API key fail-fast, drop PoC accoun…
Nic-dorman Sep 17, 2026
fc6d80c
fix(hosted): #163 review suggestions (V2-1269) — per-gateway pricing …
Nic-dorman Sep 17, 2026
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
2 changes: 1 addition & 1 deletion .antd-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.12.0
v0.13.0
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# platform); keep ANTD_IMAGE in lockstep with .antd-version. release.yml
# passes the pinned tag explicitly; this default keeps `docker compose
# up --build` and bare `docker build` working out of the box.
ARG ANTD_IMAGE=ghcr.io/withautonomi/antd:v0.12.0
ARG ANTD_IMAGE=ghcr.io/withautonomi/antd:v0.13.0
FROM ${ANTD_IMAGE} AS antd

# Build frontend on the native arch — JS output is arch-independent.
Expand Down
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,14 @@ data_dir = "./data"

# Required
jwt_secret = "your-secret-key-at-least-32-chars"
wallet_encryption_key = "64-hex-char-key-for-aes-256-gcm"
wallet_encryption_key = "64-hex-char-key-for-aes-256-gcm" # local payment backend only; optional with payment_backend = "hosted"

# Payments: "local" (default) signs uploads with a wallet you add in the admin UI;
# "hosted" pays through the Autonomi Pay gateway from prepaid credits — no wallet,
# no EVM RPC on this instance. Requires antd >= 0.13.0.
# payment_backend = "hosted"
# payment_gateway_url = "https://pay.example.com"
# payment_gateway_api_key = "pgk_..."

# Bootstrap admin — seeds the first admin on a fresh DB (self-registration is
# off by default; the server won't start with no admin and no seed).
Expand Down Expand Up @@ -183,7 +190,10 @@ curl -X POST /api/v2/tokens \
| `INDELIBLE_JWT_SECRET` | **Required.** Secret for JWT signing; **minimum 32 characters** (the server refuses to start below this). Generate with `openssl rand -hex 32` | -- |
| `INDELIBLE_JWT_SECRET_PREVIOUS` | Comma-separated **verify-only** former JWT secrets, kept during a rotation so live sessions survive. New tokens always sign with `INDELIBLE_JWT_SECRET`; these only verify already-issued tokens until they expire. See [key-rotation guide](docs/guides/key-rotation.md#rotating-the-jwt-secret) | -- |
| `INDELIBLE_JWT_SECRET_FILE` | Path to a file holding the JWT secret (Docker/K8s secrets); takes precedence over `INDELIBLE_JWT_SECRET` | -- |
| `INDELIBLE_WALLET_ENCRYPTION_KEY` | **Required.** 64-char hex key for wallet encryption (AES-256-GCM) | -- |
| `INDELIBLE_WALLET_ENCRYPTION_KEY` | **Required for the local payment backend.** 64-char hex key for wallet + OIDC client-secret encryption (AES-256-GCM). Optional with `INDELIBLE_PAYMENT_BACKEND=hosted` (no wallet exists); set it there only if you want OIDC login, whose client secrets it also encrypts | -- |
| `INDELIBLE_PAYMENT_BACKEND` | Who pays for uploads: `local` (this instance's wallet signs) or `hosted` (the Autonomi Pay gateway pays from the tenant's prepaid credits; no wallet, no EVM RPC on this instance; antd >= 0.13.0). Unknown values refuse to start. Not to be confused with the upload API's `payment_mode` (`auto`/`merkle`/`single`), which is how a payment is structured on-chain | `local` |
| `INDELIBLE_PAYMENT_GATEWAY_URL` | Gateway base URL. **Required** when the backend is `hosted` | -- |
| `INDELIBLE_PAYMENT_GATEWAY_API_KEY` | Tenant API key issued by the gateway (Bearer). Required when the backend is `hosted` | -- |
| `INDELIBLE_WALLET_ENCRYPTION_KEY_FILE` | Path to a file holding the wallet encryption key (Docker/K8s secrets); takes precedence over `INDELIBLE_WALLET_ENCRYPTION_KEY` | -- |
| `INDELIBLE_WALLET_ENCRYPTION_KEY_PREVIOUS` | Comma-separated **decrypt-only** former wallet keys, so the running service can read rows not yet re-encrypted during a rotation. See [key-rotation guide](docs/guides/key-rotation.md#rotating-the-wallet-encryption-key) | -- |
| `INDELIBLE_SECRETS_BACKEND` | Where key material is sourced from. `env` sources from env / config-file / `_FILE`. Other backends (Vault, cloud KMS) plug in behind the same provider seam | `env` |
Expand Down
9 changes: 9 additions & 0 deletions cmd/indelible/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,15 @@ func main() {
slog.SetDefault(logger)

slog.Info("starting indelible", "version", buildinfo.Version, "port", cfg.Port, "db_driver", cfg.DBDriver())
if cfg.PaymentBackend.Hosted() {
// V2-929: uploads are paid by the gateway from prepaid credits — no
// wallet record, no EVM RPC, and the wallet encryption key is optional
// (it only gates OIDC client-secret storage here).
slog.Info("payment backend: hosted — uploads paid by the gateway, no wallet or EVM RPC on this instance",
"gateway", cfg.PaymentGatewayURL, "wallet_key_configured", cfg.WalletKeyConfigured())
} else {
slog.Info("payment backend: local — uploads signed with the instance wallet", "network", cfg.Network)
}

// Managed antd
var antdMgr *managedantd.Manager
Expand Down
187 changes: 186 additions & 1 deletion docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,157 @@ const docTemplate = `{
}
}
},
"/admin/billing": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "Hosted-mode billing: gateway credits and credited top-up history",
"produces": [
"application/json"
],
"tags": [
"Admin: Billing"
],
"summary": "Billing summary",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"400": {
"description": "Not in hosted payment mode",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
}
},
"/admin/billing/topup-checkout": {
"post": {
"security": [
{
"BearerAuth": []
}
],
"description": "Create a Stripe Checkout session at the gateway; returns the hosted payment page URL and the exact credit",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Admin: Billing"
],
"summary": "Start a card top-up",
"parameters": [
{
"description": "Amount in USD cents and return URLs",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/internal_handlers.topupCheckoutRequest"
}
}
],
"responses": {
"200": {
"description": "session_id, url, credit_atto",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"502": {
"description": "Gateway unreachable",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
}
},
"/admin/billing/topup-sync": {
"post": {
"security": [
{
"BearerAuth": []
}
],
"description": "Ask the gateway to retrieve the Checkout session from Stripe and credit it if paid (idempotent; webhook-loss fallback)",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Admin: Billing"
],
"summary": "Sync a top-up",
"parameters": [
{
"description": "Checkout session id",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/internal_handlers.topupSyncRequest"
}
}
],
"responses": {
"200": {
"description": "credited, payment_status",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"502": {
"description": "Gateway unreachable",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
}
},
"/admin/departments": {
"get": {
"security": [
Expand Down Expand Up @@ -5663,7 +5814,7 @@ const docTemplate = `{
"BearerAuth": []
}
],
"description": "Get an exact cost quote by sending the file bytes. antd runs self-encryption and queries the live network for chunk pricing — no estimation, no scaling. Returns a structured estimated_cost object with cost, chunk_count, gas, and payment_mode.",
"description": "Get an exact cost quote by sending the file bytes. antd runs self-encryption and queries the live network for chunk pricing — no estimation, no scaling. Returns a structured estimated_cost object with cost, chunk_count, gas, and payment_mode (antd's on-chain payment strategy: auto | merkle | single). With the hosted payment backend the gateway debits gross — batch total plus a per-batch network fee (V2-1098) — so the response additionally carries gateway_fee_per_batch_atto, estimated_batch_count, and estimated_total_with_fee_atto (V2-1113).",
"consumes": [
"multipart/form-data"
],
Expand Down Expand Up @@ -7264,6 +7415,29 @@ const docTemplate = `{
}
}
},
"internal_handlers.topupCheckoutRequest": {
"type": "object",
"properties": {
"amount_usd_cents": {
"type": "integer"
},
"cancel_url": {
"type": "string"
},
"success_url": {
"description": "Absolute URLs back into this instance's UI; validated by the gateway\n(http/https only). Stripe substitutes {CHECKOUT_SESSION_ID} in\nsuccess_url if the placeholder is present.",
"type": "string"
}
}
},
"internal_handlers.topupSyncRequest": {
"type": "object",
"properties": {
"session_id": {
"type": "string"
}
}
},
"internal_handlers.updateGroupRequest": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -7411,12 +7585,23 @@ const docTemplate = `{
"filename": {
"type": "string"
},
"gateway_fee_atto": {
"description": "GatewayFeeAtto itemizes the gateway's per-batch network fee out of the\ngross actual_cost (V2-1098); absent when no fee was charged.",
"type": "string"
},
"gateway_payment_key": {
"type": "string"
},
"last_quoted_cost": {
"type": "string"
},
"original_filename": {
"type": "string"
},
"payment_backend": {
"description": "Payment provenance (V2-1086): \"local\" (instance wallet) or \"hosted\"\n(gateway credits) + the gateway's batch key; absent when nothing was paid.",
"type": "string"
},
"processing_at": {
"type": "string"
},
Expand Down
Loading
Loading