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
Putpayable before contract. It needs RequestScript 1.8.0 or later:
Tip.req
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 onsender, notcaller.now: the run’s time, stamped by the operator.
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_URLcan’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 betweenrun 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:payreaches 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.
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
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
fundingOfinstead of trusting its own records, because the time limit can return money behind its back. fail "reason"fails the run on purpose. A failedfundreturns 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
split.run{amount: 1000000, payees: ["org_author", "org_data"]}().
- Both payments go through together, or neither does.
- Integer division truncates, so
org_datagets the remainder and nothing is lost to rounding.int64arithmetic that overflows fails the run. - One payee can block the other. If
org_datacan no longer receive payouts, every call fails. UsetryPayfor 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 ascom.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: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.