diff --git a/package.json b/package.json index e113d9a4..4c4afffb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@curvefi/api", - "version": "2.53.12", + "version": "2.54.0", "description": "JavaScript library for curve.fi", "main": "lib/index.js", "author": "Macket", diff --git a/src/constants/dao/actions.ts b/src/constants/dao/actions.ts new file mode 100644 index 00000000..0fe1384d --- /dev/null +++ b/src/constants/dao/actions.ts @@ -0,0 +1,74 @@ +import {IDict} from "../../interfaces"; +import gaugeControllerABI from "../abis/gaugecontroller.json" assert { type: 'json' }; +import votingEscrowABI from '../abis/votingescrow.json' assert { type: 'json' }; + +export const poolActions = [ + 'commit_new_fee', + 'ramp_A', + 'commit_transfer_ownership', + 'withdraw_admin_fees', + 'unkill_me', + 'apply_transfer_ownership', + 'revert_transfer_ownership', + 'apply_new_fee', + 'stop_ramp_A', + 'revert_new_parameters', +] + +export const DaoAbiDictionaries: Record> = { + pools: { + 'commit_new_fee': undefined, + 'ramp_A': undefined, + 'commit_transfer_ownership': undefined, + 'withdraw_admin_fees': undefined, + 'unkill_me': undefined, + 'apply_transfer_ownership': undefined, + 'revert_transfer_ownership': undefined, + 'apply_new_fee': undefined, + 'stop_ramp_A': undefined, + 'revert_new_parameters': undefined, + }, + gauges: { + 'commit_transfer_ownership': gaugeControllerABI, + 'add_type': gaugeControllerABI, + 'add_gauge': gaugeControllerABI, + 'change_type_weight': gaugeControllerABI, + 'change_gauge_weight': gaugeControllerABI, + 'apply_transfer_ownership': gaugeControllerABI, + }, + member: { + 'mint': undefined, + 'burn': undefined, + }, + escrow: { + 'commit_transfer_ownership': votingEscrowABI, + 'commit_smart_wallet_checker': votingEscrowABI, + 'apply_transfer_ownership': votingEscrowABI, + 'apply_smart_wallet_checker': votingEscrowABI, + }, + poolProxy: { + 'commit_set_admins': undefined, + 'set_burner': undefined, + 'apply_set_admins': undefined, + }, + registry: { + 'add_pool': undefined, + 'add_pool_without_underlying': undefined, + 'remove_pool': undefined, + 'set_returns_none': undefined, + 'set_gas_estimate_contract': undefined, + 'set_calculator': undefined, + 'set_burner': undefined, + 'apply_set_admins': undefined, + 'commit_transfer_ownership': undefined, + 'apply_transfer_ownership': undefined, + 'revert_transfer_ownership': undefined, + 'claim_token_balance': undefined, + 'claim_eth_balance': undefined, + }, + vesting: { + 'fund_individual': undefined, + 'toggle_disable': undefined, + 'disable_can_disable': undefined, + }, +} \ No newline at end of file diff --git a/src/dao.ts b/src/dao.ts index 68dbbcf6..0a288e4a 100644 --- a/src/dao.ts +++ b/src/dao.ts @@ -3,7 +3,10 @@ import { Contract } from "ethers"; import { _getAllGauges, _getDaoProposalList, _getDaoProposal } from './external-api.js'; import { _getAddress, - DIGas, ensureAllowance, ensureAllowanceEstimateGas, hasAllowance, + DIGas, + ensureAllowance, + ensureAllowanceEstimateGas, + hasAllowance, mulBy1_3, parseUnits, smartNumber, @@ -17,8 +20,14 @@ import { IDaoProposalUserListItem, IDaoProposal, IDict, + IDaoAction, + TDaoActionType, + TDaoAvailableMethodsOfPool, } from './interfaces'; import feeDistributorViewABI from "./constants/abis/fee_distributor_view.json" assert { type: 'json' }; +import {poolActions} from "./constants/dao/actions"; +import gaugeControllerABI from "./constants/abis/gaugecontroller.json"; +import axios from "axios"; // ----------------- Refactored boosting stuff ----------------- @@ -382,3 +391,75 @@ export const voteForProposalEstimateGas = async (type: "PARAMETER" | "OWNERSHIP" export const voteForProposal = async (type: "PARAMETER" | "OWNERSHIP", id: number, support: boolean): Promise => { return await _voteForProposal(type, id, support, false) as string; } + +export const getAvailableMethodsOfPool = (address: string):TDaoAvailableMethodsOfPool => { + const result: TDaoAvailableMethodsOfPool = {}; + poolActions.forEach((item) => { + if(curve.contracts[address].contract.interface.fragments.find((method: any) => method.name === item)) { + result[item] = true; + } else { + result[item] = false; + } + }) + + return result; +} + +export const createActionForVote = (actionType: TDaoActionType, address: string, method: string, args: any[]): IDaoAction => { + if(actionType === 'pools') { + if(!getAvailableMethodsOfPool(address)[method]) { + throw Error(`Method ${{method}} is not available for this pool`); + } else { + return { + address, + contract: curve.contracts[address].contract, + method, + args, + } + } + } + if(actionType === 'gauges') { + return { + address, + contract: new Contract(address, gaugeControllerABI, curve.signer || curve.provider), + method, + args, + } + } + + throw Error('Unavailable pool type') +} + +export const createEvmScript = async (address: string, abi: any, action: IDaoAction) => { + const agent = new Contract(address, abi, curve.signer || curve.provider) + const zeroPad = (num: string, places: number) => + String(num).padStart(places, "0"); + + let evm_script = "0x00000001" + + const contract = action.contract; + const call_data = contract.interface.encodeFunctionData(action.method, [...action.args]) + + const agent_calldata = agent.interface + .encodeFunctionData("execute", [action.address, 0, call_data]) + .substring(2); + + const length = zeroPad((Math.floor(agent_calldata.length) / 2).toString(16), 8); + + evm_script = `${evm_script}${address.substring(2)}${length}${agent_calldata}`; + + return evm_script +} + +export const createVoteDescription = async (description: string) => { + const vote_description = description.replace(/(\r\n|\n|\r)/gm, ""); + const vote_data = { + text: vote_description, + }; + + const data = new FormData(); + data.append("file", JSON.stringify(vote_data)); + const url = `https://ipfs.infura.io:5001/api/v0/add`; + const response = await axios.post(url, data); + return response.data.Hash; +} diff --git a/src/index.ts b/src/index.ts index 5b3023cd..b431a1f2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -125,6 +125,9 @@ import { userProposalVotes, voteForProposalEstimateGas, voteForProposal, + createEvmScript, + createVoteDescription, + createActionForVote, } from "./dao.js"; async function init ( @@ -356,6 +359,8 @@ const curve = { userProposalVotes, // Transaction methods voteForProposal, + createEvmScript, + createVoteDescription, estimateGas: { // --- CRV lock --- diff --git a/src/interfaces.ts b/src/interfaces.ts index 0fd11fbb..d1bdcb9e 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -264,4 +264,15 @@ export interface IVolumeAndAPYs { cryptoVolume: number, cryptoShare: number, poolsData: IVolumeAndAPYsPoolData[], -} \ No newline at end of file +} + +export type TDaoActionType = 'pools' | 'gauges' + +export type TDaoAvailableMethodsOfPool = Record + +export interface IDaoAction { + address: string, + contract: Contract, + method: string, + args: any[], +} diff --git a/tsconfig.json b/tsconfig.json index 14ecd39b..143f5efd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,10 @@ // "incremental": true, /* Enable incremental compilation */ "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ "module": "ESNext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ - "lib": ["ES2020"], /* Specify library files to be included in the compilation. */ + "lib": [ + "ES2020", + "dom" + ], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */