Skip to content

Latest commit

 

History

History
153 lines (116 loc) · 15.8 KB

File metadata and controls

153 lines (116 loc) · 15.8 KB

Error Codes

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 InvoiceStatus transition and the function that triggers it — useful context for knowing which InvalidStateTransition / NotPending cases below are expected versus a real bug.


InvoiceError

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.

ContractError (invoice)

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.

ContractError (compliance)

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.rs is named ContractError, matching the naming convention used by the invoice and treasury contracts in this repo. It is sometimes referred to informally as ComplianceError in 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.


Compliance events

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.


SettlementError

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.

TreasuryError

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.

Error shape in API responses

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.


Quick-reference: HTTP status mapping

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.