YouPI-PG
A self-hosted UPI & COD payment gateway with zero per-transaction platform fees, universal JavaScript checkout integration, dynamic QR payments, WhatsApp OTP authentication, and automated payment veri
翻译准备中
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗Continuous payment streams, linear vesting, and payment splits on Stellar & Soroban.
Stellar Streams is an open protocol for programmable payments on Stellar. Instead of sending a one-off transfer, you can:
Everything runs on Soroban smart contracts written in Rust, with a typed TypeScript SDK and an open web dashboard.
Project status: pre-1.0. The contracts are implemented, unit-tested, and build clean under
clippy/rustfmt. They have not been audited or deployed to Mainnet. Before integrating, read the security model and trust assumptions — who can cancel, who holds fees, what the arbiter may do, and the inherent risks (no upgradeability, unaudited contracts, admin key custody, TTL expiry). See the Roadmap for what's next.
| Use case | How Stellar Streams helps |
|---|---|
| Payroll | Stream salary per-second; employees withdraw whenever they like. |
| Token vesting | Linear vesting with a cliff for team/investor allocations. |
| Grants & bounties | Milestone cliffs release funds as work completes. |
| Revenue sharing | Route a payment to many contributors via a single split. |
| OTC / services | Optional escrow with an arbiter and deadline refunds. |
The docs/ directory carries an index of every page — start there. Highlights:
stellar/
├── contracts/ # Soroban (Rust) smart contracts
│ ├── stream/ # ⭐ continuous streaming + linear vesting + protocol fee
│ ├── splits/ # proportional payment splits
│ └── escrow/ # arbiter-based escrow primitive
├── packages/
│ ├── sdk/ # @stellar-starter-kit/sdk — typed StreamsClient
│ ├── types/ # @stellar-starter-kit/types — shared domain types
│ ├── utils/ # @stellar-starter-kit/utils — precise stroop & vesting math
│ ├── hooks/ # @stellar-starter-kit/hooks — React hooks (useStream)
│ ├── wallets/ # @stellar-starter-kit/wallets — Freighter/Albedo/Rabet/Hana/Ledger
│ ├── contracts/ # generated Soroban TS bindings
│ └── ui/ # shared UI primitives
├── apps/
│ └── web/ # Next.js dashboard (/streams, /escrow, /docs)
├── scripts/ # cross-platform build/deploy/invoke scripts
└── docs/ # development guide + contract reference + migration guide
graph TD
Web["apps/web (Next.js 15)"] --> SDK["packages/sdk (StreamsClient)"]
Web --> Hooks["packages/hooks (useStream, mutations)"]
Web --> Wallets["packages/wallets"]
SDK --> Types["packages/types"]
Hooks --> SDK
Web --> Utils["packages/utils"]
SDK --> RPC["Soroban RPC"]
RPC --> Stream["contracts/stream"]
RPC --> Splits["contracts/splits"]
RPC --> Escrow["contracts/escrow"]
StreamsClient.packages/utils), with tests on both sides.stream contractThe flagship contract locks tokens up front and releases them linearly.
| Function | Description |
|---|---|
initialize(admin, treasury, fee_bps) | Configure the optional protocol fee (cap 10%). |
create_stream(sender, recipient, token, amount, start, end, cliff) | Lock tokens and open a stream. Returns the stream id. |
create_streams(sender, token, entries) | Lock a whole batch atomically. Returns the new ids, in input order. |
withdraw(id) | Recipient claims the vested, unclaimed balance. |
cancel(id) | Sender cancels; unvested funds are refunded, vested funds stay claimable. |
transfer_recipient(id, caller, new_recipient) | Recipient hands the unwithdrawn balance off to a new address. |
claimable(id) / vested(id) | View helpers. |
get_stream(id) / get_config() / next_stream_id() | Read helpers. |
Safety properties enforced on-chain: end > start, start ≤ cliff ≤ end, amount > 0,
authenticated sender/recipient, and idempotent cancel. create_streams applies every one of
those checks to every entry before it moves a token, so a batch either fully lands or changes
nothing. See contracts/stream/src/lib.rs.
vested(now) = 0 if now < start or now < cliff
= total if now ≥ end
= total * (now - start) / (end-start) otherwise
splits contractThe splits contract turns one payment into many. A split is an immutable, reusable
configuration of recipients with integer share weights; anyone may pay into it and the funds are
distributed on the spot.
| Function | Description |
|---|---|
create_split(creator, recipients, shares) | Register a split. Returns the split id; shares[i] is the weight of recipients[i]. |
distribute(split_id, from, token, amount) | Pull amount of token from from and pay every recipient proportionally. |
get_split(id) / next_split_id() | Read helpers. |
Validation is enforced on-chain: the recipient list cannot be empty, recipients and shares
must be the same length, every share must be non-zero, and duplicate recipients are rejected.
distribute pays each recipient amount * share / total_shares with integer math and assigns the
rounding remainder to the final recipient, so no dust is stranded. Each call requires the payer's
signature and emits a splits-tagged event for indexers.
See contracts/splits/src/lib.rs.
escrow contractThe escrow contract holds a single payment between a payer and a payee until an arbiter or one of
the parties releases or refunds it. The deadline decides when a funded escrow becomes
refundable to the payer.
| Function | Description |
|---|---|
create_escrow(id, payer, payee, arbiter, token, amount, deadline) | Register an escrow. amount > 0 and the deadline must be in the future. Status Created. |
fund_escrow(id) | Payer transfers amount into the contract. Status → Funded. |
release_escrow(id, caller) | Payer or arbiter sends the locked funds to the payee. Status → Released. |
refund_escrow(id, caller) | Payee or arbiter returns the locked funds to the payer. Status → Refunded. |
cancel_escrow(id, caller) | Payer cancels: always while Created, and once Funded only at or after the deadline (refund to payer). |
get_escrow(id) | Read helper. |
Deadline rule: create_escrow requires deadline > env.ledger().timestamp() at call time and
returns EscrowError::InvalidDeadline (code 9) when deadline <= now — a deadline in the past, or
exactly the current ledger timestamp, is rejected. Without this check a funded escrow could be
cancelled by the payer the moment it was funded, since cancel_escrow releases the funds back to
the payer as soon as the deadline has passed. Note that only cancel_escrow consults the deadline:
release_escrow and refund_escrow work at any time before finalization, including after the
deadline.
See contracts/escrow/src/lib.rs.
Every entry point with its argument and return types, every error variant with its numeric code and the condition that raises it, and every event topic with its payload shape, for all three contracts: docs/contracts.md.
For upgrading existing projects and mapping package imports, see the Migration Guide. For the complete list of technical documentation, see the Documentation Index.
The sections above are orientation. That page is what an integrator mapping a failure to a
user-facing message, or an indexer decoding an event, should actually be reading — including the
cases the Rust does not advertise: two error codes that are defined but never returned, and an
escrow cancelled event that carries no amount.
For copy-pasteable client examples covering SSR-safe construction, stream reads, config reads, write methods, and typed error handling, see the SDK guide.
.nvmrc; engines requires ≥ 22) and pnpm ≥ 9wasm32v1-none target (for contracts)pnpm install
pnpm run dev # http://localhost:3000
# Unit-test all Soroban contracts (29 tests)
cargo test --manifest-path contracts/Cargo.toml
# Lint and format
cargo clippy --manifest-path contracts/Cargo.toml --all-targets -- -D warnings
cargo fmt --manifest-path contracts/Cargo.toml --all -- --check
# Compile to optimized WASM and deploy to Testnet
pnpm build:contracts
pnpm optimize
pnpm deploy:stream
pnpm deploy:splits
pnpm run node:local # docker compose up -d (Stellar Quickstart)
Want the complete path — local node → deploy stream → fund accounts → create a stream →
advance time → withdraw — with every command and its expected output? Follow the
end-to-end walkthrough
in the development guide (verified on Linux).
Migrating from
@stellar-starter-kit/*? See the Migration Guide for a complete import mapping table and codemod snippets.
import { StreamsClient } from '@stellar-starter-kit/sdk';
const client = new StreamsClient({
contractId: process.env.NEXT_PUBLIC_STREAM_CONTRACT_ID!,
rpcUrl: 'https://soroban-testnet.stellar.org',
networkPassphrase: 'Test SDF Network ; September 2015',
publicKey: address,
signTransaction: (xdr) => wallet.signTransaction(xdr),
});
// Open a 100 XLM stream vesting over 30 days.
const { hash } = await client.createStream({
sender: address,
recipient,
token: XLM_TOKEN,
amount: '1000000000', // stroops
startTime: now,
endTime: now + 30 * 86_400,
cliffTime: now,
});
token can be any SEP-41 token contract id (C...): the native XLM SAC, the SAC of an issued
asset, or a custom Soroban token. Amounts are always in the token's smallest unit, and the
number of decimals comes from the token itself (decimals()), not from a hardcoded 7. Read it with
getTokenMetadata and scale with the decimals-aware helpers:
import { StreamsClient, assetContractId } from '@stellar-starter-kit/sdk';
import { formatTokenAmount, parseTokenAmount } from '@stellar-starter-kit/utils';
// The SAC of an issued asset (or pass 'native' for XLM), or any custom token contract id.
const token = assetContractId(`USDC:${USDC_ISSUER}`, 'Test SDF Network ; September 2015');
const { decimals, symbol } = await client.getTokenMetadata(token); // SACs have 7; custom tokens may differ
await client.createStream({
sender: address,
recipient,
token,
amount: parseTokenAmount('250.5', decimals).toString(),
startTime: now,
endTime: now + 30 * 86_400,
cliffTime: now,
});
// Display: format with the decimals of the stream's own token.
const stream = await client.getStream(id);
const meta = await client.getTokenMetadata(stream!.token);
`${formatTokenAmount(stream!.totalAmount, meta.decimals)} ${meta.symbol}`; // "250.5 USDC"
createStream and getTokenMetadata reject anything that is not a contract id, so a G...
account or an ISSUER:CODE string fails fast before any RPC call.
Formatting and vesting helpers live in @stellar-starter-kit/utils:
import {
formatTokenAmount,
formatStroopsToXlm,
computeVestedAmount,
} from '@stellar-starter-kit/utils';
formatTokenAmount(1_234_567n, 6); // "1.234567" — any token, given its decimals
formatStroopsToXlm(123_456_789n); // "12.3456789" — shorthand for 7-decimal XLM
computeVestedAmount(schedule, now); // bigint
See examples/basic-payment for a complete, runnable example that creates a stream on Testnet and prints the transaction hash and stream id. Clone it to get started:
# Setup
pnpm install
cd examples/basic-payment
cp .env.example .env
# Edit .env with your contract id, sender key, and recipient address
# Run
pnpm run dev
Copy .env.example to .env.local and set:
| Variable | Purpose |
|---|---|
NEXT_PUBLIC_STELLAR_NETWORK | local, testnet, or mainnet. |
NEXT_PUBLIC_HORIZON_URL | Horizon endpoint. |
NEXT_PUBLIC_SOROBAN_RPC_URL | Soroban RPC endpoint. |
NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE | Network passphrase. |
NEXT_PUBLIC_STREAM_CONTRACT_ID | Deployed stream contract id. |
NEXT_PUBLIC_SPLITS_CONTRACT_ID | Deployed splits contract id. |
NEXT_PUBLIC_TOKEN_CONTRACT_ID | Default token contract in the create form (defaults to testnet XLM SAC); any token id works. |
| Maintainer | Role | Contact |
|---|---|---|
| @SorobanForge | Lead maintainer | GitHub |
Questions, ideas, and show-and-tell live in GitHub Issues. Security reports go through SECURITY.md, not public issues.
Testnet contract ids and explorer links are recorded in docs/deployments.md.
Seeing Freighter wallet extension is not installed or enabled., No stream contract id configured., Account not found, or RPC kept answering TRY_AGAIN_LATER ...? Each known
symptom with its cause and fix is listed in docs/troubleshooting.md,
along with how to confirm the deployment state via the contract ids page.
We welcome contributions of all sizes. Start with CONTRIBUTING.md and the
Code of Conduct. Look for issues labelled status: good-first-issue and
status: help-wanted (see .github/labels.yml).
Maintainers can create the planned Wave backlog in one run with
./scripts/create-issues.sh and configure branch protection with
./scripts/setup-branch-protection.sh.
Good places to start:
splits dashboard page.See ROADMAP.md. Highlights:
Thanks to everyone who has contributed to Stellar Streams.
MIT — see LICENSE.