This document maps every InvoiceError, ContractError, SettlementError, and TreasuryError variant (and other contract error codes) to its numeric value, the condition that triggers it, and the recommended remediation steps for integrators.
Cross-reference: see docs/api-reference.md for HTTP-level error shapes returned by the backend.
Cross-reference: see ARCHITECTURE.md § Invoice state machine for a diagram of every legal
InvoiceStatustransition and the function that triggers it — useful context for knowing whichInvalidStateTransition/NotPendingcases below are expected versus a real bug.
Defined in contracts/invoice/src/lib.rs. The COMEBACKHERE-contracts/contracts/invoice crate declares a related enum called ContractError (invoice) (see below) — variant names overlap but numeric codes are independent.
| Code | Name | Trigger condition | Remediation |
|---|---|---|---|
| 1 | Unauthorized |
Caller is not the merchant, admin, or payer for the operation. | Ensure the signing key matches the expected role. Merchants must sign create_invoice; the admin must sign mark_paid / release_escrow; the payer must sign request_refund. |
| 2 | ContractPaused |
A state-changing call was made while the contract is in a paused state. | Check contract status before submitting. Contact the admin to unpause the contract. Do not retry until the contract is unpaused. |
| 3 | InvalidAmount |
amount_usdc ≤ 0, or gross_usdc < amount_usdc. |
Verify that both amounts are positive and that gross_usdc ≥ amount_usdc. Amounts are denominated in USDC stroops (1 USDC = 10 000 000 stroops). |
| 4 | NotPending |
An operation that requires Pending status (e.g. mark_paid, cancel) was called on an invoice in another state. |
Fetch the current invoice status before acting. If the invoice has already been paid, expired, or cancelled, no further action is needed. |
| 5 | Expired |
Payment was attempted after the invoice's expires_at timestamp. |
Create a new invoice with a future expires_in_seconds. Do not attempt to pay an invoice that has already expired. |
| 6 | NotFound |
No invoice exists for the supplied ID. | Confirm the invoice ID with the merchant. IDs are sequential u64 values returned by create_invoice. |
| 7 | AlreadyInitialized |
initialize was called on a contract that is already set up. |
This is a deployment-time error. Remove the extra initialize call; the contract can only be initialised once. |
| 8 | ZeroDuration |
expires_in_seconds was 0 on invoice creation. |
Pass a positive duration. Typical values are 3 600 (1 hour) to 2 592 000 (30 days). |
| 9 | ExpiryOverflow |
ledger_timestamp + expires_in_seconds overflows u64. |
Reduce the expiry duration. Any duration that would place the expiry beyond year 2554 will overflow. |
| 10 | NotPaid |
request_refund or release_escrow was called on an invoice that is not in Paid status. |
Confirm the invoice status is Paid before requesting a refund or releasing escrow. |
| 12 | AmountPrecision |
Amount is below the minimum of 1 USDC (10 000 000 stroops). | Set amount_usdc ≥ 10 000 000. Fractional-USDC invoices are not supported. |
| 13 | DuplicateNonce |
A merchant nonce has already been used for a previous invoice. | Generate a fresh nonce for each invoice. Reusing a nonce is rejected to prevent replay attacks. |
Defined in COMEBACKHERE-contracts/contracts/invoice/src/lib.rs. Shares some variant names with InvoiceError above, but numeric codes and semantic triggers are independent — branch on the enum name when integrating.
| Code | Name | Trigger condition | Remediation |
|---|---|---|---|
| 1 | Unauthorized |
Caller is neither the merchant nor the customer for the operation. | Sign with the merchant key for cancellation flows or with the customer key for refund flows. The admin key is required for pause, unpause, set_treasury, and set_grace_window. |
| 2 | ContractPaused |
A state-changing call was made while the contract is in a paused state. | Check contract status before submitting. Contact the admin to unpause. Do not retry until the contract is unpaused. |
| 3 | AlreadyInitialized |
initialize was called on a contract that is already set up. |
Deployment-time error. Remove the extra initialize call; the contract can only be initialised once. |
| 4 | InvoiceNotFound |
get_invoice, cancel_invoiced, mark_paids, request_refund, release_escrow, or raise_dispute was called with an ID that does not exist. |
Confirm the invoice ID returned by create_invoice. |
| 5 | InvoiceAlreadyPaid |
mark_paids was called on an invoice that is no longer in Pending status. |
Inspect invoice status with get_invoice_status before marking paid. Already-paid invoices cannot transition again. |
| 6 | InvoiceExpired |
mark_paids was called after env.ledger().timestamp() >= invoice.expires_at. |
Pay invoices before the configured expiry. Use batch_expire to sweep stale invoices off the books. |
| 7 | InvoiceCancelled |
cancel_invoiced was called on a non-Pending invoice. |
Invoices can only be cancelled while Pending. Confirm status before cancelling. |
| 8 | NotMerchant |
release_escrow was called by a non-merchant caller. |
Sign with the merchant key associated with the invoice to release escrow. |
| 9 | NotCustomer |
request_refund was called by a non-customer caller. |
Sign with the customer key associated with the invoice to request a refund. |
| 10 | RefundNotRequested |
release_escrow was called before request_refund moved the invoice to RefundRequested. |
Call request_refund first, then wait for the grace window to elapse before release_escrow. |
| 11 | AlreadyRefundRequested |
request_refund was called on an invoice that is already in RefundRequested status. |
Each invoice may transition to RefundRequested only once. Inspect status before re-requesting. |
| 12 | GraceWindowNotExpired |
release_escrow was called before created_at + grace_window. |
Wait until ledger.timestamp() >= created_at + grace_window. Admin may reduce GraceWindow via set_grace_window (default 86 400 seconds). |
| 13 | DuplicateNonce |
The (merchant, nonce) pair has already been used by a previous invoice. | Generate a fresh nonce for each invoice. Different merchants may reuse the same nonce value without collision. |
| 14 | TreasuryNotConfigured |
raise_dispute was called before the admin ran set_treasury. |
Admin must call set_treasury once before disputes can be raised. |
| 15 | NotAParty |
raise_dispute was called by an address that is neither the invoice's merchant nor its customer. |
Sign with the merchant or customer key associated with the invoice. |
| 16 | Overflow |
An internal counter (invoice ID, or created_at + grace_window) would overflow u64. |
Practically unreachable outside of adversarial ledger state; not user-actionable. |
| 17 | AddressBlocked |
mark_paids was called for a customer that the configured compliance contract reports as not allowed. |
Confirm the customer's compliance status with ComplianceContract.is_allowed before retrying. |
| 18 | InvalidStateTransition |
mark_paids was called on an invoice in RefundRequested, Released, Cancelled, or Expired status — see ARCHITECTURE.md § Invoice state machine for the full legal-transition diagram. |
Fetch the current status with get_invoice_status first. A refund already in progress must not be overridden by a stale payment confirmation. |
Defined in COMEBACKHERE-contracts/contracts/compliance/src/lib.rs.
| Code | Name | Trigger condition | Remediation |
|---|---|---|---|
| 1 | Unauthorized |
Caller is not the admin configured in initialize. |
Sign with the admin key for state-changing calls (set_status, pause, unpause, admin rotation). |
| 2 | ContractPaused |
A state-changing call was made while the compliance contract is paused. | Compliance check calls return early on pause; defer the user action or have the admin unpause. |
| 3 | AlreadyInitialized |
initialize was called on a contract that is already set up. |
Deployment-time error. The compliance contract can only be initialised once. |
| 4 | AddressNotFound |
A status query (or block/unblock flow) referenced an address that has not been recorded in Status(Address). |
Register the address via set_status first, or use the Cleared default if no entry exists. |
| 5 | PastExpiry |
allow_address_until or batch_allow_addresses was called with until <= env.ledger().timestamp(). |
Pass a until timestamp strictly greater than the current ledger time. An already-expired entry is rejected rather than silently created as a no-op. |
| 6 | BatchTooLarge |
batch_allow_addresses was called with more than 50 addresses in a single invocation. |
Split the address list into batches of 50 or fewer and submit multiple batch_allow_addresses calls. |
Note: the enum in
COMEBACKHERE-contracts/contracts/compliance/src/lib.rsis namedContractError, matching the naming convention used by the invoice and treasury contracts in this repo. It is sometimes referred to informally asComplianceErrorin design discussions — they are the same type.
See Compliance events below for the event shapes emitted by batch_allow_addresses and the other compliance entry points.
Defined in COMEBACKHERE-contracts/contracts/compliance/src/lib.rs. Every state-changing compliance call emits one or more Soroban events so an off-chain indexer can reconstruct allowlist state without re-reading contract storage.
| Event topic | Emitted by | Data payload | Notes |
|---|---|---|---|
address_allowed |
allow_address |
Address |
Permanent allow, no expiry. |
address_allowed |
batch_allow_addresses |
(Address, u64) — address and its until timestamp |
One event per address processed. Same topic as allow_address, but the payload additionally carries the until value shared by the whole batch. |
address_allowed_until |
allow_address_until |
(Address, u64) — address and its until timestamp |
Single-address, time-bounded allow. |
address_blocked |
block_address |
Address |
|
address_cleared |
clear_address |
(Address, AddressStatus) — address and the status it held immediately before clearing |
Never emitted when the address was already Cleared (that call fails with AddressNotFound instead). |
compliance_batch_processed |
batch_allow_addresses |
(Address, u32) — the calling admin and the number of addresses processed |
Emitted once per batch_allow_addresses call, after all per-address address_allowed events for that call. Lets an indexer confirm a batch operation has fully landed (processed_count matches the number of address_allowed events it should have seen in that transaction) without treating event counting as the sole source of truth. |
batch_allow_addresses caps addresses at 50 entries per call (ContractError::BatchTooLarge above that) and validates until the same way allow_address_until does (ContractError::PastExpiry if until <= env.ledger().timestamp()). Both checks run before any storage writes or events, so a rejected call has no partial effects.
Defined in contracts/settlement/src/lib.rs.
| Code | Name | Trigger condition | Remediation |
|---|---|---|---|
| 1 | NotFound |
No settlement exists for the supplied ID. | Confirm the settlement ID returned by propose. |
| 2 | Unauthorized |
Caller has no registered weight in the treasury signer set. | Use a key that was registered via initialize or a subsequent signer-rotation call. |
| 3 | AlreadyApproved |
The same signer attempted to approve the same settlement twice. | Each signer may approve a settlement only once. |
| 4 | NotPending |
approve_settlement or cancel was called on a settlement that is not in Pending status. |
Check the settlement status before calling approve or cancel. |
| 5 | DuplicateSigner |
initialize was called with the same signer address appearing more than once in the signers list. |
Ensure every (address, weight) pair in the signers vector is unique before calling initialize. |
| 6 | InvalidWeightSum |
initialize was called with a threshold greater than the sum of all signer weights. |
Lower the threshold or add signers with sufficient weight so that sum(weights) ≥ threshold. |
Defined in COMEBACKHERE-contracts/contracts/treasury/src/lib.rs.
| Code | Name | Trigger condition | Remediation |
|---|---|---|---|
| 1 | ContractPaused |
A state-changing call was made while the treasury is in a paused state. | Defer transactions until the admin runs unpause. |
| 2 | NotPending |
approve_settlement or execute_settlement was called on a settlement that is not in Pending status. |
Confirm pending status with get_pending_settlements before approving or executing. |
| 3 | InsufficientApprovals |
execute_settlement was called before accumulated signer weight reached the configured threshold. |
Continue gathering approvals until approval_weight ≥ threshold, then call execute_settlement. |
| 4 | TokenNotAllowed |
propose_settlement was called with a token not present in the allowlist (when the allowlist is non-empty). |
Admin must call add_token_to_allowlist for the token before settlements may be proposed against it. |
| 5 | Unauthorized |
Caller is not registered as a signer (for propose_settlement/approve_settlement) or not the admin (for set_signer, pause, etc). |
Use a key registered via initialize or set_signer; admin-only operations require the admin key. |
| 6 | InvalidThreshold |
update_threshold was called with a threshold of 0. |
Pass a positive u32 threshold; the multi-sig cannot function with zero required weight. |
| 7 | DuplicateSigner |
initialize was called with the same signer address appearing more than once in the signers list. |
Ensure every (address, weight) pair in the signers vector is unique before calling initialize. |
| 8 | InvalidWeightSum |
initialize was called with a threshold greater than the sum of all signer weights. |
Lower the threshold or add signers with sufficient weight so that sum(weights) ≥ threshold. |
Backend endpoints return errors as JSON:
{
"error": "Human-readable message",
"code": 6
}code corresponds directly to the numeric values in the tables above. When code is null or absent the error originates from the RPC layer rather than the contract.
| HTTP status | Typical contract code(s) | Meaning |
|---|---|---|
| 400 | — | Invalid request body (validation failed before hitting the contract). |
| 403 | 1 (Unauthorized) |
Caller is not authorised for the operation. |
| 404 | 6 (NotFound), 4 (InvoiceNotFound/AddressNotFound), Settlement 1 |
Resource does not exist. |
| 422 | 3, 4, 5, 8, 9, 10, 12, 13 | Contract rejected the transaction. |
| 422 | Settlement 5 (DuplicateSigner), Settlement 6 (InvalidWeightSum) |
SettlementContract.initialize called with duplicate signers or total weight below threshold. |
| 422 | Treasury 7 (DuplicateSigner), Treasury 8 (InvalidWeightSum) |
TreasuryContract.initialize called with duplicate signers or total weight below threshold. |
| 503 | — | Backend misconfiguration (missing env vars). |
| 504 | — | Transaction confirmation timeout waiting for Soroban. |