Skip to content

feat(contracts): expert cooldown, spending limits, vouchers, webhook events (#240-#243)#245

Merged
Luluameh merged 2 commits into
LightForgeHub:mainfrom
testersweb0-bug:feat/expert-cooldown-spending-voucher-webhooks-240-243
May 31, 2026
Merged

feat(contracts): expert cooldown, spending limits, vouchers, webhook events (#240-#243)#245
Luluameh merged 2 commits into
LightForgeHub:mainfrom
testersweb0-bug:feat/expert-cooldown-spending-voucher-webhooks-240-243

Conversation

@testersweb0-bug
Copy link
Copy Markdown
Contributor

@testersweb0-bug testersweb0-bug commented May 31, 2026

Summary

Implements contract features for issues #240#243: expert dispute-loss cooldowns, seeker self-imposed spending caps, ed25519 session vouchers, and a standardized webhook event envelope for off-chain relay daemons.

  • Expert Cooldown After Dispute Loss #240 Expert cooldown: When arbitration awards more to the seeker than the expert, the expert enters a ledger-based cooldown stored in temporary storage. start_session rejects cooled-down experts with Error::ExpertOnCooldown. Admins configure duration via set_expert_cooldown_ledgers (default ≈ 7 days of ledgers).
  • Seeker Global Spending Limit #241 Spending limit: Seekers call set_spending_limit(max_per_session); the cap is stored per address in persistent storage and enforced during session start.
  • Off-Chain Signed Session Invitations #242 Session vouchers: Experts register an ed25519 signing key and pre-sign SessionVoucher { expert, rate_per_second, max_duration, expiry, nonce } off-chain. Seekers open sessions via start_session_with_voucher; nonces are consumed to prevent replay.
  • Webhook Notification Hooks (Off-Chain Relay) #243 Webhook events: All contract events now emit a unified (event_type, session_id, timestamp, payload) envelope under the webhook topic. Adds docs/WEBHOOK_RELAY.md and an integration test validating the envelope shape.

Files changed

File Purpose
contracts/src/disputes.rs Expert cooldown helpers (#240)
contracts/src/crypto.rs Voucher struct, message encoding, ed25519 verify (#242)
contracts/src/events.rs Standardized publish_event helper (#243)
contracts/src/lib.rs New entry points, shared session guards, event migration
contracts/src/errors.rs New error codes (#50#56)
docs/WEBHOOK_RELAY.md Relay daemon integration guide (#243)

New public API

Function Issue
set_expert_cooldown_ledgers / get_expert_cooldown_ledgers / get_expert_cooldown_until #240
set_spending_limit / clear_spending_limit / get_spending_limit #241
set_voucher_signing_key / get_voucher_signing_key / start_session_with_voucher #242

Test plan

  • cd contracts && cargo test
  • test_expert_cooldown_after_dispute_loss — cooldown set after seeker-favourable resolution
  • test_start_session_rejects_expert_on_cooldown — panics with #50
  • test_seeker_spending_limit_enforced — panics with #51 when deposit exceeds cap
  • test_start_session_with_voucher — valid signature starts session; replay rejected with #53
  • test_webhook_relay_emits_standard_envelope — session, dispute, and spending-limit events use webhook topic + 4-field tuple
  • Manual: resolve dispute with equal split (5000/5000 bps) and confirm expert is not cooled down
  • Manual: admin adjusts cooldown via set_expert_cooldown_ledgers and confirm new disputes honour the updated length

Breaking changes

  • Event schema: Indexers and frontends listening to legacy topic tuples (e.g. (session, started)) must migrate to the webhook envelope documented in docs/WEBHOOK_RELAY.md.

Closes #240
Closes #241
Closes #242
Closes #243

Summary by CodeRabbit

  • New Features

    • Added support for off-chain signed session invitations with expert-configurable rates, duration limits, and expiry controls.
    • Implemented expert cooldown mechanism activated after unfavorable dispute outcomes.
    • Introduced seeker spending limit controls per session.
    • Added standardized webhook event system for external service integrations.
  • Documentation

    • Added Webhook Relay Service guide for integrating protocol events with off-chain systems.

…events (LightForgeHub#240-LightForgeHub#243)

Add dispute-loss expert cooldowns, seeker spending caps, ed25519 session
vouchers, and a unified webhook event envelope with relay documentation.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 31, 2026

Review Change Stack

Warning

Review limit reached

@testersweb0-bug, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 49 minutes and 41 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 04f3a115-ab3a-4534-bc4f-688206e76172

📥 Commits

Reviewing files that changed from the base of the PR and between 2926f80 and 5e51f3d.

📒 Files selected for processing (1)
  • contracts/src/lib.rs
📝 Walkthrough

Walkthrough

The PR implements four linked features: expert cooldown after dispute loss, seeker spending-limit enforcement, off-chain voucher-signed session creation, and standardized webhook event infrastructure for off-chain relay services. It adds Ed25519-dalek for cryptographic signing, introduces new storage keys and error variants, and broadly refactors event emission across the contract to use a unified envelope schema.

Changes

Expert Cooldown, Spending Limits, and Voucher Session Flow

Layer / File(s) Summary
Cryptographic Foundation for Vouchers
contracts/Cargo.toml, contracts/src/crypto.rs, contracts/src/errors.rs
Adds ed25519-dalek v2.1.1 dependency. Introduces SessionVoucher struct with expert address, pricing, duration, expiry, and nonce; implements voucher_message to canonicalize the voucher into bytes, verify_voucher_signature using Ed25519 verification, and storage helpers (voucher_pubkey, set_voucher_pubkey, is_nonce_consumed, consume_nonce) for nonce replay protection. Adds error variants ExpertOnCooldown, SpendingLimitExceeded, VoucherExpired, VoucherNonceUsed, InvalidVoucherSignature, VoucherPubkeyNotSet, InvalidVoucher.
Expert Cooldown After Dispute Loss
contracts/src/disputes.rs, contracts/src/lib.rs
New disputes module adds configurable cooldown duration (default ~7 days), read/write accessors, cooldown-status queries (is_expert_on_cooldown, expert_cooldown_until), and apply_cooldown_if_expert_lost that conditionally applies cooldown when seeker award exceeds expert award. Integrates into lib.rs via new DataKey variants (ExpertCooldownLedgers, ExpertCooldownUntil), initialization in contract setup, and application in dispute resolution.
Spending Limit Enforcement and Voucher Session APIs
contracts/src/lib.rs
Introduces spending-limit public methods (set_spending_limit, clear_spending_limit, get_spending_limit), voucher signing-key methods (set_voucher_signing_key, get_voucher_signing_key), and start_session_with_voucher entry point that verifies expert-signed voucher, checks expiry/rate/duration constraints, checks and consumes nonce, and creates session. Refactors start_session to use internal helpers (enforce_seeker_spending_limit, assert_expert_can_accept_session, create_active_session) for centralized validation. Adds DataKey variants for spending limits, voucher pubkeys, and nonce consumption, and is_paused query. Tests cover cooldown rejection, spending-limit enforcement, voucher verification, and nonce replay protection.
Standardized Webhook Event Infrastructure
contracts/src/events.rs, contracts/src/lib.rs
New events module with publish_event generic function publishing standardized (event_type, session_id, timestamp, payload) envelope to webhook topic, and event_type module with 30+ symbol helpers for session lifecycle, disputes, limits, admin, platform, and other events. Refactors all state-changing entrypoints across lib.rs (admin config, staking, fixed-price, subscription, dispute, settlement, expert registration, DEX integration, badge/SBT, governance) to emit via typed publish_event; updates apply_burn to accept session_id and emit fee_burn event.

Webhook Relay Service Design

Layer / File(s) Summary
Webhook Relay Documentation
docs/WEBHOOK_RELAY.md
Specifies webhook relay service design: standardized event envelope under webhook topic with (event_type, session_id, timestamp, payload) four-tuple, relay subscription/SCVal decoding, event-type catalog grouped by session, dispute, seeker limit, and admin/platform categories, webhook POST JSON format, recommended relay behaviors (idempotency, retry, HMAC-SHA256 signing), integration test reference, and minimal relay pseudocode example.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 Vouchers signed, disputes cool,
Spending limits keep seekers safe and cool,
Webhooks relay the tale so true,
From on-chain to off-chain, the flow goes through!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main features added: expert cooldown, spending limits, vouchers, and webhook events, directly corresponding to the four linked issues.
Linked Issues check ✅ Passed All four linked issues are addressed: expert cooldown (#240) implemented with cooldown storage and guards, seeker spending limits (#241) with API and enforcement, vouchers (#242) with signature verification and nonce tracking, and webhook events (#243) with standardized envelope and documentation.
Out of Scope Changes check ✅ Passed All code changes align with the four linked issue objectives. Changes focus on cooldown, spending limits, vouchers, and webhook events without introducing unrelated features or refactoring.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
contracts/src/events.rs (1)

35-133: ⚡ Quick win

Add a uniqueness guard for event_type symbols.

With 30+ short symbols, an accidental duplicate will silently break relay routing. Add a unit test that asserts all event_type::*() values are unique.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/src/events.rs` around lines 35 - 133, Add a unit test that collects
the return values of every event symbol function (e.g., session_started,
session_paused, session_resumed, session_settled, session_finished,
session_refund, session_commit, session_reveal, session_voucher,
dispute_flagged, dispute_evidence, dispute_resolved, expert_cooldown,
spending_limit, admin_config, platform_stats, fee_burn, staking, subscription,
fixed_price, expert_profile, rating, swap, governance, insurance, upgrade,
integration, heartbeat, slashing, reverify, frozen, badge) into a HashSet and
assert the set length equals the number of functions to guarantee uniqueness;
implement the test in the same module (#[cfg(test)] mod tests) as a #[test]
named like all_event_type_symbols_unique, use .insert() or collect to build the
set and assert_eq!(set.len(), expected_count) so any duplicate symbol fails the
test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@contracts/src/crypto.rs`:
- Around line 33-42: The verify_voucher_signature function currently calls
env.crypto().ed25519_verify(...) and always returns Ok(()), which causes a trap
on bad signatures; change verify_voucher_signature to check the verification
result and return Err(Error::InvalidVoucherSignature.into()) on failure instead
of allowing a panic—specifically, in verify_voucher_signature (taking Env,
SessionVoucher, BytesN<32>, BytesN<64>) call
env.crypto().ed25519_verify(public_key, &message, signature), if it indicates
failure return the contract error Error::InvalidVoucherSignature, otherwise
return Ok(()) so start_session_with_voucher receives a proper contract error
rather than aborting.

In `@contracts/src/disputes.rs`:
- Around line 59-63: The cooldown entry written by apply_cooldown_if_expert_lost
uses env.storage().temporary().set(...) but never extends its TTL, so compute
the intended expiry (you already compute `until` via
`env.ledger().sequence().saturating_add(ledgers)`) and call the temporary
storage TTL extension API immediately after setting the key: invoke
`env.storage().temporary().extend_ttl(&DataKey::ExpertCooldownUntil(expert.clone()),
ledgers_or_calculated_ttl)` (using the same `ledgers`/computed duration that
`cooldown_ledgers` returned, or convert the `until` sequence into the
appropriate TTL units expected by `extend_ttl`) to ensure the temp entry lives
at least until the intended ledger sequence.

In `@contracts/src/lib.rs`:
- Around line 2072-2077: The code currently rejects vouchers when the expert's
live profile.rate_per_second differs from the voucher's signed
voucher.rate_per_second, which makes signed voucher rates unusable; remove the
equality check that compares profile.rate_per_second to voucher.rate_per_second
in the session-accept path (the block around
Self::assert_expert_can_accept_session and the following if) so the session rate
is taken from voucher.rate_per_second instead, and ensure profile is only used
for availability/cooldown/reputation checks (i.e., keep calling
assert_expert_can_accept_session to validate the expert but do not gate validity
on profile.rate_per_second).

In `@docs/WEBHOOK_RELAY.md`:
- Line 112: The current idempotency key uses the tuple (tx_hash, event_type,
session_id) which can collide for multiple events in the same transaction;
update the dedupe key to include the event index or full event ID (e.g., add
event_index or event_id) so each emitted event is uniquely identified when
building the dedupe key in the webhook relay deduplication logic (the place that
currently constructs the `(tx_hash, event_type, session_id)` key).
- Around line 40-41: The `sessSettl` payload tuple is currently ambiguous;
update the docs for the `sessSettl` / `settle_session` entry to enumerate the
exact tuple variants (or provide a versioned payload format) so decoders can
parse deterministically — explicitly list each allowed tuple shape (field order
and types) such as e.g. variant A: ( "label" | "expert_payout", timestamp:
number, amount: number, currency?: string ), variant B: ( "partial", amount:
number, remaining: number, ts: number ), etc., or add a top-level version token
like `v1` followed by a defined schema; ensure you update the `sessSettl` line
and nearby examples to show the exact field names/types and a sample JSON/tuple
for each variant so integrations can implement strict parsing in their relay
decoders.

---

Nitpick comments:
In `@contracts/src/events.rs`:
- Around line 35-133: Add a unit test that collects the return values of every
event symbol function (e.g., session_started, session_paused, session_resumed,
session_settled, session_finished, session_refund, session_commit,
session_reveal, session_voucher, dispute_flagged, dispute_evidence,
dispute_resolved, expert_cooldown, spending_limit, admin_config, platform_stats,
fee_burn, staking, subscription, fixed_price, expert_profile, rating, swap,
governance, insurance, upgrade, integration, heartbeat, slashing, reverify,
frozen, badge) into a HashSet and assert the set length equals the number of
functions to guarantee uniqueness; implement the test in the same module
(#[cfg(test)] mod tests) as a #[test] named like all_event_type_symbols_unique,
use .insert() or collect to build the set and assert_eq!(set.len(),
expected_count) so any duplicate symbol fails the test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7d33d3e3-8941-4c2e-afc3-8084f2484e8a

📥 Commits

Reviewing files that changed from the base of the PR and between f913a19 and 2926f80.

📒 Files selected for processing (7)
  • contracts/Cargo.toml
  • contracts/src/crypto.rs
  • contracts/src/disputes.rs
  • contracts/src/errors.rs
  • contracts/src/events.rs
  • contracts/src/lib.rs
  • docs/WEBHOOK_RELAY.md

Comment thread contracts/src/crypto.rs
Comment on lines +33 to +42
pub fn verify_voucher_signature(
env: &Env,
voucher: &SessionVoucher,
public_key: &BytesN<32>,
signature: &BytesN<64>,
) -> Result<(), Error> {
let message = voucher_message(env, voucher);
env.crypto()
.ed25519_verify(public_key, &message, signature);
Ok(())
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== List relevant files =="
ls -R contracts/src | sed -n '1,200p'

echo "== Show contracts/src/crypto.rs around lines 1-120 =="
nl -ba contracts/src/crypto.rs | sed -n '1,140p'

echo "== Locate verify_voucher_signature =="
rg -n "fn verify_voucher_signature" -S contracts/src

echo "== Show exact function body =="
python3 - <<'PY'
import re, pathlib
p = pathlib.Path("contracts/src/crypto.rs")
txt = p.read_text()
m = re.search(r"pub fn verify_voucher_signature[^{]*\{", txt)
if not m:
    print("Not found")
    raise SystemExit(1)
start = m.start()
# naive brace matching
i = m.end()
depth = 1
while i < len(txt) and depth:
    if txt[i] == '{': depth += 1
    elif txt[i] == '}': depth -= 1
    i += 1
body = txt[m.start():i]
print(body)
PY

echo "== Locate Error::InvalidVoucherSignature =="
rg -n "InvalidVoucherSignature" -S contracts/src

echo "== Show Error enum definition area =="
rg -n "enum Error|struct Error|pub enum Error" -S contracts/src
# print any files likely containing Error (heuristic: match "Error" and "InvalidVoucherSignature")
for f in $(rg -l "InvalidVoucherSignature" contracts/src); do
  echo "---- $f (context) ----"
  nl -ba "$f" | sed -n '1,220p'
done

Repository: LightForgeHub/SkillSphere-Dapp

Length of output: 336


🌐 Web query:

soroban_sdk Crypto ed25519_verify return type and behavior on failure

💡 Result:

In soroban_sdk, Crypto::ed25519_verify returns no value (it’s a unit function): pub fn ed25519_verify(&self, public_key: &BytesN<32>, message: &Bytes, signature: &BytesN<64>) [1]. On failure (i.e., if the signature verification fails), it panics; the docs explicitly list “Panics: If the signature verification fails.” [2][1]. Behaviorally, successful verification means the function returns normally and execution continues; the example usage shows that after calling env.crypto.ed25519_verify(...), execution proceeds only if the signature is valid. [1] Note: Soroban’s host function details are handled by the environment/crypto implementation; there have been discussions about using stricter Ed25519 verification in the host to avoid malleability issues, but that’s separate from the SDK-level API behavior (panic-on-failure). [3]

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Show contracts/src/crypto.rs (first 220 lines) =="
cat -n contracts/src/crypto.rs | sed -n '1,220p'

echo "== Search for InvalidVoucherSignature in repo =="
rg -n "InvalidVoucherSignature" -S contracts/src

echo "== Show contracts/src/errors.rs =="
cat -n contracts/src/errors.rs | sed -n '1,260p'

echo "== Show call sites of verify_voucher_signature =="
rg -n "verify_voucher_signature" -S contracts/src

echo "== Show contracts/src/lib.rs around voucher handling =="
rg -n "Voucher|SessionVoucher|voucher" -S contracts/src/lib.rs
cat -n contracts/src/lib.rs | sed -n '1,260p'

Repository: LightForgeHub/SkillSphere-Dapp

Length of output: 20977


🌐 Web query:

soroban_sdk try_ed25519_verify non-panicking ed25519_verify returns Result or Option

💡 Result:

soroban_sdk::EnvCrypto::try_ed25519_verify does not return Result/Option; it’s a “non-panicking” verification API whose success/failure is communicated via its return type. However, in the Soroban SDK’s current documented API, the non-panicking behavior question you asked is answered by comparing with env.crypto.ed25519_verify: - soroban_sdk::crypto::ed25519_verify panics if the signature verification fails (so it is not non-panicking). [1][2] From the authoritative docs we found in this research, the exact return type of try_ed25519_verify (whether it’s a Result, Option, or something else) is not shown in the sources we pulled; we only have explicit evidence that ed25519_verify panics on failure. [1][2] If you want, paste the try_ed25519_verify signature (or a link to its docs page) and I’ll confirm whether it returns Result or Option from the exact declaration.

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Show start_session_with_voucher implementation (around 2035-2125) =="
sed -n '2020,2130p' contracts/src/lib.rs | cat -n

echo "== Show test_start_session_with_voucher (around 6570-6690) =="
sed -n '6560,6700p' contracts/src/lib.rs | cat -n

Repository: LightForgeHub/SkillSphere-Dapp

Length of output: 10177


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Search for InvalidVoucherSignature usage (entire contracts/src) =="
rg -n "InvalidVoucherSignature" contracts/src

Repository: LightForgeHub/SkillSphere-Dapp

Length of output: 205


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Search for ed25519_verify or try_ed25519_verify usage =="
rg -n "ed25519_verify|try_ed25519_verify" contracts/src

Repository: LightForgeHub/SkillSphere-Dapp

Length of output: 219


Return InvalidVoucherSignature instead of trapping on bad signatures.

contracts/src/crypto.rs::verify_voucher_signature always returns Ok(()) and calls env.crypto().ed25519_verify(...), which panics on verification failure in the Soroban SDK—so start_session_with_voucher will abort the invocation instead of returning the contract error Error::InvalidVoucherSignature (the error is defined in contracts/src/errors.rs but never used/returned).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/src/crypto.rs` around lines 33 - 42, The verify_voucher_signature
function currently calls env.crypto().ed25519_verify(...) and always returns
Ok(()), which causes a trap on bad signatures; change verify_voucher_signature
to check the verification result and return
Err(Error::InvalidVoucherSignature.into()) on failure instead of allowing a
panic—specifically, in verify_voucher_signature (taking Env, SessionVoucher,
BytesN<32>, BytesN<64>) call env.crypto().ed25519_verify(public_key, &message,
signature), if it indicates failure return the contract error
Error::InvalidVoucherSignature, otherwise return Ok(()) so
start_session_with_voucher receives a proper contract error rather than
aborting.

Comment thread contracts/src/disputes.rs
Comment on lines +59 to +63
let ledgers = cooldown_ledgers(env);
let until = env.ledger().sequence().saturating_add(ledgers);
env.storage()
.temporary()
.set(&DataKey::ExpertCooldownUntil(expert.clone()), &until);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect the exact code around the reported lines
sed -n '1,120p' contracts/src/disputes.rs | nl -ba | sed -n '40,90p'

# 2) Locate the function(s) that write the cooldown key and see if extend_ttl is used anywhere
rg -n "ExpertCooldownUntil|temporary\\(\\)\\.set\\(|extend_ttl\\(" contracts/src/disputes.rs contracts/src -S || true

# 3) Find any usage of extend_ttl across the contracts module
rg -n "extend_ttl\\(" contracts/src -S || true

Repository: LightForgeHub/SkillSphere-Dapp

Length of output: 117


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the exact region around the reported lines (59-63)
awk 'NR>=45 && NR<=80 {printf "%d:%s\n", NR, $0}' contracts/src/disputes.rs

# Confirm whether extend_ttl is used anywhere in the file/module
rg -n "extend_ttl\\(" contracts/src/disputes.rs contracts/src || true

Repository: LightForgeHub/SkillSphere-Dapp

Length of output: 700


🌐 Web query:

soroban_sdk temporary().set ttl default extend_ttl min_temp_entry_ttl

💡 Result:

In the Soroban SDK, temporary storage entries are ephemeral and automatically deleted when their time-to-live (TTL) reaches zero [1][2]. Unlike persistent or instance storage, temporary entries are not archived [1][2]. The relevant methods for managing TTL in the Soroban SDK are provided by the env.storage().temporary() interface: 1. set(&self, key: &K, val: &V): This method stores a value under a given key [3]. Temporary entries have a default TTL upon creation, which is governed by the network's configuration rather than a direct parameter in the set method [1]. 2. extend_ttl(&self, key: &K, threshold: u32, extend_to: u32): This method allows you to extend the TTL of an existing temporary storage entry [3][4]. It functions as a conditional update: - The extension is performed only if the current TTL is below the specified threshold (in ledgers) [3][4]. - If the condition is met, the TTL is extended to the specified extend_to value (in ledgers) [3][4]. Developers should note that while temporary entries can have their TTLs extended, they are designed for ephemeral data [1][2]. Relying on TTL extensions for long-term storage is generally discouraged, as anyone can submit a TTL extension operation on any entry without authorization, and temporary entries do not provide the safety or permanence of persistent storage [5]. In test environments, developers can verify these behaviors using test utilities such as get_ttl and simulating ledger advancement [4][6].

Citations:


Extend the cooldown key TTL when writing it.

apply_cooldown_if_expert_lost stores DataKey::ExpertCooldownUntil(expert) with env.storage().temporary().set(...) and never calls env.storage().temporary().extend_ttl(...), so the entry’s lifetime is limited to the network-configured default temp TTL and may expire before until, re-enabling the expert early. Use extend_ttl to ensure the temp entry’s TTL covers the intended ledger sequence. https://developers.stellar.org/docs/build/guides/archival/test-ttl-extension

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/src/disputes.rs` around lines 59 - 63, The cooldown entry written
by apply_cooldown_if_expert_lost uses env.storage().temporary().set(...) but
never extends its TTL, so compute the intended expiry (you already compute
`until` via `env.ledger().sequence().saturating_add(ledgers)`) and call the
temporary storage TTL extension API immediately after setting the key: invoke
`env.storage().temporary().extend_ttl(&DataKey::ExpertCooldownUntil(expert.clone()),
ledgers_or_calculated_ttl)` (using the same `ledgers`/computed duration that
`cooldown_ledgers` returned, or convert the `until` sequence into the
appropriate TTL units expected by `extend_ttl`) to ensure the temp entry lives
at least until the intended ledger sequence.

Comment thread contracts/src/lib.rs
Comment on lines +2072 to +2077
let profile =
Self::assert_expert_can_accept_session(&env, voucher.expert.clone(), min_reputation)?;

if profile.rate_per_second != voucher.rate_per_second {
return Err(Error::InvalidVoucher);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't bind voucher validity to the mutable profile rate.

Line 2075 makes the signed rate_per_second redundant: any profile rate change invalidates previously issued vouchers, and one-off quoted rates can never be redeemed. Use the live profile only for availability/cooldown/reputation checks and take the session rate from the voucher itself.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/src/lib.rs` around lines 2072 - 2077, The code currently rejects
vouchers when the expert's live profile.rate_per_second differs from the
voucher's signed voucher.rate_per_second, which makes signed voucher rates
unusable; remove the equality check that compares profile.rate_per_second to
voucher.rate_per_second in the session-accept path (the block around
Self::assert_expert_can_accept_session and the following if) so the session rate
is taken from voucher.rate_per_second instead, and ensure profile is only used
for availability/cooldown/reputation checks (i.e., keep calling
assert_expert_can_accept_session to validate the expert but do not gate validity
on profile.rate_per_second).

Comment thread docs/WEBHOOK_RELAY.md
Comment on lines +40 to +41
| `sessSettl` | `settle_session`, partial withdraw | `(expert_payout_or_label, ts_or_amount, …)` |
| `sessFinsh` | `end_session` | `(claimable, remaining, finished_at)` |
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Make sessSettl payload schema explicit.

(expert_payout_or_label, ts_or_amount, …) is too ambiguous for stable relay decoders. Document exact tuple variants (or versioned payload formats) so integrations can parse deterministically.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/WEBHOOK_RELAY.md` around lines 40 - 41, The `sessSettl` payload tuple is
currently ambiguous; update the docs for the `sessSettl` / `settle_session`
entry to enumerate the exact tuple variants (or provide a versioned payload
format) so decoders can parse deterministically — explicitly list each allowed
tuple shape (field order and types) such as e.g. variant A: ( "label" |
"expert_payout", timestamp: number, amount: number, currency?: string ), variant
B: ( "partial", amount: number, remaining: number, ts: number ), etc., or add a
top-level version token like `v1` followed by a defined schema; ensure you
update the `sessSettl` line and nearby examples to show the exact field
names/types and a sample JSON/tuple for each variant so integrations can
implement strict parsing in their relay decoders.

Comment thread docs/WEBHOOK_RELAY.md

### Recommended relay behaviour

- **Idempotency**: dedupe on `(tx_hash, event_type, session_id)`.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Strengthen webhook idempotency key to prevent false dedupes.

(tx_hash, event_type, session_id) can collide within a single transaction if multiple matching events are emitted, causing dropped notifications. Include an event index/order (or full event ID) in the dedupe key.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/WEBHOOK_RELAY.md` at line 112, The current idempotency key uses the
tuple (tx_hash, event_type, session_id) which can collide for multiple events in
the same transaction; update the dedupe key to include the event index or full
event ID (e.g., add event_index or event_id) so each emitted event is uniquely
identified when building the dedupe key in the webhook relay deduplication logic
(the place that currently constructs the `(tx_hash, event_type, session_id)`
key).

@Luluameh Luluameh merged commit e6b0a33 into LightForgeHub:main May 31, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants