-
Notifications
You must be signed in to change notification settings - Fork 61
feat(Solv): add APYs to Solv strategies #740
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds mainnet (Ethereum) support for SOLV tokens, maps Solv token addresses to strategy labels, parallelizes DefiLlama and Solv APY fetches, resolves reward tokens across Bob/Optimism/Mainnet, computes per-token incentive points, applies a HybridBTC 2% base-APY override before a fixed date, and falls back to Solv or zero-APY when needed. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Client
participant Strategy as getTokensIncentives()
participant Llama as getDefillamaPools()
participant Solv as getSolvAPYs()
participant Addr as resolveTokens()/ADDRESS_LOOKUP
Client->>Strategy: request incentives for tokens
par Parallel fetch
Strategy->>Llama: fetch DefiLlama pools (filter Bob)
Strategy->>Solv: fetch Solv GraphQL APYs (BTC+ and protocols)
end
Strategy->>Strategy: compute points = projectPointsIncentives + tokensPointsIncentives
alt DefiLlama pool found
Note right of Strategy #DDEBF7: If pool is HybridBTC and now < hybridBTCEndDate → apyBase = 2%
Strategy->>Addr: resolve pool.rewardTokens to public addresses (Bob/Optimism/Mainnet)
Strategy->>Client: return {apyBase, apyReward, rewardTokens, points}
else solv strategy mapped
Strategy->>Addr: resolve solvAPYs[strategy].rewardTokens
Strategy->>Client: return {apyBase, apyReward, rewardTokens, points}
else No match
Strategy->>Client: return {apyBase: 0, apyReward: 0, rewardTokens: [], points}
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
✨ Finishing Touches
🧪 Generate 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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Summary of Changes
Hello @mastilver, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
This pull request integrates Solv strategy APY data into the existing system, expanding the coverage of Annual Percentage Yields for various tokens. The changes enable the application to retrieve and display APYs for Solv-related assets, providing users with more comprehensive and accurate incentive information.
Highlights
- New Solv Strategy Mapping: Introduced a new map,
tokenToSolvStrategyMap
, to associate specific Solv token addresses (like Solv BTC+ and SolvBTC.JUP) with their corresponding strategy names. - Enhanced Token Incentives Retrieval: The
getTokensIncentives
method has been updated to concurrently fetch APY data from both DefiLlama and a new Solv-specific API, allowing for a broader range of APY calculations. - Dedicated Solv APY Fetcher: A new private method,
getSolvAPYs
, was added to handle the fetching and processing of APY data specifically from Solv's GraphQL endpoint, including error handling.
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command>
or @gemini-code-assist <command>
. Below is a summary of the supported commands.
Feature | Command | Description |
---|---|---|
Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/
folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request adds functionality to fetch APYs for Solv strategies. The implementation introduces parallel fetching of data which is good for performance. However, my review has identified a couple of critical issues related to error handling that could lead to application crashes. Specifically, there's a lack of validation for API responses from the Solv service and insufficient checks before accessing potentially undefined data. I've also included a medium-severity suggestion to improve the robustness of how the GraphQL request is constructed. Addressing these points will make the new functionality more resilient.
sdk/src/gateway/strategy.ts
Outdated
if (solvStrategy) { | ||
return { | ||
apyBase: solvAPYs[solvStrategy].apyBase, | ||
apyReward: solvAPYs[solvStrategy].apyReward, | ||
rewardTokens: [], | ||
points, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If getSolvAPYs()
fails and returns an empty object, solvAPYs[solvStrategy]
will be undefined
. Attempting to access properties on it (e.g., .apyBase
) will cause a runtime TypeError
. You should add a check to ensure solvAPYs[solvStrategy]
is defined before using it.
if (solvStrategy) { | |
return { | |
apyBase: solvAPYs[solvStrategy].apyBase, | |
apyReward: solvAPYs[solvStrategy].apyReward, | |
rewardTokens: [], | |
points, | |
}; | |
if (solvStrategy && solvAPYs[solvStrategy]) { | |
return { | |
apyBase: solvAPYs[solvStrategy].apyBase, | |
apyReward: solvAPYs[solvStrategy].apyReward, | |
rewardTokens: [], | |
points, | |
}; | |
} |
sdk/src/gateway/strategy.ts
Outdated
const data = await res.json(); | ||
|
||
const apys = { | ||
// TODO: should include Solv token as reward token | ||
'BTC+': { | ||
apyBase: Number(data.data.btcPlusStats.baseApy), | ||
apyReward: Number(data.data.btcPlusStats.rewardApy), | ||
} | ||
} | ||
|
||
data.data.lsts.details.forEach((pool) => { | ||
apys[pool.protocol] = { | ||
apyBase: Number(pool.apy), | ||
apyReward: 0, | ||
} | ||
}); | ||
|
||
return apys; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code proceeds to parse the JSON response and access its properties (data.data.btcPlusStats
) without first checking if the fetch
request was successful (e.g., res.ok
). If the API returns an error status code (like a 500 or 404), this will likely cause a crash when trying to access properties on a non-existent or error-structured response object. You should always check the response status and validate the data structure before using it.
if (!res.ok) {
const errorBody = await res.text();
throw new Error(`Failed to fetch APY data from Solv: ${res.status} ${res.statusText} - ${errorBody}`);
}
const data = await res.json();
if (!data.data?.btcPlusStats || !data.data?.lsts?.details) {
console.warn('Received unexpected data structure from Solv API', data);
return {};
}
const apys = {
// TODO: should include Solv token as reward token
'BTC+': {
apyBase: Number(data.data.btcPlusStats.baseApy),
apyReward: Number(data.data.btcPlusStats.rewardApy),
}
}
data.data.lsts.details.forEach((pool) => {
apys[pool.protocol] = {
apyBase: Number(pool.apy),
apyReward: 0,
}
});
return apys;
sdk/src/gateway/strategy.ts
Outdated
const query =` | ||
query SolvAPYs { | ||
btcPlusStats(stageNo: 1) { | ||
baseApy | ||
rewardApy | ||
tvl | ||
} | ||
lsts { | ||
details { | ||
protocol | ||
apy | ||
estApy | ||
tvlUsd | ||
} | ||
} | ||
} | ||
`.split("\n").join(" "); | ||
|
||
const res = await fetch("https://sft-api.com/graphql", { | ||
"headers": { | ||
"content-type": "application/json", | ||
"Authorization": "solv", | ||
}, | ||
"body": `{ | ||
"operationName": "SolvAPYs", | ||
"variables": {}, | ||
"query": "${query}" | ||
}`, | ||
"method": "POST", | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The GraphQL request body is constructed by manually creating a JSON string and manipulating the query string. This approach is fragile and can be simplified. A more robust and readable approach is to build a JavaScript object for the body and then serialize it using JSON.stringify
. This also makes the .split("\n").join(" ")
transformation on the query string unnecessary.
const query =`
query SolvAPYs {
btcPlusStats(stageNo: 1) {
baseApy
rewardApy
tvl
}
lsts {
details {
protocol
apy
estApy
tvlUsd
}
}
}
`;
const res = await fetch("https://sft-api.com/graphql", {
method: "POST",
headers: {
"content-type": "application/json",
"Authorization": "solv",
},
body: JSON.stringify({
operationName: "SolvAPYs",
variables: {},
query,
}),
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (2)
sdk/src/gateway/strategy.ts (2)
196-203
: Good parallelization; consider lightweight caching.These two network calls are re-done per request; add a short TTL cache (e.g., 60s) to reduce latency and rate-limit exposure.
163-175
: Match Solv strategy addresses with tokens.ts entries and use checksummed casing
0x4ca70811e831db42072cba1f0d03496ef126faad
not found intokens.ts
; verify it’s correct and add with identical casing.- Strategy.ts lowercases
0x6b062aa7f5fc52b530cb13967ae2e6bc0d8dd3e4
, buttokens.ts
uses0x6b062AA7F5FC52b530Cb13967aE2E6bc0D8Dd3E4
; update to checksum casing.const tokenToSolvStrategyMap: ReadonlyMap<string, 'BTC+' | 'Jupiter'> = new Map([...]);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
sdk/src/gateway/strategy.ts
(3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
sdk/src/gateway/strategy.ts (2)
sdk/src/gateway/tokens.ts (2)
tokenToStrategyTypeMap
(398-405)ADDRESS_LOOKUP
(359-359)sdk/src/gateway/types.ts (1)
DefiLlamaPool
(525-535)
🪛 GitHub Actions: SDK
sdk/src/gateway/strategy.ts
[warning] 1-1: Code style issues found in the above file. Run Prettier with --write to fix.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test
🔇 Additional comments (3)
sdk/src/gateway/strategy.ts (3)
208-212
: Points composition LGTM.Combines project- and token-level incentives cleanly with sensible fallbacks.
244-248
: Reasonable zero-APY fallback for unknown tokens.Keeps incentives points while avoiding undefined fields.
405-461
: Make Solv GraphQL handling resilient and typed.
- Annotate
getSolvAPYs
with
: Promise<Record<string, { apyBase: number; apyReward: number }>>
.- Use
JSON.stringify
for the request body instead of manual string interpolation.- Check
res.ok
before parsing, and guard with optional chaining (json?.data?.…
) aroundbtcPlusStats
andlsts.details
.- Coalesce
pool.apy
andpool.estApy
(e.g.Number(pool.apy ?? pool.estApy ?? 0)
).- Verify that
stageNo: 1
is the intended production stage.
sdk/src/gateway/strategy.ts
Outdated
const solvStrategy = tokenToSolvStrategyMap.get(tokenAddress); | ||
|
||
if (solvStrategy) { | ||
return { | ||
apyBase: solvAPYs[solvStrategy].apyBase, | ||
apyReward: solvAPYs[solvStrategy].apyReward, | ||
rewardTokens: [], | ||
points, | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard against missing Solv APY entries (crash risk).
If Solv API fails (returns {}
), solvAPYs[solvStrategy]
is undefined and property access throws.
Apply:
- if (solvStrategy) {
- return {
- apyBase: solvAPYs[solvStrategy].apyBase,
- apyReward: solvAPYs[solvStrategy].apyReward,
- rewardTokens: [],
- points,
- };
- }
+ if (solvStrategy) {
+ const solv = solvAPYs?.[solvStrategy];
+ return {
+ apyBase: solv?.apyBase ?? 0,
+ apyReward: solv?.apyReward ?? 0,
+ rewardTokens: [],
+ points,
+ };
+ }
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const solvStrategy = tokenToSolvStrategyMap.get(tokenAddress); | |
if (solvStrategy) { | |
return { | |
apyBase: solvAPYs[solvStrategy].apyBase, | |
apyReward: solvAPYs[solvStrategy].apyReward, | |
rewardTokens: [], | |
points, | |
}; | |
} | |
const solvStrategy = tokenToSolvStrategyMap.get(tokenAddress); | |
if (solvStrategy) { | |
- return { | |
- apyBase: solvAPYs[solvStrategy].apyBase, | |
- apyReward: solvAPYs[solvStrategy].apyReward, | |
- rewardTokens: [], | |
- points, | |
const solv = solvAPYs?.[solvStrategy]; | |
return { | |
apyBase: solv?.apyBase ?? 0, | |
apyReward: solv?.apyReward ?? 0, | |
rewardTokens: [], | |
points, | |
}; | |
} |
🤖 Prompt for AI Agents
In sdk/src/gateway/strategy.ts around lines 232 to 241, the code assumes
solvAPYs[solvStrategy] always exists and will throw if the Solv API returned an
empty object; guard by checking whether solvAPYs and solvAPYs[solvStrategy] are
defined before accessing properties, and if missing return safe defaults (e.g.,
apyBase: 0, apyReward: 0, rewardTokens: [], points) or otherwise handle the
missing entry (early return or fallback), optionally emit a warning/log when the
APY entry is absent.
sdk/src/gateway/strategy.ts
Outdated
private async getDefillamaPools() { | ||
const res = await fetch('https://yields.llama.fi/pools'); | ||
|
||
const defillamaPools: DefiLlamaPool[] = res.ok ? (await res.json()).data : []; | ||
const defillamaPoolMap = new Map<string, DefiLlamaPool>( | ||
defillamaPools.filter((pool) => pool.chain === 'Bob').map((pool) => [pool.pool, pool]) | ||
); | ||
return defillamaPoolMap; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Harden DefiLlama fetch: handle network errors and chain name variance.
A thrown fetch
will reject Promise.all
and bubble. Add try/catch, res.ok
check, and accept BOB
/Bob
.
Apply:
- private async getDefillamaPools() {
- const res = await fetch('https://yields.llama.fi/pools');
-
- const defillamaPools: DefiLlamaPool[] = res.ok ? (await res.json()).data : [];
- const defillamaPoolMap = new Map<string, DefiLlamaPool>(
- defillamaPools.filter((pool) => pool.chain === 'Bob').map((pool) => [pool.pool, pool])
- );
- return defillamaPoolMap;
- }
+ private async getDefillamaPools() {
+ try {
+ const res = await fetch('https://yields.llama.fi/pools');
+ if (!res.ok) return new Map();
+ const body = await res.json();
+ const defillamaPools: DefiLlamaPool[] = Array.isArray(body?.data) ? body.data : [];
+ const defillamaPoolMap = new Map<string, DefiLlamaPool>(
+ defillamaPools
+ .filter((pool) => pool.chain === 'Bob' || pool.chain === 'BOB')
+ .map((pool) => [pool.pool, pool])
+ );
+ return defillamaPoolMap;
+ } catch (e) {
+ console.error('Failed to fetch DefiLlama pools', e);
+ return new Map();
+ }
+ }
To confirm the exact chain casing returned today:
🏁 Script executed:
#!/bin/bash
curl -s https://yields.llama.fi/pools | jq -r '.data[].chain' | sort -u | rg -n 'BOB|Bob|bob' -n
Length of output: 113
Normalize and strictly match ‘bob’ when filtering DefiLlama pools
Replace the current casing check to avoid matching “Boba”:
sdk/src/gateway/strategy.ts (around line 255):
- .filter((pool) => pool.chain === 'Bob' || pool.chain === 'BOB')
+ .filter((pool) => pool.chain.toLowerCase() === 'bob')
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In sdk/src/gateway/strategy.ts around lines 252 to 260, the filter currently
checks pool.chain === 'Bob' which can incorrectly match variants like "Boba";
update the filter to normalize the chain string and strictly compare to 'bob'
(e.g. use pool.chain?.toLowerCase() === 'bob') to ensure only exact "bob"
matches and guard against null/undefined chain values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
sdk/src/gateway/tokens.ts (2)
431-439
: getTokenDecimals does not support optimism/ethereumWith ethereum support added, this function returns
undefined
for tokens not on BOB/BOB Sepolia. Use the generic address lookup.Apply:
-export function getTokenDecimals(tokenAddress: Address): number | undefined { - const normalizedAddress = tokenAddress.toString().toLowerCase(); - - const token = - bobTokens.find((t) => t.tokens.bob.address.toLowerCase() === normalizedAddress) || - bobSepoliaTokens.find((t) => t.tokens['bob-sepolia'].address.toLowerCase() === normalizedAddress); - - return token?.decimals; -} +export function getTokenDecimals(tokenAddress: Address): number | undefined { + const normalized = tokenAddress.toString().toLowerCase(); + for (const map of Object.values(ADDRESS_LOOKUP)) { + const t = map[normalized]; + if (t) return t.decimals; + } + return undefined; +}
441-449
: getTokenAddress is case-sensitive on symbols; hardens error pathLowercase the symbol input and guard missing chain buckets.
export function getTokenAddress(chainId: number, token: string): Address { if (isAddress(token)) { return token; - } else if (SYMBOL_LOOKUP[chainId][token]) { - return SYMBOL_LOOKUP[chainId][token].address; + } else if (SYMBOL_LOOKUP[chainId]?.[token.toLowerCase()]) { + return SYMBOL_LOOKUP[chainId][token.toLowerCase()]!.address; } else { - throw new Error('Unknown output token'); + throw new Error(`Unknown token "${token}" on chainId ${chainId}`); } }
🧹 Nitpick comments (1)
sdk/src/gateway/tokens.ts (1)
358-361
: Avoid drift by typing chain keys onceDefine a
ChainKey
union and usePartial<Record<ChainKey, { address: string }>>
to prevent future omissions.Proposed type:
type ChainKey = 'bob' | 'bob-sepolia' | 'optimism' | 'ethereum'; type ChainAddresses = Partial<Record<ChainKey, { address: string }>>;Then update the TOKENS shape to:
tokens: ChainAddresses;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
assets/SOLV.svg
is excluded by!**/*.svg
📒 Files selected for processing (2)
sdk/src/gateway/strategy.ts
(4 hunks)sdk/src/gateway/tokens.ts
(5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- sdk/src/gateway/strategy.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test
🔇 Additional comments (4)
sdk/src/gateway/tokens.ts (4)
3-3
: Importing mainnet: LGTMImport aligns with usage of
mainnet.id
below.
367-367
: Including ethereumTokens in TOKENS: LGTM
382-382
: Initialize SYMBOL_LOOKUP for mainnet: LGTM
387-387
: Initialize ADDRESS_LOOKUP for mainnet: LGTM
const ethereumTokens = [ | ||
{ | ||
name: 'Solv', | ||
symbol: 'SOLV', | ||
decimals: 18, | ||
tokens: { | ||
ethereum: { | ||
address: '0x04830a96a23ea718faa695a5aae74695aae3a23f', | ||
}, | ||
}, | ||
logoURI: 'https://raw.githubusercontent.com/bob-collective/bob/master/assets/SOLV.svg', | ||
}, | ||
]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Verify SOLV mainnet address/decimals before merge
Please confirm 0x04830a96a23ea718faa695a5aae74695aae3a23f
and decimals: 18
are correct for SOLV on Ethereum.
🌐 Web query:
Confirm the official Ethereum mainnet contract address and decimals for the SOLV token (Solv Protocol).
💡 Result:
The Solv Protocol SOLV token on Ethereum (as listed on Etherscan) is: 0xD1D8c3f9a5609027A1c7cEF070aB9c51b99DCFf7 — decimals: 18. [1]
Note: there are other unrelated contracts named "SOLV" on Etherscan (e.g. 0xeb6b6...); Solv's docs state SOLV governance operates on BNB Chain (so please confirm which chain/token you mean if you need official project confirmation). [2]
Incorrect SOLV token address on Ethereum mainnet
The address configured in sdk/src/gateway/tokens.ts
(0x04830a96a23ea718faa695a5aae74695aae3a23f
) does not match the official SOLV (Solv Protocol) contract on Ethereum mainnet, which is 0xD1D8c3f9a5609027A1c7cEF070aB9c51b99DCFf7
. The decimals: 18
value is correct.
🤖 Prompt for AI Agents
In sdk/src/gateway/tokens.ts around lines 169-181, the SOLV token entry uses an
incorrect Ethereum mainnet address; update the ethereum.tokens.ethereum.address
value to the official SOLV mainnet contract
0xD1D8c3f9a5609027A1c7cEF070aB9c51b99DCFf7 (preserve the checksummed casing),
keep decimals: 18 and existing metadata (name, symbol, logoURI) unchanged, then
run any token/address validation tests or linting to ensure the new address is
valid in the codebase.
if (token.tokens.ethereum) { | ||
addToken(token.tokens.ethereum.address, token, mainnet.id); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Mainnet registration path: LGTM. Ensure strategy wiring covers SOLV
The registration works. Please verify the new Solv strategy map consumes these mainnet entries.
🏁 Script executed:
#!/bin/bash
# Verify any strategy maps include the SOLV mainnet address.
rg -nC2 -i "0x04830a96a23ea718faa695a5aae74695aae3a23f|tokenToSolvStrategyMap|solv" --type ts
Length of output: 42793
Add SOLV mapping to tokenToSolvStrategyMap
tokenToSolvStrategyMap in sdk/src/gateway/strategy.ts currently only includes SolvBTC+ and Jupiter. It’s missing the SOLV mainnet address (0x04830a96a23ea718faa695a5aae74695aae3a23f
), so SOLV won’t receive its APY strategy. Add an entry mapping that address to the correct Solv strategy ID.
🤖 Prompt for AI Agents
In sdk/src/gateway/tokens.ts around lines 417 to 419, tokenToSolvStrategyMap is
missing the SOLV mainnet address mapping; add an entry that maps the SOLV
mainnet address 0x04830a96a23ea718faa695a5aae74695aae3a23f to the correct Solv
strategy ID (use the existing Solv strategy constant/ID used for Solv tokens),
ensure the address is normalized the same way other keys are (lowercase or
checksummed) and add/update any related exports/tests so SOLV receives its APY
strategy.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (4)
sdk/src/gateway/strategy.ts (4)
221-230
: Crash risk: missing Solv APY entry access.If Solv API returns
{}
or the key is absent,solvAPYs[solvStrategy]
isundefined
and property access throws.Apply:
- if (solvStrategy) { - return { - apyBase: solvAPYs[solvStrategy].apyBase, - apyReward: solvAPYs[solvStrategy].apyReward, - rewardTokens: this.resolveTokens(solvAPYs[solvStrategy].rewardTokens), - points, - }; - } + if (solvStrategy) { + const solv = solvAPYs?.[solvStrategy]; + return { + apyBase: solv?.apyBase ?? 0, + apyReward: solv?.apyReward ?? 0, + rewardTokens: this.resolveTokens(solv?.rewardTokens), + points, + }; + }
241-249
: Harden DefiLlama fetch and normalize chain casing.Add try/catch, check
res.ok
, validate shape, and comparepool.chain.toLowerCase() === 'bob'
.Apply:
- private async getDefillamaPools() { - const res = await fetch('https://yields.llama.fi/pools'); - - const defillamaPools: DefiLlamaPool[] = res.ok ? (await res.json()).data : []; - const defillamaPoolMap = new Map<string, DefiLlamaPool>( - defillamaPools.filter((pool) => pool.chain === 'Bob').map((pool) => [pool.pool, pool]) - ); - return defillamaPoolMap; - } + private async getDefillamaPools(): Promise<Map<string, DefiLlamaPool>> { + try { + const res = await fetch('https://yields.llama.fi/pools'); + if (!res.ok) return new Map(); + const body = await res.json(); + const defillamaPools: DefiLlamaPool[] = Array.isArray(body?.data) ? body.data : []; + const map = new Map<string, DefiLlamaPool>( + defillamaPools + .filter((pool) => pool?.chain?.toLowerCase() === 'bob') + .map((pool) => [pool.pool, pool]) + ); + return map; + } catch (e) { + console.error('Failed to fetch DefiLlama pools', e); + return new Map(); + } + }
251-264
: Guard ADDRESS_LOOKUP accesses and return a typed array.Current code can throw if a chain bucket is missing; also
filter(Boolean)
loses type info.Apply:
- private resolveTokens(tokens: string[] | undefined | null): Token[] { + private resolveTokens(tokens: string[] | undefined | null): Token[] { if (!tokens) { return []; } - return tokens - .map( - (addr) => - ADDRESS_LOOKUP[bob.id][addr.toLowerCase()] || - ADDRESS_LOOKUP[optimism.id][addr.toLowerCase()] || - ADDRESS_LOOKUP[mainnet.id][addr.toLowerCase()] - ) - .filter(Boolean); + return tokens + .map((addr) => { + const a = addr.toLowerCase(); + return ( + ADDRESS_LOOKUP[bob.id]?.[a] ?? + ADDRESS_LOOKUP[optimism.id]?.[a] ?? + ADDRESS_LOOKUP[mainnet.id]?.[a] + ); + }) + .filter((t): t is Token => Boolean(t)); }
410-467
: Make Solv GraphQL request robust and typed.Check
res.ok
, validate shape, and build the body withJSON.stringify
instead of manual string interpolation. Add an explicit return type.Apply:
- private async getSolvAPYs() { - try { - const query = ` + private async getSolvAPYs(): Promise<Record<string, { apyBase: number; apyReward: number; rewardTokens?: string[] }>> { + try { + const query = ` query SolvAPYs { btcPlusStats(stageNo: 1) { baseApy rewardApy tvl } lsts { details { protocol apy estApy tvlUsd } } } - ` - .split('\n') - .join(' '); - - const res = await fetch('https://sft-api.com/graphql', { - headers: { - 'content-type': 'application/json', - Authorization: 'solv', - }, - body: `{ - "operationName": "SolvAPYs", - "variables": {}, - "query": "${query}" - }`, - method: 'POST', - }); - - const data = await res.json(); - - const apys = { + `; + + const res = await fetch('https://sft-api.com/graphql', { + method: 'POST', + headers: { + 'content-type': 'application/json', + Authorization: 'solv', + }, + body: JSON.stringify({ + operationName: 'SolvAPYs', + variables: {}, + query, + }), + }); + if (!res.ok) { + const errorBody = await res.text().catch(() => ''); + throw new Error(`Solv APY fetch failed: ${res.status} ${res.statusText} ${errorBody}`); + } + const data = await res.json(); + if (!data?.data?.btcPlusStats || !data?.data?.lsts?.details) { + console.warn('Unexpected Solv API shape', data); + return {}; + } + + const apys: Record<string, { apyBase: number; apyReward: number; rewardTokens?: string[] }> = { 'BTC+': { apyBase: Number(data.data.btcPlusStats.baseApy), apyReward: Number(data.data.btcPlusStats.rewardApy), rewardTokens: ['0x04830a96a23ea718faa695a5aae74695aae3a23f'], }, }; data.data.lsts.details.forEach((pool) => { apys[pool.protocol] = { apyBase: Number(pool.apy), apyReward: 0, }; }); return apys; } catch (err) { console.error('Failed to fetch APY data from Solv', err); return {}; } }Optional timeout (nice-to-have):
const ctrl = new AbortController(); const t = setTimeout(() => ctrl.abort(), 8_000); try { /* fetch with signal: ctrl.signal */ } finally { clearTimeout(t); }
🧹 Nitpick comments (1)
sdk/src/gateway/strategy.ts (1)
210-219
: Avoid magic pool ID; extract a named constant.Improves readability and prevents drift if the ID changes.
Apply:
- // HACK: set HybridBTC APY to 2% - apyBase: - defillamaPoolId === 'e8bfea35-ff6d-48db-aa08-51599b363219' ? 2 : (defillamaPool?.apyBase ?? 0), + // HACK: set HybridBTC APY to 2% + apyBase: + defillamaPoolId === HYBRID_BTC_POOL_ID ? HYBRID_BTC_OVERRIDE_APY : (defillamaPool?.apyBase ?? 0),Add near the top (outside the shown range):
const HYBRID_BTC_POOL_ID = 'e8bfea35-ff6d-48db-aa08-51599b363219'; const HYBRID_BTC_OVERRIDE_APY = 2;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
sdk/src/gateway/strategy.ts
(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
sdk/src/gateway/strategy.ts (2)
sdk/src/gateway/tokens.ts (2)
tokenToStrategyTypeMap
(422-429)ADDRESS_LOOKUP
(377-377)sdk/src/gateway/types.ts (2)
DefiLlamaPool
(525-535)Token
(32-39)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test
🔇 Additional comments (2)
sdk/src/gateway/strategy.ts (2)
11-11
: Import usage looks good.
mainnet
is used in resolveTokens; no issues.
163-174
: Solv strategy map looks correct.Keys are lowercased; aligns with
tokenAddress = token.toLowerCase()
.
1ea3b33
to
ac55476
Compare
ac55476
to
fa71cca
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (4)
sdk/src/gateway/strategy.ts (4)
225-234
: Guard against missing Solv APY entry (crash risk).If the Solv API returns {} or a missing key, solvAPYs[solvStrategy] is undefined and property access throws.
- if (solvStrategy) { - return { - apyBase: solvAPYs[solvStrategy].apyBase, - apyReward: solvAPYs[solvStrategy].apyReward, - rewardTokens: this.resolveTokens(solvAPYs[solvStrategy].rewardTokens), - points, - }; - } + if (solvStrategy) { + // Optional: case-normalize if Solv protocol labels vary + const key = solvStrategy; + const s = solvAPYs?.[key]; + return { + apyBase: s?.apyBase ?? 0, + apyReward: s?.apyReward ?? 0, + rewardTokens: this.resolveTokens(s?.rewardTokens), + points, + }; + }
414-471
: Make Solv GraphQL fetch robust and validate shape.Check res.ok, build the JSON body with JSON.stringify, and guard against missing fields. Optionally normalize protocol keys and fall back to estApy when apy is null.
- private async getSolvAPYs() { - try { - const query = ` - query SolvAPYs { - btcPlusStats(stageNo: 1) { - baseApy - rewardApy - tvl - } - lsts { - details { - protocol - apy - estApy - tvlUsd - } - } - } - ` - .split('\n') - .join(' '); - - const res = await fetch('https://sft-api.com/graphql', { - headers: { - 'content-type': 'application/json', - Authorization: 'solv', - }, - body: `{ - "operationName": "SolvAPYs", - "variables": {}, - "query": "${query}" - }`, - method: 'POST', - }); - - const data = await res.json(); - - const apys = { - 'BTC+': { - apyBase: Number(data.data.btcPlusStats.baseApy), - apyReward: Number(data.data.btcPlusStats.rewardApy), - rewardTokens: ['0x04830a96a23ea718faa695a5aae74695aae3a23f'], - }, - }; - - data.data.lsts.details.forEach((pool) => { - apys[pool.protocol] = { - apyBase: Number(pool.apy), - apyReward: 0, - }; - }); - - return apys; - } catch (err) { - console.error('Failed to fetch APY data from Solv', err); - return {}; - } - } + private async getSolvAPYs() { + try { + const query = ` + query SolvAPYs { + btcPlusStats(stageNo: 1) { + baseApy + rewardApy + tvl + } + lsts { + details { + protocol + apy + estApy + tvlUsd + } + } + } + `; + + const res = await fetch('https://sft-api.com/graphql', { + method: 'POST', + headers: { + 'content-type': 'application/json', + Authorization: 'solv', + }, + body: JSON.stringify({ + operationName: 'SolvAPYs', + variables: {}, + query, + }), + }); + if (!res.ok) { + const body = await res.text().catch(() => ''); + throw new Error(`Solv GraphQL ${res.status} ${res.statusText} ${body}`.trim()); + } + const data = await res.json(); + const btc = data?.data?.btcPlusStats; + const lsts: Array<{ protocol?: string; apy?: number | string | null; estApy?: number | string | null }> = + data?.data?.lsts?.details ?? []; + if (!btc || !Array.isArray(lsts)) return {}; + + const apys: Record<string, { apyBase: number; apyReward: number; rewardTokens?: string[] }> = { + 'BTC+': { + apyBase: Number(btc.baseApy ?? 0), + apyReward: Number(btc.rewardApy ?? 0), + // SOLV token + rewardTokens: ['0x04830a96a23ea718faa695a5aae74695aae3a23f'], + }, + }; + + lsts.forEach((pool) => { + const key = (pool?.protocol ?? '').toString(); + if (!key) return; + apys[key] = { + apyBase: Number(pool.apy ?? pool.estApy ?? 0), + apyReward: 0, + }; + }); + return apys; + } catch (err) { + console.error('Failed to fetch APY data from Solv', err); + return {}; + } + }
245-253
: Harden DefiLlama fetch and normalize chain match.Network errors can throw; current filter misses 'BOB' and risks “Boba” false positives later. Use try/catch and strict lowercase equality.
- private async getDefillamaPools() { - const res = await fetch('https://yields.llama.fi/pools'); - - const defillamaPools: DefiLlamaPool[] = res.ok ? (await res.json()).data : []; - const defillamaPoolMap = new Map<string, DefiLlamaPool>( - defillamaPools.filter((pool) => pool.chain === 'Bob').map((pool) => [pool.pool, pool]) - ); - return defillamaPoolMap; - } + private async getDefillamaPools() { + try { + const res = await fetch('https://yields.llama.fi/pools'); + if (!res.ok) return new Map<string, DefiLlamaPool>(); + const body = await res.json(); + const defillamaPools: DefiLlamaPool[] = Array.isArray(body?.data) ? body.data : []; + const defillamaPoolMap = new Map<string, DefiLlamaPool>( + defillamaPools + .filter((pool) => pool?.chain?.toLowerCase?.() === 'bob') + .map((pool) => [pool.pool, pool]) + ); + return defillamaPoolMap; + } catch (e) { + console.error('Failed to fetch DefiLlama pools', e); + return new Map<string, DefiLlamaPool>(); + } + }
212-223
: HBTC override deviates from product ask; set 0% after 2025-09-08.Per prior note: “if it's past September 8th return 0 otherwise 2”. Current code falls back to DefiLlama after the date. Apply:
- // HACK: set HybridBTC APY to 2% until 2025-09-08 - apyBase: - defillamaPoolId === 'e8bfea35-ff6d-48db-aa08-51599b363219' && new Date() < hybridBTCEndDate - ? 2 - : (defillamaPool?.apyBase ?? 0), + // HACK: HybridBTC fixed APY window: 2% before cutoff, 0% after + apyBase: + defillamaPoolId === 'e8bfea35-ff6d-48db-aa08-51599b363219' + ? (new Date() < hybridBTCEndDate ? 2 : 0) + : (defillamaPool?.apyBase ?? 0),Optional: use a HYBRID_BTC_POOL_ID constant.
🧹 Nitpick comments (2)
sdk/src/gateway/strategy.ts (2)
163-174
: Verify Solv protocol key names match API exactly.tokenToSolvStrategyMap uses 'BTC+' and 'Jupiter'. Ensure these exactly match data.lsts.details[].protocol from Solv; otherwise the lookup will miss. If names can vary, normalize both sides (e.g., lowercase).
Would you like me to normalize keys end-to-end?
176-176
: Name/tighten the HBTC cutoff constants.Consider extracting a HYBRID_BTC_POOL_ID constant alongside hybridBTCEndDate to avoid the hardcoded string later and make intent explicit.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
sdk/src/gateway/strategy.ts
(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
sdk/src/gateway/strategy.ts (2)
sdk/src/gateway/tokens.ts (2)
tokenToStrategyTypeMap
(422-429)ADDRESS_LOOKUP
(377-377)sdk/src/gateway/types.ts (2)
DefiLlamaPool
(525-535)Token
(32-39)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test
🔇 Additional comments (2)
sdk/src/gateway/strategy.ts (2)
11-11
: Import looks correct.mainnet is used in resolveTokens; good addition.
204-208
: Points composition is clean.Merges project- and token-level incentives as intended.
Summary by CodeRabbit
New Features
Improvements