We strongly recommend to use instead
- go-sdk instead and the transaction broadcaster there, of coure feel free to create an issue in go-sdk, if there is any lacking feature in go-sdk that you used from this library
- plain requests to ARC or maybe look at the ARC repository for some help with creating a client
Interact with Bitcoin SV ARC exposing the interface interface.go
- Arc API Support details:
It is a wrapper around the Bitcoin SV ARC API that allows you to interact with the API in a more convenient way by providing a set of custom features to work with multiple nodes and retry logic.
-
Possibility to work with multiple nodes builder pattern
-
Define strategy how to work with multiple nodes strategy
-
Gives possibility to handle different client exposing the same interface as Arc WithArc
-
Possibility to set url and access token for each node independently Config
-
Possibility to use custom http client WithHTTPClient
-
Mock Client for testing purposes details
// Set the access token and url for the node
token := ""
apiURL := "https://tapi.taal.com/arc"
// also there is a possibility to set deploymentID which is optional and adds header XDeployment-ID to the request
deploymentID := "broadcast-client-example"
cfg := broadcast_client.ArcClientConfig{
Token: token,
APIUrl: apiURL,
DeploymentID: deploymentID,
}
client := broadcast_client.Builder().
WithArc(cfg, &logger).
Build()
// ...
hex := "9c5f5244ee45e8c3213521c1d1d5df265d6c74fb108961a876917073d65fef14"
result, err := client.QueryTransaction(context.Background(), hex)
// ...
Examples of usage can be found in the examples
Client allows you to create your own client with setting it with multiple nodes and custom http client.
// Set the access token and url for the node
token := ""
apiURL := "https://tapi.taal.com/arc"
cfg := broadcast_client.ArcClientConfig{
Token: token,
APIUrl: apiURL,
}
client := broadcast_client.Builder().
WithArc(cfg).
Build()
We can also call multiple times the method WithArc
to set multiple nodes.
// Set the access token and url for the node
client := broadcast_client.Builder().
WithArc(cfg1).
WithArc(cfg2).
WithArc(cfg3).
Build()
What is the call order if we have multiple nodes configured?
We use the strategy to define the order of the calls. The default strategy is OneByOne
in RoundRobin algorith that will call the nodes in the order they were set.
Only if all of them fail, we will return the error to the user.
// (...)
clent := broadcast_client.Builder().
WithArc(cfg).
WithHTTPClient(&http.Client{}).
Build()
We can use the method WithHTTPClient
to set the custom http client.
It needs to implement the interface HTTPInterface
that is defined in the httpclient.go
type HTTPInterface interface {
DoRequest(ctx context.Context, pld HTTPRequest) (*http.Response, error)
}
When you created your own client, you can use the method QueryTx
to query the status of a transaction.
// ...
hex := "9c5f5244ee45e8c3213521c1d1d5df265d6c74fb108961a876917073d65fef14"
result, err := client.QueryTransaction(context.Background(), hex)
// ...
Having your client created, you can use the method GetPolicyQuote
to get a slice of policy quotes from all the nodes configured in your client.
// ...
policyQuotes, err := client.GetPolicyQuote(context.Background())
// ...
Having your client created, you can use the method GetFeeQuote
to get a slice of fee quotes (which are subsets from PolicyQuotes) from all the nodes configured in your client.
// ...
feeQuotes, err := client.GetFeeQuote(context.Background())
// ...
Having your client created, you can use the method SubmitTx
to submit a single transaction to the node.
// ...
tx := broadcast.Transaction{
Hex: "xyz",
}
result, err := client.SubmitTransaction(context.Background(), tx)
// ...
You need to pass the transaction as a parameter to the method SubmitTransaction
.
You may add options to this method:
result, err := client.SubmitTransaction(context.Background(), tx, broadcast.WithCallback(callBackUrl, callbackToken))
Setting CallbackURL
and CallBackToken
will add the headers X-CallbackUrl
and X-CallbackToken
to the request.
It will allow you to get the callback from the node when the transaction is mined, and receive the transaction details and status.
result, err := client.SubmitTransaction(context.Background(), tx, broadcast.WithMerkleProof())
Setting MerkleProof
to true will add the header X-MerkleProof
to the request.
MerkleProof while broadcasting will handle the merkle proof capability of the node.
result, err := client.SubmitTransaction(context.Background(), tx, broadcast.WithWaitForstatus(broadcast.AnnouncedToNetwork))
Setting WaitForStatus
will add the header X-WaitForStatus
to the request.
It will allow you to return the result only when the transaction reaches the status you set.
result, err := client.SubmitTransaction(context.Background(), tx, broadcast.WithEfFormat())
Setting EfFormat
will accept your transaction in EF format and submit to Arc. This is the default option.
result, err := client.SubmitTransaction(context.Background(), tx, broadcast.WithBeefFormat())
Setting BeefFormat
will accept your transaction in BEEF format and decode it for a proper format acceptable by Arc.
result, err := client.SubmitTransaction(context.Background(), tx, broadcast.WithRawFormat())
Setting RawFormat
will accept your transaction in RawTx format and encode it for a proper format acceptable by Arc.
This option will become deprecated soon.
Having your client created, you can use the method SubmitBatchTx
to submit a batch of transactions to the node.
// ...
txs := []*broadcast.Transaction{
{Hex: "xyz1"},
{Hex: "xyz2"},
{Hex: "xyz3"},
{Hex: "xyz4"},
{Hex: "xyz5"},
}
result, err := client.SubmitBatchTransaction(context.Background(), txs)
// ...
The method works the same as the SubmitTx
method, but it is sending a batch of transactions instead of a single one. It is also receiving a batch of responses for each of the transactions.
type QueryTxResponse struct {
Miner string `json:"miner"`
BlockHash string `json:"blockHash,omitempty"`
BlockHeight int64 `json:"blockHeight,omitempty"`
ExtraInfo string `json:"extraInfo,omitempty"`
MerklePath *bc.MerklePath `json:"merklePath,omitempty"`
Timestamp string `json:"timestamp,omitempty"`
TxStatus TxStatus `json:"txStatus,omitempty"`
TxID string `json:"txid,omitempty"`
}
type PolicyQuoteResponse struct {
Miner string `json:"miner"`
Policy PolicyResponse `json:"policy"`
Timestamp string `json:"timestamp"`
}
type PolicyResponse struct {
MaxScriptSizePolicy int64 `json:"maxscriptsizepolicy"`
MaxTxSigOpsCountPolicy int64 `json:"maxtxsigopscountspolicy"`
MaxTxSizePolicy int64 `json:"maxtxsizepolicy"`
MiningFee MiningFeeResponse `json:"miningFee"`
}
type MiningFeeResponse struct {
Bytes int64 `json:"bytes"`
Satoshis int64 `json:"satoshis"`
}
type FeeQuote struct {
Miner string `json:"miner"`
MiningFee MiningFeeResponse `json:"miningFee"`
Timestamp string `json:"timestamp"`
}
type SubmitTxResponse struct {
Miner string `json:"miner"`
BlockHash string `json:"blockHash,omitempty"`
BlockHeight int64 `json:"blockHeight,omitempty"`
ExtraInfo string `json:"extraInfo,omitempty"`
MerklePath *bc.MerklePath `json:"merklePath,omitempty"`
Timestamp string `json:"timestamp,omitempty"`
TxStatus TxStatus `json:"txStatus,omitempty"`
TxID string `json:"txid,omitempty"`
Status int `json:"status,omitempty"`
Title string `json:"title,omitempty"`
}
type Transaction struct {
CallBackEncryption string `json:"callBackEncryption,omitempty"`
CallBackToken string `json:"callBackToken,omitempty"`
CallBackURL string `json:"callBackUrl,omitempty"`
DsCheck bool `json:"dsCheck,omitempty"`
MerkleFormat string `json:"merkleFormat,omitempty"`
MerkleProof bool `json:"merkleProof,omitempty"`
Hex string `json:"hex"`
WaitForStatus TxStatus `json:"waitForStatus,omitempty"`
}
Code | Status | Description |
---|---|---|
0 | UNKNOWN |
The transaction has been sent to metamorph, but no processing has taken place. This should never be the case, unless something goes wrong. |
1 | QUEUED |
The transaction has been queued for processing. |
2 | RECEIVED |
The transaction has been properly received by the metamorph processor. |
3 | STORED |
The transaction has been stored in the metamorph store. This should ensure the transaction will be processed and retried if not picked up immediately by a mining node. |
4 | ANNOUNCED_TO_NETWORK |
The transaction has been announced (INV message) to the Bitcoin network. |
5 | REQUESTED_BY_NETWORK |
The transaction has been requested from metamorph by a Bitcoin node. |
6 | SENT_TO_NETWORK |
The transaction has been sent to at least 1 Bitcoin node. |
7 | ACCEPTED_BY_NETWORK |
The transaction has been accepted by a connected Bitcoin node on the ZMQ interface. If metamorph is not connected to ZQM, this status will never by set. |
8 | SEEN_ON_NETWORK |
The transaction has been seen on the Bitcoin network and propagated to other nodes. This status is set when metamorph receives an INV message for the transaction from another node than it was sent to. |
9 | MINED |
The transaction has been mined into a block by a mining node. |
108 | CONFIRMED |
The transaction is marked as confirmed when it is in a block with 100 blocks built on top of that block. |
109 | REJECTED |
The transaction has been rejected by the Bitcoin network. |
Source Arc API
Mock Client allows you to test your code without using an actual client and without connecting to any nodes.
This method allows you to create a client with a different Mock Type passed as parameter.
client := broadcast_client_mock.Builder().
WithMockArc(broadcast_client_mock.MockSucces).
Build()
MockType | Description |
---|---|
MockSucces |
Client will return a successful response from all methods. |
MockFailure |
Client will return an error that no miner returned a response from all methods. |
MockTimeout |
Client will return a successful response after a timeout from all methods. |
MockTimeout will return a successfull response after around ~10ms more than the timeout provided in the context that is passed to it's method.
Example:
client := broadcast_client_mock.Builder().
WithMockArc(broadcast_client_mock.MockTimeout).
Build()
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) // Creating a timeout context with 2 seconds timeout
defer cancel()
result, err := client.GetPolicyQuote(ctx) // client will return a response after around 2 seconds and 10 milliseconds, therefore exceeding the timeout
If you pass the context without a timeout, the client will instantly return a successful response (just like from a MockSuccess type).
In order to test the Mock Client, you may often need to check if the response from the client is correct. Therefore, some useful constants are exposed from fixtures
package.
const (
ProviderMain = "https://mocked_arc_api_url.com/arc"
ProviderSecondary = "https://secondary_mocked_arc_api_url.com/arc"
MaxScriptSizePolicy int64 = 100000000
MaxTxSigOpsCountPolicy int64 = 4294967295
MaxTxSizePolicy int64 = 100000000
MaxTxSizePolicySecondary int64 = 220000000
MiningFeeBytes int64 = 1000
SatoshisPerBytes int64 = 1
SatoshisPerBytesSecondary int64 = 2
Timestamp = "2023-09-05T17:03:49.537230128Z"
TimestampSecondary = "2023-09-05T17:05:29.736256927Z"
TxResponseStatus = 200
TxResponseTitle = "OK"
TxStatus broadcast.TxStatus = "SEEN_ON_NETWORK"
TxBlockHash = "0000000000000000019a575e0ea4d9bbe251dd24c473a0d8407935973151f282"
TxBlockHashSecondary = "0000000000000000045c969f3acd5db37896aba95f91389f2d191496bf15584b"
TxBlockHeight int64 = 800182
TxBlockHeightSecondary int64 = 799439
TxExtraInfo = ""
TxMerklePath = "fd123511fc17127984cea2846b6a6b3965cbd523ea92736c11022372dc922d0013ee15f577bb7d832ce6102e22a15c6e4f4e4214e246d769f04baaf458e3407f5117de8eceb78782a1f90bfaa103915dcc3aa940c7e1476398db47dc62833f3149c51593cf9eeaec36f86c871d64e849a486b97eee1ee863c18aa28e599e3b30853a8563a87441202a97f707d0614cfc024f73ba4a2848ec63e19bc5fb168e994c2c2c105b5951dee4775f20879cb63b3b525370b35200e9f91e9e0fa234dfae5cc4792d17ab08f5cb29aa97e99742dc1e48a9d7e648be33db2d75b806519d88c62a57b39ef900cf68c9d765a1a3b17895d05077ab33280fdb4c09f818d31065b1325b6eb0366c4fd48c2840c1e3a5eb64a9596ffe53bbb315b44784882fb727a98e791d4c8a0bc1d5a88fd85365afdf83da86216cf81b587ee960b5c2a4174dfb26fa2606addf5e51edea58f76cc415f21876c9fa4551891aab537f4e463f1ded910d6a27ff27531c32b4dad15360b35d4d6da348c0c311aaf412bd19a92e2253e90c67104c8f0c0167cf43032f4ffa616cb8c5c28733a3faef5afb83d3c621b252112cbf35859c7b3b4caf820ae43f7054db87da2b7bc735f41b0969b4caadd1cc78be66fb367990103b5682f33605628dcc9af941bf555c64e1c48d3d0955f2c53f7ed7611d976d3effb053eb13b85ca2bff429516aa21227c39d6b6f4106f513069a559805977dee6462d354522d6e42f30d60744bd0f08471577ea5e1f63f319261"
TxId = "680d975a403fd9ec90f613e87d17802c029d2d930df1c8373cdcdda2f536a1c0"
TxIdSecondary = "d4f4bcb8decdbc75a3dcc54580c76457ee663a28b843956733570b38f7c73b5a"
)