Skip to content

Add Flying Tulip ftUSD APY adapter#2631

Open
patcito wants to merge 2 commits into
DefiLlama:masterfrom
patcito:add-flying-tulip-ftusd
Open

Add Flying Tulip ftUSD APY adapter#2631
patcito wants to merge 2 commits into
DefiLlama:masterfrom
patcito:add-flying-tulip-ftusd

Conversation

@patcito
Copy link
Copy Markdown

@patcito patcito commented Apr 27, 2026

Summary

Adds `src/adaptors/flying-tulip-ftusd/index.js` exposing the sftUSD pool on Ethereum and Sonic. ftUSD is Flying Tulip's yield-bearing stablecoin and `sftUSD` is its staked form. Yield is paid in FT via the EpochRewardsVault: protocol fees from the ftUSD MintAndRedeem engine are used to buy FT on the open market and distributed to stakers per epoch. FT total supply is fixed so nothing is minted.

How it works

For each chain:

  1. Read `sftUSD.totalSupply()` and treat it as USD (ftUSD is dollar-pegged, the vault is a 1:1 ERC4626 with no pricePerShare growth, and ftUSD has 6 decimals).
  2. Pull the trailing 30 days of `EpochSettled(uint32 epochId, uint256 rewardAmount, uint256 stakeTime, uint256 rateRay)` events from the vault.
  3. Sum `rewardAmount` (FT, 18 decimals), price via `coins.llama.fi` against `coingecko:flying-tulip`.
  4. `apyReward = rewardUsd / tvlUsd * 365 / 30 * 100`.

`rewardTokens` is set to FT and `underlyingTokens` is set to ftUSD so the breakdown reads correctly in the UI.

Verified locally

```
Ethereum: tvlUsd=$496,429.90, apyReward=2.19%
Sonic: tvlUsd=$340,619.84, apyReward=2.44%
```

`tvlUsd` matches `api.flyingtulip.com/ftusd/staking` exactly. The Flying Tulip dashboard currently displays 0% APY because their indexer DB is behind the chain, but the on-chain events do contain non zero `rewardAmount` values. This adapter reads the events directly, which is the canonical approach for yield-server.

Companion PRs

  • DefiLlama/DefiLlama-Adapters #18864 adds the TVL module
  • DefiLlama/defillama-server (Add Flying Tulip ftUSD protocol entry, just opened) adds the protocol slug `flying-tulip-ftusd` that this adapter references

Test plan

  • Adapter executes locally and returns 2 valid pool objects
  • tvlUsd reconciles with api.flyingtulip.com/ftusd/staking
  • No centralised API in the data path (only coins.llama.fi for FT pricing, same pattern as ethena-usde)

sftUSD is the staked form of ftUSD, Flying Tulip's yield-bearing
stablecoin. APY is funded entirely by FT rewards: protocol fees from
ftUSD MintAndRedeem are used to buy FT on the open market and FT is
distributed to stakers per epoch through the EpochRewardsVault. FT total
supply is fixed so nothing is minted.

The adapter reads sftUSD.totalSupply() for tvlUsd (1:1 with USD because
ftUSD is dollar pegged and the vault is a 1:1 ERC4626) and sums the
rewardAmount field of EpochSettled events from the trailing 30 days,
priced via coins.llama.fi. APY is annualised over the 30 day window.
Reads on chain only, no centralised API dependency.

Verified locally:
  Ethereum: tvlUsd=$496,429.90, apyReward=2.19%
  Sonic:    tvlUsd=$340,619.84, apyReward=2.44%
TVL matches api.flyingtulip.com/ftusd/staking exactly.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 27, 2026

Warning

Rate limit exceeded

@patcito has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 53 minutes and 34 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, 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 have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a2d81cd4-e43c-452e-981b-ae27f92a20e6

📥 Commits

Reviewing files that changed from the base of the PR and between 093b82a and 21ec801.

📒 Files selected for processing (1)
  • src/adaptors/flying-tulip-ftusd/index.js
✨ 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.

@github-actions
Copy link
Copy Markdown

Error while running flying-tulip-ftusd adapter:

Test Suites: 1 failed, 1 total
Tests: 1 failed, 17 passed, 18 total
Snapshots: 0 total
Time: 0.325 s
Ran all test suites.

Nb of pools: 2
 

