diff --git a/.gitignore b/.gitignore index 3b735ec..f61e718 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,62 @@ -# If you prefer the allow list template instead of the deny list, see community template: -# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore -# -# Binaries for programs and plugins +# Binaries and objects # +####################### *.exe *.exe~ *.dll *.so *.dylib - -# Test binary, built with `go test -c` *.test - -# Output of the go coverage tool, specifically when used with LiteIDE *.out +*.o +*.a +*.obj +*.swp +*.lock +*.DS_Store +*.vscode +# Compiled Object files, Static and Dynamic libs (Shared Objects) # +*.o +*.a +*.so + +# Folders to ignore # +##################### +/vendor/ +/node_modules/ + +# Dependency directories (remove the line below if you're using Go modules) # +/Godeps/ + +# Output of the Go coverage tool # +coverage.txt + +# External tool configuration files # +.idea/ +*.swp +*.swo +*.tmp +*.swp +*.swo +.vscode/ + +# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 # +.glide/ + +# Go build directory # +bin/ + +# Go Module files # +go.sum +go.mod + +# Log files # +*.log + +# Microsoft Office temporary files # +~$* -# Dependency directories (remove the comment below to include it) -# vendor/ +# JetBrains IDEs # +.idea/ -# Go workspace file -go.work +# Vscode # +.vscode/ \ No newline at end of file diff --git a/README.md b/README.md index bad1993..25f65aa 100644 --- a/README.md +++ b/README.md @@ -1 +1,357 @@ -# covalent-api-sdk-go \ No newline at end of file +# Covalent SDK for Golang + +The Covalent SDK is the fastest way to integrate the Covalent Unified API for working with blockchain data. The SDK works with all [supported chains](https://www.covalenthq.com/docs/networks/) including Mainnets and Testnets. + +**Note - Require `go1.22.1` and above for best results.** + +> **Sign up for an API Key** +> +> To get your own Covalent API key, **[sign up here](https://www.covalenthq.com/platform/auth/register/)** and create your key from the *API Keys* tab. + +## Getting started + +``` +go get github.com/covalenthq/covalent-api-sdk-go +``` + +## How to use the Covalent SDK + +After installing, you can import and use the SDK with: + +```go +package main + +import ( + "fmt" + "github.com/covalenthq/covalent-api-sdk-go/covalentclient" + "github.com/covalenthq/covalent-api-sdk-go/chains" +) + +func main() { + var Client = covalentclient.CovalentClient("API_KEY") + resp, err := Client.BalanceService.GetTokenBalancesForWalletAddress(chains.EthMainnet, "demo.eth") + if err != nil { + fmt.Printf("error: %s", err) + } else { + if len(resp.Data.Items) != 0 { + // get first Balance + fmt.Println(*resp.Data.Items[0].Balance) + } + } +} +``` +> **Name Resolution** +> +> The Covalent SDK natively supports ENS domains (e.g. `demo.eth`), Lens Handles (e.g. `@demo.lens`) and Unstoppable Domains (e.g. `demo.x`) which automatically resolve to the underlying user address (e.g. `0xfC43f5F9dd45258b3AFf31Bdbe6561D97e8B71de`) + +### How to apply supported query parameters to endpoints +Query parameters are handled using variables that are `references and pointers`. Developers can reference the `godocs` for autocomplete suggestions for the supported parameters. These supported parameters can be passed in any order. + +For example, the following sets the `QuoteCurrency` query parameter to `CAD` and the parameter `Nft` to `true` for fetching all the token balances, including NFTs, for a wallet address: +```go +package main + +import ( + "fmt" + "github.com/covalenthq/covalent-api-sdk-go/covalentclient" + "github.com/covalenthq/covalent-api-sdk-go/services" + "github.com/covalenthq/covalent-api-sdk-go/quotes" + "github.com/covalenthq/covalent-api-sdk-go/chains" +) + +func main() { + nft := true + var quoteValue quotes.Quote = quotes.CAD + var Client = covalentclient.CovalentClient("API_KEY") + resp, err := Client.BalanceService.GetTokenBalancesForWalletAddress(chains.EthMainnet, "demo.eth", services.GetTokenBalancesForWalletAddressQueryParamOpts{Nft: &nft, QuoteCurrency: "eValue}) + if err != nil { + fmt.Printf("error: %s", err) + } else { + if len(resp.Data.Items) != 0 { + // get first Balance + fmt.Println(*resp.Data.Items[0].Balance) + } + } +} +``` + +## Supported Endpoints + +The Covalent SDK provides comprehensive support for all Class A, Class B, and Pricing endpoints that are grouped under the following *Service* namespaces: + +- [`SecurityService`](#securityservice): Access to the token approvals endpoints +- [`BalanceService`](#balanceservice): Access to the balances endpoints +- [`BaseService`](#baseservice): Access to the address activity, log events, chain status, and block retrieval endpoints +- [`NftService`](#nftservice): Access to the NFT endpoints +- [`PricingService`](#pricingservice): Access to the historical token prices endpoint +- [`TransactionService`](#transactionservice): Access to the transactions endpoints +- [`XykService`](#xykservice): Access to the XY=K suite of endpoints + +### SecurityService + +The `SecurityService` class refers to the [token approvals API endpoints](https://www.covalenthq.com/docs/api/security/get-token-approvals-for-address/): + +- `GetApprovals()`: Get a list of approvals across all ERC20 token contracts categorized by spenders for a wallet’s assets. +- `GetNftApprovals()`: Get a list of approvals across all NFT contracts categorized by spenders for a wallet’s assets. + +### BalanceService + +The `BalanceService` class refers to the [balances API endpoints](https://www.covalenthq.com/docs/api/balances/get-token-balances-for-address/): + +- `GetTokenBalancesForWalletAddress()`: Fetch the native, fungible (ERC20), and non-fungible (ERC721 & ERC1155) tokens held by an address. Response includes spot prices and other metadata. +- `GetHistoricalTokenBalancesForWalletAddress()`: Fetch the historical native, fungible (ERC20), and non-fungible (ERC721 & ERC1155) tokens held by an address at a given block height or date. Response includes daily prices and other metadata. +- `GetHistoricalPortfolioForWalletAddress()`: Render a daily portfolio balance for an address broken down by the token. The timeframe is user-configurable, defaults to 30 days. +- `GetErc20TransfersForWalletAddress()`: Render the transfer-in and transfer-out of a token along with historical prices from an address. (Paginated) +- `GetErc20TransfersForWalletAddressByPage()`: Render the transfer-in and transfer-out of a token along with historical prices from an address. (NonPaginated) +- `GetTokenHoldersV2ForTokenAddress()`: Get a list of all the token holders for a specified ERC20 or ERC721 token. Returns historic token holders when block-height is set (defaults to latest). Useful for building pie charts of token holders. (Paginated) +- `GetTokenHoldersV2ForTokenAddressByPage()`: Get a list of all the token holders for a specified ERC20 or ERC721 token. Returns historic token holders when block-height is set (defaults to latest). Useful for building pie charts of token holders. (Nonpaginated) +- `GetNativeTokenBalance()`: Get the native token balance for an address. This endpoint is required because native tokens are usually not ERC20 tokens and sometimes you want something lightweight. + +### BaseService + +The `BaseService` class refers to the [address activity, log events, chain status and block retrieval API endpoints](https://www.covalenthq.com/docs/api/base/get-address-activity/): + +- `GetBlock()`: Fetch and render a single block for a block explorer. +- `GetLogs()`: Get all the event logs of the latest block, or for a range of blocks. Includes sender contract metadata as well as decoded logs. +- `GetResolvedAddress()`: Used to resolve ENS, RNS and Unstoppable Domains addresses. +- `GetBlockHeights()`: Get all the block heights within a particular date range. Useful for rendering a display where you sort blocks by day. (Paginated) +- `GetBlockHeightsByPage()`: Get all the block heights within a particular date range. Useful for rendering a display where you sort blocks by day. (Nonpaginated) +- `GetLogEventsByAddress()`: Get all the event logs emitted from a particular contract address. Useful for building dashboards that examine on-chain interactions. (Paginated) +- `GetLogEventsByAddressByPage()`: Get all the event logs emitted from a particular contract address. Useful for building dashboards that examine on-chain interactions. (Nonpaginated) +- `GetLogEventsByTopicHash()`: Get all event logs of the same topic hash across all contracts within a particular chain. Useful for cross-sectional analysis of event logs that are emitted on-chain. (Paginated) +- `GetLogEventsByTopicHashByPage()`: Get all event logs of the same topic hash across all contracts within a particular chain. Useful for cross-sectional analysis of event logs that are emitted on-chain. (Nonpaginated) +- `GetAllChains()`: Used to build internal dashboards for all supported chains on Covalent. +- `GetAllChainStatus()`: Used to build internal status dashboards of all supported chains. +- `GetAddressActivity()`: Locate chains where an address is active on with a single API call. +- `GetGasPrices()`: Get real-time gas estimates for different transaction speeds on a specific network, enabling users to optimize transaction costs and confirmation times. + +### NftService + +The `NftService` class refers to the [NFT API endpoints](https://www.covalenthq.com/docs/api/nft/get-nfts-for-address/): + +- `GetChainCollections()`: Used to fetch the list of NFT collections with downloaded and cached off chain data like token metadata and asset files. (Paginated) +- `GetChainCollectionsByPage()`: Used to fetch the list of NFT collections with downloaded and cached off chain data like token metadata and asset files. (Nonpaginated) +- `GetNftsForAddress()`: Used to render the NFTs (including ERC721 and ERC1155) held by an address. +- `GetTokenIdsForContractWithMetadata()`: Get NFT token IDs with metadata from a collection. Useful for building NFT card displays. (Paginated) +- `GetTokenIdsForContractWithMetadataByPage()`: Get NFT token IDs with metadata from a collection. Useful for building NFT card displays. (Nonpaginated) +- `GetNftMetadataForGivenTokenIDForContract()`: Get a single NFT metadata by token ID from a collection. Useful for building NFT card displays. +- `GetNftTransactionsForContractTokenId()`: Get all transactions of an NFT token. Useful for building a transaction history table or price chart. +- `GetTraitsForCollection()`: Used to fetch and render the traits of a collection as seen in rarity calculators. +- `GetAttributesForTraitInCollection()`: Used to get the count of unique values for traits within an NFT collection. +- `GetCollectionTraitsSummary()`: Used to calculate rarity scores for a collection based on its traits. +- `CheckOwnershipInNft()`: Used to verify ownership of NFTs (including ERC-721 and ERC-1155) within a collection. +- `CheckOwnershipInNftForSpecificTokenId()`: Used to verify ownership of a specific token (ERC-721 or ERC-1155) within a collection. +- `GetNftMarketSaleCount()`: Used to build a time-series chart of the sales count of an NFT collection. +- `GetNftMarketVolume()`: Used to build a time-series chart of the transaction volume of an NFT collection. +- `GetNftMarketFloorPrice()`: Used to render a price floor chart for an NFT collection. + +### PricingService + +The `PricingService` class refers to the [historical token prices API endpoint](https://www.covalenthq.com/docs/api/pricing/get-historical-token-prices/): + +- `GetTokenPrices()`: Get historic prices of a token between date ranges. Supports native tokens. + +### TransactionService + +The `TransactionService` class refers to the [transactions API endpoints](https://www.covalenthq.com/docs/api/transactions/get-a-transaction/): + +- `GetAllTransactionsForAddress()`: Fetch and render the most recent transactions involving an address. Frequently seen in wallet applications. (Paginated) +- `GetAllTransactionsForAddressByPage()`: Fetch and render the most recent transactions involving an address. Frequently seen in wallet applications. (Nonpaginated) +- `GetTransactionsForAddressV3()`: Fetch and render the most recent transactions involving an address. Frequently seen in wallet applications. +- `GetTransaction()`: Fetch and render a single transaction including its decoded log events. Additionally return semantically decoded information for DEX trades, lending and NFT sales. +- `GetTransactionsForBlock()`: Fetch all transactions including their decoded log events in a block and further flag interesting wallets or transactions. +- `GetTransactionSummary()`: Fetch the earliest and latest transactions, and the transaction count for a wallet. Calculate the age of the wallet and the time it has been idle and quickly gain insights into their engagement with web3. +- `GetTimeBucketTransactionsForAddress()`: Fetch all transactions including their decoded log events in a 15-minute time bucket interval. +- `GetTransactionsForBlockHashByPage()`: Fetch all transactions including their decoded log events in a block and further flag interesting wallets or transactions. +- `GetTransactionsForBlockHash()`: Fetch all transactions including their decoded log events in a block and further flag interesting wallets or transactions. + + +The functions `GetAllTransactionsForAddressByPage()`, `GetTransactionsForAddressV3()`, and `GetTimeBucketTransactionsForAddress()` have been enhanced with the introduction of `Next()` and `Prev()` support functions. These functions facilitate a smoother transition for developers navigating through our links object, which includes `Prev` and `Next` fields. Instead of requiring developers to manually extract values from these fields and create Golang API calls for the URL values, the new `Next()` and `Prev()` functions provide a streamlined approach, allowing developers to simulate this behavior more efficiently. + +```go +package main + +import ( + "fmt" + + "github.com/covalenthq/covalent-api-sdk-go/chains" + "github.com/covalenthq/covalent-api-sdk-go/covalentclient" +) + +func main() { + debug := true + + var Client = covalentclient.CovalentClient("API_KEY", covalentclient.CovalentClientSettings{Debug: &debug}) + resp, err := Client.TransactionService.GetAllTransactionsForAddressByPage(chains.EthMainnet, "demo.eth") + if err != nil { + fmt.Printf("error: %s", err) + } else { + if len(resp.Data.Items) != 0 { + // get first Balance + fmt.Println(*resp.Data.Items[0].BlockHash) + prev, er := resp.Data.Prev() + if er != nil { + fmt.Printf("error: %s", err) + } else { + fmt.Println(prev.Data.CurrentPage) + } + } + } +} +``` + +### XykService + +The `XykService` refers to the [Xy=k API endpoints](https://www.covalenthq.com/docs/api/xyk/get-xyk-pools/): + +- `GetPools()`: Get all the pools of a particular DEX. Supports most common DEXs (Uniswap, SushiSwap, etc), and returns detailed trading data (volume, liquidity, swap counts, fees, LP token prices). +- `GetPoolByAddress()`: Get the 7 day and 30 day time-series data (volume, liquidity, price) of a particular liquidity pool in a DEX. Useful for building time-series charts on DEX trading activity. +- `GetPoolsForTokenAddress()`: Get all pools and the supported DEX for a token. Useful for building a table of top pairs across all supported DEXes that the token is trading on. +- `GetPoolsForWalletAddress()`: Get all pools and supported DEX for a wallet. Useful for building a personal DEX UI showcasing pairs and supported DEXes associated to the wallet. +- `GetAddressExchangeBalances()`: Return balance of a wallet/contract address on a specific DEX. +- `GetNetworkExchangeTokens()`: Retrieve all network exchange tokens for a specific DEX. Useful for building a top tokens table by total liquidity within a particular DEX. +- `GetLpTokenView()`: Get a detailed view for a single liquidity pool token. Includes time series data. +- `GetSupportedDEXes()`: Get all the supported DEXs available for the xy=k endpoints, along with the swap fees and factory addresses. +- `GetDexForPoolAddress()`: Get the supported DEX given a pool address, along with the swap fees, DEX's logo url, and factory addresses. Useful to identifying the specific DEX to which a pair address is associated. +- `GetSingleNetworkExchangeToken()`: Get historical daily swap count for a single network exchange token. +- `GetTransactionsForAccountAddress()`: Get all the DEX transactions of a wallet. Useful for building tables of DEX activity segmented by wallet. +- `GetTransactionsForTokenAddress()`: Get all the transactions of a token within a particular DEX. Useful for getting a per-token view of DEX activity. +- `GetTransactionsForExchange()`: Get all the transactions of a particular DEX liquidity pool. Useful for building a transactions history table for an individual pool. +- `GetTransactionsForDex()`: Get all the the transactions for a given DEX. Useful for building DEX activity views. +- `GetEcosystemChartData()`: Get a 7d and 30d time-series chart of DEX activity. Includes volume and swap count. +- `GetHealthData()`: Ping the health of xy=k endpoints to get the synced block height per chain. + +## Built-in SDK Features +### Explaining Pagination Mechanism Within the SDK + +The following endpoints support pagination: + +- `GetErc20TransfersForWalletAddress()` +- `GetTokenHoldersV2ForTokenAddress()` +- `GetBlockHeights()` +- `GetLogEventsByAddress()` +- `GetLogEventsByTopicHash()` +- `GetChainCollections()` +- `GetTokenIdsForContractWithMetadata()` +- `GetAllTransactionsForAddress()` + +Using the Covalent API, paginated supported endpoints return only 100 items, such as transactions or log events, per page. However, the Covalent SDK leverages go channels to *seamlessly fetch all items without the user having to deal with pagination*. + +For example, the following fetches ALL transactions for `demo.eth` on Ethereum: +```go +package main + +import ( + "fmt" + "github.com/covalenthq/covalent-api-sdk-go/chains" + "github.com/covalenthq/covalent-api-sdk-go/covalentclient" + "github.com/covalenthq/covalent-api-sdk-go/services" +) + +func main() { + results := make(chan services.TransactionResult) + go func() { + var Client = covalentclient.CovalentClient("API_KEY") + // Call the function and pass parameters + resultChan := Client.TransactionService.GetAllTransactionsForAddress(chains.EthMainnet, "demo.eth") + + // Receive values from the result channel + for result := range resultChan { + // Process each result as it becomes available + results <- result + } + + // Close the results channel when done + close(results) + }() + // Now you can read values from the results channel as they arrive + for result := range results { + // Process each result + if result.Err != nil { + fmt.Printf("error: %s", result.Err) + } else { + fmt.Println(*result.Transaction.BlockHeight) + } + } +} +``` + +### Debugger Mode + +Developers have the option to enable a debugger mode that provides response times, the URLs of called endpoints, and the HTTP statuses of those endpoints. This feature helps users identify which endpoints may have encountered failures. The default is `debug = false` if no input is provided. + +```go +package main + +import ( + "fmt" + "github.com/covalenthq/covalent-api-sdk-go/chains" + "github.com/covalenthq/covalent-api-sdk-go/covalentclient" + "github.com/covalenthq/covalent-api-sdk-go/quotes" + "github.com/covalenthq/covalent-api-sdk-go/services" +) + +func main() { + nft := true + var quoteValue quotes.Quote = quotes.CAD + debug := true + + var Client = covalentclient.CovalentClient("API_KEY", covalentclient.CovalentClientSettings{Debug: &debug}) + resp, err := Client.BalanceService.GetTokenBalancesForWalletAddress(chains.EthMainnet, "demo.eth", + services.GetTokenBalancesForWalletAddressQueryParamOpts{Nft: &nft, QuoteCurrency: "eValue}) + if err != nil { + fmt.Printf("error: %s", err) + } else { + if len(resp.Data.Items) != 0 { + // get first Balance + fmt.Println(*resp.Data.Items[0].ContractName) + } + } +} +``` + +![example result image](https://www.datocms-assets.com/86369/1697154621-sdk-debugger-output.png) + +### Retry Mechanism + +Each endpoint is equipped with an exponential backoff algorithm that exponentially extends the wait time between retries, up to a `maximum of 5` retry attempts. + + +### Error Handling +The paginated endpoints throw an error message in this format: `An error occurred {ErrorCode}: {ErrorMessage}`. The developer will need to `catch` these errors. Note - these endpoints do not follow the default response format which is: +```go +❴ + "Data": ..., + "Error": false, + "ErrorMessage": nil, + "ErrorCode": nil +❵ +``` + +### Error codes +Covalent uses standard HTTP response codes to indicate the success or failure of an API request. In general: codes in the 2xx range indicate success. Codes in the 4xx range indicate an error that failed given the information provided (e.g., a required parameter was omitted, etc.). Codes in the 5xx range indicate an error with Covalent's servers (these are rare). + +| Code | Description | +| ----------- | ----------- | +| 200 | OK Everything worked as expected. | +| 400 | Bad Request The request could not be accepted, usually due to a missing required parameter. | +| 401 | Unauthorized No valid API key was provided. | +| 404 | Not Found The request path does not exist. | +| 429 | Too Many Requests You are being rate-limited. Please see the rate limiting section for more information. | +| 500, 502, 503 | Server Errors Something went wrong on Covalent's servers. These are rare. | + +## Tests +Before running tests, go to the `testutil/test_util.go` file and update your `API_KEY`. Do not commit this into your branch. Now you can run your tests in the root directory. +``` +go test ./tests/ +``` +OR + +you can run individual tests for each service, for example +``` +go test ./tests/security_service_test.go +``` + +## Documentation + +The Covalent API SDK documentation is integrated within the source code through `godoc` comments. When utilizing an Integrated Development Environment (IDE), the SDK provides generated types and accompanying documentation for seamless reference and usage. diff --git a/chains/chains.go b/chains/chains.go new file mode 100644 index 0000000..7d4b2d9 --- /dev/null +++ b/chains/chains.go @@ -0,0 +1,225 @@ +package chains + +type Chain string + +const ( + BtcMainnet Chain = "btc-mainnet" + EthMainnet Chain = "eth-mainnet" + MaticMainnet Chain = "matic-mainnet" + BscMainnet Chain = "bsc-mainnet" + AvalancheMainnet Chain = "avalanche-mainnet" + OptimismMainnet Chain = "optimism-mainnet" + FantomMainnet Chain = "fantom-mainnet" + MoonbeamMainnet Chain = "moonbeam-mainnet" + MoonbeamMoonriver Chain = "moonbeam-moonriver" + RskMainnet Chain = "rsk-mainnet" + ArbitrumMainnet Chain = "arbitrum-mainnet" + PalmMainnet Chain = "palm-mainnet" + KlaytnMainnet Chain = "klaytn-mainnet" + HecoMainnet Chain = "heco-mainnet" + NervosGodwokenMainnet Chain = "nervos-godwoken-mainnet" + AxieMainnet Chain = "axie-mainnet" + EvmosMainnet Chain = "evmos-mainnet" + AstarMainnet Chain = "astar-mainnet" + IotexMainnet Chain = "iotex-mainnet" + HarmonyMainnet Chain = "harmony-mainnet" + CronosMainnet Chain = "cronos-mainnet" + AuroraMainnet Chain = "aurora-mainnet" + EmeraldParatimeMainnet Chain = "emerald-paratime-mainnet" + BobaMainnet Chain = "boba-mainnet" + EthGoerli Chain = "eth-goerli" + MaticMumbai Chain = "matic-mumbai" + AvalancheTestnet Chain = "avalanche-testnet" + BscTestnet Chain = "bsc-testnet" + MoonbeamMoonbaseAlpha Chain = "moonbeam-moonbase-alpha" + RskTestnet Chain = "rsk-testnet" + ArbitrumGoerli Chain = "arbitrum-goerli" + FantomTestnet Chain = "fantom-testnet" + PalmTestnet Chain = "palm-testnet" + HecoTestnet Chain = "heco-testnet" + NervosGodwokenTestnet Chain = "nervos-godwoken-testnet" + EvmosTestnet Chain = "evmos-testnet" + IotexTestnet Chain = "iotex-testnet" + HarmonyTestnet Chain = "harmony-testnet" + AuroraTestnet Chain = "aurora-testnet" + ScrollL2Testnet Chain = "scroll-l2-testnet" + ScrollSepoliaTestnet Chain = "scroll-sepolia-testnet" + CovalentInternalNetworkV1 Chain = "covalent-internal-network-v1" + DefiKingdomsMainnet Chain = "defi-kingdoms-mainnet" + SwimmerMainnet Chain = "swimmer-mainnet" + BobaAvalancheMainnet Chain = "boba-avalanche-mainnet" + BobaBobabeamMainnet Chain = "boba-bobabeam-mainnet" + BobaBnbMainnet Chain = "boba-bnb-mainnet" + BobaRinkebyTestnet Chain = "boba-rinkeby-testnet" + BobaBobabaseTestnet Chain = "boba-bobabase-testnet" + BobaBnbTestnet Chain = "boba-bnb-testnet" + BobaAvalancheTestnet Chain = "boba-avalanche-testnet" + KlaytnTestnet Chain = "klaytn-testnet" + GatherMainnet Chain = "gather-mainnet" + GatherTestnet Chain = "gather-testnet" + SkaleCalypso Chain = "skale-calypso" + SkaleMainnet Chain = "skale-mainnet" + SkaleRazor Chain = "skale-razor" + AvalancheDexalotMainnet Chain = "avalanche-dexalot-mainnet" + SkaleOmnus Chain = "skale-omnus" + AvalancheDexalotTestnet Chain = "avalanche-dexalot-testnet" + AstarShibuya Chain = "astar-shibuya" + CronosTestnet Chain = "cronos-testnet" + DefiKingdomsTestnet Chain = "defi-kingdoms-testnet" + MetisMainnet Chain = "metis-mainnet" + MetisStardust Chain = "metis-stardust" + MilkomedaA1Mainnet Chain = "milkomeda-a1-mainnet" + MilkomedaA1Devnet Chain = "milkomeda-a1-devnet" + MilkomedaC1Mainnet Chain = "milkomeda-c1-mainnet" + MilkomedaC1Devnet Chain = "milkomeda-c1-devnet" + SwimmerTestnet Chain = "swimmer-testnet" + SolanaMainnet Chain = "solana-mainnet" + SkaleEuropa Chain = "skale-europa" + MeterMainnet Chain = "meter-mainnet" + MeterTestnet Chain = "meter-testnet" + SkaleExorde Chain = "skale-exorde" + BobaGoerli Chain = "boba-goerli" + NeonTestnet Chain = "neon-testnet" + SkaleStagingUum Chain = "skale-staging-uum" + SkaleStagingLcc Chain = "skale-staging-lcc" + ArbitrumNovaMainnet Chain = "arbitrum-nova-mainnet" + CantoMainnet Chain = "canto-mainnet" + BittorrentMainnet Chain = "bittorrent-mainnet" + BittorrentTestnet Chain = "bittorrent-testnet" + FlarenetworksFlareMainnet Chain = "flarenetworks-flare-mainnet" + FlarenetworksFlareTestnet Chain = "flarenetworks-flare-testnet" + FlarenetworksCanaryMainnet Chain = "flarenetworks-canary-mainnet" + FlarenetworksCanaryTestnet Chain = "flarenetworks-canary-testnet" + KccMainnet Chain = "kcc-mainnet" + KccTestnet Chain = "kcc-testnet" + PolygonZkevmTestnet Chain = "polygon-zkevm-testnet" + LineaTestnet Chain = "linea-testnet" + BaseTestnet Chain = "base-testnet" + MantleTestnet Chain = "mantle-testnet" + ScrollAlphaTestnet Chain = "scroll-alpha-testnet" + OasysMainnet Chain = "oasys-mainnet" + OasysTestnet Chain = "oasys-testnet" + FindoraMainnet Chain = "findora-mainnet" + FindoraForgeTestnet Chain = "findora-forge-testnet" + SxMainnet Chain = "sx-mainnet" + OasisSapphireMainnet Chain = "oasis-sapphire-mainnet" + OasisSapphireTestnet Chain = "oasis-sapphire-testnet" + OptimismGoerli Chain = "optimism-goerli" + PolygonZkevmMainnet Chain = "polygon-zkevm-mainnet" + HorizenYumaTestnet Chain = "horizen-yuma-testnet" + ClvParachain Chain = "clv-parachain" + EnergiMainnet Chain = "energi-mainnet" + EnergiTestnet Chain = "energi-testnet" + HorizenGobiTestnet Chain = "horizen-gobi-testnet" + EthSepolia Chain = "eth-sepolia" + SkaleNebula Chain = "skale-nebula" + SkaleBattleground Chain = "skale-battleground" + AvalancheMeldTestnet Chain = "avalanche-meld-testnet" + GunzillaTestnet Chain = "gunzilla-testnet" + UltronMainnet Chain = "ultron-mainnet" + UltronTestnet Chain = "ultron-testnet" + ZoraMainnet Chain = "zora-mainnet" + ZoraGoerliTestnet Chain = "zora-goerli-testnet" + NeonMainnet Chain = "neon-mainnet" + AvalancheShrapnelMainnet Chain = "avalanche-shrapnel-mainnet" + BaseMainnet Chain = "base-mainnet" + MantleMainnet Chain = "mantle-mainnet" + AvalancheLocoLegendsMainnet Chain = "avalanche-loco-legends-mainnet" + LineaMainnet Chain = "linea-mainnet" + HorizenEonMainnet Chain = "horizen-eon-mainnet" + AvalancheNumbers Chain = "avalanche-numbers" + AvalancheDos Chain = "avalanche-dos" + AvalancheStepNetwork Chain = "avalanche-step-network" + AvalancheXplus Chain = "avalanche-xplus" + AvalancheXanachain Chain = "avalanche-xanachain" + AvalancheMeldMainnet Chain = "avalanche-meld-mainnet" + OpsidePublicZkevm Chain = "opside-public-zkevm" + OpsideLawChain Chain = "opside-law-chain" + AvalancheShrapnelTestnet Chain = "avalanche-shrapnel-testnet" + AvalancheLocoLegendsTestnet Chain = "avalanche-loco-legends-testnet" + OpsideCbZkevm Chain = "opside-cb-zkevm" + OpsidePreAlphaTestnet Chain = "opside-pre-alpha-testnet" + OpsideEra7 Chain = "opside-era7" + OpsideXthrill Chain = "opside-xthrill" + ZksyncMainnet Chain = "zksync-mainnet" + MetisTestnet Chain = "metis-testnet" + ZksyncTestnet Chain = "zksync-testnet" + AvalancheBlitzTestnet Chain = "avalanche-blitz-testnet" + AvalancheDChainTestnet Chain = "avalanche-d-chain-testnet" + AvalancheGreenDotTestnet Chain = "avalanche-green-dot-testnet" + AvalancheMintaraTestnet Chain = "avalanche-mintara-testnet" + AvalancheBeamTestnet Chain = "avalanche-beam-testnet" + BnbMetaApesMainnet Chain = "bnb-meta-apes-mainnet" + BnbAntimatterMainnet Chain = "bnb-antimatter-mainnet" + BnbAntimatterTestnet Chain = "bnb-antimatter-testnet" + BnbOpbnbTestnet Chain = "bnb-opbnb-testnet" + OpsideDebox Chain = "opside-debox" + OpsideJackbot Chain = "opside-jackbot" + OpsideOdxZkevmTestnet Chain = "opside-odx-zkevm-testnet" + OpsideReadonContentTestnet Chain = "opside-readon-content-testnet" + OpsideRelation Chain = "opside-relation" + OpsideSoquestZkevm Chain = "opside-soquest-zkevm" + OpsideVip3 Chain = "opside-vip3" + OpsideZkmeta Chain = "opside-zkmeta" + AvalanchePulsarTestnet Chain = "avalanche-pulsar-testnet" + AvalancheUptn Chain = "avalanche-uptn" + BnbFncyMainnet Chain = "bnb-fncy-mainnet" + ZetachainTestnet Chain = "zetachain-testnet" + KintoTestnet Chain = "kinto-testnet" + ModeTestnet Chain = "mode-testnet" + LootMainnet Chain = "loot-mainnet" + BnbFncyTestnet Chain = "bnb-fncy-testnet" + MantaTestnet Chain = "manta-testnet" + PgnMainnet Chain = "pgn-mainnet" + PgnTestnet Chain = "pgn-testnet" + GnosisMainnet Chain = "gnosis-mainnet" + GnosisTestnet Chain = "gnosis-testnet" + RolluxMainnet Chain = "rollux-mainnet" + RolluxTestnet Chain = "rollux-testnet" + TaikoJolnirTestnet Chain = "taiko-jolnir-testnet" + OptimismSepolia Chain = "optimism-sepolia" + BnbOpbnbMainnet Chain = "bnb-opbnb-mainnet" + TelosMainnet Chain = "telos-mainnet" + TelosTestnet Chain = "telos-testnet" + AvalancheHubbleExchangeTestnet Chain = "avalanche-hubble-exchange-testnet" + AvalancheMihoTestnet Chain = "avalanche-miho-testnet" + AvalancheBulletinTestnet Chain = "avalanche-bulletin-testnet" + AvalancheKiwiTestnet Chain = "avalanche-kiwi-testnet" + AvalancheHeroTestnet Chain = "avalanche-hero-testnet" + AvalancheAvacloudTestnet Chain = "avalanche-avacloud-testnet" + AvalancheThirdwebTestnet Chain = "avalanche-thirdweb-testnet" + AvalancheMondrianTestnet Chain = "avalanche-mondrian-testnet" + AvalancheConduitTestnet Chain = "avalanche-conduit-testnet" + AvalancheNmacTestnet Chain = "avalanche-nmac-testnet" + AvalancheOrderlyTestnet Chain = "avalanche-orderly-testnet" + AvalancheAmplifyTestnet Chain = "avalanche-amplify-testnet" + AvalancheMiraiTestnet Chain = "avalanche-mirai-testnet" + AvalancheWagmiTestnet Chain = "avalanche-wagmi-testnet" + AvalanchePlaya3ullTestnet Chain = "avalanche-playa3ull-testnet" + AvalancheBeamMainnet Chain = "avalanche-beam-mainnet" + ScrollMainnet Chain = "scroll-mainnet" + EthHolesky Chain = "eth-holesky" + TomochainMainnet Chain = "tomochain-mainnet" + TomochainTestnet Chain = "tomochain-testnet" + AvalancheJono11Testnet Chain = "avalanche-jono11-testnet" + BaseSepoliaTestnet Chain = "base-sepolia-testnet" + XaiTestnet Chain = "xai-testnet" + ArbitrumSepolia Chain = "arbitrum-sepolia" + LumozPublicZksyncV2 Chain = "lumoz-public-zksync-v2" + LumozDecibling Chain = "lumoz-decibling" + LumozStarkSport Chain = "lumoz-stark-sport" + AvalancheLt0Testnet Chain = "avalanche-lt0-testnet" + AvalancheLt1Testnet Chain = "avalanche-lt1-testnet" + AvalancheLt2Testnet Chain = "avalanche-lt2-testnet" + AvalancheLt3Testnet Chain = "avalanche-lt3-testnet" + AvalancheLt4Testnet Chain = "avalanche-lt4-testnet" + AvalancheLt5Testnet Chain = "avalanche-lt5-testnet" + SyndrTestnet Chain = "syndr-testnet" + CrossfiEvmTestnet Chain = "crossfi-evm-testnet" + CeloMainnet Chain = "celo-mainnet" + TaikoKatlaTestnet Chain = "taiko-katla-testnet" + MovementMevmTestnet Chain = "movement-mevm-testnet" + ZoraSepoliaTestnet Chain = "zora-sepolia-testnet" + MerlinMainnet Chain = "merlin-mainnet" + MerlinTestnet Chain = "merlin-testnet" +) diff --git a/covalentclient/covalent_client.go b/covalentclient/covalent_client.go new file mode 100644 index 0000000..8f3d6b4 --- /dev/null +++ b/covalentclient/covalent_client.go @@ -0,0 +1,65 @@ +package covalentclient + +import ( + "github.com/covalenthq/covalent-api-sdk-go/services" + "github.com/covalenthq/covalent-api-sdk-go/utils" +) + +type CovalentClientSettings struct { + // Toggle to analyze the execution of each api request. + Debug *bool `json:"debug,omitempty"` + // The number of concurrent requests allowed. + ThreadCount *int `json:"thread_count,omitempty"` +} + +type CovalentClientType struct { + SecurityService services.SecurityService + BalanceService services.BalanceService + BaseService services.BaseService + NftService services.NftService + PricingService services.PricingService + TransactionService services.TransactionService + XykService services.XykService + Debug bool + ThreadCount int +} + +var defaultDebug bool = false +var defaultThreadCount int = 3 + +func CovalentClient(apiKey string, settings ...CovalentClientSettings) *CovalentClientType { + client := &CovalentClientType{} + validator := utils.NewApiKeyValidator(apiKey) + isValidKey := validator.IsValidApiKey() + + if len(settings) == 0 { + client.Debug = defaultDebug + client.ThreadCount = defaultThreadCount + } else { + // Settings were provided, apply them + setting := settings[0] // Assuming only one settings struct is passed + + if setting.Debug == nil { + client.Debug = defaultDebug + } else { + client.Debug = *setting.Debug + } + + if setting.ThreadCount == nil { + client.ThreadCount = defaultThreadCount + } else { + client.ThreadCount = *setting.ThreadCount + } + } + + client.SecurityService = services.NewSecurityServiceImpl(apiKey, client.Debug, client.ThreadCount, isValidKey) + client.BalanceService = services.NewBalanceServiceImpl(apiKey, client.Debug, client.ThreadCount, isValidKey) + client.BaseService = services.NewBaseServiceImpl(apiKey, client.Debug, client.ThreadCount, isValidKey) + client.NftService = services.NewNftServiceImpl(apiKey, client.Debug, client.ThreadCount, isValidKey) + client.PricingService = services.NewPricingServiceImpl(apiKey, client.Debug, client.ThreadCount, isValidKey) + client.TransactionService = services.NewTransactionServiceImpl(apiKey, client.Debug, client.ThreadCount, isValidKey) + client.XykService = services.NewXykServiceImpl(apiKey, client.Debug, client.ThreadCount, isValidKey) + + return client + +} diff --git a/genericmodels/models.go b/genericmodels/models.go new file mode 100644 index 0000000..974d7fe --- /dev/null +++ b/genericmodels/models.go @@ -0,0 +1,123 @@ +package genericmodels + +import ( + "time" + + "github.com/covalenthq/covalent-api-sdk-go/utils" +) + +type NftCollectionAttribute struct { + TraitType *string `json:"trait_type,omitempty"` + Value *interface{} `json:"value,omitempty"` +} +type DecodedItem struct { + Name *string `json:"name,omitempty"` + Signature *string `json:"signature,omitempty"` + Params *[]Param `json:"params,omitempty"` +} +type Param struct { + Name *string `json:"name,omitempty"` + Type *string `json:"type,omitempty"` + Indexed *bool `json:"indexed,omitempty"` + Decoded *bool `json:"decoded,omitempty"` + Value *interface{} `json:"value,omitempty"` +} +type LogEvent struct { + // The block signed timestamp in UTC. + BlockSignedAt *time.Time `json:"block_signed_at,omitempty"` + // The height of the block. + BlockHeight *int64 `json:"block_height,omitempty"` + // The offset is the position of the tx in the block. + TxOffset *int64 `json:"tx_offset,omitempty"` + // The offset is the position of the log entry within an event log. + LogOffset *int64 `json:"log_offset,omitempty"` + // The requested transaction hash. + TxHash *string `json:"tx_hash,omitempty"` + // The log topics in raw data. + RawLogTopics *[]string `json:"raw_log_topics,omitempty"` + // Use contract decimals to format the token balance for display purposes - divide the balance by `10^{contract_decimals}`. + SenderContractDecimals *int `json:"sender_contract_decimals,omitempty"` + // The name of the sender. + SenderName *string `json:"sender_name,omitempty"` + SenderContractTickerSymbol *string `json:"sender_contract_ticker_symbol,omitempty"` + // The address of the sender. + SenderAddress *string `json:"sender_address,omitempty"` + // The label of the sender address. + SenderAddressLabel *string `json:"sender_address_label,omitempty"` + // The contract logo URL. + SenderLogoUrl *string `json:"sender_logo_url,omitempty"` + // The address of the deployed UniswapV2 like factory contract for this DEX. + SenderFactoryAddress *string `json:"sender_factory_address,omitempty"` + // The log events in raw. + RawLogData *string `json:"raw_log_data,omitempty"` + // The decoded item. + Decoded *DecodedItem `json:"decoded,omitempty"` +} +type ContractMetadata struct { + // Use contract decimals to format the token balance for display purposes - divide the balance by `10^{contract_decimals}`. + ContractDecimals *int `json:"contract_decimals,omitempty"` + // The string returned by the `name()` method. + ContractName *string `json:"contract_name,omitempty"` + // The ticker symbol for this contract. This field is set by a developer and non-unique across a network. + ContractTickerSymbol *string `json:"contract_ticker_symbol,omitempty"` + // Use the relevant `contract_address` to lookup prices, logos, token transfers, etc. + ContractAddress *string `json:"contract_address,omitempty"` + // A list of supported standard ERC interfaces, eg: `ERC20` and `ERC721`. + SupportsErc *[]string `json:"supports_erc,omitempty"` + // The contract logo URL. + LogoUrl *string `json:"logo_url,omitempty"` +} +type Explorer struct { + // The name of the explorer. + Label *string `json:"label,omitempty"` + // The URL of the explorer. + Url *string `json:"url,omitempty"` +} +type Pagination struct { + // True is there is another page. + HasMore *bool `json:"has_more,omitempty"` + // The requested page number. + PageNumber *int `json:"page_number,omitempty"` + // The requested number of items on the current page. + PageSize *int `json:"page_size,omitempty"` + // The total number of items across all pages for this request. + TotalCount *int `json:"total_count,omitempty"` +} +type LogoUrls struct { + // The token logo URL. + TokenLogoUrl *string `json:"token_logo_url,omitempty"` + // The protocol logo URL. + ProtocolLogoUrl *string `json:"protocol_logo_url,omitempty"` + // The chain logo URL. + ChainLogoUrl *string `json:"chain_logo_url,omitempty"` +} +type NftData struct { + // The token's id. + TokenId *utils.BigInt `json:"token_id,omitempty"` + TokenUrl *string `json:"token_url,omitempty"` + // The original minter. + OriginalOwner *string `json:"original_owner,omitempty"` + // The current holder of this NFT. + CurrentOwner *string `json:"current_owner,omitempty"` + ExternalData *NftExternalData `json:"external_data,omitempty"` + // If `true`, the asset data is available from the Covalent CDN. + AssetCached *bool `json:"asset_cached,omitempty"` + // If `true`, the image data is available from the Covalent CDN. + ImageCached *bool `json:"image_cached,omitempty"` +} + +type NftExternalData struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + AssetUrl *string `json:"asset_url,omitempty"` + AssetFileExtension *string `json:"asset_file_extension,omitempty"` + AssetMimeType *string `json:"asset_mime_type,omitempty"` + AssetSizeBytes *string `json:"asset_size_bytes,omitempty"` + Image *string `json:"image,omitempty"` + Image256 *string `json:"image_256,omitempty"` + Image512 *string `json:"image_512,omitempty"` + Image1024 *string `json:"image_1024,omitempty"` + AnimationUrl *string `json:"animation_url,omitempty"` + ExternalUrl *string `json:"external_url,omitempty"` + Attributes *[]NftCollectionAttribute `json:"attributes,omitempty"` +} diff --git a/quotes/quotes.go b/quotes/quotes.go new file mode 100644 index 0000000..365e286 --- /dev/null +++ b/quotes/quotes.go @@ -0,0 +1,22 @@ +package quotes + +type Quote string + +const ( + USD Quote = "USD" + CAD Quote = "CAD" + EUR Quote = "EUR" + SGD Quote = "SGD" + INR Quote = "INR" + JPY Quote = "JPY" + VND Quote = "VND" + CNY Quote = "CNY" + KRW Quote = "KRW" + RUB Quote = "RUB" + TRY Quote = "TRY" + NGN Quote = "NGN" + ARS Quote = "ARS" + AUD Quote = "AUD" + CHF Quote = "CHF" + GBP Quote = "GBP" +) diff --git a/services/balance_service.go b/services/balance_service.go new file mode 100644 index 0000000..ca3d055 --- /dev/null +++ b/services/balance_service.go @@ -0,0 +1,1391 @@ +package services + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/covalenthq/covalent-api-sdk-go/chains" + "github.com/covalenthq/covalent-api-sdk-go/genericmodels" + "github.com/covalenthq/covalent-api-sdk-go/quotes" + "github.com/covalenthq/covalent-api-sdk-go/utils" +) + +type BalancesResponse struct { + // The requested address. + Address string `json:"address"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // The requested quote currency eg: `USD`. + QuoteCurrency string `json:"quote_currency"` + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // List of response items. + Items []BalanceItem `json:"items"` +} +type BalanceItem struct { + // Use contract decimals to format the token balance for display purposes - divide the balance by `10^{contract_decimals}`. + ContractDecimals *int `json:"contract_decimals,omitempty"` + // The string returned by the `name()` method. + ContractName *string `json:"contract_name,omitempty"` + // The ticker symbol for this contract. This field is set by a developer and non-unique across a network. + ContractTickerSymbol *string `json:"contract_ticker_symbol,omitempty"` + // Use the relevant `contract_address` to lookup prices, logos, token transfers, etc. + ContractAddress *string `json:"contract_address,omitempty"` + // A display-friendly name for the contract. + ContractDisplayName *string `json:"contract_display_name,omitempty"` + // A list of supported standard ERC interfaces, eg: `ERC20` and `ERC721`. + SupportsErc *[]string `json:"supports_erc,omitempty"` + // The contract logo URL. + LogoUrl *string `json:"logo_url,omitempty"` + // The contract logo URLs. + LogoUrls *LogoUrls `json:"logo_urls,omitempty"` + // The timestamp when the token was transferred. + LastTransferredAt *time.Time `json:"last_transferred_at,omitempty"` + // Indicates if a token is the chain's native gas token, eg: ETH on Ethereum. + NativeToken *bool `json:"native_token,omitempty"` + // One of `cryptocurrency`, `stablecoin`, `nft` or `dust`. + Type *string `json:"type,omitempty"` + // Denotes whether the token is suspected spam. + IsSpam *bool `json:"is_spam,omitempty"` + // The asset balance. Use `contract_decimals` to scale this balance for display purposes. + Balance *utils.BigInt `json:"balance,omitempty"` + // The 24h asset balance. Use `contract_decimals` to scale this balance for display purposes. + Balance24h *utils.BigInt `json:"balance_24h,omitempty"` + // The exchange rate for the requested quote currency. + QuoteRate *float64 `json:"quote_rate,omitempty"` + // The 24h exchange rate for the requested quote currency. + QuoteRate24h *float64 `json:"quote_rate_24h,omitempty"` + // The current balance converted to fiat in `quote-currency`. + Quote *float64 `json:"quote,omitempty"` + // The 24h balance converted to fiat in `quote-currency`. + Quote24h *float64 `json:"quote_24h,omitempty"` + // A prettier version of the quote for rendering purposes. + PrettyQuote *string `json:"pretty_quote,omitempty"` + // A prettier version of the 24h quote for rendering purposes. + PrettyQuote24h *string `json:"pretty_quote_24h,omitempty"` + // The protocol metadata. + ProtocolMetadata *ProtocolMetadata `json:"protocol_metadata,omitempty"` + // NFT-specific data. + NftData *[]BalanceNftData `json:"nft_data,omitempty"` +} +type ProtocolMetadata struct { + // The name of the protocol. + ProtocolName *string `json:"protocol_name,omitempty"` +} +type BalanceNftData struct { + // The token's id. + TokenId *utils.BigInt `json:"token_id,omitempty"` + // The count of the number of NFTs with this ID. + TokenBalance *utils.BigInt `json:"token_balance,omitempty"` + // External URL for additional metadata. + TokenUrl *string `json:"token_url,omitempty"` + // A list of supported standard ERC interfaces, eg: `ERC20` and `ERC721`. + SupportsErc *[]string `json:"supports_erc,omitempty"` + // The latest price value on chain of the token ID. + TokenPriceWei *utils.BigInt `json:"token_price_wei,omitempty"` + // The latest quote_rate of the token ID denominated in unscaled ETH. + TokenQuoteRateEth *string `json:"token_quote_rate_eth,omitempty"` + // The address of the original owner of this NFT. + OriginalOwner *string `json:"original_owner,omitempty"` + ExternalData *NftExternalDataV1 `json:"external_data,omitempty"` + // The current owner of this NFT. + Owner *string `json:"owner,omitempty"` + // The address of the current owner of this NFT. + OwnerAddress *string `json:"owner_address,omitempty"` + // When set to true, this NFT has been Burned. + Burned *bool `json:"burned,omitempty"` +} +type NftExternalDataV1 struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + Image *string `json:"image,omitempty"` + Image256 *string `json:"image_256,omitempty"` + Image512 *string `json:"image_512,omitempty"` + Image1024 *string `json:"image_1024,omitempty"` + AnimationUrl *string `json:"animation_url,omitempty"` + ExternalUrl *string `json:"external_url,omitempty"` + Attributes *[]genericmodels.NftCollectionAttribute `json:"attributes,omitempty"` + Owner *string `json:"owner,omitempty"` +} +type PortfolioResponse struct { + // The requested address. + Address string `json:"address"` + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested quote currency eg: `USD`. + QuoteCurrency string `json:"quote_currency"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []PortfolioItem `json:"items"` +} +type PortfolioItem struct { + // Use the relevant `contract_address` to lookup prices, logos, token transfers, etc. + ContractAddress *string `json:"contract_address,omitempty"` + // Use contract decimals to format the token balance for display purposes - divide the balance by `10^{contract_decimals}`. + ContractDecimals *int `json:"contract_decimals,omitempty"` + // The string returned by the `name()` method. + ContractName *string `json:"contract_name,omitempty"` + // The ticker symbol for this contract. This field is set by a developer and non-unique across a network. + ContractTickerSymbol *string `json:"contract_ticker_symbol,omitempty"` + // The contract logo URL. + LogoUrl *string `json:"logo_url,omitempty"` + Holdings *[]HoldingItem `json:"holdings,omitempty"` +} +type HoldingItem struct { + // The exchange rate for the requested quote currency. + QuoteRate *float64 `json:"quote_rate,omitempty"` + Timestamp *time.Time `json:"timestamp,omitempty"` + Close *OhlcItem `json:"close,omitempty"` + High *OhlcItem `json:"high,omitempty"` + Low *OhlcItem `json:"low,omitempty"` + Open *OhlcItem `json:"open,omitempty"` +} +type OhlcItem struct { + // The asset balance. Use `contract_decimals` to scale this balance for display purposes. + Balance *utils.BigInt `json:"balance,omitempty"` + // The current balance converted to fiat in `quote-currency`. + Quote *float64 `json:"quote,omitempty"` + // A prettier version of the quote for rendering purposes. + PrettyQuote *string `json:"pretty_quote,omitempty"` +} +type Erc20TransfersResponse struct { + // The requested address. + Address string `json:"address"` + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested quote currency eg: `USD`. + QuoteCurrency string `json:"quote_currency"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []BlockTransactionWithContractTransfers `json:"items"` + // Pagination metadata. + Pagination genericmodels.Pagination `json:"pagination"` +} +type BlockTransactionWithContractTransfers struct { + // The block signed timestamp in UTC. + BlockSignedAt *time.Time `json:"block_signed_at,omitempty"` + // The height of the block. + BlockHeight *int `json:"block_height,omitempty"` + // The hash of the block. Use it to remove transactions from re-org-ed blocks. + BlockHash *string `json:"block_hash,omitempty"` + // The requested transaction hash. + TxHash *string `json:"tx_hash,omitempty"` + // The offset is the position of the tx in the block. + TxOffset *int `json:"tx_offset,omitempty"` + // Whether or not transaction is successful. + Successful *bool `json:"successful,omitempty"` + // The address of the miner. + MinerAddress *string `json:"miner_address,omitempty"` + // The sender's wallet address. + FromAddress *string `json:"from_address,omitempty"` + // The label of `from` address. + FromAddressLabel *string `json:"from_address_label,omitempty"` + // The receiver's wallet address. + ToAddress *string `json:"to_address,omitempty"` + // The label of `to` address. + ToAddressLabel *string `json:"to_address_label,omitempty"` + // The value attached to this tx. + Value *utils.BigInt `json:"value,omitempty"` + // The value attached in `quote-currency` to this tx. + ValueQuote *float64 `json:"value_quote,omitempty"` + // A prettier version of the quote for rendering purposes. + PrettyValueQuote *string `json:"pretty_value_quote,omitempty"` + // The requested chain native gas token metadata. + GasMetadata *genericmodels.ContractMetadata `json:"gas_metadata,omitempty"` + GasOffered *int64 `json:"gas_offered,omitempty"` + // The gas spent for this tx. + GasSpent *int64 `json:"gas_spent,omitempty"` + // The gas price at the time of this tx. + GasPrice *int64 `json:"gas_price,omitempty"` + // The transaction's gas_price * gas_spent, denoted in wei. + FeesPaid *utils.BigInt `json:"fees_paid,omitempty"` + // The gas spent in `quote-currency` denomination. + GasQuote *float64 `json:"gas_quote,omitempty"` + // A prettier version of the quote for rendering purposes. + PrettyGasQuote *string `json:"pretty_gas_quote,omitempty"` + // The native gas exchange rate for the requested `quote-currency`. + GasQuoteRate *float64 `json:"gas_quote_rate,omitempty"` + Transfers *[]TokenTransferItem `json:"transfers,omitempty"` +} +type TokenTransferItem struct { + // The block signed timestamp in UTC. + BlockSignedAt *time.Time `json:"block_signed_at,omitempty"` + // The requested transaction hash. + TxHash *string `json:"tx_hash,omitempty"` + // The sender's wallet address. + FromAddress *string `json:"from_address,omitempty"` + // The label of `from` address. + FromAddressLabel *string `json:"from_address_label,omitempty"` + // The receiver's wallet address. + ToAddress *string `json:"to_address,omitempty"` + // The label of `to` address. + ToAddressLabel *string `json:"to_address_label,omitempty"` + // Use contract decimals to format the token balance for display purposes - divide the balance by `10^{contract_decimals}`. + ContractDecimals *int `json:"contract_decimals,omitempty"` + // The string returned by the `name()` method. + ContractName *string `json:"contract_name,omitempty"` + // The ticker symbol for this contract. This field is set by a developer and non-unique across a network. + ContractTickerSymbol *string `json:"contract_ticker_symbol,omitempty"` + // Use the relevant `contract_address` to lookup prices, logos, token transfers, etc. + ContractAddress *string `json:"contract_address,omitempty"` + // The contract logo URL. + LogoUrl *string `json:"logo_url,omitempty"` + // Categorizes token transactions as either `transfer-in` or `transfer-out`, indicating whether tokens are being received or sent from an account. + TransferType *string `json:"transfer_type,omitempty"` + // The delta attached to this transfer. + Delta *utils.BigInt `json:"delta,omitempty"` + // The asset balance. Use `contract_decimals` to scale this balance for display purposes. + Balance *utils.BigInt `json:"balance,omitempty"` + // The exchange rate for the requested quote currency. + QuoteRate *float64 `json:"quote_rate,omitempty"` + // The current delta converted to fiat in `quote-currency`. + DeltaQuote *float64 `json:"delta_quote,omitempty"` + // A prettier version of the quote for rendering purposes. + PrettyDeltaQuote *string `json:"pretty_delta_quote,omitempty"` + // The current balance converted to fiat in `quote-currency`. + BalanceQuote *float64 `json:"balance_quote,omitempty"` + // Additional details on which transfer events were invoked. Defaults to `true`. + MethodCalls *[]MethodCallsForTransfers `json:"method_calls,omitempty"` + // The explorer links for this transaction. + Explorers *[]genericmodels.Explorer `json:"explorers,omitempty"` +} +type MethodCallsForTransfers struct { + // The address of the sender. + SenderAddress *string `json:"sender_address,omitempty"` + Method *string `json:"method,omitempty"` +} +type TokenHoldersResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []TokenHolder `json:"items"` + // Pagination metadata. + Pagination genericmodels.Pagination `json:"pagination"` +} +type TokenHolder struct { + // Use contract decimals to format the token balance for display purposes - divide the balance by `10^{contract_decimals}`. + ContractDecimals *int `json:"contract_decimals,omitempty"` + // The string returned by the `name()` method. + ContractName *string `json:"contract_name,omitempty"` + // The ticker symbol for this contract. This field is set by a developer and non-unique across a network. + ContractTickerSymbol *string `json:"contract_ticker_symbol,omitempty"` + // Use the relevant `contract_address` to lookup prices, logos, token transfers, etc. + ContractAddress *string `json:"contract_address,omitempty"` + // A list of supported standard ERC interfaces, eg: `ERC20` and `ERC721`. + SupportsErc *[]string `json:"supports_erc,omitempty"` + // The contract logo URL. + LogoUrl *string `json:"logo_url,omitempty"` + // The requested address. + Address *string `json:"address,omitempty"` + // The asset balance. Use `contract_decimals` to scale this balance for display purposes. + Balance *utils.BigInt `json:"balance,omitempty"` + // Total supply of this token. + TotalSupply *utils.BigInt `json:"total_supply,omitempty"` + // The height of the block. + BlockHeight *int64 `json:"block_height,omitempty"` +} +type TokenHoldersChangesResponse struct { + // The token holder. + TokenHolder string `json:"token_holder"` + // The starting block balance. + PrevBalance string `json:"prev_balance"` + // The starting block height. + PrevBlockHeight int64 `json:"prev_block_height"` + // The ending block balance. + NextBalance string `json:"next_balance"` + // The ending block height. + NextBlockHeight int64 `json:"next_block_height"` + // The difference of the balance. + Diff string `json:"diff"` +} +type HistoricalBalancesResponse struct { + // The requested address. + Address string `json:"address"` + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested quote currency eg: `USD`. + QuoteCurrency string `json:"quote_currency"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []HistoricalBalanceItem `json:"items"` +} +type HistoricalBalanceItem struct { + // Use contract decimals to format the token balance for display purposes - divide the balance by `10^{contract_decimals}`. + ContractDecimals *int `json:"contract_decimals,omitempty"` + // The string returned by the `name()` method. + ContractName *string `json:"contract_name,omitempty"` + // The ticker symbol for this contract. This field is set by a developer and non-unique across a network. + ContractTickerSymbol *string `json:"contract_ticker_symbol,omitempty"` + // Use the relevant `contract_address` to lookup prices, logos, token transfers, etc. + ContractAddress *string `json:"contract_address,omitempty"` + // A list of supported standard ERC interfaces, eg: `ERC20` and `ERC721`. + SupportsErc *[]string `json:"supports_erc,omitempty"` + // The contract logo URL. + LogoUrl *string `json:"logo_url,omitempty"` + // The height of the block. + BlockHeight *int64 `json:"block_height,omitempty"` + // The block height when the token was last transferred. + LastTransferredBlockHeight *int64 `json:"last_transferred_block_height,omitempty"` + ContractDisplayName *string `json:"contract_display_name,omitempty"` + // The timestamp when the token was transferred. + LastTransferredAt *time.Time `json:"last_transferred_at,omitempty"` + // Indicates if a token is the chain's native gas token, eg: ETH on Ethereum. + NativeToken *bool `json:"native_token,omitempty"` + // One of `cryptocurrency`, `stablecoin`, `nft` or `dust`. + Type *string `json:"type,omitempty"` + // Denotes whether the token is suspected spam. + IsSpam *bool `json:"is_spam,omitempty"` + // The asset balance. Use `contract_decimals` to scale this balance for display purposes. + Balance *utils.BigInt `json:"balance,omitempty"` + // The exchange rate for the requested quote currency. + QuoteRate *float64 `json:"quote_rate,omitempty"` + // The current balance converted to fiat in `quote-currency`. + Quote *float64 `json:"quote,omitempty"` + // A prettier version of the quote for rendering purposes. + PrettyQuote *string `json:"pretty_quote,omitempty"` + // The protocol metadata. + ProtocolMetadata *ProtocolMetadata `json:"protocol_metadata,omitempty"` + // NFT-specific data. + NftData *[]BalanceNftData `json:"nft_data,omitempty"` +} +type TokenBalanceNativeResponse struct { + // The requested address. + Address string `json:"address"` + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested quote currency eg: `USD`. + QuoteCurrency string `json:"quote_currency"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []NativeBalanceItem `json:"items"` +} +type NativeBalanceItem struct { + // Use contract decimals to format the token balance for display purposes - divide the balance by `10^{contract_decimals}`. + ContractDecimals *int `json:"contract_decimals,omitempty"` + // The string returned by the `name()` method. + ContractName *string `json:"contract_name,omitempty"` + // The ticker symbol for this contract. This field is set by a developer and non-unique across a network. + ContractTickerSymbol *string `json:"contract_ticker_symbol,omitempty"` + // Use the relevant `contract_address` to lookup prices, logos, token transfers, etc. + ContractAddress *string `json:"contract_address,omitempty"` + // A list of supported standard ERC interfaces, eg: `ERC20` and `ERC721`. + SupportsErc *[]string `json:"supports_erc,omitempty"` + // The contract logo URL. + LogoUrl *string `json:"logo_url,omitempty"` + // The height of the block. + BlockHeight *int64 `json:"block_height,omitempty"` + // The asset balance. Use `contract_decimals` to scale this balance for display purposes. + Balance *utils.BigInt `json:"balance,omitempty"` + // The exchange rate for the requested quote currency. + QuoteRate *float64 `json:"quote_rate,omitempty"` + // The current balance converted to fiat in `quote-currency`. + Quote *float64 `json:"quote,omitempty"` + // A prettier version of the quote for rendering purposes. + PrettyQuote *string `json:"pretty_quote,omitempty"` +} +type BlockTransactionWithContractTransfersResult struct { + BlockTransactionWithContractTransfers BlockTransactionWithContractTransfers + Err error +} + +type TokenHolderResult struct { + TokenHolder TokenHolder + Err error +} + +type GetTokenBalancesForWalletAddressQueryParamOpts struct { + // The currency to convert. Supports `USD`, `CAD`, `EUR`, `SGD`, `INR`, `JPY`, `VND`, `CNY`, `KRW`, `RUB`, `TRY`, `NGN`, `ARS`, `AUD`, `CHF`, and `GBP`. + QuoteCurrency *quotes.Quote `json:"quoteCurrency,omitempty"` + // If `true`, NFTs will be included in the response. + Nft *bool `json:"nft,omitempty"` + // If `true`, only NFTs that have been cached will be included in the response. Helpful for faster response times. + NoNftFetch *bool `json:"noNftFetch,omitempty"` + // If `true`, the suspected spam tokens are removed. Supports `eth-mainnet` and `matic-mainnet`. + NoSpam *bool `json:"noSpam,omitempty"` + // If `true`, the response shape is limited to a list of collections and token ids, omitting metadata and asset information. Helpful for faster response times and wallets holding a large number of NFTs. + NoNftAssetMetadata *bool `json:"noNftAssetMetadata,omitempty"` +} +type GetHistoricalPortfolioForWalletAddressQueryParamOpts struct { + // The currency to convert. Supports `USD`, `CAD`, `EUR`, `SGD`, `INR`, `JPY`, `VND`, `CNY`, `KRW`, `RUB`, `TRY`, `NGN`, `ARS`, `AUD`, `CHF`, and `GBP`. + QuoteCurrency *quotes.Quote `json:"quoteCurrency,omitempty"` + // The number of days to return data for. Defaults to 30 days. + Days *int `json:"days,omitempty"` +} +type GetErc20TransfersForWalletAddressQueryParamOpts struct { + // The currency to convert. Supports `USD`, `CAD`, `EUR`, `SGD`, `INR`, `JPY`, `VND`, `CNY`, `KRW`, `RUB`, `TRY`, `NGN`, `ARS`, `AUD`, `CHF`, and `GBP`. + QuoteCurrency *quotes.Quote `json:"quoteCurrency,omitempty"` + // The requested contract address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically. + ContractAddress *string `json:"contractAddress,omitempty"` + // The block height to start from, defaults to `0`. + StartingBlock *int `json:"startingBlock,omitempty"` + // The block height to end at, defaults to current block height. + EndingBlock *int `json:"endingBlock,omitempty"` + // Number of items per page. Omitting this parameter defaults to 100. + PageSize *int `json:"pageSize,omitempty"` + // 0-indexed page number to begin pagination. + PageNumber *int `json:"pageNumber,omitempty"` +} +type GetTokenHoldersV2ForTokenAddressQueryParamOpts struct { + // Ending block to define a block range. Omitting this parameter defaults to the latest block height. + BlockHeight *string `json:"blockHeight,omitempty"` + // Ending date to define a block range (YYYY-MM-DD). Omitting this parameter defaults to the current date. + Date *string `json:"date,omitempty"` + // Number of items per page. Note: Currently, only values of `100` and `1000` are supported. Omitting this parameter defaults to 100. + PageSize *int `json:"pageSize,omitempty"` + // 0-indexed page number to begin pagination. + PageNumber *int `json:"pageNumber,omitempty"` +} +type GetHistoricalTokenBalancesForWalletAddressQueryParamOpts struct { + // The currency to convert. Supports `USD`, `CAD`, `EUR`, `SGD`, `INR`, `JPY`, `VND`, `CNY`, `KRW`, `RUB`, `TRY`, `NGN`, `ARS`, `AUD`, `CHF`, and `GBP`. + QuoteCurrency *quotes.Quote `json:"quoteCurrency,omitempty"` + // If `true`, NFTs will be included in the response. + Nft *bool `json:"nft,omitempty"` + // If `true`, only NFTs that have been cached will be included in the response. Helpful for faster response times. + NoNftFetch *bool `json:"noNftFetch,omitempty"` + // If `true`, the suspected spam tokens are removed. Supports `eth-mainnet` and `matic-mainnet`. + NoSpam *bool `json:"noSpam,omitempty"` + // If `true`, the response shape is limited to a list of collections and token ids, omitting metadata and asset information. Helpful for faster response times and wallets holding a large number of NFTs. + NoNftAssetMetadata *bool `json:"noNftAssetMetadata,omitempty"` + // Ending block to define a block range. Omitting this parameter defaults to the latest block height. + BlockHeight *string `json:"blockHeight,omitempty"` + // Ending date to define a block range (YYYY-MM-DD). Omitting this parameter defaults to the current date. + Date *string `json:"date,omitempty"` +} +type GetNativeTokenBalanceQueryParamOpts struct { + // The currency to convert. Supports `USD`, `CAD`, `EUR`, `SGD`, `INR`, `JPY`, `VND`, `CNY`, `KRW`, `RUB`, `TRY`, `NGN`, `ARS`, `AUD`, `CHF`, and `GBP`. + QuoteCurrency *quotes.Quote `json:"quoteCurrency,omitempty"` + // Ending block to define a block range. Omitting this parameter defaults to the latest block height. + BlockHeight *string `json:"blockHeight,omitempty"` +} + +func NewBalanceServiceImpl(apiKey string, debug bool, threadCount int, isValidKey bool) BalanceService { + + return &balanceServiceImpl{APIKey: apiKey, Debug: debug, ThreadCount: threadCount, IskeyValid: isValidKey} +} + +type BalanceService interface { + + // Commonly used to fetch the native, fungible (ERC20), and non-fungible (ERC721 & ERC1155) tokens held by an address. Response includes spot prices and other metadata. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // walletAddress: The requested address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetTokenBalancesForWalletAddress(chainName chains.Chain, walletAddress string, queryParamOpts ...GetTokenBalancesForWalletAddressQueryParamOpts) (*utils.Response[BalancesResponse], error) + + // Commonly used to render a daily portfolio balance for an address broken down by the token. The timeframe is user-configurable, defaults to 30 days. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // walletAddress: The requested address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetHistoricalPortfolioForWalletAddress(chainName chains.Chain, walletAddress string, queryParamOpts ...GetHistoricalPortfolioForWalletAddressQueryParamOpts) (*utils.Response[PortfolioResponse], error) + + // Commonly used to render the transfer-in and transfer-out of a token along with historical prices from an address. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // walletAddress: The requested address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetErc20TransfersForWalletAddress(chainName chains.Chain, walletAddress string, queryParamOpts ...GetErc20TransfersForWalletAddressQueryParamOpts) <-chan BlockTransactionWithContractTransfersResult + + // Commonly used to render the transfer-in and transfer-out of a token along with historical prices from an address. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // walletAddress: The requested address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetErc20TransfersForWalletAddressByPage(chainName chains.Chain, walletAddress string, queryParamOpts ...GetErc20TransfersForWalletAddressQueryParamOpts) (*utils.Response[Erc20TransfersResponse], error) + + // Commonly used to get a list of all the token holders for a specified ERC20 or ERC721 token. Returns historic token holders when block-height is set (defaults to `latest`). Useful for building pie charts of token holders. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // tokenAddress: The requested address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetTokenHoldersV2ForTokenAddress(chainName chains.Chain, tokenAddress string, queryParamOpts ...GetTokenHoldersV2ForTokenAddressQueryParamOpts) <-chan TokenHolderResult + + // Commonly used to get a list of all the token holders for a specified ERC20 or ERC721 token. Returns historic token holders when block-height is set (defaults to `latest`). Useful for building pie charts of token holders. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // tokenAddress: The requested address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetTokenHoldersV2ForTokenAddressByPage(chainName chains.Chain, tokenAddress string, queryParamOpts ...GetTokenHoldersV2ForTokenAddressQueryParamOpts) (*utils.Response[TokenHoldersResponse], error) + + // Commonly used to fetch the historical native, fungible (ERC20), and non-fungible (ERC721 & ERC1155) tokens held by an address at a given block height or date. Response includes daily prices and other metadata. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // walletAddress: The requested address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetHistoricalTokenBalancesForWalletAddress(chainName chains.Chain, walletAddress string, queryParamOpts ...GetHistoricalTokenBalancesForWalletAddressQueryParamOpts) (*utils.Response[HistoricalBalancesResponse], error) + + // Commonly used to get the native token balance for an address. This endpoint is required because native tokens are usually not ERC20 tokens and sometimes you want something lightweight. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // walletAddress: The requested address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetNativeTokenBalance(chainName chains.Chain, walletAddress string, queryParamOpts ...GetNativeTokenBalanceQueryParamOpts) (*utils.Response[TokenBalanceNativeResponse], error) +} + +type balanceServiceImpl struct { + APIKey string + Debug bool + ThreadCount int + IskeyValid bool +} + +func (s *balanceServiceImpl) GetTokenBalancesForWalletAddress(chainName chains.Chain, walletAddress string, queryParamOpts ...GetTokenBalancesForWalletAddressQueryParamOpts) (*utils.Response[BalancesResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/address/%s/balances_v2/", chainName, walletAddress) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[BalancesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.QuoteCurrency != nil { + params.Add("quote-currency", fmt.Sprintf("%v", *opts.QuoteCurrency)) + } + + if opts.Nft != nil { + params.Add("nft", fmt.Sprintf("%v", *opts.Nft)) + } + + if opts.NoNftFetch != nil { + params.Add("no-nft-fetch", fmt.Sprintf("%v", *opts.NoNftFetch)) + } + + if opts.NoSpam != nil { + params.Add("no-spam", fmt.Sprintf("%v", *opts.NoSpam)) + } + + if opts.NoNftAssetMetadata != nil { + params.Add("no-nft-asset-metadata", fmt.Sprintf("%v", *opts.NoNftAssetMetadata)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[BalancesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[BalancesResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[BalancesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[BalancesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[BalancesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[BalancesResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[BalancesResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *balanceServiceImpl) GetHistoricalPortfolioForWalletAddress(chainName chains.Chain, walletAddress string, queryParamOpts ...GetHistoricalPortfolioForWalletAddressQueryParamOpts) (*utils.Response[PortfolioResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/address/%s/portfolio_v2/", chainName, walletAddress) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[PortfolioResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.QuoteCurrency != nil { + params.Add("quote-currency", fmt.Sprintf("%v", *opts.QuoteCurrency)) + } + + if opts.Days != nil { + params.Add("days", fmt.Sprintf("%v", *opts.Days)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[PortfolioResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[PortfolioResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[PortfolioResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[PortfolioResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[PortfolioResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[PortfolioResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[PortfolioResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *balanceServiceImpl) GetErc20TransfersForWalletAddress(chainName chains.Chain, walletAddress string, queryParamOpts ...GetErc20TransfersForWalletAddressQueryParamOpts) <-chan BlockTransactionWithContractTransfersResult { + blockTransactionWithContractTransfersChannel := make(chan BlockTransactionWithContractTransfersResult) + + go func() { + defer close(blockTransactionWithContractTransfersChannel) + + hasNext := true + + if !s.IskeyValid { + blockTransactionWithContractTransfersChannel <- BlockTransactionWithContractTransfersResult{Err: fmt.Errorf(`An error occurred 401: ` + utils.InvalidAPIKeyMessage)} + return + } + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/address/%s/transfers_v2/", chainName, walletAddress) + + // Parse the formatted URL + parsedURL, err := url.Parse(apiURL) + if err != nil { + blockTransactionWithContractTransfersChannel <- BlockTransactionWithContractTransfersResult{Err: err} + return + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.QuoteCurrency != nil { + params.Add("quote-currency", fmt.Sprintf("%v", *opts.QuoteCurrency)) + } + + if opts.ContractAddress != nil { + params.Add("contract-address", fmt.Sprintf("%v", *opts.ContractAddress)) + } + + if opts.StartingBlock != nil { + params.Add("starting-block", fmt.Sprintf("%v", *opts.StartingBlock)) + } + + if opts.EndingBlock != nil { + params.Add("ending-block", fmt.Sprintf("%v", *opts.EndingBlock)) + } + + if opts.PageSize != nil { + params.Add("page-size", fmt.Sprintf("%v", *opts.PageSize)) + } + + if opts.PageNumber != nil { + params.Add("page-number", fmt.Sprintf("%v", *opts.PageNumber)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + page := 0 + + if !params.Has("page-number") { + page = 0 + } else { + page, err = strconv.Atoi(params.Get("page-number")) + if err != nil { + blockTransactionWithContractTransfersChannel <- BlockTransactionWithContractTransfersResult{Err: err} + return + } + } + + var data utils.Response[Erc20TransfersResponse] + for hasNext { + + res, err := utils.PaginateEndpoint(apiURL, s.APIKey, params, page, s.Debug, s.ThreadCount) + if err != nil { + blockTransactionWithContractTransfersChannel <- BlockTransactionWithContractTransfersResult{Err: err} + hasNext = false + return + } + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + blockTransactionWithContractTransfersChannel <- BlockTransactionWithContractTransfersResult{Err: err} + res.Body.Close() + hasNext = false + return + } + res.Body.Close() // Ensure the body is closed after processing it + + if data.Error { + var errorMessage string + if data.ErrorMessage != nil { + errorMessage = *data.ErrorMessage + } else { + errorMessage = "default error message" // Provide a default or handle differently + } + blockTransactionWithContractTransfersChannel <- BlockTransactionWithContractTransfersResult{Err: errors.New("An error occurred " + strconv.Itoa(*data.ErrorCode) + ": " + errorMessage)} + return + } + + for _, item := range data.Data.Items { + blockTransactionWithContractTransfersChannel <- BlockTransactionWithContractTransfersResult{BlockTransactionWithContractTransfers: item, Err: err} + } + + hasNext = *data.Data.Pagination.HasMore + page++ + } + + }() + return blockTransactionWithContractTransfersChannel +} + +func (s *balanceServiceImpl) GetErc20TransfersForWalletAddressByPage(chainName chains.Chain, walletAddress string, queryParamOpts ...GetErc20TransfersForWalletAddressQueryParamOpts) (*utils.Response[Erc20TransfersResponse], error) { + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/address/%s/transfers_v2/", chainName, walletAddress) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[Erc20TransfersResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.QuoteCurrency != nil { + params.Add("quote-currency", fmt.Sprintf("%v", *opts.QuoteCurrency)) + } + + if opts.ContractAddress != nil { + params.Add("contract-address", fmt.Sprintf("%v", *opts.ContractAddress)) + } + + if opts.StartingBlock != nil { + params.Add("starting-block", fmt.Sprintf("%v", *opts.StartingBlock)) + } + + if opts.EndingBlock != nil { + params.Add("ending-block", fmt.Sprintf("%v", *opts.EndingBlock)) + } + + if opts.PageSize != nil { + params.Add("page-size", fmt.Sprintf("%v", *opts.PageSize)) + } + + if opts.PageNumber != nil { + params.Add("page-number", fmt.Sprintf("%v", *opts.PageNumber)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[Erc20TransfersResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[Erc20TransfersResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[Erc20TransfersResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[Erc20TransfersResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[Erc20TransfersResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[Erc20TransfersResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[Erc20TransfersResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *balanceServiceImpl) GetTokenHoldersV2ForTokenAddress(chainName chains.Chain, tokenAddress string, queryParamOpts ...GetTokenHoldersV2ForTokenAddressQueryParamOpts) <-chan TokenHolderResult { + tokenHolderChannel := make(chan TokenHolderResult) + + go func() { + defer close(tokenHolderChannel) + + hasNext := true + + if !s.IskeyValid { + tokenHolderChannel <- TokenHolderResult{Err: fmt.Errorf(`An error occurred 401: ` + utils.InvalidAPIKeyMessage)} + return + } + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/tokens/%s/token_holders_v2/", chainName, tokenAddress) + + // Parse the formatted URL + parsedURL, err := url.Parse(apiURL) + if err != nil { + tokenHolderChannel <- TokenHolderResult{Err: err} + return + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.BlockHeight != nil { + params.Add("block-height", fmt.Sprintf("%v", *opts.BlockHeight)) + } + + if opts.Date != nil { + params.Add("date", fmt.Sprintf("%v", *opts.Date)) + } + + if opts.PageSize != nil { + params.Add("page-size", fmt.Sprintf("%v", *opts.PageSize)) + } + + if opts.PageNumber != nil { + params.Add("page-number", fmt.Sprintf("%v", *opts.PageNumber)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + page := 0 + + if !params.Has("page-number") { + page = 0 + } else { + page, err = strconv.Atoi(params.Get("page-number")) + if err != nil { + tokenHolderChannel <- TokenHolderResult{Err: err} + return + } + } + + var data utils.Response[TokenHoldersResponse] + for hasNext { + + res, err := utils.PaginateEndpoint(apiURL, s.APIKey, params, page, s.Debug, s.ThreadCount) + if err != nil { + tokenHolderChannel <- TokenHolderResult{Err: err} + hasNext = false + return + } + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + tokenHolderChannel <- TokenHolderResult{Err: err} + res.Body.Close() + hasNext = false + return + } + res.Body.Close() // Ensure the body is closed after processing it + + if data.Error { + var errorMessage string + if data.ErrorMessage != nil { + errorMessage = *data.ErrorMessage + } else { + errorMessage = "default error message" // Provide a default or handle differently + } + tokenHolderChannel <- TokenHolderResult{Err: errors.New("An error occurred " + strconv.Itoa(*data.ErrorCode) + ": " + errorMessage)} + return + } + + for _, item := range data.Data.Items { + tokenHolderChannel <- TokenHolderResult{TokenHolder: item, Err: err} + } + + hasNext = *data.Data.Pagination.HasMore + page++ + } + + }() + return tokenHolderChannel +} + +func (s *balanceServiceImpl) GetTokenHoldersV2ForTokenAddressByPage(chainName chains.Chain, tokenAddress string, queryParamOpts ...GetTokenHoldersV2ForTokenAddressQueryParamOpts) (*utils.Response[TokenHoldersResponse], error) { + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/tokens/%s/token_holders_v2/", chainName, tokenAddress) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[TokenHoldersResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.BlockHeight != nil { + params.Add("block-height", fmt.Sprintf("%v", *opts.BlockHeight)) + } + + if opts.Date != nil { + params.Add("date", fmt.Sprintf("%v", *opts.Date)) + } + + if opts.PageSize != nil { + params.Add("page-size", fmt.Sprintf("%v", *opts.PageSize)) + } + + if opts.PageNumber != nil { + params.Add("page-number", fmt.Sprintf("%v", *opts.PageNumber)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[TokenHoldersResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[TokenHoldersResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[TokenHoldersResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TokenHoldersResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TokenHoldersResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[TokenHoldersResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[TokenHoldersResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *balanceServiceImpl) GetHistoricalTokenBalancesForWalletAddress(chainName chains.Chain, walletAddress string, queryParamOpts ...GetHistoricalTokenBalancesForWalletAddressQueryParamOpts) (*utils.Response[HistoricalBalancesResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/address/%s/historical_balances/", chainName, walletAddress) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[HistoricalBalancesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.QuoteCurrency != nil { + params.Add("quote-currency", fmt.Sprintf("%v", *opts.QuoteCurrency)) + } + + if opts.Nft != nil { + params.Add("nft", fmt.Sprintf("%v", *opts.Nft)) + } + + if opts.NoNftFetch != nil { + params.Add("no-nft-fetch", fmt.Sprintf("%v", *opts.NoNftFetch)) + } + + if opts.NoSpam != nil { + params.Add("no-spam", fmt.Sprintf("%v", *opts.NoSpam)) + } + + if opts.NoNftAssetMetadata != nil { + params.Add("no-nft-asset-metadata", fmt.Sprintf("%v", *opts.NoNftAssetMetadata)) + } + + if opts.BlockHeight != nil { + params.Add("block-height", fmt.Sprintf("%v", *opts.BlockHeight)) + } + + if opts.Date != nil { + params.Add("date", fmt.Sprintf("%v", *opts.Date)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[HistoricalBalancesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[HistoricalBalancesResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[HistoricalBalancesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[HistoricalBalancesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[HistoricalBalancesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[HistoricalBalancesResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[HistoricalBalancesResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *balanceServiceImpl) GetNativeTokenBalance(chainName chains.Chain, walletAddress string, queryParamOpts ...GetNativeTokenBalanceQueryParamOpts) (*utils.Response[TokenBalanceNativeResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/address/%s/balances_native/", chainName, walletAddress) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[TokenBalanceNativeResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.QuoteCurrency != nil { + params.Add("quote-currency", fmt.Sprintf("%v", *opts.QuoteCurrency)) + } + + if opts.BlockHeight != nil { + params.Add("block-height", fmt.Sprintf("%v", *opts.BlockHeight)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[TokenBalanceNativeResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[TokenBalanceNativeResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[TokenBalanceNativeResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TokenBalanceNativeResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TokenBalanceNativeResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[TokenBalanceNativeResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[TokenBalanceNativeResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} diff --git a/services/base_service.go b/services/base_service.go new file mode 100644 index 0000000..2133e6f --- /dev/null +++ b/services/base_service.go @@ -0,0 +1,1673 @@ +package services + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/covalenthq/covalent-api-sdk-go/chains" + "github.com/covalenthq/covalent-api-sdk-go/genericmodels" + "github.com/covalenthq/covalent-api-sdk-go/quotes" + "github.com/covalenthq/covalent-api-sdk-go/utils" +) + +type BlockResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []Block `json:"items"` +} +type Block struct { + // The hash of the block. + BlockHash *string `json:"block_hash,omitempty"` + // The block signed timestamp in UTC. + SignedAt *time.Time `json:"signed_at,omitempty"` + // The block height. + Height *int `json:"height,omitempty"` + // The parent block hash. + BlockParentHash *string `json:"block_parent_hash,omitempty"` + // Extra data written to the block. + ExtraData *string `json:"extra_data,omitempty"` + // The address of the miner. + MinerAddress *string `json:"miner_address,omitempty"` + // The associated mining cost. + MiningCost *int64 `json:"mining_cost,omitempty"` + // The associated gas used. + GasUsed *int64 `json:"gas_used,omitempty"` + // The associated gas limit. + GasLimit *int64 `json:"gas_limit,omitempty"` + // The link to the related tx by block endpoint. + TransactionsLink *string `json:"transactions_link,omitempty"` +} +type ResolvedAddress struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []ResolvedAddressItem `json:"items"` +} +type ResolvedAddressItem struct { + // The requested address. + Address *string `json:"address,omitempty"` + Name *string `json:"name,omitempty"` +} +type BlockHeightsResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []BlockHeights `json:"items"` + // Pagination metadata. + Pagination genericmodels.Pagination `json:"pagination"` +} +type BlockHeights struct { + // The block signed timestamp in UTC. + SignedAt *time.Time `json:"signed_at,omitempty"` + // The block height. + Height *int `json:"height,omitempty"` +} +type GetLogsResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []GetLogsEvent `json:"items"` +} +type GetLogsEvent struct { + // The block signed timestamp in UTC. + BlockSignedAt *time.Time `json:"block_signed_at,omitempty"` + // The height of the block. + BlockHeight *int64 `json:"block_height,omitempty"` + // The hash of the block. + BlockHash *string `json:"block_hash,omitempty"` + // The offset is the position of the tx in the block. + TxOffset *int `json:"tx_offset,omitempty"` + // The offset is the position of the log entry within an event log. + LogOffset *int `json:"log_offset,omitempty"` + // The requested transaction hash. + TxHash *string `json:"tx_hash,omitempty"` + // The log topics in raw data. + RawLogTopics *[]string `json:"raw_log_topics,omitempty"` + // Use contract decimals to format the token balance for display purposes - divide the balance by `10^{contract_decimals}`. + SenderContractDecimals *int `json:"sender_contract_decimals,omitempty"` + // The name of the sender. + SenderName *string `json:"sender_name,omitempty"` + // The ticker symbol for the sender. This field is set by a developer and non-unique across a network. + SenderContractTickerSymbol *string `json:"sender_contract_ticker_symbol,omitempty"` + // The address of the sender. + SenderAddress *string `json:"sender_address,omitempty"` + // The label of the sender address. + SenderAddressLabel *string `json:"sender_address_label,omitempty"` + // The contract logo URL. + SenderLogoUrl *string `json:"sender_logo_url,omitempty"` + // The address of the deployed UniswapV2 like factory contract for this DEX. + SenderFactoryAddress *string `json:"sender_factory_address,omitempty"` + // The log events in raw. + RawLogData *string `json:"raw_log_data,omitempty"` + // The decoded item. + Decoded *genericmodels.DecodedItem `json:"decoded,omitempty"` +} +type LogEventsByAddressResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []genericmodels.LogEvent `json:"items"` + // Pagination metadata. + Pagination genericmodels.Pagination `json:"pagination"` +} +type LogEventsByTopicHashResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []genericmodels.LogEvent `json:"items"` + // Pagination metadata. + Pagination genericmodels.Pagination `json:"pagination"` +} +type AllChainsResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // List of response items. + Items []ChainItem `json:"items"` +} +type ChainItem struct { + // The chain name eg: `eth-mainnet`. + Name *string `json:"name,omitempty"` + // The requested chain ID eg: `1`. + ChainId *string `json:"chain_id,omitempty"` + // True if the chain is a testnet. + IsTestnet *bool `json:"is_testnet,omitempty"` + // Schema name to use for direct SQL. + DbSchemaName *string `json:"db_schema_name,omitempty"` + // The chains label eg: `Ethereum Mainnet`. + Label *string `json:"label,omitempty"` + // The category label eg: `Ethereum`. + CategoryLabel *string `json:"category_label,omitempty"` + // A svg logo url for the chain. + LogoUrl *string `json:"logo_url,omitempty"` + // A black png logo url for the chain. + BlackLogoUrl *string `json:"black_logo_url,omitempty"` + // A white png logo url for the chain. + WhiteLogoUrl *string `json:"white_logo_url,omitempty"` + // The color theme for the chain. + ColorTheme *ColorTheme `json:"color_theme,omitempty"` + // True if the chain is an AppChain. + IsAppchain *bool `json:"is_appchain,omitempty"` + // The ChainItem the appchain is a part of. + AppchainOf *ChainItem `json:"appchain_of,omitempty"` +} +type ColorTheme struct { + // The red color code. + Red *int `json:"red,omitempty"` + // The green color code. + Green *int `json:"green,omitempty"` + // The blue color code. + Blue *int `json:"blue,omitempty"` + // The alpha color code. + Alpha *int `json:"alpha,omitempty"` + // The hexadecimal color code. + Hex *string `json:"hex,omitempty"` + // The color represented in css rgb() functional notation. + CssRgb *string `json:"css_rgb,omitempty"` +} +type AllChainsStatusResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // List of response items. + Items []ChainStatusItem `json:"items"` +} +type ChainStatusItem struct { + // The chain name eg: `eth-mainnet`. + Name *string `json:"name,omitempty"` + // The requested chain ID eg: `1`. + ChainId *string `json:"chain_id,omitempty"` + // True if the chain is a testnet. + IsTestnet *bool `json:"is_testnet,omitempty"` + // A svg logo url for the chain. + LogoUrl *string `json:"logo_url,omitempty"` + // A black png logo url for the chain. + BlackLogoUrl *string `json:"black_logo_url,omitempty"` + // A white png logo url for the chain. + WhiteLogoUrl *string `json:"white_logo_url,omitempty"` + // True if the chain is an AppChain. + IsAppchain *bool `json:"is_appchain,omitempty"` + // The height of the lastest block available. + SyncedBlockHeight *int `json:"synced_block_height,omitempty"` + // The signed timestamp of lastest block available. + SyncedBlockedSignedAt *time.Time `json:"synced_blocked_signed_at,omitempty"` + // True if the chain has data and ready for querying. + HasData *bool `json:"has_data,omitempty"` +} +type ChainActivityResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested address. + Address string `json:"address"` + // List of response items. + Items []ChainActivityEvent `json:"items"` +} +type ChainActivityEvent struct { + ChainItem + // The timestamp when the address was last seen on the chain. + LastSeenAt *time.Time `json:"last_seen_at,omitempty"` +} +type GasPricesResponse struct { + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // The requested quote currency eg: `USD`. + QuoteCurrency string `json:"quote_currency"` + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested event type. + EventType string `json:"event_type"` + // The exchange rate for the requested quote currency. + GasQuoteRate float64 `json:"gas_quote_rate"` + // The lowest gas fee for the latest block height. + BaseFee utils.BigInt `json:"base_fee"` + // List of response items. + Items []PriceItem `json:"items"` +} +type PriceItem struct { + // The average gas price, in WEI, for the time interval. + GasPrice *string `json:"gas_price,omitempty"` + // The average gas spent for the time interval. + GasSpent *string `json:"gas_spent,omitempty"` + // The average gas spent in `quote-currency` denomination for the time interval. + GasQuote *float64 `json:"gas_quote,omitempty"` + // Other fees, when applicable. For example: OP chain L1 fees. + OtherFees *OtherFees `json:"other_fees,omitempty"` + // The sum of the L1 and L2 gas spent, in quote-currency, for the specified time interval. + TotalGasQuote *float64 `json:"total_gas_quote,omitempty"` + // A prettier version of the total average gas spent, in quote-currency, for the specified time interval, for rendering purposes. + PrettyTotalGasQuote *string `json:"pretty_total_gas_quote,omitempty"` + // The specified time interval. + Interval *string `json:"interval,omitempty"` +} +type OtherFees struct { + // The calculated L1 gas spent, when applicable, in quote-currency, for the specified time interval. + L1GasQuote *float64 `json:"l1_gas_quote,omitempty"` +} +type BlockHeightsResult struct { + BlockHeights BlockHeights + Err error +} + +type LogEventResult struct { + LogEvent genericmodels.LogEvent + Err error +} + +type GetBlockHeightsQueryParamOpts struct { + // Number of items per page. Omitting this parameter defaults to 100. + PageSize *int `json:"pageSize,omitempty"` + // 0-indexed page number to begin pagination. + PageNumber *int `json:"pageNumber,omitempty"` +} +type GetLogsQueryParamOpts struct { + // The first block to retrieve log events with. Accepts decimals, hexadecimals, or the strings `earliest` and `latest`. + StartingBlock *int `json:"startingBlock,omitempty"` + // The last block to retrieve log events with. Accepts decimals, hexadecimals, or the strings `earliest` and `latest`. + EndingBlock *string `json:"endingBlock,omitempty"` + // The address of the log events sender contract. + Address *string `json:"address,omitempty"` + // The topic hash(es) to retrieve logs with. + Topics *string `json:"topics,omitempty"` + // The block hash to retrieve logs for. + BlockHash *string `json:"blockHash,omitempty"` + // Omit decoded log events. + SkipDecode *bool `json:"skipDecode,omitempty"` +} +type GetLogEventsByAddressQueryParamOpts struct { + // The first block to retrieve log events with. Accepts decimals, hexadecimals, or the strings `earliest` and `latest`. + StartingBlock *int `json:"startingBlock,omitempty"` + // The last block to retrieve log events with. Accepts decimals, hexadecimals, or the strings `earliest` and `latest`. + EndingBlock *string `json:"endingBlock,omitempty"` + // Number of items per page. Omitting this parameter defaults to 100. + PageSize *int `json:"pageSize,omitempty"` + // 0-indexed page number to begin pagination. + PageNumber *int `json:"pageNumber,omitempty"` +} +type GetLogEventsByTopicHashQueryParamOpts struct { + // The first block to retrieve log events with. Accepts decimals, hexadecimals, or the strings `earliest` and `latest`. + StartingBlock *int `json:"startingBlock,omitempty"` + // The last block to retrieve log events with. Accepts decimals, hexadecimals, or the strings `earliest` and `latest`. + EndingBlock *string `json:"endingBlock,omitempty"` + // Additional topic hash(es) to filter on - padded & unpadded address fields are supported. Separate multiple topics with a comma. + SecondaryTopics *string `json:"secondaryTopics,omitempty"` + // Number of items per page. Omitting this parameter defaults to 100. + PageSize *int `json:"pageSize,omitempty"` + // 0-indexed page number to begin pagination. + PageNumber *int `json:"pageNumber,omitempty"` +} +type GetAddressActivityQueryParamOpts struct { + // Set to true to include testnets with activity in the response. By default, it's set to `false` and only returns mainnet activity. + Testnets *bool `json:"testnets,omitempty"` +} +type GetGasPricesQueryParamOpts struct { + // The currency to convert. Supports `USD`, `CAD`, `EUR`, `SGD`, `INR`, `JPY`, `VND`, `CNY`, `KRW`, `RUB`, `TRY`, `NGN`, `ARS`, `AUD`, `CHF`, and `GBP`. + QuoteCurrency *quotes.Quote `json:"quoteCurrency,omitempty"` +} + +func NewBaseServiceImpl(apiKey string, debug bool, threadCount int, isValidKey bool) BaseService { + + return &baseServiceImpl{APIKey: apiKey, Debug: debug, ThreadCount: threadCount, IskeyValid: isValidKey} +} + +type BaseService interface { + + // Commonly used to fetch and render a single block for a block explorer. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // blockHeight: The block height or `latest` for the latest block available.. Type: string + GetBlock(chainName chains.Chain, blockHeight string) (*utils.Response[BlockResponse], error) + + // Commonly used to resolve ENS, RNS and Unstoppable Domains addresses. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // walletAddress: The requested address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetResolvedAddress(chainName chains.Chain, walletAddress string) (*utils.Response[ResolvedAddress], error) + + // Commonly used to get all the block heights within a particular date range. Useful for rendering a display where you sort blocks by day. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // startDate: The start date in YYYY-MM-DD format.. Type: string + // endDate: The end date in YYYY-MM-DD format.. Type: string + GetBlockHeights(chainName chains.Chain, startDate string, endDate string, queryParamOpts ...GetBlockHeightsQueryParamOpts) <-chan BlockHeightsResult + + // Commonly used to get all the block heights within a particular date range. Useful for rendering a display where you sort blocks by day. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // startDate: The start date in YYYY-MM-DD format.. Type: string + // endDate: The end date in YYYY-MM-DD format.. Type: string + GetBlockHeightsByPage(chainName chains.Chain, startDate string, endDate string, queryParamOpts ...GetBlockHeightsQueryParamOpts) (*utils.Response[BlockHeightsResponse], error) + + // Commonly used to get all the event logs of the latest block, or for a range of blocks. Includes sender contract metadata as well as decoded logs. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + GetLogs(chainName chains.Chain, queryParamOpts ...GetLogsQueryParamOpts) (*utils.Response[GetLogsResponse], error) + + // Commonly used to get all the event logs emitted from a particular contract address. Useful for building dashboards that examine on-chain interactions. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // contractAddress: The requested contract address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetLogEventsByAddress(chainName chains.Chain, contractAddress string, queryParamOpts ...GetLogEventsByAddressQueryParamOpts) <-chan LogEventResult + + // Commonly used to get all the event logs emitted from a particular contract address. Useful for building dashboards that examine on-chain interactions. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // contractAddress: The requested contract address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetLogEventsByAddressByPage(chainName chains.Chain, contractAddress string, queryParamOpts ...GetLogEventsByAddressQueryParamOpts) (*utils.Response[LogEventsByAddressResponse], error) + + // Commonly used to get all event logs of the same topic hash across all contracts within a particular chain. Useful for cross-sectional analysis of event logs that are emitted on-chain. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // topicHash: The endpoint will return event logs that contain this topic hash.. Type: string + GetLogEventsByTopicHash(chainName chains.Chain, topicHash string, queryParamOpts ...GetLogEventsByTopicHashQueryParamOpts) <-chan LogEventResult + + // Commonly used to get all event logs of the same topic hash across all contracts within a particular chain. Useful for cross-sectional analysis of event logs that are emitted on-chain. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // topicHash: The endpoint will return event logs that contain this topic hash.. Type: string + GetLogEventsByTopicHashByPage(chainName chains.Chain, topicHash string, queryParamOpts ...GetLogEventsByTopicHashQueryParamOpts) (*utils.Response[LogEventsByTopicHashResponse], error) + + // Commonly used to build internal dashboards for all supported chains on Covalent. + // Parameters: + + GetAllChains() (*utils.Response[AllChainsResponse], error) + + // Commonly used to build internal status dashboards of all supported chains. + // Parameters: + + GetAllChainStatus() (*utils.Response[AllChainsStatusResponse], error) + + // Commonly used to locate chains which an address is active on with a single API call. + // Parameters: + // walletAddress: The requested wallet address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetAddressActivity(walletAddress string, queryParamOpts ...GetAddressActivityQueryParamOpts) (*utils.Response[ChainActivityResponse], error) + + // Get real-time gas estimates for different transaction speeds on a specific network, enabling users to optimize transaction costs and confirmation times. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // eventType: The desired event type to retrieve gas prices for. Supports `erc20` transfer events, `uniswapv3` swap events and `nativetokens` transfers.. Type: string + GetGasPrices(chainName chains.Chain, eventType string, queryParamOpts ...GetGasPricesQueryParamOpts) (*utils.Response[GasPricesResponse], error) +} + +type baseServiceImpl struct { + APIKey string + Debug bool + ThreadCount int + IskeyValid bool +} + +func (s *baseServiceImpl) GetBlock(chainName chains.Chain, blockHeight string) (*utils.Response[BlockResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/block_v2/%s/", chainName, blockHeight) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[BlockResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[BlockResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[BlockResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[BlockResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[BlockResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[BlockResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[BlockResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[BlockResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *baseServiceImpl) GetResolvedAddress(chainName chains.Chain, walletAddress string) (*utils.Response[ResolvedAddress], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/address/%s/resolve_address/", chainName, walletAddress) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[ResolvedAddress]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[ResolvedAddress]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[ResolvedAddress] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[ResolvedAddress]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[ResolvedAddress]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[ResolvedAddress]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[ResolvedAddress]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[ResolvedAddress]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *baseServiceImpl) GetBlockHeights(chainName chains.Chain, startDate string, endDate string, queryParamOpts ...GetBlockHeightsQueryParamOpts) <-chan BlockHeightsResult { + blockHeightsChannel := make(chan BlockHeightsResult) + + go func() { + defer close(blockHeightsChannel) + + hasNext := true + + if !s.IskeyValid { + blockHeightsChannel <- BlockHeightsResult{Err: fmt.Errorf(`An error occurred 401: ` + utils.InvalidAPIKeyMessage)} + return + } + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/block_v2/%s/%s/", chainName, startDate, endDate) + + // Parse the formatted URL + parsedURL, err := url.Parse(apiURL) + if err != nil { + blockHeightsChannel <- BlockHeightsResult{Err: err} + return + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.PageSize != nil { + params.Add("page-size", fmt.Sprintf("%v", *opts.PageSize)) + } + + if opts.PageNumber != nil { + params.Add("page-number", fmt.Sprintf("%v", *opts.PageNumber)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + page := 0 + + if !params.Has("page-number") { + page = 0 + } else { + page, err = strconv.Atoi(params.Get("page-number")) + if err != nil { + blockHeightsChannel <- BlockHeightsResult{Err: err} + return + } + } + + var data utils.Response[BlockHeightsResponse] + for hasNext { + + res, err := utils.PaginateEndpoint(apiURL, s.APIKey, params, page, s.Debug, s.ThreadCount) + if err != nil { + blockHeightsChannel <- BlockHeightsResult{Err: err} + hasNext = false + return + } + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + blockHeightsChannel <- BlockHeightsResult{Err: err} + res.Body.Close() + hasNext = false + return + } + res.Body.Close() // Ensure the body is closed after processing it + + if data.Error { + var errorMessage string + if data.ErrorMessage != nil { + errorMessage = *data.ErrorMessage + } else { + errorMessage = "default error message" // Provide a default or handle differently + } + blockHeightsChannel <- BlockHeightsResult{Err: errors.New("An error occurred " + strconv.Itoa(*data.ErrorCode) + ": " + errorMessage)} + return + } + + for _, item := range data.Data.Items { + blockHeightsChannel <- BlockHeightsResult{BlockHeights: item, Err: err} + } + + hasNext = *data.Data.Pagination.HasMore + page++ + } + + }() + return blockHeightsChannel +} + +func (s *baseServiceImpl) GetBlockHeightsByPage(chainName chains.Chain, startDate string, endDate string, queryParamOpts ...GetBlockHeightsQueryParamOpts) (*utils.Response[BlockHeightsResponse], error) { + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/block_v2/%s/%s/", chainName, startDate, endDate) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[BlockHeightsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.PageSize != nil { + params.Add("page-size", fmt.Sprintf("%v", *opts.PageSize)) + } + + if opts.PageNumber != nil { + params.Add("page-number", fmt.Sprintf("%v", *opts.PageNumber)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[BlockHeightsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[BlockHeightsResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[BlockHeightsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[BlockHeightsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[BlockHeightsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[BlockHeightsResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[BlockHeightsResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *baseServiceImpl) GetLogs(chainName chains.Chain, queryParamOpts ...GetLogsQueryParamOpts) (*utils.Response[GetLogsResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/events/", chainName) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[GetLogsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.StartingBlock != nil { + params.Add("starting-block", fmt.Sprintf("%v", *opts.StartingBlock)) + } + + if opts.EndingBlock != nil { + params.Add("ending-block", fmt.Sprintf("%v", *opts.EndingBlock)) + } + + if opts.Address != nil { + params.Add("address", fmt.Sprintf("%v", *opts.Address)) + } + + if opts.Topics != nil { + params.Add("topics", fmt.Sprintf("%v", *opts.Topics)) + } + + if opts.BlockHash != nil { + params.Add("block-hash", fmt.Sprintf("%v", *opts.BlockHash)) + } + + if opts.SkipDecode != nil { + params.Add("skip-decode", fmt.Sprintf("%v", *opts.SkipDecode)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[GetLogsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[GetLogsResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[GetLogsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[GetLogsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[GetLogsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[GetLogsResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[GetLogsResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *baseServiceImpl) GetLogEventsByAddress(chainName chains.Chain, contractAddress string, queryParamOpts ...GetLogEventsByAddressQueryParamOpts) <-chan LogEventResult { + logEventChannel := make(chan LogEventResult) + + go func() { + defer close(logEventChannel) + + hasNext := true + + if !s.IskeyValid { + logEventChannel <- LogEventResult{Err: fmt.Errorf(`An error occurred 401: ` + utils.InvalidAPIKeyMessage)} + return + } + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/events/address/%s/", chainName, contractAddress) + + // Parse the formatted URL + parsedURL, err := url.Parse(apiURL) + if err != nil { + logEventChannel <- LogEventResult{Err: err} + return + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.StartingBlock != nil { + params.Add("starting-block", fmt.Sprintf("%v", *opts.StartingBlock)) + } + + if opts.EndingBlock != nil { + params.Add("ending-block", fmt.Sprintf("%v", *opts.EndingBlock)) + } + + if opts.PageSize != nil { + params.Add("page-size", fmt.Sprintf("%v", *opts.PageSize)) + } + + if opts.PageNumber != nil { + params.Add("page-number", fmt.Sprintf("%v", *opts.PageNumber)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + page := 0 + + if !params.Has("page-number") { + page = 0 + } else { + page, err = strconv.Atoi(params.Get("page-number")) + if err != nil { + logEventChannel <- LogEventResult{Err: err} + return + } + } + + var data utils.Response[LogEventsByAddressResponse] + for hasNext { + + res, err := utils.PaginateEndpoint(apiURL, s.APIKey, params, page, s.Debug, s.ThreadCount) + if err != nil { + logEventChannel <- LogEventResult{Err: err} + hasNext = false + return + } + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + logEventChannel <- LogEventResult{Err: err} + res.Body.Close() + hasNext = false + return + } + res.Body.Close() // Ensure the body is closed after processing it + + if data.Error { + var errorMessage string + if data.ErrorMessage != nil { + errorMessage = *data.ErrorMessage + } else { + errorMessage = "default error message" // Provide a default or handle differently + } + logEventChannel <- LogEventResult{Err: errors.New("An error occurred " + strconv.Itoa(*data.ErrorCode) + ": " + errorMessage)} + return + } + + for _, item := range data.Data.Items { + logEventChannel <- LogEventResult{LogEvent: item, Err: err} + } + + hasNext = *data.Data.Pagination.HasMore + page++ + } + + }() + return logEventChannel +} + +func (s *baseServiceImpl) GetLogEventsByAddressByPage(chainName chains.Chain, contractAddress string, queryParamOpts ...GetLogEventsByAddressQueryParamOpts) (*utils.Response[LogEventsByAddressResponse], error) { + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/events/address/%s/", chainName, contractAddress) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[LogEventsByAddressResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.StartingBlock != nil { + params.Add("starting-block", fmt.Sprintf("%v", *opts.StartingBlock)) + } + + if opts.EndingBlock != nil { + params.Add("ending-block", fmt.Sprintf("%v", *opts.EndingBlock)) + } + + if opts.PageSize != nil { + params.Add("page-size", fmt.Sprintf("%v", *opts.PageSize)) + } + + if opts.PageNumber != nil { + params.Add("page-number", fmt.Sprintf("%v", *opts.PageNumber)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[LogEventsByAddressResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[LogEventsByAddressResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[LogEventsByAddressResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[LogEventsByAddressResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[LogEventsByAddressResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[LogEventsByAddressResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[LogEventsByAddressResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *baseServiceImpl) GetLogEventsByTopicHash(chainName chains.Chain, topicHash string, queryParamOpts ...GetLogEventsByTopicHashQueryParamOpts) <-chan LogEventResult { + logEventChannel := make(chan LogEventResult) + + go func() { + defer close(logEventChannel) + + hasNext := true + + if !s.IskeyValid { + logEventChannel <- LogEventResult{Err: fmt.Errorf(`An error occurred 401: ` + utils.InvalidAPIKeyMessage)} + return + } + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/events/topics/%s/", chainName, topicHash) + + // Parse the formatted URL + parsedURL, err := url.Parse(apiURL) + if err != nil { + logEventChannel <- LogEventResult{Err: err} + return + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.StartingBlock != nil { + params.Add("starting-block", fmt.Sprintf("%v", *opts.StartingBlock)) + } + + if opts.EndingBlock != nil { + params.Add("ending-block", fmt.Sprintf("%v", *opts.EndingBlock)) + } + + if opts.SecondaryTopics != nil { + params.Add("secondary-topics", fmt.Sprintf("%v", *opts.SecondaryTopics)) + } + + if opts.PageSize != nil { + params.Add("page-size", fmt.Sprintf("%v", *opts.PageSize)) + } + + if opts.PageNumber != nil { + params.Add("page-number", fmt.Sprintf("%v", *opts.PageNumber)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + page := 0 + + if !params.Has("page-number") { + page = 0 + } else { + page, err = strconv.Atoi(params.Get("page-number")) + if err != nil { + logEventChannel <- LogEventResult{Err: err} + return + } + } + + var data utils.Response[LogEventsByTopicHashResponse] + for hasNext { + + res, err := utils.PaginateEndpoint(apiURL, s.APIKey, params, page, s.Debug, s.ThreadCount) + if err != nil { + logEventChannel <- LogEventResult{Err: err} + hasNext = false + return + } + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + logEventChannel <- LogEventResult{Err: err} + res.Body.Close() + hasNext = false + return + } + res.Body.Close() // Ensure the body is closed after processing it + + if data.Error { + var errorMessage string + if data.ErrorMessage != nil { + errorMessage = *data.ErrorMessage + } else { + errorMessage = "default error message" // Provide a default or handle differently + } + logEventChannel <- LogEventResult{Err: errors.New("An error occurred " + strconv.Itoa(*data.ErrorCode) + ": " + errorMessage)} + return + } + + for _, item := range data.Data.Items { + logEventChannel <- LogEventResult{LogEvent: item, Err: err} + } + + hasNext = *data.Data.Pagination.HasMore + page++ + } + + }() + return logEventChannel +} + +func (s *baseServiceImpl) GetLogEventsByTopicHashByPage(chainName chains.Chain, topicHash string, queryParamOpts ...GetLogEventsByTopicHashQueryParamOpts) (*utils.Response[LogEventsByTopicHashResponse], error) { + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/events/topics/%s/", chainName, topicHash) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[LogEventsByTopicHashResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.StartingBlock != nil { + params.Add("starting-block", fmt.Sprintf("%v", *opts.StartingBlock)) + } + + if opts.EndingBlock != nil { + params.Add("ending-block", fmt.Sprintf("%v", *opts.EndingBlock)) + } + + if opts.SecondaryTopics != nil { + params.Add("secondary-topics", fmt.Sprintf("%v", *opts.SecondaryTopics)) + } + + if opts.PageSize != nil { + params.Add("page-size", fmt.Sprintf("%v", *opts.PageSize)) + } + + if opts.PageNumber != nil { + params.Add("page-number", fmt.Sprintf("%v", *opts.PageNumber)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[LogEventsByTopicHashResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[LogEventsByTopicHashResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[LogEventsByTopicHashResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[LogEventsByTopicHashResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[LogEventsByTopicHashResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[LogEventsByTopicHashResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[LogEventsByTopicHashResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *baseServiceImpl) GetAllChains() (*utils.Response[AllChainsResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/chains/") + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[AllChainsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[AllChainsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[AllChainsResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[AllChainsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[AllChainsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[AllChainsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[AllChainsResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[AllChainsResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *baseServiceImpl) GetAllChainStatus() (*utils.Response[AllChainsStatusResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/chains/status/") + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[AllChainsStatusResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[AllChainsStatusResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[AllChainsStatusResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[AllChainsStatusResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[AllChainsStatusResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[AllChainsStatusResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[AllChainsStatusResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[AllChainsStatusResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *baseServiceImpl) GetAddressActivity(walletAddress string, queryParamOpts ...GetAddressActivityQueryParamOpts) (*utils.Response[ChainActivityResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/address/%s/activity/", walletAddress) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[ChainActivityResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.Testnets != nil { + params.Add("testnets", fmt.Sprintf("%v", *opts.Testnets)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[ChainActivityResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[ChainActivityResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[ChainActivityResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[ChainActivityResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[ChainActivityResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[ChainActivityResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[ChainActivityResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *baseServiceImpl) GetGasPrices(chainName chains.Chain, eventType string, queryParamOpts ...GetGasPricesQueryParamOpts) (*utils.Response[GasPricesResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/event/%s/gas_prices/", chainName, eventType) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[GasPricesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.QuoteCurrency != nil { + params.Add("quote-currency", fmt.Sprintf("%v", *opts.QuoteCurrency)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[GasPricesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[GasPricesResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[GasPricesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[GasPricesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[GasPricesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[GasPricesResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[GasPricesResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} diff --git a/services/nft_service.go b/services/nft_service.go new file mode 100644 index 0000000..3dd15b7 --- /dev/null +++ b/services/nft_service.go @@ -0,0 +1,1975 @@ +package services + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/covalenthq/covalent-api-sdk-go/chains" + "github.com/covalenthq/covalent-api-sdk-go/genericmodels" + "github.com/covalenthq/covalent-api-sdk-go/quotes" + "github.com/covalenthq/covalent-api-sdk-go/utils" +) + +type ChainCollectionResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []ChainCollectionItem `json:"items"` + // Pagination metadata. + Pagination genericmodels.Pagination `json:"pagination"` +} +type ChainCollectionItem struct { + // Use the relevant `contract_address` to lookup prices, logos, token transfers, etc. + ContractAddress *string `json:"contract_address,omitempty"` + // The string returned by the `name()` method. + ContractName *string `json:"contract_name,omitempty"` + // Denotes whether the token is suspected spam. Supports `eth-mainnet` and `matic-mainnet`. + IsSpam *bool `json:"is_spam,omitempty"` + TokenTotalSupply *int64 `json:"token_total_supply,omitempty"` + CachedMetadataCount *int64 `json:"cached_metadata_count,omitempty"` + CachedAssetCount *int64 `json:"cached_asset_count,omitempty"` + LastScrapedAt *time.Time `json:"last_scraped_at,omitempty"` +} +type NftAddressBalanceNftResponse struct { + // The requested address. + Address string `json:"address"` + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // List of response items. + Items []NftTokenContractBalanceItem `json:"items"` +} +type NftTokenContractBalanceItem struct { + // The string returned by the `name()` method. + ContractName *string `json:"contract_name,omitempty"` + // The ticker symbol for this contract. This field is set by a developer and non-unique across a network. + ContractTickerSymbol *string `json:"contract_ticker_symbol,omitempty"` + // Use the relevant `contract_address` to lookup prices, logos, token transfers, etc. + ContractAddress *string `json:"contract_address,omitempty"` + // A list of supported standard ERC interfaces, eg: `ERC20` and `ERC721`. + SupportsErc *[]string `json:"supports_erc,omitempty"` + // Denotes whether the token is suspected spam. Supports `eth-mainnet` and `matic-mainnet`. + IsSpam *bool `json:"is_spam,omitempty"` + LastTransferedAt *time.Time `json:"last_transfered_at,omitempty"` + // The asset balance. Use `contract_decimals` to scale this balance for display purposes. + Balance *utils.BigInt `json:"balance,omitempty"` + Balance24h *string `json:"balance_24h,omitempty"` + Type *string `json:"type,omitempty"` + // The current floor price converted to fiat in `quote-currency`. The floor price is determined by the last minimum sale price within the last 30 days across all the supported markets where the collection is sold on. + FloorPriceQuote *float64 `json:"floor_price_quote,omitempty"` + // A prettier version of the floor price quote for rendering purposes. + PrettyFloorPriceQuote *string `json:"pretty_floor_price_quote,omitempty"` + // The current floor price in native currency. The floor price is determined by the last minimum sale price within the last 30 days across all the supported markets where the collection is sold on. + FloorPriceNativeQuote *float64 `json:"floor_price_native_quote,omitempty"` + NftData *[]NftData `json:"nft_data,omitempty"` +} +type NftData struct { + // The token's id. + TokenId *utils.BigInt `json:"token_id,omitempty"` + TokenUrl *string `json:"token_url,omitempty"` + // The original minter. + OriginalOwner *string `json:"original_owner,omitempty"` + // The current holder of this NFT. + CurrentOwner *string `json:"current_owner,omitempty"` + ExternalData *NftExternalData `json:"external_data,omitempty"` + // If `true`, the asset data is available from the Covalent CDN. + AssetCached *bool `json:"asset_cached,omitempty"` + // If `true`, the image data is available from the Covalent CDN. + ImageCached *bool `json:"image_cached,omitempty"` +} +type NftExternalData struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + AssetUrl *string `json:"asset_url,omitempty"` + AssetFileExtension *string `json:"asset_file_extension,omitempty"` + AssetMimeType *string `json:"asset_mime_type,omitempty"` + AssetSizeBytes *string `json:"asset_size_bytes,omitempty"` + Image *string `json:"image,omitempty"` + Image256 *string `json:"image_256,omitempty"` + Image512 *string `json:"image_512,omitempty"` + Image1024 *string `json:"image_1024,omitempty"` + AnimationUrl *string `json:"animation_url,omitempty"` + ExternalUrl *string `json:"external_url,omitempty"` + Attributes *[]genericmodels.NftCollectionAttribute `json:"attributes,omitempty"` +} +type NftMetadataResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // List of response items. + Items []NftTokenContract `json:"items"` + // Pagination metadata. + Pagination genericmodels.Pagination `json:"pagination"` +} +type NftTokenContract struct { + // The string returned by the `name()` method. + ContractName *string `json:"contract_name,omitempty"` + // The ticker symbol for this contract. This field is set by a developer and non-unique across a network. + ContractTickerSymbol *string `json:"contract_ticker_symbol,omitempty"` + // Use the relevant `contract_address` to lookup prices, logos, token transfers, etc. + ContractAddress *string `json:"contract_address,omitempty"` + // Denotes whether the token is suspected spam. Supports `eth-mainnet` and `matic-mainnet`. + IsSpam *bool `json:"is_spam,omitempty"` + Type *string `json:"type,omitempty"` + NftData *NftData `json:"nft_data,omitempty"` +} +type NftTransactionsResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []NftTransaction `json:"items"` +} +type NftTransaction struct { + // Use contract decimals to format the token balance for display purposes - divide the balance by `10^{contract_decimals}`. + ContractDecimals *int `json:"contract_decimals,omitempty"` + // The string returned by the `name()` method. + ContractName *string `json:"contract_name,omitempty"` + // The ticker symbol for this contract. This field is set by a developer and non-unique across a network. + ContractTickerSymbol *string `json:"contract_ticker_symbol,omitempty"` + // The contract logo URL. + LogoUrl *string `json:"logo_url,omitempty"` + // Use the relevant `contract_address` to lookup prices, logos, token transfers, etc. + ContractAddress *string `json:"contract_address,omitempty"` + // A list of supported standard ERC interfaces, eg: `ERC20` and `ERC721`. + SupportsErc *[]string `json:"supports_erc,omitempty"` + NftTransactions *[]NftTransactionItem `json:"nft_transactions,omitempty"` + // Denotes whether the token is suspected spam. Supports `eth-mainnet` and `matic-mainnet`. + IsSpam *bool `json:"is_spam,omitempty"` +} +type NftTransactionItem struct { + // The block signed timestamp in UTC. + BlockSignedAt *time.Time `json:"block_signed_at,omitempty"` + // The height of the block. + BlockHeight *int `json:"block_height,omitempty"` + // The requested transaction hash. + TxHash *string `json:"tx_hash,omitempty"` + // The offset is the position of the tx in the block. + TxOffset *int `json:"tx_offset,omitempty"` + // Whether or not transaction is successful. + Successful *bool `json:"successful,omitempty"` + // The sender's wallet address. + FromAddress *string `json:"from_address,omitempty"` + // The label of `from` address. + FromAddressLabel *string `json:"from_address_label,omitempty"` + // The receiver's wallet address. + ToAddress *string `json:"to_address,omitempty"` + // The label of `to` address. + ToAddressLabel *string `json:"to_address_label,omitempty"` + // The value attached to this tx. + Value *utils.BigInt `json:"value,omitempty"` + // The value attached in `quote-currency` to this tx. + ValueQuote *float64 `json:"value_quote,omitempty"` + // A prettier version of the quote for rendering purposes. + PrettyValueQuote *string `json:"pretty_value_quote,omitempty"` + GasOffered *int64 `json:"gas_offered,omitempty"` + // The gas spent for this tx. + GasSpent *int64 `json:"gas_spent,omitempty"` + // The gas price at the time of this tx. + GasPrice *int64 `json:"gas_price,omitempty"` + // The total transaction fees (gas_price * gas_spent) paid for this tx, denoted in wei. + FeesPaid *utils.BigInt `json:"fees_paid,omitempty"` + // The gas spent in `quote-currency` denomination. + GasQuote *float64 `json:"gas_quote,omitempty"` + // A prettier version of the quote for rendering purposes. + PrettyGasQuote *string `json:"pretty_gas_quote,omitempty"` + GasQuoteRate *float64 `json:"gas_quote_rate,omitempty"` + // The log events. + LogEvents *[]genericmodels.LogEvent `json:"log_events,omitempty"` +} +type NftCollectionTraitsResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // List of response items. + Items []NftTrait `json:"items"` +} +type NftTrait struct { + Name *string `json:"name,omitempty"` +} +type NftCollectionAttributesForTraitResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // List of response items. + Items []NftSummaryAttribute `json:"items"` +} +type NftSummaryAttribute struct { + TraitType *string `json:"trait_type,omitempty"` + Values *[]NftAttribute `json:"values,omitempty"` + UniqueValues *int `json:"unique_values,omitempty"` +} +type NftAttribute struct { + Value *string `json:"value,omitempty"` + Count *int `json:"count,omitempty"` +} +type NftCollectionTraitSummaryResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // List of response items. + Items []NftTraitSummary `json:"items"` +} +type NftTraitSummary struct { + // Trait name + Name *string `json:"name,omitempty"` + // Type of the value of the trait. + ValueType *string `json:"value_type,omitempty"` + // Populated for `numeric` traits. + ValueNumeric *NftTraitNumeric `json:"value_numeric,omitempty"` + // Populated for `string` traits. + ValueString *NftTraitString `json:"value_string,omitempty"` + Attributes *[]NftSummaryAttribute `json:"attributes,omitempty"` +} +type NftTraitNumeric struct { + Min *float64 `json:"min,omitempty"` + Max *float64 `json:"max,omitempty"` +} +type NftTraitString struct { + // String value + Value *string `json:"value,omitempty"` + // Number of distinct tokens that have this trait value. + TokenCount *int `json:"token_count,omitempty"` + // Percentage of tokens in the collection that have this trait. + TraitPercentage *float64 `json:"trait_percentage,omitempty"` +} +type NftOwnershipForCollectionResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested address. + Address string `json:"address"` + // The requested collection. + Collection string `json:"collection"` + // Denotes whether the token is suspected spam. Supports `eth-mainnet` and `matic-mainnet`. + IsSpam bool `json:"is_spam"` + // List of response items. + Items []NftOwnershipForCollectionItem `json:"items"` +} +type NftOwnershipForCollectionItem struct { + // The string returned by the `name()` method. + ContractName *string `json:"contract_name,omitempty"` + // The ticker symbol for this contract. This field is set by a developer and non-unique across a network. + ContractTickerSymbol *string `json:"contract_ticker_symbol,omitempty"` + // Use the relevant `contract_address` to lookup prices, logos, token transfers, etc. + ContractAddress *string `json:"contract_address,omitempty"` + // The token's id. + TokenId *utils.BigInt `json:"token_id,omitempty"` + // A list of supported standard ERC interfaces, eg: `ERC20` and `ERC721`. + SupportsErc *[]string `json:"supports_erc,omitempty"` + LastTransferedAt *time.Time `json:"last_transfered_at,omitempty"` + // Nft balance. + Balance *utils.BigInt `json:"balance,omitempty"` + Balance24h *string `json:"balance_24h,omitempty"` + Type *string `json:"type,omitempty"` + NftData *NftData `json:"nft_data,omitempty"` +} +type NftMarketSaleCountResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested address. + Address string `json:"address"` + // The requested quote currency eg: `USD`. + QuoteCurrency string `json:"quote_currency"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // List of response items. + Items []MarketSaleCountItem `json:"items"` +} +type MarketSaleCountItem struct { + // The timestamp of the date of sale. + Date *time.Time `json:"date,omitempty"` + // The total amount of sales for the current day. + SaleCount *int `json:"sale_count,omitempty"` +} +type NftMarketVolumeResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested address. + Address string `json:"address"` + // The requested quote currency eg: `USD`. + QuoteCurrency string `json:"quote_currency"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // List of response items. + Items []MarketVolumeItem `json:"items"` +} +type MarketVolumeItem struct { + // The timestamp of the date of sale. + Date *time.Time `json:"date,omitempty"` + // The ticker symbol for the native currency. + NativeTickerSymbol *string `json:"native_ticker_symbol,omitempty"` + // The contract name of the native currency. + NativeName *string `json:"native_name,omitempty"` + // The current volume converted to fiat in `quote-currency`. + VolumeQuote *float64 `json:"volume_quote,omitempty"` + // The current volume in native currency. + VolumeNativeQuote *float64 `json:"volume_native_quote,omitempty"` + // A prettier version of the volume quote for rendering purposes. + PrettyVolumeQuote *string `json:"pretty_volume_quote,omitempty"` +} +type NftMarketFloorPriceResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested address. + Address string `json:"address"` + // The requested quote currency eg: `USD`. + QuoteCurrency string `json:"quote_currency"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // List of response items. + Items []MarketFloorPriceItem `json:"items"` +} +type MarketFloorPriceItem struct { + // The timestamp of the date of sale. + Date *time.Time `json:"date,omitempty"` + // The ticker symbol for the native currency. + NativeTickerSymbol *string `json:"native_ticker_symbol,omitempty"` + // The contract name of the native currency. + NativeName *string `json:"native_name,omitempty"` + // The current floor price in native currency. + FloorPriceNativeQuote *float64 `json:"floor_price_native_quote,omitempty"` + // The current floor price converted to fiat in `quote-currency`. + FloorPriceQuote *float64 `json:"floor_price_quote,omitempty"` + // A prettier version of the floor price quote for rendering purposes. + PrettyFloorPriceQuote *string `json:"pretty_floor_price_quote,omitempty"` +} +type ChainCollectionItemResult struct { + ChainCollectionItem ChainCollectionItem + Err error +} + +type NftTokenContractResult struct { + NftTokenContract NftTokenContract + Err error +} + +type GetChainCollectionsQueryParamOpts struct { + // Number of items per page. Omitting this parameter defaults to 100. + PageSize *int `json:"pageSize,omitempty"` + // 0-indexed page number to begin pagination. + PageNumber *int `json:"pageNumber,omitempty"` + // If `true`, the suspected spam tokens are removed. Supports `eth-mainnet` and `matic-mainnet`. + NoSpam *bool `json:"noSpam,omitempty"` +} +type GetNftsForAddressQueryParamOpts struct { + // If `true`, the suspected spam tokens are removed. Supports `eth-mainnet` and `matic-mainnet`. + NoSpam *bool `json:"noSpam,omitempty"` + // If `true`, the response shape is limited to a list of collections and token ids, omitting metadata and asset information. Helpful for faster response times and wallets holding a large number of NFTs. + NoNftAssetMetadata *bool `json:"noNftAssetMetadata,omitempty"` + // By default, this endpoint only works on chains where we've cached the assets and the metadata. When set to `true`, the API will fetch metadata from upstream servers even if it's not cached - the downside being that the upstream server can block or rate limit the call and therefore resulting in time outs or slow response times on the Covalent side. + WithUncached *bool `json:"withUncached,omitempty"` +} +type GetTokenIdsForContractWithMetadataQueryParamOpts struct { + // Omit metadata. + NoMetadata *bool `json:"noMetadata,omitempty"` + // Number of items per page. Omitting this parameter defaults to 100. + PageSize *int `json:"pageSize,omitempty"` + // 0-indexed page number to begin pagination. + PageNumber *int `json:"pageNumber,omitempty"` + // Filters NFTs based on a specific trait. If this filter is used, the API will return all NFTs with the specified trait. Accepts comma-separated values, is case-sensitive, and requires proper URL encoding. + TraitsFilter *string `json:"traitsFilter,omitempty"` + // Filters NFTs based on a specific trait value. If this filter is used, the API will return all NFTs with the specified trait value. If used with "traits-filter", only NFTs matching both filters will be returned. Accepts comma-separated values, is case-sensitive, and requires proper URL encoding. + ValuesFilter *string `json:"valuesFilter,omitempty"` + // By default, this endpoint only works on chains where we've cached the assets and the metadata. When set to `true`, the API will fetch metadata from upstream servers even if it's not cached - the downside being that the upstream server can block or rate limit the call and therefore resulting in time outs or slow response times on the Covalent side. + WithUncached *bool `json:"withUncached,omitempty"` +} +type GetNftMetadataForGivenTokenIdForContractQueryParamOpts struct { + // Omit metadata. + NoMetadata *bool `json:"noMetadata,omitempty"` + // By default, this endpoint only works on chains where we've cached the assets and the metadata. When set to `true`, the API will fetch metadata from upstream servers even if it's not cached - the downside being that the upstream server can block or rate limit the call and therefore resulting in time outs or slow response times on the Covalent side. + WithUncached *bool `json:"withUncached,omitempty"` +} +type GetNftTransactionsForContractTokenIdQueryParamOpts struct { + // If `true`, the suspected spam tokens are removed. Supports `eth-mainnet` and `matic-mainnet`. + NoSpam *bool `json:"noSpam,omitempty"` +} +type CheckOwnershipInNftQueryParamOpts struct { + // Filters NFTs based on a specific trait. If this filter is used, the API will return all NFTs with the specified trait. Must be used with "values-filter", is case-sensitive, and requires proper URL encoding. + TraitsFilter *string `json:"traitsFilter,omitempty"` + // Filters NFTs based on a specific trait value. If this filter is used, the API will return all NFTs with the specified trait value. Must be used with "traits-filter", is case-sensitive, and requires proper URL encoding. + ValuesFilter *string `json:"valuesFilter,omitempty"` +} +type GetNftMarketSaleCountQueryParamOpts struct { + // The number of days to return data for. Request up 365 days. Defaults to 30 days. + Days *int `json:"days,omitempty"` + // The currency to convert. Supports `USD`, `CAD`, `EUR`, `SGD`, `INR`, `JPY`, `VND`, `CNY`, `KRW`, `RUB`, `TRY`, `NGN`, `ARS`, `AUD`, `CHF`, and `GBP`. + QuoteCurrency *quotes.Quote `json:"quoteCurrency,omitempty"` +} +type GetNftMarketVolumeQueryParamOpts struct { + // The number of days to return data for. Request up 365 days. Defaults to 30 days. + Days *int `json:"days,omitempty"` + // The currency to convert. Supports `USD`, `CAD`, `EUR`, `SGD`, `INR`, `JPY`, `VND`, `CNY`, `KRW`, `RUB`, `TRY`, `NGN`, `ARS`, `AUD`, `CHF`, and `GBP`. + QuoteCurrency *quotes.Quote `json:"quoteCurrency,omitempty"` +} +type GetNftMarketFloorPriceQueryParamOpts struct { + // The number of days to return data for. Request up 365 days. Defaults to 30 days. + Days *int `json:"days,omitempty"` + // The currency to convert. Supports `USD`, `CAD`, `EUR`, `SGD`, `INR`, `JPY`, `VND`, `CNY`, `KRW`, `RUB`, `TRY`, `NGN`, `ARS`, `AUD`, `CHF`, and `GBP`. + QuoteCurrency *quotes.Quote `json:"quoteCurrency,omitempty"` +} + +func NewNftServiceImpl(apiKey string, debug bool, threadCount int, isValidKey bool) NftService { + + return &nftServiceImpl{APIKey: apiKey, Debug: debug, ThreadCount: threadCount, IskeyValid: isValidKey} +} + +type NftService interface { + + // Commonly used to fetch the list of NFT collections with downloaded and cached off chain data like token metadata and asset files. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + GetChainCollections(chainName chains.Chain, queryParamOpts ...GetChainCollectionsQueryParamOpts) <-chan ChainCollectionItemResult + + // Commonly used to fetch the list of NFT collections with downloaded and cached off chain data like token metadata and asset files. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + GetChainCollectionsByPage(chainName chains.Chain, queryParamOpts ...GetChainCollectionsQueryParamOpts) (*utils.Response[ChainCollectionResponse], error) + + // Commonly used to render the NFTs (including ERC721 and ERC1155) held by an address. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // walletAddress: The requested address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetNftsForAddress(chainName chains.Chain, walletAddress string, queryParamOpts ...GetNftsForAddressQueryParamOpts) (*utils.Response[NftAddressBalanceNftResponse], error) + + // Commonly used to get NFT token IDs with metadata from a collection. Useful for building NFT card displays. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // contractAddress: The requested contract address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetTokenIdsForContractWithMetadata(chainName chains.Chain, contractAddress string, queryParamOpts ...GetTokenIdsForContractWithMetadataQueryParamOpts) <-chan NftTokenContractResult + + // Commonly used to get NFT token IDs with metadata from a collection. Useful for building NFT card displays. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // contractAddress: The requested contract address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetTokenIdsForContractWithMetadataByPage(chainName chains.Chain, contractAddress string, queryParamOpts ...GetTokenIdsForContractWithMetadataQueryParamOpts) (*utils.Response[NftMetadataResponse], error) + + // Commonly used to get a single NFT metadata by token ID from a collection. Useful for building NFT card displays. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // contractAddress: The requested contract address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + // tokenId: The requested token ID.. Type: string + GetNftMetadataForGivenTokenIdForContract(chainName chains.Chain, contractAddress string, tokenId string, queryParamOpts ...GetNftMetadataForGivenTokenIdForContractQueryParamOpts) (*utils.Response[NftMetadataResponse], error) + + // Commonly used to get all transactions of an NFT token. Useful for building a transaction history table or price chart. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // contractAddress: The requested contract address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + // tokenId: The requested token ID.. Type: string + GetNftTransactionsForContractTokenId(chainName chains.Chain, contractAddress string, tokenId string, queryParamOpts ...GetNftTransactionsForContractTokenIdQueryParamOpts) (*utils.Response[NftTransactionsResponse], error) + + // Commonly used to fetch and render the traits of a collection as seen in rarity calculators. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // collectionContract: The requested collection address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetTraitsForCollection(chainName chains.Chain, collectionContract string) (*utils.Response[NftCollectionTraitsResponse], error) + + // Commonly used to get the count of unique values for traits within an NFT collection. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // collectionContract: The requested collection address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + // trait: The requested trait.. Type: string + GetAttributesForTraitInCollection(chainName chains.Chain, collectionContract string, trait string) (*utils.Response[NftCollectionAttributesForTraitResponse], error) + + // Commonly used to calculate rarity scores for a collection based on its traits. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // collectionContract: The requested collection address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetCollectionTraitsSummary(chainName chains.Chain, collectionContract string) (*utils.Response[NftCollectionTraitSummaryResponse], error) + + // Commonly used to verify ownership of NFTs (including ERC-721 and ERC-1155) within a collection. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // walletAddress: The requested address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + // collectionContract: The requested collection address.. Type: string + CheckOwnershipInNft(chainName chains.Chain, walletAddress string, collectionContract string, queryParamOpts ...CheckOwnershipInNftQueryParamOpts) (*utils.Response[NftOwnershipForCollectionResponse], error) + + // Commonly used to verify ownership of a specific token (ERC-721 or ERC-1155) within a collection. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // walletAddress: The requested address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + // collectionContract: The requested collection address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + // tokenId: The requested token ID.. Type: string + CheckOwnershipInNftForSpecificTokenId(chainName chains.Chain, walletAddress string, collectionContract string, tokenId string) (*utils.Response[NftOwnershipForCollectionResponse], error) + + // Commonly used to build a time-series chart of the sales count of an NFT collection. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // contractAddress: The requested contract address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetNftMarketSaleCount(chainName chains.Chain, contractAddress string, queryParamOpts ...GetNftMarketSaleCountQueryParamOpts) (*utils.Response[NftMarketSaleCountResponse], error) + + // Commonly used to build a time-series chart of the transaction volume of an NFT collection. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // contractAddress: The requested contract address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetNftMarketVolume(chainName chains.Chain, contractAddress string, queryParamOpts ...GetNftMarketVolumeQueryParamOpts) (*utils.Response[NftMarketVolumeResponse], error) + + // Commonly used to render a price floor chart for an NFT collection. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // contractAddress: The requested contract address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetNftMarketFloorPrice(chainName chains.Chain, contractAddress string, queryParamOpts ...GetNftMarketFloorPriceQueryParamOpts) (*utils.Response[NftMarketFloorPriceResponse], error) +} + +type nftServiceImpl struct { + APIKey string + Debug bool + ThreadCount int + IskeyValid bool +} + +func (s *nftServiceImpl) GetChainCollections(chainName chains.Chain, queryParamOpts ...GetChainCollectionsQueryParamOpts) <-chan ChainCollectionItemResult { + chainCollectionItemChannel := make(chan ChainCollectionItemResult) + + go func() { + defer close(chainCollectionItemChannel) + + hasNext := true + + if !s.IskeyValid { + chainCollectionItemChannel <- ChainCollectionItemResult{Err: fmt.Errorf(`An error occurred 401: ` + utils.InvalidAPIKeyMessage)} + return + } + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/nft/collections/", chainName) + + // Parse the formatted URL + parsedURL, err := url.Parse(apiURL) + if err != nil { + chainCollectionItemChannel <- ChainCollectionItemResult{Err: err} + return + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.PageSize != nil { + params.Add("page-size", fmt.Sprintf("%v", *opts.PageSize)) + } + + if opts.PageNumber != nil { + params.Add("page-number", fmt.Sprintf("%v", *opts.PageNumber)) + } + + if opts.NoSpam != nil { + params.Add("no-spam", fmt.Sprintf("%v", *opts.NoSpam)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + page := 0 + + if !params.Has("page-number") { + page = 0 + } else { + page, err = strconv.Atoi(params.Get("page-number")) + if err != nil { + chainCollectionItemChannel <- ChainCollectionItemResult{Err: err} + return + } + } + + var data utils.Response[ChainCollectionResponse] + for hasNext { + + res, err := utils.PaginateEndpoint(apiURL, s.APIKey, params, page, s.Debug, s.ThreadCount) + if err != nil { + chainCollectionItemChannel <- ChainCollectionItemResult{Err: err} + hasNext = false + return + } + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + chainCollectionItemChannel <- ChainCollectionItemResult{Err: err} + res.Body.Close() + hasNext = false + return + } + res.Body.Close() // Ensure the body is closed after processing it + + if data.Error { + var errorMessage string + if data.ErrorMessage != nil { + errorMessage = *data.ErrorMessage + } else { + errorMessage = "default error message" // Provide a default or handle differently + } + chainCollectionItemChannel <- ChainCollectionItemResult{Err: errors.New("An error occurred " + strconv.Itoa(*data.ErrorCode) + ": " + errorMessage)} + return + } + + for _, item := range data.Data.Items { + chainCollectionItemChannel <- ChainCollectionItemResult{ChainCollectionItem: item, Err: err} + } + + hasNext = *data.Data.Pagination.HasMore + page++ + } + + }() + return chainCollectionItemChannel +} + +func (s *nftServiceImpl) GetChainCollectionsByPage(chainName chains.Chain, queryParamOpts ...GetChainCollectionsQueryParamOpts) (*utils.Response[ChainCollectionResponse], error) { + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/nft/collections/", chainName) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[ChainCollectionResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.PageSize != nil { + params.Add("page-size", fmt.Sprintf("%v", *opts.PageSize)) + } + + if opts.PageNumber != nil { + params.Add("page-number", fmt.Sprintf("%v", *opts.PageNumber)) + } + + if opts.NoSpam != nil { + params.Add("no-spam", fmt.Sprintf("%v", *opts.NoSpam)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[ChainCollectionResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[ChainCollectionResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[ChainCollectionResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[ChainCollectionResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[ChainCollectionResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[ChainCollectionResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[ChainCollectionResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *nftServiceImpl) GetNftsForAddress(chainName chains.Chain, walletAddress string, queryParamOpts ...GetNftsForAddressQueryParamOpts) (*utils.Response[NftAddressBalanceNftResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/address/%s/balances_nft/", chainName, walletAddress) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[NftAddressBalanceNftResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.NoSpam != nil { + params.Add("no-spam", fmt.Sprintf("%v", *opts.NoSpam)) + } + + if opts.NoNftAssetMetadata != nil { + params.Add("no-nft-asset-metadata", fmt.Sprintf("%v", *opts.NoNftAssetMetadata)) + } + + if opts.WithUncached != nil { + params.Add("with-uncached", fmt.Sprintf("%v", *opts.WithUncached)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[NftAddressBalanceNftResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[NftAddressBalanceNftResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[NftAddressBalanceNftResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NftAddressBalanceNftResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NftAddressBalanceNftResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[NftAddressBalanceNftResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[NftAddressBalanceNftResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *nftServiceImpl) GetTokenIdsForContractWithMetadata(chainName chains.Chain, contractAddress string, queryParamOpts ...GetTokenIdsForContractWithMetadataQueryParamOpts) <-chan NftTokenContractResult { + nftTokenContractChannel := make(chan NftTokenContractResult) + + go func() { + defer close(nftTokenContractChannel) + + hasNext := true + + if !s.IskeyValid { + nftTokenContractChannel <- NftTokenContractResult{Err: fmt.Errorf(`An error occurred 401: ` + utils.InvalidAPIKeyMessage)} + return + } + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/nft/%s/metadata/", chainName, contractAddress) + + // Parse the formatted URL + parsedURL, err := url.Parse(apiURL) + if err != nil { + nftTokenContractChannel <- NftTokenContractResult{Err: err} + return + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.NoMetadata != nil { + params.Add("no-metadata", fmt.Sprintf("%v", *opts.NoMetadata)) + } + + if opts.PageSize != nil { + params.Add("page-size", fmt.Sprintf("%v", *opts.PageSize)) + } + + if opts.PageNumber != nil { + params.Add("page-number", fmt.Sprintf("%v", *opts.PageNumber)) + } + + if opts.TraitsFilter != nil { + params.Add("traits-filter", fmt.Sprintf("%v", *opts.TraitsFilter)) + } + + if opts.ValuesFilter != nil { + params.Add("values-filter", fmt.Sprintf("%v", *opts.ValuesFilter)) + } + + if opts.WithUncached != nil { + params.Add("with-uncached", fmt.Sprintf("%v", *opts.WithUncached)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + page := 0 + + if !params.Has("page-number") { + page = 0 + } else { + page, err = strconv.Atoi(params.Get("page-number")) + if err != nil { + nftTokenContractChannel <- NftTokenContractResult{Err: err} + return + } + } + + var data utils.Response[NftMetadataResponse] + for hasNext { + + res, err := utils.PaginateEndpoint(apiURL, s.APIKey, params, page, s.Debug, s.ThreadCount) + if err != nil { + nftTokenContractChannel <- NftTokenContractResult{Err: err} + hasNext = false + return + } + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + nftTokenContractChannel <- NftTokenContractResult{Err: err} + res.Body.Close() + hasNext = false + return + } + res.Body.Close() // Ensure the body is closed after processing it + + if data.Error { + var errorMessage string + if data.ErrorMessage != nil { + errorMessage = *data.ErrorMessage + } else { + errorMessage = "default error message" // Provide a default or handle differently + } + nftTokenContractChannel <- NftTokenContractResult{Err: errors.New("An error occurred " + strconv.Itoa(*data.ErrorCode) + ": " + errorMessage)} + return + } + + for _, item := range data.Data.Items { + nftTokenContractChannel <- NftTokenContractResult{NftTokenContract: item, Err: err} + } + + hasNext = *data.Data.Pagination.HasMore + page++ + } + + }() + return nftTokenContractChannel +} + +func (s *nftServiceImpl) GetTokenIdsForContractWithMetadataByPage(chainName chains.Chain, contractAddress string, queryParamOpts ...GetTokenIdsForContractWithMetadataQueryParamOpts) (*utils.Response[NftMetadataResponse], error) { + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/nft/%s/metadata/", chainName, contractAddress) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[NftMetadataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.NoMetadata != nil { + params.Add("no-metadata", fmt.Sprintf("%v", *opts.NoMetadata)) + } + + if opts.PageSize != nil { + params.Add("page-size", fmt.Sprintf("%v", *opts.PageSize)) + } + + if opts.PageNumber != nil { + params.Add("page-number", fmt.Sprintf("%v", *opts.PageNumber)) + } + + if opts.TraitsFilter != nil { + params.Add("traits-filter", fmt.Sprintf("%v", *opts.TraitsFilter)) + } + + if opts.ValuesFilter != nil { + params.Add("values-filter", fmt.Sprintf("%v", *opts.ValuesFilter)) + } + + if opts.WithUncached != nil { + params.Add("with-uncached", fmt.Sprintf("%v", *opts.WithUncached)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[NftMetadataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[NftMetadataResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[NftMetadataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NftMetadataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NftMetadataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[NftMetadataResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[NftMetadataResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *nftServiceImpl) GetNftMetadataForGivenTokenIdForContract(chainName chains.Chain, contractAddress string, tokenId string, queryParamOpts ...GetNftMetadataForGivenTokenIdForContractQueryParamOpts) (*utils.Response[NftMetadataResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/nft/%s/metadata/%s/", chainName, contractAddress, tokenId) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[NftMetadataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.NoMetadata != nil { + params.Add("no-metadata", fmt.Sprintf("%v", *opts.NoMetadata)) + } + + if opts.WithUncached != nil { + params.Add("with-uncached", fmt.Sprintf("%v", *opts.WithUncached)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[NftMetadataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[NftMetadataResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[NftMetadataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NftMetadataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NftMetadataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[NftMetadataResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[NftMetadataResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *nftServiceImpl) GetNftTransactionsForContractTokenId(chainName chains.Chain, contractAddress string, tokenId string, queryParamOpts ...GetNftTransactionsForContractTokenIdQueryParamOpts) (*utils.Response[NftTransactionsResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/tokens/%s/nft_transactions/%s/", chainName, contractAddress, tokenId) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[NftTransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.NoSpam != nil { + params.Add("no-spam", fmt.Sprintf("%v", *opts.NoSpam)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[NftTransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[NftTransactionsResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[NftTransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NftTransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NftTransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[NftTransactionsResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[NftTransactionsResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *nftServiceImpl) GetTraitsForCollection(chainName chains.Chain, collectionContract string) (*utils.Response[NftCollectionTraitsResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/nft/%s/traits/", chainName, collectionContract) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[NftCollectionTraitsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[NftCollectionTraitsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[NftCollectionTraitsResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[NftCollectionTraitsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NftCollectionTraitsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NftCollectionTraitsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[NftCollectionTraitsResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[NftCollectionTraitsResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *nftServiceImpl) GetAttributesForTraitInCollection(chainName chains.Chain, collectionContract string, trait string) (*utils.Response[NftCollectionAttributesForTraitResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/nft/%s/traits/%s/attributes/", chainName, collectionContract, trait) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[NftCollectionAttributesForTraitResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[NftCollectionAttributesForTraitResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[NftCollectionAttributesForTraitResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[NftCollectionAttributesForTraitResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NftCollectionAttributesForTraitResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NftCollectionAttributesForTraitResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[NftCollectionAttributesForTraitResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[NftCollectionAttributesForTraitResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *nftServiceImpl) GetCollectionTraitsSummary(chainName chains.Chain, collectionContract string) (*utils.Response[NftCollectionTraitSummaryResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/nft/%s/traits_summary/", chainName, collectionContract) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[NftCollectionTraitSummaryResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[NftCollectionTraitSummaryResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[NftCollectionTraitSummaryResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[NftCollectionTraitSummaryResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NftCollectionTraitSummaryResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NftCollectionTraitSummaryResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[NftCollectionTraitSummaryResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[NftCollectionTraitSummaryResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *nftServiceImpl) CheckOwnershipInNft(chainName chains.Chain, walletAddress string, collectionContract string, queryParamOpts ...CheckOwnershipInNftQueryParamOpts) (*utils.Response[NftOwnershipForCollectionResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/address/%s/collection/%s/", chainName, walletAddress, collectionContract) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[NftOwnershipForCollectionResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.TraitsFilter != nil { + params.Add("traits-filter", fmt.Sprintf("%v", *opts.TraitsFilter)) + } + + if opts.ValuesFilter != nil { + params.Add("values-filter", fmt.Sprintf("%v", *opts.ValuesFilter)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[NftOwnershipForCollectionResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[NftOwnershipForCollectionResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[NftOwnershipForCollectionResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NftOwnershipForCollectionResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NftOwnershipForCollectionResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[NftOwnershipForCollectionResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[NftOwnershipForCollectionResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *nftServiceImpl) CheckOwnershipInNftForSpecificTokenId(chainName chains.Chain, walletAddress string, collectionContract string, tokenId string) (*utils.Response[NftOwnershipForCollectionResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/address/%s/collection/%s/token/%s/", chainName, walletAddress, collectionContract, tokenId) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[NftOwnershipForCollectionResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[NftOwnershipForCollectionResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[NftOwnershipForCollectionResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[NftOwnershipForCollectionResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NftOwnershipForCollectionResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NftOwnershipForCollectionResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[NftOwnershipForCollectionResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[NftOwnershipForCollectionResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *nftServiceImpl) GetNftMarketSaleCount(chainName chains.Chain, contractAddress string, queryParamOpts ...GetNftMarketSaleCountQueryParamOpts) (*utils.Response[NftMarketSaleCountResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/nft_market/%s/sale_count/", chainName, contractAddress) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[NftMarketSaleCountResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.Days != nil { + params.Add("days", fmt.Sprintf("%v", *opts.Days)) + } + + if opts.QuoteCurrency != nil { + params.Add("quote-currency", fmt.Sprintf("%v", *opts.QuoteCurrency)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[NftMarketSaleCountResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[NftMarketSaleCountResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[NftMarketSaleCountResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NftMarketSaleCountResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NftMarketSaleCountResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[NftMarketSaleCountResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[NftMarketSaleCountResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *nftServiceImpl) GetNftMarketVolume(chainName chains.Chain, contractAddress string, queryParamOpts ...GetNftMarketVolumeQueryParamOpts) (*utils.Response[NftMarketVolumeResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/nft_market/%s/volume/", chainName, contractAddress) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[NftMarketVolumeResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.Days != nil { + params.Add("days", fmt.Sprintf("%v", *opts.Days)) + } + + if opts.QuoteCurrency != nil { + params.Add("quote-currency", fmt.Sprintf("%v", *opts.QuoteCurrency)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[NftMarketVolumeResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[NftMarketVolumeResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[NftMarketVolumeResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NftMarketVolumeResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NftMarketVolumeResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[NftMarketVolumeResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[NftMarketVolumeResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *nftServiceImpl) GetNftMarketFloorPrice(chainName chains.Chain, contractAddress string, queryParamOpts ...GetNftMarketFloorPriceQueryParamOpts) (*utils.Response[NftMarketFloorPriceResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/nft_market/%s/floor_price/", chainName, contractAddress) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[NftMarketFloorPriceResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.Days != nil { + params.Add("days", fmt.Sprintf("%v", *opts.Days)) + } + + if opts.QuoteCurrency != nil { + params.Add("quote-currency", fmt.Sprintf("%v", *opts.QuoteCurrency)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[NftMarketFloorPriceResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[NftMarketFloorPriceResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[NftMarketFloorPriceResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NftMarketFloorPriceResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NftMarketFloorPriceResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[NftMarketFloorPriceResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[NftMarketFloorPriceResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} diff --git a/services/pricing_service.go b/services/pricing_service.go new file mode 100644 index 0000000..ad662d6 --- /dev/null +++ b/services/pricing_service.go @@ -0,0 +1,216 @@ +package services + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "strings" + "time" + + "github.com/covalenthq/covalent-api-sdk-go/chains" + "github.com/covalenthq/covalent-api-sdk-go/genericmodels" + "github.com/covalenthq/covalent-api-sdk-go/quotes" + "github.com/covalenthq/covalent-api-sdk-go/utils" +) + +type TokenPricesResponse struct { + // Use contract decimals to format the token balance for display purposes - divide the balance by `10^{contract_decimals}`. + ContractDecimals int `json:"contract_decimals"` + // The string returned by the `name()` method. + ContractName string `json:"contract_name"` + // The ticker symbol for this contract. This field is set by a developer and non-unique across a network. + ContractTickerSymbol string `json:"contract_ticker_symbol"` + // Use the relevant `contract_address` to lookup prices, logos, token transfers, etc. + ContractAddress string `json:"contract_address"` + // A list of supported standard ERC interfaces, eg: `ERC20` and `ERC721`. + SupportsErc []string `json:"supports_erc"` + // The contract logo URL. + LogoUrl string `json:"logo_url"` + UpdateAt time.Time `json:"update_at"` + // The requested quote currency eg: `USD`. + QuoteCurrency string `json:"quote_currency"` + // The contract logo URLs. + LogoUrls LogoUrls `json:"logo_urls"` + // List of response items. + Prices []Price `json:"prices"` + // List of response items. + Items []Price `json:"items"` +} +type LogoUrls struct { + // The token logo URL. + TokenLogoUrl *string `json:"token_logo_url,omitempty"` + // The protocol logo URL. + ProtocolLogoUrl *string `json:"protocol_logo_url,omitempty"` + // The chain logo URL. + ChainLogoUrl *string `json:"chain_logo_url,omitempty"` +} +type Price struct { + ContractMetadata *genericmodels.ContractMetadata `json:"contract_metadata,omitempty"` + // The date of the price capture. + Date *CustomTime `json:"date,omitempty"` + // The price in the requested `quote-currency`. + Price *float64 `json:"price,omitempty"` + // A prettier version of the price for rendering purposes. + PrettyPrice *string `json:"pretty_price,omitempty"` +} + +type GetTokenPricesQueryParamOpts struct { + // The start day of the historical price range (YYYY-MM-DD). + From *string `json:"from,omitempty"` + // The end day of the historical price range (YYYY-MM-DD). + To *string `json:"to,omitempty"` + // Sort the prices in chronological ascending order. By default, it's set to `false` and returns prices in chronological descending order. + PricesAtAsc *bool `json:"pricesAtAsc,omitempty"` +} + +type CustomTime struct { + *time.Time +} + +func (ct *CustomTime) UnmarshalJSON(b []byte) error { + // Trim quotes since JSON numbers and booleans come in quotes + s := strings.Trim(string(b), "\"") + if s == "null" || s == "" { + return nil + } + // Parse the string to time.Time using the expected layout + // Adjust "2006-01-02" as needed to match your input format + t, err := time.Parse("2006-01-02", s) + if err != nil { + return err + } + ct.Time = &t + return nil +} + +type Response[T any] struct { + Data *[]T `json:"data,omitempty"` + Error bool `json:"error"` + ErrorCode *int `json:"error_code"` + ErrorMessage *string `json:"error_message"` +} + +func NewPricingServiceImpl(apiKey string, debug bool, threadCount int, isValidKey bool) PricingService { + + return &pricingServiceImpl{APIKey: apiKey, Debug: debug, ThreadCount: threadCount, IskeyValid: isValidKey} +} + +type PricingService interface { + + // Commonly used to get historic prices of a token between date ranges. Supports native tokens. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // quoteCurrency: The currency to convert. Supports `USD`, `CAD`, `EUR`, `SGD`, `INR`, `JPY`, `VND`, `CNY`, `KRW`, `RUB`, `TRY`, `NGN`, `ARS`, `AUD`, `CHF`, and `GBP`.. Type: quotes.Quote + // contractAddress: Contract address for the token. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically. Supports multiple contract addresses separated by commas.. Type: string + GetTokenPrices(chainName chains.Chain, quoteCurrency quotes.Quote, contractAddress string, queryParamOpts ...GetTokenPricesQueryParamOpts) (*Response[TokenPricesResponse], error) +} + +type pricingServiceImpl struct { + APIKey string + Debug bool + ThreadCount int + IskeyValid bool +} + +func (s *pricingServiceImpl) GetTokenPrices(chainName chains.Chain, quoteCurrency quotes.Quote, contractAddress string, queryParamOpts ...GetTokenPricesQueryParamOpts) (*Response[TokenPricesResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/pricing/historical_by_addresses_v2/%v/%v/%s/", chainName, quoteCurrency, contractAddress) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &Response[TokenPricesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.From != nil { + params.Add("from", fmt.Sprintf("%v", *opts.From)) + } + + if opts.To != nil { + params.Add("to", fmt.Sprintf("%v", *opts.To)) + } + + if opts.PricesAtAsc != nil { + params.Add("prices-at-asc", fmt.Sprintf("%v", *opts.PricesAtAsc)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &Response[TokenPricesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data Response[TokenPricesResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &Response[TokenPricesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &Response[TokenPricesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &Response[TokenPricesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &Response[TokenPricesResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &Response[TokenPricesResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} diff --git a/services/security_service.go b/services/security_service.go new file mode 100644 index 0000000..6a474cc --- /dev/null +++ b/services/security_service.go @@ -0,0 +1,333 @@ +package services + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "time" + + "github.com/covalenthq/covalent-api-sdk-go/chains" + "github.com/covalenthq/covalent-api-sdk-go/utils" +) + +type ApprovalsResponse struct { + // The requested address. + Address string `json:"address"` + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested quote currency eg: `USD`. + QuoteCurrency string `json:"quote_currency"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []TokensApprovalItem `json:"items"` +} +type TokensApprovalItem struct { + // The address for the token that has approvals. + TokenAddress *string `json:"token_address,omitempty"` + // The name for the token that has approvals. + TokenAddressLabel *string `json:"token_address_label,omitempty"` + // The ticker symbol for this contract. This field is set by a developer and non-unique across a network. + TickerSymbol *string `json:"ticker_symbol,omitempty"` + // Use contract decimals to format the token balance for display purposes - divide the balance by `10^{contract_decimals}`. + ContractDecimals *int `json:"contract_decimals,omitempty"` + // The contract logo URL. + LogoUrl *string `json:"logo_url,omitempty"` + // The exchange rate for the requested quote currency. + QuoteRate *float64 `json:"quote_rate,omitempty"` + // Wallet balance of the token. + Balance *utils.BigInt `json:"balance,omitempty"` + // Value of the wallet balance of the token. + BalanceQuote *float64 `json:"balance_quote,omitempty"` + // A prettier version of the quote for rendering purposes. + PrettyBalanceQuote *string `json:"pretty_balance_quote,omitempty"` + // Total amount at risk across all spenders. + ValueAtRisk *string `json:"value_at_risk,omitempty"` + // Value of total amount at risk across all spenders. + ValueAtRiskQuote *float64 `json:"value_at_risk_quote,omitempty"` + // A prettier version of the quote for rendering purposes. + PrettyValueAtRiskQuote *string `json:"pretty_value_at_risk_quote,omitempty"` + // Contracts with non-zero approvals for this token. + Spenders *[]TokenSpenderItem `json:"spenders,omitempty"` +} +type TokenSpenderItem struct { + // The height of the block. + BlockHeight *int64 `json:"block_height,omitempty"` + // The offset is the position of the tx in the block. + TxOffset *int64 `json:"tx_offset,omitempty"` + // The offset is the position of the log entry within an event log." + LogOffset *int64 `json:"log_offset,omitempty"` + // The block signed timestamp in UTC. + BlockSignedAt *time.Time `json:"block_signed_at,omitempty"` + // Most recent transaction that updated approval amounts for the token. + TxHash *string `json:"tx_hash,omitempty"` + // Address of the contract with approval for the token. + SpenderAddress *string `json:"spender_address,omitempty"` + // Name of the contract with approval for the token. + SpenderAddressLabel *string `json:"spender_address_label,omitempty"` + // Remaining number of tokens granted to the spender by the approval. + Allowance *string `json:"allowance,omitempty"` + // Value of the remaining allowance specified by the approval. + AllowanceQuote *float64 `json:"allowance_quote,omitempty"` + // A prettier version of the quote for rendering purposes. + PrettyAllowanceQuote *string `json:"pretty_allowance_quote,omitempty"` + // Amount at risk for spender. + ValueAtRisk *string `json:"value_at_risk,omitempty"` + // Value of amount at risk for spender. + ValueAtRiskQuote *float64 `json:"value_at_risk_quote,omitempty"` + // A prettier version of the quote for rendering purposes. + PrettyValueAtRiskQuote *string `json:"pretty_value_at_risk_quote,omitempty"` + RiskFactor *string `json:"risk_factor,omitempty"` +} +type NftApprovalsResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // The requested address. + Address string `json:"address"` + // List of response items. + Items []NftApprovalsItem `json:"items"` +} +type NftApprovalsItem struct { + // Use the relevant `contract_address` to lookup prices, logos, token transfers, etc. + ContractAddress *string `json:"contract_address,omitempty"` + // The label of the contract address. + ContractAddressLabel *string `json:"contract_address_label,omitempty"` + // The ticker symbol for this contract. This field is set by a developer and non-unique across a network. + ContractTickerSymbol *string `json:"contract_ticker_symbol,omitempty"` + // List of asset balances held by the user. + TokenBalances *[]NftApprovalBalance `json:"token_balances,omitempty"` + // Contracts with non-zero approvals for this token. + Spenders *[]NftApprovalSpender `json:"spenders,omitempty"` +} +type NftApprovalBalance struct { + // The token's id. + TokenId *utils.BigInt `json:"token_id,omitempty"` + // The NFT's token balance. + TokenBalance *utils.BigInt `json:"token_balance,omitempty"` +} +type NftApprovalSpender struct { + // The height of the block. + BlockHeight *int64 `json:"block_height,omitempty"` + // The offset is the position of the tx in the block. + TxOffset *int64 `json:"tx_offset,omitempty"` + // The offset is the position of the log entry within an event log." + LogOffset *int64 `json:"log_offset,omitempty"` + // The block signed timestamp in UTC. + BlockSignedAt *time.Time `json:"block_signed_at,omitempty"` + // Most recent transaction that updated approval amounts for the token. + TxHash *string `json:"tx_hash,omitempty"` + // Address of the contract with approval for the token. + SpenderAddress *string `json:"spender_address,omitempty"` + // Name of the contract with approval for the token. + SpenderAddressLabel *string `json:"spender_address_label,omitempty"` + // The token ids approved. + TokenIdsApproved *string `json:"token_ids_approved,omitempty"` + // Remaining number of tokens granted to the spender by the approval. + Allowance *string `json:"allowance,omitempty"` +} + +func NewSecurityServiceImpl(apiKey string, debug bool, threadCount int, isValidKey bool) SecurityService { + + return &securityServiceImpl{APIKey: apiKey, Debug: debug, ThreadCount: threadCount, IskeyValid: isValidKey} +} + +type SecurityService interface { + + // Commonly used to get a list of approvals across all token contracts categorized by spenders for a wallet’s assets. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // walletAddress: The requested address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetApprovals(chainName chains.Chain, walletAddress string) (*utils.Response[ApprovalsResponse], error) + + // Commonly used to get a list of NFT approvals across all token contracts categorized by spenders for a wallet’s assets. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // walletAddress: The requested address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetNftApprovals(chainName chains.Chain, walletAddress string) (*utils.Response[NftApprovalsResponse], error) +} + +type securityServiceImpl struct { + APIKey string + Debug bool + ThreadCount int + IskeyValid bool +} + +func (s *securityServiceImpl) GetApprovals(chainName chains.Chain, walletAddress string) (*utils.Response[ApprovalsResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/approvals/%s/", chainName, walletAddress) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[ApprovalsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[ApprovalsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[ApprovalsResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[ApprovalsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[ApprovalsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[ApprovalsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[ApprovalsResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[ApprovalsResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *securityServiceImpl) GetNftApprovals(chainName chains.Chain, walletAddress string) (*utils.Response[NftApprovalsResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/nft/approvals/%s/", chainName, walletAddress) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[NftApprovalsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[NftApprovalsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[NftApprovalsResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[NftApprovalsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NftApprovalsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NftApprovalsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[NftApprovalsResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[NftApprovalsResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} diff --git a/services/transaction_service.go b/services/transaction_service.go new file mode 100644 index 0000000..c46dcfa --- /dev/null +++ b/services/transaction_service.go @@ -0,0 +1,2179 @@ +package services + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/covalenthq/covalent-api-sdk-go/chains" + "github.com/covalenthq/covalent-api-sdk-go/genericmodels" + "github.com/covalenthq/covalent-api-sdk-go/quotes" + "github.com/covalenthq/covalent-api-sdk-go/utils" +) + +var clientKey string +var debugOutput bool +var workerCount int +var isKeyValid bool + +type TransactionResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []Transaction `json:"items"` +} +type Transaction struct { + // The block signed timestamp in UTC. + BlockSignedAt *time.Time `json:"block_signed_at,omitempty"` + // The height of the block. + BlockHeight *int `json:"block_height,omitempty"` + // The hash of the block. Use it to remove transactions from re-org-ed blocks. + BlockHash *string `json:"block_hash,omitempty"` + // The requested transaction hash. + TxHash *string `json:"tx_hash,omitempty"` + // The offset is the position of the tx in the block. + TxOffset *int `json:"tx_offset,omitempty"` + // Indicates whether a transaction failed or succeeded. + Successful *bool `json:"successful,omitempty"` + // The sender's wallet address. + FromAddress *string `json:"from_address,omitempty"` + // The address of the miner. + MinerAddress *string `json:"miner_address,omitempty"` + // The label of `from` address. + FromAddressLabel *string `json:"from_address_label,omitempty"` + // The receiver's wallet address. + ToAddress *string `json:"to_address,omitempty"` + // The label of `to` address. + ToAddressLabel *string `json:"to_address_label,omitempty"` + // The value attached to this tx. + Value *utils.BigInt `json:"value,omitempty"` + // The value attached in `quote-currency` to this tx. + ValueQuote *float64 `json:"value_quote,omitempty"` + // A prettier version of the quote for rendering purposes. + PrettyValueQuote *string `json:"pretty_value_quote,omitempty"` + // The requested chain native gas token metadata. + GasMetadata *genericmodels.ContractMetadata `json:"gas_metadata,omitempty"` + GasOffered *int64 `json:"gas_offered,omitempty"` + // The gas spent for this tx. + GasSpent *int64 `json:"gas_spent,omitempty"` + // The gas price at the time of this tx. + GasPrice *int64 `json:"gas_price,omitempty"` + // The total transaction fees (`gas_price` * `gas_spent`) paid for this tx, denoted in wei. + FeesPaid *utils.BigInt `json:"fees_paid,omitempty"` + // The gas spent in `quote-currency` denomination. + GasQuote *float64 `json:"gas_quote,omitempty"` + // A prettier version of the quote for rendering purposes. + PrettyGasQuote *string `json:"pretty_gas_quote,omitempty"` + // The native gas exchange rate for the requested `quote-currency`. + GasQuoteRate *float64 `json:"gas_quote_rate,omitempty"` + // The explorer links for this transaction. + Explorers *[]genericmodels.Explorer `json:"explorers,omitempty"` + // The details for the dex transaction. + DexDetails *[]DexReport `json:"dex_details,omitempty"` + // The details for the NFT sale transaction. + NftSaleDetails *[]NftSalesReport `json:"nft_sale_details,omitempty"` + // The details for the lending protocol transaction. + LendingDetails *[]LendingReport `json:"lending_details,omitempty"` + // The log events. + LogEvents *[]genericmodels.LogEvent `json:"log_events,omitempty"` + // The details related to the safe transaction. + SafeDetails *[]SafeDetails `json:"safe_details,omitempty"` +} +type DexReport struct { + // The offset is the position of the log entry within an event log. + LogOffset *int64 `json:"log_offset,omitempty"` + // Stores the name of the protocol that facilitated the event. + ProtocolName *string `json:"protocol_name,omitempty"` + // Stores the contract address of the protocol that facilitated the event. + ProtocolAddress *string `json:"protocol_address,omitempty"` + // The protocol logo URL. + ProtocolLogoUrl *string `json:"protocol_logo_url,omitempty"` + // Stores the aggregator responsible for the event. + AggregatorName *string `json:"aggregator_name,omitempty"` + // Stores the contract address of the aggregator responsible for the event. + AggregatorAddress *string `json:"aggregator_address,omitempty"` + // DEXs often have multiple version - e.g Uniswap V1, V2 and V3. The `version` field allows you to look at a specific version of the DEX. + Version *int64 `json:"version,omitempty"` + // Similarly to the `version` field, `fork_version` gives you the version of the forked DEX. For example, most DEXs are a fork of Uniswap V2; therefore, `fork` = `aave` & `fork_version` = `2` + ForkVersion *int64 `json:"fork_version,omitempty"` + // Many DEXs are a fork of an already established DEX. The fork field allows you to see which DEX has been forked. + Fork *string `json:"fork,omitempty"` + // Stores the event taking place - e.g `swap`, `add_liquidity` and `remove_liquidity`. + Event *string `json:"event,omitempty"` + // Stores the address of the pair that the user interacts with. + PairAddress *string `json:"pair_address,omitempty"` + PairLpFeeBps *float64 `json:"pair_lp_fee_bps,omitempty"` + LpTokenAddress *string `json:"lp_token_address,omitempty"` + LpTokenTicker *string `json:"lp_token_ticker,omitempty"` + LpTokenNumDecimals *int64 `json:"lp_token_num_decimals,omitempty"` + LpTokenName *string `json:"lp_token_name,omitempty"` + LpTokenValue *string `json:"lp_token_value,omitempty"` + ExchangeRateUsd *float64 `json:"exchange_rate_usd,omitempty"` + // Stores the address of token 0 in the specific pair. + Token0Address *string `json:"token_0_address,omitempty"` + // Stores the ticker symbol of token 0 in the specific pair. + Token0Ticker *string `json:"token_0_ticker,omitempty"` + // Stores the number of contract decimals of token 0 in the specific pair. + Token0NumDecimals *int64 `json:"token_0_num_decimals,omitempty"` + // Stores the contract name of token 0 in the specific pair. + Token0Name *string `json:"token_0_name,omitempty"` + // Stores the address of token 1 in the specific pair. + Token1Address *string `json:"token_1_address,omitempty"` + // Stores the ticker symbol of token 1 in the specific pair. + Token1Ticker *string `json:"token_1_ticker,omitempty"` + // Stores the number of contract decimals of token 1 in the specific pair. + Token1NumDecimals *int64 `json:"token_1_num_decimals,omitempty"` + // Stores the contract name of token 1 in the specific pair. + Token1Name *string `json:"token_1_name,omitempty"` + // Stores the amount of token 0 used in the transaction. For example, 1 ETH, 100 USDC, 30 UNI, etc. + Token0Amount *string `json:"token_0_amount,omitempty"` + Token0QuoteRate *float64 `json:"token_0_quote_rate,omitempty"` + Token0UsdQuote *float64 `json:"token_0_usd_quote,omitempty"` + PrettyToken0UsdQuote *string `json:"pretty_token_0_usd_quote,omitempty"` + Token0LogoUrl *string `json:"token_0_logo_url,omitempty"` + // Stores the amount of token 1 used in the transaction. For example, 1 ETH, 100 USDC, 30 UNI, etc. + Token1Amount *string `json:"token_1_amount,omitempty"` + Token1QuoteRate *float64 `json:"token_1_quote_rate,omitempty"` + Token1UsdQuote *float64 `json:"token_1_usd_quote,omitempty"` + PrettyToken1UsdQuote *string `json:"pretty_token_1_usd_quote,omitempty"` + Token1LogoUrl *string `json:"token_1_logo_url,omitempty"` + // Stores the wallet address that initiated the transaction (i.e the wallet paying the gas fee). + Sender *string `json:"sender,omitempty"` + // Stores the recipient of the transaction - recipients can be other wallets or smart contracts. For example, if you want to Swap tokens on Uniswap, the Uniswap router would typically be the recipient of the transaction. + Recipient *string `json:"recipient,omitempty"` +} +type NftSalesReport struct { + // The offset is the position of the log entry within an event log. + LogOffset *int64 `json:"log_offset,omitempty"` + // Stores the topic event hash. All events have a unique topic event hash. + Topic0 *string `json:"topic0,omitempty"` + // Stores the contract address of the protocol that facilitated the event. + ProtocolContractAddress *string `json:"protocol_contract_address,omitempty"` + // Stores the name of the protocol that facilitated the event. + ProtocolName *string `json:"protocol_name,omitempty"` + // The protocol logo URL. + ProtocolLogoUrl *string `json:"protocol_logo_url,omitempty"` + // Stores the address of the transaction recipient. + To *string `json:"to,omitempty"` + // Stores the address of the transaction sender. + From *string `json:"from,omitempty"` + // Stores the address selling the NFT. + Maker *string `json:"maker,omitempty"` + // Stores the address buying the NFT. + Taker *string `json:"taker,omitempty"` + // Stores the NFTs token ID. All NFTs have a token ID. Within a collection, these token IDs are unique. If the NFT is transferred to another owner, the token id remains the same, as this number is its identifier within a collection. For example, if a collection has 10K NFTs then an NFT in that collection can have a token ID from 1-10K. + TokenId *string `json:"token_id,omitempty"` + // Stores the address of the collection. For example, [Bored Ape Yacht Club](https://etherscan.io/token/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d) + CollectionAddress *string `json:"collection_address,omitempty"` + // Stores the name of the collection. + CollectionName *string `json:"collection_name,omitempty"` + // Stores the address of the token used to purchase the NFT. + TokenAddress *string `json:"token_address,omitempty"` + // Stores the name of the token used to purchase the NFT. + TokenName *string `json:"token_name,omitempty"` + // Stores the ticker symbol of the token used to purchase the NFT. + TickerSymbol *string `json:"ticker_symbol,omitempty"` + // Stores the number decimal of the token used to purchase the NFT. + NumDecimals *int64 `json:"num_decimals,omitempty"` + ContractQuoteRate *float64 `json:"contract_quote_rate,omitempty"` + // The token amount used to purchase the NFT. For example, if the user purchased an NFT for 1 ETH. The `nft_token_price` field will hold `1`. + NftTokenPrice *float64 `json:"nft_token_price,omitempty"` + // The USD amount used to purchase the NFT. + NftTokenPriceUsd *float64 `json:"nft_token_price_usd,omitempty"` + PrettyNftTokenPriceUsd *string `json:"pretty_nft_token_price_usd,omitempty"` + // The price of the NFT denominated in the chains native token. Even if a seller sells their NFT for DAI or MANA, this field denominates the price in the native token (e.g. ETH, AVAX, FTM, etc.) + NftTokenPriceNative *float64 `json:"nft_token_price_native,omitempty"` + PrettyNftTokenPriceNative *string `json:"pretty_nft_token_price_native,omitempty"` + // Stores the number of NFTs involved in the sale. It's quick routine to see multiple NFTs involved in a single sale. + TokenCount *int64 `json:"token_count,omitempty"` + NumTokenIdsSoldPerSale *int64 `json:"num_token_ids_sold_per_sale,omitempty"` + NumTokenIdsSoldPerTx *int64 `json:"num_token_ids_sold_per_tx,omitempty"` + NumCollectionsSoldPerSale *int64 `json:"num_collections_sold_per_sale,omitempty"` + NumCollectionsSoldPerTx *int64 `json:"num_collections_sold_per_tx,omitempty"` + TradeType *string `json:"trade_type,omitempty"` + TradeGroupType *string `json:"trade_group_type,omitempty"` +} +type LendingReport struct { + // The offset is the position of the log entry within an event log. + LogOffset *int64 `json:"log_offset,omitempty"` + // Stores the name of the lending protocol that facilitated the event. + ProtocolName *string `json:"protocol_name,omitempty"` + // Stores the contract address of the lending protocol that facilitated the event. + ProtocolAddress *string `json:"protocol_address,omitempty"` + // The protocol logo URL. + ProtocolLogoUrl *string `json:"protocol_logo_url,omitempty"` + // Lending protocols often have multiple version (e.g. Aave V1, V2 and V3). The `version` field allows you to look at a specific version of the Lending protocol. + Version *string `json:"version,omitempty"` + // Many lending protocols are a fork of an already established protocol. The fork column allows you to see which lending protocol has been forked. + Fork *string `json:"fork,omitempty"` + // Similarly to the `version` column, `fork_version` gives you the version of the forked lending protocol. For example, most lending protocols in the space are a fork of Aave V2; therefore, `fork` = `aave` & `fork_version` = `2` + ForkVersion *string `json:"fork_version,omitempty"` + // Stores the event taking place - e.g `borrow`, `deposit`, `liquidation`, 'repay' and 'withdraw'. + Event *string `json:"event,omitempty"` + // Stores the name of the LP token issued by the lending protocol. LP tokens can be debt or interest bearing tokens. + LpTokenName *string `json:"lp_token_name,omitempty"` + // Stores the number decimal of the LP token. + LpDecimals *int `json:"lp_decimals,omitempty"` + // Stores the ticker symbol of the LP token. + LpTickerSymbol *string `json:"lp_ticker_symbol,omitempty"` + // Stores the token address of the LP token. + LpTokenAddress *string `json:"lp_token_address,omitempty"` + // Stores the amount of LP token used in the event (e.g. 1 aETH, 100 cUSDC, etc). + LpTokenAmount *float64 `json:"lp_token_amount,omitempty"` + // Stores the total USD amount of all the LP Token used in the event. + LpTokenPrice *float64 `json:"lp_token_price,omitempty"` + // Stores the exchange rate between the LP and underlying token. + ExchangeRate *float64 `json:"exchange_rate,omitempty"` + // Stores the USD price of the LP Token used in the event. + ExchangeRateUsd *float64 `json:"exchange_rate_usd,omitempty"` + // Stores the name of the token going into the lending protocol (e.g the token getting deposited). + TokenNameIn *string `json:"token_name_in,omitempty"` + // Stores the number decimal of the token going into the lending protocol. + TokenDecimalIn *int `json:"token_decimal_in,omitempty"` + // Stores the address of the token going into the lending protocol. + TokenAddressIn *string `json:"token_address_in,omitempty"` + // Stores the ticker symbol of the token going into the lending protocol. + TokenTickerIn *string `json:"token_ticker_in,omitempty"` + // Stores the logo URL of the token going into the lending protocol. + TokenLogoIn *string `json:"token_logo_in,omitempty"` + // Stores the amount of tokens going into the lending protocol (e.g 1 ETH, 100 USDC, etc). + TokenAmountIn *float64 `json:"token_amount_in,omitempty"` + // Stores the total USD amount of all tokens going into the lending protocol. + AmountInUsd *float64 `json:"amount_in_usd,omitempty"` + PrettyAmountInUsd *string `json:"pretty_amount_in_usd,omitempty"` + // Stores the name of the token going out of the lending protocol (e.g the token getting deposited). + TokenNameOut *string `json:"token_name_out,omitempty"` + // Stores the number decimal of the token going out of the lending protocol. + TokenDecimalsOut *int `json:"token_decimals_out,omitempty"` + // Stores the address of the token going out of the lending protocol. + TokenAddressOut *string `json:"token_address_out,omitempty"` + // Stores the ticker symbol of the token going out of the lending protocol. + TokenTickerOut *string `json:"token_ticker_out,omitempty"` + // Stores the logo URL of the token going out of the lending protocol. + TokenLogoOut *string `json:"token_logo_out,omitempty"` + // Stores the amount of tokens going out of the lending protocol (e.g 1 ETH, 100 USDC, etc). + TokenAmountOut *float64 `json:"token_amount_out,omitempty"` + // Stores the total USD amount of all tokens going out of the lending protocol. + AmountOutUsd *float64 `json:"amount_out_usd,omitempty"` + PrettyAmountOutUsd *string `json:"pretty_amount_out_usd,omitempty"` + // Stores the type of loan the user is taking out. Lending protocols enable you to take out a stable or variable loan. Only relevant to borrow events. + BorrowRateMode *float64 `json:"borrow_rate_mode,omitempty"` + // Stores the interest rate of the loan. Only relevant to borrow events. + BorrowRate *float64 `json:"borrow_rate,omitempty"` + OnBehalfOf *string `json:"on_behalf_of,omitempty"` + // Stores the wallet address liquidating the loan. Only relevant to liquidation events. + Liquidator *string `json:"liquidator,omitempty"` + // Stores the wallet address of the user initiating the event. + User *string `json:"user,omitempty"` +} +type SafeDetails struct { + // The address that signed the safe transaction. + OwnerAddress *string `json:"owner_address,omitempty"` + // The signature of the owner for the safe transaction. + Signature *string `json:"signature,omitempty"` + // The type of safe signature used. + SignatureType *string `json:"signature_type,omitempty"` +} +type TransactionsResponse struct { + // The requested address. + Address string `json:"address"` + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested quote currency eg: `USD`. + QuoteCurrency string `json:"quote_currency"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // The current page of the response. + CurrentPage int `json:"current_page"` + Links PaginationLinks `json:"links"` + // List of response items. + Items []Transaction `json:"items"` +} +type PaginationLinks struct { + // URL link to the next page. + Prev *string `json:"prev,omitempty"` + // URL link to the previous page. + Next *string `json:"next,omitempty"` +} +type RecentTransactionsResponse struct { + // The requested address. + Address string `json:"address"` + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested quote currency eg: `USD`. + QuoteCurrency string `json:"quote_currency"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // The current page of the response. + CurrentPage int `json:"current_page"` + Links PaginationLinks `json:"links"` + // List of response items. + Items []Transaction `json:"items"` +} +type TransactionsTimeBucketResponse struct { + // The requested address. + Address string `json:"address"` + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested quote currency eg: `USD`. + QuoteCurrency string `json:"quote_currency"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + Complete bool `json:"complete"` + // The current bucket of the response. + CurrentBucket int `json:"current_bucket"` + Links PaginationLinks `json:"links"` + // List of response items. + Items []Transaction `json:"items"` +} +type TransactionsBlockPageResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + Links PaginationLinks `json:"links"` + // List of response items. + Items []Transaction `json:"items"` +} +type TransactionsBlockResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []Transaction `json:"items"` +} +type TransactionsSummaryResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested address. + Address string `json:"address"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []TransactionsSummary `json:"items"` +} +type TransactionsSummary struct { + // The total number of transactions. + TotalCount *int64 `json:"total_count,omitempty"` + // The earliest transaction detected. + EarliestTransaction *TransactionSummary `json:"earliest_transaction,omitempty"` + // The latest transaction detected. + LatestTransaction *TransactionSummary `json:"latest_transaction,omitempty"` + // The gas summary for the transactions. + GasSummary *GasSummary `json:"gas_summary,omitempty"` +} +type TransactionSummary struct { + // The block signed timestamp in UTC. + BlockSignedAt *time.Time `json:"block_signed_at,omitempty"` + // The requested transaction hash. + TxHash *string `json:"tx_hash,omitempty"` + // The link to the transaction details using the Covalent API. + TxDetailLink *string `json:"tx_detail_link,omitempty"` +} +type GasSummary struct { + // The total number of transactions sent by the address. + TotalSentCount *int64 `json:"total_sent_count,omitempty"` + // The total transaction fees paid by the address, denoted in wei. + TotalFeesPaid *utils.BigInt `json:"total_fees_paid,omitempty"` + // The total transaction fees paid by the address, denoted in `quote-currency`. + TotalGasQuote *float64 `json:"total_gas_quote,omitempty"` + // A prettier version of the quote for rendering purposes. + PrettyTotalGasQuote *string `json:"pretty_total_gas_quote,omitempty"` + // The average gas quote per transaction. + AverageGasQuotePerTx *float64 `json:"average_gas_quote_per_tx,omitempty"` + // A prettier version of the quote for rendering purposes. + PrettyAverageGasQuotePerTx *string `json:"pretty_average_gas_quote_per_tx,omitempty"` + // The requested chain native gas token metadata. + GasMetadata *genericmodels.ContractMetadata `json:"gas_metadata,omitempty"` +} + +type TransactionResult struct { + Transaction Transaction + Err error +} + +func (t *RecentTransactionsResponse) Prev() (*utils.Response[RecentTransactionsResponse], error) { + // implementation here + if t.Links.Prev == nil { + errorCode := 400 + errorMessage := "Invalid URL: URL link cannot be null" + return &utils.Response[RecentTransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf("Invalid URL: URL link cannot be null") + } + + if !isKeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[RecentTransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + // Create an HTTP client + client := &http.Client{} + + parsedURL, err := url.Parse(*t.Links.Prev) + if err != nil { + return nil, err + } + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[RecentTransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+clientKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if debugOutput { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(clientKey, debugOutput, 0) + + // // Read the response body + var data utils.Response[RecentTransactionsResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[RecentTransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[RecentTransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[RecentTransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[RecentTransactionsResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[RecentTransactionsResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil + +} + +func (t *RecentTransactionsResponse) Next() (*utils.Response[RecentTransactionsResponse], error) { + // implementation here + if t.Links.Next == nil { + errorCode := 400 + errorMessage := "Invalid URL: URL link cannot be null" + return &utils.Response[RecentTransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf("Invalid URL: URL link cannot be null") + } + + if !isKeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[RecentTransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + // Create an HTTP client + client := &http.Client{} + + parsedURL, err := url.Parse(*t.Links.Next) + if err != nil { + return nil, err + } + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[RecentTransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+clientKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if debugOutput { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(clientKey, debugOutput, 0) + + // // Read the response body + var data utils.Response[RecentTransactionsResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[RecentTransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[RecentTransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[RecentTransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[RecentTransactionsResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[RecentTransactionsResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil + +} + +func (t *TransactionsResponse) Prev() (*utils.Response[TransactionsResponse], error) { + // implementation here + if t.Links.Prev == nil { + errorCode := 400 + errorMessage := "Invalid URL: URL link cannot be null" + return &utils.Response[TransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf("Invalid URL: URL link cannot be null") + } + + if !isKeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[TransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + // Create an HTTP client + client := &http.Client{} + + parsedURL, err := url.Parse(*t.Links.Prev) + if err != nil { + return nil, err + } + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[TransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+clientKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if debugOutput { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(clientKey, debugOutput, 0) + + // // Read the response body + var data utils.Response[TransactionsResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[TransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[TransactionsResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[TransactionsResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil + +} + +func (t *TransactionsResponse) Next() (*utils.Response[TransactionsResponse], error) { + // implementation here + if t.Links.Next == nil { + errorCode := 400 + errorMessage := "Invalid URL: URL link cannot be null" + return &utils.Response[TransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf("Invalid URL: URL link cannot be null") + } + + if !isKeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[TransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + // Create an HTTP client + client := &http.Client{} + + parsedURL, err := url.Parse(*t.Links.Next) + if err != nil { + return nil, err + } + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[TransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+clientKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if debugOutput { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(clientKey, debugOutput, 0) + + // // Read the response body + var data utils.Response[TransactionsResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[TransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[TransactionsResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[TransactionsResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil + +} + +func (t *TransactionsTimeBucketResponse) Prev() (*utils.Response[TransactionsTimeBucketResponse], error) { + // implementation here + if t.Links.Prev == nil { + errorCode := 400 + errorMessage := "Invalid URL: URL link cannot be null" + return &utils.Response[TransactionsTimeBucketResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf("Invalid URL: URL link cannot be null") + } + + if !isKeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[TransactionsTimeBucketResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + // Create an HTTP client + client := &http.Client{} + + parsedURL, err := url.Parse(*t.Links.Prev) + if err != nil { + return nil, err + } + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[TransactionsTimeBucketResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+clientKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if debugOutput { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(clientKey, debugOutput, 0) + + // // Read the response body + var data utils.Response[TransactionsTimeBucketResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[TransactionsTimeBucketResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsTimeBucketResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsTimeBucketResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[TransactionsTimeBucketResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[TransactionsTimeBucketResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil + +} + +func (t *TransactionsTimeBucketResponse) Next() (*utils.Response[TransactionsTimeBucketResponse], error) { + // implementation here + if t.Links.Next == nil { + errorCode := 400 + errorMessage := "Invalid URL: URL link cannot be null" + return &utils.Response[TransactionsTimeBucketResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf("Invalid URL: URL link cannot be null") + } + + if !isKeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[TransactionsTimeBucketResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + // Create an HTTP client + client := &http.Client{} + + parsedURL, err := url.Parse(*t.Links.Next) + if err != nil { + return nil, err + } + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[TransactionsTimeBucketResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+clientKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if debugOutput { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(clientKey, debugOutput, 0) + + // // Read the response body + var data utils.Response[TransactionsTimeBucketResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[TransactionsTimeBucketResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsTimeBucketResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsTimeBucketResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[TransactionsTimeBucketResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[TransactionsTimeBucketResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil + +} + +func (t *TransactionsBlockPageResponse) Prev() (*utils.Response[TransactionsBlockPageResponse], error) { + // implementation here + if t.Links.Prev == nil { + errorCode := 400 + errorMessage := "Invalid URL: URL link cannot be null" + return &utils.Response[TransactionsBlockPageResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf("Invalid URL: URL link cannot be null") + } + + if !isKeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[TransactionsBlockPageResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + // Create an HTTP client + client := &http.Client{} + + parsedURL, err := url.Parse(*t.Links.Prev) + if err != nil { + return nil, err + } + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[TransactionsBlockPageResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+clientKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if debugOutput { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(clientKey, debugOutput, 0) + + // // Read the response body + var data utils.Response[TransactionsBlockPageResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[TransactionsBlockPageResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsBlockPageResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsBlockPageResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[TransactionsBlockPageResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[TransactionsBlockPageResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil + +} + +func (t *TransactionsBlockPageResponse) Next() (*utils.Response[TransactionsBlockPageResponse], error) { + // implementation here + if t.Links.Next == nil { + errorCode := 400 + errorMessage := "Invalid URL: URL link cannot be null" + return &utils.Response[TransactionsBlockPageResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf("Invalid URL: URL link cannot be null") + } + + if !isKeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[TransactionsBlockPageResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + // Create an HTTP client + client := &http.Client{} + + parsedURL, err := url.Parse(*t.Links.Next) + if err != nil { + return nil, err + } + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[TransactionsBlockPageResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+clientKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if debugOutput { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(clientKey, debugOutput, 0) + + // // Read the response body + var data utils.Response[TransactionsBlockPageResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[TransactionsBlockPageResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsBlockPageResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsBlockPageResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[TransactionsBlockPageResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[TransactionsBlockPageResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil + +} + +type GetTransactionQueryParamOpts struct { + // The currency to convert. Supports `USD`, `CAD`, `EUR`, `SGD`, `INR`, `JPY`, `VND`, `CNY`, `KRW`, `RUB`, `TRY`, `NGN`, `ARS`, `AUD`, `CHF`, and `GBP`. + QuoteCurrency *quotes.Quote `json:"quoteCurrency,omitempty"` + // Omit log events. + NoLogs *bool `json:"noLogs,omitempty"` + // Decoded DEX details including protocol (e.g. Uniswap), event (e.g 'add_liquidity') and tokens involved with historical prices. Additional 0.05 credits charged if data available. + WithDex *bool `json:"withDex,omitempty"` + // Decoded NFT sales details including marketplace (e.g. Opensea) and cached media links. Additional 0.05 credits charged if data available. + WithNftSales *bool `json:"withNftSales,omitempty"` + // Decoded lending details including protocol (e.g. Aave), event (e.g. 'deposit') and tokens involved with prices. Additional 0.05 credits charged if data available. + WithLending *bool `json:"withLending,omitempty"` + // Include safe details. + WithSafe *bool `json:"withSafe,omitempty"` +} +type GetTransactionsForAddressQueryParamOpts struct { + // The currency to convert. Supports `USD`, `CAD`, `EUR`, `SGD`, `INR`, `JPY`, `VND`, `CNY`, `KRW`, `RUB`, `TRY`, `NGN`, `ARS`, `AUD`, `CHF`, and `GBP`. + QuoteCurrency *quotes.Quote `json:"quoteCurrency,omitempty"` + // Omit log events. + NoLogs *bool `json:"noLogs,omitempty"` + // Sort the transactions in ascending chronological order. By default, it's set to `false` and returns transactions in descending chronological order. + BlockSignedAtAsc *bool `json:"blockSignedAtAsc,omitempty"` +} +type GetAllTransactionsForAddressQueryParamOpts struct { + // The currency to convert. Supports `USD`, `CAD`, `EUR`, `SGD`, `INR`, `JPY`, `VND`, `CNY`, `KRW`, `RUB`, `TRY`, `NGN`, `ARS`, `AUD`, `CHF`, and `GBP`. + QuoteCurrency *quotes.Quote `json:"quoteCurrency,omitempty"` + // Omit log events. + NoLogs *bool `json:"noLogs,omitempty"` + // Sort the transactions in ascending chronological order. By default, it's set to `false` and returns transactions in descending chronological order. + BlockSignedAtAsc *bool `json:"blockSignedAtAsc,omitempty"` + // Include safe details. + WithSafe *bool `json:"withSafe,omitempty"` +} +type GetTransactionsForAddressV3QueryParamOpts struct { + // The currency to convert. Supports `USD`, `CAD`, `EUR`, `SGD`, `INR`, `JPY`, `VND`, `CNY`, `KRW`, `RUB`, `TRY`, `NGN`, `ARS`, `AUD`, `CHF`, and `GBP`. + QuoteCurrency *quotes.Quote `json:"quoteCurrency,omitempty"` + // Omit log events. + NoLogs *bool `json:"noLogs,omitempty"` + // Sort the transactions in ascending chronological order. By default, it's set to `false` and returns transactions in descending chronological order. + BlockSignedAtAsc *bool `json:"blockSignedAtAsc,omitempty"` + // Include safe details. + WithSafe *bool `json:"withSafe,omitempty"` +} +type GetTimeBucketTransactionsForAddressQueryParamOpts struct { + // The currency to convert. Supports `USD`, `CAD`, `EUR`, `SGD`, `INR`, `JPY`, `VND`, `CNY`, `KRW`, `RUB`, `TRY`, `NGN`, `ARS`, `AUD`, `CHF`, and `GBP`. + QuoteCurrency *quotes.Quote `json:"quoteCurrency,omitempty"` + // Omit log events. + NoLogs *bool `json:"noLogs,omitempty"` + // Include safe details. + WithSafe *bool `json:"withSafe,omitempty"` +} +type GetEarliestTimeBucketTransactionsForAddressQueryParamOpts struct { + // The currency to convert. Supports `USD`, `CAD`, `EUR`, `SGD`, `INR`, `JPY`, `VND`, `CNY`, `KRW`, `RUB`, `TRY`, `NGN`, `ARS`, `AUD`, `CHF`, and `GBP`. + QuoteCurrency *quotes.Quote `json:"quoteCurrency,omitempty"` + // Omit log events. + NoLogs *bool `json:"noLogs,omitempty"` + // Include safe details. + WithSafe *bool `json:"withSafe,omitempty"` +} +type GetTransactionsForBlockByPageQueryParamOpts struct { + // The currency to convert. Supports `USD`, `CAD`, `EUR`, `SGD`, `INR`, `JPY`, `VND`, `CNY`, `KRW`, `RUB`, `TRY`, `NGN`, `ARS`, `AUD`, `CHF`, and `GBP`. + QuoteCurrency *quotes.Quote `json:"quoteCurrency,omitempty"` + // Omit log events. + NoLogs *bool `json:"noLogs,omitempty"` + // Include safe details. + WithSafe *bool `json:"withSafe,omitempty"` +} +type GetTransactionsForBlockQueryParamOpts struct { + // The currency to convert. Supports `USD`, `CAD`, `EUR`, `SGD`, `INR`, `JPY`, `VND`, `CNY`, `KRW`, `RUB`, `TRY`, `NGN`, `ARS`, `AUD`, `CHF`, and `GBP`. + QuoteCurrency *quotes.Quote `json:"quoteCurrency,omitempty"` + // Omit log events. + NoLogs *bool `json:"noLogs,omitempty"` + // Include safe details. + WithSafe *bool `json:"withSafe,omitempty"` +} +type GetTransactionsForBlockHashByPageQueryParamOpts struct { + // The currency to convert. Supports `USD`, `CAD`, `EUR`, `SGD`, `INR`, `JPY`, `VND`, `CNY`, `KRW`, `RUB`, `TRY`, `NGN`, `ARS`, `AUD`, `CHF`, and `GBP`. + QuoteCurrency *quotes.Quote `json:"quoteCurrency,omitempty"` + // Omit log events. + NoLogs *bool `json:"noLogs,omitempty"` + // Include safe details. + WithSafe *bool `json:"withSafe,omitempty"` +} +type GetTransactionsForBlockHashQueryParamOpts struct { + // The currency to convert. Supports `USD`, `CAD`, `EUR`, `SGD`, `INR`, `JPY`, `VND`, `CNY`, `KRW`, `RUB`, `TRY`, `NGN`, `ARS`, `AUD`, `CHF`, and `GBP`. + QuoteCurrency *quotes.Quote `json:"quoteCurrency,omitempty"` + // Omit log events. + NoLogs *bool `json:"noLogs,omitempty"` + // Include safe details. + WithSafe *bool `json:"withSafe,omitempty"` +} +type GetTransactionSummaryQueryParamOpts struct { + // The currency to convert. Supports `USD`, `CAD`, `EUR`, `SGD`, `INR`, `JPY`, `VND`, `CNY`, `KRW`, `RUB`, `TRY`, `NGN`, `ARS`, `AUD`, `CHF`, and `GBP`. + QuoteCurrency *quotes.Quote `json:"quoteCurrency,omitempty"` + // Include gas summary details. Additional charge of 1 credit when true. Response times may be impacted for wallets with millions of transactions. + WithGas *bool `json:"withGas,omitempty"` +} + +func NewTransactionServiceImpl(apiKey string, debug bool, threadCount int, isValidKey bool) TransactionService { + + clientKey = apiKey + debugOutput = debug + workerCount = threadCount + isKeyValid = isValidKey + + return &transactionServiceImpl{APIKey: apiKey, Debug: debug, ThreadCount: threadCount, IskeyValid: isValidKey} +} + +type TransactionService interface { + + // Commonly used to fetch and render a single transaction including its decoded log events. Additionally return semantically decoded information for DEX trades, lending and NFT sales. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // txHash: The transaction hash.. Type: string + GetTransaction(chainName chains.Chain, txHash string, queryParamOpts ...GetTransactionQueryParamOpts) (*utils.Response[TransactionResponse], error) + + // Commonly used to fetch and render the most recent transactions involving an address. Frequently seen in wallet applications. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // walletAddress: The requested address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetAllTransactionsForAddress(chainName chains.Chain, walletAddress string, queryParamOpts ...GetAllTransactionsForAddressQueryParamOpts) <-chan TransactionResult + + // Commonly used to fetch and render the most recent transactions involving an address. Frequently seen in wallet applications. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // walletAddress: The requested address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetAllTransactionsForAddressByPage(chainName chains.Chain, walletAddress string, queryParamOpts ...GetAllTransactionsForAddressQueryParamOpts) (*utils.Response[RecentTransactionsResponse], error) + + // Commonly used to fetch the transactions involving an address including the decoded log events in a paginated fashion. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // walletAddress: The requested address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + // page: The requested page, 0-indexed.. Type: int + GetTransactionsForAddressV3(chainName chains.Chain, walletAddress string, page int, queryParamOpts ...GetTransactionsForAddressV3QueryParamOpts) (*utils.Response[TransactionsResponse], error) + + // Commonly used to fetch all transactions including their decoded log events in a 15-minute time bucket interval. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // walletAddress: The requested address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + // timeBucket: The 0-indexed 15-minute time bucket. E.g. 27 Feb 2023 05:23 GMT = 1677475383 (Unix time). 1677475383/900=1863861 timeBucket.. Type: int + GetTimeBucketTransactionsForAddress(chainName chains.Chain, walletAddress string, timeBucket int, queryParamOpts ...GetTimeBucketTransactionsForAddressQueryParamOpts) (*utils.Response[TransactionsTimeBucketResponse], error) + + // Commonly used to fetch all transactions including their decoded log events in a block and further flag interesting wallets or transactions. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // blockHeight: The requested block height.. Type: string + GetTransactionsForBlock(chainName chains.Chain, blockHeight string, queryParamOpts ...GetTransactionsForBlockQueryParamOpts) (*utils.Response[TransactionsBlockResponse], error) + + // undefined + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // blockHash: The requested block hash.. Type: string + // page: The requested 0-indexed page number.. Type: int + GetTransactionsForBlockHashByPage(chainName chains.Chain, blockHash string, page int, queryParamOpts ...GetTransactionsForBlockHashByPageQueryParamOpts) (*utils.Response[TransactionsBlockPageResponse], error) + + // Commonly used to fetch all transactions including their decoded log events in a block and further flag interesting wallets or transactions. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // blockHash: The requested block hash.. Type: string + GetTransactionsForBlockHash(chainName chains.Chain, blockHash string, queryParamOpts ...GetTransactionsForBlockHashQueryParamOpts) (*utils.Response[TransactionsBlockResponse], error) + + // Commonly used to fetch the earliest and latest transactions, and the transaction count for a wallet. Calculate the age of the wallet and the time it has been idle and quickly gain insights into their engagement with web3. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // walletAddress: The requested address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetTransactionSummary(chainName chains.Chain, walletAddress string, queryParamOpts ...GetTransactionSummaryQueryParamOpts) (*utils.Response[TransactionsSummaryResponse], error) +} + +type transactionServiceImpl struct { + APIKey string + Debug bool + ThreadCount int + IskeyValid bool +} + +func (s *transactionServiceImpl) GetTransaction(chainName chains.Chain, txHash string, queryParamOpts ...GetTransactionQueryParamOpts) (*utils.Response[TransactionResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/transaction_v2/%s/", chainName, txHash) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[TransactionResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.QuoteCurrency != nil { + params.Add("quote-currency", fmt.Sprintf("%v", *opts.QuoteCurrency)) + } + + if opts.NoLogs != nil { + params.Add("no-logs", fmt.Sprintf("%v", *opts.NoLogs)) + } + + if opts.WithDex != nil { + params.Add("with-dex", fmt.Sprintf("%v", *opts.WithDex)) + } + + if opts.WithNftSales != nil { + params.Add("with-nft-sales", fmt.Sprintf("%v", *opts.WithNftSales)) + } + + if opts.WithLending != nil { + params.Add("with-lending", fmt.Sprintf("%v", *opts.WithLending)) + } + + if opts.WithSafe != nil { + params.Add("with-safe", fmt.Sprintf("%v", *opts.WithSafe)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[TransactionResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[TransactionResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[TransactionResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[TransactionResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[TransactionResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *transactionServiceImpl) GetAllTransactionsForAddress(chainName chains.Chain, walletAddress string, queryParamOpts ...GetAllTransactionsForAddressQueryParamOpts) <-chan TransactionResult { + transactionChannel := make(chan TransactionResult) + + go func() { + defer close(transactionChannel) + + hasNext := true + + if !s.IskeyValid { + transactionChannel <- TransactionResult{Err: fmt.Errorf(`An error occurred 401: ` + utils.InvalidAPIKeyMessage)} + return + } + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/address/%s/transactions_v3/", chainName, walletAddress) + + // Parse the formatted URL + parsedURL, err := url.Parse(apiURL) + if err != nil { + transactionChannel <- TransactionResult{Err: err} + return + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.QuoteCurrency != nil { + params.Add("quote-currency", fmt.Sprintf("%v", *opts.QuoteCurrency)) + } + + if opts.NoLogs != nil { + params.Add("no-logs", fmt.Sprintf("%v", *opts.NoLogs)) + } + + if opts.BlockSignedAtAsc != nil { + params.Add("block-signed-at-asc", fmt.Sprintf("%v", *opts.BlockSignedAtAsc)) + } + + if opts.WithSafe != nil { + params.Add("with-safe", fmt.Sprintf("%v", *opts.WithSafe)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + var data utils.Response[RecentTransactionsResponse] + for hasNext { + + res, err := utils.PaginateEndpointUsingLinks(apiURL, s.APIKey, params, s.Debug, s.ThreadCount) + if err != nil { + transactionChannel <- TransactionResult{Err: err} + hasNext = false + return + } + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + transactionChannel <- TransactionResult{Err: err} + res.Body.Close() + hasNext = false + return + } + res.Body.Close() // Ensure the body is closed after processing it + + if data.Error { + var errorMessage string + if data.ErrorMessage != nil { + errorMessage = *data.ErrorMessage + } else { + errorMessage = "default error message" // Provide a default or handle differently + } + transactionChannel <- TransactionResult{Err: errors.New("An error occurred " + strconv.Itoa(*data.ErrorCode) + ": " + errorMessage)} + return + } + + for _, item := range data.Data.Items { + transactionChannel <- TransactionResult{Transaction: item, Err: err} + } + + if data.Data.Links.Prev == nil { + hasNext = false + } else { + // Dereference Prev pointer to get its string value + apiURL = *data.Data.Links.Prev + } + } + + }() + return transactionChannel +} + +func (s *transactionServiceImpl) GetAllTransactionsForAddressByPage(chainName chains.Chain, walletAddress string, queryParamOpts ...GetAllTransactionsForAddressQueryParamOpts) (*utils.Response[RecentTransactionsResponse], error) { + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/address/%s/transactions_v3/", chainName, walletAddress) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[RecentTransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.QuoteCurrency != nil { + params.Add("quote-currency", fmt.Sprintf("%v", *opts.QuoteCurrency)) + } + + if opts.NoLogs != nil { + params.Add("no-logs", fmt.Sprintf("%v", *opts.NoLogs)) + } + + if opts.BlockSignedAtAsc != nil { + params.Add("block-signed-at-asc", fmt.Sprintf("%v", *opts.BlockSignedAtAsc)) + } + + if opts.WithSafe != nil { + params.Add("with-safe", fmt.Sprintf("%v", *opts.WithSafe)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[RecentTransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[RecentTransactionsResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[RecentTransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[RecentTransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[RecentTransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[RecentTransactionsResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[RecentTransactionsResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *transactionServiceImpl) GetTransactionsForAddressV3(chainName chains.Chain, walletAddress string, page int, queryParamOpts ...GetTransactionsForAddressV3QueryParamOpts) (*utils.Response[TransactionsResponse], error) { + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/address/%s/transactions_v3/page/%d/", chainName, walletAddress, page) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[TransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.QuoteCurrency != nil { + params.Add("quote-currency", fmt.Sprintf("%v", *opts.QuoteCurrency)) + } + + if opts.NoLogs != nil { + params.Add("no-logs", fmt.Sprintf("%v", *opts.NoLogs)) + } + + if opts.BlockSignedAtAsc != nil { + params.Add("block-signed-at-asc", fmt.Sprintf("%v", *opts.BlockSignedAtAsc)) + } + + if opts.WithSafe != nil { + params.Add("with-safe", fmt.Sprintf("%v", *opts.WithSafe)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[TransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[TransactionsResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[TransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[TransactionsResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[TransactionsResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *transactionServiceImpl) GetTimeBucketTransactionsForAddress(chainName chains.Chain, walletAddress string, timeBucket int, queryParamOpts ...GetTimeBucketTransactionsForAddressQueryParamOpts) (*utils.Response[TransactionsTimeBucketResponse], error) { + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/bulk/transactions/%s/%d/", chainName, walletAddress, timeBucket) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[TransactionsTimeBucketResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.QuoteCurrency != nil { + params.Add("quote-currency", fmt.Sprintf("%v", *opts.QuoteCurrency)) + } + + if opts.NoLogs != nil { + params.Add("no-logs", fmt.Sprintf("%v", *opts.NoLogs)) + } + + if opts.WithSafe != nil { + params.Add("with-safe", fmt.Sprintf("%v", *opts.WithSafe)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[TransactionsTimeBucketResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[TransactionsTimeBucketResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[TransactionsTimeBucketResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsTimeBucketResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsTimeBucketResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[TransactionsTimeBucketResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[TransactionsTimeBucketResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *transactionServiceImpl) GetTransactionsForBlock(chainName chains.Chain, blockHeight string, queryParamOpts ...GetTransactionsForBlockQueryParamOpts) (*utils.Response[TransactionsBlockResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/block/%s/transactions_v3/", chainName, blockHeight) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[TransactionsBlockResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.QuoteCurrency != nil { + params.Add("quote-currency", fmt.Sprintf("%v", *opts.QuoteCurrency)) + } + + if opts.NoLogs != nil { + params.Add("no-logs", fmt.Sprintf("%v", *opts.NoLogs)) + } + + if opts.WithSafe != nil { + params.Add("with-safe", fmt.Sprintf("%v", *opts.WithSafe)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[TransactionsBlockResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[TransactionsBlockResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[TransactionsBlockResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsBlockResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsBlockResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[TransactionsBlockResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[TransactionsBlockResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *transactionServiceImpl) GetTransactionsForBlockHashByPage(chainName chains.Chain, blockHash string, page int, queryParamOpts ...GetTransactionsForBlockHashByPageQueryParamOpts) (*utils.Response[TransactionsBlockPageResponse], error) { + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/block_hash/%s/transactions_v3/page/%d/", chainName, blockHash, page) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[TransactionsBlockPageResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.QuoteCurrency != nil { + params.Add("quote-currency", fmt.Sprintf("%v", *opts.QuoteCurrency)) + } + + if opts.NoLogs != nil { + params.Add("no-logs", fmt.Sprintf("%v", *opts.NoLogs)) + } + + if opts.WithSafe != nil { + params.Add("with-safe", fmt.Sprintf("%v", *opts.WithSafe)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[TransactionsBlockPageResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[TransactionsBlockPageResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[TransactionsBlockPageResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsBlockPageResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsBlockPageResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[TransactionsBlockPageResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[TransactionsBlockPageResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *transactionServiceImpl) GetTransactionsForBlockHash(chainName chains.Chain, blockHash string, queryParamOpts ...GetTransactionsForBlockHashQueryParamOpts) (*utils.Response[TransactionsBlockResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/block_hash/%s/transactions_v3/", chainName, blockHash) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[TransactionsBlockResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.QuoteCurrency != nil { + params.Add("quote-currency", fmt.Sprintf("%v", *opts.QuoteCurrency)) + } + + if opts.NoLogs != nil { + params.Add("no-logs", fmt.Sprintf("%v", *opts.NoLogs)) + } + + if opts.WithSafe != nil { + params.Add("with-safe", fmt.Sprintf("%v", *opts.WithSafe)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[TransactionsBlockResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[TransactionsBlockResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[TransactionsBlockResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsBlockResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsBlockResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[TransactionsBlockResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[TransactionsBlockResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *transactionServiceImpl) GetTransactionSummary(chainName chains.Chain, walletAddress string, queryParamOpts ...GetTransactionSummaryQueryParamOpts) (*utils.Response[TransactionsSummaryResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/address/%s/transactions_summary/", chainName, walletAddress) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[TransactionsSummaryResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.QuoteCurrency != nil { + params.Add("quote-currency", fmt.Sprintf("%v", *opts.QuoteCurrency)) + } + + if opts.WithGas != nil { + params.Add("with-gas", fmt.Sprintf("%v", *opts.WithGas)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[TransactionsSummaryResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[TransactionsSummaryResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[TransactionsSummaryResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsSummaryResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsSummaryResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[TransactionsSummaryResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[TransactionsSummaryResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} diff --git a/services/xyk_service.go b/services/xyk_service.go new file mode 100644 index 0000000..6926695 --- /dev/null +++ b/services/xyk_service.go @@ -0,0 +1,2384 @@ +package services + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "time" + + "github.com/covalenthq/covalent-api-sdk-go/chains" + "github.com/covalenthq/covalent-api-sdk-go/genericmodels" + "github.com/covalenthq/covalent-api-sdk-go/quotes" + "github.com/covalenthq/covalent-api-sdk-go/utils" +) + +type PoolResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []Pool `json:"items"` + // Pagination metadata. + Pagination genericmodels.Pagination `json:"pagination"` +} +type Pool struct { + // The pair address. + Exchange *string `json:"exchange,omitempty"` + SwapCount24h *int64 `json:"swap_count_24h,omitempty"` + // The total liquidity converted to fiat in `quote-currency`. + TotalLiquidityQuote *float64 `json:"total_liquidity_quote,omitempty"` + Volume24hQuote *float64 `json:"volume_24h_quote,omitempty"` + Fee24hQuote *float64 `json:"fee_24h_quote,omitempty"` + // Total supply of this pool token. + TotalSupply *utils.BigInt `json:"total_supply,omitempty"` + // The exchange rate for the requested quote currency. + QuoteRate *float64 `json:"quote_rate,omitempty"` + // A prettier version of the total liquidity quote for rendering purposes. + PrettyTotalLiquidityQuote *string `json:"pretty_total_liquidity_quote,omitempty"` + // A prettier version of the volume 24h quote for rendering purposes. + PrettyVolume24hQuote *string `json:"pretty_volume_24h_quote,omitempty"` + // A prettier version of the fee 24h quote for rendering purposes. + PrettyFee24hQuote *string `json:"pretty_fee_24h_quote,omitempty"` + // A prettier version of the volume 7d quote for rendering purposes. + PrettyVolume7dQuote *string `json:"pretty_volume_7d_quote,omitempty"` + // The requested chain name eg: `eth-mainnet`. + ChainName *string `json:"chain_name,omitempty"` + // The requested chain ID eg: `1`. + ChainId *string `json:"chain_id,omitempty"` + // The name of the DEX, eg: `uniswap_v2`. + DexName *string `json:"dex_name,omitempty"` + Volume7dQuote *float64 `json:"volume_7d_quote,omitempty"` + AnnualizedFee *float64 `json:"annualized_fee,omitempty"` + Token0 *Token `json:"token_0,omitempty"` + Token1 *Token `json:"token_1,omitempty"` +} +type Token struct { + // Use the relevant `contract_address` to lookup prices, logos, token transfers, etc. + ContractAddress *string `json:"contract_address,omitempty"` + // The string returned by the `name()` method. + ContractName *string `json:"contract_name,omitempty"` + VolumeIn24h *string `json:"volume_in_24h,omitempty"` + VolumeOut24h *string `json:"volume_out_24h,omitempty"` + // The exchange rate for the requested quote currency. + QuoteRate *float64 `json:"quote_rate,omitempty"` + Reserve *string `json:"reserve,omitempty"` + // The contract logo URL. + LogoUrl *string `json:"logo_url,omitempty"` + // The ticker symbol for this contract. This field is set by a developer and non-unique across a network. + ContractTickerSymbol *string `json:"contract_ticker_symbol,omitempty"` + // Use contract decimals to format the token balance for display purposes - divide the balance by `10^{contract_decimals}`. + ContractDecimals *int `json:"contract_decimals,omitempty"` + VolumeIn7d *string `json:"volume_in_7d,omitempty"` + VolumeOut7d *string `json:"volume_out_7d,omitempty"` +} +type PoolToDexResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested address. + Address string `json:"address"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []PoolToDexItem `json:"items"` +} +type PoolToDexItem struct { + SupportedDex + // The dex logo URL. + LogoUrl *string `json:"logo_url,omitempty"` +} +type SupportedDex struct { + // The requested chain ID eg: `1`. + ChainId *string `json:"chain_id,omitempty"` + // The requested chain name eg: `eth-mainnet`. + ChainName *string `json:"chain_name,omitempty"` + // The name of the DEX, eg: `uniswap_v2`. + DexName *string `json:"dex_name,omitempty"` + // A display-friendly name for the dex. + DisplayName *string `json:"display_name,omitempty"` + // The dex logo URL. + LogoUrl *string `json:"logo_url,omitempty"` + FactoryContractAddress *string `json:"factory_contract_address,omitempty"` + RouterContractAddresses *[]string `json:"router_contract_addresses,omitempty"` + SwapFee *float64 `json:"swap_fee,omitempty"` +} +type PoolByAddressResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []PoolWithTimeseries `json:"items"` + // Pagination metadata. + Pagination genericmodels.Pagination `json:"pagination"` +} +type PoolWithTimeseries struct { + // The pair address. + Exchange *string `json:"exchange,omitempty"` + // A list of explorers for this address. + Explorers *[]genericmodels.Explorer `json:"explorers,omitempty"` + SwapCount24h *int64 `json:"swap_count_24h,omitempty"` + // The total liquidity converted to fiat in `quote-currency`. + TotalLiquidityQuote *float64 `json:"total_liquidity_quote,omitempty"` + Volume24hQuote *float64 `json:"volume_24h_quote,omitempty"` + Fee24hQuote *float64 `json:"fee_24h_quote,omitempty"` + // Total supply of this pool token. + TotalSupply *utils.BigInt `json:"total_supply,omitempty"` + // The exchange rate for the requested quote currency. + QuoteRate *float64 `json:"quote_rate,omitempty"` + // The requested chain ID eg: `1`. + ChainId *string `json:"chain_id,omitempty"` + // The name of the DEX, eg: `uniswap_v2`. + DexName *string `json:"dex_name,omitempty"` + Volume7dQuote *float64 `json:"volume_7d_quote,omitempty"` + AnnualizedFee *float64 `json:"annualized_fee,omitempty"` + // A prettier version of the total liquidity quote for rendering purposes. + PrettyTotalLiquidityQuote *string `json:"pretty_total_liquidity_quote,omitempty"` + // A prettier version of the volume 24h quote for rendering purposes. + PrettyVolume24hQuote *string `json:"pretty_volume_24h_quote,omitempty"` + // A prettier version of the fee 24h quote for rendering purposes. + PrettyFee24hQuote *string `json:"pretty_fee_24h_quote,omitempty"` + // A prettier version of the volume 7d quote for rendering purposes. + PrettyVolume7dQuote *string `json:"pretty_volume_7d_quote,omitempty"` + Token0 *Token `json:"token_0,omitempty"` + Token1 *Token `json:"token_1,omitempty"` + Token0ReserveQuote *float64 `json:"token_0_reserve_quote,omitempty"` + Token1ReserveQuote *float64 `json:"token_1_reserve_quote,omitempty"` + VolumeTimeseries7d *[]VolumeTimeseries `json:"volume_timeseries_7d,omitempty"` + VolumeTimeseries30d *[]VolumeTimeseries `json:"volume_timeseries_30d,omitempty"` + LiquidityTimeseries7d *[]LiquidityTimeseries `json:"liquidity_timeseries_7d,omitempty"` + LiquidityTimeseries30d *[]LiquidityTimeseries `json:"liquidity_timeseries_30d,omitempty"` + PriceTimeseries7d *[]PriceTimeseries `json:"price_timeseries_7d,omitempty"` + PriceTimeseries30d *[]PriceTimeseries `json:"price_timeseries_30d,omitempty"` +} +type VolumeTimeseries struct { + // The name of the DEX, eg: `uniswap_v2`. + DexName *string `json:"dex_name,omitempty"` + // The requested chain ID eg: `1`. + ChainId *string `json:"chain_id,omitempty"` + Dt *time.Time `json:"dt,omitempty"` + // The pair address. + Exchange *string `json:"exchange,omitempty"` + SumAmount0in *string `json:"sum_amount0in,omitempty"` + SumAmount0out *string `json:"sum_amount0out,omitempty"` + SumAmount1in *string `json:"sum_amount1in,omitempty"` + SumAmount1out *string `json:"sum_amount1out,omitempty"` + VolumeQuote *float64 `json:"volume_quote,omitempty"` + // A prettier version of the volume quote for rendering purposes. + PrettyVolumeQuote *string `json:"pretty_volume_quote,omitempty"` + Token0QuoteRate *float64 `json:"token_0_quote_rate,omitempty"` + Token1QuoteRate *float64 `json:"token_1_quote_rate,omitempty"` + SwapCount24 *int64 `json:"swap_count_24,omitempty"` +} +type LiquidityTimeseries struct { + // The name of the DEX, eg: `uniswap_v2`. + DexName *string `json:"dex_name,omitempty"` + // The requested chain ID eg: `1`. + ChainId *string `json:"chain_id,omitempty"` + Dt *time.Time `json:"dt,omitempty"` + // The pair address. + Exchange *string `json:"exchange,omitempty"` + R0C *string `json:"r0_c,omitempty"` + R1C *string `json:"r1_c,omitempty"` + LiquidityQuote *float64 `json:"liquidity_quote,omitempty"` + // A prettier version of the liquidity quote for rendering purposes. + PrettyLiquidityQuote *string `json:"pretty_liquidity_quote,omitempty"` + Token0QuoteRate *float64 `json:"token_0_quote_rate,omitempty"` + Token1QuoteRate *float64 `json:"token_1_quote_rate,omitempty"` +} +type PriceTimeseries struct { + // The name of the DEX, eg: `uniswap_v2`. + DexName *string `json:"dex_name,omitempty"` + // The requested chain ID eg: `1`. + ChainId *string `json:"chain_id,omitempty"` + Dt *time.Time `json:"dt,omitempty"` + // The pair address. + Exchange *string `json:"exchange,omitempty"` + PriceOfToken0InToken1 *float64 `json:"price_of_token0_in_token1,omitempty"` + // A prettier version of the price token0 for rendering purposes. + PrettyPriceOfToken0InToken1 *string `json:"pretty_price_of_token0_in_token1,omitempty"` + PriceOfToken0InToken1Description *string `json:"price_of_token0_in_token1_description,omitempty"` + PriceOfToken1InToken0 *float64 `json:"price_of_token1_in_token0,omitempty"` + // A prettier version of the price token1 for rendering purposes. + PrettyPriceOfToken1InToken0 *string `json:"pretty_price_of_token1_in_token0,omitempty"` + PriceOfToken1InToken0Description *string `json:"price_of_token1_in_token0_description,omitempty"` + // The requested quote currency eg: `USD`. + QuoteCurrency *string `json:"quote_currency,omitempty"` + PriceOfToken0InQuoteCurrency *float64 `json:"price_of_token0_in_quote_currency,omitempty"` + PriceOfToken1InQuoteCurrency *float64 `json:"price_of_token1_in_quote_currency,omitempty"` +} +type PoolsDexDataResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested address. + Address string `json:"address"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // The requested quote currency eg: `USD`. + QuoteCurrency string `json:"quote_currency"` + // List of response items. + Items []PoolsDexDataItem `json:"items"` + // Pagination metadata. + Pagination genericmodels.Pagination `json:"pagination"` +} +type PoolsDexDataItem struct { + // The name of the DEX, eg: `uniswap_v2`. + DexName *string `json:"dex_name,omitempty"` + // The pair address. + Exchange *string `json:"exchange,omitempty"` + // The combined ticker symbol of token0 and token1 separated with a hypen. + ExchangeTickerSymbol *string `json:"exchange_ticker_symbol,omitempty"` + // The dex logo URL for the pair address. + ExchangeLogoUrl *string `json:"exchange_logo_url,omitempty"` + // The list of explorers for the token address. + Explorers *[]genericmodels.Explorer `json:"explorers,omitempty"` + // The total liquidity converted to fiat in `quote-currency`. + TotalLiquidityQuote *float64 `json:"total_liquidity_quote,omitempty"` + // A prettier version of the total liquidity quote for rendering purposes. + PrettyTotalLiquidityQuote *string `json:"pretty_total_liquidity_quote,omitempty"` + // The volume 24h converted to fiat in `quote-currency`. + Volume24hQuote *float64 `json:"volume_24h_quote,omitempty"` + // The volume 7d converted to fiat in `quote-currency`. + Volume7dQuote *float64 `json:"volume_7d_quote,omitempty"` + // The fee 24h converted to fiat in `quote-currency`. + Fee24hQuote *float64 `json:"fee_24h_quote,omitempty"` + // The exchange rate for the requested quote currency. + QuoteRate *float64 `json:"quote_rate,omitempty"` + // A prettier version of the quote rate for rendering purposes. + PrettyQuoteRate *string `json:"pretty_quote_rate,omitempty"` + // The annual fee percentage. + AnnualizedFee *float64 `json:"annualized_fee,omitempty"` + // A prettier version of the volume 24h quote for rendering purposes. + PrettyVolume24hQuote *string `json:"pretty_volume_24h_quote,omitempty"` + // A prettier version of the volume 7d quote for rendering purposes. + PrettyVolume7dQuote *string `json:"pretty_volume_7d_quote,omitempty"` + // A prettier version of the fee 24h quote for rendering purposes. + PrettyFee24hQuote *string `json:"pretty_fee_24h_quote,omitempty"` + // Token0's contract metadata and reserve data. + Token0 *PoolsDexToken `json:"token_0,omitempty"` + // Token1's contract metadata and reserve data. + Token1 *PoolsDexToken `json:"token_1,omitempty"` +} +type PoolsDexToken struct { + // The reserves for the token. + Reserve *string `json:"reserve,omitempty"` + // The string returned by the `name()` method. + ContractName *string `json:"contract_name,omitempty"` + // Use contract decimals to format the token balance for display purposes - divide the balance by `10^{contract_decimals}`. + ContractDecimals *int `json:"contract_decimals,omitempty"` + // The ticker symbol for this contract. This field is set by a developer and non-unique across a network. + ContractTickerSymbol *string `json:"contract_ticker_symbol,omitempty"` + // Use the relevant `contract_address` to lookup prices, logos, token transfers, etc. + ContractAddress *string `json:"contract_address,omitempty"` + // The contract logo URL. + LogoUrl *string `json:"logo_url,omitempty"` + // The exchange rate for the requested quote currency. + QuoteRate *float64 `json:"quote_rate,omitempty"` +} +type AddressExchangeBalancesResponse struct { + // The requested address. + Address string `json:"address"` + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []UniswapLikeBalanceItem `json:"items"` +} +type UniswapLikeBalanceItem struct { + Token0 *UniswapLikeToken `json:"token_0,omitempty"` + Token1 *UniswapLikeToken `json:"token_1,omitempty"` + PoolToken *UniswapLikeTokenWithSupply `json:"pool_token,omitempty"` +} +type UniswapLikeToken struct { + // Use contract decimals to format the token balance for display purposes - divide the balance by `10^{contract_decimals}`. + ContractDecimals *int `json:"contract_decimals,omitempty"` + // The ticker symbol for this contract. This field is set by a developer and non-unique across a network. + ContractTickerSymbol *string `json:"contract_ticker_symbol,omitempty"` + // Use the relevant `contract_address` to lookup prices, logos, token transfers, etc. + ContractAddress *string `json:"contract_address,omitempty"` + // The contract logo URL. + LogoUrl *string `json:"logo_url,omitempty"` + // The asset balance. Use `contract_decimals` to scale this balance for display purposes. + Balance *utils.BigInt `json:"balance,omitempty"` + Quote *float64 `json:"quote,omitempty"` + // A prettier version of the quote for rendering purposes. + PrettyQuote *string `json:"pretty_quote,omitempty"` + // The exchange rate for the requested quote currency. + QuoteRate *float64 `json:"quote_rate,omitempty"` +} +type UniswapLikeTokenWithSupply struct { + // Use contract decimals to format the token balance for display purposes - divide the balance by `10^{contract_decimals}`. + ContractDecimals *int `json:"contract_decimals,omitempty"` + // The ticker symbol for this contract. This field is set by a developer and non-unique across a network. + ContractTickerSymbol *string `json:"contract_ticker_symbol,omitempty"` + // Use the relevant `contract_address` to lookup prices, logos, token transfers, etc. + ContractAddress *string `json:"contract_address,omitempty"` + // The contract logo URL. + LogoUrl *string `json:"logo_url,omitempty"` + // The asset balance. Use `contract_decimals` to scale this balance for display purposes. + Balance *utils.BigInt `json:"balance,omitempty"` + Quote *float64 `json:"quote,omitempty"` + // A prettier version of the quote for rendering purposes. + PrettyQuote *string `json:"pretty_quote,omitempty"` + // The exchange rate for the requested quote currency. + QuoteRate *float64 `json:"quote_rate,omitempty"` + // Total supply of this pool token. + TotalSupply *utils.BigInt `json:"total_supply,omitempty"` +} +type NetworkExchangeTokensResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []TokenV2Volume `json:"items"` + // Pagination metadata. + Pagination genericmodels.Pagination `json:"pagination"` +} +type TokenV2Volume struct { + // The requested chain name eg: `eth-mainnet`. + ChainName *string `json:"chain_name,omitempty"` + // The requested chain ID eg: `1`. + ChainId *string `json:"chain_id,omitempty"` + // The name of the DEX, eg: `uniswap_v2`. + DexName *string `json:"dex_name,omitempty"` + // Use the relevant `contract_address` to lookup prices, logos, token transfers, etc. + ContractAddress *string `json:"contract_address,omitempty"` + // The string returned by the `name()` method. + ContractName *string `json:"contract_name,omitempty"` + TotalLiquidity *string `json:"total_liquidity,omitempty"` + TotalVolume24h *string `json:"total_volume_24h,omitempty"` + // The contract logo URL. + LogoUrl *string `json:"logo_url,omitempty"` + // The ticker symbol for this contract. This field is set by a developer and non-unique across a network. + ContractTickerSymbol *string `json:"contract_ticker_symbol,omitempty"` + // Use contract decimals to format the token balance for display purposes - divide the balance by `10^{contract_decimals}`. + ContractDecimals *int `json:"contract_decimals,omitempty"` + SwapCount24h *int64 `json:"swap_count_24h,omitempty"` + // The list of explorers for the token address. + Explorers *[]genericmodels.Explorer `json:"explorers,omitempty"` + // The exchange rate for the requested quote currency. + QuoteRate *float64 `json:"quote_rate,omitempty"` + // The 24h exchange rate for the requested quote currency. + QuoteRate24h *float64 `json:"quote_rate_24h,omitempty"` + // A prettier version of the exchange rate for rendering purposes. + PrettyQuoteRate *string `json:"pretty_quote_rate,omitempty"` + // A prettier version of the 24h exchange rate for rendering purposes. + PrettyQuoteRate24h *string `json:"pretty_quote_rate_24h,omitempty"` + // A prettier version of the total liquidity quote for rendering purposes. + PrettyTotalLiquidityQuote *string `json:"pretty_total_liquidity_quote,omitempty"` + // A prettier version of the 24h volume quote for rendering purposes. + PrettyTotalVolume24hQuote *string `json:"pretty_total_volume_24h_quote,omitempty"` + // The total liquidity converted to fiat in `quote-currency`. + TotalLiquidityQuote *float64 `json:"total_liquidity_quote,omitempty"` + // The total volume 24h converted to fiat in `quote-currency`. + TotalVolume24hQuote *float64 `json:"total_volume_24h_quote,omitempty"` +} +type NetworkExchangeTokenViewResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []TokenV2VolumeWithChartData `json:"items"` + // Pagination metadata. + Pagination genericmodels.Pagination `json:"pagination"` +} +type TokenV2VolumeWithChartData struct { + // The requested chain name eg: `eth-mainnet`. + ChainName *string `json:"chain_name,omitempty"` + // The requested chain ID eg: `1`. + ChainId *string `json:"chain_id,omitempty"` + // The name of the DEX, eg: `uniswap_v2`. + DexName *string `json:"dex_name,omitempty"` + // Use the relevant `contract_address` to lookup prices, logos, token transfers, etc. + ContractAddress *string `json:"contract_address,omitempty"` + // The string returned by the `name()` method. + ContractName *string `json:"contract_name,omitempty"` + // A list of explorers for this address. + Explorers *[]genericmodels.Explorer `json:"explorers,omitempty"` + // The total liquidity unscaled value. + TotalLiquidity *string `json:"total_liquidity,omitempty"` + // The total volume 24h unscaled value. + TotalVolume24h *string `json:"total_volume_24h,omitempty"` + // The contract logo URL. + LogoUrl *string `json:"logo_url,omitempty"` + // The ticker symbol for this contract. This field is set by a developer and non-unique across a network. + ContractTickerSymbol *string `json:"contract_ticker_symbol,omitempty"` + // Use contract decimals to format the token balance for display purposes - divide the balance by `10^{contract_decimals}`. + ContractDecimals *int `json:"contract_decimals,omitempty"` + // The total amount of swaps in the last 24h. + SwapCount24h *int64 `json:"swap_count_24h,omitempty"` + // The exchange rate for the requested quote currency. + QuoteRate *float64 `json:"quote_rate,omitempty"` + // The 24h exchange rate for the requested quote currency. + QuoteRate24h *float64 `json:"quote_rate_24h,omitempty"` + // A prettier version of the exchange rate for rendering purposes. + PrettyQuoteRate *string `json:"pretty_quote_rate,omitempty"` + // A prettier version of the 24h exchange rate for rendering purposes. + PrettyQuoteRate24h *string `json:"pretty_quote_rate_24h,omitempty"` + // A prettier version of the total liquidity quote for rendering purposes. + PrettyTotalLiquidityQuote *string `json:"pretty_total_liquidity_quote,omitempty"` + // A prettier version of the 24h volume quote for rendering purposes. + PrettyTotalVolume24hQuote *string `json:"pretty_total_volume_24h_quote,omitempty"` + // The total liquidity converted to fiat in `quote-currency`. + TotalLiquidityQuote *float64 `json:"total_liquidity_quote,omitempty"` + // The total volume 24h converted to fiat in `quote-currency`. + TotalVolume24hQuote *float64 `json:"total_volume_24h_quote,omitempty"` + // The number of transactions in the last 24h. + Transactions24h *int64 `json:"transactions_24h,omitempty"` + VolumeTimeseries7d *[]VolumeTokenTimeseries `json:"volume_timeseries_7d,omitempty"` + VolumeTimeseries30d *[]VolumeTokenTimeseries `json:"volume_timeseries_30d,omitempty"` + LiquidityTimeseries7d *[]LiquidityTokenTimeseries `json:"liquidity_timeseries_7d,omitempty"` + LiquidityTimeseries30d *[]LiquidityTokenTimeseries `json:"liquidity_timeseries_30d,omitempty"` + PriceTimeseries7d *[]PriceTokenTimeseries `json:"price_timeseries_7d,omitempty"` + PriceTimeseries30d *[]PriceTokenTimeseries `json:"price_timeseries_30d,omitempty"` +} +type VolumeTokenTimeseries struct { + // The name of the DEX, eg: `uniswap_v2`. + DexName *string `json:"dex_name,omitempty"` + // The requested chain ID eg: `1`. + ChainId *string `json:"chain_id,omitempty"` + // The current date. + Dt *time.Time `json:"dt,omitempty"` + // The total volume unscaled for this day. + TotalVolume *string `json:"total_volume,omitempty"` + // The volume in `quote-currency` denomination. + VolumeQuote *float64 `json:"volume_quote,omitempty"` + // A prettier version of the volume quote for rendering purposes. + PrettyVolumeQuote *string `json:"pretty_volume_quote,omitempty"` +} +type LiquidityTokenTimeseries struct { + // The name of the DEX, eg: `uniswap_v2`. + DexName *string `json:"dex_name,omitempty"` + // The requested chain ID eg: `1`. + ChainId *string `json:"chain_id,omitempty"` + // The current date. + Dt *time.Time `json:"dt,omitempty"` + // The total liquidity unscaled up to this day. + TotalLiquidity *string `json:"total_liquidity,omitempty"` + // The liquidity in `quote-currency` denomination. + LiquidityQuote *float64 `json:"liquidity_quote,omitempty"` + // A prettier version of the liquidity quote for rendering purposes. + PrettyLiquidityQuote *string `json:"pretty_liquidity_quote,omitempty"` +} +type PriceTokenTimeseries struct { + // The name of the DEX, eg: `uniswap_v2`. + DexName *string `json:"dex_name,omitempty"` + // The requested chain ID eg: `1`. + ChainId *string `json:"chain_id,omitempty"` + // The current date. + Dt *time.Time `json:"dt,omitempty"` + // The currency to convert. Supports `USD`, `CAD`, `EUR`, `SGD`, `INR`, `JPY`, `VND`, `CNY`, `KRW`, `RUB`, `TRY`, `NGN`, `ARS`, `AUD`, `CHF`, and `GBP`. + QuoteCurrency *string `json:"quote_currency,omitempty"` + // The exchange rate for the requested quote currency. + QuoteRate *float64 `json:"quote_rate,omitempty"` + // A prettier version of the exchange rate for rendering purposes. + PrettyQuoteRate *string `json:"pretty_quote_rate,omitempty"` +} +type SupportedDexesResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // List of response items. + Items []SupportedDex `json:"items"` + // Pagination metadata. + Pagination genericmodels.Pagination `json:"pagination"` +} +type SingleNetworkExchangeTokenResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []PoolWithTimeseries `json:"items"` + // Pagination metadata. + Pagination genericmodels.Pagination `json:"pagination"` +} +type TransactionsForAccountAddressResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []ExchangeTransaction `json:"items"` + // Pagination metadata. + Pagination genericmodels.Pagination `json:"pagination"` +} +type ExchangeTransaction struct { + // The block signed timestamp in UTC. + BlockSignedAt *time.Time `json:"block_signed_at,omitempty"` + // The requested transaction hash. + TxHash *string `json:"tx_hash,omitempty"` + Act *string `json:"act,omitempty"` + // The requested address. + Address *string `json:"address,omitempty"` + // A list of explorers for this transaction. + Explorers *[]genericmodels.Explorer `json:"explorers,omitempty"` + Amount0 *string `json:"amount_0,omitempty"` + Amount1 *string `json:"amount_1,omitempty"` + Amount0In *string `json:"amount_0_in,omitempty"` + Amount0Out *string `json:"amount_0_out,omitempty"` + Amount1In *string `json:"amount_1_in,omitempty"` + Amount1Out *string `json:"amount_1_out,omitempty"` + ToAddress *string `json:"to_address,omitempty"` + FromAddress *string `json:"from_address,omitempty"` + SenderAddress *string `json:"sender_address,omitempty"` + TotalQuote *float64 `json:"total_quote,omitempty"` + // A prettier version of the total quote for rendering purposes. + PrettyTotalQuote *string `json:"pretty_total_quote,omitempty"` + // The value attached to this tx. + Value *utils.BigInt `json:"value,omitempty"` + // The value attached in `quote-currency` to this tx. + ValueQuote *float64 `json:"value_quote,omitempty"` + // A prettier version of the quote for rendering purposes. + PrettyValueQuote *string `json:"pretty_value_quote,omitempty"` + // The requested chain native gas token metadata. + GasMetadata *genericmodels.ContractMetadata `json:"gas_metadata,omitempty"` + // The amount of gas supplied for this tx. + GasOffered *int64 `json:"gas_offered,omitempty"` + // The gas spent for this tx. + GasSpent *int64 `json:"gas_spent,omitempty"` + // The gas price at the time of this tx. + GasPrice *int64 `json:"gas_price,omitempty"` + // The total transaction fees (`gas_price` * `gas_spent`) paid for this tx, denoted in wei. + FeesPaid *utils.BigInt `json:"fees_paid,omitempty"` + // The gas spent in `quote-currency` denomination. + GasQuote *float64 `json:"gas_quote,omitempty"` + // A prettier version of the quote for rendering purposes. + PrettyGasQuote *string `json:"pretty_gas_quote,omitempty"` + // The native gas exchange rate for the requested `quote-currency`. + GasQuoteRate *float64 `json:"gas_quote_rate,omitempty"` + // The requested quote currency eg: `USD`. + QuoteCurrency *string `json:"quote_currency,omitempty"` + Token0 *PoolToken `json:"token_0,omitempty"` + Token1 *PoolToken `json:"token_1,omitempty"` + Token0QuoteRate *float64 `json:"token_0_quote_rate,omitempty"` + Token1QuoteRate *float64 `json:"token_1_quote_rate,omitempty"` +} +type PoolToken struct { + // Use contract decimals to format the token balance for display purposes - divide the balance by `10^{contract_decimals}`. + ContractDecimals *int `json:"contract_decimals,omitempty"` + // The string returned by the `name()` method. + ContractName *string `json:"contract_name,omitempty"` + // The ticker symbol for this contract. This field is set by a developer and non-unique across a network. + ContractTickerSymbol *string `json:"contract_ticker_symbol,omitempty"` + // Use the relevant `contract_address` to lookup prices, logos, token transfers, etc. + ContractAddress *string `json:"contract_address,omitempty"` + // A list of supported standard ERC interfaces, eg: `ERC20` and `ERC721`. + SupportsErc *bool `json:"supports_erc,omitempty"` + // The contract logo URL. + LogoUrl *string `json:"logo_url,omitempty"` +} +type TransactionsForTokenAddressResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []ExchangeTransaction `json:"items"` + // Pagination metadata. + Pagination genericmodels.Pagination `json:"pagination"` +} +type TransactionsForExchangeResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []ExchangeTransaction `json:"items"` + // Pagination metadata. + Pagination genericmodels.Pagination `json:"pagination"` +} +type NetworkTransactionsResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []ExchangeTransaction `json:"items"` + // Pagination metadata. + Pagination genericmodels.Pagination `json:"pagination"` +} +type EcosystemChartDataResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []UniswapLikeEcosystemCharts `json:"items"` + // Pagination metadata. + Pagination genericmodels.Pagination `json:"pagination"` +} +type UniswapLikeEcosystemCharts struct { + // The name of the DEX, eg: `uniswap_v2`. + DexName *string `json:"dex_name,omitempty"` + // The requested chain ID eg: `1`. + ChainId *string `json:"chain_id,omitempty"` + // The requested quote currency eg: `USD`. + QuoteCurrency *string `json:"quote_currency,omitempty"` + GasTokenPriceQuote *float64 `json:"gas_token_price_quote,omitempty"` + TotalSwaps24h *int64 `json:"total_swaps_24h,omitempty"` + TotalActivePairs7d *int64 `json:"total_active_pairs_7d,omitempty"` + TotalFees24h *float64 `json:"total_fees_24h,omitempty"` + // A prettier version of the gas quote for rendering purposes. + PrettyGasTokenPriceQuote *string `json:"pretty_gas_token_price_quote,omitempty"` + // A prettier version of the 24h total fees for rendering purposes. + PrettyTotalFees24h *string `json:"pretty_total_fees_24h,omitempty"` + VolumeChart7d *[]VolumeEcosystemChart `json:"volume_chart_7d,omitempty"` + VolumeChart30d *[]VolumeEcosystemChart `json:"volume_chart_30d,omitempty"` + LiquidityChart7d *[]LiquidityEcosystemChart `json:"liquidity_chart_7d,omitempty"` + LiquidityChart30d *[]LiquidityEcosystemChart `json:"liquidity_chart_30d,omitempty"` +} +type VolumeEcosystemChart struct { + // The name of the DEX, eg: `uniswap_v2`. + DexName *string `json:"dex_name,omitempty"` + // The requested chain ID eg: `1`. + ChainId *string `json:"chain_id,omitempty"` + Dt *time.Time `json:"dt,omitempty"` + // The requested quote currency eg: `USD`. + QuoteCurrency *string `json:"quote_currency,omitempty"` + VolumeQuote *float64 `json:"volume_quote,omitempty"` + // A prettier version of the volume quote for rendering purposes. + PrettyVolumeQuote *string `json:"pretty_volume_quote,omitempty"` + SwapCount24 *int64 `json:"swap_count_24,omitempty"` +} +type LiquidityEcosystemChart struct { + // The name of the DEX, eg: `uniswap_v2`. + DexName *string `json:"dex_name,omitempty"` + // The requested chain ID eg: `1`. + ChainId *string `json:"chain_id,omitempty"` + Dt *time.Time `json:"dt,omitempty"` + // The requested quote currency eg: `USD`. + QuoteCurrency *string `json:"quote_currency,omitempty"` + LiquidityQuote *float64 `json:"liquidity_quote,omitempty"` + // A prettier version of the liquidity quote for rendering purposes. + PrettyLiquidityQuote *string `json:"pretty_liquidity_quote,omitempty"` +} +type HealthDataResponse struct { + // The timestamp when the response was generated. Useful to show data staleness to users. + UpdatedAt time.Time `json:"updated_at"` + // The requested chain ID eg: `1`. + ChainId int `json:"chain_id"` + // The requested chain name eg: `eth-mainnet`. + ChainName string `json:"chain_name"` + // List of response items. + Items []HealthData `json:"items"` + // Pagination metadata. + Pagination genericmodels.Pagination `json:"pagination"` +} +type HealthData struct { + SyncedBlockHeight *int `json:"synced_block_height,omitempty"` + SyncedBlockSignedAt *time.Time `json:"synced_block_signed_at,omitempty"` + LatestBlockHeight *int `json:"latest_block_height,omitempty"` + LatestBlockSignedAt *time.Time `json:"latest_block_signed_at,omitempty"` +} + +type GetPoolsQueryParamOpts struct { + // Ending date to define a block range (YYYY-MM-DD). Omitting this parameter defaults to the current date. + Date *string `json:"date,omitempty"` + // Number of items per page. Omitting this parameter defaults to 100. + PageSize *int `json:"pageSize,omitempty"` + // 0-indexed page number to begin pagination. + PageNumber *int `json:"pageNumber,omitempty"` +} +type GetPoolsForTokenAddressQueryParamOpts struct { + // The currency to convert. Supports `USD`, `CAD`, `EUR`, `SGD`, `INR`, `JPY`, `VND`, `CNY`, `KRW`, `RUB`, `TRY`, `NGN`, `ARS`, `AUD`, `CHF`, and `GBP`. + QuoteCurrency *quotes.Quote `json:"quoteCurrency,omitempty"` + // The DEX name eg: `uniswap_v2`. + DexName *string `json:"dexName,omitempty"` + // Number of items per page. Omitting this parameter defaults to 100. + PageSize *int `json:"pageSize,omitempty"` +} +type GetPoolsForWalletAddressQueryParamOpts struct { + // The token contract address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically. + TokenAddress *string `json:"tokenAddress,omitempty"` + // The currency to convert. Supports `USD`, `CAD`, `EUR`, `SGD`, `INR`, `JPY`, `VND`, `CNY`, `KRW`, `RUB`, `TRY`, `NGN`, `ARS`, `AUD`, `CHF`, and `GBP`. + QuoteCurrency *quotes.Quote `json:"quoteCurrency,omitempty"` + // The DEX name eg: `uniswap_v2`. + DexName *string `json:"dexName,omitempty"` + // Number of items per page. Omitting this parameter defaults to 100. + PageSize *int `json:"pageSize,omitempty"` +} +type GetNetworkExchangeTokensQueryParamOpts struct { + // Number of items per page. Omitting this parameter defaults to 100. + PageSize *int `json:"pageSize,omitempty"` + // 0-indexed page number to begin pagination. + PageNumber *int `json:"pageNumber,omitempty"` +} +type GetLpTokenViewQueryParamOpts struct { + // The currency to convert. Supports `USD`, `CAD`, `EUR`, `SGD`, `INR`, `JPY`, `VND`, `CNY`, `KRW`, `RUB`, `TRY`, `NGN`, `ARS`, `AUD`, `CHF`, and `GBP`. + QuoteCurrency *quotes.Quote `json:"quoteCurrency,omitempty"` +} +type GetSingleNetworkExchangeTokenQueryParamOpts struct { + // Number of items per page. Omitting this parameter defaults to 100. + PageSize *int `json:"pageSize,omitempty"` + // 0-indexed page number to begin pagination. + PageNumber *int `json:"pageNumber,omitempty"` +} +type GetTransactionsForTokenAddressQueryParamOpts struct { + // Number of items per page. Omitting this parameter defaults to 100. + PageSize *int `json:"pageSize,omitempty"` + // 0-indexed page number to begin pagination. + PageNumber *int `json:"pageNumber,omitempty"` +} +type GetTransactionsForExchangeQueryParamOpts struct { + // Number of items per page. Omitting this parameter defaults to 100. + PageSize *int `json:"pageSize,omitempty"` + // 0-indexed page number to begin pagination. + PageNumber *int `json:"pageNumber,omitempty"` +} +type GetTransactionsForDexQueryParamOpts struct { + // The currency to convert. Supports `USD`, `CAD`, `EUR`, `SGD`, `INR`, `JPY`, `VND`, `CNY`, `KRW`, `RUB`, `TRY`, `NGN`, `ARS`, `AUD`, `CHF`, and `GBP`. + QuoteCurrency *quotes.Quote `json:"quoteCurrency,omitempty"` + // Number of items per page. Omitting this parameter defaults to 100. + PageSize *int `json:"pageSize,omitempty"` + // 0-indexed page number to begin pagination. + PageNumber *int `json:"pageNumber,omitempty"` +} + +func NewXykServiceImpl(apiKey string, debug bool, threadCount int, isValidKey bool) XykService { + + return &xykServiceImpl{APIKey: apiKey, Debug: debug, ThreadCount: threadCount, IskeyValid: isValidKey} +} + +type XykService interface { + + // Commonly used to get all the pools of a particular DEX. Supports most common DEXs (Uniswap, SushiSwap, etc), and returns detailed trading data (volume, liquidity, swap counts, fees, LP token prices). + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // dexName: The DEX name eg: `uniswap_v2`.. Type: string + GetPools(chainName chains.Chain, dexName string, queryParamOpts ...GetPoolsQueryParamOpts) (*utils.Response[PoolResponse], error) + + // Commonly used to get the corresponding supported DEX given a pool address, along with the swap fees, DEX's logo url, and factory addresses. Useful to identifying the specific DEX to which a pair address is associated. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // poolAddress: The requested pool address.. Type: string + GetDexForPoolAddress(chainName chains.Chain, poolAddress string) (*utils.Response[PoolToDexResponse], error) + + // Commonly used to get the 7 day and 30 day time-series data (volume, liquidity, price) of a particular liquidity pool in a DEX. Useful for building time-series charts on DEX trading activity. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // dexName: The DEX name eg: `uniswap_v2`.. Type: string + // poolAddress: The pool contract address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetPoolByAddress(chainName chains.Chain, dexName string, poolAddress string) (*utils.Response[PoolByAddressResponse], error) + + // Commonly used to get all pools and the supported DEX for a token. Useful for building a table of top pairs across all supported DEXes that the token is trading on. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // tokenAddress: The token contract address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + // page: The requested 0-indexed page number.. Type: int + GetPoolsForTokenAddress(chainName chains.Chain, tokenAddress string, page int, queryParamOpts ...GetPoolsForTokenAddressQueryParamOpts) (*utils.Response[PoolsDexDataResponse], error) + + // Commonly used to return balance of a wallet/contract address on a specific DEX. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // dexName: The DEX name eg: `uniswap_v2`.. Type: string + // accountAddress: The account address.. Type: string + GetAddressExchangeBalances(chainName chains.Chain, dexName string, accountAddress string) (*utils.Response[AddressExchangeBalancesResponse], error) + + // Commonly used to get all pools and supported DEX for a wallet. Useful for building a personal DEX UI showcasing pairs and supported DEXes associated to the wallet. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // walletAddress: The account address.. Type: string + // page: The requested 0-indexed page number.. Type: int + GetPoolsForWalletAddress(chainName chains.Chain, walletAddress string, page int, queryParamOpts ...GetPoolsForWalletAddressQueryParamOpts) (*utils.Response[PoolsDexDataResponse], error) + + // Commonly used to retrieve all network exchange tokens for a specific DEX. Useful for building a top tokens table by total liquidity within a particular DEX. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // dexName: The DEX name eg: `uniswap_v2`.. Type: string + GetNetworkExchangeTokens(chainName chains.Chain, dexName string, queryParamOpts ...GetNetworkExchangeTokensQueryParamOpts) (*utils.Response[NetworkExchangeTokensResponse], error) + + // Commonly used to get a detailed view for a single liquidity pool token. Includes time series data. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // dexName: The DEX name eg: `uniswap_v2`.. Type: string + // tokenAddress: The token contract address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetLpTokenView(chainName chains.Chain, dexName string, tokenAddress string, queryParamOpts ...GetLpTokenViewQueryParamOpts) (*utils.Response[NetworkExchangeTokenViewResponse], error) + + // Commonly used to get all the supported DEXs available for the xy=k endpoints, along with the swap fees and factory addresses. + // Parameters: + + GetSupportedDEXes() (*utils.Response[SupportedDexesResponse], error) + + // Commonly used to get historical daily swap count for a single network exchange token. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // dexName: The DEX name eg: `uniswap_v2`.. Type: string + // tokenAddress: The token contract address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetSingleNetworkExchangeToken(chainName chains.Chain, dexName string, tokenAddress string, queryParamOpts ...GetSingleNetworkExchangeTokenQueryParamOpts) (*utils.Response[SingleNetworkExchangeTokenResponse], error) + + // Commonly used to get all the DEX transactions of a wallet. Useful for building tables of DEX activity segmented by wallet. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // dexName: The DEX name eg: `uniswap_v2`.. Type: string + // accountAddress: The account address. Passing in an `ENS` or `RNS` resolves automatically.. Type: string + GetTransactionsForAccountAddress(chainName chains.Chain, dexName string, accountAddress string) (*utils.Response[TransactionsForAccountAddressResponse], error) + + // Commonly used to get all the transactions of a token within a particular DEX. Useful for getting a per-token view of DEX activity. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // dexName: The DEX name eg: `uniswap_v2`.. Type: string + // tokenAddress: The token contract address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetTransactionsForTokenAddress(chainName chains.Chain, dexName string, tokenAddress string, queryParamOpts ...GetTransactionsForTokenAddressQueryParamOpts) (*utils.Response[TransactionsForTokenAddressResponse], error) + + // Commonly used for getting all the transactions of a particular DEX liquidity pool. Useful for building a transactions history table for an individual pool. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // dexName: The DEX name eg: `uniswap_v2`.. Type: string + // poolAddress: The pool contract address. Passing in an `ENS`, `RNS`, `Lens Handle`, or an `Unstoppable Domain` resolves automatically.. Type: string + GetTransactionsForExchange(chainName chains.Chain, dexName string, poolAddress string, queryParamOpts ...GetTransactionsForExchangeQueryParamOpts) (*utils.Response[TransactionsForExchangeResponse], error) + + // Commonly used to get all the the transactions for a given DEX. Useful for building DEX activity views. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // dexName: The DEX name eg: `uniswap_v2`.. Type: string + GetTransactionsForDex(chainName chains.Chain, dexName string, queryParamOpts ...GetTransactionsForDexQueryParamOpts) (*utils.Response[NetworkTransactionsResponse], error) + + // Commonly used to get a 7d and 30d time-series chart of DEX activity. Includes volume and swap count. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // dexName: The DEX name eg: `uniswap_v2`.. Type: string + GetEcosystemChartData(chainName chains.Chain, dexName string) (*utils.Response[EcosystemChartDataResponse], error) + + // Commonly used to ping the health of xy=k endpoints to get the synced block height per chain. + // Parameters: + // chainName: The chain name eg: `eth-mainnet`.. Type: chains.Chain + // dexName: The DEX name eg: `uniswap_v2`.. Type: string + GetHealthData(chainName chains.Chain, dexName string) (*utils.Response[HealthDataResponse], error) +} + +type xykServiceImpl struct { + APIKey string + Debug bool + ThreadCount int + IskeyValid bool +} + +func (s *xykServiceImpl) GetPools(chainName chains.Chain, dexName string, queryParamOpts ...GetPoolsQueryParamOpts) (*utils.Response[PoolResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/xy=k/%s/pools/", chainName, dexName) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[PoolResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.Date != nil { + params.Add("date", fmt.Sprintf("%v", *opts.Date)) + } + + if opts.PageSize != nil { + params.Add("page-size", fmt.Sprintf("%v", *opts.PageSize)) + } + + if opts.PageNumber != nil { + params.Add("page-number", fmt.Sprintf("%v", *opts.PageNumber)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[PoolResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[PoolResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[PoolResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[PoolResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[PoolResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[PoolResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[PoolResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *xykServiceImpl) GetDexForPoolAddress(chainName chains.Chain, poolAddress string) (*utils.Response[PoolToDexResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/xy=k/address/%s/dex_name/", chainName, poolAddress) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[PoolToDexResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[PoolToDexResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[PoolToDexResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[PoolToDexResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[PoolToDexResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[PoolToDexResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[PoolToDexResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[PoolToDexResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *xykServiceImpl) GetPoolByAddress(chainName chains.Chain, dexName string, poolAddress string) (*utils.Response[PoolByAddressResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/xy=k/%s/pools/address/%s/", chainName, dexName, poolAddress) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[PoolByAddressResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[PoolByAddressResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[PoolByAddressResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[PoolByAddressResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[PoolByAddressResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[PoolByAddressResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[PoolByAddressResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[PoolByAddressResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *xykServiceImpl) GetPoolsForTokenAddress(chainName chains.Chain, tokenAddress string, page int, queryParamOpts ...GetPoolsForTokenAddressQueryParamOpts) (*utils.Response[PoolsDexDataResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/xy=k/tokens/address/%s/pools/page/%d/", chainName, tokenAddress, page) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[PoolsDexDataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.QuoteCurrency != nil { + params.Add("quote-currency", fmt.Sprintf("%v", *opts.QuoteCurrency)) + } + + if opts.DexName != nil { + params.Add("dex-name", fmt.Sprintf("%v", *opts.DexName)) + } + + if opts.PageSize != nil { + params.Add("page-size", fmt.Sprintf("%v", *opts.PageSize)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[PoolsDexDataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[PoolsDexDataResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[PoolsDexDataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[PoolsDexDataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[PoolsDexDataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[PoolsDexDataResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[PoolsDexDataResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *xykServiceImpl) GetAddressExchangeBalances(chainName chains.Chain, dexName string, accountAddress string) (*utils.Response[AddressExchangeBalancesResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/xy=k/%s/address/%s/balances/", chainName, dexName, accountAddress) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[AddressExchangeBalancesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[AddressExchangeBalancesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[AddressExchangeBalancesResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[AddressExchangeBalancesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[AddressExchangeBalancesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[AddressExchangeBalancesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[AddressExchangeBalancesResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[AddressExchangeBalancesResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *xykServiceImpl) GetPoolsForWalletAddress(chainName chains.Chain, walletAddress string, page int, queryParamOpts ...GetPoolsForWalletAddressQueryParamOpts) (*utils.Response[PoolsDexDataResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/xy=k/address/%s/pools/page/%d/", chainName, walletAddress, page) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[PoolsDexDataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.TokenAddress != nil { + params.Add("token-address", fmt.Sprintf("%v", *opts.TokenAddress)) + } + + if opts.QuoteCurrency != nil { + params.Add("quote-currency", fmt.Sprintf("%v", *opts.QuoteCurrency)) + } + + if opts.DexName != nil { + params.Add("dex-name", fmt.Sprintf("%v", *opts.DexName)) + } + + if opts.PageSize != nil { + params.Add("page-size", fmt.Sprintf("%v", *opts.PageSize)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[PoolsDexDataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[PoolsDexDataResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[PoolsDexDataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[PoolsDexDataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[PoolsDexDataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[PoolsDexDataResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[PoolsDexDataResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *xykServiceImpl) GetNetworkExchangeTokens(chainName chains.Chain, dexName string, queryParamOpts ...GetNetworkExchangeTokensQueryParamOpts) (*utils.Response[NetworkExchangeTokensResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/xy=k/%s/tokens/", chainName, dexName) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[NetworkExchangeTokensResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.PageSize != nil { + params.Add("page-size", fmt.Sprintf("%v", *opts.PageSize)) + } + + if opts.PageNumber != nil { + params.Add("page-number", fmt.Sprintf("%v", *opts.PageNumber)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[NetworkExchangeTokensResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[NetworkExchangeTokensResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[NetworkExchangeTokensResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NetworkExchangeTokensResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NetworkExchangeTokensResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[NetworkExchangeTokensResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[NetworkExchangeTokensResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *xykServiceImpl) GetLpTokenView(chainName chains.Chain, dexName string, tokenAddress string, queryParamOpts ...GetLpTokenViewQueryParamOpts) (*utils.Response[NetworkExchangeTokenViewResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/xy=k/%s/tokens/address/%s/view/", chainName, dexName, tokenAddress) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[NetworkExchangeTokenViewResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.QuoteCurrency != nil { + params.Add("quote-currency", fmt.Sprintf("%v", *opts.QuoteCurrency)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[NetworkExchangeTokenViewResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[NetworkExchangeTokenViewResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[NetworkExchangeTokenViewResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NetworkExchangeTokenViewResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NetworkExchangeTokenViewResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[NetworkExchangeTokenViewResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[NetworkExchangeTokenViewResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *xykServiceImpl) GetSupportedDEXes() (*utils.Response[SupportedDexesResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/xy=k/supported_dexes/") + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[SupportedDexesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[SupportedDexesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[SupportedDexesResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[SupportedDexesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[SupportedDexesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[SupportedDexesResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[SupportedDexesResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[SupportedDexesResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *xykServiceImpl) GetSingleNetworkExchangeToken(chainName chains.Chain, dexName string, tokenAddress string, queryParamOpts ...GetSingleNetworkExchangeTokenQueryParamOpts) (*utils.Response[SingleNetworkExchangeTokenResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/xy=k/%s/tokens/address/%s/", chainName, dexName, tokenAddress) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[SingleNetworkExchangeTokenResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.PageSize != nil { + params.Add("page-size", fmt.Sprintf("%v", *opts.PageSize)) + } + + if opts.PageNumber != nil { + params.Add("page-number", fmt.Sprintf("%v", *opts.PageNumber)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[SingleNetworkExchangeTokenResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[SingleNetworkExchangeTokenResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[SingleNetworkExchangeTokenResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[SingleNetworkExchangeTokenResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[SingleNetworkExchangeTokenResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[SingleNetworkExchangeTokenResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[SingleNetworkExchangeTokenResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *xykServiceImpl) GetTransactionsForAccountAddress(chainName chains.Chain, dexName string, accountAddress string) (*utils.Response[TransactionsForAccountAddressResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/xy=k/%s/address/%s/transactions/", chainName, dexName, accountAddress) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[TransactionsForAccountAddressResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[TransactionsForAccountAddressResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[TransactionsForAccountAddressResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[TransactionsForAccountAddressResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsForAccountAddressResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsForAccountAddressResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[TransactionsForAccountAddressResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[TransactionsForAccountAddressResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *xykServiceImpl) GetTransactionsForTokenAddress(chainName chains.Chain, dexName string, tokenAddress string, queryParamOpts ...GetTransactionsForTokenAddressQueryParamOpts) (*utils.Response[TransactionsForTokenAddressResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/xy=k/%s/tokens/address/%s/transactions/", chainName, dexName, tokenAddress) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[TransactionsForTokenAddressResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.PageSize != nil { + params.Add("page-size", fmt.Sprintf("%v", *opts.PageSize)) + } + + if opts.PageNumber != nil { + params.Add("page-number", fmt.Sprintf("%v", *opts.PageNumber)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[TransactionsForTokenAddressResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[TransactionsForTokenAddressResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[TransactionsForTokenAddressResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsForTokenAddressResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsForTokenAddressResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[TransactionsForTokenAddressResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[TransactionsForTokenAddressResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *xykServiceImpl) GetTransactionsForExchange(chainName chains.Chain, dexName string, poolAddress string, queryParamOpts ...GetTransactionsForExchangeQueryParamOpts) (*utils.Response[TransactionsForExchangeResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/xy=k/%s/pools/address/%s/transactions/", chainName, dexName, poolAddress) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[TransactionsForExchangeResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.PageSize != nil { + params.Add("page-size", fmt.Sprintf("%v", *opts.PageSize)) + } + + if opts.PageNumber != nil { + params.Add("page-number", fmt.Sprintf("%v", *opts.PageNumber)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[TransactionsForExchangeResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[TransactionsForExchangeResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[TransactionsForExchangeResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsForExchangeResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[TransactionsForExchangeResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[TransactionsForExchangeResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[TransactionsForExchangeResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *xykServiceImpl) GetTransactionsForDex(chainName chains.Chain, dexName string, queryParamOpts ...GetTransactionsForDexQueryParamOpts) (*utils.Response[NetworkTransactionsResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/xy=k/%s/transactions/", chainName, dexName) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[NetworkTransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + if len(queryParamOpts) > 0 { + opts := queryParamOpts[0] + + if opts.QuoteCurrency != nil { + params.Add("quote-currency", fmt.Sprintf("%v", *opts.QuoteCurrency)) + } + + if opts.PageSize != nil { + params.Add("page-size", fmt.Sprintf("%v", *opts.PageSize)) + } + + if opts.PageNumber != nil { + params.Add("page-number", fmt.Sprintf("%v", *opts.PageNumber)) + } + + } + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[NetworkTransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[NetworkTransactionsResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[NetworkTransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NetworkTransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[NetworkTransactionsResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[NetworkTransactionsResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[NetworkTransactionsResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *xykServiceImpl) GetEcosystemChartData(chainName chains.Chain, dexName string) (*utils.Response[EcosystemChartDataResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/xy=k/%s/ecosystem/", chainName, dexName) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[EcosystemChartDataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[EcosystemChartDataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[EcosystemChartDataResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[EcosystemChartDataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[EcosystemChartDataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[EcosystemChartDataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[EcosystemChartDataResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[EcosystemChartDataResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} + +func (s *xykServiceImpl) GetHealthData(chainName chains.Chain, dexName string) (*utils.Response[HealthDataResponse], error) { + + apiURL := fmt.Sprintf("https://api.covalenthq.com/v1/%v/xy=k/%s/health/", chainName, dexName) + + if !s.IskeyValid { + errorCode := 401 + errorMessage := utils.InvalidAPIKeyMessage + return &utils.Response[HealthDataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, fmt.Errorf(utils.InvalidAPIKeyMessage) + } + + parsedURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + params := url.Values{} + + // Add query parameters to the URL + parsedURL.RawQuery = params.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + + if err != nil { + errorCode := 500 + errorMessage := "Unknown Error" + return &utils.Response[HealthDataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + req.Header.Set("Authorization", `Bearer `+s.APIKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if s.Debug { + startTime = time.Now() // Initialize startTime with the current time + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + // create a &Response that returns data: nil, error: true, errorCode: "Unknown Error Code", errorMessage: "Unknown Error" + return nil, err + } + + defer resp.Body.Close() + + utils.DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + backoff := utils.NewExponentialBackoff(s.APIKey, s.Debug, 0) + + // // Read the response body + var data utils.Response[HealthDataResponse] + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + errorCode := resp.StatusCode + errorMessage := err.Error() + return &utils.Response[HealthDataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + res.Body.Close() + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[HealthDataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + res.Body.Close() + } else { + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + errorCode := 500 + errorMessage := err.Error() + return &utils.Response[HealthDataResponse]{Data: nil, Error: true, ErrorCode: &errorCode, ErrorMessage: &errorMessage}, err + } + } + + if data.Error { + return &utils.Response[HealthDataResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, fmt.Errorf(*data.ErrorMessage) + } + + return &utils.Response[HealthDataResponse]{Data: data.Data, Error: data.Error, ErrorCode: data.ErrorCode, ErrorMessage: data.ErrorMessage}, nil +} diff --git a/tests/back_off_test.go b/tests/back_off_test.go new file mode 100644 index 0000000..5df3aa2 --- /dev/null +++ b/tests/back_off_test.go @@ -0,0 +1,72 @@ +package tests + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/covalenthq/covalent-api-sdk-go/utils" +) + +func TestExponentialBackoff_BackOff(t *testing.T) { + // Create a test server + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusTooManyRequests) + }) + server := httptest.NewServer(handler) + defer server.Close() + + // Create a new ExponentialBackoff instance for testing + backoff := utils.NewExponentialBackoff("API_KEY", true, 3) + + // Call the BackOff method with the test server URL + response, err := backoff.BackOff(server.URL) + if err == nil { + t.Errorf("Expected error, got nil") + } + if response != nil { + t.Errorf("Expected nil response, got %v", response) + } +} + +func TestExponentialBackoff_BackOffSuccess(t *testing.T) { + // Create a test server + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + server := httptest.NewServer(handler) + defer server.Close() + + // Create a new ExponentialBackoff instance for testing + backoff := utils.NewExponentialBackoff("API_KEY", true, 3) + + // Call the BackOff method with the test server URL + response, err := backoff.BackOff(server.URL) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if response.StatusCode != http.StatusOK { + t.Errorf("Expected status code %d, got %d", http.StatusOK, response.StatusCode) + } +} + +func TestExponentialBackoff_BackOffMaxRetriesExceeded(t *testing.T) { + // Create a test server + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusTooManyRequests) + }) + server := httptest.NewServer(handler) + defer server.Close() + + // Create a new ExponentialBackoff instance for testing + backoff := utils.NewExponentialBackoff("API_KEY", true, 5) + + // Call the BackOff method with the test server URL + _, err := backoff.BackOff(server.URL) + if err == nil { + t.Errorf("Expected error, got nil") + } + if err.Error() != "max retries exceeded: 5" { + t.Errorf("Expected error message 'max retries exceeded: 5', got '%s'", err.Error()) + } +} diff --git a/tests/balance_service_test.go b/tests/balance_service_test.go new file mode 100644 index 0000000..99fda15 --- /dev/null +++ b/tests/balance_service_test.go @@ -0,0 +1,145 @@ +package tests + +import ( + "testing" + + "github.com/covalenthq/covalent-api-sdk-go/chains" + "github.com/covalenthq/covalent-api-sdk-go/services" + "github.com/covalenthq/covalent-api-sdk-go/testutil" +) + +func TestGetTokenBalancesForWalletAddress(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.BalanceService.GetTokenBalancesForWalletAddress(chains.EthMainnet, "demo.eth") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetTokenBalancesForWalletAddressNFT(t *testing.T) { + // Test case 1 + nft := true + noFetchNft := true + resp, err := testutil.Client.BalanceService.GetTokenBalancesForWalletAddress(chains.EthMainnet, "demo.eth", services.GetTokenBalancesForWalletAddressQueryParamOpts{Nft: &nft, NoNftFetch: &noFetchNft}) + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetHistoricalPortfolioForWalletAddress(t *testing.T) { + resp, err := testutil.Client.BalanceService.GetHistoricalPortfolioForWalletAddress(chains.EthMainnet, "demo.eth") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetTokenHoldersV2ForTokenAddress(t *testing.T) { + results := make(chan services.TokenHolderResult) + go func() { + // Call the function and pass parameters + resultChan := testutil.Client.BalanceService.GetTokenHoldersV2ForTokenAddress(chains.EthMainnet, "0x987d7cc04652710b74fff380403f5c02f82e290a") + + // Receive values from the result channel + for result := range resultChan { + // Process each result as it becomes available + results <- result + } + + // Close the results channel when done + close(results) + }() + // Now you can read values from the results channel as they arrive + for result := range results { + // Process each result + if result.Err != nil { + t.Errorf("error: %s", result.Err) + } + } +} + +func TestGetErc20TransfersForWalletAddress(t *testing.T) { + results := make(chan services.BlockTransactionWithContractTransfersResult) + go func() { + // Call the function and pass parameters + contractAddress := "0xdac17f958d2ee523a2206206994597c13d831ec7" + resultChan := testutil.Client.BalanceService.GetErc20TransfersForWalletAddress(chains.EthMainnet, "demo.eth", services.GetErc20TransfersForWalletAddressQueryParamOpts{ContractAddress: &contractAddress}) + + // Receive values from the result channel + for result := range resultChan { + // Process each result as it becomes available + results <- result + } + + // Close the results channel when done + close(results) + }() + // Now you can read values from the results channel as they arrive + for result := range results { + // Process each result + if result.Err != nil { + t.Errorf("error: %s", result.Err) + } + } +} + +func TestGetHistoricalTokenBalancesForWalletAddress(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.BalanceService.GetHistoricalTokenBalancesForWalletAddress(chains.EthMainnet, "demo.eth") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetHistoricalTokenBalancesForWalletAddressNFT(t *testing.T) { + // Test case 1 + nft := true + noFetchNft := true + resp, err := testutil.Client.BalanceService.GetHistoricalTokenBalancesForWalletAddress(chains.EthMainnet, "demo.eth", services.GetHistoricalTokenBalancesForWalletAddressQueryParamOpts{Nft: &nft, NoNftFetch: &noFetchNft}) + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetNativeTokenBalance(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.BalanceService.GetNativeTokenBalance(chains.EthMainnet, "demo.eth") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetTokenHoldersV2ForTokenAddressByPage(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.BalanceService.GetTokenHoldersV2ForTokenAddressByPage(chains.EthMainnet, "0x987d7cc04652710b74fff380403f5c02f82e290a") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} diff --git a/tests/base_service_test.go b/tests/base_service_test.go new file mode 100644 index 0000000..1aa0e41 --- /dev/null +++ b/tests/base_service_test.go @@ -0,0 +1,213 @@ +package tests + +import ( + "testing" + + "github.com/covalenthq/covalent-api-sdk-go/chains" + "github.com/covalenthq/covalent-api-sdk-go/services" + "github.com/covalenthq/covalent-api-sdk-go/testutil" +) + +func TestGetAddressActivity(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.BaseService.GetAddressActivity("demo.eth") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetAllChainStatus(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.BaseService.GetAllChainStatus() + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetAllChains(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.BaseService.GetAllChains() + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetBlock(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.BaseService.GetBlock(chains.EthMainnet, "latest") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetBlockHeights(t *testing.T) { + results := make(chan services.BlockHeightsResult) + go func() { + // Call the function and pass parameters + resultChan := testutil.Client.BaseService.GetBlockHeights(chains.EthMainnet, "2023-01-01", "2023-01-02") + + // Receive values from the result channel + for result := range resultChan { + // Process each result as it becomes available + results <- result + } + + // Close the results channel when done + close(results) + }() + // Now you can read values from the results channel as they arrive + for result := range results { + // Process each result + if result.Err != nil { + t.Errorf("error: %s", result.Err) + } + } +} + +func TestGetBlockHeightsByPage(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.BaseService.GetBlockHeightsByPage(chains.EthMainnet, "2023-01-01", "2023-01-02") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetGasPrices(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.BaseService.GetGasPrices(chains.EthMainnet, "erc20") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetLogEventsByAddress(t *testing.T) { + results := make(chan services.LogEventResult) + go func() { + // Call the function and pass parameters + startingBlock := 17679143 + endingBlock := "17679148" + resultChan := testutil.Client.BaseService.GetLogEventsByAddress(chains.EthMainnet, "0xdac17f958d2ee523a2206206994597c13d831ec7", services.GetLogEventsByAddressQueryParamOpts{StartingBlock: &startingBlock, EndingBlock: &endingBlock}) + + // Receive values from the result channel + for result := range resultChan { + // Process each result as it becomes available + results <- result + } + + // Close the results channel when done + close(results) + }() + // Now you can read values from the results channel as they arrive + for result := range results { + // Process each result + if result.Err != nil { + t.Errorf("error: %s", result.Err) + } + } +} + +func TestGetLogEventsByAddressByPage(t *testing.T) { + // Test case 1 + startingBlock := 17679143 + endingBlock := "17679148" + resp, err := testutil.Client.BaseService.GetLogEventsByAddressByPage(chains.EthMainnet, "0xdac17f958d2ee523a2206206994597c13d831ec7", services.GetLogEventsByAddressQueryParamOpts{StartingBlock: &startingBlock, EndingBlock: &endingBlock}) + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetLogEventsByTopicHash(t *testing.T) { + results := make(chan services.LogEventResult) + go func() { + // Call the function and pass parameters + startingBlock := 17666774 + endingBlock := "17679143" + resultChan := testutil.Client.BaseService.GetLogEventsByTopicHash(chains.EthMainnet, "0x27f12abfe35860a9a927b465bb3d4a9c23c8428174b83f278fe45ed7b4da2662", services.GetLogEventsByTopicHashQueryParamOpts{StartingBlock: &startingBlock, EndingBlock: &endingBlock}) + + // Receive values from the result channel + for result := range resultChan { + // Process each result as it becomes available + results <- result + } + + // Close the results channel when done + close(results) + }() + // Now you can read values from the results channel as they arrive + for result := range results { + // Process each result + if result.Err != nil { + t.Errorf("error: %s", result.Err) + } + } +} + +func TestGetLogEventsByTopicHashByPage(t *testing.T) { + // Test case 1 + startingBlock := 17666774 + endingBlock := "17679143" + resp, err := testutil.Client.BaseService.GetLogEventsByTopicHashByPage(chains.EthMainnet, "0x27f12abfe35860a9a927b465bb3d4a9c23c8428174b83f278fe45ed7b4da2662", services.GetLogEventsByTopicHashQueryParamOpts{StartingBlock: &startingBlock, EndingBlock: &endingBlock}) + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetLogs(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.BaseService.GetLogs(chains.EthMainnet) + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetResolvedAddress(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.BaseService.GetResolvedAddress(chains.EthMainnet, "demo.eth") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + expectedAddress := "0xfc43f5f9dd45258b3aff31bdbe6561d97e8b71de" + if *resp.Data.Items[0].Address != expectedAddress { + t.Errorf("Error - Got this: %s, should be %s", *resp.Data.Items[0].Address, expectedAddress) + } + } +} diff --git a/tests/big_int_test.go b/tests/big_int_test.go new file mode 100644 index 0000000..4329d79 --- /dev/null +++ b/tests/big_int_test.go @@ -0,0 +1,52 @@ +package tests + +import ( + "math/big" + "reflect" + "testing" + + "github.com/covalenthq/covalent-api-sdk-go/utils" +) + +func TestBigInt_UnmarshalJSON(t *testing.T) { + // Test case 1: Valid JSON number + validJSON := []byte("\"123456789\"") + b := &utils.BigInt{} + err := b.UnmarshalJSON(validJSON) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + expected := big.NewInt(123456789) + if !reflect.DeepEqual(b.Int, expected) { + t.Errorf("Unexpected value. Got: %v, Expected: %v", b.Int, expected) + } + + // Test case 2: Invalid JSON number + invalidJSON := []byte("invalid") + b = &utils.BigInt{} + err = b.UnmarshalJSON(invalidJSON) + if err == nil { + t.Errorf("Expected error, got nil") + } + + // Test case 3: Empty JSON number + emptyJSON := []byte("") + b = &utils.BigInt{} + err = b.UnmarshalJSON(emptyJSON) + if err == nil { + t.Errorf("Expected error, got nil") + } +} + +func TestBigInt_UnmarshalJSON_NilInt(t *testing.T) { + // Test case: Initialize with nil Int + b := &utils.BigInt{} + err := b.UnmarshalJSON([]byte("\"123\"")) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + expected := big.NewInt(123) + if !reflect.DeepEqual(b.Int, expected) { + t.Errorf("Unexpected value. Got: %v, Expected: %v", b.Int, expected) + } +} diff --git a/tests/custom_time_test.go b/tests/custom_time_test.go new file mode 100644 index 0000000..3497131 --- /dev/null +++ b/tests/custom_time_test.go @@ -0,0 +1,52 @@ +package tests + +import ( + "testing" + "time" + + "github.com/covalenthq/covalent-api-sdk-go/utils" +) + +func TestCustomTime_UnmarshalJSON(t *testing.T) { + // Test case 1: Valid JSON string + validJSON := []byte("\"2023-11-23\"") + ct := &utils.CustomTime{} + err := ct.UnmarshalJSON(validJSON) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + expected := time.Date(2023, 11, 23, 0, 0, 0, 0, time.UTC) + if !ct.Time.Equal(expected) { + t.Errorf("Unexpected value. Got: %v, Expected: %v", ct.Time, expected) + } + + // Test case 2: Empty JSON string + emptyJSON := []byte("\"\"") + ct = &utils.CustomTime{} + err = ct.UnmarshalJSON(emptyJSON) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if ct.Time != nil { + t.Errorf("Expected nil time, got: %v", ct.Time) + } + + // Test case 3: Null JSON string + nullJSON := []byte("null") + ct = &utils.CustomTime{} + err = ct.UnmarshalJSON(nullJSON) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if ct.Time != nil { + t.Errorf("Expected nil time, got: %v", ct.Time) + } + + // Test case 4: Invalid JSON string + invalidJSON := []byte("\"invalid\"") + ct = &utils.CustomTime{} + err = ct.UnmarshalJSON(invalidJSON) + if err == nil { + t.Errorf("Expected error, got nil") + } +} diff --git a/tests/nft_service_test.go b/tests/nft_service_test.go new file mode 100644 index 0000000..c19b6fe --- /dev/null +++ b/tests/nft_service_test.go @@ -0,0 +1,217 @@ +package tests + +import ( + "testing" + + "github.com/covalenthq/covalent-api-sdk-go/chains" + "github.com/covalenthq/covalent-api-sdk-go/services" + "github.com/covalenthq/covalent-api-sdk-go/testutil" +) + +func TestCheckOwnershipInNft(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.NftService.CheckOwnershipInNft(chains.EthMainnet, "0xA926597159c76314fBb8aAD077e394F2C77cfFBf", "0x39ee2c7b3cb80254225884ca001F57118C8f21B6") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestCheckOwnershipInNftForSpecificTokenId(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.NftService.CheckOwnershipInNftForSpecificTokenId(chains.EthMainnet, "0xA926597159c76314fBb8aAD077e394F2C77cfFBf", "0x39ee2c7b3cb80254225884ca001F57118C8f21B6", "9465") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetAttributesForTraitInCollection(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.NftService.GetAttributesForTraitInCollection(chains.EthMainnet, "0x39ee2c7b3cb80254225884ca001f57118c8f21b6", "Type") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetChainCollections(t *testing.T) { + results := make(chan services.ChainCollectionItemResult) + go func() { + // Call the function and pass parameters + resultChan := testutil.Client.NftService.GetChainCollections(chains.EthMainnet) + + // Receive values from the result channel + for result := range resultChan { + // Process each result as it becomes available + results <- result + } + + // Close the results channel when done + close(results) + }() + // Now you can read values from the results channel as they arrive + for result := range results { + // Process each result + if result.Err != nil { + t.Errorf("error: %s", result.Err) + } + } +} + +func TestGetChainCollectionsByPage(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.NftService.GetChainCollectionsByPage(chains.EthMainnet) + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetCollectionTraitsSummary(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.NftService.GetCollectionTraitsSummary(chains.EthMainnet, "0x39ee2c7b3cb80254225884ca001f57118c8f21b6") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetNftMarketFloorPrice(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.NftService.GetNftMarketFloorPrice(chains.EthMainnet, "0x3e511FE60D5FE09503C5F2a6477a75d0b905b335") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetNftMarketSaleCount(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.NftService.GetNftMarketSaleCount(chains.EthMainnet, "0x3e511FE60D5FE09503C5F2a6477a75d0b905b335") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetNftMarketVolume(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.NftService.GetNftMarketVolume(chains.EthMainnet, "0x3e511FE60D5FE09503C5F2a6477a75d0b905b335") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetNftMetadataForGivenTokenIdForContract(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.NftService.GetNftMetadataForGivenTokenIdForContract(chains.EthMainnet, "0x39ee2c7b3cb80254225884ca001f57118c8f21b6", "7142") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + expectedAddress := "0x39ee2c7b3cb80254225884ca001f57118c8f21b6" + if *resp.Data.Items[0].ContractAddress != expectedAddress { + t.Errorf("Error - Got this: %s, should be %s", *resp.Data.Items[0].ContractAddress, expectedAddress) + } + } +} + +func TestGetNftTransactionsForContractTokenId(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.NftService.GetNftTransactionsForContractTokenId(chains.EthMainnet, "0x39ee2c7b3cb80254225884ca001f57118c8f21b6", "7142") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetNftsForAddress(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.NftService.GetNftsForAddress(chains.EthMainnet, "demo.eth") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetTokenIdsForContractWithMetadata(t *testing.T) { + results := make(chan services.NftTokenContractResult) + go func() { + // Call the function and pass parameters + resultChan := testutil.Client.NftService.GetTokenIdsForContractWithMetadata(chains.EthMainnet, "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D") + + // Receive values from the result channel + for result := range resultChan { + // Process each result as it becomes available + results <- result + } + + // Close the results channel when done + close(results) + }() + // Now you can read values from the results channel as they arrive + for result := range results { + // Process each result + if result.Err != nil { + t.Errorf("WHAT HAPPENED WITH THIS ERROR error: %s", result.Err) + } + } +} + +func TestGetTokenIdsForContractWithMetadataByPage(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.NftService.GetTokenIdsForContractWithMetadataByPage(chains.EthMainnet, "0x39ee2c7b3cb80254225884ca001f57118c8f21b6") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetTraitsForCollection(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.NftService.GetTraitsForCollection(chains.EthMainnet, "0x39ee2c7b3cb80254225884ca001f57118c8f21b6") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} diff --git a/tests/pricing_service_test.go b/tests/pricing_service_test.go new file mode 100644 index 0000000..bc67d81 --- /dev/null +++ b/tests/pricing_service_test.go @@ -0,0 +1,20 @@ +package tests + +import ( + "testing" + + "github.com/covalenthq/covalent-api-sdk-go/chains" + "github.com/covalenthq/covalent-api-sdk-go/testutil" +) + +func TestGetTokenPrices(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.PricingService.GetTokenPrices(chains.EthMainnet, "USD", "0xb8c77482e45f1f44de1745f52c74426c631bdd52") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(*resp.Data) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} diff --git a/tests/security_service_test.go b/tests/security_service_test.go new file mode 100644 index 0000000..b6ae31b --- /dev/null +++ b/tests/security_service_test.go @@ -0,0 +1,32 @@ +package tests + +import ( + "testing" + + "github.com/covalenthq/covalent-api-sdk-go/chains" + "github.com/covalenthq/covalent-api-sdk-go/testutil" +) + +func TestApprovals(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.SecurityService.GetApprovals(chains.EthMainnet, "demo.eth") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestNftApprovals(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.SecurityService.GetNftApprovals(chains.EthMainnet, "demo.eth") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} diff --git a/tests/transaction_service_test.go b/tests/transaction_service_test.go new file mode 100644 index 0000000..651baff --- /dev/null +++ b/tests/transaction_service_test.go @@ -0,0 +1,129 @@ +package tests + +import ( + "testing" + + "github.com/covalenthq/covalent-api-sdk-go/chains" + "github.com/covalenthq/covalent-api-sdk-go/services" + "github.com/covalenthq/covalent-api-sdk-go/testutil" +) + +func TestGetRecentTransactionsForAddress(t *testing.T) { + results := make(chan services.TransactionResult) + go func() { + // Call the function and pass parameters + resultChan := testutil.Client.TransactionService.GetAllTransactionsForAddress(chains.EthMainnet, "demo.eth") + + // Receive values from the result channel + for result := range resultChan { + // Process each result as it becomes available + results <- result + } + + // Close the results channel when done + close(results) + }() + // Now you can read values from the results channel as they arrive + for result := range results { + // Process each result + if result.Err != nil { + t.Errorf("error: %s", result.Err) + } + } +} + +func TestGetRecentTransactionsForAddressByPage(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.TransactionService.GetAllTransactionsForAddressByPage(chains.EthMainnet, "demo.eth") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetTimeBucketTransactionsForAddress(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.TransactionService.GetTimeBucketTransactionsForAddress(chains.EthMainnet, "demo.eth", 1799272) + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetTransaction(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.TransactionService.GetTransaction(chains.EthMainnet, "0xb27a3a3d660b7d679ebbd7065635c8c3613e32eb0ebae24863a6375d73d1a128") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetTransactionSummary(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.TransactionService.GetTransactionSummary(chains.EthMainnet, "demo.eth") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetTransactionsForAddressV3(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.TransactionService.GetTransactionsForAddressV3(chains.EthMainnet, "demo.eth", 0) + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetTransactionsForBlock(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.TransactionService.GetTransactionsForBlock(chains.EthMainnet, "17685920") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetTransactionsForBlockHash(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.TransactionService.GetTransactionsForBlockHash(chains.EthMainnet, "0x4ee50495ce7fbc4bfe412c38052eb8ca1bc470c0c07d756757f2fced9ad9d60b") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetTransactionsForBlockHashByPage(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.TransactionService.GetTransactionsForBlockHashByPage(chains.EthMainnet, "0x4ee50495ce7fbc4bfe412c38052eb8ca1bc470c0c07d756757f2fced9ad9d60b", 0) + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} diff --git a/tests/xyk_service_test.go b/tests/xyk_service_test.go new file mode 100644 index 0000000..2c91095 --- /dev/null +++ b/tests/xyk_service_test.go @@ -0,0 +1,243 @@ +package tests + +import ( + "testing" + + "github.com/covalenthq/covalent-api-sdk-go/chains" + "github.com/covalenthq/covalent-api-sdk-go/services" + "github.com/covalenthq/covalent-api-sdk-go/testutil" +) + +func TestGetAddressExchangeBalances(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.XykService.GetAddressExchangeBalances(chains.EthMainnet, "uniswap_v2", "demo.eth") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetDexForPoolAddress(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.XykService.GetDexForPoolAddress(chains.EthMainnet, "0x4161fa43eaa1ac3882aeed12c5fc05249e533e67") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + expectedDexName := "uniswap_v2" + if *resp.Data.Items[0].DexName != expectedDexName { + t.Errorf("Error - Got this: %s, should be %s", *resp.Data.Items[0].DexName, expectedDexName) + } + } +} + +func TestGetEcosystemChartData(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.XykService.GetEcosystemChartData(chains.EthMainnet, "uniswap_v2") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetHealthData(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.XykService.GetHealthData(chains.EthMainnet, "uniswap_v2") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetLpTokenView(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.XykService.GetLpTokenView(chains.EthMainnet, "uniswap_v2", "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + expectedTicker := "WETH" + expectedName := "Wrapped Ether" + if *resp.Data.Items[0].ContractTickerSymbol != expectedTicker { + t.Errorf("Error - Got this: %s, should be %s", *resp.Data.Items[0].ContractTickerSymbol, expectedTicker) + } + if *resp.Data.Items[0].ContractName != expectedName { + t.Errorf("Error - Got this: %s, should be %s", *resp.Data.Items[0].ContractName, expectedName) + } + } +} + +func TestGetNetworkExchangeTokens(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.XykService.GetNetworkExchangeTokens(chains.EthMainnet, "uniswap_v2") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + expectedTicker := "WETH" + expectedName := "Wrapped Ether" + if *resp.Data.Items[0].ContractTickerSymbol != expectedTicker { + t.Errorf("Error - Got this: %s, should be %s", *resp.Data.Items[0].ContractTickerSymbol, expectedTicker) + } + if *resp.Data.Items[0].ContractName != expectedName { + t.Errorf("Error - Got this: %s, should be %s", *resp.Data.Items[0].ContractName, expectedName) + } + } +} + +func TestGetPoolByAddress(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.XykService.GetPoolByAddress(chains.FantomMainnet, "spiritswap", "0xdbc490b47508d31c9ec44afb6e132ad01c61a02c") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + expectedExchange := "0xdbc490b47508d31c9ec44afb6e132ad01c61a02c" + if *resp.Data.Items[0].Exchange != expectedExchange { + t.Errorf("Error - Got this: %s, should be %s", *resp.Data.Items[0].Exchange, expectedExchange) + } + } +} + +func TestGetPools(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.XykService.GetPools(chains.EthMainnet, "uniswap_v2") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + expectedDexName := "uniswap_v2" + if *resp.Data.Items[0].DexName != expectedDexName { + t.Errorf("Error - Got this: %s, should be %s", *resp.Data.Items[0].DexName, expectedDexName) + } + } +} + +func TestGetPoolsForTokenAddress(t *testing.T) { + // Test case 1 + requestedDexName := "uniswap_v2" + resp, err := testutil.Client.XykService.GetPoolsForTokenAddress(chains.EthMainnet, "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", 0, services.GetPoolsForTokenAddressQueryParamOpts{DexName: &requestedDexName}) + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + expectedDexName := "uniswap_v2" + if *resp.Data.Items[0].DexName != expectedDexName { + t.Errorf("Error - Got this: %s, should be %s", *resp.Data.Items[0].DexName, expectedDexName) + } + } +} + +func TestGetPoolsForWalletAddress(t *testing.T) { + // Test case 1 + requestedDexName := "uniswap_v2" + resp, err := testutil.Client.XykService.GetPoolsForWalletAddress(chains.EthMainnet, "ganeshswami.eth", 0, services.GetPoolsForWalletAddressQueryParamOpts{DexName: &requestedDexName}) + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + expectedDexName := "uniswap_v2" + if *resp.Data.Items[0].DexName != expectedDexName { + t.Errorf("Error - Got this: %s, should be %s", *resp.Data.Items[0].DexName, expectedDexName) + } + } +} + +func TestGetSingleNetworkExchangeToken(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.XykService.GetSingleNetworkExchangeToken(chains.EthMainnet, "uniswap_v2", "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + expectedDexName := "uniswap_v2" + if *resp.Data.Items[0].DexName != expectedDexName { + t.Errorf("Error - Got this: %s, should be %s", *resp.Data.Items[0].DexName, expectedDexName) + } + } +} + +func TestGetSupportedDEXes(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.XykService.GetSupportedDEXes() + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetTransactionsForAccountAddress(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.XykService.GetTransactionsForAccountAddress(chains.EthMainnet, "uniswap_v2", "demo.eth") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetTransactionsForDex(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.XykService.GetTransactionsForDex(chains.EthMainnet, "uniswap_v2") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetTransactionsForExchange(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.XykService.GetTransactionsForExchange(chains.FantomMainnet, "spiritswap", "0xdbc490b47508d31c9ec44afb6e132ad01c61a02c") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} + +func TestGetTransactionsForTokenAddress(t *testing.T) { + // Test case 1 + resp, err := testutil.Client.XykService.GetTransactionsForTokenAddress(chains.EthMainnet, "uniswap_v2", "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599") + if err != nil { + t.Errorf("error: %s", err) + } else { + if len(resp.Data.Items) == 0 { + t.Errorf("Error - length of items is 0") + } + } +} diff --git a/testutil/test_util.go b/testutil/test_util.go new file mode 100644 index 0000000..b7158ec --- /dev/null +++ b/testutil/test_util.go @@ -0,0 +1,5 @@ +package testutil + +import "github.com/covalenthq/covalent-api-sdk-go/covalentclient" + +var Client *covalentclient.CovalentClientType = covalentclient.CovalentClient("API_KEY") diff --git a/utils/api_key_validator.go b/utils/api_key_validator.go new file mode 100644 index 0000000..74e4b0b --- /dev/null +++ b/utils/api_key_validator.go @@ -0,0 +1,27 @@ +package utils + +import ( + "regexp" +) + +type ApiKeyValidator struct { + APIKey string + APIKeyV1Pattern *regexp.Regexp + APIKeyV2Pattern *regexp.Regexp +} + +const InvalidAPIKeyMessage string = "invalid or missing API key (sign up at covalenthq.com/platform)" + +// NewApiKeyValidator is a constructor function for ApiKeyValidator +func NewApiKeyValidator(apiKey string) *ApiKeyValidator { + return &ApiKeyValidator{ + APIKey: apiKey, + APIKeyV1Pattern: regexp.MustCompile(`^ckey_([a-f0-9]{27})$`), + APIKeyV2Pattern: regexp.MustCompile(`^cqt_(wF|rQ)([bcdfghjkmpqrtvwxyBCDFGHJKMPQRTVWXY346789]{26})$`), + } +} + +// IsValidApiKey checks if the provided API key matches either of the patterns +func (v *ApiKeyValidator) IsValidApiKey() bool { + return v.APIKeyV1Pattern.MatchString(v.APIKey) || v.APIKeyV2Pattern.MatchString(v.APIKey) +} diff --git a/utils/backoff.go b/utils/backoff.go new file mode 100644 index 0000000..1246f10 --- /dev/null +++ b/utils/backoff.go @@ -0,0 +1,79 @@ +package utils + +import ( + "fmt" + "math" + "net/http" + "time" +) + +const DefaultBackoffMaxRetries = 5 +const BaseDelayMs = 1000 // Base delay in milliseconds + +type ExponentialBackoff struct { + RetryCount int + APIKey string + Debug bool + MaxRetries int +} + +func NewExponentialBackoff(apiKey string, debug bool, maxRetries int) *ExponentialBackoff { + if maxRetries == 0 { + maxRetries = DefaultBackoffMaxRetries + } + return &ExponentialBackoff{ + APIKey: apiKey, + Debug: debug, + MaxRetries: maxRetries, + RetryCount: 1, + } +} + +func (e *ExponentialBackoff) BackOff(url string) (*http.Response, error) { + var startTime time.Time + if e.Debug { + startTime = time.Now() + } + + response, err := e.makeRequest(url) + if err != nil { + return nil, err // Return the error if request fails + } + + defer DebugOutput(url, response.StatusCode, startTime) + + // Check for rate limiting or other errors + if response.StatusCode == http.StatusTooManyRequests || response.StatusCode < 200 || response.StatusCode >= 300 { + response.Body.Close() + if e.RetryCount < e.MaxRetries { + e.RetryCount++ + delayMs := time.Duration(math.Pow(2, float64(e.RetryCount)) * float64(BaseDelayMs)) + time.Sleep(delayMs * time.Millisecond) + return e.BackOff(url) // Retry the request + } else { + return nil, fmt.Errorf("max retries exceeded: %d", e.MaxRetries) + } + } + + return response, nil + +} + +func (e *ExponentialBackoff) makeRequest(url string) (*http.Response, error) { + client := &http.Client{} + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + req.Header.Add("Authorization", "Bearer "+e.APIKey) + response, err := client.Do(req) + if err != nil { + return nil, err + } + + return response, nil +} + +func (e *ExponentialBackoff) SetNumAttempts(retryCount int) { + e.RetryCount = retryCount +} \ No newline at end of file diff --git a/utils/big_int.go b/utils/big_int.go new file mode 100644 index 0000000..77b3e53 --- /dev/null +++ b/utils/big_int.go @@ -0,0 +1,33 @@ +package utils + +import ( + "encoding/json" + "fmt" + "math/big" +) + +// BigInt is a wrapper around math/big.Int that implements json.Unmarshaler. +type BigInt struct { + *big.Int +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (b *BigInt) UnmarshalJSON(p []byte) error { + // Unmarshal the JSON number into a string. + var s string + if err := json.Unmarshal(p, &s); err != nil { + return err + } + + // Initialize the Int if it's nil. + if b.Int == nil { + b.Int = new(big.Int) + } + + // Set the value of Int. + _, ok := b.Int.SetString(s, 10) + if !ok { + return fmt.Errorf("cannot set big.Int value: %v", s) + } + return nil +} diff --git a/utils/custom_time.go b/utils/custom_time.go new file mode 100644 index 0000000..01f6a4b --- /dev/null +++ b/utils/custom_time.go @@ -0,0 +1,26 @@ +package utils + +import ( + "strings" + "time" +) + +type CustomTime struct { + *time.Time +} + +func (ct *CustomTime) UnmarshalJSON(b []byte) error { + // Trim quotes since JSON numbers and booleans come in quotes + s := strings.Trim(string(b), "\"") + if s == "null" || s == "" { + return nil + } + // Parse the string to time.Time using the expected layout + // Adjust "2006-01-02" as needed to match your input format + t, err := time.Parse("2006-01-02", s) + if err != nil { + return err + } + ct.Time = &t + return nil +} diff --git a/utils/debugger.go b/utils/debugger.go new file mode 100644 index 0000000..1ea187b --- /dev/null +++ b/utils/debugger.go @@ -0,0 +1,21 @@ +package utils + +import ( + "fmt" + "time" +) + +func DebugOutput(url string, responseStatus int, startTime time.Time) { + // Check if startTime is the zero value, which means it was not initialized + if startTime.IsZero() { + return + } + // Calculate elapsed time + endTime := time.Now() + elapsedTime := endTime.Sub(startTime) + + // Log the details + // Note: Sprintf is used for string formatting, and Printf is used for printing + fmt.Printf("[DEBUG] | Request URL: %s | Response code: %d | Response time: %.2fms\n", + url, responseStatus, elapsedTime.Seconds()*1000) +} diff --git a/utils/pagination.go b/utils/pagination.go new file mode 100644 index 0000000..7d63172 --- /dev/null +++ b/utils/pagination.go @@ -0,0 +1,108 @@ +package utils + +import ( + "net/http" + "net/url" + "strconv" + "time" +) + +func PaginateEndpoint(urlStr, apiKey string, urlParams url.Values, page int, debug bool, threadCount int) (*http.Response, error) { + + parsedURL, err := url.Parse(urlStr) + if err != nil { + return nil, err + } + + // Check if "page-number" parameter exists and get its value + if urlParams.Has("page-number") { + urlParams.Set("page-number", strconv.Itoa(page)) + } else { + urlParams.Add("page-number", strconv.Itoa(page)) + } + + parsedURL.RawQuery = urlParams.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + if err != nil { + return nil, err + } + + req.Header.Set("Authorization", `Bearer `+apiKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if debug { + startTime = time.Now() // Initialize startTime with the current time + } + + backoff := NewExponentialBackoff(apiKey, debug, 0) + + // Perform the request + resp, err := client.Do(req) + if err != nil { + return nil, err + } + + DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + return nil, err + } + return res, nil + } else { + return resp, nil + } +} + +func PaginateEndpointUsingLinks(urlStr, apiKey string, urlParams url.Values, debug bool, threadCount int) (*http.Response, error) { + parsedURL, err := url.Parse(urlStr) + if err != nil { + return nil, err + } + + parsedURL.RawQuery = urlParams.Encode() + + // Create an HTTP client + client := &http.Client{} + + // Create a GET request + req, err := http.NewRequest("GET", parsedURL.String(), nil) + if err != nil { + return nil, err + } + + req.Header.Set("Authorization", `Bearer `+apiKey) + + var startTime time.Time // Declares startTime, initially set to zero value of time.Time + + if debug { + startTime = time.Now() // Initialize startTime with the current time + } + + backoff := NewExponentialBackoff(apiKey, debug, 0) + + // Perform the request + resp, err := client.Do(req) + if err != nil { + return nil, err + } + + DebugOutput(resp.Request.URL.String(), resp.StatusCode, startTime) + + if resp.StatusCode == 429 { + res, err := backoff.BackOff(resp.Request.URL.String()) + if err != nil { + return nil, err + } + return res, nil + } else { + return resp, nil + } +} diff --git a/utils/response.go b/utils/response.go new file mode 100644 index 0000000..88d1015 --- /dev/null +++ b/utils/response.go @@ -0,0 +1,8 @@ +package utils + +type Response[T any] struct { + Data *T `json:"data"` + Error bool `json:"error"` + ErrorCode *int `json:"error_code"` + ErrorMessage *string `json:"error_message"` +}