You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Path payment slippage is currently enforced client-side in the backend (SLIPPAGE_BPS = 500 hardcoded in contributions.js:14). The backend fetches a quote, calculates sendMax, and submits — but there is no on-chain guarantee the slippage rule is respected. A malicious or buggy backend could submit a transaction with no slippage protection.
A Soroban contract can enforce contribution routing rules on-chain, making the contribution flow trustless.
Backend indexes ContributionRouted events instead of raw path_payment_strict_receive operations
Why atomic fee split matters
Currently the fee would need to be a separate transaction or operation. The router contract does it in one atomic call — the campaign either receives the full net amount or nothing. No partial states.
Acceptance criteria
Slippage ceiling enforced at contract level, not backend
Fee split to platform wallet atomic with the contribution
ContributionRouted event emitted and indexable
Existing contribution UI works unchanged
Path payment quote still fetched from Horizon (off-chain is fine for quotes)
Contract rejects transactions that exceed slippage threshold
Problem
Path payment slippage is currently enforced client-side in the backend (
SLIPPAGE_BPS = 500hardcoded incontributions.js:14). The backend fetches a quote, calculatessendMax, and submits — but there is no on-chain guarantee the slippage rule is respected. A malicious or buggy backend could submit a transaction with no slippage protection.A Soroban contract can enforce contribution routing rules on-chain, making the contribution flow trustless.
What needs to happen
Soroban Contract (
contracts/soroban/router/)Write a Rust Soroban contract that:
send_asset,send_max,dest_asset,dest_amount,path,campaign_wallet,platform_wallet,fee_bpssend_maxdoes not exceeddest_amount * (1 + max_slippage_bps / 10000)path_payment_strict_receiveon-chain(1 - fee_bps / 10000)to campaign wallet,fee_bps / 10000to platform wallet — atomically in one callContributionRoutedevent with: sender, campaign, dest_amount, source_amount, fee_amount, pathBackend changes
prepareSignedContributionPathPayment()instellarService.jswith a contract invocationContributionRoutedevents instead of rawpath_payment_strict_receiveoperationsWhy atomic fee split matters
Currently the fee would need to be a separate transaction or operation. The router contract does it in one atomic call — the campaign either receives the full net amount or nothing. No partial states.
Acceptance criteria
ContributionRoutedevent emitted and indexableDependencies