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
8 changes: 4 additions & 4 deletions docs/concepts/merchants.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,15 @@ All verified in `merchant.rs`. Every one below requires `merchant.require_auth()

| Function | Inputs | Validation | Resulting state | Event |
|---|---|---|---|---|
| `set_merchant_account` | `account: Address` | Merchant must already be registered (`MerchantNotFound` otherwise). No validation that `account` is a real `account`-contract instance. | `DataKey::MerchantAccount(merchant_id) = account` | None emitted (verified absent from this function's body). |
| `deploy_merchant_account` | — | Merchant must be registered and active; the account WASM hash must be set; one account per merchant (`MerchantAccountAlreadySet` otherwise). | Deploys the account with the hub as manager and sets `DataKey::MerchantAccount(merchant_id)` | `MerchantAccountDeployedEvent` |
| `set_merchant_key` | `key: BytesN<32>` | Merchant must be registered. | `DataKey::MerchantKey(merchant) = key` | `MerchantKeySetEvent { merchant, key, timestamp }`. |
| `set_merchant_webhook` | `webhook: String` | Merchant must be registered (reads the record via `get_merchant`, which panics `MerchantNotFound`). | `Merchant.webhook = webhook`, record re-stored. | `MerchantWebhookSetEvent { merchant, merchant_id, webhook, timestamp }`. |
| `set_merchant_accepted_tokens` | `tokens: Vec<Address>` | Merchant registered and **active** (`MerchantNotActive` otherwise); every token must already be globally accepted (`admin::is_accepted_token`, else `TokenNotAccepted`); list is deduplicated before storage. | `DataKey::MerchantTokens(merchant) = deduped` (full replace, not merge). | `MerchantTokensSetEvent { merchant, tokens, timestamp }`. |
| `remove_merchant_accepted_token` | `token: Address` | Merchant registered and active; token must currently be present in the merchant's list (`TokenNotAccepted` otherwise). | `DataKey::MerchantTokens(merchant)` rewritten without `token`. | `MerchantTokenRemovedEvent { merchant, token, timestamp }`. |

**Security implications, verified from the code above (not speculative):**

- `set_merchant_account` performs no validation on the supplied `account` address — a merchant could point it at any address, including one they don't control an `account`-contract deployment of. This affects where refunds are sourced from (`refund_invoice`/`refund_invoice_partial`/`claim_refund` all resolve the merchant's account via `get_merchant_account` and call `refund` on it via `MerchantAccountRefundClient`) and where auto-withdrawal sweeps funds to.
- The hub only links accounts it deployed itself, so the account is always a real `account` contract with the hub as manager. This matters for where refunds are sourced from (`refund_invoice`/`refund_invoice_partial`/`claim_refund` all resolve the merchant's account via `get_merchant_account` and call `refund` on it via `MerchantAccountRefundClient`) and where auto-withdrawal sweeps funds to.
- `set_merchant_key` sets the key used by `signature_util::verify_invoice_signature` for `create_invoice_signed` — a merchant fully controls this key with no admin oversight, since the setter only requires the merchant's own auth.

## Querying
Expand Down Expand Up @@ -116,7 +116,7 @@ Derived strictly from the enforcement call sites found in [Activation](#activati
| Backer-reward operations (`backer_rewards.rs`) | **Yes** (one call site) | No | — |
| `pay_invoice` / `pay_invoice_partial` / `pay_invoices_batch` | No (not checked) | No | Invoice status must be `Pending`/`PartiallyPaid`; token accepted; global pause. |
| `refund_invoice` / `refund_invoice_partial` / `void_invoice` / `amend_invoice` / `claim_refund` | No (not checked) | No | See [Refunds and voids](refunds-and-voids.md) for the full precondition set. |
| Every other query/config function not listed above (`set_merchant_account`, `set_merchant_key`, `set_merchant_webhook`, `get_merchant`, etc.) | No | No | Merchant must be registered where the function reads a merchant record. |
| Every other query/config function not listed above (`set_merchant_key`, `set_merchant_webhook`, `get_merchant`, etc.) | No | No | Merchant must be registered where the function reads a merchant record. |

**Cross-cutting dependencies flagged explicitly, since they belong to other guard layers rather than to the merchant state model itself:**

Expand All @@ -128,4 +128,4 @@ Derived strictly from the enforcement call sites found in [Activation](#activati

- [Storage model](../architecture/storage-model.md) — `DataKey::Merchant(u64)` and every other merchant-related `DataKey` variant.
- [Shade contract reference](../contracts/shade.md#merchants) — full list of merchant-related `ShadeTrait` functions with auth summaries.
- [Refunds and voids](refunds-and-voids.md) — how the merchant's `account` (set via `set_merchant_account`) is used as the funding source for every refund path.
- [Refunds and voids](refunds-and-voids.md) — how the merchant's `account` (created by `deploy_merchant_account`) is used as the funding source for every refund path.
4 changes: 2 additions & 2 deletions docs/concepts/payments.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This page documents how value actually moves when a [payer](../glossary.md#payer) settles an [invoice](../glossary.md#invoice): the checks that run before any token transfer, the order transfers happen in, and the guarantees a payer and [merchant](../glossary.md#merchant) get from `pay_invoice`, `pay_invoice_partial`, and `pay_invoices_batch`.

> **Implementation note:** the payment logic described here lives in [`contracts/shade/src/components/invoice.rs`](../../contracts/shade/src/components/invoice.rs) (the `pay_invoice*` family) and [`contracts/shade/src/components/platform_fee.rs`](../../contracts/shade/src/components/platform_fee.rs) (the fee split). `contracts/shade/src/components/payment.rs` is a separate, smaller component that only validates swap-routing payloads (`validate_payment_payload`) — see [Payment payloads, swap routing, and the cross-chain bridge placeholder](payment-payloads-and-routing.md). It is not where invoice settlement happens.
> **Implementation note:** the payment logic described here lives in [`contracts/shade/src/components/invoice.rs`](../../contracts/shade/src/components/invoice.rs) (the `pay_invoice*` family) and [`contracts/shade/src/components/platform_fee.rs`](../../contracts/shade/src/components/platform_fee.rs) (the fee split). `contracts/shade/src/components/payment.rs` is a separate, smaller component that only validates swap-routing payloads (`validate_payment_payload`) — see [Payment payloads and swap routing](payment-payloads-and-routing.md). It is not where invoice settlement happens.

## Why it exists

Expand Down Expand Up @@ -142,6 +142,6 @@ sequenceDiagram
- [Auto-withdrawal and merchant settlement](auto-withdrawal.md)
- [Refunds and voids](refunds-and-voids.md)
- [Amounts, token decimals, and rounding](amounts-and-rounding.md)
- [Payment payloads, swap routing, and the cross-chain bridge placeholder](payment-payloads-and-routing.md)
- [Payment payloads and swap routing](payment-payloads-and-routing.md)

← [Back to concepts](README.md)
10 changes: 5 additions & 5 deletions docs/getting-started/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Being explicit about what Shade does *not* do is as useful as describing what it
| **Issuing tokens** | Shade settles in tokens issued elsewhere. The [admin](../glossary.md#admin) whitelists which ones are acceptable; the protocol mints nothing. |
| **Being a wallet** | Shade holds no user keys and offers no key management. Payers and merchants bring their own Stellar accounts. |
| **General-purpose identity or full KYC** | The protocol records a verification flag and a KYC request lifecycle that reviewers act on. It does not collect, store, or validate identity documents on chain — that work happens off chain, and only the resulting status is recorded. |
| **Reversing settled payments unilaterally** | There is no chargeback. Refunds are explicit contract calls made by the party holding the funds; escrow exists for deals that need a hold before release. |
| **Reversing settled payments unilaterally** | There is no chargeback. Refunds are explicit contract calls made by the party holding the funds. |
| **Off-chain infrastructure** | Dashboards, SDKs, and notification services live outside this repository. Shade emits events; those systems consume them. |

## The actors
Expand All @@ -45,19 +45,19 @@ Four roles appear throughout the protocol and the rest of these docs.

The single address stored under `DataKey::Admin`, set once by `initialize` and thereafter handed over only through a two-step propose/accept transfer. The admin governs the protocol, not any individual payment.

The admin can whitelist and remove [accepted tokens](../glossary.md#accepted-token), set and time-lock protocol fees per token, designate the platform fee account, configure price oracles, activate and verify merchants, restrict a merchant account, pause and unpause the whole contract, register governance council members, set the account contract's WASM hash, and upgrade the contract's code.
The admin can whitelist and remove [accepted tokens](../glossary.md#accepted-token), set and time-lock protocol fees per token, designate the platform fee account, configure price oracles, activate and verify merchants, restrict a merchant account, pause and unpause the whole contract, set the account contract's WASM hash, and upgrade the contract's code.

The admin **cannot** move a merchant's funds. Balances live in each merchant's own account contract, and only the merchant authorizes a withdrawal from it.

### Merchant — the business getting paid

Any address that calls `register_merchant`. Registration is permissionless: it self-authorizes, and the new merchant record starts `active: true` and `verified: false`. Verification is a separate admin action that gates the features requiring it.

A merchant deploys its settlement account (`deploy_merchant_account`), creates and manages [invoices](../glossary.md#invoice), publishes [subscription plans](../glossary.md#subscription-plan), issues event tickets, opens escrows and campaigns, narrows which tokens it will accept, and withdraws its balance from its account contract.
A merchant deploys its settlement account (`deploy_merchant_account`), creates and manages [invoices](../glossary.md#invoice), narrows which tokens it will accept, and withdraws its balance from its account contract.

### Payer — the customer

Any address paying a merchant. Payers do not register and hold no protocol role; the only requirement is authorizing the payment. A payer settles an invoice in full (`pay_invoice`), in part (`pay_invoice_partial`), or several at once (`pay_invoices_batch`), subscribes to a plan and authorizes recurring billing, buys tickets, funds escrow, and pledges to campaigns.
Any address paying a merchant. Payers do not register and hold no protocol role; the only requirement is authorizing the payment. A payer settles an invoice in full (`pay_invoice`), in part (`pay_invoice_partial`), or several at once (`pay_invoices_batch`).

### Platform — the fee recipient

Expand Down Expand Up @@ -121,7 +121,7 @@ Two crates live under [`contracts/`](../../contracts): `shade` is the hub, and `

