diff --git a/.env b/.env index e081d02..8ed9160 100644 --- a/.env +++ b/.env @@ -1,5 +1,6 @@ VITE_INFO_GRAPH=https://api.thegraph.com/subgraphs/name/iliaazhel/integral-core VITE_LIMIT_ORDERS_GRAPH=https://api.thegraph.com/subgraphs/name/iliaazhel/integral-limit-order VITE_BLOCKS_GRAPH=https://api.thegraph.com/subgraphs/name/iliaazhel/goerli-blocks -VITE_INFURA_RPC=https://1rpc.io/holesky +VITE_FARMING_GRAPH=https://api.thegraph.com/subgraphs/name/iliaazhel/farming-test +VITE_INFURA_RPC=https://ethereum-holesky-rpc.publicnode.com VITE_WALLETCONNECT_PROJECT_ID=79c313a96c99edbc26d06cd97bff1126 \ No newline at end of file diff --git a/codegen.ts b/codegen.ts index 77dbfa1..a6ff6e8 100644 --- a/codegen.ts +++ b/codegen.ts @@ -1,22 +1,26 @@ - import type { CodegenConfig } from '@graphql-codegen/cli'; const config: CodegenConfig = { - overwrite: true, - schema: [ - "https://api.thegraph.com/subgraphs/name/iliaazhel/integral-core", - "https://api.thegraph.com/subgraphs/name/iliaazhel/goerli-blocks" - ], - documents: "src/graphql/queries/!(*.d).{ts,tsx}", - generates: { - "src/graphql/generated/graphql.tsx": { - plugins: ['typescript', 'typescript-operations', 'typescript-react-apollo'], - config: { - withHooks: true, - withResultType: true - } - } - } + overwrite: true, + schema: [ + 'https://api.thegraph.com/subgraphs/name/iliaazhel/integral-core', + 'https://api.thegraph.com/subgraphs/name/iliaazhel/goerli-blocks', + 'https://api.thegraph.com/subgraphs/name/iliaazhel/farming-test', + ], + documents: 'src/graphql/queries/!(*.d).{ts,tsx}', + generates: { + 'src/graphql/generated/graphql.tsx': { + plugins: [ + 'typescript', + 'typescript-operations', + 'typescript-react-apollo', + ], + config: { + withHooks: true, + withResultType: true, + }, + }, + }, }; export default config; diff --git a/src/abis/algebraFactory.ts b/src/abis/algebraFactory.ts index 1350873..338ad41 100644 --- a/src/abis/algebraFactory.ts +++ b/src/abis/algebraFactory.ts @@ -1,766 +1,766 @@ export const algebraFactoryABI = [ { - "inputs": [ + inputs: [ { - "internalType": "address", - "name": "_poolDeployer", - "type": "address" - } + internalType: 'address', + name: '_poolDeployer', + type: 'address', + }, ], - "stateMutability": "nonpayable", - "type": "constructor" + stateMutability: 'nonpayable', + type: 'constructor', }, { - "anonymous": false, - "inputs": [ + anonymous: false, + inputs: [ { - "indexed": false, - "internalType": "uint8", - "name": "newDefaultCommunityFee", - "type": "uint8" - } + indexed: false, + internalType: 'uint8', + name: 'newDefaultCommunityFee', + type: 'uint8', + }, ], - "name": "DefaultCommunityFee", - "type": "event" + name: 'DefaultCommunityFee', + type: 'event', }, { - "anonymous": false, - "inputs": [ + anonymous: false, + inputs: [ { - "indexed": true, - "internalType": "address", - "name": "newFarmingAddress", - "type": "address" - } + indexed: true, + internalType: 'address', + name: 'newFarmingAddress', + type: 'address', + }, ], - "name": "FarmingAddress", - "type": "event" + name: 'FarmingAddress', + type: 'event', }, { - "anonymous": false, - "inputs": [ + anonymous: false, + inputs: [ { - "indexed": false, - "internalType": "uint16", - "name": "alpha1", - "type": "uint16" + indexed: false, + internalType: 'uint16', + name: 'alpha1', + type: 'uint16', }, { - "indexed": false, - "internalType": "uint16", - "name": "alpha2", - "type": "uint16" + indexed: false, + internalType: 'uint16', + name: 'alpha2', + type: 'uint16', }, { - "indexed": false, - "internalType": "uint32", - "name": "beta1", - "type": "uint32" + indexed: false, + internalType: 'uint32', + name: 'beta1', + type: 'uint32', }, { - "indexed": false, - "internalType": "uint32", - "name": "beta2", - "type": "uint32" + indexed: false, + internalType: 'uint32', + name: 'beta2', + type: 'uint32', }, { - "indexed": false, - "internalType": "uint16", - "name": "gamma1", - "type": "uint16" + indexed: false, + internalType: 'uint16', + name: 'gamma1', + type: 'uint16', }, { - "indexed": false, - "internalType": "uint16", - "name": "gamma2", - "type": "uint16" + indexed: false, + internalType: 'uint16', + name: 'gamma2', + type: 'uint16', }, { - "indexed": false, - "internalType": "uint16", - "name": "baseFee", - "type": "uint16" - } + indexed: false, + internalType: 'uint16', + name: 'baseFee', + type: 'uint16', + }, ], - "name": "FeeConfiguration", - "type": "event" + name: 'FeeConfiguration', + type: 'event', }, { - "anonymous": false, - "inputs": [ + anonymous: false, + inputs: [ { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" + indexed: true, + internalType: 'address', + name: 'previousOwner', + type: 'address', }, { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } + indexed: true, + internalType: 'address', + name: 'newOwner', + type: 'address', + }, ], - "name": "OwnershipTransferStarted", - "type": "event" + name: 'OwnershipTransferStarted', + type: 'event', }, { - "anonymous": false, - "inputs": [ + anonymous: false, + inputs: [ { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" + indexed: true, + internalType: 'address', + name: 'previousOwner', + type: 'address', }, { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } + indexed: true, + internalType: 'address', + name: 'newOwner', + type: 'address', + }, ], - "name": "OwnershipTransferred", - "type": "event" + name: 'OwnershipTransferred', + type: 'event', }, { - "anonymous": false, - "inputs": [ + anonymous: false, + inputs: [ { - "indexed": true, - "internalType": "address", - "name": "token0", - "type": "address" + indexed: true, + internalType: 'address', + name: 'token0', + type: 'address', }, { - "indexed": true, - "internalType": "address", - "name": "token1", - "type": "address" + indexed: true, + internalType: 'address', + name: 'token1', + type: 'address', }, { - "indexed": false, - "internalType": "address", - "name": "pool", - "type": "address" - } + indexed: false, + internalType: 'address', + name: 'pool', + type: 'address', + }, ], - "name": "Pool", - "type": "event" + name: 'Pool', + type: 'event', }, { - "anonymous": false, - "inputs": [ + anonymous: false, + inputs: [ { - "indexed": true, - "internalType": "bytes32", - "name": "role", - "type": "bytes32" + indexed: true, + internalType: 'bytes32', + name: 'role', + type: 'bytes32', }, { - "indexed": true, - "internalType": "bytes32", - "name": "previousAdminRole", - "type": "bytes32" + indexed: true, + internalType: 'bytes32', + name: 'previousAdminRole', + type: 'bytes32', }, { - "indexed": true, - "internalType": "bytes32", - "name": "newAdminRole", - "type": "bytes32" - } + indexed: true, + internalType: 'bytes32', + name: 'newAdminRole', + type: 'bytes32', + }, ], - "name": "RoleAdminChanged", - "type": "event" + name: 'RoleAdminChanged', + type: 'event', }, { - "anonymous": false, - "inputs": [ + anonymous: false, + inputs: [ { - "indexed": true, - "internalType": "bytes32", - "name": "role", - "type": "bytes32" + indexed: true, + internalType: 'bytes32', + name: 'role', + type: 'bytes32', }, { - "indexed": true, - "internalType": "address", - "name": "account", - "type": "address" + indexed: true, + internalType: 'address', + name: 'account', + type: 'address', }, { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - } + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, ], - "name": "RoleGranted", - "type": "event" + name: 'RoleGranted', + type: 'event', }, { - "anonymous": false, - "inputs": [ + anonymous: false, + inputs: [ { - "indexed": true, - "internalType": "bytes32", - "name": "role", - "type": "bytes32" + indexed: true, + internalType: 'bytes32', + name: 'role', + type: 'bytes32', }, { - "indexed": true, - "internalType": "address", - "name": "account", - "type": "address" + indexed: true, + internalType: 'address', + name: 'account', + type: 'address', }, { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - } + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, ], - "name": "RoleRevoked", - "type": "event" + name: 'RoleRevoked', + type: 'event', }, { - "anonymous": false, - "inputs": [ + anonymous: false, + inputs: [ { - "indexed": false, - "internalType": "uint256", - "name": "timestamp", - "type": "uint256" - } + indexed: false, + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', + }, ], - "name": "renounceOwnershipFinished", - "type": "event" + name: 'renounceOwnershipFinished', + type: 'event', }, { - "anonymous": false, - "inputs": [ + anonymous: false, + inputs: [ { - "indexed": false, - "internalType": "uint256", - "name": "timestamp", - "type": "uint256" + indexed: false, + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', }, { - "indexed": false, - "internalType": "uint256", - "name": "finishTimestamp", - "type": "uint256" - } + indexed: false, + internalType: 'uint256', + name: 'finishTimestamp', + type: 'uint256', + }, ], - "name": "renounceOwnershipStarted", - "type": "event" + name: 'renounceOwnershipStarted', + type: 'event', }, { - "anonymous": false, - "inputs": [ + anonymous: false, + inputs: [ { - "indexed": false, - "internalType": "uint256", - "name": "timestamp", - "type": "uint256" - } + indexed: false, + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', + }, ], - "name": "renounceOwnershipStopped", - "type": "event" + name: 'renounceOwnershipStopped', + type: 'event', }, { - "inputs": [], - "name": "DEFAULT_ADMIN_ROLE", - "outputs": [ + inputs: [], + name: 'DEFAULT_ADMIN_ROLE', + outputs: [ { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, ], - "stateMutability": "view", - "type": "function" + stateMutability: 'view', + type: 'function', }, { - "inputs": [], - "name": "acceptOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + inputs: [], + name: 'acceptOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', }, { - "inputs": [], - "name": "baseFeeConfiguration", - "outputs": [ + inputs: [], + name: 'baseFeeConfiguration', + outputs: [ { - "internalType": "uint16", - "name": "alpha1", - "type": "uint16" + internalType: 'uint16', + name: 'alpha1', + type: 'uint16', }, { - "internalType": "uint16", - "name": "alpha2", - "type": "uint16" + internalType: 'uint16', + name: 'alpha2', + type: 'uint16', }, { - "internalType": "uint32", - "name": "beta1", - "type": "uint32" + internalType: 'uint32', + name: 'beta1', + type: 'uint32', }, { - "internalType": "uint32", - "name": "beta2", - "type": "uint32" + internalType: 'uint32', + name: 'beta2', + type: 'uint32', }, { - "internalType": "uint16", - "name": "gamma1", - "type": "uint16" + internalType: 'uint16', + name: 'gamma1', + type: 'uint16', }, { - "internalType": "uint16", - "name": "gamma2", - "type": "uint16" + internalType: 'uint16', + name: 'gamma2', + type: 'uint16', }, { - "internalType": "uint16", - "name": "baseFee", - "type": "uint16" - } + internalType: 'uint16', + name: 'baseFee', + type: 'uint16', + }, ], - "stateMutability": "view", - "type": "function" + stateMutability: 'view', + type: 'function', }, { - "inputs": [], - "name": "communityVault", - "outputs": [ + inputs: [], + name: 'communityVault', + outputs: [ { - "internalType": "address", - "name": "", - "type": "address" - } + internalType: 'address', + name: '', + type: 'address', + }, ], - "stateMutability": "view", - "type": "function" + stateMutability: 'view', + type: 'function', }, { - "inputs": [ + inputs: [ { - "internalType": "address", - "name": "tokenA", - "type": "address" + internalType: 'address', + name: 'tokenA', + type: 'address', }, { - "internalType": "address", - "name": "tokenB", - "type": "address" - } + internalType: 'address', + name: 'tokenB', + type: 'address', + }, ], - "name": "createPool", - "outputs": [ + name: 'createPool', + outputs: [ { - "internalType": "address", - "name": "pool", - "type": "address" - } + internalType: 'address', + name: 'pool', + type: 'address', + }, ], - "stateMutability": "nonpayable", - "type": "function" + stateMutability: 'nonpayable', + type: 'function', }, { - "inputs": [], - "name": "defaultCommunityFee", - "outputs": [ + inputs: [], + name: 'defaultCommunityFee', + outputs: [ { - "internalType": "uint8", - "name": "", - "type": "uint8" - } + internalType: 'uint8', + name: '', + type: 'uint8', + }, ], - "stateMutability": "view", - "type": "function" + stateMutability: 'view', + type: 'function', }, { - "inputs": [], - "name": "farmingAddress", - "outputs": [ + inputs: [], + name: 'farmingAddress', + outputs: [ { - "internalType": "address", - "name": "", - "type": "address" - } + internalType: 'address', + name: '', + type: 'address', + }, ], - "stateMutability": "view", - "type": "function" + stateMutability: 'view', + type: 'function', }, { - "inputs": [ + inputs: [ { - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - } + internalType: 'bytes32', + name: 'role', + type: 'bytes32', + }, ], - "name": "getRoleAdmin", - "outputs": [ + name: 'getRoleAdmin', + outputs: [ { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, ], - "stateMutability": "view", - "type": "function" + stateMutability: 'view', + type: 'function', }, { - "inputs": [ + inputs: [ { - "internalType": "bytes32", - "name": "role", - "type": "bytes32" + internalType: 'bytes32', + name: 'role', + type: 'bytes32', }, { - "internalType": "uint256", - "name": "index", - "type": "uint256" - } + internalType: 'uint256', + name: 'index', + type: 'uint256', + }, ], - "name": "getRoleMember", - "outputs": [ + name: 'getRoleMember', + outputs: [ { - "internalType": "address", - "name": "", - "type": "address" - } + internalType: 'address', + name: '', + type: 'address', + }, ], - "stateMutability": "view", - "type": "function" + stateMutability: 'view', + type: 'function', }, { - "inputs": [ + inputs: [ { - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - } + internalType: 'bytes32', + name: 'role', + type: 'bytes32', + }, ], - "name": "getRoleMemberCount", - "outputs": [ + name: 'getRoleMemberCount', + outputs: [ { - "internalType": "uint256", - "name": "", - "type": "uint256" - } + internalType: 'uint256', + name: '', + type: 'uint256', + }, ], - "stateMutability": "view", - "type": "function" + stateMutability: 'view', + type: 'function', }, { - "inputs": [ + inputs: [ { - "internalType": "bytes32", - "name": "role", - "type": "bytes32" + internalType: 'bytes32', + name: 'role', + type: 'bytes32', }, { - "internalType": "address", - "name": "account", - "type": "address" - } + internalType: 'address', + name: 'account', + type: 'address', + }, ], - "name": "grantRole", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + name: 'grantRole', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', }, { - "inputs": [ + inputs: [ { - "internalType": "bytes32", - "name": "role", - "type": "bytes32" + internalType: 'bytes32', + name: 'role', + type: 'bytes32', }, { - "internalType": "address", - "name": "account", - "type": "address" - } + internalType: 'address', + name: 'account', + type: 'address', + }, ], - "name": "hasRole", - "outputs": [ + name: 'hasRole', + outputs: [ { - "internalType": "bool", - "name": "", - "type": "bool" - } + internalType: 'bool', + name: '', + type: 'bool', + }, ], - "stateMutability": "view", - "type": "function" + stateMutability: 'view', + type: 'function', }, { - "inputs": [ + inputs: [ { - "internalType": "bytes32", - "name": "role", - "type": "bytes32" + internalType: 'bytes32', + name: 'role', + type: 'bytes32', }, { - "internalType": "address", - "name": "account", - "type": "address" - } + internalType: 'address', + name: 'account', + type: 'address', + }, ], - "name": "hasRoleOrOwner", - "outputs": [ + name: 'hasRoleOrOwner', + outputs: [ { - "internalType": "bool", - "name": "", - "type": "bool" - } + internalType: 'bool', + name: '', + type: 'bool', + }, ], - "stateMutability": "view", - "type": "function" + stateMutability: 'view', + type: 'function', }, { - "inputs": [], - "name": "owner", - "outputs": [ + inputs: [], + name: 'owner', + outputs: [ { - "internalType": "address", - "name": "", - "type": "address" - } + internalType: 'address', + name: '', + type: 'address', + }, ], - "stateMutability": "view", - "type": "function" + stateMutability: 'view', + type: 'function', }, { - "inputs": [], - "name": "pendingOwner", - "outputs": [ + inputs: [], + name: 'pendingOwner', + outputs: [ { - "internalType": "address", - "name": "", - "type": "address" - } + internalType: 'address', + name: '', + type: 'address', + }, ], - "stateMutability": "view", - "type": "function" + stateMutability: 'view', + type: 'function', }, { - "inputs": [ + inputs: [ { - "internalType": "address", - "name": "", - "type": "address" + internalType: 'address', + name: '', + type: 'address', }, { - "internalType": "address", - "name": "", - "type": "address" - } + internalType: 'address', + name: '', + type: 'address', + }, ], - "name": "poolByPair", - "outputs": [ + name: 'poolByPair', + outputs: [ { - "internalType": "address", - "name": "", - "type": "address" - } + internalType: 'address', + name: '', + type: 'address', + }, ], - "stateMutability": "view", - "type": "function" + stateMutability: 'view', + type: 'function', }, { - "inputs": [], - "name": "poolDeployer", - "outputs": [ + inputs: [], + name: 'poolDeployer', + outputs: [ { - "internalType": "address", - "name": "", - "type": "address" - } + internalType: 'address', + name: '', + type: 'address', + }, ], - "stateMutability": "view", - "type": "function" + stateMutability: 'view', + type: 'function', }, { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + inputs: [], + name: 'renounceOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', }, { - "inputs": [], - "name": "renounceOwnershipStartTimestamp", - "outputs": [ + inputs: [], + name: 'renounceOwnershipStartTimestamp', + outputs: [ { - "internalType": "uint256", - "name": "", - "type": "uint256" - } + internalType: 'uint256', + name: '', + type: 'uint256', + }, ], - "stateMutability": "view", - "type": "function" + stateMutability: 'view', + type: 'function', }, { - "inputs": [ + inputs: [ { - "internalType": "bytes32", - "name": "role", - "type": "bytes32" + internalType: 'bytes32', + name: 'role', + type: 'bytes32', }, { - "internalType": "address", - "name": "account", - "type": "address" - } + internalType: 'address', + name: 'account', + type: 'address', + }, ], - "name": "renounceRole", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + name: 'renounceRole', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', }, { - "inputs": [ + inputs: [ { - "internalType": "bytes32", - "name": "role", - "type": "bytes32" + internalType: 'bytes32', + name: 'role', + type: 'bytes32', }, { - "internalType": "address", - "name": "account", - "type": "address" - } + internalType: 'address', + name: 'account', + type: 'address', + }, ], - "name": "revokeRole", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + name: 'revokeRole', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', }, { - "inputs": [ + inputs: [ { - "components": [ + components: [ { - "internalType": "uint16", - "name": "alpha1", - "type": "uint16" + internalType: 'uint16', + name: 'alpha1', + type: 'uint16', }, { - "internalType": "uint16", - "name": "alpha2", - "type": "uint16" + internalType: 'uint16', + name: 'alpha2', + type: 'uint16', }, { - "internalType": "uint32", - "name": "beta1", - "type": "uint32" + internalType: 'uint32', + name: 'beta1', + type: 'uint32', }, { - "internalType": "uint32", - "name": "beta2", - "type": "uint32" + internalType: 'uint32', + name: 'beta2', + type: 'uint32', }, { - "internalType": "uint16", - "name": "gamma1", - "type": "uint16" + internalType: 'uint16', + name: 'gamma1', + type: 'uint16', }, { - "internalType": "uint16", - "name": "gamma2", - "type": "uint16" + internalType: 'uint16', + name: 'gamma2', + type: 'uint16', }, { - "internalType": "uint16", - "name": "baseFee", - "type": "uint16" - } + internalType: 'uint16', + name: 'baseFee', + type: 'uint16', + }, ], - "internalType": "struct IAlgebraFeeConfiguration.Configuration", - "name": "_config", - "type": "tuple" - } + internalType: 'struct IAlgebraFeeConfiguration.Configuration', + name: '_config', + type: 'tuple', + }, ], - "name": "setBaseFeeConfiguration", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + name: 'setBaseFeeConfiguration', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', }, { - "inputs": [ + inputs: [ { - "internalType": "uint8", - "name": "newDefaultCommunityFee", - "type": "uint8" - } + internalType: 'uint8', + name: 'newDefaultCommunityFee', + type: 'uint8', + }, ], - "name": "setDefaultCommunityFee", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + name: 'setDefaultCommunityFee', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', }, { - "inputs": [ + inputs: [ { - "internalType": "address", - "name": "newFarmingAddress", - "type": "address" - } + internalType: 'address', + name: 'newFarmingAddress', + type: 'address', + }, ], - "name": "setFarmingAddress", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + name: 'setFarmingAddress', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', }, { - "inputs": [], - "name": "startRenounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + inputs: [], + name: 'startRenounceOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', }, { - "inputs": [], - "name": "stopRenounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + inputs: [], + name: 'stopRenounceOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', }, { - "inputs": [ + inputs: [ { - "internalType": "bytes4", - "name": "interfaceId", - "type": "bytes4" - } + internalType: 'bytes4', + name: 'interfaceId', + type: 'bytes4', + }, ], - "name": "supportsInterface", - "outputs": [ + name: 'supportsInterface', + outputs: [ { - "internalType": "bool", - "name": "", - "type": "bool" - } + internalType: 'bool', + name: '', + type: 'bool', + }, ], - "stateMutability": "view", - "type": "function" + stateMutability: 'view', + type: 'function', }, { - "inputs": [ + inputs: [ { - "internalType": "address", - "name": "newOwner", - "type": "address" - } + internalType: 'address', + name: 'newOwner', + type: 'address', + }, ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] as const + name: 'transferOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const; diff --git a/src/abis/farming/algebraEternalFarming.ts b/src/abis/farming/algebraEternalFarming.ts new file mode 100644 index 0000000..254a855 --- /dev/null +++ b/src/abis/farming/algebraEternalFarming.ts @@ -0,0 +1,1141 @@ +export const algebraEternalFarmingABI = [ + { + inputs: [ + { + internalType: 'contract IAlgebraPoolDeployer', + name: '_deployer', + type: 'address', + }, + { + internalType: 'contract INonfungiblePositionManager', + name: '_nonfungiblePositionManager', + type: 'address', + }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: [], + name: 'anotherFarmingIsActive', + type: 'error', + }, + { + inputs: [], + name: 'claimToZeroAddress', + type: 'error', + }, + { + inputs: [], + name: 'emergencyActivated', + type: 'error', + }, + { + inputs: [], + name: 'farmDoesNotExist', + type: 'error', + }, + { + inputs: [], + name: 'incentiveNotExist', + type: 'error', + }, + { + inputs: [], + name: 'incentiveStopped', + type: 'error', + }, + { + inputs: [], + name: 'invalidPool', + type: 'error', + }, + { + inputs: [], + name: 'invalidTokenAmount', + type: 'error', + }, + { + inputs: [], + name: 'minimalPositionWidthTooWide', + type: 'error', + }, + { + inputs: [], + name: 'pluginNotConnected', + type: 'error', + }, + { + inputs: [], + name: 'poolReentrancyLock', + type: 'error', + }, + { + inputs: [], + name: 'positionIsTooNarrow', + type: 'error', + }, + { + inputs: [], + name: 'reentrancyLock', + type: 'error', + }, + { + inputs: [], + name: 'tokenAlreadyFarmed', + type: 'error', + }, + { + inputs: [], + name: 'zeroLiquidity', + type: 'error', + }, + { + inputs: [], + name: 'zeroRewardAmount', + type: 'error', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'bool', + name: 'newStatus', + type: 'bool', + }, + ], + name: 'EmergencyWithdraw', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'contract IERC20Minimal', + name: 'rewardToken', + type: 'address', + }, + { + indexed: true, + internalType: 'contract IERC20Minimal', + name: 'bonusRewardToken', + type: 'address', + }, + { + indexed: true, + internalType: 'contract IAlgebraPool', + name: 'pool', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'virtualPool', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'reward', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'bonusReward', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint24', + name: 'minimalAllowedPositionWidth', + type: 'uint24', + }, + ], + name: 'EternalFarmingCreated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'incentiveId', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'address', + name: 'rewardAddress', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'bonusRewardToken', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'reward', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'bonusReward', + type: 'uint256', + }, + ], + name: 'FarmEnded', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'incentiveId', + type: 'bytes32', + }, + { + indexed: false, + internalType: 'uint128', + name: 'liquidity', + type: 'uint128', + }, + ], + name: 'FarmEntered', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'farmingCenter', + type: 'address', + }, + ], + name: 'FarmingCenter', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'bytes32', + name: 'incentiveId', + type: 'bytes32', + }, + ], + name: 'IncentiveDeactivated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'rewardAmount', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'bonusRewardAmount', + type: 'uint256', + }, + { + indexed: false, + internalType: 'bytes32', + name: 'incentiveId', + type: 'bytes32', + }, + ], + name: 'RewardAmountsDecreased', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'reward', + type: 'uint256', + }, + { + indexed: true, + internalType: 'address', + name: 'rewardAddress', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + ], + name: 'RewardClaimed', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'rewardAmount', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'bonusRewardAmount', + type: 'uint256', + }, + { + indexed: false, + internalType: 'bytes32', + name: 'incentiveId', + type: 'bytes32', + }, + ], + name: 'RewardsAdded', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'bytes32', + name: 'incentiveId', + type: 'bytes32', + }, + { + indexed: false, + internalType: 'uint256', + name: 'rewardAmount', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'bonusRewardAmount', + type: 'uint256', + }, + ], + name: 'RewardsCollected', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint128', + name: 'rewardRate', + type: 'uint128', + }, + { + indexed: false, + internalType: 'uint128', + name: 'bonusRewardRate', + type: 'uint128', + }, + { + indexed: false, + internalType: 'bytes32', + name: 'incentiveId', + type: 'bytes32', + }, + ], + name: 'RewardsRatesChanged', + type: 'event', + }, + { + inputs: [], + name: 'FARMINGS_ADMINISTRATOR_ROLE', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'INCENTIVE_MAKER_ROLE', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'contract IERC20Minimal', + name: 'rewardToken', + type: 'address', + }, + { + internalType: 'contract IERC20Minimal', + name: 'bonusRewardToken', + type: 'address', + }, + { + internalType: 'contract IAlgebraPool', + name: 'pool', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + ], + internalType: 'struct IncentiveKey', + name: 'key', + type: 'tuple', + }, + { + internalType: 'uint128', + name: 'rewardAmount', + type: 'uint128', + }, + { + internalType: 'uint128', + name: 'bonusRewardAmount', + type: 'uint128', + }, + ], + name: 'addRewards', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'contract IERC20Minimal', + name: 'rewardToken', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amountRequested', + type: 'uint256', + }, + ], + name: 'claimReward', + outputs: [ + { + internalType: 'uint256', + name: 'reward', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'contract IERC20Minimal', + name: 'rewardToken', + type: 'address', + }, + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amountRequested', + type: 'uint256', + }, + ], + name: 'claimRewardFrom', + outputs: [ + { + internalType: 'uint256', + name: 'reward', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'contract IERC20Minimal', + name: 'rewardToken', + type: 'address', + }, + { + internalType: 'contract IERC20Minimal', + name: 'bonusRewardToken', + type: 'address', + }, + { + internalType: 'contract IAlgebraPool', + name: 'pool', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + ], + internalType: 'struct IncentiveKey', + name: 'key', + type: 'tuple', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'address', + name: '_owner', + type: 'address', + }, + ], + name: 'collectRewards', + outputs: [ + { + internalType: 'uint256', + name: 'reward', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'bonusReward', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'contract IERC20Minimal', + name: 'rewardToken', + type: 'address', + }, + { + internalType: 'contract IERC20Minimal', + name: 'bonusRewardToken', + type: 'address', + }, + { + internalType: 'contract IAlgebraPool', + name: 'pool', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + ], + internalType: 'struct IncentiveKey', + name: 'key', + type: 'tuple', + }, + { + components: [ + { + internalType: 'uint128', + name: 'reward', + type: 'uint128', + }, + { + internalType: 'uint128', + name: 'bonusReward', + type: 'uint128', + }, + { + internalType: 'uint128', + name: 'rewardRate', + type: 'uint128', + }, + { + internalType: 'uint128', + name: 'bonusRewardRate', + type: 'uint128', + }, + { + internalType: 'uint24', + name: 'minimalPositionWidth', + type: 'uint24', + }, + ], + internalType: 'struct IAlgebraEternalFarming.IncentiveParams', + name: 'params', + type: 'tuple', + }, + { + internalType: 'address', + name: 'plugin', + type: 'address', + }, + ], + name: 'createEternalFarming', + outputs: [ + { + internalType: 'address', + name: 'virtualPool', + type: 'address', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'contract IERC20Minimal', + name: 'rewardToken', + type: 'address', + }, + { + internalType: 'contract IERC20Minimal', + name: 'bonusRewardToken', + type: 'address', + }, + { + internalType: 'contract IAlgebraPool', + name: 'pool', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + ], + internalType: 'struct IncentiveKey', + name: 'key', + type: 'tuple', + }, + ], + name: 'deactivateIncentive', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'contract IERC20Minimal', + name: 'rewardToken', + type: 'address', + }, + { + internalType: 'contract IERC20Minimal', + name: 'bonusRewardToken', + type: 'address', + }, + { + internalType: 'contract IAlgebraPool', + name: 'pool', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + ], + internalType: 'struct IncentiveKey', + name: 'key', + type: 'tuple', + }, + { + internalType: 'uint128', + name: 'rewardAmount', + type: 'uint128', + }, + { + internalType: 'uint128', + name: 'bonusRewardAmount', + type: 'uint128', + }, + ], + name: 'decreaseRewardsAmount', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'contract IERC20Minimal', + name: 'rewardToken', + type: 'address', + }, + { + internalType: 'contract IERC20Minimal', + name: 'bonusRewardToken', + type: 'address', + }, + { + internalType: 'contract IAlgebraPool', + name: 'pool', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + ], + internalType: 'struct IncentiveKey', + name: 'key', + type: 'tuple', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'enterFarming', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'contract IERC20Minimal', + name: 'rewardToken', + type: 'address', + }, + { + internalType: 'contract IERC20Minimal', + name: 'bonusRewardToken', + type: 'address', + }, + { + internalType: 'contract IAlgebraPool', + name: 'pool', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + ], + internalType: 'struct IncentiveKey', + name: 'key', + type: 'tuple', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'address', + name: '_owner', + type: 'address', + }, + ], + name: 'exitFarming', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'farmingCenter', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'incentiveId', + type: 'bytes32', + }, + ], + name: 'farms', + outputs: [ + { + internalType: 'uint128', + name: 'liquidity', + type: 'uint128', + }, + { + internalType: 'int24', + name: 'tickLower', + type: 'int24', + }, + { + internalType: 'int24', + name: 'tickUpper', + type: 'int24', + }, + { + internalType: 'uint256', + name: 'innerRewardGrowth0', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'innerRewardGrowth1', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'contract IERC20Minimal', + name: 'rewardToken', + type: 'address', + }, + { + internalType: 'contract IERC20Minimal', + name: 'bonusRewardToken', + type: 'address', + }, + { + internalType: 'contract IAlgebraPool', + name: 'pool', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + ], + internalType: 'struct IncentiveKey', + name: 'key', + type: 'tuple', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'getRewardInfo', + outputs: [ + { + internalType: 'uint256', + name: 'reward', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'bonusReward', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'incentiveId', + type: 'bytes32', + }, + ], + name: 'incentives', + outputs: [ + { + internalType: 'uint128', + name: 'totalReward', + type: 'uint128', + }, + { + internalType: 'uint128', + name: 'bonusReward', + type: 'uint128', + }, + { + internalType: 'address', + name: 'virtualPoolAddress', + type: 'address', + }, + { + internalType: 'uint24', + name: 'minimalPositionWidth', + type: 'uint24', + }, + { + internalType: 'bool', + name: 'deactivated', + type: 'bool', + }, + { + internalType: 'address', + name: 'pluginAddress', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'isEmergencyWithdrawActivated', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'incentiveId', + type: 'bytes32', + }, + ], + name: 'isIncentiveDeactivated', + outputs: [ + { + internalType: 'bool', + name: 'res', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'nonfungiblePositionManager', + outputs: [ + { + internalType: 'contract INonfungiblePositionManager', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'numOfIncentives', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'contract IERC20Minimal', + name: 'rewardToken', + type: 'address', + }, + ], + name: 'rewards', + outputs: [ + { + internalType: 'uint256', + name: 'rewardAmount', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bool', + name: 'newStatus', + type: 'bool', + }, + ], + name: 'setEmergencyWithdrawStatus', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_farmingCenter', + type: 'address', + }, + ], + name: 'setFarmingCenterAddress', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'contract IERC20Minimal', + name: 'rewardToken', + type: 'address', + }, + { + internalType: 'contract IERC20Minimal', + name: 'bonusRewardToken', + type: 'address', + }, + { + internalType: 'contract IAlgebraPool', + name: 'pool', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + ], + internalType: 'struct IncentiveKey', + name: 'key', + type: 'tuple', + }, + { + internalType: 'uint128', + name: 'rewardRate', + type: 'uint128', + }, + { + internalType: 'uint128', + name: 'bonusRewardRate', + type: 'uint128', + }, + ], + name: 'setRates', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const; diff --git a/src/abis/farming/farmingCenter.ts b/src/abis/farming/farmingCenter.ts new file mode 100644 index 0000000..942f53e --- /dev/null +++ b/src/abis/farming/farmingCenter.ts @@ -0,0 +1,392 @@ +export const farmingCenterABI = [ + { + inputs: [ + { + internalType: 'contract IAlgebraEternalFarming', + name: '_eternalFarming', + type: 'address', + }, + { + internalType: 'contract INonfungiblePositionManager', + name: '_nonfungiblePositionManager', + type: 'address', + }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'int256', + name: 'liquidityDelta', + type: 'int256', + }, + ], + name: 'applyLiquidityDelta', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'burnPosition', + outputs: [ + { + internalType: 'bool', + name: 'success', + type: 'bool', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'contract IERC20Minimal', + name: 'rewardToken', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amountRequested', + type: 'uint256', + }, + ], + name: 'claimReward', + outputs: [ + { + internalType: 'uint256', + name: 'reward', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'contract IERC20Minimal', + name: 'rewardToken', + type: 'address', + }, + { + internalType: 'contract IERC20Minimal', + name: 'bonusRewardToken', + type: 'address', + }, + { + internalType: 'contract IAlgebraPool', + name: 'pool', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + ], + internalType: 'struct IncentiveKey', + name: 'key', + type: 'tuple', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'collectRewards', + outputs: [ + { + internalType: 'uint256', + name: 'reward', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'bonusReward', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'contract IAlgebraPool', + name: 'pool', + type: 'address', + }, + { + internalType: 'address', + name: 'newVirtualPool', + type: 'address', + }, + ], + name: 'connectVirtualPool', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'liquidityDelta', + type: 'uint256', + }, + ], + name: 'decreaseLiquidity', + outputs: [ + { + internalType: 'bool', + name: 'success', + type: 'bool', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + name: 'deposits', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'contract IERC20Minimal', + name: 'rewardToken', + type: 'address', + }, + { + internalType: 'contract IERC20Minimal', + name: 'bonusRewardToken', + type: 'address', + }, + { + internalType: 'contract IAlgebraPool', + name: 'pool', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + ], + internalType: 'struct IncentiveKey', + name: 'key', + type: 'tuple', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'enterFarming', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'eternalFarming', + outputs: [ + { + internalType: 'contract IAlgebraEternalFarming', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'contract IERC20Minimal', + name: 'rewardToken', + type: 'address', + }, + { + internalType: 'contract IERC20Minimal', + name: 'bonusRewardToken', + type: 'address', + }, + { + internalType: 'contract IAlgebraPool', + name: 'pool', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + ], + internalType: 'struct IncentiveKey', + name: 'key', + type: 'tuple', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'exitFarming', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + name: 'incentiveKeys', + outputs: [ + { + internalType: 'contract IERC20Minimal', + name: 'rewardToken', + type: 'address', + }, + { + internalType: 'contract IERC20Minimal', + name: 'bonusRewardToken', + type: 'address', + }, + { + internalType: 'contract IAlgebraPool', + name: 'pool', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'liquidityDelta', + type: 'uint256', + }, + ], + name: 'increaseLiquidity', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes[]', + name: 'data', + type: 'bytes[]', + }, + ], + name: 'multicall', + outputs: [ + { + internalType: 'bytes[]', + name: 'results', + type: 'bytes[]', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'nonfungiblePositionManager', + outputs: [ + { + internalType: 'contract INonfungiblePositionManager', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + name: 'virtualPoolAddresses', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, +] as const; diff --git a/src/abis/farming/index.ts b/src/abis/farming/index.ts new file mode 100644 index 0000000..e520655 --- /dev/null +++ b/src/abis/farming/index.ts @@ -0,0 +1,2 @@ +export * from './algebraEternalFarming'; +export * from './farmingCenter'; diff --git a/src/abis/index.ts b/src/abis/index.ts index 35a0f18..8cd8749 100644 --- a/src/abis/index.ts +++ b/src/abis/index.ts @@ -1,8 +1,10 @@ -export * from './algebraFactory' -export * from './algebraPool' -export * from './algebraPositionManager' -export * from './algebraQuoter' -export * from './algebraQuoterV2' -export * from './algebraRouter' -export * from './plugins' -export * from './tokens' \ No newline at end of file +export * from './algebraFactory'; +export * from './algebraPool'; +export * from './algebraPositionManager'; +export * from './algebraQuoter'; +export * from './algebraQuoterV2'; +export * from './algebraRouter'; +export * from './plugins'; +export * from './farming'; +export * from './tokens' + diff --git a/src/components/common/CardInfo/index.tsx b/src/components/common/CardInfo/index.tsx new file mode 100644 index 0000000..66788af --- /dev/null +++ b/src/components/common/CardInfo/index.tsx @@ -0,0 +1,35 @@ +import { cn } from '@/lib/utils'; +import React, { FC } from 'react'; + +interface CardInfoProps { + title: string; + additional?: string; + className?: string; + children?: React.ReactNode; +} + +const CardInfo: FC = ({ + title, + children, + additional, + className, +}) => { + return ( +
+

