diff --git a/sdk/README.md b/sdk/README.md
index 3fb434c..cd1fd1b 100644
--- a/sdk/README.md
+++ b/sdk/README.md
@@ -199,6 +199,162 @@ const tx = buildUnsignedTransaction({
---
+## Example 5 — Redeem shares at maturity
+
+Once the vault reaches the `Matured` state the operator calls `mature_vault`, after which investors can redeem their full principal plus any unclaimed yield in a single transaction.
+
+```typescript
+import { Networks, rpc } from "@stellar/stellar-sdk";
+import {
+ SingleRwaVaultClient,
+ buildUnsignedTransaction,
+} from "@stellaryield/sdk";
+
+const server = new rpc.Server("https://soroban-testnet.stellar.org");
+const user = "G...";
+const vaultId = "C...";
+
+const account = await server.getAccount(user);
+const vault = new SingleRwaVaultClient(vaultId);
+
+// Check current share balance
+const sharesOp = vault.balance(user);
+// simulate to read balance, then redeem the full amount
+const shares = 5_000_000n; // stroops of share tokens
+
+const op = vault.redeemAtMaturity(user, shares, user, user);
+const tx = buildUnsignedTransaction({
+ account,
+ networkPassphrase: Networks.TESTNET,
+ operation: op,
+});
+
+const sim = await server.simulateTransaction(tx);
+if (rpc.Api.isSimulationError(sim)) throw new Error(sim.error);
+// assemble with sorobanData from sim, sign, submit
+```
+
+**Early redemption (while vault is Active):**
+
+```typescript
+// Request an early exit — shares are escrowed until the operator processes it
+const requestOp = vault.requestEarlyRedemption(user, shares);
+const requestTx = buildUnsignedTransaction({
+ account,
+ networkPassphrase: Networks.TESTNET,
+ operation: requestOp,
+});
+const requestSim = await server.simulateTransaction(requestTx);
+// ... sign & submit; the response includes the queue position hint in events
+```
+
+---
+
+## Example 6 — List vaults from the factory
+
+Use the `VaultFactoryClient` to discover all deployed vaults or filter by asset.
+
+```typescript
+import { Networks, rpc } from "@stellar/stellar-sdk";
+import {
+ VaultFactoryClient,
+ simulateInvocation,
+} from "@stellaryield/sdk";
+
+const server = new rpc.Server("https://soroban-testnet.stellar.org");
+const factoryId = "C..."; // VaultFactory contract address
+const factory = new VaultFactoryClient(factoryId);
+
+const account = await server.getAccount("G...");
+
+// All registered vaults (returns Vec
)
+const allVaults = await simulateInvocation({
+ server,
+ account,
+ networkPassphrase: Networks.TESTNET,
+ contractId: factory.contractId,
+ method: "get_single_rwa_vaults",
+ args: [],
+});
+console.log("Vault addresses:", allVaults);
+
+// Paginated list — useful for large registries
+const page = factory.getVaultsPaginated(/* offset */ 0, /* limit */ 10);
+const pageTx = buildUnsignedTransaction({
+ account,
+ networkPassphrase: Networks.TESTNET,
+ operation: page,
+});
+const pageSim = await server.simulateTransaction(pageTx);
+// parse pageSim.result?.retval for the Vec value
+
+// Vaults backed by a specific asset (e.g. USDC)
+const usdcVaultsOp = factory.getVaultsByAsset("C...USDC...");
+```
+
+---
+
+## Example 7 — Status and config checks
+
+Read vault state, configuration, and a user's position without any on-chain writes.
+
+```typescript
+import { Networks, rpc } from "@stellar/stellar-sdk";
+import {
+ SingleRwaVaultClient,
+ simulateInvocation,
+} from "@stellaryield/sdk";
+
+const server = new rpc.Server("https://soroban-testnet.stellar.org");
+const user = "G...";
+const vaultId = "C...";
+const vault = new SingleRwaVaultClient(vaultId);
+const account = await server.getAccount(user);
+
+// One-call vault overview (state, total assets, epoch, maturity date …)
+const overview = await simulateInvocation({
+ server,
+ account,
+ networkPassphrase: Networks.TESTNET,
+ contractId: vault.contractId,
+ method: "get_vault_overview",
+ args: [],
+});
+console.log("Vault overview:", overview);
+
+// Consolidated config snapshot — cache and refresh only on relevant events
+// (dep_lim, fee_set, zkme_upd, coop_upd)
+const config = await simulateInvocation({
+ server,
+ account,
+ networkPassphrase: Networks.TESTNET,
+ contractId: vault.contractId,
+ method: "get_config_snapshot",
+ args: [],
+});
+console.log("Fee bps:", config.early_redemption_fee_bps);
+console.log("Min deposit:", config.min_deposit);
+
+// Per-user summary (balance, pending yield, KYC status …)
+const userOp = vault.invoke("get_user_overview", /* scAddress(user) */ );
+// or use simulateInvocation with method "get_user_overview"
+
+// KYC check before attempting a deposit
+const kyc = await simulateInvocation({
+ server,
+ account,
+ networkPassphrase: Networks.TESTNET,
+ contractId: vault.contractId,
+ method: "is_kyc_verified",
+ args: [/* scAddress(user) */],
+});
+if (!kyc) {
+ console.warn("User has not passed KYC — deposit will be rejected.");
+}
+```
+
+---
+
## Read-only simulation (preview / views)
```typescript
diff --git a/soroban-contracts/contracts/single_rwa_vault/src/events.rs b/soroban-contracts/contracts/single_rwa_vault/src/events.rs
index cfbff75..b1487f8 100644
--- a/soroban-contracts/contracts/single_rwa_vault/src/events.rs
+++ b/soroban-contracts/contracts/single_rwa_vault/src/events.rs
@@ -172,7 +172,6 @@ pub fn emit_redeem_at_maturity(
/// Which early-redemption user event to emit (same topics/data layout for all variants).
#[derive(Copy, Clone)]
enum EarlyRedemptionUserEventKind {
- Requested,
Processed,
Cancelled,
}
@@ -196,10 +195,6 @@ fn publish_early_redemption_user_event(
amount: i128,
) {
match kind {
- EarlyRedemptionUserEventKind::Requested => {
- e.events()
- .publish((symbol_short!("erq_req"), user), (request_id, amount));
- }
EarlyRedemptionUserEventKind::Processed => {
e.events()
.publish((symbol_short!("erq_done"), user), (request_id, amount));
@@ -238,13 +233,21 @@ fn publish_early_redemption_non_success_event_v2(
}
/// Emitted by `request_early_redemption`.
-pub fn emit_early_redemption_requested(e: &Env, user: Address, request_id: u32, shares: i128) {
- publish_early_redemption_user_event(
- e,
- EarlyRedemptionUserEventKind::Requested,
- user,
- request_id,
- shares,
+///
+/// `queue_position` is an approximate 1-based position in the pending queue at
+/// the moment of submission (i.e. how many unprocessed requests preceded this
+/// one, plus one). It is computed with a best-effort scan and may not reflect
+/// concurrent submissions; integrators should treat it as a UI hint only.
+pub fn emit_early_redemption_requested(
+ e: &Env,
+ user: Address,
+ request_id: u32,
+ shares: i128,
+ queue_position: u32,
+) {
+ e.events().publish(
+ (symbol_short!("erq_req"), user),
+ (request_id, shares, queue_position),
);
}
diff --git a/soroban-contracts/contracts/single_rwa_vault/src/lib.rs b/soroban-contracts/contracts/single_rwa_vault/src/lib.rs
index 61c1e6f..711246d 100644
--- a/soroban-contracts/contracts/single_rwa_vault/src/lib.rs
+++ b/soroban-contracts/contracts/single_rwa_vault/src/lib.rs
@@ -2532,7 +2532,18 @@ impl SingleRWAVault {
put_escrowed_shares(e, &caller, escrowed);
bump_balance(e, &caller);
- let id = get_redemption_counter(e) + 1;
+ // Compute approximate 1-based queue position before inserting the new
+ // request — count unprocessed entries that precede it.
+ let prev_total = get_redemption_counter(e);
+ let mut pending_before: u32 = 0;
+ for i in 1..=prev_total {
+ if !get_redemption_request(e, i).processed {
+ pending_before += 1;
+ }
+ }
+ let queue_position = pending_before + 1;
+
+ let id = prev_total + 1;
put_redemption_counter(e, id);
let user = caller.clone();
put_redemption_request(
@@ -2547,7 +2558,7 @@ impl SingleRWAVault {
},
);
- emit_early_redemption_requested(e, user, id, shares);
+ emit_early_redemption_requested(e, user, id, shares, queue_position);
bump_instance(e);
id
}
diff --git a/soroban-contracts/contracts/single_rwa_vault/src/types.rs b/soroban-contracts/contracts/single_rwa_vault/src/types.rs
index b952edd..323dfe8 100644
--- a/soroban-contracts/contracts/single_rwa_vault/src/types.rs
+++ b/soroban-contracts/contracts/single_rwa_vault/src/types.rs
@@ -616,6 +616,51 @@ pub struct EmergencyProposal {
pub executed: bool,
}
+// ─────────────────────────────────────────────────────────────────────────────
+// Config snapshot (issue-265)
+// ─────────────────────────────────────────────────────────────────────────────
+
+/// Immutable-ish consolidated view of frequently-read vault configuration
+/// parameters. Integrators can cache this struct and only refresh it on
+/// relevant admin events rather than issuing separate RPC calls per field.
+#[contracttype]
+#[derive(Clone, Debug)]
+pub struct ConfigSnapshot {
+ /// Early redemption fee in basis points (0–1_000; divide by 10_000 for %).
+ pub early_redemption_fee_bps: u32,
+ /// Minimum deposit amount in underlying asset units (0 = no minimum).
+ pub min_deposit: i128,
+ /// Maximum deposit per user in underlying asset units (0 = uncapped).
+ pub max_deposit_per_user: i128,
+ /// Address of the zkMe KYC verifier contract.
+ pub zkme_verifier: Address,
+ /// Cooperator address used when calling the zkMe verifier.
+ pub cooperator: Address,
+}
+
+// ─────────────────────────────────────────────────────────────────────────────
+// Pending redemption pagination (issue-282)
+// ─────────────────────────────────────────────────────────────────────────────
+
+/// A single entry in the paginated pending-redemption list returned by
+/// `list_pending_redemptions`. Contains only the fields useful for operator
+/// review dashboards; the full `RedemptionRequest` is available via
+/// `redemption_request(id)`.
+#[contracttype]
+#[derive(Clone, Debug)]
+pub struct PendingRedemptionEntry {
+ /// Monotonically increasing redemption ID (1-based).
+ pub id: u32,
+ /// Address that submitted the redemption request.
+ pub user: Address,
+ /// Number of shares locked in escrow for this request.
+ pub shares: i128,
+ /// Asset value snapshotted at request time (before fee).
+ pub locked_asset_value: i128,
+ /// Unix timestamp when the request was submitted.
+ pub request_time: u64,
+}
+
// ─────────────────────────────────────────────────────────────────────────────
// Interface IDs for supports_interface (#299)
// ─────────────────────────────────────────────────────────────────────────────