A unidirectional payment channel contract for Soroban (Stellar).
A payment channel allows a funder to make many small payments to a recipient off-chain, with only two on-chain transactions: opening the channel and closing it. This avoids per-payment transaction fees and latency.
Warning
The contracts in this repository have not been audited.
- Funder (
from): Deposits tokens into the channel and signs commitments authorizing the recipient to settle or close the channel and receive a given amount. - Recipient (
to): Receives commitments off-chain and can settle or close the channel on-chain at any time using a signed commitment.
Participants have the following responsibilities to receive the funds owing to them.
- Keeping the private key corresponding to
commitment_key(the commitment signing key) secret.
- Verifies the
refund_waiting_periodat channel creation is long enough to allow them to react to a close_start event. - Verifies the
amountin each commitment is less than the channels balance. - Monitors the channel for [
event::Close] events. - Calls
settlewith a commitment promptly after seeing a close_start event, before the funder callsrefund.
stateDiagram-v2
[*] --> Open: __constructor
Open --> Closed: close
Open --> Closing: close_start
Closing --> Closed: close
Closing --> Closed: [after wait]
Closed --> [*]: refund
top_up, settle, and close can be called in any state.
| Function | Description |
|---|---|
__constructor |
Open a channel with an initial deposit. Callable by the funder, or anyone if amount is zero. |
top_up |
Deposit additional tokens into the channel. |
settle |
Withdraw funds using a signed commitment without closing the channel. |
close |
Close the channel using a signed commitment, withdrawing funds to the recipient. Automatically attempts to refund the funder. |
close_start |
Begin closing the channel, effective after a waiting period. |
refund |
Refund the remaining balance to the funder after the close is effective. |
| Function | Description |
|---|---|
prepare_commitment |
Generate the commitment bytes to sign. |
| Function | Description |
|---|---|
token |
Returns the token address. |
from |
Returns the funder address. |
to |
Returns the recipient address. |
refund_waiting_period |
Returns the refund waiting period in ledgers. |
| Function | Description |
|---|---|
deposited |
Returns the total amount deposited. |
balance |
Returns the current balance. |
withdrawn |
Returns the total amount already withdrawn. |
The channel is deployed with a SEP-41 token, funder address, recipient
address, an ed25519 commitment_key (public key), an initial deposit
amount, and a refund_waiting_period (in ledgers).
The funder's tokens are transferred into the channel contract on deployment.
The funder can also top up the channel later using [Contract::top_up], or
by transferring the token directly to the channel contract address.
The funder makes payments by signing commitments off-chain and sending them to the recipient. A commitment authorizes the recipient to settle or close the channel and receive a cumulative total amount. Each new commitment replaces the previous one.
For example:
- Commitment for 100: recipient can settle or close and receive 100.
- Commitment for 140: recipient can settle or close and receive 140 (40 more if 100 was already settled).
A commitment is an XDR serialized [Commitment] struct containing a domain
separator (chancmmt), the network ID, the channel contract address, and
the amount. The
funder signs the serialized bytes with the ed25519 key corresponding to the
commitment_key. Use [Contract::prepare_commitment] as a convenience to
generate the bytes to sign.
The serialized commitment is an XDR ScVal::Map with four entries
(sorted alphabetically by key):
ScVal::Map({
Symbol("amount"): I128(amount),
Symbol("channel"): Address(channel_contract_address),
Symbol("domain"): Symbol("chancmmt"),
Symbol("network"): BytesN<32>(network_id),
})
The recipient calls [Contract::settle] at any time with a commitment
amount and its signature. The contract verifies the signature, then
transfers the difference between the commitment amount and what has
already been withdrawn. If the commitment amount is less than or equal
to what has already been withdrawn, no transfer occurs.
Settlement is optional. The recipient does not need to settle at all —
[Contract::close] will also settle any unsettled amount. The recipient
may choose to settle periodically to receive funds without closing the
channel.
The recipient calls [Contract::close] with a commitment amount and its
signature. Like settle, only the difference between the commitment
amount and what has already been withdrawn is transferred.
After transferring the committed funds, the close function automatically
attempts to refund the remaining balance to the funder. This refund attempt
uses try_transfer and will silently succeed or fail without affecting the
withdrawal. If the automatic refund fails, the funder can call
[Contract::refund] to reclaim the remaining balance.
Like settle, can be called even after the channel is closed, up until
the funder calls [Contract::refund] and the balance is drained.
The funder calls [Contract::close_start] to begin closing the channel.
The close does not take effect immediately — there is a waiting period of
refund_waiting_period ledgers.
The recipient can still call [Contract::settle] or [Contract::close]
during and after the waiting period. Once the waiting period has elapsed,
the funder can call refund to reclaim the remaining balance.
Important: The recipient should monitor for [event::Close] events and
settle or close before the funder calls refund.
After the refund waiting period has elapsed, the funder calls
[Contract::refund] to reclaim whatever balance remains in the channel.
This transfers the entire remaining token balance to the funder,
including any amount the recipient was entitled to but did not settle or
close for.
The contract does not reserve funds for the recipient. If the recipient
has not closed before the funder calls refund, those funds are lost to
the recipient and assumed to be of no interest to the recipient.
- Commitments are signed with an ed25519 key, not a Stellar account. The
commitment_keyis set at deployment and cannot be changed. - The commitment includes a domain separator, the network ID, and the channel contract address, preventing signatures from being reused across networks, channels, or confused with other signed payloads.
- The refund waiting period protects the recipient: it gives them time to settle or close using their latest commitment before the funder can reclaim funds.
A factory contract for opening channel contracts on Soroban (Stellar).
The factory stores a channel contract wasm hash and opens new channel instances using it. An admin can update the wasm hash to open newer versions of the channel contract.
| Function | Description |
|---|---|
__constructor |
Initialize the factory with an admin and channel wasm hash. |
set_wasm |
Update the stored channel wasm hash. Admin only. |
open |
Deploy a new channel contract with the given parameters. |
admin |
Returns the admin address. |
wasm_hash |
Returns the stored channel wasm hash. |