| Crate | Role | Reference |
|---|---|---|
| [`shade`](../../contracts/shade) | The hub contract. Owns admin config, accepted tokens, fees, merchants, invoices, payment routing, and the on-hub subscription, escrow, ticketing, campaign, governance, and upgrade components. | [Contracts](../contracts/README.md) — *page planned* |
| [`shade`](../../contracts/shade) | The hub contract. Owns admin config, accepted tokens, fees, merchants, invoices, payment routing, refunds, analytics, and upgrades. | [Shade](../contracts/shade.md) |
| [`account`](../../contracts/account) | The per-merchant settlement account. Holds token balances, enforces withdrawal thresholds and multi-approval, and carries the verified and restricted flags. Deployed per merchant from a WASM hash the admin registers. | [Contracts](../contracts/README.md) — *page planned* |

## Next steps
Expand Down
2 changes: 1 addition & 1 deletion docs/operations/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ Copy the changelog section for the version and add the build record. Use this te

- Commit: `<full SHA of the tag>`
- Toolchain: Rust `<rustc --version>`, Stellar CLI `<stellar --version>`, soroban-sdk `<version from Cargo.lock>`
- Build command: see [Releases runbook](docs/operations/releases.md#5-build-reproducible-wasms)
- Build command: see [Releases runbook](#5-build-reproducible-wasms)
- Independently reproduced by: `<name or CI run>`

### Upgrading
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Lookup material: terminology, error codes, and storage layout.
- [Data types reference](data-types.md) — all structs, enums, and their fields.
- [Errors reference](errors.md) — every error enum in the workspace, its codes, triggers, and fixes.
- [Resource costs and Soroban limits](../../example/docs/reference/resource-costs.md) — measured CPU, memory, ledger and event costs, and safe batch sizes.
- Storage key layout across `DataKey`, `EventKey`, `CampaignKey`, and related enums — *planned*.
- [Storage model](../architecture/storage-model.md) — every `DataKey` variant.
- Events reference (`contracts/shade/src/events.rs`) — *planned*.

← [Back to documentation home](../README.md)
3 changes: 1 addition & 2 deletions docs/reference/shade-interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,11 @@ Complete API reference for the `shade` contract's public interface — every fun
| [`revoke_role`](#revoke_role) | Access control |
| [`search_invoices_paginated`](#search_invoices_paginated) | Search |
| [`search_merchants_paginated`](#search_merchants_paginated) | Search |
| [`set_accepted_tokens`](#set_accepted_tokens) | Admin |
| [`set_account_wasm_hash`](#set_account_wasm_hash) | Admin |
| [`set_auto_withdrawal_recipient`](#set_auto_withdrawal_recipient) | Merchant |
| [`deploy_merchant_account`](#deploy_merchant_account) | Merchant |
| [`set_auto_withdrawal_threshold`](#set_auto_withdrawal_threshold) | Merchant |
| [`set_fee`](#set_fee) | Fee |
| [`set_merchant_account`](#set_merchant_account) | Merchant |
| [`set_merchant_accepted_tokens`](#set_merchant_accepted_tokens) | Merchant |
| [`set_merchant_key`](#set_merchant_key) | Security |
| [`set_merchant_platform_fee`](#set_merchant_platform_fee) | Fee |
Expand Down
Loading