Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

service_market package

Overview

Realm service_market is a custodial GNOT marketplace for SERVICES: providers publish a standing offer (title, description, price, delivery window), a customer purchases it by paying the exact price into escrow, the provider marks the work delivered, and the escrow is released to the provider only once the customer accepts — or once the acceptance window lapses.

COMPOSITION (per the recorded DISCOVERY / REUSE ANALYSIS in DISCOVERY.md): balance accounting is feeledger, coin movement is coinio, free-text render safety is p/nt/markdown/sanitize/v0. This realm owns only the two-level offer/order state machine, which the discovery gate found nowhere else: every escrow realm in the searched sources escrows FUNDER-FIRST (the party who will later decide escrows first, then solicits work), while a service marketplace must escrow CUSTOMER-FIRST against a standing offer.

LIFECYCLE. A service is a reusable offer; an order is one purchase of it. Order terminal states are frozen and reached exactly once.

Example
 1RegisterService (anyone, no coins) : status Active. The fee bps is
 2                                     SNAPSHOTTED under the
 3                                     provider's own maxFeeBps
 4                                     ceiling.
 5RetireService (provider only)      : Active -> Retired. Blocks NEW
 6                                     orders only; orders already in
 7                                     flight are untouched.
 8PurchaseService (EOA + -send)      : order Purchased, amount into
 9                                     escrow. Sets the delivery
10                                     deadline.
11MarkDelivered (provider only)      : Purchased -> Delivered. Starts
12                                     the acceptance window.
13AcceptDelivery (customer only)     : Delivered -> Released. THE
14                                     RESOLUTION. Escrow becomes the
15                                     provider's claimable balance
16                                     minus the snapshotted fee.
17ReleaseTimeout (anyone)            : Delivered -> Released after
18                                     AcceptanceBlocks. Deemed
19                                     acceptance.
20DeclineOrder (provider only)       : Purchased -> Refunded, fee-free.
21ClaimRefund (anyone)               : Purchased -> Refunded after the
22                                     delivery deadline, fee-free.
23Claim / ClaimAll (anyone)          : pays out the caller's own
24                                     claimable balance.

EVERY VALUE EXIT IS A PULL. Release and refund only move numbers between the escrow pool and a claimable balance; the beneficiary later calls Claim. Nothing in this realm ever pushes coins to a third party, which is what lets both timeout valves stay permissionless: a stranger triggering ReleaseTimeout or ClaimRefund performs no send, so a beneficiary that cannot receive a send can never brick the valve.

THE THREE REQUIRED PROTECTIONS.

Unauthorized settlement: every identity derives from the crossing entrypoint's cur.Previous().Address(); no function takes a caller identity as a parameter (the designation-forgery shape that disqualified most of the escrow realms found in discovery). The payee is COPIED INTO THE ORDER at purchase, so neither retiring the service nor any later edit can redirect an in-flight order's payment. There is no admin path to any order's escrow: the admin sets the fee for FUTURE registrations and nothing else.

Double payment: an order's status is checked and driven terminal in the same transaction that moves its value, and escrowTotal is decremented in lockstep with the credit. Release and refund both require a non-terminal status, so at most one of them can ever succeed for a given order, and neither can succeed twice.

Stuck funds: both counterparties have a permissionless valve against the other's inaction. A provider who never delivers loses the escrow back to the customer at the delivery deadline (ClaimRefund); a customer who never accepts loses it to the provider at the acceptance deadline (ReleaseTimeout). Neither valve trusts its caller.

ACCEPTANCE IS A PROTOCOL CONSTANT, NOT A PROVIDER SETTING. The provider chooses the delivery window (their own commitment, and their own risk), but AcceptanceBlocks is fixed. Were it provider-chosen, a provider would set it to zero, mark work delivered, and auto-release in the same block — settlement without resolution, wearing the costume of a timeout.

MONETARY INVARIANT (conservation). With H = ugnot held at the realm address, E = escrowTotal (Σ amount over Purchased and Delivered orders), U = claimable balances, F = the fee pot, S >= 0 out-of-band surplus:

Example
1H == E + U + F + S

PurchaseService raises H and E by exactly the price. Release moves amount from E to U+F (feeledger guarantees credited + fee == amount); refund moves amount from E to U at zero fee. Claim*/WithdrawFees debit the ledger before coinio.Payout moves the identical amount out. Any panic aborts the whole transaction; this realm never issues or removes coins. Surplus is recoverable only via SweepDenom (fee recipient), which reserves Liabilities() = E + U + F.