{title}

+
+
+ {children} +
+ {additional &&

{additional}

} +
+
+ ); +}; + +export default CardInfo; diff --git a/src/components/common/Table/dataTable.tsx b/src/components/common/Table/dataTable.tsx index 8d415a7..89bd223 100644 --- a/src/components/common/Table/dataTable.tsx +++ b/src/components/common/Table/dataTable.tsx @@ -1,50 +1,198 @@ -import { Button } from "@/components/ui/button"; -import { Skeleton } from "@/components/ui/skeleton"; -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; -import { ColumnDef, ColumnFiltersState, SortingState, flexRender, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table" -import { useState } from "react"; -import { useNavigate } from "react-router-dom"; +import { Button } from '@/components/ui/button'; +import { Skeleton } from '@/components/ui/skeleton'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table'; +import { + ColumnDef, + ColumnFiltersState, + SortingState, + flexRender, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, +} from '@tanstack/react-table'; +import React, { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; interface DataTableProps { - columns: ColumnDef[]; - data: TData[]; - selectedRow?: number; - action?: (args?: any) => void; - defaultSortingID?: string; - link?: string; - showPagination?: boolean; - searchID?: string; - loading?: boolean; + columns: ColumnDef[]; + data: TData[]; + selectedRow?: number; + action?: (args?: any) => void; + defaultSortingID?: string; + link?: string; + showPagination?: boolean; + searchID?: string; + loading?: boolean; } -const DataTable = ({ columns, data, selectedRow, action, link, defaultSortingID, showPagination = true, loading }: DataTableProps) => { +const DataTable = ({ + columns, + data, + selectedRow, + action, + link, + defaultSortingID, + showPagination = true, + loading, +}: DataTableProps) => { + const [sorting, setSorting] = useState( + defaultSortingID ? [{ id: defaultSortingID, desc: true }] : [] + ); + const [columnFilters, setColumnFilters] = useState([]); - const [sorting, setSorting] = useState(defaultSortingID ? [{id: defaultSortingID, desc: true}] : []) - const [columnFilters, setColumnFilters] = useState( - [] - ) + const navigate = useNavigate(); - const navigate = useNavigate() + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + onSortingChange: setSorting, + getSortedRowModel: getSortedRowModel(), + onColumnFiltersChange: setColumnFilters, + getFilteredRowModel: getFilteredRowModel(), + state: { + sorting, + columnFilters, + }, + }); - const table = useReactTable({ - data, - columns, - getCoreRowModel: getCoreRowModel(), - getPaginationRowModel: getPaginationRowModel(), - onSortingChange: setSorting, - getSortedRowModel: getSortedRowModel(), - onColumnFiltersChange: setColumnFilters, - getFilteredRowModel: getFilteredRowModel(), - state: { - sorting, - columnFilters + const farmingPositions = data.filter((pos: any) => pos.inFarming); + + const zeroLiquidityPositions = data.filter( + (pos: any) => pos.liquidityUSD === 0 + ); + + function renderFarmingPositions() { + if (farmingPositions.length === 0) return null; + let firstMatchFound = false; + + return table.getRowModel().rows.map((row: any) => { + const isSelected = Number(selectedRow) === Number(row.original.id); + if (row.original.inFarming) { + const renderIndex = firstMatchFound ? null : ( + + + On Farming + + + ); + firstMatchFound = true; + return ( + + {renderIndex} + { + if (action) { + action(row.original.id); + } else if (link) { + navigate(`/${link}/${row.original.id}`); + } + }} + > + {row.getVisibleCells().map((cell: any) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + ))} + + + ); + } + return null; + }); } - }) - if (loading) return + function renderZeroLiquidityPositions() { + if (zeroLiquidityPositions.length === 0) return null; + let firstMatchFound = false; - return <> - {/* {searchID &&
+ return table.getRowModel().rows.map((row: any) => { + const isSelected = Number(selectedRow) === Number(row.original.id); + if (row.original.liquidityUSD === 0) { + const renderIndex = firstMatchFound ? null : ( + + Closed + + ); + firstMatchFound = true; + return ( + + {renderIndex} + { + if (action) { + action(row.original.id); + } else if (link) { + navigate(`/${link}/${row.original.id}`); + } + }} + > + {row.getVisibleCells().map((cell: any) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + ))} + + + ); + } + return null; + }); + } + + if (loading) return ; + + return ( + <> + {/* {searchID &&
({ columns, data, selectedRow, action, link, de className="max-w-sm" />
} */} - - - {table.getHeaderGroups().map(headerGroup => - { - headerGroup.headers.map(header => - { - header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext()) - } - ) - } - )} - - - {table.getRowModel().rows?.length ? table.getRowModel().rows.map((row: any) => { - - const isSelected = Number(selectedRow) === Number(row.original.id) - - return { - - if (action) { - action(row.original.id) - } else if (link) { - navigate(`/${link}/${row.original.id}`) - } - - }} - > - {row.getVisibleCells().map((cell: any) => - {flexRender(cell.column.columnDef.cell, cell.getContext())} - +
+ + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ))} + + ))} + + + {!table.getRowModel().rows?.length ? ( + + + No results. + + + ) : ( + <> + {table.getRowModel().rows.map((row: any) => { + const isSelected = + Number(selectedRow) === + Number(row.original.id); + if ( + (row.original.liquidityUSD > 0 && + !row.original.inFarming) || + row.original.liquidityUSD === undefined + ) + return ( + { + if (action) { + action(row.original.id); + } else if (link) { + navigate( + `/${link}/${row.original.id}` + ); + } + }} + > + {row + .getVisibleCells() + .map((cell: any) => ( + + {flexRender( + cell.column + .columnDef.cell, + cell.getContext() + )} + + ))} + + ); + })} + {farmingPositions.length > 0 && + renderFarmingPositions()} + {zeroLiquidityPositions.length > 0 && + renderZeroLiquidityPositions()} + + )} + +
+ {showPagination && ( +
+ + +
)} - - }) : - - No results. - - - } - - - { - showPagination && -
- - -
} - -} + + ); +}; -const LoadingState = () =>
- {[1,2,3,4].map(v => )} -
+const LoadingState = () => ( +
+ {[1, 2, 3, 4].map((v) => ( + + ))} +
+); -export default DataTable \ No newline at end of file +export default DataTable; diff --git a/src/components/farming/ActiveFarming/index.tsx b/src/components/farming/ActiveFarming/index.tsx new file mode 100644 index 0000000..8ed41fb --- /dev/null +++ b/src/components/farming/ActiveFarming/index.tsx @@ -0,0 +1,280 @@ +import { useEffect, useState } from 'react'; +import { SelectPositionFarmModal } from '@/components/modals/SelectPositionFarmModal'; +import { isSameRewards } from '@/utils/farming/isSameRewards'; +import { Deposit } from '@/graphql/generated/graphql'; +import { Farming } from '@/types/farming-info'; +import { Button } from '@/components/ui/button'; +import CardInfo from '@/components/common/CardInfo'; +import { formatUnits } from 'viem'; +import { getFarmingRewards } from '@/utils/farming/getFarmingRewards'; +import { FormattedPosition } from '@/types/formatted-position'; +import CurrencyLogo from '@/components/common/CurrencyLogo'; +import { useCurrency } from '@/hooks/common/useCurrency'; +import { useAccount } from 'wagmi'; +import { useFarmHarvestAll } from '@/hooks/farming/useFarmHarvest'; +import Loader from '@/components/common/Loader'; +import { ADDRESS_ZERO } from '@cryptoalgebra/integral-sdk'; +import { useRewardEarnedUSD } from '@/hooks/farming/useRewardEarnedUSD'; + +interface ActiveFarmingProps { + farming: Farming; + deposits: Deposit[]; + positionsData: FormattedPosition[]; +} + +const ActiveFarming = ({ + farming, + deposits, + positionsData, +}: ActiveFarmingProps) => { + const { address: account } = useAccount(); + + const [rewardEarned, setRewardEarned] = useState(0n); + const [bonusRewardEarned, setBonusRewardEarned] = useState(0n); + + const isSameReward = isSameRewards( + farming.farming.rewardToken, + farming.farming.bonusRewardToken + ); + + const formattedRewardEarned = Number( + formatUnits(rewardEarned, farming.rewardToken.decimals) + ); + + const formattedBonusRewardEarned = Number( + formatUnits(bonusRewardEarned, farming.bonusRewardToken?.decimals) + ); + + const rewardEarnedUSD = useRewardEarnedUSD({ + token: farming.rewardToken, + reward: rewardEarned, + }); + + const bonusRewardEarnedUSD = useRewardEarnedUSD({ + token: farming.bonusRewardToken, + reward: bonusRewardEarned, + }); + + const rewardTokenCurrency = useCurrency(farming.farming.rewardToken); + const bonusRewardTokenCurrency = useCurrency( + farming.farming.bonusRewardToken + ); + + const TVL = deposits.reduce((acc, deposit) => { + const currentFormattedPosition = positionsData.find( + (position) => Number(position.id) === Number(deposit.id) + ); + if (deposit.eternalFarming !== null && currentFormattedPosition) { + return acc + currentFormattedPosition.liquidityUSD; + } else { + return acc; + } + }, 0); + + const formattedTVL = TVL.toFixed(2); + + const rewardRatePerDay = + Number( + formatUnits( + farming.farming.rewardRate, + farming.rewardToken.decimals + ) + ) * + 60 * + 60 * + 24; + + const bonusRewardRatePerDay = + Number( + formatUnits( + farming.farming.bonusRewardRate, + farming.bonusRewardToken?.decimals + ) + ) * + 60 * + 60 * + 24; + + const { isLoading, onHarvestAll, isSuccess } = useFarmHarvestAll( + { + rewardToken: farming.farming.rewardToken, + bonusRewardToken: farming.farming.bonusRewardToken, + pool: farming.farming.pool, + nonce: farming.farming.nonce, + account: account ?? ADDRESS_ZERO, + }, + deposits + ); + + const handleHarvestAll = async () => { + if (isLoading || !onHarvestAll) return; + onHarvestAll(); + }; + + useEffect(() => { + const promises: Promise<{ + reward: bigint; + bonusReward: bigint; + }>[] = []; + deposits.forEach((deposit) => { + if (deposit.eternalFarming !== null) { + promises.push( + getFarmingRewards({ + rewardToken: farming.farming.rewardToken, + bonusRewardToken: farming.farming.bonusRewardToken, + pool: farming.farming.pool, + nonce: farming.farming.nonce, + tokenId: BigInt(deposit.id), + }) + ); + } + }); + if (promises.length === 0) return; + Promise.all(promises).then((rewards) => { + setRewardEarned(0n); + setBonusRewardEarned(0n); + rewards.forEach((reward) => { + setRewardEarned((prev) => prev + reward.reward); + setBonusRewardEarned((prev) => prev + reward.bonusReward); + }); + }); + }, [deposits, farming, isSuccess]); + + return ( +
+
+
+
+ +

45%

+
+ +

${formattedTVL}

+
+
+ + +

+ $ + {(rewardEarnedUSD + bonusRewardEarnedUSD).toFixed( + 4 + )} +

+
+
+ + +
+
+ {isSameReward ? ( + <> + +

+ {`${( + rewardRatePerDay + + bonusRewardRatePerDay + ).toFixed(2)} ${ + farming.rewardToken.symbol + } / day`} +

+ + ) : ( +
+
+ +

+ {`${ + rewardRatePerDay.toFixed(2) === + '0.00' + ? '<0.01' + : rewardRatePerDay.toFixed( + 2 + ) + } ${ + farming.rewardToken.symbol + } / day`} +

+
+ {bonusRewardRatePerDay > 0 && ( +
+ +

+ {`${ + bonusRewardRatePerDay.toFixed( + 2 + ) === '0.00' + ? '<0.01' + : bonusRewardRatePerDay.toFixed( + 2 + ) + } ${ + farming.bonusRewardToken + ?.symbol + } / day`} +

+
+ )} +
+ )} +
+
+
+ +
+ + +
+
+
+ ); +}; + +export default ActiveFarming; diff --git a/src/components/farming/FarmingPositionCard/index.tsx b/src/components/farming/FarmingPositionCard/index.tsx new file mode 100644 index 0000000..0508d0c --- /dev/null +++ b/src/components/farming/FarmingPositionCard/index.tsx @@ -0,0 +1,61 @@ +import { Deposit } from '@/graphql/generated/graphql'; +import { cn } from '@/lib/utils'; + +interface FarmingPositionCardProps { + position: Deposit; + status: string; + className?: string; + onClick?: () => void; +} + +const FarmingPositionCard = ({ + position, + status, + className, + onClick, +}: FarmingPositionCardProps) => { + return ( +
+
+ {position.id} +
+
+

Position #{position.id}

+
+
+
+

{status}

+
+
+
+
+ ); +}; + +export default FarmingPositionCard; diff --git a/src/components/modals/SelectPositionFarmModal/index.tsx b/src/components/modals/SelectPositionFarmModal/index.tsx new file mode 100644 index 0000000..57cbea8 --- /dev/null +++ b/src/components/modals/SelectPositionFarmModal/index.tsx @@ -0,0 +1,171 @@ +import Loader from '@/components/common/Loader'; +import FarmingPositionCard from '@/components/farming/FarmingPositionCard'; +import { Button } from '@/components/ui/button'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/components/ui/dialog'; +import { Deposit } from '@/graphql/generated/graphql'; +import { useFarmApprove } from '@/hooks/farming/useFarmApprove'; +import { useFarmCheckApprove } from '@/hooks/farming/useFarmCheckApprove'; +import { cn } from '@/lib/utils'; +import { Farming } from '@/types/farming-info'; +import { FormattedPosition } from '@/types/formatted-position'; +import { useEffect, useState } from 'react'; +import { useFarmStake } from '@/hooks/farming/useFarmStake'; + +interface SelectPositionFarmModalProps { + positions: Deposit[]; + farming: Farming; + positionsData: FormattedPosition[]; + isHarvestLoading: boolean; +} + +export function SelectPositionFarmModal({ + positions, + farming, + positionsData, + isHarvestLoading, +}: SelectPositionFarmModalProps) { + const [selectedPosition, setSelectedPosition] = useState(); + const tokenId = selectedPosition ? BigInt(selectedPosition.id) : 0n; + + const { approved, isLoading: isApproving } = useFarmCheckApprove(tokenId); + + const { isLoading: isApproveLoading, onApprove } = useFarmApprove(tokenId); + + const { + isLoading: isStakeLoading, + onStake, + isSuccess, + } = useFarmStake({ + tokenId, + rewardToken: farming.farming.rewardToken, + bonusRewardToken: farming.farming.bonusRewardToken, + pool: farming.farming.pool, + nonce: farming.farming.nonce, + }); + + const handleApprove = async () => { + if (approved || !onApprove) return; + onApprove(); + }; + + const handleStake = async () => { + if (!approved || !onStake) return; + if (isStakeLoading || isApproveLoading) return; + onStake(); + }; + + const handleSelectPosition = (position: Deposit) => { + if (isStakeLoading || isApproveLoading || isApproving) return; + setSelectedPosition(position); + }; + + const availablePositions = positions.filter( + (position) => + position.eternalFarming === null && position.liquidity > 0n + ); + + useEffect(() => { + if (isSuccess) { + setSelectedPosition(null); + } + }, [isSuccess]); + + return ( + + + + + + + + Select Position + + + +
    + {availablePositions.length > 0 ? ( + availablePositions.map((position) => { + const currentFormattedPosition = positionsData.find( + (fposition) => + Number(fposition.id) === Number(position.id) + ); + if (!currentFormattedPosition) return; + return ( + + handleSelectPosition(position) + } + position={position} + status={ + currentFormattedPosition.outOfRange + ? 'Out of range' + : 'In range' + } + /> + ); + }) + ) : ( +

    + You don't have available positions for this pool +

    + )} +
