Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[client] Add more client options #97

Merged
merged 2 commits into from
Oct 8, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
372 changes: 370 additions & 2 deletions client.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package aptos

import (
"fmt"
"net/http"
"time"

"github.com/aptos-labs/aptos-go-sdk/api"
Expand Down Expand Up @@ -75,21 +77,387 @@ func init() {
setNN(MainnetConfig)
}

// AptosClient is an interface for all functionality on the Client.
// It is a combination of [AptosRpcClient], [AptosIndexerClient], and [AptosFaucetClient] for the purposes
// of mocking and convenince.
type AptosClient interface {
AptosRpcClient
AptosIndexerClient
AptosFaucetClient
}

// AptosRpcClient is an interface for all functionality on the Client that is Node RPC related. Its main implementation
// is [NodeClient]
type AptosRpcClient interface {
// SetTimeout adjusts the HTTP client timeout
//
// client.SetTimeout(5 * time.Millisecond)
SetTimeout(timeout time.Duration)

// SetHeader sets the header for all future requests
//
// client.SetHeader("Authorization", "Bearer abcde")
SetHeader(key string, value string)

// RemoveHeader removes the header from being automatically set all future requests.
//
// client.RemoveHeader("Authorization")
RemoveHeader(key string)

// Info Retrieves the node info about the network and it's current state
Info() (info NodeInfo, err error)

// Account Retrieves information about the account such as [SequenceNumber] and [crypto.AuthenticationKey]
Account(address AccountAddress, ledgerVersion ...uint64) (info AccountInfo, err error)

// AccountResource Retrieves a single resource given its struct name.
//
// address := AccountOne
// dataMap, _ := client.AccountResource(address, "0x1::coin::CoinStore")
//
// Can also fetch at a specific ledger version
//
// address := AccountOne
// dataMap, _ := client.AccountResource(address, "0x1::coin::CoinStore", 1)
AccountResource(address AccountAddress, resourceType string, ledgerVersion ...uint64) (data map[string]any, err error)

// AccountResources fetches resources for an account into a JSON-like map[string]any in AccountResourceInfo.Data
// For fetching raw Move structs as BCS, See #AccountResourcesBCS
//
// address := AccountOne
// dataMap, _ := client.AccountResources(address)
//
// Can also fetch at a specific ledger version
//
// address := AccountOne
// dataMap, _ := client.AccountResource(address, 1)
AccountResources(address AccountAddress, ledgerVersion ...uint64) (resources []AccountResourceInfo, err error)

// AccountResourcesBCS fetches account resources as raw Move struct BCS blobs in AccountResourceRecord.Data []byte
AccountResourcesBCS(address AccountAddress, ledgerVersion ...uint64) (resources []AccountResourceRecord, err error)

// BlockByHeight fetches a block by height
//
// block, _ := client.BlockByHeight(1, false)
//
// Can also fetch with transactions
//
// block, _ := client.BlockByHeight(1, true)
BlockByHeight(blockHeight uint64, withTransactions bool) (data *api.Block, err error)

// BlockByVersion fetches a block by ledger version
//
// block, _ := client.BlockByVersion(123, false)
//
// Can also fetch with transactions
//
// block, _ := client.BlockByVersion(123, true)
BlockByVersion(ledgerVersion uint64, withTransactions bool) (data *api.Block, err error)

// TransactionByHash gets info on a transaction
// The transaction may be pending or recently committed.
//
// data, err := client.TransactionByHash("0xabcd")
// if err != nil {
// if httpErr, ok := err.(aptos.HttpError) {
// if httpErr.StatusCode == 404 {
// // if we're sure this has been submitted, assume it is still pending elsewhere in the mempool
// }
// }
// } else {
// if data["type"] == "pending_transaction" {
// // known to local mempool, but not committed yet
// }
// }
TransactionByHash(txnHash string) (data *api.Transaction, err error)

// TransactionByVersion gets info on a transaction from its LedgerVersion. It must have been
// committed to have a ledger version
//
// data, err := client.TransactionByVersion("0xabcd")
// if err != nil {
// if httpErr, ok := err.(aptos.HttpError) {
// if httpErr.StatusCode == 404 {
// // if we're sure this has been submitted, the full node might not be caught up to this version yet
// }
// }
// }
TransactionByVersion(version uint64) (data *api.CommittedTransaction, err error)

// PollForTransactions Waits up to 10 seconds for transactions to be done, polling at 10Hz
// Accepts options PollPeriod and PollTimeout which should wrap time.Duration values.
//
// hashes := []string{"0x1234", "0x4567"}
// err := client.PollForTransactions(hashes)
//
// Can additionally configure different options
//
// hashes := []string{"0x1234", "0x4567"}
// err := client.PollForTransactions(hashes, PollPeriod(500 * time.Milliseconds), PollTimeout(5 * time.Seconds))
PollForTransactions(txnHashes []string, options ...any) error

// WaitForTransaction Do a long-GET for one transaction and wait for it to complete
//
// data, err := client.WaitForTransaction("0x1234")
WaitForTransaction(txnHash string, options ...any) (data *api.UserTransaction, err error)

// Transactions Get recent transactions.
// Start is a version number. Nil for most recent transactions.
// Limit is a number of transactions to return. 'about a hundred' by default.
//
// client.Transactions(0, 2) // Returns 2 transactions
// client.Transactions(1, 100) // Returns 100 transactions
Transactions(start *uint64, limit *uint64) (data []*api.CommittedTransaction, err error)

// AccountTransactions Get transactions associated with an account.
// Start is a version number. Nil for most recent transactions.
// Limit is a number of transactions to return. 'about a hundred' by default.
//
// client.AccountTransactions(AccountOne, 0, 2) // Returns 2 transactions for 0x1
// client.AccountTransactions(AccountOne, 1, 100) // Returns 100 transactions for 0x1
AccountTransactions(address AccountAddress, start *uint64, limit *uint64) (data []*api.CommittedTransaction, err error)

// SubmitTransaction Submits an already signed transaction to the blockchain
//
// sender := NewEd25519Account()
// txnPayload := TransactionPayload{
// Payload: &EntryFunction{
// Module: ModuleId{
// Address: AccountOne,
// Name: "aptos_account",
// },
// Function: "transfer",
// ArgTypes: []TypeTag{},
// Args: [][]byte{
// dest[:],
// amountBytes,
// },
// }
// }
// rawTxn, _ := client.BuildTransaction(sender.AccountAddress(), txnPayload)
// signedTxn, _ := sender.SignTransaction(rawTxn)
// submitResponse, err := client.SubmitTransaction(signedTxn)
SubmitTransaction(signedTransaction *SignedTransaction) (data *api.SubmitTransactionResponse, err error)

// BatchSubmitTransaction submits a collection of signed transactions to the network in a single request
//
// It will return the responses in the same order as the input transactions that failed. If the response is empty, then
// all transactions succeeded.
//
// sender := NewEd25519Account()
// txnPayload := TransactionPayload{
// Payload: &EntryFunction{
// Module: ModuleId{
// Address: AccountOne,
// Name: "aptos_account",
// },
// Function: "transfer",
// ArgTypes: []TypeTag{},
// Args: [][]byte{
// dest[:],
// amountBytes,
// },
// }
// }
// rawTxn, _ := client.BuildTransaction(sender.AccountAddress(), txnPayload)
// signedTxn, _ := sender.SignTransaction(rawTxn)
// submitResponse, err := client.BatchSubmitTransaction([]*SignedTransaction{signedTxn})
BatchSubmitTransaction(signedTxns []*SignedTransaction) (response *api.BatchSubmitTransactionResponse, err error)

// SimulateTransaction Simulates a raw transaction without sending it to the blockchain
//
// sender := NewEd25519Account()
// txnPayload := TransactionPayload{
// Payload: &EntryFunction{
// Module: ModuleId{
// Address: AccountOne,
// Name: "aptos_account",
// },
// Function: "transfer",
// ArgTypes: []TypeTag{},
// Args: [][]byte{
// dest[:],
// amountBytes,
// },
// }
// }
// rawTxn, _ := client.BuildTransaction(sender.AccountAddress(), txnPayload)
// simResponse, err := client.SimulateTransaction(rawTxn, sender)
SimulateTransaction(rawTxn *RawTransaction, sender TransactionSigner, options ...any) (data []*api.UserTransaction, err error)

// GetChainId Retrieves the ChainId of the network
// Note this will be cached forever, or taken directly from the config
GetChainId() (chainId uint8, err error)

// BuildTransaction Builds a raw transaction from the payload and fetches any necessary information from on-chain
//
// sender := NewEd25519Account()
// txnPayload := TransactionPayload{
// Payload: &EntryFunction{
// Module: ModuleId{
// Address: AccountOne,
// Name: "aptos_account",
// },
// Function: "transfer",
// ArgTypes: []TypeTag{},
// Args: [][]byte{
// dest[:],
// amountBytes,
// },
// }
// }
// rawTxn, err := client.BuildTransaction(sender.AccountAddress(), txnPayload)
BuildTransaction(sender AccountAddress, payload TransactionPayload, options ...any) (rawTxn *RawTransaction, err error)

// BuildTransactionMultiAgent Builds a raw transaction for MultiAgent or FeePayer from the payload and fetches any necessary information from on-chain
//
// sender := NewEd25519Account()
// txnPayload := TransactionPayload{
// Payload: &EntryFunction{
// Module: ModuleId{
// Address: AccountOne,
// Name: "aptos_account",
// },
// Function: "transfer",
// ArgTypes: []TypeTag{},
// Args: [][]byte{
// dest[:],
// amountBytes,
// },
// }
// }
// rawTxn, err := client.BuildTransactionMultiAgent(sender.AccountAddress(), txnPayload, FeePayer(AccountZero))
BuildTransactionMultiAgent(sender AccountAddress, payload TransactionPayload, options ...any) (rawTxn *RawTransactionWithData, err error)

// BuildSignAndSubmitTransaction Convenience function to do all three in one
// for more configuration, please use them separately
//
// sender := NewEd25519Account()
// txnPayload := TransactionPayload{
// Payload: &EntryFunction{
// Module: ModuleId{
// Address: AccountOne,
// Name: "aptos_account",
// },
// Function: "transfer",
// ArgTypes: []TypeTag{},
// Args: [][]byte{
// dest[:],
// amountBytes,
// },
// }
// }
// submitResponse, err := client.BuildSignAndSubmitTransaction(sender, txnPayload)
BuildSignAndSubmitTransaction(sender *Account, payload TransactionPayload, options ...any) (data *api.SubmitTransactionResponse, err error)

// View Runs a view function on chain returning a list of return values.
//
// address := AccountOne
// payload := &ViewPayload{
// Module: ModuleId{
// Address: AccountOne,
// Name: "coin",
// },
// Function: "balance",
// ArgTypes: []TypeTag{AptosCoinTypeTag},
// Args: [][]byte{address[:]},
// }
// vals, err := client.aptosClient.View(payload)
// balance := StrToU64(vals.(any[])[0].(string))
View(payload *ViewPayload, ledgerVersion ...uint64) (vals []any, err error)

// EstimateGasPrice Retrieves the gas estimate from the network.
EstimateGasPrice() (info EstimateGasInfo, err error)

// AccountAPTBalance retrieves the APT balance in the account
AccountAPTBalance(address AccountAddress) (uint64, error)

// NodeAPIHealthCheck checks if the node is within durationSecs of the current time, if not provided the node default is used
NodeAPIHealthCheck(durationSecs ...uint64) (api.HealthCheckResponse, error)
}

// AptosFaucetClient is an interface for all functionality on the Client that is Faucet related. Its main implementation
// is [FaucetClient]
type AptosFaucetClient interface {
// Fund Uses the faucet to fund an address, only applies to non-production networks
Fund(address AccountAddress, amount uint64) error
}

// AptosIndexerClient is an interface for all functionality on the Client that is Indexer related. Its main implementation
// is [IndexerClient]
type AptosIndexerClient interface {

// QueryIndexer queries the indexer using GraphQL to fill the `query` struct with data. See examples in the indexer client on how to make queries
//
// var out []CoinBalance
// var q struct {
// Current_coin_balances []struct {
// CoinType string `graphql:"coin_type"`
// Amount uint64
// OwnerAddress string `graphql:"owner_address"`
// } `graphql:"current_coin_balances(where: {owner_address: {_eq: $address}})"`
// }
// variables := map[string]any{
// "address": address.StringLong(),
// }
// err := client.QueryIndexer(&q, variables)
// if err != nil {
// return nil, err
// }
//
// for _, coin := range q.Current_coin_balances {
// out = append(out, CoinBalance{
// CoinType: coin.CoinType,
// Amount: coin.Amount,
// })
// }
//
// return out, nil
QueryIndexer(query any, variables map[string]any, options ...graphql.Option) error

// GetProcessorStatus returns the ledger version up to which the processor has processed
GetProcessorStatus(processorName string) (uint64, error)

// GetCoinBalances gets the balances of all coins associated with a given address
GetCoinBalances(address AccountAddress) ([]CoinBalance, error)
}

// Client is a facade over the multiple types of underlying clients, as the user doesn't actually care where the data
// comes from. It will be then handled underneath
//
// To create a new client, please use [NewClient]. An example below for Devnet:
//
// client := NewClient(DevnetConfig)
//
// Implements AptosClient
type Client struct {
nodeClient *NodeClient
faucetClient *FaucetClient
indexerClient *IndexerClient
}

// NewClient Creates a new client with a specific network config that can be extended in the future
func NewClient(config NetworkConfig) (client *Client, err error) {
nodeClient, err := NewNodeClient(config.NodeUrl, config.ChainId)
func NewClient(config NetworkConfig, options ...any) (client *Client, err error) {
var httpClient *http.Client = nil
for i, arg := range options {
switch value := arg.(type) {
case *http.Client:
if httpClient != nil {
err = fmt.Errorf("NewClient only accepts one http.Client")
return
}
httpClient = value
default:
err = fmt.Errorf("NewClient arg %d bad type %T", i+1, arg)
return
}
}
var nodeClient *NodeClient
if httpClient == nil {
nodeClient, err = NewNodeClient(config.NodeUrl, config.ChainId)
} else {
nodeClient, err = NewNodeClientWithHttpClient(config.NodeUrl, config.ChainId, httpClient)
}
if err != nil {
return nil, err
}
Expand Down