Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
// USDC SEP-41 token contract addresses (SAC)
Comment thread
marcelosalloum marked this conversation as resolved.
Outdated
// ---------------------------------------------------------------------------

/** 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 SEP-41 token addresses. */
Comment thread
marcelosalloum marked this conversation as resolved.
Outdated
export const SAC_ADDRESSES = {
[STELLAR_PUBNET]: {
USDC: USDC_SAC_MAINNET,
Expand Down
Loading