+
+ {isApproving ? ( + + ) : selectedPosition && availablePositions.length > 0 ? ( + <> + + + + ) : ( + + )} +
+
+
+ ); +} diff --git a/src/components/pool/MyPositions/index.tsx b/src/components/pool/MyPositions/index.tsx index c771cef..7c56886 100644 --- a/src/components/pool/MyPositions/index.tsx +++ b/src/components/pool/MyPositions/index.tsx @@ -1,21 +1,31 @@ -import { myPositionsColumns } from "@/components/common/Table/myPositionsColumns" -import DataTable from "@/components/common/Table/dataTable" -import { Address } from "wagmi" -import { FormattedPosition } from "@/types/formatted-position" +import { myPositionsColumns } from '@/components/common/Table/myPositionsColumns'; +import DataTable from '@/components/common/Table/dataTable'; +import { Address } from 'wagmi'; +import { FormattedPosition } from '@/types/formatted-position'; interface MyPositionsProps { positions: FormattedPosition[]; poolId: Address | undefined; selectedPosition: number | undefined; - selectPosition: (positionId: number | null) => void; + selectPosition: (positionId: number | null) => void; } -const MyPositions = ({ positions, selectedPosition, selectPosition }: MyPositionsProps) => { +const MyPositions = ({ + positions, + selectedPosition, + selectPosition, +}: MyPositionsProps) => { + return ( +
+ +
+ ); +}; - return
- -
- -} - -export default MyPositions \ No newline at end of file +export default MyPositions; diff --git a/src/components/pool/PoolHeader/index.tsx b/src/components/pool/PoolHeader/index.tsx index 5a6e881..a59f07a 100644 --- a/src/components/pool/PoolHeader/index.tsx +++ b/src/components/pool/PoolHeader/index.tsx @@ -1,35 +1,34 @@ -import CurrencyLogo from "@/components/common/CurrencyLogo" -import PageTitle from "@/components/common/PageTitle" -import { Skeleton } from "@/components/ui/skeleton" -import { formatPercent } from "@/utils/common/formatPercent" -import { Pool } from "@cryptoalgebra/integral-sdk" +import CurrencyLogo from '@/components/common/CurrencyLogo'; +import PageTitle from '@/components/common/PageTitle'; +import { Skeleton } from '@/components/ui/skeleton'; +import { formatPercent } from '@/utils/common/formatPercent'; +import { Pool } from '@cryptoalgebra/integral-sdk'; interface PoolHeaderProps { - pool: Pool | null + pool: Pool | null; } const PoolHeader = ({ pool }: PoolHeaderProps) => { - - const [token0, token1] = pool ? [pool.token0, pool.token1] : [] - - const poolFee = pool && formatPercent.format(pool.fee / 10_00000) - - return
- -
- - + const [token0, token1] = pool ? [pool.token0, pool.token1] : []; + + const poolFee = pool && formatPercent.format(pool.fee / 10_00000); + + return ( +
+
+ + +
+ + {token0 && token1 ? ( + + {`${poolFee}`} + + ) : ( + + )}
+ ); +}; - {token0 && token1 ? - - {`${poolFee}`} - - : - } - -
- -} - -export default PoolHeader; \ No newline at end of file +export default PoolHeader; diff --git a/src/components/position/ActiveFarmingCard/index.tsx b/src/components/position/ActiveFarmingCard/index.tsx new file mode 100644 index 0000000..ddf725a --- /dev/null +++ b/src/components/position/ActiveFarmingCard/index.tsx @@ -0,0 +1,105 @@ +import { useEffect, useState } from 'react'; +import { Farming } from '@/types/farming-info'; +import { ADDRESS_ZERO } from '@cryptoalgebra/integral-sdk'; +import { useFarmHarvest } from '@/hooks/farming/useFarmHarvest'; +import { useFarmUnstake } from '@/hooks/farming/useFarmStake'; +import { useAccount } from 'wagmi'; +import { getFarmingRewards } from '@/utils/farming/getFarmingRewards'; +import { Button } from '@/components/ui/button'; +import Loader from '@/components/common/Loader'; +import { Deposit } from '@/graphql/generated/graphql'; +import { useRewardEarnedUSD } from '@/hooks/farming/useRewardEarnedUSD'; + +interface ActiveFarmingCardProps { + farming: Farming; + selectedPosition: Deposit; +} + +const ActiveFarmingCard = ({ + farming, + selectedPosition, +}: ActiveFarmingCardProps) => { + const { address: account } = useAccount(); + + const [rewardEarned, setRewardEarned] = useState(0n); + const [bonusRewardEarned, setBonusRewardEarned] = useState(0n); + + const rewardEarnedUSD = useRewardEarnedUSD({ + token: farming.rewardToken, + reward: rewardEarned, + }); + + const bonusRewardEarnedUSD = useRewardEarnedUSD({ + token: farming.bonusRewardToken, + reward: bonusRewardEarned, + }); + + const farmingRewards = (rewardEarnedUSD + bonusRewardEarnedUSD).toFixed(4); + + const farmingArgs = { + tokenId: BigInt(selectedPosition.id), + rewardToken: farming.farming.rewardToken, + bonusRewardToken: farming.farming.bonusRewardToken, + pool: farming.farming.pool, + nonce: farming.farming.nonce, + account: account ?? ADDRESS_ZERO, + }; + + const { + onHarvest, + isLoading: isHarvesting, + isSuccess: isHarvested, + } = useFarmHarvest(farmingArgs); + + const { onUnstake, isLoading: isUnstaking } = useFarmUnstake(farmingArgs); + + const handleUnstake = async () => { + if (!account) return; + if (!onUnstake) return; + onUnstake(); + }; + + const handleHarvest = async () => { + if (!account) return; + if (!onHarvest) return; + onHarvest(); + }; + + useEffect(() => { + if (!account) return; + getFarmingRewards(farmingArgs).then((rewards) => { + setRewardEarned(rewards.reward); + setBonusRewardEarned(rewards.bonusReward); + }); + }, [farming, account, selectedPosition, isHarvested]); + + return ( +
+
+
+
EARNED REWARDS
+
+ + ${farmingRewards} + +
+
+ +
+ +
+ ); +}; + +export default ActiveFarmingCard; diff --git a/src/components/position/ClosedFarmingCard/index.tsx b/src/components/position/ClosedFarmingCard/index.tsx new file mode 100644 index 0000000..0db6380 --- /dev/null +++ b/src/components/position/ClosedFarmingCard/index.tsx @@ -0,0 +1,38 @@ +import Loader from '@/components/common/Loader'; +import { Button } from '@/components/ui/button'; +import { EternalFarming } from '@/graphql/generated/graphql'; +import { useFarmUnstake } from '@/hooks/farming/useFarmStake'; +import { FormattedPosition } from '@/types/formatted-position'; +import { ADDRESS_ZERO } from '@cryptoalgebra/integral-sdk'; +import { useAccount } from 'wagmi'; + +interface ClosedFarmingCardProps { + positionInEndedFarming: EternalFarming; + selectedPosition: FormattedPosition; +} + +const ClosedFarmingCard = ({ + positionInEndedFarming, + selectedPosition, +}: ClosedFarmingCardProps) => { + const { address: account } = useAccount(); + + const farmingArgs = { + tokenId: BigInt(selectedPosition.id ?? 0), + rewardToken: positionInEndedFarming.rewardToken, + bonusRewardToken: positionInEndedFarming.bonusRewardToken, + pool: positionInEndedFarming.pool, + nonce: positionInEndedFarming.nonce, + account: account ?? ADDRESS_ZERO, + }; + + const { onUnstake, isLoading: isUnstaking } = useFarmUnstake(farmingArgs); + + return ( + + ); +}; + +export default ClosedFarmingCard; diff --git a/src/components/position/PositionCard/index.tsx b/src/components/position/PositionCard/index.tsx index 2762afb..78b9787 100644 --- a/src/components/position/PositionCard/index.tsx +++ b/src/components/position/PositionCard/index.tsx @@ -1,32 +1,53 @@ -import { usePool } from "@/hooks/pools/usePool"; -import { usePosition } from "@/hooks/positions/usePositions"; -import { INITIAL_POOL_FEE, Position } from "@cryptoalgebra/integral-sdk"; -import PositionNFT from "../PositionNFT"; -import { FormattedPosition } from "@/types/formatted-position"; -import { formatUSD } from "@/utils/common/formatUSD"; -import { Skeleton } from "@/components/ui/skeleton"; -import PositionRangeChart from "../PositionRangeChart"; -import TokenRatio from "@/components/create-position/TokenRatio"; -import { useDerivedMintInfo } from "@/state/mintStore"; -import CollectFees from "../CollectFees"; -import RemoveLiquidityModal from "@/components/modals/RemoveLiquidityModal"; +import { usePool } from '@/hooks/pools/usePool'; +import { + usePosition, + usePositionInFarming, +} from '@/hooks/positions/usePositions'; +import { INITIAL_POOL_FEE, Position } from '@cryptoalgebra/integral-sdk'; +import PositionNFT from '../PositionNFT'; +import { FormattedPosition } from '@/types/formatted-position'; +import { formatUSD } from '@/utils/common/formatUSD'; +import { Skeleton } from '@/components/ui/skeleton'; +import PositionRangeChart from '../PositionRangeChart'; +import TokenRatio from '@/components/create-position/TokenRatio'; +import { useDerivedMintInfo } from '@/state/mintStore'; +import CollectFees from '../CollectFees'; +import RemoveLiquidityModal from '@/components/modals/RemoveLiquidityModal'; +import { Farming } from '@/types/farming-info'; +import { EternalFarming } from '@/graphql/generated/graphql'; +import ActiveFarmingCard from '../ActiveFarmingCard'; +import ClosedFarmingCard from '../ClosedFarmingCard'; interface PositionCardProps { - selectedPosition: FormattedPosition | undefined + selectedPosition: FormattedPosition | undefined; + farming?: Farming | null; + closedFarmings?: EternalFarming[] | null; } -const PositionCard = ({ selectedPosition }: PositionCardProps) => { +const PositionCard = ({ + selectedPosition, + farming, + closedFarmings, +}: PositionCardProps) => { + const { loading, position } = usePosition(selectedPosition?.id); - const { loading, position } = usePosition(selectedPosition?.id) + const positionInFarming = usePositionInFarming(selectedPosition?.id); - const [, pool] = usePool(position?.pool) + const positionInEndedFarming = closedFarmings?.filter( + (closedFarming) => + closedFarming.id === positionInFarming?.eternalFarming + )[0]; - const positionEntity = pool && position && new Position({ - pool, - liquidity: position.liquidity.toString(), - tickLower: Number(position.tickLower), - tickUpper: Number(position.tickUpper) - }) + const [, pool] = usePool(position?.pool); + const positionEntity = + pool && + position && + new Position({ + pool, + liquidity: position.liquidity.toString(), + tickLower: Number(position.tickLower), + tickUpper: Number(position.tickUpper), + }); const mintInfo = useDerivedMintInfo( positionEntity?.amount0.currency, @@ -35,63 +56,97 @@ const PositionCard = ({ selectedPosition }: PositionCardProps) => { INITIAL_POOL_FEE, positionEntity?.amount0.currency, positionEntity || undefined - ) + ); - const [positionLiquidityUSD, positionFeesUSD, positionAPR] = selectedPosition ? [ - formatUSD.format(selectedPosition.liquidityUSD), - formatUSD.format(selectedPosition.feesUSD), - `${selectedPosition.apr.toFixed(2)}%` - ] : [] + const [positionLiquidityUSD, positionFeesUSD, positionAPR] = + selectedPosition + ? [ + formatUSD.format(selectedPosition.liquidityUSD), + formatUSD.format(selectedPosition.feesUSD), + `${selectedPosition.apr.toFixed(2)}%`, + ] + : []; - if (!selectedPosition || loading) return + if (!selectedPosition || loading) return; - return
-
-
- -
-
-

{`Position #${selectedPosition?.id}`}

-
-
-
LIQUIDITY
-
- {positionLiquidityUSD ? {positionLiquidityUSD} : } + return ( +
+
+
+ +
+
+

{`Position #${selectedPosition?.id}`}

+
+
+
LIQUIDITY
+
+ {positionLiquidityUSD ? ( + + {positionLiquidityUSD} + + ) : ( + + )} +
-
-
-
APR
-
- {positionAPR ? {positionAPR} : } +
+
APR
+
+ {positionAPR ? ( + + {positionAPR} + + ) : ( + + )} +
-
- - + + - { - positionEntity && -
-
- {`${positionEntity.amount0.toFixed(2)} ${positionEntity.amount0.currency.symbol}`} -
-
- {`${positionEntity.amount1.toFixed(2)} ${positionEntity.amount1.currency.symbol}`} + {positionEntity && ( +
+
+ {`${positionEntity.amount0.toFixed(2)} ${ + positionEntity.amount0.currency.symbol + }`} +
+
+ {`${positionEntity.amount1.toFixed(2)} ${ + positionEntity.amount1.currency.symbol + }`} +
+ )} + {pool && positionEntity && ( + + )} +
+
- } - { - pool && positionEntity && - - } -
- + {positionInFarming && farming && !positionInEndedFarming && ( + + )} + {positionInEndedFarming && ( + + )}
-
- -} + ); +}; -export default PositionCard \ No newline at end of file +export default PositionCard; diff --git a/src/constants/addresses.ts b/src/constants/addresses.ts index 4d5bc5a..85d4b34 100644 --- a/src/constants/addresses.ts +++ b/src/constants/addresses.ts @@ -1,15 +1,28 @@ -import { Address } from "viem"; +import { Address } from 'viem'; -export const POOL_INIT_CODE_HASH: Address = '0xf96d2474815c32e070cd63233f06af5413efc5dcb430aee4ff18cc29007c562d' +export const POOL_INIT_CODE_HASH: Address = + '0xf96d2474815c32e070cd63233f06af5413efc5dcb430aee4ff18cc29007c562d'; -export const ALGEBRA_FACTORY: Address = '0x6AD6A4f233F1E33613e996CCc17409B93fF8bf5f' +export const ALGEBRA_FACTORY: Address = + '0x6AD6A4f233F1E33613e996CCc17409B93fF8bf5f'; -export const ALGEBRA_POOL_DEPLOYER: Address = '0x69D57B9D705eaD73a5d2f2476C30c55bD755cc2F' +export const ALGEBRA_POOL_DEPLOYER: Address = + '0x69D57B9D705eaD73a5d2f2476C30c55bD755cc2F'; -export const ALGEBRA_POSITION_MANAGER: Address = '0x5AeFBA317BAba46EAF98Fd6f381d07673bcA6467' +export const ALGEBRA_POSITION_MANAGER: Address = + '0x5AeFBA317BAba46EAF98Fd6f381d07673bcA6467'; -export const ALGEBRA_QUOTER: Address = '0x38A5C36FA8c8c9E4649b51FCD61810B14e7ce047' +export const ALGEBRA_QUOTER: Address = + '0x38A5C36FA8c8c9E4649b51FCD61810B14e7ce047'; -export const ALGEBRA_QUOTER_V2: Address = '0x83D4a9Ea77a4dbA073cD90b30410Ac9F95F93E7C' +export const ALGEBRA_QUOTER_V2: Address = + '0x83D4a9Ea77a4dbA073cD90b30410Ac9F95F93E7C'; -export const ALGEBRA_ROUTER: Address = '0xEC250E6856e14A494cb1f0abC61d72348c79F418' \ No newline at end of file +export const ALGEBRA_ROUTER: Address = + '0xEC250E6856e14A494cb1f0abC61d72348c79F418'; + +export const ALGEBRA_ETERNAL_FARMING: Address = + '0x49a390a3dFd2d01389f799965F3af5961f87d228'; + +export const FARMING_CENTER: Address = + '0x37A4950b4ea0C46596404895c5027B088B0e70e7'; diff --git a/src/graphql/clients/index.tsx b/src/graphql/clients/index.tsx index 9d7d20f..b556446 100644 --- a/src/graphql/clients/index.tsx +++ b/src/graphql/clients/index.tsx @@ -1,4 +1,4 @@ -import { ApolloClient, InMemoryCache } from "@apollo/client"; +import { ApolloClient, InMemoryCache } from '@apollo/client'; export const infoClient = new ApolloClient({ uri: import.meta.env.VITE_INFO_GRAPH, @@ -7,5 +7,10 @@ export const infoClient = new ApolloClient({ export const blocksClient = new ApolloClient({ uri: import.meta.env.VITE_BLOCKS_GRAPH, - cache: new InMemoryCache() -}) + cache: new InMemoryCache(), +}); + +export const farmingClient = new ApolloClient({ + uri: import.meta.env.VITE_FARMING_GRAPH, + cache: new InMemoryCache(), +}); diff --git a/src/graphql/queries/farmings.ts b/src/graphql/queries/farmings.ts new file mode 100644 index 0000000..f9f7532 --- /dev/null +++ b/src/graphql/queries/farmings.ts @@ -0,0 +1,33 @@ +import { gql } from '@apollo/client'; + +export const ETERNAL_FARMINGS = gql` + query EternalFarmings($pool: Bytes) { + eternalFarmings(where: { pool: $pool }) { + id + reward + bonusReward + rewardRate + bonusRewardRate + rewardToken + bonusRewardToken + isDeactivated + nonce + minRangeLength + virtualPool + pool + } + } +`; + +export const DEPOSITS = gql` + query Deposits($owner: Bytes, $pool: Bytes) { + deposits(where: { owner: $owner, pool: $pool }) { + eternalFarming + id + liquidity + owner + pool + rangeLength + } + } +`; diff --git a/src/hooks/common/useTransactionAwait.tsx b/src/hooks/common/useTransactionAwait.tsx index b77cf47..fec6ee9 100644 --- a/src/hooks/common/useTransactionAwait.tsx +++ b/src/hooks/common/useTransactionAwait.tsx @@ -1,26 +1,39 @@ -import { ToastAction } from "@/components/ui/toast"; -import { useToast } from "@/components/ui/use-toast"; -import { ExternalLinkIcon } from "lucide-react"; -import { useEffect } from "react"; -import { Link, useNavigate } from "react-router-dom"; -import { Address, useWaitForTransaction } from "wagmi"; +import { ToastAction } from '@/components/ui/toast'; +import { useToast } from '@/components/ui/use-toast'; +import { ExternalLinkIcon } from 'lucide-react'; +import { useEffect } from 'react'; +import { Link, useNavigate } from 'react-router-dom'; +import { Address, useWaitForTransaction } from 'wagmi'; -const ViewTxOnExplorer = ({hash}: { hash: Address | undefined }) => hash ? - - View on explorer - - - : null +export const ViewTxOnExplorer = ({ hash }: { hash: Address | undefined }) => + hash ? ( + + + View on explorer + + + + ) : ( + <> + ); -export function useTransitionAwait(hash: Address | undefined, title: string, description?: string, redirectPath?: string) { +export function useTransitionAwait( + hash: Address | undefined, + title: string, + description?: string, + redirectPath?: string +) { + const { toast } = useToast(); - const { toast } = useToast() - - const navigate = useNavigate() + const navigate = useNavigate(); const { data, isError, isLoading, isSuccess } = useWaitForTransaction({ - hash - }) + hash, + }); useEffect(() => { if (isLoading) { @@ -28,9 +41,9 @@ export function useTransitionAwait(hash: Address | undefined, title: string, des title: title, description: description || 'Transaction was sent', action: , - }) + }); } - }, [isLoading]) + }, [isLoading]); useEffect(() => { if (isLoading) { @@ -38,9 +51,9 @@ export function useTransitionAwait(hash: Address | undefined, title: string, des title: title, description: description || 'Transaction failed', action: , - }) + }); } - }, [isError]) + }, [isError]); useEffect(() => { if (isSuccess) { @@ -48,18 +61,17 @@ export function useTransitionAwait(hash: Address | undefined, title: string, des title: title, description: description || 'Transaction confirmed', action: , - }) + }); if (redirectPath) { - navigate(redirectPath) + navigate(redirectPath); } } - }, [isSuccess]) + }, [isSuccess]); return { data, isError, isLoading, - isSuccess - } - -} \ No newline at end of file + isSuccess, + }; +} diff --git a/src/hooks/farming/useActiveFarming.ts b/src/hooks/farming/useActiveFarming.ts new file mode 100644 index 0000000..798a0d9 --- /dev/null +++ b/src/hooks/farming/useActiveFarming.ts @@ -0,0 +1,87 @@ +import { Address, useAccount } from 'wagmi'; +import { useClients } from '../graphql/useClients'; +import { useEffect, useState } from 'react'; +import { + useDepositsQuery, + useEternalFarmingsQuery, + useSingleTokenQuery, + SinglePoolQuery, +} from '@/graphql/generated/graphql'; +import { Farming } from '@/types/farming-info'; + +export function useActiveFarming({ + poolId, + poolInfo, +}: { + poolId: Address; + poolInfo: SinglePoolQuery | undefined; +}) { + const { address: account } = useAccount(); + + const [farmingInfo, setFarmingInfo] = useState(); + + const { farmingClient } = useClients(); + + const { data: farmings, loading: isFarmingLoading } = + useEternalFarmingsQuery({ + variables: { + pool: poolId, + }, + client: farmingClient, + skip: !poolInfo, + }); + + const activeFarming = farmings?.eternalFarmings.filter( + (farming) => !farming.isDeactivated + )[0]; + + const { data: rewardToken } = useSingleTokenQuery({ + skip: !activeFarming, + variables: { + tokenId: activeFarming?.rewardToken, + }, + }); + + const { data: bonusRewardToken } = useSingleTokenQuery({ + skip: !activeFarming || !activeFarming?.bonusRewardToken, + variables: { + tokenId: activeFarming?.bonusRewardToken, + }, + }); + + const { data: deposits, loading: areDepositsLoading } = useDepositsQuery({ + variables: { + owner: account, + pool: poolId, + }, + client: farmingClient, + skip: !poolInfo, + pollInterval: 5000, + }); + + useEffect(() => { + if (!farmings?.eternalFarmings) return; + if (!poolInfo) return; + if (!rewardToken) return; + if (!bonusRewardToken) return; + if (!activeFarming || !rewardToken.token) { + console.debug('Active farming not found'); + setFarmingInfo(null); + return; + } + + setFarmingInfo({ + farming: activeFarming, + rewardToken: rewardToken.token, + bonusRewardToken: bonusRewardToken.token ?? null, + pool: poolInfo.pool, + }); + }, [farmings, rewardToken, bonusRewardToken, poolInfo, activeFarming]); + + return { + farmingInfo, + deposits, + isFarmingLoading, + areDepositsLoading, + }; +} diff --git a/src/hooks/farming/useClosedFarmings.ts b/src/hooks/farming/useClosedFarmings.ts new file mode 100644 index 0000000..69e55b3 --- /dev/null +++ b/src/hooks/farming/useClosedFarmings.ts @@ -0,0 +1,44 @@ +import { + EternalFarming, + SinglePoolQuery, + useEternalFarmingsQuery, +} from '@/graphql/generated/graphql'; +import { useMemo, useState } from 'react'; +import { Address } from 'viem'; +import { useClients } from '../graphql/useClients'; + +export function useClosedFarmings({ + poolId, + poolInfo, +}: { + poolId: Address; + poolInfo: SinglePoolQuery | undefined; +}) { + const [closedFarmings, setClosedFarmings] = useState< + EternalFarming[] | null + >(); + + const { farmingClient } = useClients(); + + const { data: initialData, loading: isLoading } = useEternalFarmingsQuery({ + variables: { + pool: poolId, + }, + client: farmingClient, + skip: !poolInfo, + }); + + useMemo(() => { + if (initialData && initialData.eternalFarmings) { + const filteredFarmings = initialData.eternalFarmings.filter( + (farming) => farming.isDeactivated + ); + setClosedFarmings(filteredFarmings); + } + }, [initialData]); + + return { + closedFarmings, + isLoading, + }; +} diff --git a/src/hooks/farming/useFarmApprove.ts b/src/hooks/farming/useFarmApprove.ts new file mode 100644 index 0000000..907add8 --- /dev/null +++ b/src/hooks/farming/useFarmApprove.ts @@ -0,0 +1,41 @@ +import { + ALGEBRA_POSITION_MANAGER, + FARMING_CENTER, +} from '@/constants/addresses'; +import { algebraPositionManagerABI } from '@/generated'; +import { useContractWrite, usePrepareContractWrite } from 'wagmi'; +import { useTransitionAwait } from '../common/useTransactionAwait'; +import { useEffect } from 'react'; +import { useFarmCheckApprove } from './useFarmCheckApprove'; + +export function useFarmApprove(tokenId: bigint) { + const APPROVE = true; + + const { config } = usePrepareContractWrite({ + address: tokenId ? ALGEBRA_POSITION_MANAGER : undefined, + abi: algebraPositionManagerABI, + functionName: 'approveForFarming', + args: [tokenId, APPROVE, FARMING_CENTER], + }); + + const { data: data, writeAsync: onApprove } = useContractWrite(config); + + const { isLoading, isSuccess } = useTransitionAwait( + data?.hash, + `Approve Position #${tokenId}` + ); + + const { handleCheckApprove } = useFarmCheckApprove(tokenId); + + useEffect(() => { + if (isSuccess) { + handleCheckApprove(); + } + }, [isSuccess]); + + return { + isLoading, + isSuccess, + onApprove, + }; +} diff --git a/src/hooks/farming/useFarmCheckApprove.ts b/src/hooks/farming/useFarmCheckApprove.ts new file mode 100644 index 0000000..07c796a --- /dev/null +++ b/src/hooks/farming/useFarmCheckApprove.ts @@ -0,0 +1,25 @@ +import { useAlgebraPositionManagerFarmingApprovals } from '@/generated'; +import { ADDRESS_ZERO } from '@cryptoalgebra/integral-sdk'; +import { useEffect, useState } from 'react'; + +export function useFarmCheckApprove(tokenId: bigint) { + const [approved, setApproved] = useState(); + + const { + data, + isLoading: isApproveLoading, + refetch, + } = useAlgebraPositionManagerFarmingApprovals({ + args: [tokenId], + }); + + useEffect(() => { + setApproved(data !== ADDRESS_ZERO); + }, [tokenId, data]); + + return { + approved, + handleCheckApprove: refetch, + isLoading: approved === undefined || isApproveLoading, + }; +} diff --git a/src/hooks/farming/useFarmHarvest.ts b/src/hooks/farming/useFarmHarvest.ts new file mode 100644 index 0000000..c343e5f --- /dev/null +++ b/src/hooks/farming/useFarmHarvest.ts @@ -0,0 +1,111 @@ +import { FARMING_CENTER } from '@/constants/addresses'; +import { farmingCenterABI } from '@/generated'; +import { getRewardsCalldata } from '@/utils/farming/getRewardsCalldata'; +import { Address, useContractWrite, usePrepareContractWrite } from 'wagmi'; +import { useTransitionAwait } from '../common/useTransactionAwait'; +import { encodeFunctionData } from 'viem'; +import { Deposit } from '@/graphql/generated/graphql'; + +export function useFarmHarvest({ + tokenId, + rewardToken, + bonusRewardToken, + pool, + nonce, + account, +}: { + tokenId: bigint; + rewardToken: Address; + bonusRewardToken: Address; + pool: Address; + nonce: bigint; + account: Address; +}) { + const calldata = getRewardsCalldata({ + rewardToken, + bonusRewardToken, + pool, + nonce, + tokenId, + account, + }); + + const { config } = usePrepareContractWrite({ + address: account && tokenId ? FARMING_CENTER : undefined, + abi: farmingCenterABI, + functionName: 'multicall', + args: [calldata], + }); + + const { data: data, writeAsync: onHarvest } = useContractWrite(config); + + const { isLoading, isSuccess } = useTransitionAwait( + data?.hash, + `Harvest Position #${tokenId}` + ); + + return { + isLoading, + isSuccess, + onHarvest, + }; +} + +export function useFarmHarvestAll( + { + rewardToken, + bonusRewardToken, + pool, + nonce, + account, + }: { + rewardToken: Address; + bonusRewardToken: Address; + pool: Address; + nonce: bigint; + account: Address; + }, + deposits: Deposit[] +) { + const calldatas: Address[] = []; + + deposits.forEach((deposit) => { + if (deposit.eternalFarming !== null) { + const rewardsCalldata = getRewardsCalldata({ + rewardToken, + bonusRewardToken, + pool, + nonce, + tokenId: BigInt(deposit.id), + account, + }); + + const calldata = encodeFunctionData({ + abi: farmingCenterABI, + functionName: 'multicall', + args: [rewardsCalldata], + }); + calldatas.push(calldata); + } + }); + + const { config } = usePrepareContractWrite({ + address: FARMING_CENTER, + abi: farmingCenterABI, + functionName: 'multicall', + args: [calldatas], + }); + + const { data: data, writeAsync: onHarvestAll } = useContractWrite(config); + + const { isLoading, isSuccess } = useTransitionAwait( + data?.hash, + `Harvest All Positions` + ); + + return { + isLoading, + isSuccess, + onHarvestAll, + }; +} diff --git a/src/hooks/farming/useFarmStake.ts b/src/hooks/farming/useFarmStake.ts new file mode 100644 index 0000000..baeec84 --- /dev/null +++ b/src/hooks/farming/useFarmStake.ts @@ -0,0 +1,121 @@ +import { FARMING_CENTER } from '@/constants/addresses'; +import { farmingCenterABI } from '@/generated'; +import { Address, useContractWrite, usePrepareContractWrite } from 'wagmi'; +import { useTransitionAwait } from '../common/useTransactionAwait'; +import { encodeFunctionData } from 'viem'; +import { MaxUint128 } from '@cryptoalgebra/integral-sdk'; +import { useFarmCheckApprove } from './useFarmCheckApprove'; + +export function useFarmStake({ + tokenId, + rewardToken, + bonusRewardToken, + pool, + nonce, +}: { + tokenId: bigint; + rewardToken: Address; + bonusRewardToken: Address; + pool: Address; + nonce: bigint; +}) { + const { approved } = useFarmCheckApprove(tokenId); + + const address = tokenId && approved ? FARMING_CENTER : undefined; + + const { config } = usePrepareContractWrite({ + address, + abi: farmingCenterABI, + functionName: 'enterFarming', + args: [ + { + rewardToken, + bonusRewardToken, + pool, + nonce, + }, + tokenId, + ], + }); + + const { data: data, writeAsync: onStake } = useContractWrite(config); + + const { isLoading, isSuccess } = useTransitionAwait( + data?.hash, + `Stake Position #${tokenId}` + ); + + return { + isLoading, + isSuccess, + onStake, + }; +} + +export function useFarmUnstake({ + tokenId, + rewardToken, + bonusRewardToken, + pool, + nonce, + account, +}: { + tokenId: bigint; + rewardToken: Address; + bonusRewardToken: Address; + pool: Address; + nonce: bigint; + account: Address; +}) { + const exitFarmingCalldata = encodeFunctionData({ + abi: farmingCenterABI, + functionName: 'exitFarming', + args: [ + { + rewardToken, + bonusRewardToken, + pool, + nonce, + }, + tokenId, + ], + }); + + const rewardClaimCalldata = encodeFunctionData({ + abi: farmingCenterABI, + functionName: 'claimReward', + args: [rewardToken, account, BigInt(MaxUint128)], + }); + + const bonusRewardClaimCalldata = encodeFunctionData({ + abi: farmingCenterABI, + functionName: 'claimReward', + args: [bonusRewardToken, account, BigInt(MaxUint128)], + }); + + const calldatas = [ + exitFarmingCalldata, + rewardClaimCalldata, + bonusRewardClaimCalldata, + ]; + + const { config } = usePrepareContractWrite({ + address: account && tokenId ? FARMING_CENTER : undefined, + abi: farmingCenterABI, + functionName: 'multicall', + args: [calldatas], + }); + + const { data: data, writeAsync: onUnstake } = useContractWrite(config); + + const { isLoading, isSuccess } = useTransitionAwait( + data?.hash, + `Unstake Position #${tokenId}` + ); + + return { + isLoading, + isSuccess, + onUnstake, + }; +} diff --git a/src/hooks/farming/useRewardEarnedUSD.ts b/src/hooks/farming/useRewardEarnedUSD.ts new file mode 100644 index 0000000..57efadc --- /dev/null +++ b/src/hooks/farming/useRewardEarnedUSD.ts @@ -0,0 +1,29 @@ +import { + TokenFieldsFragment, + useNativePriceQuery, +} from '@/graphql/generated/graphql'; +import { useMemo } from 'react'; +import { formatUnits } from 'viem'; + +export function useRewardEarnedUSD({ + token, + reward, +}: { + token: TokenFieldsFragment | null; + reward: bigint; +}): number { + const { data: nativePrice } = useNativePriceQuery(); + + return useMemo(() => { + const formattedRewardEarned = Number( + formatUnits(reward, token?.decimals) + ); + + const rewardUSD = + token?.derivedMatic * + formattedRewardEarned * + nativePrice?.bundles[0].maticPriceUSD; + + return rewardUSD; + }, [nativePrice, token, reward]); +} diff --git a/src/hooks/graphql/useClients.ts b/src/hooks/graphql/useClients.ts index 2b27fb6..39a447a 100644 --- a/src/hooks/graphql/useClients.ts +++ b/src/hooks/graphql/useClients.ts @@ -1,10 +1,9 @@ -import { infoClient, blocksClient } from "@/graphql/clients"; +import { infoClient, blocksClient, farmingClient } from '@/graphql/clients'; export function useClients() { - return { infoClient, - blocksClient - } - -} \ No newline at end of file + blocksClient, + farmingClient, + }; +} diff --git a/src/hooks/positions/usePositions.ts b/src/hooks/positions/usePositions.ts index d4f2497..24d5fb8 100644 --- a/src/hooks/positions/usePositions.ts +++ b/src/hooks/positions/usePositions.ts @@ -1,10 +1,12 @@ -import { algebraPositionManagerABI } from "@/abis" -import { ALGEBRA_POSITION_MANAGER } from "@/constants/addresses" -import { DEFAULT_CHAIN_ID } from "@/constants/default-chain-id" -import { useAlgebraPositionManagerBalanceOf } from "@/generated" -import { Token, computePoolAddress } from "@cryptoalgebra/integral-sdk" -import { useMemo } from "react" -import { Address, useAccount, useContractReads } from "wagmi" +import { algebraPositionManagerABI } from '@/abis'; +import { ALGEBRA_POSITION_MANAGER } from '@/constants/addresses'; +import { DEFAULT_CHAIN_ID } from '@/constants/default-chain-id'; +import { useAlgebraPositionManagerBalanceOf } from '@/generated'; +import { farmingClient } from '@/graphql/clients'; +import { useDepositsQuery } from '@/graphql/generated/graphql'; +import { Token, computePoolAddress } from '@cryptoalgebra/integral-sdk'; +import { useMemo } from 'react'; +import { Address, useAccount, useContractReads } from 'wagmi'; export interface PositionFromTokenId { tokenId: number; @@ -22,125 +24,165 @@ export interface PositionFromTokenId { pool: Address; } -function usePositionsFromTokenIds(tokenIds: any[] | undefined): { isLoading: boolean; positions: PositionFromTokenId[] | undefined } { - - const inputs = useMemo(() => (tokenIds ? tokenIds.map((tokenId) => tokenId) : []), [tokenIds]) - - const { data: results, isLoading, isError, error } = useContractReads({ - contracts: inputs.map(x => ({ +function usePositionsFromTokenIds(tokenIds: any[] | undefined): { + isLoading: boolean; + positions: PositionFromTokenId[] | undefined; +} { + const inputs = useMemo( + () => (tokenIds ? tokenIds.map((tokenId) => tokenId) : []), + [tokenIds] + ); + + const { + data: results, + isLoading, + isError, + error, + } = useContractReads({ + contracts: inputs.map((x) => ({ address: ALGEBRA_POSITION_MANAGER, abi: algebraPositionManagerABI, functionName: 'positions', - args: [[Number(x)]] + args: [[Number(x)]], })), - cacheTime: 10_000 - }) + cacheTime: 10_000, + }); - const { address: account } = useAccount() + const { address: account } = useAccount(); const positions = useMemo(() => { if (!isLoading && !isError && tokenIds && !error) { - return results?.filter(v => !v.error).map((call, i) => { - const tokenId = tokenIds[i] - const result = call.result as any - - const pool = computePoolAddress({ - tokenA: new Token(DEFAULT_CHAIN_ID, result[2], 18), - tokenB: new Token(DEFAULT_CHAIN_ID, result[3], 18) - }) as Address - - return { - tokenId, - feeGrowthInside0LastX128: result[7], - feeGrowthInside1LastX128: result[8], - liquidity: result[6], - nonce: result[0], - operator: result[1], - tickLower: result[4], - tickUpper: result[5], - token0: result[2], - token1: result[3], - tokensOwed0: result[9], - tokensOwed1: result[10], - pool - } - }) + return results + ?.filter((v) => !v.error) + .map((call, i) => { + const tokenId = tokenIds[i]; + const result = call.result as any; + + const pool = computePoolAddress({ + tokenA: new Token(DEFAULT_CHAIN_ID, result[2], 18), + tokenB: new Token(DEFAULT_CHAIN_ID, result[3], 18), + }) as Address; + + return { + tokenId, + feeGrowthInside0LastX128: result[7], + feeGrowthInside1LastX128: result[8], + liquidity: result[6], + nonce: result[0], + operator: result[1], + tickLower: result[4], + tickUpper: result[5], + token0: result[2], + token1: result[3], + tokensOwed0: result[9], + tokensOwed1: result[10], + pool, + }; + }); } - return undefined - }, [isLoading, isError, error, results, tokenIds, account]) + return undefined; + }, [isLoading, isError, error, results, tokenIds, account]); return useMemo(() => { return { isLoading, - positions - } - }, [isLoading, positions]) + positions, + }; + }, [isLoading, positions]); } export function usePositions() { + const { address: account } = useAccount(); - const { address: account } = useAccount() - - const { data: balanceResult, isLoading: balanceLoading } = useAlgebraPositionManagerBalanceOf({ - args: account ? [account] : undefined, - cacheTime: 10_000 - }) + const { data: balanceResult, isLoading: balanceLoading } = + useAlgebraPositionManagerBalanceOf({ + args: account ? [account] : undefined, + cacheTime: 10_000, + }); const tokenIdsArgs: [Address, number][] = useMemo(() => { + if (!balanceResult || !account) return []; - if (!balanceResult || !account) return [] - - const tokenRequests: any[] = [] + const tokenRequests: any[] = []; for (let i = 0; i < balanceResult; i++) { - tokenRequests.push([account, i]) + tokenRequests.push([account, i]); } - return tokenRequests - }, [account, balanceResult]) + return tokenRequests; + }, [account, balanceResult]); - - const { data: tokenIdResults, isLoading: someTokenIdsLoading } = useContractReads({ - contracts: tokenIdsArgs.map((args) => ({ - address: ALGEBRA_POSITION_MANAGER, - abi: algebraPositionManagerABI, - functionName: 'tokenOfOwnerByIndex', - args - })), - cacheTime: 10_000 - }) + const { data: tokenIdResults, isLoading: someTokenIdsLoading } = + useContractReads({ + contracts: tokenIdsArgs.map((args) => ({ + address: ALGEBRA_POSITION_MANAGER, + abi: algebraPositionManagerABI, + functionName: 'tokenOfOwnerByIndex', + args, + })), + cacheTime: 10_000, + }); const tokenIds = useMemo(() => { if (account) { return tokenIdResults ?.map(({ result }) => result) .filter((result) => !!result) - .map((result) => result) + .map((result) => result); } - return [] - }, [account, tokenIdResults]) + return []; + }, [account, tokenIdResults]); - const { positions, isLoading: positionsLoading } = usePositionsFromTokenIds(tokenIds) + const { positions, isLoading: positionsLoading } = + usePositionsFromTokenIds(tokenIds); return { loading: someTokenIdsLoading || balanceLoading || positionsLoading, - positions - } + positions, + }; } -export function usePosition(tokenId: string | number | undefined): { loading: boolean; position: PositionFromTokenId | undefined } { - +export function usePosition(tokenId: string | number | undefined): { + loading: boolean; + position: PositionFromTokenId | undefined; +} { const tokenIdArr = useMemo(() => { - if (!tokenId) return - return [tokenId] - }, [tokenId]) + if (!tokenId) return; + return [tokenId]; + }, [tokenId]); - const { isLoading, positions } = usePositionsFromTokenIds(tokenIdArr) + const { isLoading, positions } = usePositionsFromTokenIds(tokenIdArr); return useMemo(() => { return { loading: isLoading, position: positions?.[0], - } - }, [isLoading, positions]) + }; + }, [isLoading, positions]); +} + +export function usePositionInFarming(tokenId: string | number | undefined) { + const { position } = usePosition(tokenId); + + const { address: account } = useAccount(); + + const { data: deposits } = useDepositsQuery({ + variables: { + owner: account ? account : undefined, + pool: position?.pool, + }, + client: farmingClient, + }); + + if (!deposits) return; + const openedPositions = deposits.deposits.filter( + (deposit) => deposit.eternalFarming !== null + ); + + const positionInFarming = openedPositions.find( + (deposit) => Number(deposit.id) === Number(tokenId) + ); + + if (!positionInFarming) return; + return positionInFarming; } diff --git a/src/main.tsx b/src/main.tsx index bae56fe..29866e1 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,56 +1,76 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' +import React from 'react'; +import ReactDOM from 'react-dom/client'; import { - createBrowserRouter, - Navigate, - RouterProvider, -} from "react-router-dom"; + createBrowserRouter, + Navigate, + RouterProvider, +} from 'react-router-dom'; -import App from './App.tsx' +import App from './App.tsx'; -import './index.css' +import './index.css'; -import SwapPage from "@/pages/Swap"; -import Page404 from "@/pages/Page404"; -import PoolsPage from "@/pages/Pools"; -import PoolPage from "@/pages/Pool"; -import NewPositionPage from "@/pages/NewPosition"; +import SwapPage from '@/pages/Swap'; +import Page404 from '@/pages/Page404'; +import PoolsPage from '@/pages/Pools'; +import PoolPage from '@/pages/Pool'; +import NewPositionPage from '@/pages/NewPosition'; -import { ApolloClient, ApolloProvider, InMemoryCache } from "@apollo/client"; +import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client'; const router = createBrowserRouter([ - { - path: "/", - element: , - errorElement: - }, - { - path: '/swap', - element: , - }, - { - path: '/pools', - element: - }, - { - path: '/pool/:pool', - element: - }, - { - path: '/pool/:pool/new-position', - element: - } + { + path: '/', + element: , + errorElement: , + }, + { + path: '/swap', + element: ( + + {' '} + {' '} + + ), + }, + { + path: '/pools', + element: ( + + {' '} + {' '} + + ), + }, + { + path: '/pool/:pool', + element: ( + + {' '} + {' '} + + ), + }, + { + path: '/pool/:pool/new-position', + element: ( + + {' '} + {' '} + + ), + }, ]); const client = new ApolloClient({ - uri: import.meta.env.VITE_INFO_GRAPH, - cache: new InMemoryCache(), + uri: import.meta.env.VITE_INFO_GRAPH, + cache: new InMemoryCache(), }); ReactDOM.createRoot(document.getElementById('root')!).render( - - - - - , -) + + + + + +); diff --git a/src/pages/Pool/index.tsx b/src/pages/Pool/index.tsx index de247dd..db82e6a 100644 --- a/src/pages/Pool/index.tsx +++ b/src/pages/Pool/index.tsx @@ -1,189 +1,302 @@ -import PageContainer from "@/components/common/PageContainer" -import MyPositions from "@/components/pool/MyPositions" -import MyPositionsToolbar from "@/components/pool/MyPositionsToolbar" -import PoolHeader from "@/components/pool/PoolHeader" -import PositionCard from "@/components/position/PositionCard" -import { Button } from "@/components/ui/button" -import { Skeleton } from "@/components/ui/skeleton" -import { useNativePriceQuery, usePoolFeeDataQuery, useSinglePoolQuery } from "@/graphql/generated/graphql" -import { usePool } from "@/hooks/pools/usePool" -import { usePositions } from "@/hooks/positions/usePositions" -import { FormattedPosition } from "@/types/formatted-position" -import { getPositionAPR } from "@/utils/positions/getPositionAPR" -import { getPositionFees } from "@/utils/positions/getPositionFees" -import { Position } from "@cryptoalgebra/integral-sdk" -import { useWeb3Modal } from "@web3modal/wagmi/react" -import { MoveRightIcon } from "lucide-react" -import { useEffect, useMemo, useState } from "react" -import { Link, useParams } from "react-router-dom" -import { Address, useAccount } from "wagmi" - +import PageContainer from '@/components/common/PageContainer'; +import ActiveFarming from '@/components/farming/ActiveFarming'; +import MyPositions from '@/components/pool/MyPositions'; +import MyPositionsToolbar from '@/components/pool/MyPositionsToolbar'; +import PoolHeader from '@/components/pool/PoolHeader'; +import PositionCard from '@/components/position/PositionCard'; +import { Button } from '@/components/ui/button'; +import { Skeleton } from '@/components/ui/skeleton'; +import { + useNativePriceQuery, + usePoolFeeDataQuery, + useSinglePoolQuery, +} from '@/graphql/generated/graphql'; +import { useActiveFarming } from '@/hooks/farming/useActiveFarming'; +import { useClosedFarmings } from '@/hooks/farming/useClosedFarmings'; +import { usePool } from '@/hooks/pools/usePool'; +import { usePositions } from '@/hooks/positions/usePositions'; +import { FormattedPosition } from '@/types/formatted-position'; +import { getPositionAPR } from '@/utils/positions/getPositionAPR'; +import { getPositionFees } from '@/utils/positions/getPositionFees'; +import { Position } from '@cryptoalgebra/integral-sdk'; +import { useWeb3Modal } from '@web3modal/wagmi/react'; +import { MoveRightIcon } from 'lucide-react'; +import { useEffect, useMemo, useState } from 'react'; +import { Link, useParams } from 'react-router-dom'; +import { Address, useAccount } from 'wagmi'; const PoolPage = () => { + const { address: account } = useAccount(); - const { address: account } = useAccount() - - const { pool: poolId } = useParams() as { pool: Address } + const { pool: poolId } = useParams() as { pool: Address }; - const [selectedPositionId, selectPosition] = useState() + const [selectedPositionId, selectPosition] = useState(); - const [, poolEntity] = usePool(poolId) + const [, poolEntity] = usePool(poolId); const { data: poolInfo } = useSinglePoolQuery({ variables: { - poolId - } - }) + poolId, + }, + }); const { data: poolFeeData } = usePoolFeeDataQuery({ variables: { - poolId - } - }) - - const { data: bundles } = useNativePriceQuery() + poolId, + }, + }); - const [positionsFees, setPositionsFees] = useState() - const [positionsAPRs, setPositionsAPRs] = useState() + const { data: bundles } = useNativePriceQuery(); + const nativePrice = bundles?.bundles[0].maticPriceUSD; - const { positions, loading: positionsLoading } = usePositions() + const { farmingInfo, deposits, isFarmingLoading, areDepositsLoading } = + useActiveFarming({ + poolId: poolId, + poolInfo: poolInfo, + }); - const filteredPositions = useMemo(() => { + const { closedFarmings } = useClosedFarmings({ + poolId: poolId, + poolInfo: poolInfo, + }); - if (!positions || !poolEntity) return [] + const [positionsFees, setPositionsFees] = useState(); + const [positionsAPRs, setPositionsAPRs] = useState(); - return positions.filter(({ pool }) => pool.toLowerCase() === poolId.toLowerCase()).map(position => ({ - positionId: position.tokenId, - position: new Position({ - pool: poolEntity, - liquidity: position.liquidity.toString(), - tickLower: Number(position.tickLower), - tickUpper: Number(position.tickUpper) - }) - })) + const { positions, loading: positionsLoading } = usePositions(); - }, [positions, poolEntity]) + const filteredPositions = useMemo(() => { + if (!positions || !poolEntity) return []; + + return positions + .filter(({ pool }) => pool.toLowerCase() === poolId.toLowerCase()) + .map((position) => ({ + positionId: position.tokenId, + position: new Position({ + pool: poolEntity, + liquidity: position.liquidity.toString(), + tickLower: Number(position.tickLower), + tickUpper: Number(position.tickUpper), + }), + })); + }, [positions, poolEntity]); useEffect(() => { - async function getPositionsFees() { - const fees = await Promise.all(filteredPositions.map(({ positionId, position }) => getPositionFees(position.pool, positionId))) - setPositionsFees(fees) + const fees = await Promise.all( + filteredPositions.map(({ positionId, position }) => + getPositionFees(position.pool, positionId) + ) + ); + setPositionsFees(fees); } - if (filteredPositions) getPositionsFees() - - }, [filteredPositions]) + if (filteredPositions) getPositionsFees(); + }, [filteredPositions]); useEffect(() => { - async function getPositionsAPRs() { - const nativePrice = bundles?.bundles[0].maticPriceUSD - const aprs = await Promise.all(filteredPositions.map(({ position }) => getPositionAPR(poolId, position, poolInfo?.pool, poolFeeData?.poolDayDatas, nativePrice))) - setPositionsAPRs(aprs) + const aprs = await Promise.all( + filteredPositions.map(({ position }) => + getPositionAPR( + poolId, + position, + poolInfo?.pool, + poolFeeData?.poolDayDatas, + nativePrice + ) + ) + ); + setPositionsAPRs(aprs); } - if (filteredPositions && poolInfo?.pool && poolFeeData?.poolDayDatas && bundles?.bundles && poolId) getPositionsAPRs() - - }, [filteredPositions, poolInfo, poolId, poolFeeData, bundles]) + if ( + filteredPositions && + poolInfo?.pool && + poolFeeData?.poolDayDatas && + bundles?.bundles && + poolId + ) + getPositionsAPRs(); + }, [filteredPositions, poolInfo, poolId, poolFeeData, bundles]); const formatLiquidityUSD = (position: Position) => { - if (!poolInfo?.pool) return 0 + if (!poolInfo?.pool) return 0; - const amount0USD = Number(position.amount0.toSignificant()) * Number(poolInfo.pool.token1Price) - const amount1USD = Number(position.amount1.toSignificant()) * Number(poolInfo.pool.token0Price) + const amount0USD = + Number(position.amount0.toSignificant()) * + (Number(poolInfo.pool.token0.derivedMatic) * + (Number(nativePrice) || 0)); + const amount1USD = + Number(position.amount1.toSignificant()) * + (Number(poolInfo.pool.token1.derivedMatic) * + (Number(nativePrice) || 0)); - return amount0USD + amount1USD - } + return amount0USD + amount1USD; + }; const formatFeesUSD = (idx: number) => { - if (!positionsFees || !positionsFees[idx] || !poolInfo?.pool) return 0 + if (!positionsFees || !positionsFees[idx] || !poolInfo?.pool) return 0; - const fees0USD = positionsFees[idx][0] ? Number(positionsFees[idx][0].toSignificant()) * Number(poolInfo.pool.token0Price) : 0 - const fees1USD = positionsFees[idx][1] ? Number(positionsFees[idx][1].toSignificant()) * Number(poolInfo.pool.token1Price) : 0 + const fees0USD = positionsFees[idx][0] + ? Number(positionsFees[idx][0].toSignificant()) * + (Number(poolInfo.pool.token0.derivedMatic) * Number(nativePrice)) + : 0; + const fees1USD = positionsFees[idx][1] + ? Number(positionsFees[idx][1].toSignificant()) * + (Number(poolInfo.pool.token1.derivedMatic) * Number(nativePrice)) + : 0; - return fees0USD + fees1USD - } + return fees0USD + fees1USD; + }; const formatAPR = (idx: number) => { - if (!positionsAPRs || !positionsAPRs[idx]) return 0 - return positionsAPRs[idx] - } + if (!positionsAPRs || !positionsAPRs[idx]) return 0; + return positionsAPRs[idx]; + }; const positionsData = useMemo(() => { - - if (!filteredPositions || !poolEntity) return [] - - return filteredPositions.map(({ positionId, position }, idx) => ({ - id: positionId, - outOfRange: poolEntity.tickCurrent < position.tickLower || poolEntity.tickCurrent > position.tickUpper, - range: `${position.token0PriceLower.toFixed()} — ${position.token0PriceUpper.toFixed()}`, - liquidityUSD: formatLiquidityUSD(position), - feesUSD: formatFeesUSD(idx), - apr: formatAPR(idx) - }) as FormattedPosition) - - }, [filteredPositions, poolEntity, poolInfo, positionsFees, positionsAPRs]) + if (!filteredPositions || !poolEntity || !deposits) return []; + + return filteredPositions.map(({ positionId, position }, idx) => { + const currentPosition = deposits.deposits.find( + (deposit) => Number(deposit.id) === Number(positionId) + ); + return { + id: positionId, + outOfRange: + poolEntity.tickCurrent < position.tickLower || + poolEntity.tickCurrent > position.tickUpper, + range: `${position.token0PriceLower.toFixed()} — ${position.token0PriceUpper.toFixed()}`, + liquidityUSD: formatLiquidityUSD(position), + feesUSD: formatFeesUSD(idx), + apr: formatAPR(idx), + inFarming: Boolean(currentPosition?.eternalFarming), + } as FormattedPosition; + }); + }, [ + filteredPositions, + poolEntity, + poolInfo, + positionsFees, + positionsAPRs, + deposits, + ]); const selectedPosition = useMemo(() => { - - if (!positionsData || !selectedPositionId) return - - return positionsData.find(({ id }) => Number(id) === Number(selectedPositionId)) - - }, [selectedPositionId, positionsData]) - - const noPositions = !positionsLoading && positionsData.length === 0 && poolEntity - - return - - - -
- -
- { - !account ? : positionsLoading ? : noPositions ? : <> - - selectPosition(prev => prev === positionId ? null : positionId)} /> - - } -
- -
- + if (!positionsData || !selectedPositionId) return; + + return positionsData.find( + ({ id }) => Number(id) === Number(selectedPositionId) + ); + }, [selectedPositionId, positionsData]); + + const noPositions = + (!positionsLoading || !isFarmingLoading || !areDepositsLoading) && + positionsData.length === 0 && + poolEntity; + + return ( + + + +
+
+ {!account ? ( + + ) : positionsLoading || + isFarmingLoading || + areDepositsLoading ? ( + + ) : noPositions ? ( + + ) : ( + <> + + + selectPosition((prev) => + prev === positionId ? null : positionId + ) + } + /> + {farmingInfo && + deposits && + !isFarmingLoading && + !areDepositsLoading && ( +
+

+ Farming +

+ +
+ )} + + )} +
+ +
+ +
- -
- - -} - -const NoPositions = ({ poolId }: { poolId: Address }) =>
-

