Skip to content

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

Seller showing a listing QR on their phone

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

Buyer paying from their phone

Buyer
1. Meet / agree price
2. Pay the listing
3. Take the item
  1. 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.
  2. 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.
  3. Meet. The item changes hands in person.
  4. 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