diff --git a/package.json b/package.json index bd90b8c..7c7b0ff 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "dlc-btc-lib", - "version": "2.0.0", + "version": "2.1.0", "description": "This library provides a comprehensive set of interfaces and functions for minting dlcBTC tokens on supported blockchains.", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/constants/ethereum-constants.ts b/src/constants/ethereum-constants.ts index 04331bc..7a82fbb 100644 --- a/src/constants/ethereum-constants.ts +++ b/src/constants/ethereum-constants.ts @@ -18,14 +18,23 @@ export const ethereumArbitrum: EthereumNetwork = { defaultNodeURL: 'https://arb1.arbitrum.io/rpc', }; +const ethereumHardhat: EthereumNetwork = { + name: 'Hardhat', + displayName: 'Hardhat', + id: EthereumNetworkID.Hardhat, + defaultNodeURL: 'http://localhost:8545', +}; + export const supportedEthereumNetworks: EthereumNetwork[] = [ ethereumArbitrumSepolia, ethereumArbitrum, + ethereumHardhat, ]; export const hexChainIDs: { [key in EthereumNetworkID]: string } = { [EthereumNetworkID.ArbitrumSepolia]: '0x66eee', [EthereumNetworkID.Arbitrum]: '0xa4b1', + [EthereumNetworkID.Hardhat]: '0x7a69', }; export const addNetworkParams = { @@ -55,6 +64,19 @@ export const addNetworkParams = { blockExplorerUrls: ['https://arbiscan.io/'], }, ], + [EthereumNetworkID.Hardhat]: [ + { + chainId: '31337', + rpcUrls: ['http://localhost:8545'], + chainName: 'Hardhat', + nativeCurrency: { + name: 'ETH', + symbol: 'ETH', + decimals: 18, + }, + blockExplorerUrls: [], + }, + ], }; export const GITHUB_SOLIDITY_URL = 'https://raw.githubusercontent.com/DLC-link/dlc-solidity'; diff --git a/src/functions/ethereum/ethereum-functions.ts b/src/functions/ethereum/ethereum-functions.ts index 6978656..e013418 100644 --- a/src/functions/ethereum/ethereum-functions.ts +++ b/src/functions/ethereum/ethereum-functions.ts @@ -8,6 +8,7 @@ import { } from '../../constants/ethereum-constants.js'; import { EthereumError } from '../../models/errors.js'; import { + DLCEthereumContractName, DLCEthereumContracts, DLCSolidityBranchName, EthereumDeploymentPlan, @@ -88,10 +89,14 @@ export function getProvider( } } -export function getEthereumontract( +export function getEthereumContract( ethereumDeploymentPlans: EthereumDeploymentPlan[], contractName: string, - signerOrProvider: Wallet | providers.JsonRpcSigner | providers.JsonRpcProvider + signerOrProvider: + | Wallet + | providers.JsonRpcSigner + | providers.JsonRpcProvider + | providers.WebSocketProvider ): Contract { try { const contractDeploymentPlan = ethereumDeploymentPlans.find( @@ -118,31 +123,12 @@ export function getEthereumContracts( ethereumDeploymentPlans: EthereumDeploymentPlan[], signer: Wallet | providers.JsonRpcSigner ): DLCEthereumContracts { - const dlcManagerContract = getEthereumontract(ethereumDeploymentPlans, 'DLCManager', signer); - const dlcBTCContract = getEthereumontract(ethereumDeploymentPlans, 'DLCBTC', signer); + const dlcManagerContract = getEthereumContract(ethereumDeploymentPlans, 'DLCManager', signer); + const dlcBTCContract = getEthereumContract(ethereumDeploymentPlans, 'DLCBTC', signer); return { dlcManagerContract, dlcBTCContract }; } -export function getReadOnlyEthereumContracts( - ethereumDeploymentPlans: EthereumDeploymentPlan[], - readOnlyProvider: providers.JsonRpcProvider -): { protocolContract: Contract; dlcManagerContract: Contract; dlcBTCContract: Contract } { - const protocolContract = getEthereumontract( - ethereumDeploymentPlans, - 'TokenManager', - readOnlyProvider - ); - const dlcManagerContract = getEthereumontract( - ethereumDeploymentPlans, - 'DLCManager', - readOnlyProvider - ); - const dlcBTCContract = getEthereumontract(ethereumDeploymentPlans, 'DLCBTC', readOnlyProvider); - - return { protocolContract, dlcManagerContract, dlcBTCContract }; -} - export async function getLockedBTCBalance(userVaults: RawVault[]): Promise { try { const fundedVaults = userVaults.filter(vault => vault.status === VaultState.FUNDED); @@ -155,3 +141,151 @@ export async function getLockedBTCBalance(userVaults: RawVault[]): Promise { + try { + const dlcManagerContractDeploymentPlan = await fetchEthereumDeploymentPlan( + contractName, + ethereumNetwork, + ethereumContractBranch, + GITHUB_SOLIDITY_URL + ); + + const provider = getProvider(rpcEndpoint ?? ethereumNetwork.defaultNodeURL); + + return new Contract( + dlcManagerContractDeploymentPlan.contract.address, + dlcManagerContractDeploymentPlan.contract.abi, + provider + ); + } catch (error) { + throw new EthereumError(`Could not fetch DLCManager Contract: ${error}`); + } +} + +export async function isUserWhitelisted( + dlcManagerContract: Contract, + userAddress: string +): Promise { + try { + return await dlcManagerContract.isWhitelisted(userAddress); + } catch (error) { + throw new EthereumError(`Could not check if User is whitelisted: ${error}`); + } +} + +export async function isWhitelistingEnabled(dlcManagerContract: Contract): Promise { + return await dlcManagerContract.whitelistingEnabled(); +} + +export async function getAttestorGroupPublicKey(dlcManagerContract: Contract): Promise { + try { + const attestorGroupPubKey = await dlcManagerContract.attestorGroupPubKey(); + if (!attestorGroupPubKey) + throw new Error('Attestor Group Public key is not set on DLCManager Contract'); + return attestorGroupPubKey; + } catch (error) { + throw new EthereumError(`Could not fetch Attestor Public Key: ${error}`); + } +} + +export async function getContractVaults( + dlcManagerContract: Contract, + amount: number = 50 +): Promise { + try { + let totalFetched = 0; + const allVaults: RawVault[] = []; + + let shouldContinue = true; + while (shouldContinue) { + const fetchedVaults: RawVault[] = await dlcManagerContract.getAllDLCs( + totalFetched, + totalFetched + amount + ); + + allVaults.push(...fetchedVaults); + + totalFetched += amount; + shouldContinue = fetchedVaults.length === amount; + } + + return allVaults; + } catch (error) { + throw new EthereumError(`Could not fetch All Vaults: ${error}`); + } +} + +export async function getAllAddressVaults( + dlcManagerContract: Contract, + ethereumAddress: string +): Promise { + try { + return await dlcManagerContract.getAllVaultsForAddress(ethereumAddress); + } catch (error) { + throw new EthereumError(`Could not fetch User Vaults: ${error}`); + } +} + +export async function getRawVault( + dlcManagerContract: Contract, + vaultUUID: string +): Promise { + try { + const vault: RawVault = await dlcManagerContract.getVault(vaultUUID); + if (!vault) throw new EthereumError('Vault not found'); + return vault; + } catch (error) { + throw new EthereumError(`Could not fetch Vault: ${error}`); + } +} + +export async function setupVault(dlcManagerContract: Contract): Promise { + try { + await dlcManagerContract.callStatic.setupVault(); + const transaction = await dlcManagerContract.setupVault(); + return await transaction.wait(); + } catch (error) { + throw new EthereumError(`Could not Setup Vault: ${error}`); + } +} + +export async function withdraw( + dlcManagerContract: Contract, + vaultUUID: string, + withdrawAmount: bigint +) { + try { + await dlcManagerContract.callStatic.withdraw(vaultUUID, withdrawAmount); + const transaction = await dlcManagerContract.withdraw(vaultUUID, withdrawAmount); + return await transaction.wait(); + } catch (error) { + throw new EthereumError(`Could not Withdraw: ${error}`); + } +} + +export async function getAddressDLCBTCBalance( + dlcBTCContract: Contract, + ethereumAddress: string +): Promise { + try { + const ethereumAddressBalance = await dlcBTCContract.balanceOf(ethereumAddress); + return ethereumAddressBalance.toNumber(); + } catch (error) { + throw new EthereumError(`Could not fetch dlcBTC balance: ${error}`); + } +} + +export async function getDLCBTCTotalSupply(dlcBTCContract: Contract): Promise { + try { + const totalSupply = await dlcBTCContract.totalSupply(); + return totalSupply.toNumber(); + } catch (error: any) { + throw new EthereumError(`Could not fetch Total Supply: ${error}`); + } +} diff --git a/src/functions/ethereum/index.ts b/src/functions/ethereum/index.ts index fa0770f..e1bb230 100644 --- a/src/functions/ethereum/index.ts +++ b/src/functions/ethereum/index.ts @@ -1,6 +1 @@ -import { - fetchEthereumDeploymentPlan, - fetchEthereumDeploymentPlansByNetwork, -} from '../ethereum/ethereum-functions.js'; - -export { fetchEthereumDeploymentPlan, fetchEthereumDeploymentPlansByNetwork }; +export * from './ethereum-functions.js'; diff --git a/src/index.ts b/src/index.ts index 24adf02..e750e7c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,7 +2,6 @@ import { LedgerDLCHandler } from './dlc-handlers/ledger-dlc-handler.js'; import { PrivateKeyDLCHandler } from './dlc-handlers/private-key-dlc-handler.js'; import { SoftwareWalletDLCHandler } from './dlc-handlers/software-wallet-dlc-handler.js'; import { EthereumHandler } from './network-handlers/ethereum-handler.js'; -import { ReadOnlyEthereumHandler } from './network-handlers/read-only-ethereum-handler.js'; import { ProofOfReserveHandler } from './proof-of-reserve-handlers/proof-of-reserve-handler.js'; export { @@ -10,6 +9,5 @@ export { LedgerDLCHandler, SoftwareWalletDLCHandler, EthereumHandler, - ReadOnlyEthereumHandler, ProofOfReserveHandler, }; diff --git a/src/models/errors.ts b/src/models/errors.ts index 67d4e05..30e65d7 100644 --- a/src/models/errors.ts +++ b/src/models/errors.ts @@ -5,6 +5,13 @@ export class EthereumError extends Error { } } +export class EthereumHandlerError extends Error { + constructor(message: string) { + super(message); + this.name = 'EthereumHandlerError'; + } +} + export class BitcoinError extends Error { constructor(message: string) { super(message); diff --git a/src/models/ethereum-models.ts b/src/models/ethereum-models.ts index 6ebc35c..c1771c1 100644 --- a/src/models/ethereum-models.ts +++ b/src/models/ethereum-models.ts @@ -10,6 +10,7 @@ export interface EthereumNetwork { export enum EthereumNetworkID { ArbitrumSepolia = '421614', Arbitrum = '42161', + Hardhat = '31337', } export enum VaultState { @@ -51,6 +52,14 @@ export interface EthereumDeploymentPlan { contract: EthereumContract; } +export interface DetailedEvent { + from: string; + to: string; + value: number; + timestamp: number; + txHash: string; +} + export interface DLCEthereumContracts { dlcManagerContract: Contract; dlcBTCContract: Contract; diff --git a/src/network-handlers/ethereum-handler.ts b/src/network-handlers/ethereum-handler.ts index 503871b..d2a7ca6 100644 --- a/src/network-handlers/ethereum-handler.ts +++ b/src/network-handlers/ethereum-handler.ts @@ -1,13 +1,24 @@ -import { Event, Wallet, providers } from 'ethers'; +import { Wallet, providers } from 'ethers'; -import { getEthereumContracts, getProvider } from '../functions/ethereum/ethereum-functions.js'; -import { EthereumError } from '../models/errors.js'; import { - DLCEthereumContractName, + getAddressDLCBTCBalance, + getAllAddressVaults, + getAttestorGroupPublicKey, + getDLCBTCTotalSupply, + getEthereumContracts, + getLockedBTCBalance, + getProvider, + getRawVault, + isUserWhitelisted, + isWhitelistingEnabled, + setupVault, + withdraw, +} from '../functions/ethereum/ethereum-functions.js'; +import { EthereumError, EthereumHandlerError } from '../models/errors.js'; +import { DLCEthereumContracts, EthereumDeploymentPlan, RawVault, - VaultState, } from '../models/ethereum-models.js'; export class EthereumHandler { @@ -40,126 +51,97 @@ export class EthereumHandler { return this.ethereumContracts; } - async getAllVaults(): Promise { + async getAllUserVaults(): Promise { try { const userAddress = await this.ethereumContracts.dlcManagerContract.signer.getAddress(); - return await this.ethereumContracts.dlcManagerContract.getAllVaultsForAddress(userAddress); + return await getAllAddressVaults(this.ethereumContracts.dlcManagerContract, userAddress); } catch (error) { - throw new EthereumError(`Could not fetch Vaults: ${error}`); + throw new EthereumHandlerError(`Could not fetch all User Vaults: ${error}`); } } async getRawVault(vaultUUID: string): Promise { - const vault: RawVault = await this.ethereumContracts.dlcManagerContract.getVault(vaultUUID); - if (!vault) throw new Error('Vault not found'); - return vault; + try { + return await getRawVault(this.ethereumContracts.dlcManagerContract, vaultUUID); + } catch (error) { + throw new EthereumHandlerError(`Could not fetch User Vault: ${error}`); + } } async setupVault(): Promise { try { - await this.ethereumContracts.dlcManagerContract.callStatic.setupVault(); - const transaction = await this.ethereumContracts.dlcManagerContract.setupVault(); - return await transaction.wait(); - } catch (error: any) { - throw new EthereumError(`Could not setup Vault: ${error}`); + return await setupVault(this.ethereumContracts.dlcManagerContract); + } catch (error) { + throw new EthereumHandlerError(`Could not setup Vault for User: ${error}`); } } - async withdraw(vaultUUID: string, amount: bigint) { + async withdraw(vaultUUID: string, withdrawAmount: bigint) { try { - await this.ethereumContracts.dlcManagerContract.callStatic.withdraw(vaultUUID, amount); - const transaction = await this.ethereumContracts.dlcManagerContract.withdraw( - vaultUUID, - amount - ); - return await transaction.wait(); - } catch (error: any) { - throw new EthereumError(`Unable to perform withdraw: ${error}`); + return await withdraw(this.ethereumContracts.dlcManagerContract, vaultUUID, withdrawAmount); + } catch (error) { + throw new EthereumHandlerError(`Unable to perform Withdraw for User: ${error}`); } } - async closeVault(vaultUUID: string) { + async getUserDLCBTCBalance(): Promise { try { - await this.ethereumContracts.dlcManagerContract.callStatic.closeVault(vaultUUID); - const transaction = await this.ethereumContracts.dlcManagerContract.closeVault(vaultUUID); - return await transaction.wait(); - } catch (error: any) { - throw new EthereumError(`Could not close Vault: ${error}`); + const userAddress = await this.ethereumContracts.dlcManagerContract.signer.getAddress(); + return await getAddressDLCBTCBalance(this.ethereumContracts.dlcBTCContract, userAddress); + } catch (error) { + throw new EthereumHandlerError(`Could not fetch User's dlcBTC balance: ${error}`); } } - async getDLCBTCBalance(): Promise { + async getDLCBTCTotalSupply(): Promise { try { - const userAddress = await this.ethereumContracts.dlcManagerContract.signer.getAddress(); - const balance = await this.ethereumContracts.dlcBTCContract.balanceOf(userAddress); - return balance.toNumber(); + return await getDLCBTCTotalSupply(this.ethereumContracts.dlcBTCContract); } catch (error) { - throw new EthereumError(`Could not fetch dlcBTC balance: ${error}`); + throw new EthereumHandlerError(`Could not fetch Total Supply of dlcBTC: ${error}`); } } - async getAttestorGroupPublicKey(): Promise { + async getLockedBTCBalance(userVaults?: RawVault[]): Promise { try { - const attestorGroupPubKey = - await this.ethereumContracts.dlcManagerContract.attestorGroupPubKey(); - if (!attestorGroupPubKey) throw new Error('Could not get Attestor Group Public Key'); - return attestorGroupPubKey; + if (!userVaults) { + userVaults = await this.getAllUserVaults(); + } + return await getLockedBTCBalance(userVaults); } catch (error) { - throw new EthereumError(`Could not fetch Attestor Public Key: ${error}`); + throw new EthereumHandlerError(`Could not fetch Total Supply of Locked dlcBTC: ${error}`); } } - async getContractTransferEvents(contractName: DLCEthereumContractName): Promise { + async getAttestorGroupPublicKey(): Promise { try { - switch (contractName) { - case 'DLCBTC': - return await this.ethereumContracts.dlcBTCContract.queryFilter( - this.ethereumContracts.dlcBTCContract.filters.Transfer() - ); - case 'DLCManager': - return await this.ethereumContracts.dlcManagerContract.queryFilter( - this.ethereumContracts.dlcManagerContract.filters.Transfer() - ); - default: - throw new Error('Invalid Contract Name'); - } - } catch (error: any) { - throw new EthereumError(`Could not fetch Transfer Events: ${error}`); + return getAttestorGroupPublicKey(this.ethereumContracts.dlcManagerContract); + } catch (error) { + throw new EthereumHandlerError(`Could not fetch Attestor Public Key: ${error}`); } } - async getContractTotalSupply(): Promise { + async isWhiteLisingEnabled(): Promise { try { - return await this.ethereumContracts.dlcBTCContract.totalSupply().toNumber(); - } catch (error: any) { - throw new EthereumError(`Could not fetch Total Supply: ${error}`); + return await isWhitelistingEnabled(this.ethereumContracts.dlcManagerContract); + } catch (error) { + throw new EthereumHandlerError(`Could not fetch Whitelisting Status: ${error}`); } } - async getContractFundedVaults(amount: number = 50): Promise { + async isUserWhitelisted(): Promise { try { - let totalFetched = 0; - const fundedVaults: RawVault[] = []; - - let shouldContinue = true; - while (shouldContinue) { - const fetchedVaults: RawVault[] = - await this.ethereumContracts.dlcManagerContract.getAllDLCs( - totalFetched, - totalFetched + amount - ); - const filteredVaults = fetchedVaults.filter(vault => vault.status === VaultState.FUNDED); - fundedVaults.push(...filteredVaults); - - totalFetched += amount; - shouldContinue = fetchedVaults.length === amount; - } + const userAddress = await this.ethereumContracts.dlcManagerContract.signer.getAddress(); + return await isUserWhitelisted(this.ethereumContracts.dlcManagerContract, userAddress); + } catch (error) { + throw new EthereumHandlerError(`Could not fetch User Whitelisting Status: ${error}`); + } + } - return fundedVaults; + async getContractVaults(amount: number = 50): Promise { + try { + return await this.getContractVaults(amount); } catch (error) { - throw new EthereumError( - `Could not fetch Funded Vaults: ${error instanceof Error ? error.message : error}` - ); + throw new EthereumError(`Could not fetch All Vaults: ${error}`); } } } diff --git a/src/network-handlers/read-only-ethereum-handler.ts b/src/network-handlers/read-only-ethereum-handler.ts deleted file mode 100644 index e953ca1..0000000 --- a/src/network-handlers/read-only-ethereum-handler.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { Event } from 'ethers'; - -import { - getProvider, - getReadOnlyEthereumContracts, -} from '../functions/ethereum/ethereum-functions.js'; -import { EthereumError } from '../models/errors.js'; -import { - DLCEthereumContractName, - DLCEthereumContracts, - EthereumDeploymentPlan, - RawVault, - VaultState, -} from '../models/ethereum-models.js'; - -export class ReadOnlyEthereumHandler { - private ethereumContracts: DLCEthereumContracts; - - constructor(ethereumDeploymentPlans: EthereumDeploymentPlan[], rpcEndpoint: string) { - this.ethereumContracts = getReadOnlyEthereumContracts( - ethereumDeploymentPlans, - getProvider(rpcEndpoint) - ); - } - - getContracts(): DLCEthereumContracts { - return this.ethereumContracts; - } - - async getRawVault(vaultUUID: string): Promise { - const vault: RawVault = await this.ethereumContracts.dlcManagerContract.getVault(vaultUUID); - if (!vault) throw new Error('Vault not found'); - return vault; - } - - async getAttestorGroupPublicKey(): Promise { - try { - const attestorGroupPubKey = - await this.ethereumContracts.dlcManagerContract.attestorGroupPubKey(); - if (!attestorGroupPubKey) throw new Error('Could not get Attestor Group Public Key'); - return attestorGroupPubKey; - } catch (error) { - throw new EthereumError(`Could not fetch Attestor Public Key: ${error}`); - } - } - - async getContractTransferEvents(contractName: DLCEthereumContractName): Promise { - try { - switch (contractName) { - case 'DLCBTC': - return await this.ethereumContracts.dlcBTCContract.queryFilter( - this.ethereumContracts.dlcBTCContract.filters.Transfer() - ); - case 'DLCManager': - return await this.ethereumContracts.dlcManagerContract.queryFilter( - this.ethereumContracts.dlcManagerContract.filters.Transfer() - ); - default: - throw new Error('Invalid Contract Name'); - } - } catch (error: any) { - throw new EthereumError(`Could not fetch Transfer Events: ${error}`); - } - } - - async getContractTotalSupply(): Promise { - try { - const totalSupply = await this.ethereumContracts.dlcBTCContract.totalSupply(); - return totalSupply.toNumber(); - } catch (error: any) { - throw new EthereumError(`Could not fetch Total Supply: ${error}`); - } - } - - async getContractFundedVaults(amount: number = 50): Promise { - try { - let totalFetched = 0; - const fundedVaults: RawVault[] = []; - - let shouldContinue = true; - while (shouldContinue) { - const fetchedVaults: RawVault[] = - await this.ethereumContracts.dlcManagerContract.getAllDLCs( - totalFetched, - totalFetched + amount - ); - const filteredVaults = fetchedVaults.filter(vault => vault.status === VaultState.FUNDED); - fundedVaults.push(...filteredVaults); - - totalFetched += amount; - shouldContinue = fetchedVaults.length === amount; - } - - return fundedVaults; - } catch (error) { - throw new EthereumError( - `Could not fetch Funded Vaults: ${error instanceof Error ? error.message : error}` - ); - } - } -}