Skip to main content
A payable contract is a contract that accepts money attached to a call and pays it out on conditions written in its code: payment on delivery, milestones, revenue splits. The money is the same prepaid US dollars as every paid call. The operator keeps each contract’s balance in its ledger, and payees are paid out through Stripe Connect. A contract only decides where money already on the network goes, among the organizations whoever sent it named.
A node can receive contract money only up to a limit Stellarium sets for it, which starts at 0 for every node. Until Stellarium raises your node’s limit, calls that would send money to contracts it hosts are refused.

Where money can go

A contract’s balance, and money attached to a call, can only go to: A contract can’t pay a public key, an organization its funder didn’t name, or anyone outside Stripe Connect. Payable contracts are for conditional payouts that resolve within the time limit. They aren’t for transfers between users, lending, interest or tokens.

Writing a payable contract

Put payable before contract. It needs RequestScript 1.8.0 or later:
Tip.req
A payable contract reads three built-in variables that other contracts don’t:
  • payment: the money attached to this call, who it came from and who it may be paid to.
  • sender: who made this call. Authorize money decisions on sender, not caller.
  • now: the run’s time, stamped by the operator.
It spends its balance through sh.stellarium.money.Account, and draws random values for money decisions with sh.stellarium.random.Random. Both are built into every node. When you deploy it with POST /v1/contracts:
  • The node registers it with the operator first. The operator keeps its source, owner, RequestScript version and dependencies, which buyers read before they fund it. If the operator refuses the registration, the deploy is refused. A node without OPERATOR_URL can’t host payable contracts.
  • Its source is public. Anyone can read a registered version from the operator, so never put secrets in it.
  • Every contract it calls is pinned to a version, as in const job: com.example.Job@3, and the node must know which node hosts each one. See Versions and ownership.
  • It stays payable. A contract keeps the same payability across versions. An event contract can’t be payable, since nobody calls it.

Attaching money to a call

Attach money as call options, in braces between run and its arguments:
Hire.req
  • Calls that attach money name a version, as in com.example.Job@1, in request scripts too. You fund the code you read, not whatever its owner deploys next.
  • Attaching money to a contract that isn’t payable fails the call before the contract runs, and the request script fails.
  • The tokens a call runs are paid separately, as for any contract call. They never come out of the attached amount.

Where the money comes from

Money a request script attaches comes from top-up credit charged in the last 7 days, oldest first. Each organization can also attach only so much in a 30-day window, a limit that rises with its payment history. Above a monthly amount, the organization’s identity has to be verified first: contact support.

Payees

Whoever attaches money decides who it can be paid to:
  • pay reaches only the payees of the funding it spends. Paying anyone else fails the run.
  • A contract passing money on to another contract can narrow its payees, but never add one.
  • Refunds ignore payees. Money can always go back to whoever sent it.
So even a buggy contract can only pay your money to the organizations you named, or give it back.

Example: payment on delivery

A buyer funds a job. The provider is paid when the buyer or a third-party arbiter confirms delivery. The buyer gets the money back if the provider or the arbiter cancels:
Job.req
The buyer funds it with the Hire script above.
  • The contract chooses job ids, so nobody can claim one before the buyer funds it.
  • Each job’s money is earmarked with its id. The operator’s funding records are the job’s balance, so the contract keeps no balance of its own, and a release can only pay out its own job’s money.
  • Each job is its own state scope, so jobs don’t wait for each other (see When money moves).
  • It reads fundingOf instead of trusting its own records, because the time limit can return money behind its back.
  • fail "reason" fails the run on purpose. A failed fund returns the attached money to the buyer and saves no state.
  • Authorization uses sender. A contract the buyer called on its way here can’t release the job in the buyer’s name.

Example: splitting a payment

Split.req
The buyer names both organizations as payees: split.run{amount: 1000000, payees: ["org_author", "org_data"]}().
  • Both payments go through together, or neither does.
  • Integer division truncates, so org_data gets the remainder and nothing is lost to rounding. int64 arithmetic that overflows fails the run.
  • One payee can block the other. If org_data can no longer receive payouts, every call fails. Use tryPay for each payee and refund what it couldn’t pay to keep paying the author.

When money moves

Nothing moves while a contract runs. Each change is staged with the operator, and every change in the run applies together only when the run that started everything succeeds.
1

The run stages its changes

Each attached amount, pay and refund is checked against the funding it names and its payees, counting everything already staged in the same run. A change that doesn’t fit fails the call, and a failed Account call fails the run even if the script ignores the error.
2

The host sends receipts

The node records what each contract run did: its inputs, every call and its answer, and the state it read and wrote. Providers running current versions of stellarium-ts sign their answers.
3

The operator replays the runs

It runs each contract’s registered source again on those inputs. A host that staged a change its contract never asked for, or changed a provider’s answer, is caught, and its changes are dropped. Any other disagreement waits for staff review before money moves.
4

Money and state commit together

Every change in the run applies in one ledger transaction, and each payable contract’s state is saved when it does. A run that fails, is released or expires saves neither.
5

Payees are paid out

A payment, less the platform fee, joins the payee’s payable balance and is paid out through Stripe Connect after the payout hold.
  • State waits for the money. Until a run’s money settles, the state scopes it wrote stay locked, and other calls that write them wait. Keep state in narrow scopes, one per job or order, so unrelated calls don’t wait for each other.
  • Re-entering is safe but fails. A call that re-enters a contract already in its chain and writes the same scope fails the run. Finish your own writes to a scope before you call out with money attached.
  • Contracts that call providers that don’t sign their answers, or the hosting node’s own resources, are flagged to buyers.

Protections for funders

Time limit

Every payment into a contract must be paid out or refunded within 14 days. The operator returns anything older to whoever sent it, automatically. Passing money between contracts doesn’t reset the clock, and outflows spend the oldest money first. Design contracts that wait for something, such as a delivery, to resolve well within the limit, and split longer arrangements into milestones.

Reading a contract before you fund it

The operator, not the hosting node, publishes each registered version: its source, owner, RequestScript version, dependencies, balance and flags. Look one up on the Platform’s Contracts page, or read it from the operator:

Versions keep their own money

Money belongs to the version that received it. A new version starts with no balance, and an old version stays callable as com.example.Job@1 until it’s empty. There’s no way to move money from one version to another: funders fund the new version themselves, after reading it.

Refund all

A contract’s owner can return all of a version’s money to the organizations that funded it, from the version’s page on the Platform’s Contracts page. The version then refuses money, though it can still be called without any. Use it as the emergency stop for a version with a bug.

Notifications

When the operator moves your funding on its own, for example when the time limit or a refund-all returns it, it emails your organization’s owners. To get each notification as a webhook too, set a URL on the Platform’s Organization page. The operator posts the notification as JSON, retrying for a day:
The Stellarium-Signature header is the operator’s Ed25519 signature of the request body, base64url, and Stellarium-Key is its public key, SPKI DER in base64url. Check the key against the one you expect before you trust a signature.