From 5ee87e0774c62db117100b37e5f8aed90b37bf80 Mon Sep 17 00:00:00 2001 From: Xavier Date: Fri, 15 Nov 2024 23:19:58 +0900 Subject: [PATCH] feat: add governance apis --- src/abis/governance/election-governor-abi.ts | 834 +++++++++++++++++++ src/abis/governance/vclob-abi.ts | 739 ++++++++++++++++ src/apis/vclob.ts | 67 ++ src/call.ts | 98 +++ src/constants/addresses.ts | 20 + src/model/vclob.ts | 6 + src/type.ts | 45 + src/view.ts | 185 +++- 8 files changed, 1993 insertions(+), 1 deletion(-) create mode 100644 src/abis/governance/election-governor-abi.ts create mode 100644 src/abis/governance/vclob-abi.ts create mode 100644 src/apis/vclob.ts create mode 100644 src/model/vclob.ts diff --git a/src/abis/governance/election-governor-abi.ts b/src/abis/governance/election-governor-abi.ts new file mode 100644 index 0000000..e20c92f --- /dev/null +++ b/src/abis/governance/election-governor-abi.ts @@ -0,0 +1,834 @@ +export const ELECTION_GOVERNOR_ABI = [ + { + type: 'constructor', + inputs: [ + { + name: 'registry_', + type: 'address', + internalType: 'address', + }, + { + name: 'voteToken_', + type: 'address', + internalType: 'address', + }, + { + name: 'timelockController_', + type: 'address', + internalType: 'address', + }, + { + name: 'timelockDelay_', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'minCandidateBalance_', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'minApprovalRate_', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'quota_', + type: 'uint8', + internalType: 'uint8', + }, + { + name: 'votingPeriod_', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'registrationPeriod_', + type: 'uint32', + internalType: 'uint32', + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'CLOCK_MODE', + inputs: [], + outputs: [ + { + name: '', + type: 'string', + internalType: 'string', + }, + ], + stateMutability: 'pure', + }, + { + type: 'function', + name: 'UPGRADE_INTERFACE_VERSION', + inputs: [], + outputs: [ + { + name: '', + type: 'string', + internalType: 'string', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'acceptOwnership', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'clock', + inputs: [], + outputs: [ + { + name: '', + type: 'uint48', + internalType: 'uint48', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'currentRound', + inputs: [], + outputs: [ + { + name: '', + type: 'uint16', + internalType: 'uint16', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'end', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'getCandidates', + inputs: [ + { + name: 'round', + type: 'uint16', + internalType: 'uint16', + }, + ], + outputs: [ + { + name: '', + type: 'address[]', + internalType: 'address[]', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getFinalists', + inputs: [ + { + name: 'round', + type: 'uint16', + internalType: 'uint16', + }, + ], + outputs: [ + { + name: '', + type: 'address[]', + internalType: 'address[]', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRoundData', + inputs: [ + { + name: 'round', + type: 'uint16', + internalType: 'uint16', + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct IElectionGovernor.RoundData', + components: [ + { + name: 'status', + type: 'uint8', + internalType: 'enum IElectionGovernor.RoundStatus', + }, + { + name: 'quota', + type: 'uint8', + internalType: 'uint8', + }, + { + name: 'finalistsThreshold', + type: 'uint96', + internalType: 'uint96', + }, + { + name: 'startTime', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'votingEndTime', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'registrationEndTime', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'candidatesLength', + type: 'uint16', + internalType: 'uint16', + }, + { + name: 'finalistsLength', + type: 'uint8', + internalType: 'uint8', + }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getStatus', + inputs: [ + { + name: 'round', + type: 'uint16', + internalType: 'uint16', + }, + ], + outputs: [ + { + name: '', + type: 'uint8', + internalType: 'enum IElectionGovernor.RoundStatus', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getVotes', + inputs: [ + { + name: 'round', + type: 'uint16', + internalType: 'uint16', + }, + { + name: 'candidate', + type: 'address', + internalType: 'address', + }, + ], + outputs: [ + { + name: 'forVotes', + type: 'uint96', + internalType: 'uint96', + }, + { + name: 'againstVotes', + type: 'uint96', + internalType: 'uint96', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'hasVotedTo', + inputs: [ + { + name: 'round', + type: 'uint16', + internalType: 'uint16', + }, + { + name: 'voter', + type: 'address', + internalType: 'address', + }, + { + name: 'candidate', + type: 'address', + internalType: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + internalType: 'bool', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'initialize', + inputs: [ + { + name: 'initialOwner', + type: 'address', + internalType: 'address', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'minApprovalRate', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'minCandidateBalance', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'nextRoundStartTime', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'owner', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'pendingOwner', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'proxiableUUID', + inputs: [], + outputs: [ + { + name: '', + type: 'bytes32', + internalType: 'bytes32', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'quota', + inputs: [], + outputs: [ + { + name: '', + type: 'uint8', + internalType: 'uint8', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'register', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'registrationPeriod', + inputs: [], + outputs: [ + { + name: '', + type: 'uint32', + internalType: 'uint32', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'registry', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'renounceOwnership', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'start', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'timelockController', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'timelockDelay', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'transferOwnership', + inputs: [ + { + name: 'newOwner', + type: 'address', + internalType: 'address', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'upgradeToAndCall', + inputs: [ + { + name: 'newImplementation', + type: 'address', + internalType: 'address', + }, + { + name: 'data', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [], + stateMutability: 'payable', + }, + { + type: 'function', + name: 'vote', + inputs: [ + { + name: 'candidate', + type: 'address', + internalType: 'address', + }, + { + name: 'inFavor', + type: 'bool', + internalType: 'bool', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'voteToken', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'votingPeriod', + inputs: [], + outputs: [ + { + name: '', + type: 'uint32', + internalType: 'uint32', + }, + ], + stateMutability: 'view', + }, + { + type: 'event', + name: 'Initialized', + inputs: [ + { + name: 'version', + type: 'uint64', + indexed: false, + internalType: 'uint64', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferStarted', + inputs: [ + { + name: 'previousOwner', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'newOwner', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferred', + inputs: [ + { + name: 'previousOwner', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'newOwner', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'Registered', + inputs: [ + { + name: 'account', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'finalists', + type: 'address[]', + indexed: false, + internalType: 'address[]', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RoundEnded', + inputs: [ + { + name: 'round', + type: 'uint16', + indexed: true, + internalType: 'uint16', + }, + { + name: 'keepers', + type: 'address[]', + indexed: false, + internalType: 'address[]', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RoundStarted', + inputs: [ + { + name: 'round', + type: 'uint16', + indexed: true, + internalType: 'uint16', + }, + { + name: 'quota', + type: 'uint8', + indexed: false, + internalType: 'uint8', + }, + { + name: 'finalistsThreshold', + type: 'uint96', + indexed: false, + internalType: 'uint96', + }, + { + name: 'startTime', + type: 'uint32', + indexed: false, + internalType: 'uint32', + }, + { + name: 'votingPeriod', + type: 'uint32', + indexed: false, + internalType: 'uint32', + }, + { + name: 'registrationPeriod', + type: 'uint32', + indexed: false, + internalType: 'uint32', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'Upgraded', + inputs: [ + { + name: 'implementation', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'Voted', + inputs: [ + { + name: 'round', + type: 'uint16', + indexed: true, + internalType: 'uint16', + }, + { + name: 'voter', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'candidate', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'votes', + type: 'uint96', + indexed: false, + internalType: 'uint96', + }, + { + name: 'inFavor', + type: 'bool', + indexed: false, + internalType: 'bool', + }, + ], + anonymous: false, + }, + { + type: 'error', + name: 'AddressEmptyCode', + inputs: [ + { + name: 'target', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'AlreadyRegistered', + inputs: [], + }, + { + type: 'error', + name: 'AlreadyVoted', + inputs: [], + }, + { + type: 'error', + name: 'ERC1967InvalidImplementation', + inputs: [ + { + name: 'implementation', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'ERC1967NonPayable', + inputs: [], + }, + { + type: 'error', + name: 'FailedCall', + inputs: [], + }, + { + type: 'error', + name: 'InsufficientBalance', + inputs: [], + }, + { + type: 'error', + name: 'InsufficientVotes', + inputs: [], + }, + { + type: 'error', + name: 'InvalidInitialization', + inputs: [], + }, + { + type: 'error', + name: 'InvalidTime', + inputs: [], + }, + { + type: 'error', + name: 'NotInitializing', + inputs: [], + }, + { + type: 'error', + name: 'OwnableInvalidOwner', + inputs: [ + { + name: 'owner', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'OwnableUnauthorizedAccount', + inputs: [ + { + name: 'account', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'UUPSUnauthorizedCallContext', + inputs: [], + }, + { + type: 'error', + name: 'UUPSUnsupportedProxiableUUID', + inputs: [ + { + name: 'slot', + type: 'bytes32', + internalType: 'bytes32', + }, + ], + }, +] as const diff --git a/src/abis/governance/vclob-abi.ts b/src/abis/governance/vclob-abi.ts new file mode 100644 index 0000000..31702c4 --- /dev/null +++ b/src/abis/governance/vclob-abi.ts @@ -0,0 +1,739 @@ +export const VCLOB_ABI = [ + { + type: 'constructor', + inputs: [ + { + name: 'token_', + type: 'address', + internalType: 'contract IERC20', + }, + { + name: 'depositDuration_', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'withdrawDuration_', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'UPGRADE_INTERFACE_VERSION', + inputs: [], + outputs: [ + { + name: '', + type: 'string', + internalType: 'string', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'acceptOwnership', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'balanceOf', + inputs: [ + { + name: 'account', + type: 'address', + internalType: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'burn', + inputs: [ + { + name: 'id', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'recipient', + type: 'address', + internalType: 'address', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'depositDuration', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getEscrowBalance', + inputs: [ + { + name: 'id', + type: 'uint32', + internalType: 'uint32', + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct IVotingEscrow.EscrowBalance', + components: [ + { + name: 'owner', + type: 'address', + internalType: 'address', + }, + { + name: 'amount', + type: 'uint88', + internalType: 'uint88', + }, + { + name: 'lockedTimepoint', + type: 'uint24', + internalType: 'uint24', + }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getPastBalanceOf', + inputs: [ + { + name: 'account', + type: 'address', + internalType: 'address', + }, + { + name: 'timepoint', + type: 'uint48', + internalType: 'uint48', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getPastTotalSupply', + inputs: [ + { + name: 'timepoint', + type: 'uint48', + internalType: 'uint48', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'initialize', + inputs: [ + { + name: 'initialOwner', + type: 'address', + internalType: 'address', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'isWhitelisted', + inputs: [ + { + name: 'recipient', + type: 'address', + internalType: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + internalType: 'bool', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'mint', + inputs: [ + { + name: 'amount', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'recipient', + type: 'address', + internalType: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint32', + internalType: 'uint32', + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'name', + inputs: [], + outputs: [ + { + name: '', + type: 'string', + internalType: 'string', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'nextId', + inputs: [], + outputs: [ + { + name: '', + type: 'uint32', + internalType: 'uint32', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'owner', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'pendingOwner', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'proxiableUUID', + inputs: [], + outputs: [ + { + name: '', + type: 'bytes32', + internalType: 'bytes32', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'renounceOwnership', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setWhitelisted', + inputs: [ + { + name: 'recipient', + type: 'address', + internalType: 'address', + }, + { + name: 'whitelisted', + type: 'bool', + internalType: 'bool', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'symbol', + inputs: [], + outputs: [ + { + name: '', + type: 'string', + internalType: 'string', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'token', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'contract IERC20', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'totalSupply', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'transfer', + inputs: [ + { + name: 'to', + type: 'address', + internalType: 'address', + }, + { + name: 'id', + type: 'uint32', + internalType: 'uint32', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'transferOwnership', + inputs: [ + { + name: 'newOwner', + type: 'address', + internalType: 'address', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'upgradeToAndCall', + inputs: [ + { + name: 'newImplementation', + type: 'address', + internalType: 'address', + }, + { + name: 'data', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [], + stateMutability: 'payable', + }, + { + type: 'function', + name: 'withdrawDuration', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'event', + name: 'Initialized', + inputs: [ + { + name: 'version', + type: 'uint64', + indexed: false, + internalType: 'uint64', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferStarted', + inputs: [ + { + name: 'previousOwner', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'newOwner', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferred', + inputs: [ + { + name: 'previousOwner', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'newOwner', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'Transfer', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'value', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'Transfer', + inputs: [ + { + name: 'id', + type: 'uint32', + indexed: true, + internalType: 'uint32', + }, + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'value', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'Upgraded', + inputs: [ + { + name: 'implementation', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'VotesChanged', + inputs: [ + { + name: 'account', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'previousVotes', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + { + name: 'newVotes', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'error', + name: 'AddressEmptyCode', + inputs: [ + { + name: 'target', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'CheckpointUnorderedInsertion', + inputs: [], + }, + { + type: 'error', + name: 'ERC1967InvalidImplementation', + inputs: [ + { + name: 'implementation', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'ERC1967NonPayable', + inputs: [], + }, + { + type: 'error', + name: 'ERC20InsufficientBalance', + inputs: [ + { + name: 'sender', + type: 'address', + internalType: 'address', + }, + { + name: 'balance', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'needed', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + { + type: 'error', + name: 'ERC20InvalidReceiver', + inputs: [ + { + name: 'receiver', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'ERC5805FutureLookup', + inputs: [ + { + name: 'timepoint', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'clock', + type: 'uint48', + internalType: 'uint48', + }, + ], + }, + { + type: 'error', + name: 'FailedCall', + inputs: [], + }, + { + type: 'error', + name: 'InvalidAccess', + inputs: [], + }, + { + type: 'error', + name: 'InvalidInitialization', + inputs: [], + }, + { + type: 'error', + name: 'InvalidWithdrawalTime', + inputs: [], + }, + { + type: 'error', + name: 'NonWhitelistedAddress', + inputs: [], + }, + { + type: 'error', + name: 'NotInitializing', + inputs: [], + }, + { + type: 'error', + name: 'OwnableInvalidOwner', + inputs: [ + { + name: 'owner', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'OwnableUnauthorizedAccount', + inputs: [ + { + name: 'account', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'SafeCastOverflowedUintDowncast', + inputs: [ + { + name: 'bits', + type: 'uint8', + internalType: 'uint8', + }, + { + name: 'value', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + { + type: 'error', + name: 'SafeERC20FailedOperation', + inputs: [ + { + name: 'token', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'UUPSUnauthorizedCallContext', + inputs: [], + }, + { + type: 'error', + name: 'UUPSUnsupportedProxiableUUID', + inputs: [ + { + name: 'slot', + type: 'bytes32', + internalType: 'bytes32', + }, + ], + }, +] as const diff --git a/src/apis/vclob.ts b/src/apis/vclob.ts new file mode 100644 index 0000000..4447556 --- /dev/null +++ b/src/apis/vclob.ts @@ -0,0 +1,67 @@ +import { PublicClient } from 'viem' + +import { CHAIN_IDS } from '../constants/chain' +import { Subgraph } from '../constants/subgraph' +import { VCLOB as VCLOBModel } from '../model/vclob' +import { CONTRACT_ADDRESSES } from '../constants/addresses' +import { VCLOB_ABI } from '../abis/governance/vclob-abi' +import { VCLOB } from '../type' + +const fetchVCLOBListFromSubgraph = async ( + chainId: CHAIN_IDS, + userAddress: `0x${string}`, +) => { + const result = await Subgraph.get<{ + data: { + vclobs: VCLOBModel[] + } + }>( + chainId, + 'getVCLOBList', + 'query getVCLOBList($owner: String!) { vclobs( where: { owner: $owner } ) { id owner amount lockedTimepoint } }', + { + owner: userAddress, + }, + ) + return result.data.vclobs +} + +export const fetchVCLOBList = async ( + publicClient: PublicClient, + chainId: CHAIN_IDS, + userAddress: `0x${string}`, + useSubgraph: boolean, +): Promise => { + if (!useSubgraph) { + return [] + } + const [depositDuration, withdrawDuration] = await publicClient.multicall({ + contracts: [ + { + address: CONTRACT_ADDRESSES[chainId]!.VoteLockedCloberToken, + abi: VCLOB_ABI, + functionName: 'depositDuration', + }, + { + address: CONTRACT_ADDRESSES[chainId]!.VoteLockedCloberToken, + abi: VCLOB_ABI, + functionName: 'withdrawDuration', + }, + ], + allowFailure: false, + }) + const currentTimepoint = BigInt(Math.floor(Date.now() / 1000)) + const vclobs = await fetchVCLOBListFromSubgraph(chainId, userAddress) + return vclobs.map((vclob) => { + const phaseDuration = depositDuration + withdrawDuration + const phase = (currentTimepoint - vclob.lockedTimepoint) / phaseDuration + const withdrawalStartTimepoint = + vclob.lockedTimepoint + phase * phaseDuration + depositDuration + const withdrawalEndTimepoint = withdrawalStartTimepoint + withdrawDuration + return { + ...vclob, + withdrawalStartTimepoint, + withdrawalEndTimepoint, + } + }) +} diff --git a/src/call.ts b/src/call.ts index 632ed26..3500384 100644 --- a/src/call.ts +++ b/src/call.ts @@ -46,6 +46,7 @@ import { abs } from './utils/math' import { toBytes32 } from './utils/pool-key' import { OPERATOR_ABI } from './abis/rebalancer/operator-abi' import { STRATEGY_ABI } from './abis/rebalancer/strategy-abi' +import { ELECTION_GOVERNOR_ABI } from './abis/governance/election-governor-abi' /** * Build a transaction to open a market. @@ -1907,3 +1908,100 @@ export const resumePool = async ({ options?.gasPriceLimit, ) } + +export const vote = async ({ + chainId, + userAddress, + candidateAddress, + inFavor, + options, +}: { + chainId: CHAIN_IDS + userAddress: `0x${string}` + candidateAddress: `0x${string}` + inFavor: boolean + options?: { + useSubgraph?: boolean + } & DefaultWriteContractOptions +}): Promise => { + const publicClient = createPublicClient({ + chain: CHAIN_MAP[chainId], + transport: options?.rpcUrl ? http(options.rpcUrl) : http(), + }) + + return buildTransaction( + publicClient, + { + chain: CHAIN_MAP[chainId], + account: userAddress, + address: CONTRACT_ADDRESSES[chainId]!.ElectionGovernor, + abi: ELECTION_GOVERNOR_ABI, + functionName: 'vote', + args: [candidateAddress, inFavor], + }, + options?.gasLimit, + options?.gasPriceLimit, + ) +} + +export const register = async ({ + chainId, + userAddress, + options, +}: { + chainId: CHAIN_IDS + userAddress: `0x${string}` + options?: { + useSubgraph?: boolean + } & DefaultWriteContractOptions +}): Promise => { + const publicClient = createPublicClient({ + chain: CHAIN_MAP[chainId], + transport: options?.rpcUrl ? http(options.rpcUrl) : http(), + }) + + return buildTransaction( + publicClient, + { + chain: CHAIN_MAP[chainId], + account: userAddress, + address: CONTRACT_ADDRESSES[chainId]!.ElectionGovernor, + abi: ELECTION_GOVERNOR_ABI, + functionName: 'register', + args: [], + }, + options?.gasLimit, + options?.gasPriceLimit, + ) +} + +export const end = async ({ + chainId, + userAddress, + options, +}: { + chainId: CHAIN_IDS + userAddress: `0x${string}` + options?: { + useSubgraph?: boolean + } & DefaultWriteContractOptions +}): Promise => { + const publicClient = createPublicClient({ + chain: CHAIN_MAP[chainId], + transport: options?.rpcUrl ? http(options.rpcUrl) : http(), + }) + + return buildTransaction( + publicClient, + { + chain: CHAIN_MAP[chainId], + account: userAddress, + address: CONTRACT_ADDRESSES[chainId]!.ElectionGovernor, + abi: ELECTION_GOVERNOR_ABI, + functionName: 'end', + args: [], + }, + options?.gasLimit, + options?.gasPriceLimit, + ) +} diff --git a/src/constants/addresses.ts b/src/constants/addresses.ts index e6d9d7f..c3460f1 100644 --- a/src/constants/addresses.ts +++ b/src/constants/addresses.ts @@ -11,6 +11,8 @@ export const CONTRACT_ADDRESSES: { Strategy: `0x${string}` Minter: `0x${string}` Operator: `0x${string}` + VoteLockedCloberToken: `0x${string}` + ElectionGovernor: `0x${string}` } } = { [CHAIN_IDS.CLOBER_TESTNET]: { @@ -21,6 +23,10 @@ export const CONTRACT_ADDRESSES: { Strategy: getAddress('0x8aDF62b0b6078EaE5a2D54e9e5DD2AA71F6748C4'), Minter: getAddress('0xF2f51B00C2e9b77F23fD66649bbabf8a025c39eF'), Operator: getAddress('0x33559576B062D08230b467ea7DC7Ce75aFcbdE92'), + VoteLockedCloberToken: getAddress( + '0xA8d4E6BC755b3ed8DCE2FaFE4104Bdad645763A5', + ), + ElectionGovernor: getAddress('0xE002A871B314Cc253d4e25E43Afca0557df9577f'), }, [CHAIN_IDS.CLOBER_TESTNET_2]: { Controller: getAddress('0xE64aCE1bF550E57461cd4e24706633d7faC9D7b0'), @@ -30,6 +36,10 @@ export const CONTRACT_ADDRESSES: { Strategy: getAddress('0x540488b54c8DE6e44Db7553c3A2C4ABEb09Fc69C'), Minter: getAddress('0x0b8361a2bbF853F5F6Aa0911a9d238d9CFDD9f1a'), Operator: getAddress('0xFa47E8dD8F04BF23b238900e754041123a6bc6e2'), + VoteLockedCloberToken: getAddress( + '0xA8d4E6BC755b3ed8DCE2FaFE4104Bdad645763A5', + ), + ElectionGovernor: getAddress('0xE002A871B314Cc253d4e25E43Afca0557df9577f'), }, [CHAIN_IDS.ARBITRUM_SEPOLIA]: { Controller: getAddress('0xE64aCE1bF550E57461cd4e24706633d7faC9D7b0'), @@ -39,6 +49,10 @@ export const CONTRACT_ADDRESSES: { Strategy: getAddress('0x540488b54c8DE6e44Db7553c3A2C4ABEb09Fc69C'), Minter: getAddress('0x0b8361a2bbF853F5F6Aa0911a9d238d9CFDD9f1a'), Operator: getAddress('0xFa47E8dD8F04BF23b238900e754041123a6bc6e2'), + VoteLockedCloberToken: getAddress( + '0xA8d4E6BC755b3ed8DCE2FaFE4104Bdad645763A5', + ), + ElectionGovernor: getAddress('0xE002A871B314Cc253d4e25E43Afca0557df9577f'), }, [CHAIN_IDS.BASE]: { Controller: getAddress('0xe4AB03992e214acfdCD05ccFB5C5C16e3d0Ca371'), @@ -48,6 +62,8 @@ export const CONTRACT_ADDRESSES: { Strategy: getAddress('0xB203475338cfFF99357E7301617Ba5fC0f47329A'), Minter: getAddress('0x3A46d45c36F5D3cEDf87B67E3A34F34aBe06AbA8'), Operator: getAddress('0xBB854e8C0f04d919aD770b27015Ee90a9EF31Bf0'), + VoteLockedCloberToken: zeroAddress, + ElectionGovernor: zeroAddress, }, [CHAIN_IDS.BERACHAIN_TESTNET]: { Controller: getAddress('0xce3F3C90970C08Fe451998441b30879560AA6757'), @@ -57,6 +73,8 @@ export const CONTRACT_ADDRESSES: { Strategy: zeroAddress, Minter: zeroAddress, Operator: zeroAddress, + VoteLockedCloberToken: zeroAddress, + ElectionGovernor: zeroAddress, }, [CHAIN_IDS.ZKSYNC]: { Controller: getAddress('0x2Bd904F455928833F8E8C706d1cf01Eb5daaee7C'), @@ -66,5 +84,7 @@ export const CONTRACT_ADDRESSES: { Strategy: zeroAddress, Minter: zeroAddress, Operator: zeroAddress, + VoteLockedCloberToken: zeroAddress, + ElectionGovernor: zeroAddress, }, } diff --git a/src/model/vclob.ts b/src/model/vclob.ts new file mode 100644 index 0000000..9e3b700 --- /dev/null +++ b/src/model/vclob.ts @@ -0,0 +1,6 @@ +export type VCLOB = { + id: string + owner: `0x${string}` + amount: bigint + lockedTimepoint: bigint +} diff --git a/src/type.ts b/src/type.ts index 2e275b1..7d3ea97 100644 --- a/src/type.ts +++ b/src/type.ts @@ -173,3 +173,48 @@ export type PoolPerformanceData = { poolSnapshots: PoolSnapshotDto[] poolSpreadProfits: PoolSpreadProfitDto[] } + +export type VCLOB = { + id: string + owner: `0x${string}` + amount: bigint + lockedTimepoint: bigint + withdrawalStartTimepoint: bigint + withdrawalEndTimepoint: bigint +} + +export type ElectionGovernorMetadata = { + minCandidateBalance: bigint + quota: number +} + +export enum ElectionRoundStatus { + NotStarted, + Voting, + Registration, + Ended, +} + +export type ElectionCandidate = { + address: `0x${string}` + vclobAmount: bigint + hasVotedTo: boolean + forVotes: bigint + againstVotes: bigint +} + +export type ElectionRoundData = { + round: number + nextRoundStartTime: bigint + vclobAmount: bigint + status: ElectionRoundStatus + quota: number + finalistsThreshold: bigint + startTime: bigint + votingEndTime: bigint + registrationEndTime: bigint + candidatesLength: number + finalistsLength: number + candidates: ElectionCandidate[] + finalists: ElectionCandidate[] +} diff --git a/src/view.ts b/src/view.ts index 804e99b..4e06cbe 100644 --- a/src/view.ts +++ b/src/view.ts @@ -1,4 +1,5 @@ import { + Address, createPublicClient, formatUnits, getAddress, @@ -13,13 +14,16 @@ import type { ChartLog, Currency, DefaultReadContractOptions, + ElectionGovernorMetadata, + ElectionRoundData, LastRawAmounts, Market, Pool, PoolPerformanceData, StrategyPosition, + VCLOB, } from './type' -import { CHART_LOG_INTERVALS } from './type' +import { CHART_LOG_INTERVALS, ElectionCandidate } from './type' import { formatPrice, parsePrice } from './utils/prices' import { fetchOpenOrder, fetchOpenOrdersByUserAddress } from './apis/open-order' import { OpenOrder } from './model/open-order' @@ -41,6 +45,9 @@ import { PoolSpreadProfitDto as ModelPoolSpreadProfit, PoolVolumeDto as ModelPoolVolume, } from './model/pool' +import { fetchVCLOBList } from './apis/vclob' +import { ELECTION_GOVERNOR_ABI } from './abis/governance/election-governor-abi' +import { VCLOB_ABI } from './abis/governance/vclob-abi' /** * Get contract addresses by chain id @@ -422,6 +429,182 @@ export const getLastRawAmounts = async ({ ) } +export const getVCLOBList = async ({ + chainId, + userAddress, + options, +}: { + chainId: CHAIN_IDS + userAddress: `0x${string}` + options?: DefaultReadContractOptions & { + useSubgraph?: boolean + } +}): Promise => { + const publicClient = createPublicClient({ + chain: CHAIN_MAP[chainId], + transport: options?.rpcUrl ? http(options.rpcUrl) : http(), + }) + return fetchVCLOBList( + publicClient, + chainId, + userAddress, + !!(options && options.useSubgraph), + ) +} + +export const getKeepersElectionGovernorMetadata = async ({ + chainId, + options, +}: { + chainId: CHAIN_IDS + options?: DefaultReadContractOptions +}): Promise => { + const publicClient = createPublicClient({ + chain: CHAIN_MAP[chainId], + transport: options?.rpcUrl ? http(options.rpcUrl) : http(), + }) + const [minCandidateBalance, quota] = await publicClient.multicall({ + contracts: [ + { + address: CONTRACT_ADDRESSES[chainId]!.ElectionGovernor, + abi: ELECTION_GOVERNOR_ABI, + functionName: 'minCandidateBalance', + }, + { + address: CONTRACT_ADDRESSES[chainId]!.ElectionGovernor, + abi: ELECTION_GOVERNOR_ABI, + functionName: 'quota', + }, + ], + allowFailure: false, + }) + return { + minCandidateBalance, + quota, + } +} + +export const getKeepersElectionCurrentRoundData = async ({ + chainId, + userAddress, + options, +}: { + chainId: CHAIN_IDS + userAddress: `0x${string}` + options?: DefaultReadContractOptions & { + useSubgraph?: boolean + } +}): Promise => { + const publicClient = createPublicClient({ + chain: CHAIN_MAP[chainId], + transport: options?.rpcUrl ? http(options.rpcUrl) : http(), + }) + const [currentRound, nextRoundStartTime] = await publicClient.multicall({ + contracts: [ + { + address: CONTRACT_ADDRESSES[chainId]!.ElectionGovernor, + abi: ELECTION_GOVERNOR_ABI, + functionName: 'currentRound', + }, + { + address: CONTRACT_ADDRESSES[chainId]!.ElectionGovernor, + abi: ELECTION_GOVERNOR_ABI, + functionName: 'nextRoundStartTime', + }, + ], + allowFailure: false, + }) + const [currentRoundData, candidates, finalists] = + await publicClient.multicall({ + contracts: [ + { + address: CONTRACT_ADDRESSES[chainId]!.ElectionGovernor, + abi: ELECTION_GOVERNOR_ABI, + functionName: 'getRoundData', + args: [currentRound], + }, + { + address: CONTRACT_ADDRESSES[chainId]!.ElectionGovernor, + abi: ELECTION_GOVERNOR_ABI, + functionName: 'getCandidates', + args: [currentRound], + }, + { + address: CONTRACT_ADDRESSES[chainId]!.ElectionGovernor, + abi: ELECTION_GOVERNOR_ABI, + functionName: 'getFinalists', + args: [currentRound], + }, + ], + allowFailure: false, + }) + const [userVCLOBAmount, ...multicallResults] = await publicClient.multicall({ + contracts: [ + { + address: CONTRACT_ADDRESSES[chainId]!.VoteLockedCloberToken, + abi: VCLOB_ABI, + functionName: 'getPastBalanceOf', + args: [userAddress, currentRoundData.startTime], + }, + ...candidates.map((candidate: Address) => ({ + address: CONTRACT_ADDRESSES[chainId]!.VoteLockedCloberToken, + abi: VCLOB_ABI, + functionName: 'getPastBalanceOf', + args: [candidate, currentRoundData.startTime], + })), + ...candidates.map((candidate: Address) => ({ + address: CONTRACT_ADDRESSES[chainId]!.ElectionGovernor, + abi: ELECTION_GOVERNOR_ABI, + functionName: 'hasVotedTo', + args: [currentRound, userAddress, candidate], + })), + ...candidates.map((candidate: Address) => ({ + address: CONTRACT_ADDRESSES[chainId]!.ElectionGovernor, + abi: ELECTION_GOVERNOR_ABI, + functionName: 'getVotes', + args: [currentRound, candidate], + })), + ], + allowFailure: false, + }) + const vclobAmountOfCandidateList = multicallResults.slice( + 0, + candidates.length, + ) as bigint[] + const hasVotedToCandidateList = multicallResults.slice( + candidates.length, + candidates.length * 2, + ) as any as boolean[] + const votesForCandidateList = multicallResults.slice( + candidates.length * 2, + ) as any as [bigint, bigint][] + const candidateDataMap = new Map() + for (let i = 0; i < candidates.length; i++) { + candidateDataMap.set(candidates[i], { + address: candidates[i], + vclobAmount: vclobAmountOfCandidateList[i], + hasVotedTo: hasVotedToCandidateList[i], + forVotes: votesForCandidateList[i][0], + againstVotes: votesForCandidateList[i][1], + }) + } + return { + round: currentRound, + nextRoundStartTime: nextRoundStartTime, + vclobAmount: userVCLOBAmount as bigint, + status: currentRoundData.status, + quota: currentRoundData.quota, + finalistsThreshold: currentRoundData.finalistsThreshold, + startTime: BigInt(currentRoundData.startTime), + votingEndTime: BigInt(currentRoundData.votingEndTime), + registrationEndTime: BigInt(currentRoundData.registrationEndTime), + candidatesLength: currentRoundData.candidatesLength, + finalistsLength: currentRoundData.finalistsLength, + candidates: candidates.map((candidate) => candidateDataMap.get(candidate)!), + finalists: finalists.map((finalist) => candidateDataMap.get(finalist)!), + } +} + /** * Calculates and returns the neighboring price ticks and their corresponding prices for a given input price. *