Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- Replace generic SAC terminology with SEP-41 across docs, comments, and error messages ([#39](https://github.com/stellar/stellar-mpp-sdk/pull/39))

### Fixed

- Fix CHANGELOG entries for v0.3.0 ([#36](https://github.com/stellar/stellar-mpp-sdk/pull/36))
Expand Down Expand Up @@ -38,7 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Initial release of `@stellar/mpp` — a TypeScript SDK for Stellar blockchain payment methods in the Machine Payments Protocol (MPP)
- **Charge module**: one-time on-chain SAC token transfers with pull (transaction credential) and push (hash credential) modes, following the [draft-stellar-charge-00](https://paymentauth.org/draft-stellar-charge-00) specification
- **Charge module**: one-time on-chain SEP-41 token transfers with pull (transaction credential) and push (hash credential) modes, following the [draft-stellar-charge-00](https://paymentauth.org/draft-stellar-charge-00) specification
- **Channel module**: off-chain payment commitments via one-way payment channel contracts with batch settlement on close (session spec in progress)
- Subpath exports for selective imports (`@stellar/mpp/charge/client`, `@stellar/mpp/charge/server`, `@stellar/mpp/channel/client`, `@stellar/mpp/channel/server`, `@stellar/mpp/env`)
- Env parsing primitives for Stellar-aware configuration
Expand Down
8 changes: 4 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

Stellar MPP SDK — a TypeScript SDK implementing Stellar blockchain payment methods for the Machine Payments Protocol (MPP). Provides two payment modes:

- **Charge**: One-time on-chain SAC (Stellar Asset Contract) token transfers with pull/push credential modes
- **Charge**: One-time on-chain SEP-41 token transfers with pull/push credential modes
- **Channel**: Off-chain payment commitments via one-way payment channel contracts (batch settlement on close)

Built on the `mppx` framework. Peer dependencies: `@stellar/stellar-sdk` (^14.6.1) and `mppx` (^0.4.11).
Expand Down Expand Up @@ -110,8 +110,8 @@ Methods.ts (Zod schema) → client/ (create credentials) + server/ (verify crede
| Path | Role |
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `sdk/src/charge/Methods.ts` | Charge method schema (Zod discriminated union: `transaction` vs `hash` credentials) |
| `sdk/src/charge/client/Charge.ts` | Creates SAC `transfer` invocations; handles pull (send XDR) and push (broadcast + send hash) flows |
| `sdk/src/charge/server/Charge.ts` | Verifies and broadcasts SAC transfers; supports fee sponsorship via FeeBumpTransaction |
| `sdk/src/charge/client/Charge.ts` | Creates SEP-41 `transfer` invocations; handles pull (send XDR) and push (broadcast + send hash) flows |
| `sdk/src/charge/server/Charge.ts` | Verifies and broadcasts SEP-41 transfers; supports fee sponsorship via FeeBumpTransaction |
| `sdk/src/channel/Methods.ts` | Channel method schema (discriminated union: `open` / `voucher` / `close` actions) |
| `sdk/src/channel/client/Channel.ts` | Signs cumulative commitment amounts off-chain via ed25519; handles `open` action (sends signed deploy tx XDR + initial commitment) |
| `sdk/src/channel/server/Channel.ts` | Verifies commitment signatures via contract simulation; `open` action broadcasts the deploy tx and initialises cumulative store |
Expand Down Expand Up @@ -158,7 +158,7 @@ All other `shared/` modules are strictly internal and consumed only by `charge/`

- **mppx integration**: Methods defined via `Method.from()`, adapted with `.toClient()` / `.toServer()`. Namespaced as `stellar.charge()` and `stellar.channel()`.
- **Serialization locks**: Both Charge and Channel servers use Promise-based locks (`let verifyLock: Promise<unknown> = Promise.resolve()`) to serialize verification and prevent race conditions on store get/put.
- **Contract simulation**: Uses Soroban RPC `simulateTransaction` for read-only verification — SAC transfer validation, `prepare_commitment` for commitment bytes, and channel state queries.
- **Contract simulation**: Uses Soroban RPC `simulateTransaction` for read-only verification — SEP-41 transfer validation, `prepare_commitment` for commitment bytes, and channel state queries.
- **Zod validation**: All method schemas use Zod v4 with discriminated unions for credential/action types.
- **Shared utility extraction**: Common logic (polling, fee bumps, simulation, keypair resolution, validation, error types, logging) lives in `shared/` and is imported by both `charge/` and `channel/`.
- **Configurable defaults**: Server and client functions accept optional parameters (`pollMaxAttempts`, `pollDelayMs`, `pollTimeoutMs`, `simulationTimeoutMs`, `maxFeeBumpStroops`, `logger`) with defaults from `shared/defaults.ts`, applied via parameter destructuring.
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# @stellar/mpp

Stellar blockchain payment method for the [Machine Payments Protocol (MPP)](https://mpp.dev). Enables machine-to-machine payments using Soroban SAC token transfers on the Stellar network, with optional support for [one-way payment channels](https://github.com/stellar-experimental/one-way-channel) for high-frequency off-chain payments.
Stellar blockchain payment method for the [Machine Payments Protocol (MPP)](https://mpp.dev). Enables machine-to-machine payments using Soroban SEP-41 token transfers on the Stellar network, with optional support for [one-way payment channels](https://github.com/stellar-experimental/one-way-channel) for high-frequency off-chain payments.

## Specification

Expand All @@ -10,7 +10,7 @@ The charge payment mode implements the [draft-stellar-charge-00](https://payment

### Charge (one-time transfers)

Each payment is a Soroban SAC `transfer` settled on-chain individually.
Each payment is a Soroban SEP-41 `transfer` settled on-chain individually.

```
Client Server Stellar
Expand All @@ -23,7 +23,7 @@ Client Server Stellar
|<------------------------------| |
| | |
| prepareTransaction ----------------- (simulate) ------------>|
| Sign SAC transfer | |
| Sign SEP-41 transfer | |
| Send credential (XDR) | |
|------------------------------>| |
| | sendTransaction ------------>|
Expand Down Expand Up @@ -232,7 +232,7 @@ const data = await response.json()
```ts
stellar.charge({
recipient: string, // Stellar public key (G...) or contract (C...)
currency: string, // SAC contract address
currency: string, // SEP-41 token contract address
network?: 'stellar:testnet' | 'stellar:pubnet', // default: 'stellar:testnet'
decimals?: number, // default: 7
rpcUrl?: string, // custom Soroban RPC URL
Expand Down
4 changes: 2 additions & 2 deletions demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Interactive playground for testing the Stellar MPP payment flow — via browser

Two demo modes are available:

- **Charge** — one-time on-chain SAC token transfers (default)
- **Charge** — one-time on-chain SEP-41 token transfers (default)
- **Channel** — off-chain payment channel commitments (no on-chain tx per payment)

## Prerequisites
Expand Down Expand Up @@ -82,7 +82,7 @@ Client Server
| currency, recipient) |
|<------------------------------|
| |
| Sign Soroban SAC transfer |
| Sign Soroban SEP-41 transfer |
| Send credential (XDR/hash) |
|------------------------------>|
| |
Expand Down
10 changes: 5 additions & 5 deletions diagrams/charge-flow.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Charge Payment Flow — On-Chain SAC Transfer
# Charge Payment Flow — On-Chain SEP-41 Transfer

> Implements [draft-stellar-charge-00](https://paymentauth.org/draft-stellar-charge-00)

Expand All @@ -18,7 +18,7 @@ sequenceDiagram
SC-->>App: 402 + Challenge JSON<br/>{amount, currency, recipient, methodDetails}

App->>CC: createCredential(challenge)
CC->>CC: Resolve network, build SAC transfer(from, to, amount)
CC->>CC: Resolve network, build SEP-41 transfer(from, to, amount)

alt Pull Mode — Sponsored (feePayer: true, default when server has feePayer)
Note over CC: Uses all-zeros source account (GAAA...WHF)<br/>so server can substitute its own account
Expand All @@ -29,7 +29,7 @@ sequenceDiagram
CC->>CC: Sign only sorobanCredentialsAddress<br/>auth entries (not the envelope)
CC-->>App: Credential {type:'transaction', transaction: xdr}
App->>SC: Credential with auth-entry-signed XDR
SC->>SC: verifySacInvocation(tx) — validate structure
SC->>SC: verifyTokenInvocation(tx) — validate structure
SC->>SC: Detect all-zeros source → spec rebuild path
SC->>RPC: getAccount(feePayerKey)
RPC-->>SC: Server account with current sequence
Expand All @@ -51,7 +51,7 @@ sequenceDiagram
CC->>CC: keypair.sign(preparedTx)
CC-->>App: Credential {type:'transaction', transaction: xdr}
App->>SC: Credential with fully-signed XDR
SC->>SC: verifySacInvocation(tx) — validate structure
SC->>SC: verifyTokenInvocation(tx) — validate structure
SC->>RPC: simulateTransaction(signedTx)
RPC-->>SC: Verify transfer events match challenge
SC->>RPC: sendTransaction(signedTx as-is)
Expand All @@ -71,7 +71,7 @@ sequenceDiagram
App->>SC: Credential with tx hash
SC->>RPC: getTransaction(hash)
RPC-->>SC: TX result
SC->>SC: verifySacTransfer(result) — verify on-chain
SC->>SC: verifyTokenTransfer(result) — verify on-chain
SC-->>App: Receipt {status:'success', reference:hash}
end
```
4 changes: 2 additions & 2 deletions diagrams/mindmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ mindmap
defaults hook: currency + recipient
request hook: toBaseUnits, UUID, methodDetails
verify hook
Pull: verifySacInvocation → broadcast → poll
Push: fetch tx → verifySacTransfer
Pull: verifyTokenInvocation → broadcast → poll
Push: fetch tx → verifyTokenTransfer
Fee-bump wrapping
Replay protection via Store
Channel — channel/server/Channel.ts
Expand Down
2 changes: 1 addition & 1 deletion examples/charge-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Example: Stellar MPP Client
*
* Automatically handles 402 Payment Required responses by paying
* via Soroban SAC transfer on Stellar testnet.
* via Soroban SEP-41 transfer on Stellar testnet.
*
* Usage:
* STELLAR_SECRET=SYOUR_SECRET_KEY npx tsx examples/charge-client.ts
Expand Down
2 changes: 1 addition & 1 deletion examples/charge-server.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Example: Stellar MPP Server
*
* Charges 0.01 USDC per request via Soroban SAC transfer.
* Charges 0.01 USDC per request via Soroban SEP-41 transfer.
* Uses Express with security headers (helmet, rate limiting).
*
* Usage:
Expand Down
6 changes: 3 additions & 3 deletions sdk/src/charge/Methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { Method } from 'mppx'
import { z } from 'zod/mini'

/**
* Stellar charge intent for one-time SAC token transfers.
* Stellar charge intent for one-time SEP-41 token transfers.
*
* Supports two credential flows:
* - `type: "transaction"` — **server-broadcast** (pull mode):
* Client signs a Soroban SAC `transfer` invocation and sends the
* Client signs a Soroban SEP-41 `transfer` invocation and sends the
* serialised XDR as `payload.transaction`. The server broadcasts it.
* - `type: "hash"` — **client-broadcast** (push mode):
* Client broadcasts itself and sends the transaction hash.
Expand All @@ -29,7 +29,7 @@ export const charge = Method.from({
request: z.object({
/** Payment amount in base units (stroops). */
amount: z.string(),
/** SAC contract address (C...) for the token to transfer. */
/** SEP-41 token contract address (C...) for the token to transfer. */
currency: z.string(),
/** Recipient Stellar public key (G...) or contract address (C...). */
recipient: z.string(),
Expand Down
4 changes: 2 additions & 2 deletions sdk/src/charge/client/Charge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {
/**
* Creates a Stellar charge method for use on the **client**.
*
* Builds a Soroban SAC `transfer` invocation, signs it, and either:
* Builds a Soroban SEP-41 `transfer` invocation, signs it, and either:
* - **pull** (default): sends the signed XDR to the server to broadcast
* - **push**: broadcasts itself and sends the tx hash
*
Expand Down Expand Up @@ -101,7 +101,7 @@ export function charge(parameters: charge.Parameters) {
const networkPassphrase = NETWORK_PASSPHRASE[network]
const server = new rpc.Server(resolvedRpcUrl)

// Build SAC `transfer(from, to, amount)` invocation
// Build SEP-41 `transfer(from, to, amount)` invocation
const contract = new Contract(currency)
const stellarAmount = BigInt(amount)

Expand Down
6 changes: 3 additions & 3 deletions sdk/src/charge/server/Charge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ describe('charge transaction verification', () => {

await expect(
method.verify({ credential: cred as any, request: cred.challenge.request }),
).rejects.toThrow('matching SAC transfer invocation')
).rejects.toThrow('matching SEP-41 transfer invocation')
})

it('rejects transaction with wrong amount', async () => {
Expand All @@ -395,7 +395,7 @@ describe('charge transaction verification', () => {

await expect(
method.verify({ credential: cred as any, request: cred.challenge.request }),
).rejects.toThrow('matching SAC transfer invocation')
).rejects.toThrow('matching SEP-41 transfer invocation')
})

it('rejects transaction with wrong currency', async () => {
Expand All @@ -420,7 +420,7 @@ describe('charge transaction verification', () => {

await expect(
method.verify({ credential: cred as any, request: cred.challenge.request }),
).rejects.toThrow('matching SAC transfer invocation')
).rejects.toThrow('matching SEP-41 transfer invocation')
})

it('rejects sponsored source without feePayer configured', async () => {
Expand Down
12 changes: 6 additions & 6 deletions sdk/src/charge/server/Charge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
/**
* Creates a Stellar charge method for use on the **server**.
*
* Verifies and settles Soroban SAC `transfer` invocations received as
* Verifies and settles Soroban SEP-41 `transfer` invocations received as
* pull-mode (signed XDR) or push-mode (on-chain tx hash) credentials.
*
* @see https://paymentauth.org/draft-stellar-charge-00
Expand Down Expand Up @@ -93,7 +93,7 @@
},
async verify({ credential }) {
// Serialize through the lock to prevent concurrent race conditions
const result = await new Promise<any>((resolve, reject) => {

Check warning on line 96 in sdk/src/charge/server/Charge.ts

View workflow job for this annotation

GitHub Actions / check-test-build

Unexpected any. Specify a different type
verifyLock = verifyLock.then(
() => doVerify(credential).then(resolve, reject),
() => doVerify(credential).then(resolve, reject),
Expand All @@ -103,7 +103,7 @@
},
})

async function doVerify(credential: any) {

Check warning on line 106 in sdk/src/charge/server/Charge.ts

View workflow job for this annotation

GitHub Actions / check-test-build

Unexpected any. Specify a different type
const { challenge } = credential
const { request: challengeRequest } = challenge

Expand Down Expand Up @@ -161,7 +161,7 @@
timeoutMs: pollTimeoutMs,
})

verifySacTransfer(
verifyTokenTransfer(
txResult,
{
amount: expectedAmount,
Expand Down Expand Up @@ -195,7 +195,7 @@

verifyExactlyOneInvokeOp(tx)

verifySacInvocation(tx, {
verifyTokenInvocation(tx, {
amount: expectedAmount,
currency: expectedCurrency,
recipient: expectedRecipient,
Expand Down Expand Up @@ -485,7 +485,7 @@
}
}

function verifySacInvocation(
function verifyTokenInvocation(
tx: Transaction,
expected: { amount: bigint; currency: string; recipient: string },
) {
Expand Down Expand Up @@ -534,7 +534,7 @@

if (!found) {
throw new PaymentVerificationError(
`${LOG_PREFIX} Transaction does not contain a matching SAC transfer invocation.`,
`${LOG_PREFIX} Transaction does not contain a matching SEP-41 transfer invocation.`,
{
currency: expected.currency,
recipient: expected.recipient,
Expand All @@ -544,7 +544,7 @@
}
}

function verifySacTransfer(
function verifyTokenTransfer(
txResult: rpc.Api.GetSuccessfulTransactionResponse,
expected: { amount: bigint; currency: string; recipient: string },
networkPassphrase: string,
Expand Down
12 changes: 6 additions & 6 deletions sdk/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,22 @@ export const HORIZON_URLS: Record<NetworkId, string> = {
}

// ---------------------------------------------------------------------------
// USDC Stellar Asset Contract (SAC) addresses
// Well-known SAC (SEP-41) token contract addresses
// ---------------------------------------------------------------------------

/** USDC SAC contract address on Stellar mainnet. */
/** USDC SEP-41 token contract address on Stellar mainnet (SAC). */
export const USDC_SAC_MAINNET = 'CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI'

/** USDC SAC contract address on Stellar testnet. */
/** USDC SEP-41 token contract address on Stellar testnet (SAC). */
export const USDC_SAC_TESTNET = 'CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA'

/** Native XLM SAC contract address on mainnet. */
/** Native XLM SEP-41 token contract address on mainnet (SAC). */
export const XLM_SAC_MAINNET = 'CAS3J7GYLGVE45MR3HPSFG352DAANEV5GGMFTO3IZIE4JMCDALQO57Y'

/** Native XLM SAC contract address on testnet. */
/** Native XLM SEP-41 token contract address on testnet (SAC). */
export const XLM_SAC_TESTNET = 'CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC'

/** Map from network to well-known SAC addresses. */
/** Map from network to well-known SAC (SEP-41) token addresses (USDC/XLM). */
export const SAC_ADDRESSES = {
[STELLAR_PUBNET]: {
USDC: USDC_SAC_MAINNET,
Expand Down
Loading