From d9ffc06956aa99f75d468746e1833d39036769a9 Mon Sep 17 00:00:00 2001 From: joshfatoye0011-bit Date: Tue, 25 Aug 2026 05:51:06 -0700 Subject: [PATCH 1/3] =?UTF-8?q?docs:=20add=20local=20security=20checks=20s?= =?UTF-8?q?ection=20to=20CONTRIBUTING.md=20=E2=80=94=20closes=20#519?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 17e5981..cabf718 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -183,6 +183,46 @@ Copy the returned contract ID into your `.env.local` as --- +## Local security checks + +CI runs two security scripts on every push. Run them locally before opening a +PR to catch issues before they block the build. + +### Secret scanner + +Scans TypeScript, JavaScript, YAML, and JSON source files for hardcoded secrets +(Stripe keys, AWS access key IDs, GitHub tokens, private keys, etc.). + +```bash +node scripts/check-secrets.mjs +``` + +Exits `0` with `✅ No hardcoded secrets found.` when clean. Exits `1` and +prints a `❌ HIGH [SECRETS-001]` line for each finding, including the file +path, line number, and the matched pattern label. All findings must be resolved +before merging. + +### Soroban / Rust contract checker + +Scans `contracts/**/*.rs` for four classes of issues: + +| ID | Severity | What it catches | +|---|---|---| +| SOROBAN-001 | HIGH | Unchecked arithmetic (`+=`, `-=`, `*=`) on `i128`/`u128`/`u64` variables | +| SOROBAN-002 | HIGH | Public write functions missing `require_auth()` | +| SOROBAN-003 | HIGH | `persistent().set()` calls not paired with `extend_ttl()` | +| SOROBAN-004 | LOW | `panic!("…")` with a string literal instead of `ContractError` | + +```bash +node scripts/soroban-security-check.mjs +``` + +Exits `0` when clean or when only LOW-severity warnings are present. Exits `1` +if any HIGH-severity issue is found. Fix all `❌ HIGH` findings before merging; +`⚠️ LOW` warnings are non-blocking but should be addressed. + +--- + ## Code style ### TypeScript From d05cff6ed95c8156f0dd645ffa970cf2770f7b31 Mon Sep 17 00:00:00 2001 From: joshfatoye0011-bit Date: Tue, 25 Aug 2026 06:04:46 -0700 Subject: [PATCH 2/3] docs: document all 28 public contract functions, fix count from 12 Closes #521 The API reference and README both claimed the contract exposed 12 public functions. A grep of contracts/streaming/src/lib.rs shows 28. This commit documents the 16 previously undocumented functions following the existing per-function pattern (signature, params, authorization, preconditions, returns, behavior, example): Admin / lifecycle (5): initialize, pause, unpause, upgrade, migrate Core write (2 new): create_streams_batch, cleanup_stream Query (2 new): get_archived_sent_streams, get_archived_received_streams Metadata (2): update_stream_metadata, get_stream_metadata Delegation (3): set_delegate, remove_delegate, get_delegate Contract info (2): version, name Additional fixes: - bump_stream: corrected auth claim (no auth required, not sender-only) - README TTL FAQ: 30 days not 6 months (matches PERSISTENT_TTL_LEDGERS) - README max-duration FAQ: 10-year hard cap documented - Auth table in README expanded to all 17 write/admin operations - Type Definitions updated: Stream gains linear_amount + duration fields; CreateStreamInput and StreamMetadata interfaces added --- docs/README.md | 73 ++++- docs/api-reference.md | 642 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 697 insertions(+), 18 deletions(-) diff --git a/docs/README.md b/docs/README.md index 45a5ad8..000044b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -31,25 +31,49 @@ FlowStar is a Stellar Soroban smart contract that enables token streaming with f ### 1. For Smart Contract Integrators -Start with the **[API Reference](./api-reference.md)** to understand all 12 public functions: +Start with the **[API Reference](./api-reference.md)** to understand all 28 public functions: + +- Admin & Lifecycle + - `initialize()` - One-time contract setup with admin address + - `pause()` - Halt all write operations (admin only) + - `unpause()` - Resume write operations (admin only) + - `upgrade()` - Replace contract Wasm bytecode (admin only) + - `migrate()` - Post-upgrade storage migration hook (admin only) - Stream Creation & Modification - `create_stream()` - Create a new payment stream + - `create_streams_batch()` - Create up to 20 streams atomically - `cancel()` - Cancel a stream - `transfer_stream()` - Transfer stream to new recipient - `top_up()` - Add funds to existing stream - - `bump_stream()` - Extend stream TTL + - `bump_stream()` - Extend stream TTL (callable by anyone) + - `cleanup_stream()` - Delete completed/cancelled stream data - Recipient Operations - `withdraw()` - Withdraw available funds +- Metadata + - `update_stream_metadata()` - Set name, category, and memo on a stream + - `get_stream_metadata()` - Retrieve stream metadata + +- Delegation + - `set_delegate()` - Authorize a delegate to withdraw on recipient's behalf + - `remove_delegate()` - Remove a stream's delegate + - `get_delegate()` - Query the current delegate for a stream + - Query Functions - `get_stream()` - Fetch stream details - `get_withdrawable()` - Get available withdrawal amount - - `get_sent_streams()` - List streams sent by address - - `get_received_streams()` - List streams received by address - - `get_sent_stream_count()` - Count of sent streams - - `get_received_stream_count()` - Count of received streams + - `get_sent_streams()` - List active streams sent by address + - `get_received_streams()` - List active streams received by address + - `get_sent_stream_count()` - Count of active sent streams + - `get_received_stream_count()` - Count of active received streams + - `get_archived_sent_streams()` - List completed/cancelled streams sent by address + - `get_archived_received_streams()` - List completed/cancelled streams received by address + +- Contract Info + - `version()` - Contract version number + - `name()` - Contract name string ### 2. For dApp Developers @@ -121,12 +145,22 @@ All write operations require authorization from a specific account: | Operation | Requires | |-----------|----------| +| `initialize()` | `admin` must authorize | +| `pause()` | Stored admin must authorize | +| `unpause()` | Stored admin must authorize | +| `upgrade()` | `admin` param must authorize and match stored admin | +| `migrate()` | Stored admin must authorize | | `create_stream()` | Sender must authorize | -| `withdraw()` | Recipient must authorize | +| `create_streams_batch()` | Sender must authorize | +| `withdraw()` | Recipient (or registered delegate) must authorize | | `cancel()` | Sender must authorize | | `transfer_stream()` | Current recipient must authorize | | `top_up()` | Sender must authorize | -| `bump_stream()` | Sender must authorize | +| `bump_stream()` | No authorization required | +| `cleanup_stream()` | Sender or recipient must authorize | +| `update_stream_metadata()` | Sender must authorize | +| `set_delegate()` | Recipient must authorize | +| `remove_delegate()` | Recipient must authorize | Query operations (read-only) do not require authorization or fees. @@ -184,6 +218,8 @@ interface Stream { cliff_amount: i128; amount_per_second: i128; cancelled: boolean; + linear_amount: i128; + duration: i128; } interface StreamParams { @@ -195,6 +231,23 @@ interface StreamParams { cliff_time: u64; cliff_amount: i128; } + +// Used by create_streams_batch; same fields as StreamParams but a distinct type +interface CreateStreamInput { + recipient: Address; + token: Address; + total_amount: i128; + start_time: u64; + end_time: u64; + cliff_time: u64; + cliff_amount: i128; +} + +interface StreamMetadata { + name: string; + category: string; + memo: string; +} ``` --- @@ -226,7 +279,7 @@ A: All remaining funds are immediately returned to the sender's token account. A: Yes! You can withdraw any amount up to the currently available balance. **Q: How often should I bump the stream TTL?** -A: Streams last ~6 months before needing a bump. Call `bump_stream()` every 5 months for active streams. +A: Persistent storage entries expire after ~30 days of inactivity. Call `bump_stream()` at least once every 25 days for streams that won't be touched by withdrawals, top-ups, or other writes in that period. Any write operation (withdraw, top_up, cancel, etc.) bumps the TTL automatically. **Q: What tokens are supported?** A: Any SEP-41 token on Stellar is supported. Common tokens include XLM, USDC, and EURC. @@ -235,7 +288,7 @@ A: Any SEP-41 token on Stellar is supported. Common tokens include XLM, USDC, an A: Yes! Call `transfer_stream()` to transfer your recipient rights to another address. **Q: What's the maximum stream duration?** -A: Theoretically unlimited, but practical limit is ~6 months before TTL bump needed. +A: The contract enforces a hard cap of 10 years (315,360,000 seconds). For streams longer than ~30 days that won't receive regular writes, call `bump_stream()` periodically to keep the storage entry alive. --- diff --git a/docs/api-reference.md b/docs/api-reference.md index a64879e..23a94d0 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -4,12 +4,212 @@ Complete reference for integrating FlowStar's token streaming smart contract int ## Table of Contents -1. [Core Functions](#core-functions) -2. [Query Functions](#query-functions) -3. [Authorization](#authorization) -4. [Error Codes](#error-codes) -5. [Gas Estimates](#gas-estimates) -6. [Type Definitions](#type-definitions) +1. [Admin Functions](#admin-functions) +2. [Core Functions](#core-functions) +3. [Query Functions](#query-functions) +4. [Metadata Functions](#metadata-functions) +5. [Delegation Functions](#delegation-functions) +6. [Contract Info Functions](#contract-info-functions) +7. [Authorization](#authorization) +8. [Error Codes](#error-codes) +9. [Gas Estimates](#gas-estimates) +10. [Type Definitions](#type-definitions) + +--- + +## Admin Functions + +### initialize + +Initializes the contract with an admin address. Must be called exactly once after deployment; subsequent calls panic. + +**Signature:** +```rust +pub fn initialize(env: Env, admin: Address) +``` + +**Parameters:** +- `admin: Address` - The account that will have administrative control over the contract (upgrade, pause, migrate) + +**Authorization Required:** +- `admin` must authorize the transaction + +**Preconditions:** +- Contract must not already be initialized (no `Admin` key in instance storage) + +**Returns:** +- `()` on success +- Panics with `"already initialized"` if called a second time + +**Behavior:** +- Sets `Admin` in instance storage to `admin` +- Sets `Paused` flag to `false` +- Bumps instance storage TTL to ~1 day + +**Example - CLI:** +```bash +soroban contract invoke \ + --id CXXXXX \ + -- \ + initialize \ + --admin GXXXXXX +``` + +--- + +### pause + +Halts all write operations contract-wide. While paused, calls to `create_stream`, `create_streams_batch`, `withdraw`, `cancel`, `transfer_stream`, and `top_up` will panic with `"contract is paused"`. + +**Signature:** +```rust +pub fn pause(env: Env) +``` + +**Parameters:** +- None (admin identity is read from instance storage) + +**Authorization Required:** +- The stored admin address must authorize the transaction + +**Preconditions:** +- Contract must be initialized + +**Returns:** +- `()` on success + +**Behavior:** +- Sets `Paused` flag to `true` in instance storage +- Bumps instance storage TTL +- Emits a `PauseEvent` with the current ledger timestamp + +**Example - CLI:** +```bash +soroban contract invoke \ + --id CXXXXX \ + --source GADMIN \ + -- \ + pause +``` + +--- + +### unpause + +Resumes all write operations after a pause. + +**Signature:** +```rust +pub fn unpause(env: Env) +``` + +**Parameters:** +- None + +**Authorization Required:** +- The stored admin address must authorize the transaction + +**Preconditions:** +- Contract must be initialized + +**Returns:** +- `()` on success + +**Behavior:** +- Sets `Paused` flag to `false` in instance storage +- Bumps instance storage TTL +- Emits an `UnpauseEvent` with the current ledger timestamp + +**Example - CLI:** +```bash +soroban contract invoke \ + --id CXXXXX \ + --source GADMIN \ + -- \ + unpause +``` + +--- + +### upgrade + +Replaces the contract's Wasm bytecode with a new version. Only the admin can call this. + +**Signature:** +```rust +pub fn upgrade(env: Env, admin: Address, new_wasm_hash: BytesN<32>) +``` + +**Parameters:** +- `admin: Address` - Must match the stored admin address (validated on-chain) +- `new_wasm_hash: BytesN<32>` - Hash of the new Wasm blob, which must already be uploaded to the network via `soroban contract upload` + +**Authorization Required:** +- `admin` must authorize the transaction + +**Preconditions:** +- `admin` must equal the stored admin address; panics with `"unauthorized"` otherwise +- New Wasm hash must exist on the network + +**Returns:** +- `()` on success + +**Behavior:** +- Calls `env.deployer().update_current_contract_wasm(new_wasm_hash)` to perform the in-place upgrade +- Storage layout is not migrated automatically; call `migrate` after upgrading if the new version requires it + +**Example - CLI:** +```bash +# 1. Upload new wasm first +soroban contract upload \ + --wasm target/wasm32-unknown-unknown/release/streaming.wasm \ + --source GADMIN + +# 2. Upgrade using the returned hash +soroban contract invoke \ + --id CXXXXX \ + --source GADMIN \ + -- \ + upgrade \ + --admin GADMIN \ + --new_wasm_hash <32-byte-hex-hash> +``` + +--- + +### migrate + +Post-upgrade data migration hook. Call this once after `upgrade` when the new contract version requires storage layout changes. + +**Signature:** +```rust +pub fn migrate(env: Env) +``` + +**Parameters:** +- None + +**Authorization Required:** +- The stored admin address must authorize the transaction + +**Preconditions:** +- Contract must be initialized + +**Returns:** +- `()` on success + +**Behavior:** +- Sets `Paused` to `false` (unfreezes the contract after an upgrade) +- Any version-specific storage migrations are implemented here in future contract versions + +**Example - CLI:** +```bash +soroban contract invoke \ + --id CXXXXX \ + --source GADMIN \ + -- \ + migrate +``` --- @@ -88,6 +288,75 @@ const result = await invoke( --- +### create_streams_batch + +Creates multiple token streams in a single atomic transaction. All streams are validated before any funds are transferred — if any stream fails validation the entire batch is rejected with no side-effects. + +**Signature:** +```rust +pub fn create_streams_batch( + env: Env, + sender: Address, + streams: Vec, +) -> Result, StreamError> +``` + +**Parameters:** +- `sender: Address` - The account funding all streams in the batch (must authorize) +- `streams: Vec` - Between 1 and 20 stream definitions. Each `CreateStreamInput` contains: + - `recipient: Address` - Account receiving the funds + - `token: Address` - Token contract address (SEP-41) + - `total_amount: i128` - Total amount for this stream (smallest unit) + - `start_time: u64` - Stream start time (UNIX seconds) + - `end_time: u64` - Stream end time (UNIX seconds) + - `cliff_time: u64` - Cliff time (UNIX seconds) + - `cliff_amount: i128` - Amount unlocked at cliff (smallest unit) + +**Authorization Required:** +- `sender` must authorize the transaction +- `sender` must have approved the contract to spend the **sum** of all `total_amount` values for each token used across the batch (separate approvals per distinct token) + +**Preconditions:** +- `streams` must not be empty; returns `StreamError::BatchEmpty` otherwise +- `streams.len() <= 20`; returns `StreamError::BatchSizeExceeded` otherwise +- Each stream entry must satisfy the same per-stream validation rules as `create_stream` + +**Returns:** +- `Ok(Vec)` - Stream IDs in the same order as the input `streams` vector +- `Err(StreamError)` - First validation error encountered (no streams are created) + +**Errors:** +- `BatchEmpty` (12) — `streams` vector is empty +- `BatchSizeExceeded` (11) — more than 20 streams in the batch +- `InvalidAmount` (1), `InvalidTimeRange` (2), `InvalidCliff` (3), `SelfStream` (4) — per-stream validation failures + +**Example - JavaScript:** +```typescript +const streams = [ + { + recipient: new Address('GRECIPIENT1...').toScVal(), + token: new Address(tokenAddress).toScVal(), + total_amount: nativeToScVal(1000_0000000n, { type: 'i128' }), + start_time: nativeToScVal(now, { type: 'u64' }), + end_time: nativeToScVal(now + 86400 * 30, { type: 'u64' }), + cliff_time: nativeToScVal(now, { type: 'u64' }), + cliff_amount: nativeToScVal(0n, { type: 'i128' }), + }, + { + recipient: new Address('GRECIPIENT2...').toScVal(), + // ...same fields... + }, +]; + +const streamIds = await invoke('create_streams_batch', [ + new Address(senderAddress).toScVal(), + nativeToScVal(streams, { type: 'vec' }), +]); +// Returns [1n, 2n, ...] +``` + +--- + ### withdraw Withdraws available funds from a stream to the recipient's account. @@ -239,7 +508,7 @@ const result = await invoke('top_up', [ ### bump_stream -Extends the stream's time-to-live in storage (required every ~6 months). +Extends the stream's time-to-live in storage. Anyone may call this — no authorization required. Useful for keeping a long-running stream accessible without modifying its data. **Signature:** ```rust @@ -250,7 +519,7 @@ pub fn bump_stream(stream_id: u64) -> Result<(), StreamError> - `stream_id: u64` - Stream ID to bump **Authorization Required:** -- The stream sender must authorize the transaction +- None — any caller may extend a stream's TTL **Preconditions:** - Stream must exist @@ -259,6 +528,11 @@ pub fn bump_stream(stream_id: u64) -> Result<(), StreamError> - `Ok(())` on success - `Err(StreamError)` on failure +**Behavior:** +- Extends the `Stream(id)` persistent storage entry TTL to ~30 days (`PERSISTENT_TTL_LEDGERS`) +- Emits a `StreamBumpedEvent` +- Does **not** modify any stream data + **Example - JavaScript:** ```typescript const streamId = 1n; @@ -270,6 +544,46 @@ const result = await invoke('bump_stream', [ --- +### cleanup_stream + +Permanently removes all on-chain data for a completed or cancelled stream, reclaiming storage. Either the sender or recipient may call this. + +**Signature:** +```rust +pub fn cleanup_stream(env: Env, caller: Address, stream_id: u64) +``` + +**Parameters:** +- `caller: Address` - The account initiating cleanup (must be sender or recipient) +- `stream_id: u64` - Stream ID to remove + +**Authorization Required:** +- `caller` must authorize the transaction + +**Preconditions:** +- Stream must exist +- `caller` must be either `stream.sender` or `stream.recipient`; panics with `"only sender or recipient may clean up a stream"` otherwise +- Stream must be cancelled **or** fully drained after `end_time`; panics with `"stream must be cancelled or fully completed before cleanup"` otherwise + +**Returns:** +- `()` on success + +**Behavior:** +- Removes the stream from all active and archive index lists for both sender and recipient +- Deletes the `Stream(id)` persistent storage entry +- Deletes the `StreamMetadata(id)` entry if present +- Deletes the `Delegate(id)` entry if present + +**Example - JavaScript:** +```typescript +await invoke('cleanup_stream', [ + new Address(senderAddress).toScVal(), + nativeToScVal(streamId, { type: 'u64' }), +]); +``` + +--- + ## Query Functions ### get_stream @@ -406,6 +720,299 @@ pub fn get_received_stream_count(recipient: Address) -> u32 --- +### get_archived_sent_streams + +Returns paginated stream IDs for completed or cancelled streams where `address` is the sender. Streams move to the archive index when they are cancelled or fully drained. + +**Signature:** +```rust +pub fn get_archived_sent_streams( + env: Env, + address: Address, + offset: u32, + limit: u32, +) -> Vec +``` + +**Parameters:** +- `address: Address` - Sender address to look up +- `offset: u32` - Zero-based pagination offset +- `limit: u32` - Maximum number of IDs to return + +**Returns:** +- `Vec` - Archived stream IDs (may be empty if none exist or offset is out of range) + +**Example - JavaScript:** +```typescript +const archivedIds = await query('get_archived_sent_streams', [ + new Address(senderAddress).toScVal(), + nativeToScVal(0, { type: 'u32' }), + nativeToScVal(100, { type: 'u32' }), +]); +``` + +--- + +### get_archived_received_streams + +Returns paginated stream IDs for completed or cancelled streams where `address` is the recipient. + +**Signature:** +```rust +pub fn get_archived_received_streams( + env: Env, + address: Address, + offset: u32, + limit: u32, +) -> Vec +``` + +**Parameters:** +- `address: Address` - Recipient address to look up +- `offset: u32` - Zero-based pagination offset +- `limit: u32` - Maximum number of IDs to return + +**Returns:** +- `Vec` - Archived stream IDs (may be empty if none exist or offset is out of range) + +**Example - JavaScript:** +```typescript +const archivedIds = await query('get_archived_received_streams', [ + new Address(recipientAddress).toScVal(), + nativeToScVal(0, { type: 'u32' }), + nativeToScVal(100, { type: 'u32' }), +]); +``` + +--- + +## Metadata Functions + +### update_stream_metadata + +Attaches or replaces human-readable metadata on a stream. Only the sender can update. + +**Signature:** +```rust +pub fn update_stream_metadata( + env: Env, + stream_id: u64, + metadata: StreamMetadata, +) -> Result<(), StreamError> +``` + +**Parameters:** +- `stream_id: u64` - Stream ID to update +- `metadata: StreamMetadata` - Metadata to store, containing: + - `name: String` - Short display name for the stream + - `category: String` - Freeform category label (e.g. `"payroll"`, `"vesting"`) + - `memo: String` - Longer freeform note + +**Authorization Required:** +- `stream.sender` must authorize the transaction + +**Preconditions:** +- Stream must exist + +**Returns:** +- `Ok(())` on success +- `Err(StreamError::StreamNotFound)` if the stream does not exist + +**Behavior:** +- Stores the `StreamMetadata` struct in persistent storage under `StreamMetadata(stream_id)` +- Bumps the metadata entry TTL to ~30 days + +**Example - JavaScript:** +```typescript +await invoke('update_stream_metadata', [ + nativeToScVal(streamId, { type: 'u64' }), + nativeToScVal( + { name: 'Alice Salary', category: 'payroll', memo: 'Q3 2026' }, + { type: 'map' }, + ), +]); +``` + +--- + +### get_stream_metadata + +Returns the metadata for a stream, if any has been set. + +**Signature:** +```rust +pub fn get_stream_metadata(env: Env, stream_id: u64) -> Option +``` + +**Parameters:** +- `stream_id: u64` - Stream ID to query + +**Returns:** +- `Some(StreamMetadata)` if metadata exists for this stream +- `None` if no metadata has been set + +**Example - JavaScript:** +```typescript +const metadata = await query('get_stream_metadata', [ + nativeToScVal(streamId, { type: 'u64' }), +]); +// { name: 'Alice Salary', category: 'payroll', memo: 'Q3 2026' } or null +``` + +--- + +## Delegation Functions + +### set_delegate + +Registers a delegate address that can authorize `withdraw` calls on behalf of the stream's recipient. Useful for automating withdrawals via a bot or smart contract without granting full account control. + +**Signature:** +```rust +pub fn set_delegate( + env: Env, + stream_id: u64, + delegate: Address, +) -> Result<(), StreamError> +``` + +**Parameters:** +- `stream_id: u64` - Stream ID to configure +- `delegate: Address` - Address that will be permitted to call `withdraw` + +**Authorization Required:** +- `stream.recipient` must authorize the transaction + +**Preconditions:** +- Stream must exist + +**Returns:** +- `Ok(())` on success +- `Err(StreamError::StreamNotFound)` if the stream does not exist + +**Behavior:** +- Stores `delegate` in persistent storage under `Delegate(stream_id)` +- Bumps the delegate entry TTL to ~30 days +- The delegate is cleared automatically if the stream is transferred via `transfer_stream` + +**Example - JavaScript:** +```typescript +await invoke('set_delegate', [ + nativeToScVal(streamId, { type: 'u64' }), + new Address(delegateAddress).toScVal(), +]); +``` + +--- + +### remove_delegate + +Removes the registered delegate for a stream. After this call, only the recipient can authorize withdrawals. + +**Signature:** +```rust +pub fn remove_delegate(env: Env, stream_id: u64) -> Result<(), StreamError> +``` + +**Parameters:** +- `stream_id: u64` - Stream ID to update + +**Authorization Required:** +- `stream.recipient` must authorize the transaction + +**Preconditions:** +- Stream must exist + +**Returns:** +- `Ok(())` on success +- `Err(StreamError::StreamNotFound)` if the stream does not exist + +**Behavior:** +- Deletes the `Delegate(stream_id)` entry from persistent storage (no-op if no delegate was set) + +**Example - JavaScript:** +```typescript +await invoke('remove_delegate', [ + nativeToScVal(streamId, { type: 'u64' }), +]); +``` + +--- + +### get_delegate + +Returns the delegate address for a stream, if one has been set. + +**Signature:** +```rust +pub fn get_delegate(env: Env, stream_id: u64) -> Option
+``` + +**Parameters:** +- `stream_id: u64` - Stream ID to query + +**Returns:** +- `Some(Address)` if a delegate is registered +- `None` if no delegate has been set + +**Example - JavaScript:** +```typescript +const delegate = await query('get_delegate', [ + nativeToScVal(streamId, { type: 'u64' }), +]); +// 'GDELEGATE...' or null +``` + +--- + +## Contract Info Functions + +### version + +Returns the contract's version number as a `u32`. Useful for on-chain version checks after an upgrade. + +**Signature:** +```rust +pub fn version(_env: Env) -> u32 +``` + +**Parameters:** +- None + +**Returns:** +- `u32` - Current contract version (currently `1`) + +**Example - JavaScript:** +```typescript +const v = await query('version', []); +// 1 +``` + +--- + +### name + +Returns the human-readable contract name as a Soroban `String`. + +**Signature:** +```rust +pub fn name(env: Env) -> soroban_sdk::String +``` + +**Parameters:** +- None + +**Returns:** +- `String` - Contract name (currently `"FlowStar Streaming"`) + +**Example - JavaScript:** +```typescript +const contractName = await query('name', []); +// 'FlowStar Streaming' +``` + +--- + ## Authorization All write operations require transaction authorization from a specific account: @@ -478,6 +1085,8 @@ interface Stream { cliff_amount: i128; amount_per_second: i128; cancelled: boolean; + linear_amount: i128; + duration: i128; } interface StreamParams { @@ -489,6 +1098,23 @@ interface StreamParams { cliff_time: u64; cliff_amount: i128; } + +// Used by create_streams_batch; same fields as StreamParams but a distinct type +interface CreateStreamInput { + recipient: Address; + token: Address; + total_amount: i128; + start_time: u64; + end_time: u64; + cliff_time: u64; + cliff_amount: i128; +} + +interface StreamMetadata { + name: string; + category: string; + memo: string; +} ``` --- From 3607803c0119a14cfabbb17df954b2207a7801ed Mon Sep 17 00:00:00 2001 From: joshfatoye0011-bit Date: Tue, 25 Aug 2026 06:26:36 -0700 Subject: [PATCH 3/3] chore: move IMPLEMENTATION_SUMMARY.md to docs/archive Resolves confusion for newcomers by relocating the internal AI-agent work log out of the repo root and into docs/archive, where it won't be mistaken for authoritative project documentation. Closes #516 --- .../archive/IMPLEMENTATION_SUMMARY.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename IMPLEMENTATION_SUMMARY.md => docs/archive/IMPLEMENTATION_SUMMARY.md (100%) diff --git a/IMPLEMENTATION_SUMMARY.md b/docs/archive/IMPLEMENTATION_SUMMARY.md similarity index 100% rename from IMPLEMENTATION_SUMMARY.md rename to docs/archive/IMPLEMENTATION_SUMMARY.md