Sample pools:
┌─────────┬───────────────────────────────────────────────────────┬────────────┬──────────────────────┬──────────┬───────────────┬────────────────────┬──────────────────────────────────────────────────┬──────────────────────────────────────────────────┬───────────────────────────────────────────────────┬────────────────────────────────┐
│ (index) │ pool                                                  │ chain      │ project              │ symbol   │ tvlUsd        │ apyReward          │ rewardTokens                                     │ underlyingTokens                                 │ poolMeta                                          │ url                            │
├─────────┼───────────────────────────────────────────────────────┼────────────┼──────────────────────┼──────────┼───────────────┼────────────────────┼──────────────────────────────────────────────────┼──────────────────────────────────────────────────┼───────────────────────────────────────────────────┼────────────────────────────────┤
│ 0       │ '0xeb48218a4c35c814c7678cbcae88c6ee037f7625-ethereum' │ 'Ethereum' │ 'flying-tulip-ftusd' │ 'sftUSD' │ 496429.8955   │ 3.6899374553729483 │ [ '0x5DD1A7A369e8273371d2DBf9d83356057088082c' ] │ [ '0xF7D85EC4E7710f71992752eac2111312e73E9C9C' ] │ 'staked ftUSD (FT rewards bought on open market)' │ 'https://app.flyingtulip.com/' │
│ 1       │ '0xd1e5a86f1005f6356bd022c587de0f430cd2aeb1-sonic'    │ 'Sonic'    │ 'flying-tulip-ftusd' │ 'sftUSD' │ 340619.836318 │ 2.4423481092518484 │ [ '0x5DD1A7A369e8273371d2DBf9d83356057088082c' ] │ [ '0xF7D85EC4E7710f71992752eac2111312e73E9C9C' ] │ 'staked ftUSD (FT rewards bought on open market)' │ 'https://app.flyingtulip.com/' │
└─────────┴───────────────────────────────────────────────────────┴────────────┴──────────────────────┴──────────┴───────────────┴────────────────────┴──────────────────────────────────────────────────┴──────────────────────────────────────────────────┴───────────────────────────────────────────────────┴────────────────────────────────┘

- Sum rewards in the BigInt domain and only convert to Number after
  scaling down by 10^12 wei. The previous direct Number(totalRewardWei)
  cast lost precision once a single epoch's rewardAmount went past 2^53
  (around 0.009 FT).
- Treat a missing or zero FT price from coins.llama.fi as soft fail
  (apyReward = 0) instead of throwing. A transient pricing miss
  shouldn't drop both pools.
@github-actions
Copy link
Copy Markdown

Error while running flying-tulip-ftusd adapter:

Test Suites: 1 failed, 1 total
Tests: 1 failed, 17 passed, 18 total
Snapshots: 0 total
Time: 0.313 s
Ran all test suites.

Nb of pools: 2
 

Sample pools:
┌─────────┬───────────────────────────────────────────────────────┬────────────┬──────────────────────┬──────────┬───────────────┬────────────────────┬──────────────────────────────────────────────────┬──────────────────────────────────────────────────┬───────────────────────────────────────────────────┬────────────────────────────────┐
│ (index) │ pool                                                  │ chain      │ project              │ symbol   │ tvlUsd        │ apyReward          │ rewardTokens                                     │ underlyingTokens                                 │ poolMeta                                          │ url                            │
├─────────┼───────────────────────────────────────────────────────┼────────────┼──────────────────────┼──────────┼───────────────┼────────────────────┼──────────────────────────────────────────────────┼──────────────────────────────────────────────────┼───────────────────────────────────────────────────┼────────────────────────────────┤
│ 0       │ '0xeb48218a4c35c814c7678cbcae88c6ee037f7625-ethereum' │ 'Ethereum' │ 'flying-tulip-ftusd' │ 'sftUSD' │ 496429.8955   │ 3.8025060578194076 │ [ '0x5DD1A7A369e8273371d2DBf9d83356057088082c' ] │ [ '0xF7D85EC4E7710f71992752eac2111312e73E9C9C' ] │ 'staked ftUSD (FT rewards bought on open market)' │ 'https://app.flyingtulip.com/' │
│ 1       │ '0xd1e5a86f1005f6356bd022c587de0f430cd2aeb1-sonic'    │ 'Sonic'    │ 'flying-tulip-ftusd' │ 'sftUSD' │ 340619.836318 │ 2.516856611417955  │ [ '0x5DD1A7A369e8273371d2DBf9d83356057088082c' ] │ [ '0xF7D85EC4E7710f71992752eac2111312e73E9C9C' ] │ 'staked ftUSD (FT rewards bought on open market)' │ 'https://app.flyingtulip.com/' │
└─────────┴───────────────────────────────────────────────────────┴────────────┴──────────────────────┴──────────┴───────────────┴────────────────────┴──────────────────────────────────────────────────┴──────────────────────────────────────────────────┴───────────────────────────────────────────────────┴────────────────────────────────┘

@patcito
Copy link
Copy Markdown
Author

patcito commented Apr 28, 2026

Heads up on the failing CI: the project-slug validation is expected to fail until DefiLlama/defillama-server#11839 merges and api.llama.fi starts returning the flying-tulip-ftusd slug. The adapter itself runs cleanly, the only check that's red is the known protocol slug test. Happy to rebase as soon as the slug is live.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant