How a deal works¶
Same deal, two views: what you do when you meet, and what Starknet does with the payment.
At the meetup¶
You found a listing nearby. You agree on a price in USDC. You do not want the seller opening an explorer and seeing the rest of your wallet.
What you do¶

1. Lists the item + QR
2. Hands over the item
3. Cashes out

1. Meet / agree price
2. Pay the listing
3. Take the item
- List. The seller creates the listing on their phone. The app shows a claim secret once, like a backup phrase. The listing carries the price (USDC or STRK) and a hash of that secret. No transaction yet.
- Pay. On Account the buyer shields the listing token and enough STRK for pool fees (Show balance asks the wallet for both in one consent; an empty token shows 0). Then they open the listing (QR or link) and pay from shielded funds. Funds lock in the GhostDeal escrow. The seller sees that it is paid, not who paid from which notes.
- Meet. The item changes hands in person.
- Cash out. The seller claims with the secret kept on their phone (or pasted from backup). The price lands as a private note. If the deal falls through, the buyer cancels with the refund secret saved at pay time and the listing can reopen.
What the chain does¶
sequenceDiagram
participant Seller
participant Buyer
participant App as GhostDeal
participant Wallet as Wallet
participant Pool as STRK20 pool
participant Escrow as GhostDeal helper
Note over Seller,App: Publish (off-chain)
Seller->>App: create listing
App-->>Seller: claimSecret shown once
Note over App: listing = price + claimHash
Note over Buyer,Pool: Shield (public by design)
Buyer->>App: Shield
App->>Wallet: strk20InvokeTransaction
Wallet->>Pool: deposit, pool fee deducted
Note over Buyer,Escrow: Pay (private)
Buyer->>App: Pay
App->>Wallet: strk20InvokeTransaction
Wallet->>Pool: withdraw price + invoke Deposit
Pool->>Escrow: privacy_invoke(Deposit)
Note over Escrow: locks price on claimHash, payer hidden
Note over Seller,Buyer: In person: item changes hands
alt Seller cash out
Seller->>App: Cash out
App->>Wallet: strk20InvokeTransaction
Wallet->>Pool: transfer OPEN (whole open note) + invoke Claim
Pool->>Escrow: privacy_invoke(Claim)
Escrow-->>Pool: OpenNoteDeposit
Note over Pool: credits seller open note, receiver hidden
else Buyer cancel
Buyer->>App: Cancel
App->>Wallet: strk20InvokeTransaction
Wallet->>Pool: transfer OPEN (whole open note) + invoke Cancel
Pool->>Escrow: privacy_invoke(Cancel)
Escrow-->>Pool: OpenNoteDeposit
Note over Pool: credits buyer open note, receiver hidden
end
Shield is public on purpose. The chain sees that someone deposited an amount. After that, Pay and cash-out run as private pool transactions. Observers see the pool move the price into escrow, not which notes were spent or who paid.
Deal states¶
%%{init: {'flowchart': {'curve': 'stepAfter'}} }%%
flowchart TD
L["1. Listed (Open)"] -->|Buyer pays| E["2. Locked (In Escrow)"]
E -->|Seller cash out with claimSecret| C["3a. Claimed (Seller paid)"]
E -->|Buyer cancel with refundSecret| X["3b. Cancelled (Buyer refunded)"]
On chain, a commitment is either open or closed. Claim and cancel both close it.
Who touches what¶
| Action | Escrow function | On-chain visibility |
|---|---|---|
| Publish | none | none |
| Shield | none | public deposit to the pool |
| Pay | privacy_invoke op Deposit |
pool to escrow transfer, amount public, payer hidden |
| Cash out | privacy_invoke op Claim |
open-note amount public, receiver hidden |
| Cancel | privacy_invoke op Cancel |
same shape as claim |
| Any read | get_commitment(claimHash) |
funded / closed, no identities |