-
Notifications
You must be signed in to change notification settings - Fork 1.1k
add zest-v2 adapter #2464
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
add zest-v2 adapter #2464
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,173 @@ | ||||||||||||||||||
| const axios = require('axios'); | ||||||||||||||||||
| const { | ||||||||||||||||||
| callReadOnlyFunction, | ||||||||||||||||||
| contractPrincipalCV, | ||||||||||||||||||
| } = require('@stacks/transactions'); | ||||||||||||||||||
| const { StacksMainnet } = require('@stacks/network'); | ||||||||||||||||||
|
|
||||||||||||||||||
| const DEPLOYER = 'SP1A27KFY4XERQCCRCARCYD1CC5N7M6688BSYADJ7'; | ||||||||||||||||||
|
|
||||||||||||||||||
| const POOLS = [ | ||||||||||||||||||
| { | ||||||||||||||||||
| symbol: 'STX', | ||||||||||||||||||
| vaultContract: 'v0-vault-stx', | ||||||||||||||||||
| assetAddress: 'SP1A27KFY4XERQCCRCARCYD1CC5N7M6688BSYADJ7', | ||||||||||||||||||
| contractName: 'wstx', | ||||||||||||||||||
| decimals: 6, | ||||||||||||||||||
| priceKeys: ['coingecko:blockstack'], | ||||||||||||||||||
| }, | ||||||||||||||||||
| { | ||||||||||||||||||
| symbol: 'sBTC', | ||||||||||||||||||
| vaultContract: 'v0-vault-sbtc', | ||||||||||||||||||
| assetAddress: 'SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4', | ||||||||||||||||||
| contractName: 'sbtc-token', | ||||||||||||||||||
| decimals: 8, | ||||||||||||||||||
| priceKeys: [ | ||||||||||||||||||
| 'stacks:SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token', | ||||||||||||||||||
| 'coingecko:bitcoin', | ||||||||||||||||||
| ], | ||||||||||||||||||
| }, | ||||||||||||||||||
| { | ||||||||||||||||||
| symbol: 'stSTX', | ||||||||||||||||||
| vaultContract: 'v0-vault-ststx', | ||||||||||||||||||
| assetAddress: 'SP4SZE494VC2YC5JYG7AYFQ44F5Q4PYV7DVMDPBG', | ||||||||||||||||||
| contractName: 'ststx-token', | ||||||||||||||||||
| decimals: 6, | ||||||||||||||||||
| priceKeys: [ | ||||||||||||||||||
| 'stacks:SP4SZE494VC2YC5JYG7AYFQ44F5Q4PYV7DVMDPBG.ststx-token', | ||||||||||||||||||
| 'coingecko:blockstack', | ||||||||||||||||||
| ], | ||||||||||||||||||
| }, | ||||||||||||||||||
| { | ||||||||||||||||||
| symbol: 'USDC', | ||||||||||||||||||
| vaultContract: 'v0-vault-usdc', | ||||||||||||||||||
| assetAddress: 'SP120SBRBQJ00MCWS7TM5R8WJNTTKD5K0HFRC2CNE', | ||||||||||||||||||
| contractName: 'usdcx', | ||||||||||||||||||
| decimals: 6, | ||||||||||||||||||
| priceKeys: [ | ||||||||||||||||||
| 'stacks:SP120SBRBQJ00MCWS7TM5R8WJNTTKD5K0HFRC2CNE.usdcx', | ||||||||||||||||||
| 'coingecko:usd-coin', | ||||||||||||||||||
| ], | ||||||||||||||||||
| }, | ||||||||||||||||||
| { | ||||||||||||||||||
| symbol: 'USDH', | ||||||||||||||||||
| vaultContract: 'v0-vault-usdh', | ||||||||||||||||||
| assetAddress: 'SPN5AKG35QZSK2M8GAMR4AFX45659RJHDW353HSG', | ||||||||||||||||||
| contractName: 'usdh-token-v1', | ||||||||||||||||||
| decimals: 8, | ||||||||||||||||||
| priceKeys: [ | ||||||||||||||||||
| 'stacks:SPN5AKG35QZSK2M8GAMR4AFX45659RJHDW353HSG.usdh-token-v1', | ||||||||||||||||||
| 'coingecko:usd-coin', | ||||||||||||||||||
| ], | ||||||||||||||||||
| }, | ||||||||||||||||||
| { | ||||||||||||||||||
| symbol: 'stSTXbtc', | ||||||||||||||||||
| vaultContract: 'v0-vault-ststxbtc', | ||||||||||||||||||
| assetAddress: 'SP4SZE494VC2YC5JYG7AYFQ44F5Q4PYV7DVMDPBG', | ||||||||||||||||||
| contractName: 'ststxbtc-token-v2', | ||||||||||||||||||
| decimals: 6, | ||||||||||||||||||
| priceKeys: [ | ||||||||||||||||||
| 'stacks:SP4SZE494VC2YC5JYG7AYFQ44F5Q4PYV7DVMDPBG.ststxbtc-token-v2', | ||||||||||||||||||
| 'coingecko:blockstack', | ||||||||||||||||||
| ], | ||||||||||||||||||
| }, | ||||||||||||||||||
| ]; | ||||||||||||||||||
|
|
||||||||||||||||||
| async function fetchPrices() { | ||||||||||||||||||
| const keys = [...new Set(POOLS.flatMap((p) => p.priceKeys))].join(','); | ||||||||||||||||||
| const url = `https://coins.llama.fi/prices/current/${keys}`; | ||||||||||||||||||
| const { data } = await axios.get(url); | ||||||||||||||||||
| return data.coins; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| function getPrice(prices, priceKeys) { | ||||||||||||||||||
| for (const key of priceKeys) { | ||||||||||||||||||
| if (prices[key]?.price) return { price: prices[key].price, key }; | ||||||||||||||||||
| } | ||||||||||||||||||
| return null; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| async function fetchApys(pool, network) { | ||||||||||||||||||
| const result = await callReadOnlyFunction({ | ||||||||||||||||||
| contractAddress: DEPLOYER, | ||||||||||||||||||
| contractName: 'v0-1-data', | ||||||||||||||||||
| functionName: 'get-asset-apys', | ||||||||||||||||||
| network, | ||||||||||||||||||
| functionArgs: [contractPrincipalCV(pool.assetAddress, pool.contractName)], | ||||||||||||||||||
| senderAddress: DEPLOYER, | ||||||||||||||||||
| }); | ||||||||||||||||||
|
|
||||||||||||||||||
| const tupleData = result.value.data; | ||||||||||||||||||
| const supplyApy = Number(tupleData['supply-apy'].value) / 100; | ||||||||||||||||||
| const borrowApy = Number(tupleData['borrow-apy'].value) / 100; | ||||||||||||||||||
|
|
||||||||||||||||||
| return { supplyApy, borrowApy }; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| async function fetchTotalAssets(pool, network) { | ||||||||||||||||||
| const result = await callReadOnlyFunction({ | ||||||||||||||||||
| contractAddress: DEPLOYER, | ||||||||||||||||||
| contractName: pool.vaultContract, | ||||||||||||||||||
| functionName: 'get-total-assets', | ||||||||||||||||||
| network, | ||||||||||||||||||
| functionArgs: [], | ||||||||||||||||||
| senderAddress: DEPLOYER, | ||||||||||||||||||
| }); | ||||||||||||||||||
|
|
||||||||||||||||||
| const rawAmount = Number(result.value.value); | ||||||||||||||||||
| return rawAmount / Math.pow(10, pool.decimals); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| async function getZestV2Pools() { | ||||||||||||||||||
| try { | ||||||||||||||||||
| const network = new StacksMainnet(); | ||||||||||||||||||
| const chain = 'Stacks'; | ||||||||||||||||||
|
|
||||||||||||||||||
| const prices = await fetchPrices(); | ||||||||||||||||||
|
|
||||||||||||||||||
| const results = []; | ||||||||||||||||||
|
|
||||||||||||||||||
| for (const pool of POOLS) { | ||||||||||||||||||
| try { | ||||||||||||||||||
| const priceResult = getPrice(prices, pool.priceKeys); | ||||||||||||||||||
| if (!priceResult) { | ||||||||||||||||||
| console.log(`Skipping ${pool.symbol}: price not available`); | ||||||||||||||||||
| continue; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| const [apys, totalAssets] = await Promise.all([ | ||||||||||||||||||
| fetchApys(pool, network), | ||||||||||||||||||
| fetchTotalAssets(pool, network), | ||||||||||||||||||
| ]); | ||||||||||||||||||
|
|
||||||||||||||||||
| const tvlUsd = totalAssets * priceResult.price; | ||||||||||||||||||
|
|
||||||||||||||||||
| results.push({ | ||||||||||||||||||
| pool: `${DEPLOYER}.${pool.vaultContract}-${chain}`.toLowerCase(), | ||||||||||||||||||
| chain: chain, | ||||||||||||||||||
| project: 'zest-v2', | ||||||||||||||||||
| symbol: pool.symbol, | ||||||||||||||||||
| tvlUsd: tvlUsd, | ||||||||||||||||||
| apyBase: apys.supplyApy, | ||||||||||||||||||
| apyBaseBorrow: apys.borrowApy, | ||||||||||||||||||
| underlyingTokens: [`${pool.assetAddress}.${pool.contractName}`], | ||||||||||||||||||
| token: `${pool.assetAddress}.${pool.contractName}`, | ||||||||||||||||||
| url: 'https://app.zestprotocol.com/market/main', | ||||||||||||||||||
| }); | ||||||||||||||||||
| } catch (error) { | ||||||||||||||||||
| console.log(`Error processing pool ${pool.symbol}: ${error.message}`); | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| return results; | ||||||||||||||||||
| } catch (error) { | ||||||||||||||||||
| console.log(`Error in getZestV2Pools: ${error.message}`); | ||||||||||||||||||
| return []; | ||||||||||||||||||
|
Comment on lines
+163
to
+165
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't mask adapter-wide failures as “zero pools”. Returning 🛠️ Proposed fix } catch (error) {
- console.log(`Error in getZestV2Pools: ${error.message}`);
- return [];
+ console.error(`Error in getZestV2Pools: ${error.message}`);
+ throw error;
}
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| module.exports = { | ||||||||||||||||||
| timetravel: false, | ||||||||||||||||||
| apy: getZestV2Pools, | ||||||||||||||||||
| url: 'https://app.zestprotocol.com/market/main', | ||||||||||||||||||
| }; | ||||||||||||||||||
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.
🧩 Analysis chain
🏁 Script executed:
Repository: DefiLlama/yield-server
Length of output: 3469
🏁 Script executed:
Repository: DefiLlama/yield-server
Length of output: 906
🏁 Script executed:
Repository: DefiLlama/yield-server
Length of output: 136
🌐 Web query:
axios default timeout behavior javascript library💡 Result:
timeoutis0, which means no timeout (it will wait indefinitely unless something else aborts the request). [1][2]timeoutis milliseconds before Axios aborts the request (when set to a positive number). [1]axios.get(url, { timeout: 5000 })), globally (axios.defaults.timeout = 5000), or per instance (instance.defaults.timeout = 5000). [1][2]timeoutwith cancellation (e.g.,AbortController/AbortSignal.timeout(...)). [3]Sources:
[1] Axios docs – Request Config (
timeout: 1000, // default is 0 (no timeout)) (axios-http.com)[2] Axios docs – Config defaults (notes default
timeoutis0) (axios.rest)[3] Axios docs – Cancellation (timeout vs cancellation,
AbortSignal.timeout) (axios-http.com)Citations:
Add a timeout to the price request.
axios.get()defaults to no timeout, so a hung llama.fi call will block the entire adapter indefinitely before any pools are emitted.🛠️ Proposed fix
async function fetchPrices() { const keys = [...new Set(POOLS.flatMap((p) => p.priceKeys))].join(','); const url = `https://coins.llama.fi/prices/current/${keys}`; - const { data } = await axios.get(url); + const { data } = await axios.get(url, { timeout: 10_000 }); return data.coins; }🤖 Prompt for AI Agents