Dogukan Sar

Bounty reviewTokenized equitiesSeptember 2026

← Bugbounties

Spout Finance beta

A real Devnet order exposed a gap between placing an order and owning shares. I also found conflicting live fee and risk disclosures.

Solana Devnet · NVDA orders · Covered callsBounty brief

Spout Finance white wordmark

The short version

I tested Spout’s authenticated beta on 23 September 2026, from 18:37 to 20:06 UTC. I placed a real Devnet NVDA order, followed it through Solana and the app, and checked the public Terms against the product docs. Three findings matter most:

  • The app said “You own 0.02 NVDA” as soon as the order transaction finalized. Portfolio still showed an open order and no holding. Borrow showed zero NVDA shares.
  • A cancellation request for an older order finalized onchain, but the app showed no cancellation state or refund progress. The wallet balance was unchanged 27 minutes later.
  • The live Terms and docs publish different stock transaction, withdrawal and liquidation fees, as well as different Junior yield targets and loss waterfalls.

This is a review of the current beta. The onchain evidence proves order placement and a USDC transfer. It does not prove a broker fill, permanent fund loss, or a production contract exploit. The Superteam brief asks for testnet transactions, product feedback and analysis of the borrowing model; those are the boundaries of this report.

Why the handoff matters

Spout describes a route from tokenized US stocks to a stablecoin loan at up to 50% loan-to-value and 0% recurring interest. Borrower shares enter a covered-call cycle. Options premium funds lender yield. The proposition is useful to a stock holder who wants liquidity without selling at the time of borrowing.

The beta makes the first step look easy: choose a stock, enter dollars, sign. But a placed order is not yet a stock holding. Until shares appear, there is nothing to post as collateral. That distinction was lost in the confirmation I saw.

The order was called a purchase before shares arrived

I bought $5 of NVDA with the connected Devnet wallet. The confirmation modal said “Your purchase has been confirmed,” “You own 0.02 NVDA,” and “Bought at $225.42.” The USDC balance fell from $18 to $13.

The observable sequence was different from that message:

  • 19:25 UTC: The finalized Solana transaction invoked PlaceBuyOrder. It transferred 5 Devnet USDC from my wallet to Spout’s receiving account and created an order account. It did not deliver NVDA in that transaction.
  • 19:27 UTC: A separate transaction moved those 5 USDC to another token account. That movement alone does not establish whether the broker executed the trade.
  • 19:33–19:44 UTC: Portfolio listed the $5 order under Open Orders with the tooltip “Not mirrored by the backend yet.” It showed no holdings, positions or transaction history. Borrow showed 0 NVDA shares and $0.00 available to borrow.
  • 20:06 UTC: The orders API still returned both this order and an earlier $2 NVDA order. The status endpoint still returned an empty array.
Portfolio with two open NVDA orders and no holdings
Portfolio showed two open NVDA orders and no holding. Click to enlarge.

An earlier $2 NVDA order had the same visible state more than an hour after placement. I cannot see the offchain broker ledger, so I cannot say why either order remained open.

For a user, the effect is concrete: the app says “You own”, but a sale or collateral-backed borrow cannot follow. After PlaceBuyOrder, the confirmation should say “Order placed; $5 USDC reserved pending execution.” The app needs visible states for broker acknowledgement, partial fill, fill, token delivery, cancellation and refund. Save “You own” and “Bought at” for a completed fill.

Cancellation showed no progress

At 19:39 UTC, I requested cancellation of the older $2 order. The finalized transaction logged RequestCancelOrder. It did not transfer USDC or change the order account. The app briefly said “Cancel requested — the backend processes the refund.”

Five minutes later, Portfolio still showed the order as open, with an active Cancel button. At 20:06 UTC, 27 minutes after the request, my wallet still held $13 USDC and the status API still returned no result. The refund might arrive later; I cannot call it permanently stuck. The immediate defect is that the user has no persistent cancellation requested state, expected timing, or refund transaction to follow.

A read-only simulation showed that the onchain instruction would accept a second cancellation request in the then-current state. I did not submit one, and I do not know how the backend handles duplicates. The interface should disable repeat requests while one is pending and explain the eventual outcome.

The live financial disclosures disagree

I compared the live Terms of Service with the Fees and Lending Tranches pages. The numbers differ where a trader or lender would expect one answer:

Measure Terms of Service Product docs
Stock mint or redeem / app buy or sell 0.25% 0.20%
Withdrawal 1% on borrowing and lending withdrawals 0.20% on stablecoins leaving the lending pool
Liquidation fee 7.5%–20% by asset About 4%–12.5% by asset
Junior lender target About 24%–27% net About 32% APY in normal conditions

