> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stellarium.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Payable contracts: conditional payouts in RequestScript

> Write payable contracts that accept money attached to a call and pay it out to payees the funder named, for payment on delivery, milestones and revenue splits.

A **payable contract** is a [contract](https://docs.stellarium.sh/concepts/contracts.md) 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](https://docs.stellarium.sh/concepts/payments.md). 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.

> [!NOTE]
>
> 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:

| Destination | How |
| - | - |
| A payee the funder named | [`pay` or `tryPay`](https://docs.stellarium.sh/builtins/account), less the 1% platform fee. The payee must have finished [payout setup](https://docs.stellarium.sh/concepts/payments.md#getting-paid) |
| Back to whoever sent it | [`refund`](https://docs.stellarium.sh/builtins/account#refund), with no fee |
| Another payable contract | A call with money attached, keeping the same payees or fewer |

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](#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:

```kotlin Tip.req theme={null}
payable contract com.example.Tip(note: string): int64 {
    return payment.amount
}
```

A payable contract reads three [built-in variables](https://docs.stellarium.sh/concepts/built-in-variables.md) that other contracts don't:

* [`payment`](https://docs.stellarium.sh/concepts/built-in-variables#payment): the money attached to this call, who it came from and who it may be paid to.
* [`sender`](https://docs.stellarium.sh/concepts/built-in-variables#sender): who made this call. Authorize money decisions on `sender`, not `caller`.
* [`now`](https://docs.stellarium.sh/concepts/built-in-variables#now): the run's time, stamped by the operator.

It spends its balance through [`sh.stellarium.money.Account`](https://docs.stellarium.sh/builtins/account), and draws random values for money decisions with [`sh.stellarium.random.Random`](https://docs.stellarium.sh/builtins/random). Both are built into every node.

When you deploy it with [`POST /v1/contracts`](https://docs.stellarium.sh/node/http-api#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](https://docs.stellarium.sh/concepts/contracts.md#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:

```kotlin Hire.req theme={null}
request Hire {
    const job: com.example.Job@1
    return job.run{amount: 20000000, payees: ["org_courier"]}(action: "fund", jobId: "", provider: "org_courier", arbiter: "org_arbiter").value
}
```

| Option | Purpose |
| - | - |
| `amount` | The micro-USD to attach, an `int64` (`20000000` is \$20). `0` attaches nothing |
| `payees` | The organizations the money may be paid to, 1 to 16 of them. Required in a request script |
| `from` | In a contract: whose funding to spend. Required in contracts |
| `key` | In a contract: which of that funder's earmarked money to spend. Optional |

* **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](https://docs.stellarium.sh/concepts/contracts.md#how-a-call-is-paid). They never come out of the attached amount.

### Where the money comes from

| Who attaches it | Paid from | Limit |
| - | - | - |
| A request script run with a [run token](https://docs.stellarium.sh/concepts/api-clients.md) | The API client's organization | The client's **maximum run value**, `0` until an admin raises it on the [Platform](https://platform.stellarium.sh)'s **Clients** page. Attached money counts toward the client's monthly cap |
| A request script run with the node's `API_KEY` | The node's organization | Per-run and monthly limits Stellarium sets for the node, `0` by default |
| A contract | Its own balance, from the funding it names with `from` | What that funding has left |

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:

```kotlin Job.req theme={null}
payable contract com.example.Job(action: string, jobId: string, provider: string, arbiter: string): string {
    const account: sh.stellarium.money.Account
    const uuid: sh.stellarium.stdlib.Uuid

    var id: string = jobId
    if (action == "fund") {
        const created = uuid.new()
        if (created.error != null) {
            fail "couldn't create a job id"
        }
        id = created.value
    }
    var state(id) payer: string = null
    var state(id) payee: string = null
    var state(id) judge: string = null

    if (action == "fund") {
        if (payment.amount == 0) {
            fail "fund needs money attached"
        }
        if (provider == arbiter || provider == payment.from) {
            fail "the arbiter must be a third party"
        }
        if (account.canReceive(org: provider).value == false) {
            fail "the provider can't receive payouts"
        }
        account.earmark(key: id)
        payer = payment.from
        payee = provider
        judge = arbiter
        return id
    }
    if (payer == null) {
        fail "no such job"
    }

    // This job's money, after anything the time limit already returned.
    const due = account.fundingOf(org: payer, key: id).value

    if (action == "release" && (sender.org == payer || sender.org == judge)) {
        if (due > 0) {
            account.pay(to: payee, amount: due, from: payer, key: id)
        }
        return id
    }
    if (action == "cancel" && (sender.org == payee || sender.org == judge)) {
        if (due > 0) {
            account.refund(to: payer, amount: due, key: id)
        }
        return id
    }
    fail "not allowed"
}
```

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](#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

```kotlin Split.req theme={null}
payable contract com.example.Split: int64 {
    const account: sh.stellarium.money.Account
    const author = payment.amount * 70 / 100
    account.pay(to: "org_author", amount: author, from: payment.from, key: null)
    account.pay(to: "org_data", amount: payment.amount - author, from: payment.from, key: null)
    return payment.amount
}
```

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`](https://docs.stellarium.sh/builtins/account#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](https://docs.stellarium.sh/concepts/payments.md#getting-paid).

* **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](https://platform.stellarium.sh)'s **Contracts** page, or read it from the operator:

```bash theme={null}
curl https://operator.stellarium.sh/v1/contracts/$NODE_PUBLIC_KEY/com.example.Job@1
```

### 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:

```json theme={null}
{ "id": 42, "kind": "funding_returned", "organizationId": "org_...", "details": { "reason": "expiry_refund", "amountMicroUsd": 20000000 }, "createdAt": "2026-11-14T12:00:00.000Z" }
```

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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.