LIMITATION — NO DISPUTE ARBITRATION, DELIBERATELY. This realm resolves on acceptance or on the acceptance timeout. It does NOT adjudicate whether delivered work was good. A customer who considers the work inadequate has no lever here beyond declining to accept, and the timeout will still pay the provider. That is a real limitation and it is the deliberate price of the property above: an arbiter empowered to redirect escrow is a party who can seize funds, and the closest realm found in discovery (r/samcrew/escrow_v3) carries exactly that shape — a single hardcoded admin key, with no rotation function, that is simultaneously sole arbiter, a unilateral release path, and a permanent pause switch over every fund path. Adding arbitration here would change what this application IS and expand its security model, so it is refused rather than smuggled in. A deployment that needs adjudicated disputes needs that designed, audited and authorized as its own application.

REALM-CALLER CAVEAT: assertNoSend reads the ORIGIN transaction's send envelope, not this realm's receipt, so a realm caller is refused by every non-payable function whenever the SAME transaction attached a -send anywhere — even though this realm received nothing. It fails closed, it is not third-party triggerable, and the workaround is to isolate the call in its own -send-free transaction.

Named concretely, because the generic phrasing understates where it lands. It applies to every exported function except PurchaseService, the only payable one, but four of them carry the weight: MarkDelivered, without which no order against a realm provider can ever reach a release; ReleaseTimeout and ClaimRefund, the two valves this design deliberately leaves permissionless; and Claim / ClaimAll, the only paths that extract a credited balance. A realm-based keeper bot that batches a valve call into a transaction carrying coins for some other purpose is therefore silently unusable, and a realm provider must budget a dedicated transaction both to deliver and to collect. Requirement 3 (no stuck funds) survives this: both valves are permissionless and any EOA can pull them, so no order depends on a realm caller to unstick.

Function

RegisterService

func RegisterService(cur realm, title, description string, price, deliveryBlocks, maxFeeBps int64) int64

RegisterService publishes a standing offer and returns its id. No coins are accepted; the storage deposit the caller pays is the anti-spam. The current protocol fee is snapshotted into the service and must not exceed maxFeeBps, the ceiling the provider signed for — pass MaxFeeBps to accept any legal fee.

Providers may be EOAs or realms, but a realm provider takes on three obligations that this realm cannot check for it. Realm bytes are immutable after addpkg, so a realm deployed without them cannot acquire them later: failing (1) means it never earns, failing (2) or (3) means what it earned is permanently stranded.

  1. It must already expose a crossing entrypoint that calls MarkDelivered. That is the only transition to OrderDelivered, and both release paths require it, so a provider realm that cannot call it never earns a credit at all — every order against it runs to ClaimRefund instead. This obligation binds before the next one.
  2. It must already expose a crossing entrypoint that calls Claim or ClaimAll on this realm. A credited balance is a pull, never a push; nothing here can reach into a provider realm to deliver it.
  3. It must make both calls in transactions whose ORIGIN carried no -send, per the REALM-CALLER CAVEAT in the package header.

The customer side is not symmetric: PurchaseService is payable and routes through coinio.Receive, which requires an EOA calling directly via MsgCall. A realm can therefore sell a service here but cannot buy one — and "can sell" is exactly as strong as the three obligations above, no stronger. A customer is structurally always an EOA, which is why the permissionless valves are always pullable.

Params

Command

# WARNING: This command is running in an INSECURE mode.
# It is strongly recommended to use a hardware device for signing
# and avoid trusting any computer connected to the internet,
# as your private keys could be exposed.

gnokey maketx call -pkgpath "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/service_market" -func "RegisterService" -args $'' -args $'' -args $'' -args $'' -args $'' -gas-fee 1000000ugnot -gas-wanted 1_000_000_000 -send "" -chainid "pearl-1" -remote "https://rpc.pearl.testnets.gno.land" ADDRESSgnokey query -remote "https://rpc.pearl.testnets.gno.land" auth/accounts/ADDRESS
gnokey maketx call -pkgpath "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/service_market" -func "RegisterService" -args $'' -args $'' -args $'' -args $'' -args $'' -gas-fee 1000000ugnot -gas-wanted 1_000_000_000 -send "" -broadcast=false ADDRESS > call.tx
gnokey sign -tx-path call.tx -chainid "pearl-1" -account-number ACCOUNTNUMBER -account-sequence SEQUENCENUMBER ADDRESS
gnokey broadcast -remote "https://rpc.pearl.testnets.gno.land" call.tx