From 9e4e03608f2cae6d41bfd2245e4691fe9f6b34c8 Mon Sep 17 00:00:00 2001 From: Moses <103143573+Defi-Moses@users.noreply.github.com> Date: Wed, 22 Jan 2025 17:22:05 +0000 Subject: [PATCH] [synapse] strategy addition (#1675) * adding the synapse strategy * checksummed addresses * adding multichain functionality * reducing the number of calls to a single chain * Update synapse strategy to handle network-specific requests --------- Co-authored-by: Chaitanya --- src/strategies/index.ts | 4 +- src/strategies/synapse/README.md | 64 +++++++++++ src/strategies/synapse/examples.json | 19 ++++ src/strategies/synapse/index.ts | 164 +++++++++++++++++++++++++++ src/strategies/synapse/schema.json | 23 ++++ 5 files changed, 273 insertions(+), 1 deletion(-) create mode 100644 src/strategies/synapse/README.md create mode 100644 src/strategies/synapse/examples.json create mode 100644 src/strategies/synapse/index.ts create mode 100644 src/strategies/synapse/schema.json diff --git a/src/strategies/index.ts b/src/strategies/index.ts index 100a05eb8..7ce87f08c 100644 --- a/src/strategies/index.ts +++ b/src/strategies/index.ts @@ -470,6 +470,7 @@ import * as morphoDelegation from './morpho-delegation'; import * as lizcoinStrategy2024 from './lizcoin-strategy-2024'; import * as realt from './realt'; import * as superfluidVesting from './superfluid-vesting'; +import * as synapse from './synapse'; const strategies = { 'delegatexyz-erc721-balance-of': delegatexyzErc721BalanceOf, @@ -950,7 +951,8 @@ const strategies = { 'morpho-delegation': morphoDelegation, 'lizcoin-strategy-2024': lizcoinStrategy2024, realt, - 'superfluid-vesting': superfluidVesting + 'superfluid-vesting': superfluidVesting, + synapse }; Object.keys(strategies).forEach(function (strategyName) { diff --git a/src/strategies/synapse/README.md b/src/strategies/synapse/README.md new file mode 100644 index 000000000..80c4b8253 --- /dev/null +++ b/src/strategies/synapse/README.md @@ -0,0 +1,64 @@ +# Synapse Strategy + +This strategy calculates voting power by combining two components: +1. SYN token balance +2. Locked SYN tokens in the staking contract + +## Overview + +The strategy sums up: +- The regular SYN token balance (`balanceOf`) +- The locked amount in the staking contract (`lockedAmountOf`) + +## Parameters + +Here is an example of parameters: + +```json +{ + "symbol": "SYN", + "tokenAddress": "0x0f2D719407FdBeFF09D87557AbB7232601FD9F29", + "stakingAddress": "0x00000010cd90b3688d249d84c616de3a0343e60f", + "decimals": 18 +} +``` + +### Parameter Details + +- `symbol`: Token symbol (default: "SYN") +- `tokenAddress`: The SYN token contract address for the specific network +- `stakingAddress`: The staking contract address (default: 0x00000010cd90b3688d249d84c616de3a0343e60f) +- `decimals`: Token decimals (default: 18) + +## Networks & Addresses + +The strategy works across multiple networks. Here are the SYN token addresses for each supported chain: + +- Ethereum: `0x0f2D719407FdBeFF09D87557AbB7232601FD9F29` +- Arbitrum: `0x080f6aed32fc474dd5717105dba5ea57268f46eb` +- Aurora: `0xd80d8688b02B3FD3afb81cDb124F188BB5aD0445` +- Avalanche: `0x1f1E7c893855525b303f99bDF5c3c05Be09ca251` +- Base: `0x432036208d2717394d2614d6697c46DF3Ed69540` +- Blast: `0x9592f08387134e218327E6E8423400eb845EdE0E` +- Boba: `0xb554A55358fF0382Fb21F0a478C3546d1106Be8c` +- BSC: `0xa4080f1778e69467e905b8d6f72f6e441f9e9484` +- Canto: `0x555982d2E211745b96736665e19D9308B615F78e` +- Cronos: `0xFD0F80899983b8D46152aa1717D76cba71a31616` +- DFK Chain: `0xB6b5C854a8f71939556d4f3a2e5829F7FcC1bf2A` +- Fantom: `0xE55e19Fb4F2D85af758950957714292DAC1e25B2` +- Harmony: `0xE55e19Fb4F2D85af758950957714292DAC1e25B2` +- Metis: `0x67c10c397dd0ba417329543c1a40eb48aaa7cd00` +- Moonbeam: `0xF44938b0125A6662f9536281aD2CD6c499F22004` +- Moonriver: `0xd80d8688b02B3FD3afb81cDb124F188BB5aD0445` +- Optimism: `0x5A5fFf6F753d7C11A56A52FE47a177a87e431655` +- Polygon: `0xf8f9efc0db77d8881500bb06ff5d6abc3070e695` + +Note: Staking functionality is only available on Ethereum, Arbitrum, Avalanche, Optimism, BSC, Polygon, and Base networks. + +## Example + +If a user has: +- 100 SYN tokens in their wallet +- 50 SYN tokens locked in staking + +Their total voting power would be 150 SYN tokens. diff --git a/src/strategies/synapse/examples.json b/src/strategies/synapse/examples.json new file mode 100644 index 000000000..4bdea07c2 --- /dev/null +++ b/src/strategies/synapse/examples.json @@ -0,0 +1,19 @@ +[ + { + "name": "Example Query", + "strategy": { + "name": "synapse", + "params": { + "symbol": "SYN", + "decimals": 18 + } + }, + "network": "1", + "addresses": [ + "0xBdfbc9f2A873E07599CafA9BC18F80D4e07D7832", + "0x20D84F5e6533b48cdC8E557eD50d7825eBA77766", + "0xb73AcB429Ba868984c0236bdf940D4FE1E643F27" + ], + "snapshot": 21622022 + } +] diff --git a/src/strategies/synapse/index.ts b/src/strategies/synapse/index.ts new file mode 100644 index 000000000..c7ceed2d8 --- /dev/null +++ b/src/strategies/synapse/index.ts @@ -0,0 +1,164 @@ +import { formatUnits } from '@ethersproject/units'; +import { BigNumber } from '@ethersproject/bignumber'; +import { getAddress } from '@ethersproject/address'; +import { Multicaller } from '../../utils'; + +export const author = 'defi-moses'; +export const version = '0.2.0'; + +const SUPPORTED_CHAINS = { + '1': { + tokenAddress: '0x0f2D719407FdBeFF09D87557AbB7232601FD9F29', + stakingAddress: '0x00000010cd90b3688d249d84c616de3a0343e60f' + }, + '42161': { + // Arbitrum + tokenAddress: '0x080f6aed32fc474dd5717105dba5ea57268f46eb', + stakingAddress: '0x00000010cd90b3688d249d84c616de3a0343e60f' + }, + '43114': { + // Avalanche + tokenAddress: '0x1f1E7c893855525b303f99bDF5c3c05Be09ca251', + stakingAddress: '0x00000010cd90b3688d249d84c616de3a0343e60f' + }, + '10': { + // Optimism + tokenAddress: '0x5A5fFf6F753d7C11A56A52FE47a177a87e431655', + stakingAddress: '0x00000010cd90b3688d249d84c616de3a0343e60f' + }, + '56': { + // BSC + tokenAddress: '0xa4080f1778e69467e905b8d6f72f6e441f9e9484', + stakingAddress: '0x00000010cd90b3688d249d84c616de3a0343e60f' + }, + '137': { + // Polygon + tokenAddress: '0xf8f9efc0db77d8881500bb06ff5d6abc3070e695', + stakingAddress: '0x00000010cd90b3688d249d84c616de3a0343e60f' + }, + '8453': { + // Base + tokenAddress: '0x432036208d2717394d2614d6697c46DF3Ed69540', + stakingAddress: '0x00000010cd90b3688d249d84c616de3a0343e60f' + } +}; + +const tokenAbi = [ + 'function balanceOf(address) view returns (uint256)', + 'function decimals() view returns (uint8)' +]; + +const stakingAbi = [ + 'function lockedAmountOf(address account) view returns (uint256)', + 'function balanceOf(address account) view returns (uint256)' // Backup method +]; + +interface MulticallResult { + [key: string]: { + [address: string]: BigNumber; + }; +} + +async function getChainBalance( + network: string, + provider, + addresses: string[], + options, + snapshot +): Promise> { + const blockTag = typeof snapshot === 'number' ? snapshot : 'latest'; + const chainConfig = SUPPORTED_CHAINS[network]; + if (!chainConfig) { + throw new Error(`Network ${network} not supported`); + } + + try { + const multi = new Multicaller(network, provider, tokenAbi, { blockTag }); + const checksumAddresses = addresses.map(getAddress); + + checksumAddresses.forEach((address) => { + multi.call(`token.${address}`, chainConfig.tokenAddress, 'balanceOf', [ + address + ]); + }); + + const result = (await multi.execute()) as MulticallResult; + const stakingMulti = new Multicaller(network, provider, stakingAbi, { + blockTag + }); + let stakingResult: MulticallResult = { staking: {} }; + + try { + checksumAddresses.forEach((address) => { + stakingMulti.call( + `staking.${address}`, + chainConfig.stakingAddress, + 'lockedAmountOf', + [address] + ); + }); + stakingResult = await stakingMulti.execute(); + } catch (error) { + const balanceMulti = new Multicaller(network, provider, stakingAbi, { + blockTag + }); + checksumAddresses.forEach((address) => { + balanceMulti.call( + `staking.${address}`, + chainConfig.stakingAddress, + 'balanceOf', + [address] + ); + }); + + try { + stakingResult = await balanceMulti.execute(); + } catch (error) { + console.warn(`Failed to fetch staking balances for network ${network}`); + } + } + + return Object.fromEntries( + checksumAddresses.map((address) => { + const tokenBalance = result.token?.[address] || BigNumber.from(0); + const stakedBalance = + stakingResult.staking?.[address] || BigNumber.from(0); + + const formattedTokenBalance = parseFloat( + formatUnits(tokenBalance, options.decimals) + ); + const formattedStakedBalance = parseFloat( + formatUnits(stakedBalance, options.decimals) + ); + + return [address, formattedTokenBalance + formattedStakedBalance]; + }) + ); + } catch (error) { + throw new Error(`Error fetching balances for network ${network}: ${error}`); + } +} + +export async function strategy( + space, + network, + provider, + addresses, + options, + snapshot +): Promise> { + // Check if the selected network is supported + if (!SUPPORTED_CHAINS[network]) { + throw new Error(`Network ${network} not supported`); + } + + const result = await getChainBalance( + network, + provider, + addresses, + options, + snapshot + ); + + return result; +} diff --git a/src/strategies/synapse/schema.json b/src/strategies/synapse/schema.json new file mode 100644 index 000000000..3505fbf05 --- /dev/null +++ b/src/strategies/synapse/schema.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/Strategy", + "definitions": { + "Strategy": { + "title": "Strategy", + "type": "object", + "properties": { + "symbol": { + "type": ["string", "null"], + "title": "Symbol", + "default": "SYN" + }, + "decimals": { + "type": "number", + "title": "Decimals", + "default": 18 + } + }, + "required": ["decimals"] + } + } +}