You don't have positions for this pool

-

Let's create one!

- -
+ + ); +}; + +const NoPositions = ({ poolId }: { poolId: Address }) => ( +
+

+ You don't have positions for this pool +

+

Let's create one!

+ +
+); const NoAccount = () => { - - const { open } = useWeb3Modal() - - return
-

Connect Wallet

-

Connect your account to view or create positions

- + const { open } = useWeb3Modal(); + + return ( +
+

Connect Wallet

+

+ Connect your account to view or create positions +

+ +
+ ); +}; + +const LoadingState = () => ( +
+ {[1, 2, 3, 4].map((v) => ( + + ))}
-} - -const LoadingState = () =>
- {[1, 2, 3, 4].map(v => )} -
- +); -export default PoolPage \ No newline at end of file +export default PoolPage; diff --git a/src/pages/Pools/index.tsx b/src/pages/Pools/index.tsx index b318fb6..7d8270b 100644 --- a/src/pages/Pools/index.tsx +++ b/src/pages/Pools/index.tsx @@ -1,26 +1,23 @@ -import PageContainer from "@/components/common/PageContainer" -import PageTitle from "@/components/common/PageTitle" -import PoolsList from "@/components/pools/PoolsList" -import { CreatePoolModal } from "@/components/modals/CreatePoolModal" +import PageContainer from '@/components/common/PageContainer'; +import PageTitle from '@/components/common/PageTitle'; +import PoolsList from '@/components/pools/PoolsList'; +import { CreatePoolModal } from '@/components/modals/CreatePoolModal'; const PoolsPage = () => { - - - return - -
- - -
- -
-
- + return ( + +
+ +
-
- - -} +
+
+ +
+
+ + ); +}; -export default PoolsPage \ No newline at end of file +export default PoolsPage; diff --git a/src/types/farming-info.ts b/src/types/farming-info.ts new file mode 100644 index 0000000..c5f5ed7 --- /dev/null +++ b/src/types/farming-info.ts @@ -0,0 +1,12 @@ +import { + EternalFarming, + SinglePoolQuery, + TokenFieldsFragment, +} from '@/graphql/generated/graphql'; + +export interface Farming { + farming: EternalFarming; + rewardToken: TokenFieldsFragment; + bonusRewardToken: TokenFieldsFragment | null; + pool: SinglePoolQuery['pool']; +} diff --git a/src/utils/farming/getFarmingRewards.ts b/src/utils/farming/getFarmingRewards.ts new file mode 100644 index 0000000..85371ae --- /dev/null +++ b/src/utils/farming/getFarmingRewards.ts @@ -0,0 +1,41 @@ +import { getFarmingCenter } from '@/generated'; +import { Address } from 'viem'; + +export async function getFarmingRewards({ + rewardToken, + bonusRewardToken, + pool, + nonce, + tokenId, +}: { + rewardToken: Address; + bonusRewardToken: Address; + pool: Address; + nonce: bigint; + tokenId: bigint; +}): Promise<{ reward: bigint; bonusReward: bigint }> { + try { + const farmingCenter = getFarmingCenter({}); + const { + result: [reward, bonusReward], + } = await farmingCenter.simulate.collectRewards([ + { + rewardToken, + bonusRewardToken, + pool, + nonce, + }, + tokenId, + ]); + return { + reward, + bonusReward, + }; + } catch (e) { + console.error(e); + return { + reward: 0n, + bonusReward: 0n, + }; + } +} diff --git a/src/utils/farming/getRewardsCalldata.ts b/src/utils/farming/getRewardsCalldata.ts new file mode 100644 index 0000000..d5d72a5 --- /dev/null +++ b/src/utils/farming/getRewardsCalldata.ts @@ -0,0 +1,62 @@ +import { farmingCenterABI } from '@/generated'; +import { MaxUint128 } from '@cryptoalgebra/integral-sdk'; +import { Address, encodeFunctionData } from 'viem'; +import { isSameRewards } from './isSameRewards'; + +export function getRewardsCalldata({ + rewardToken, + bonusRewardToken, + pool, + nonce, + tokenId, + account, +}: { + rewardToken: Address; + bonusRewardToken: Address; + pool: Address; + nonce: bigint; + tokenId: bigint; + account: Address; +}): Address[] { + const collectRewardsCalldata = encodeFunctionData({ + abi: farmingCenterABI, + functionName: 'collectRewards', + args: [ + { + rewardToken, + bonusRewardToken, + pool, + nonce, + }, + tokenId, + ], + }); + + const rewardClaimCalldata = encodeFunctionData({ + abi: farmingCenterABI, + functionName: 'claimReward', + args: [rewardToken, account, BigInt(MaxUint128)], + }); + + const bonusRewardClaimCalldata = encodeFunctionData({ + abi: farmingCenterABI, + functionName: 'claimReward', + args: [bonusRewardToken, account, BigInt(MaxUint128)], + }); + + let calldata; + + const isSameReward = isSameRewards(rewardToken, bonusRewardToken); + + if (isSameReward) { + calldata = [ + collectRewardsCalldata, + rewardClaimCalldata, + bonusRewardClaimCalldata, + ]; + } else { + calldata = [collectRewardsCalldata, rewardClaimCalldata]; + } + + return calldata; +} diff --git a/src/utils/farming/isSameRewards.ts b/src/utils/farming/isSameRewards.ts new file mode 100644 index 0000000..010026b --- /dev/null +++ b/src/utils/farming/isSameRewards.ts @@ -0,0 +1,8 @@ +import { Address } from 'viem'; + +export const isSameRewards = ( + rewardToken: Address, + bonusRewardToken: Address +): boolean => { + return rewardToken.toLowerCase() === bonusRewardToken.toLowerCase(); +}; diff --git a/tailwind.config.js b/tailwind.config.js index 3b06674..c7131d5 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,97 +1,106 @@ /** @type {import('tailwindcss').Config} */ module.exports = { - darkMode: ["class"], - content: [ - './pages/**/*.{ts,tsx}', - './components/**/*.{ts,tsx}', - './app/**/*.{ts,tsx}', - './src/**/*.{ts,tsx}', - ], - theme: { - container: { - center: true, - padding: "2rem", - screens: { - "2xl": "1400px", - }, - }, - extend: { - colors: { - border: "#5F5F82", - input: "hsl(var(--input))", - ring: "hsl(var(--ring))", - background: "hsl(var(--background))", - foreground: "hsl(var(--foreground))", - primary: { - DEFAULT: "hsl(var(--primary))", - foreground: "hsl(var(--primary-foreground))", - button: "#2797ff", - text: "#56adff" - }, - secondary: { - DEFAULT: "hsl(var(--secondary))", - foreground: "hsl(var(--secondary-foreground))", - }, - destructive: { - DEFAULT: "hsl(var(--destructive))", - foreground: "hsl(var(--destructive-foreground))", - }, - muted: { - DEFAULT: "hsl(var(--muted))", - foreground: "hsl(var(--muted-foreground))", - primary: "#0a2b49" - }, - accent: { - DEFAULT: "hsl(var(--accent))", - foreground: "hsl(var(--accent-foreground))", - }, - popover: { - DEFAULT: "hsl(var(--popover))", - foreground: "hsl(var(--popover-foreground))", + darkMode: ['class'], + content: [ + './pages/**/*.{ts,tsx}', + './components/**/*.{ts,tsx}', + './app/**/*.{ts,tsx}', + './src/**/*.{ts,tsx}', + ], + theme: { + screens: { + xs: '380px', + sm: '640px', + md: '768px', + lg: '1024px', + xl: '1280px', + '2xl': '1400px', }, - card: { - DEFAULT: "#1A1D2B", - foreground: "hsl(var(--card-foreground))", - hover: "#2e3242", - dark: "#101321", - light: "#31333e" - } - }, - borderRadius: { - lg: "var(--radius)", - md: "calc(var(--radius) - 2px)", - sm: "calc(var(--radius) - 4px)", - }, - keyframes: { - "accordion-down": { - from: { height: 0 }, - to: { height: "var(--radix-accordion-content-height)" }, + container: { + center: true, + padding: '2rem', + screens: { + '2xl': '1400px', + }, }, - "accordion-up": { - from: { height: "var(--radix-accordion-content-height)" }, - to: { height: 0 }, + extend: { + colors: { + border: '#5F5F82', + input: 'hsl(var(--input))', + ring: 'hsl(var(--ring))', + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))', + button: '#2797ff', + text: '#56adff', + }, + secondary: { + DEFAULT: 'hsl(var(--secondary))', + foreground: 'hsl(var(--secondary-foreground))', + }, + destructive: { + DEFAULT: 'hsl(var(--destructive))', + foreground: 'hsl(var(--destructive-foreground))', + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))', + primary: '#0a2b49', + }, + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))', + }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))', + }, + card: { + DEFAULT: '#1A1D2B', + foreground: 'hsl(var(--card-foreground))', + hover: '#2e3242', + dark: '#101321', + light: '#31333e', + }, + }, + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)', + }, + keyframes: { + 'accordion-down': { + from: { height: 0 }, + to: { height: 'var(--radix-accordion-content-height)' }, + }, + 'accordion-up': { + from: { height: 'var(--radix-accordion-content-height)' }, + to: { height: 0 }, + }, + 'fade-in': { + '0%': { opacity: 0, transform: 'translateY(10px)' }, + '100%': { opacity: 1, transform: 'translateY(0)' }, + }, + }, + animation: { + 'accordion-down': 'accordion-down 0.2s ease-out', + 'accordion-up': 'accordion-up 0.2s ease-out', + 'fade-in': 'fade-in 0.2s ease-out', + }, + backgroundImage: { + 'card-gradient': + 'linear-gradient(224deg, #1A1D2B 1.67%, #090B15 97.94%)', + }, + borderColor: { + 'card-border': '#5F5F82', + }, + dropShadow: { + cyan: '0 0 5px rgba(7, 142, 253, 0.8)', + pink: '0 0 5px rgba(255, 120, 217, 0.7)', + }, }, - "fade-in": { - '0%': { opacity: 0, transform: 'translateY(10px)' }, - '100%': { opacity: 1, transform: 'translateY(0)' } - } - }, - animation: { - "accordion-down": "accordion-down 0.2s ease-out", - "accordion-up": "accordion-up 0.2s ease-out", - "fade-in": "fade-in 0.2s ease-out" - }, - backgroundImage: { - "card-gradient": "linear-gradient(224deg, #1A1D2B 1.67%, #090B15 97.94%)" - }, - borderColor: { - "card-border": "#5F5F82" - }, - dropShadow: { - "cyan": "0 0 5px rgba(7, 142, 253, 0.8)", - "pink": "0 0 5px rgba(255, 120, 217, 0.7)" - } }, - }, - plugins: [require("tailwindcss-animate")], -} \ No newline at end of file + plugins: [require('tailwindcss-animate')], +}; diff --git a/wagmi.config.ts b/wagmi.config.ts index 69701b0..acf2a60 100644 --- a/wagmi.config.ts +++ b/wagmi.config.ts @@ -1,58 +1,87 @@ -import { ContractConfig, defineConfig } from '@wagmi/cli' -import { actions, react } from '@wagmi/cli/plugins' -import { ALGEBRA_FACTORY, ALGEBRA_POSITION_MANAGER, ALGEBRA_QUOTER, ALGEBRA_QUOTER_V2, ALGEBRA_ROUTER } from './src/constants/addresses' -import { algebraFactoryABI, algebraPoolABI, algebraPositionManagerABI, algebraQuoterABI, algebraBasePluginABI, algebraRouterABI, algebraQuoterV2ABI, wNativeABI } from './src/abis' +import { ContractConfig, defineConfig } from '@wagmi/cli'; +import { actions, react } from '@wagmi/cli/plugins'; +import { + ALGEBRA_ETERNAL_FARMING, + ALGEBRA_FACTORY, + ALGEBRA_POSITION_MANAGER, + ALGEBRA_QUOTER, + ALGEBRA_QUOTER_V2, + ALGEBRA_ROUTER, + FARMING_CENTER, +} from './src/constants/addresses'; +import { + algebraFactoryABI, + algebraPoolABI, + algebraPositionManagerABI, + algebraQuoterABI, + algebraBasePluginABI, + algebraRouterABI, + algebraQuoterV2ABI, + algebraEternalFarmingABI, + farmingCenterABI, + wNativeABI +} from './src/abis'; const contracts: ContractConfig[] = [ - { - address: ALGEBRA_FACTORY, - abi: algebraFactoryABI, - name: 'AlgebraFactory' - }, - { - abi: algebraPoolABI, - name: 'AlgebraPool' - }, - { - abi: algebraBasePluginABI, - name: 'AlgebraBasePlugin' - }, - { - address: ALGEBRA_POSITION_MANAGER, - abi: algebraPositionManagerABI, - name: 'AlgebraPositionManager' - }, - { - address: ALGEBRA_QUOTER, - abi: algebraQuoterABI, - name: 'AlgebraQuoter' - }, - { - address: ALGEBRA_QUOTER_V2, - abi: algebraQuoterV2ABI, - name: 'AlgerbaQuoterV2' - }, - { - address: ALGEBRA_ROUTER, - abi: algebraRouterABI, - name: 'AlgebraRouter' - }, - { - abi: wNativeABI, - name: 'WrappedNative' - } -] + { + address: ALGEBRA_FACTORY, + abi: algebraFactoryABI, + name: 'AlgebraFactory', + }, + { + abi: algebraPoolABI, + name: 'AlgebraPool', + }, + { + abi: algebraBasePluginABI, + name: 'AlgebraBasePlugin', + }, + { + address: ALGEBRA_POSITION_MANAGER, + abi: algebraPositionManagerABI, + name: 'AlgebraPositionManager', + }, + { + address: ALGEBRA_QUOTER, + abi: algebraQuoterABI, + name: 'AlgebraQuoter', + }, + { + address: ALGEBRA_QUOTER_V2, + abi: algebraQuoterV2ABI, + name: 'AlgerbaQuoterV2', + }, + { + address: ALGEBRA_ROUTER, + abi: algebraRouterABI, + name: 'AlgebraRouter', + }, + { + address: ALGEBRA_ETERNAL_FARMING, + abi: algebraEternalFarmingABI, + name: 'AlgebraEternalFarming', + }, + { + address: FARMING_CENTER, + abi: farmingCenterABI, + name: 'FarmingCenter', + }, + { + abi: wNativeABI, + name: 'WrappedNative' + }, +]; export default defineConfig({ - out: 'src/generated.ts', - contracts, - plugins: [ - actions({ - watchContractEvent: false - }), - react({ - useContractEvent: false, - useContractItemEvent: false - }) - ], -}) + out: 'src/generated.ts', + contracts, + plugins: [ + actions({ + watchContractEvent: false, + }), + react({ + useContractEvent: false, + useContractItemEvent: false, + }), + ], +});