Soroban smart contract powering the Sharpy split payment protocol on Stellar. Handles invoice creation, multi-recipient fund distribution, escrow management, recurring billing, and agentic payment integration.
| Network | Contract ID | Status |
|---|---|---|
| Testnet | CAEWQX36RLGP2WY6ACOREDJEIGELYV3HWWUPGV3CJMC27OWGQWZHTH6T |
Live (2026-09-04, 184-test build) |
| Mainnet | Coming soon | Pending |
Recent testnet transactions demonstrating all features:
- Create Invoice #3 — Basic invoice creation with XLM
- Create Batch Invoice — Multiple invoices in one call
- Multi-recipient Payment — Split payment to multiple addresses
- Escrow Invoice — Invoice with escrow protection
- Recurring Invoice — Subscription invoice creation
Test Account: GD4Q2BH6KISIHTZWV5CSUMZC7VUBQAAXPNVSCESTUGH5WEYALMOTRS63 (View on Explorer)
Contributor-oriented storage keys, state machines, and events: ARCHITECTURE.md.
graph TD
App["sharpy-app\nNext.js 14"]
SDK["@stellar-sharpy/sdk"]
RPC["Soroban RPC"]
Contract["Sharpy Contract\nSoroban · Protocol 27"]
Stellar["Stellar Network"]
App -->|"calls"| SDK
SDK -->|"simulate + submit"| RPC
RPC -->|"executes"| Contract
Contract -->|"ledger state + events"| Stellar
Stellar -->|"events"| SDK
- Multi-recipient invoices — split funds to any number of recipients in one transaction
- Split rules — Fixed, Percentage (validated ≤ 100%), Tiered (threshold-based)
- Multi-token support — one token per recipient (USDC, XLM, AQUA, yXLM)
- Recurring/subscription invoices — auto-generates next invoice on release
- Escrow protection — configurable release delay with optional arbitrator
- Escrow dispute mechanism — arbitrator can intervene before release
- Batch invoice creation — up to 10 invoices in a single transaction
- Pool payments — pay multiple invoices across different tokens in one call
- Structured events — for all lifecycle actions (created, payment, released, refunded, cancelled, escrow_funded)
- Invoice stats — funded/total/completion_bps/unique_payers via
get_invoice_stats - Full audit log — on-chain audit trail per invoice
- Admin circuit breaker — pause/unpause contract
- Payer index —
get_invoices_by_payertracks all invoices a payer touched (viapayandpool_pay) - Creator index —
get_invoices_by_creatorfor dashboard pagination - Fallback balance recovery —
claim()+get_claimable_balance()for failed recipient transfers (CEI pattern) - Storage TTL auto-extended — ~1 year on every write
- Treasury & tips —
get_treasury()+pay_with_tip()routes gratuity to treasury, excluded fromfunded - Freeze control —
freeze_invoice()/unfreeze_invoice()admin blocks/re-enablespay(frozen field) - Invoice notes —
set_invoice_notes()/get_invoice_notes()free-textInvoiceNotes { text, updated_at } - Invoice tags
- Archival —
archive_invoice/unarchive_invoice/is_archivedterminal invoice archiving - Streaming payments —
create_stream/withdraw_vested/cancel_stream/top_up_streamcliff-gated linear vestingStreamingState - Composable routing —
set_route/get_route/resolve_routepass-through hop to another invoiceComposableRoute(self-route and 2-cycle rejected) - Tranche release —
release_tranche/get_released_bpspartial release in basis pointsTrancheState(capped at 100%) - Whitelist gating —
set_whitelist/get_whitelist/add_whitelisted_payer/remove_whitelisted_payercreator-managed payer allowlist enforced inpay - Protocol fee —
set_protocol_fee/get_protocol_fee/preview_feeadmin-set bps cutFeeConfig(pure preview, no release-path change) - Approval flow —
set_approval_config/approve_invoice/get_approval_statemulti-sig prep - Invoice templates —
create_template/get_templatereusable configsInvoiceTemplate - Recurring pause —
pause_recurring/resume_recurring/is_recurring_paused - Discount config —
set/get_discountDiscountConfig { discount_bps, updated_at } - Invoice metadata —
set/get_invoice_metadataInvoiceMetadata { entries, updated_at } - Deadline extension —
extend_deadline(caller, id, new_deadline)creator can push deadline forward - Batch refund —
refund_batch(caller, ids)refund up to 10 deadline-passed invoices in one tx - Extra memo —
set_invoice_memo_ext()/get_invoice_memo_ext()creator memoInvoiceExtraMemo { memo, updated_at }(256 chars) —set_invoice_tags()/get_invoice_tags()categorizedInvoiceTags { tags, updated_at }(max 10, 32 chars each) - Recurring query —
get_recurring_params()exposes fullSubscriptionParams - Version query —
get_invoice_version()returns schema version (1)
| CAP | Protocol | Feature | Implementation |
|---|---|---|---|
| CAP-82 | 26 | Checked 256-bit arithmetic | Overflow-safe split calculations in _release() and get_invoice_stats() |
| CAP-78 | 26 | Limited TTL extension host functions | bump_invoice_ttl() — anyone can extend invoice storage lifetime |
| CAP-75 | 25 | Poseidon/crypto host functions | get_invoice_fingerprint() — SHA-256 tamper-evident content hash |
| Function | Description |
|---|---|
initialize(admin, treasury) |
Set admin and treasury addresses |
create_invoice(creator, recipients, amounts, tokens, deadline, options) |
Create invoice with split rules and escrow options |
create_batch(creator, invoices) |
Create up to 10 invoices in one transaction |
create_recurring(creator, recipients, amounts, token, deadline, interval, max) |
Create recurring invoice with auto-generation on release |
pay(payer, invoice_id, amount) |
Pay toward an invoice |
pool_pay(payer, payments) |
Pay multiple invoices in one call (multi-token) |
release_escrow(invoice_id) |
Release escrow-held funds after delay passes |
release(invoice_id) |
Manual release for fully funded invoice |
refund(invoice_id) |
Refund all payers after deadline passes |
cancel_invoice(caller, invoice_id) |
Creator cancels invoice and refunds payments |
dispute_release(invoice_id) |
Raise an escrow dispute before release |
resolve_dispute(invoice_id, release) |
Arbitrator resolves dispute — release or refund |
get_invoice(id) |
Read full invoice state |
get_invoice_stats(id) |
Get funded/total/completion_bps/payment_count/unique_payers |
get_invoice_fingerprint(id) |
SHA-256 tamper-evident content hash (Protocol 25/26) |
get_audit_log(id) |
Full audit trail as Vec |
get_payer_total(id, payer) |
Total amount paid by a specific address |
get_next_recurring(id) |
Next invoice ID in a recurring chain |
get_escrow_state(id) |
Current escrow/dispute state |
bump_invoice_ttl(id) |
Extend invoice storage TTL to prevent archival (Protocol 26 CAP-78) |
get_invoice_count() |
Total number of invoices ever created — O(1) global stat |
get_invoices_by_creator(creator) |
All invoice IDs created by an address |
get_invoices_by_payer(payer) |
All invoice IDs paid by a given address (payer index) |
get_claimable_balance(account, token) |
Claimable balance for account after failed transfer |
claim(account, token) |
Withdraw credited balance for account/token |
get_invoice_version(id) |
Invoice schema version (always 1) |
get_treasury() |
Treasury address set at initialize |
pay_with_tip(payer, id, amount, tip) |
Pay with gratuity routed to treasury (tip excluded from funded) |
freeze_invoice(id) / unfreeze_invoice(id) |
Admin freeze/unfreeze — blocks pay/pay_with_tip when frozen |
get_recurring_params(id) |
Full SubscriptionParams for recurring invoices (None if not recurring) |
set_invoice_notes(caller, id, text) / get_invoice_notes(id) |
Creator free-text notes InvoiceNotes { text, updated_at } |
set_invoice_tags(caller, id, tags) / get_invoice_tags(id) |
Creator tags InvoiceTags { tags, updated_at } (10 max) |
create_stream(id, recipient, amount, start, end, cliff) / withdraw_vested(id, recipient) / cancel_stream(id, recipient) / top_up_stream(id, recipient, additional) |
Cliff-gated linear vesting schedule per invoice |
set_route(caller, id, target) / get_route(id) / resolve_route(id) |
Pass-through hop to another invoice (one level) |
release_tranche(caller, id, bps) / get_released_bps(id) |
Partial release accounting, cumulative cap 10_000 bps |
set_whitelist(caller, id, payers) / get_whitelist(id) / add_whitelisted_payer / remove_whitelisted_payer |
Payer allowlist enforced by pay (absent = open) |
set_protocol_fee(bps, collector) / get_protocol_fee() / preview_fee(amount) |
Admin bps fee with pure preview query |
pause / unpause |
Admin circuit breaker |
| Type | Behaviour | Example |
|---|---|---|
Fixed(amount) |
Pay exact amount regardless of funded total | Fixed(500_000_000) → always 50 USDC |
Percentage(bps) |
Pay funded * bps / 10_000 (validated ≤ 100%) |
Percentage(6000) → 60% of funded |
Tiered(threshold, bps) |
Pay percentage only if funded > threshold, else 0 |
Tiered(100_000_000, 5000) → 50% if funded > 10 USDC |
sharpy-contracts/
├── Cargo.toml # Workspace (soroban-sdk 26.1.0)
├── Makefile # Build/test/deploy commands
├── CONTRIBUTING.md
├── SECURITY.md
├── CODE_OF_CONDUCT.md
├── CHANGELOG.md
├── contracts/sharpy/
│ ├── Cargo.toml
│ └── src/
│ ├── lib.rs # All contract logic (600+ lines)
│ ├── types.rs # Invoice, SplitRule, AuditEntry, etc.
│ ├── events.rs # Structured event helpers
│ └── test.rs # 184 unit tests (streaming, routing, tranche, whitelist, fee-module)
└── .github/
├── workflows/ci.yml # Test + WASM build on every PR
└── ISSUE_TEMPLATE/ # Bug report, feature request
make test # cargo test (184 passing)
make build # build WASM
make optimize # optimize WASM with stellar contract optimize
make deploy-testnet # deploy to testnet
make deploy-mainnet # deploy to mainnet| soroban-sdk | Protocol | Status |
|---|---|---|
| 26.1.0 | 27 | Current |
| Repo | Description |
|---|---|
| sharpy-sdk | TypeScript SDK |
| sharpy-app | Next.js frontend dApp |
See CONTRIBUTING.md for setup, standards, and commit conventions.
See SECURITY.md for the vulnerability disclosure process.