The withdrawal rates differ by $80 on $10,000; the stock transaction rates differ by $5 on $10,000. These compare published figures. I could not measure a charged fee because the stock order did not settle.

The loss waterfall differs too. Terms section 9 places Protocol Treasury between Junior and Senior after the Insurance Fund. The dedicated waterfall page omits Treasury and goes directly from Junior to Senior. The extra layer may be meaningful protection, but the two descriptions are not the same.

The product needs one canonical table for fees, yield targets and loss order. Link it at the point of trade or deposit, then reconcile the Terms and explanatory pages.

Other beta friction

The connected-wallet Trade form still shows a 1.0×–2.0× leverage slider and explains that 2.0× uses half cash and half borrowing. For this wallet the slider was disabled and stayed at 1.0×. The deployed client says “Leverage is only available in demo mode.” Label the control as a demo preview, or hide it in a real-wallet order. This says nothing conclusive about the separate Borrow flow, which I could not reach without settled shares.

The onboarding tour also needs a guard. I reproduced a page-level crash twice by pressing Next rapidly through its final step: the tour index advanced beyond its 11 steps, then the page threw a missing-title error. Clamp the final step and make the final action safe to repeat. The same tour asks users to compare Senior and Junior pools on Earn, while that route currently says “Earn is coming soon.” The bounty brief already says lending will roll out later; the tour should say so too.

What the 0% loan costs economically

Spout’s Terms and fee page both describe a 0% loan interest rate. The borrower still gives the strategy something of value: upside above the covered-call strike during an options cycle. The assignment documentation says shares can be sold at the strike and repurchased through Auto-Roll.

A simple illustration shows the trade:

  • A share starts at $100 and a call is written at $110.
  • At expiry the share is $130, so the call is assigned at $110.
  • Relative to holding the share without a call, $20 of upside per share is forgone, before accounting for option premium and the benefit of the loan.

That is an economic cost rather than loan interest. A borrower should see this kind of example beside the 0% claim before locking collateral.

On the lender side, Spout’s tranche design uses roughly 85% Senior capital and 15% Junior capital. Its example of a $10 million pool earning $30,000 gross premium in a week leaves $24,000 after a 20% protocol fee. The 7% annualized Senior priority uses about $11,442 that week. Of the $12,558 left, 25% goes to Senior and 75% to Junior. That produces about $14,582 for Senior and $9,418 for Junior that week: roughly 8.9% and 32.6% annualized if repeated. It is a scenario, not a promised return. Weak premiums reduce the residual available to Junior first.

Two more decision points deserve the same clarity:

  • Exit liquidity. The homepage’s “no lockups” language sits beside withdrawal mechanics that give Junior lenders a minimum 45-day notice and make Senior instant exits depend on reserves, with a possible 0%–3% haircut.
  • Custody protection. The homepage names an SIPC-covered broker. Terms section 9.8 tells users to assume SIPC protection does not apply to their spAsset positions through this intermediated arrangement. That caveat belongs beside the custody claim.

The risk pages also name market, oracle, liquidity, smart-contract, stablecoin, regulatory and broker risks. An interactive preview of a fall below the liquidation threshold and a rally beyond the call strike would make those mechanics easier to judge.

What I would fix first

  1. Separate order placement from ownership. Reconcile order, broker and token states across Trade, Portfolio and Borrow.
  2. Make cancellation traceable. Keep the requested state visible and show the final refund or a useful failure.
  3. Publish one set of financial terms. Align the Terms, fee page, waterfall, yield targets and transaction preview.
  4. Show the strategy tradeoff. Put assignment, capped upside, exit timing and custody limits where users make decisions.
  5. Match the beta interface to what works. Mark leverage and Earn as previews, and stop the tour at its final step.

I verified order placement and the onchain USDC transfer. I could not verify an NVDA fill, equity custody, collateral deposit, loan draw or repayment, sale, or lender deposit. The Devnet transaction log refers to mocked KYC; that is Devnet behavior, not evidence of a production KYC bypass. The core borrowing flow needs another test once shares actually appear.

Evidence images

The screenshots below record what the beta and public documents showed during this review. Open any image to read it at full size.

Borrow page with zero NVDA shares and zero available loan
Borrow showed zero shares after the order.
Cancelled order still shown as open with an active Cancel button
The older order was still open after the request.
Spout Terms of Service fee section
Fees in the live Terms of Service.
Spout product documentation fee page
Different figures in the product docs.
Connected-wallet Trade form with disabled leverage slider
Leverage remained at 1.0× with a connected wallet.
Page-level error after advancing the onboarding tour too far
The tour rendered a page-level error.
Earn page displaying an Earn is coming soon message
Earn was still a coming-soon page.