From 2248c28debf50cd954daec241c87df22a926b6ba Mon Sep 17 00:00:00 2001 From: Ales Verbic Date: Tue, 2 Jul 2024 07:41:23 -0400 Subject: [PATCH] feat: add submit example with refactor of common functions Signed-off-by: Ales Verbic --- examples/submit/main.go | 121 ++++++++++++++++++++++++++++++++++++++++ examples/sync/main.go | 70 +++++------------------ main.go | 91 ++++++++++++++++++++++++++---- 3 files changed, 215 insertions(+), 67 deletions(-) create mode 100644 examples/submit/main.go diff --git a/examples/submit/main.go b/examples/submit/main.go new file mode 100644 index 0000000..8d8a7fa --- /dev/null +++ b/examples/submit/main.go @@ -0,0 +1,121 @@ +package main + +import ( + "context" + "encoding/hex" + "fmt" + + "connectrpc.com/connect" + "github.com/utxorpc/go-codegen/utxorpc/v1alpha/submit" + utxorpc "github.com/utxorpc/go-sdk" +) + +func main() { + ctx := context.Background() + baseUrl := "https://preview.utxorpc-v0.demeter.run" + // set API key for demeter + client := utxorpc.CreateUtxoRPCClient(baseUrl, + // set API key for demeter + utxorpc.WithHeaders(map[string]string{ + "dmtr-api-key": "dmtr_utxorpc1...", + }), + ) + + // Set mode to "submitTx", "readMempool", "waitForTx", or "watchMempool" to select the desired example. + var mode string = "waitForTx" + + switch mode { + case "submitTx": + submitTx(ctx, client, "Replace this with the signed transaction in CBOR format.") + case "readMempool": + readMempool(ctx, client) + case "waitForTx": + waitForTx(ctx, client) + case "watchMempool": + watchMempool(ctx, client) + default: + fmt.Println("Unknown mode:", mode) + } +} + +func submitTx(ctx context.Context, client *utxorpc.UtxorpcClient, txCbor string) { + // Decode the transaction data from hex + txRawBytes, err := hex.DecodeString(txCbor) + if err != nil { + panic(fmt.Errorf("failed to decode transaction hash: %v", err)) + } + + // Create a SubmitTxRequest with the transaction data + tx := &submit.AnyChainTx{ + Type: &submit.AnyChainTx_Raw{ + Raw: txRawBytes, + }, + } + + // Create a list with one transaction + req := connect.NewRequest(&submit.SubmitTxRequest{ + Tx: []*submit.AnyChainTx{tx}, + }) + + fmt.Println("Connecting to utxorpc host:", client.URL()) + resp, err := client.Submit.SubmitTx(ctx, req) + if err != nil { + utxorpc.HandleError(err) + } + fmt.Printf("Response: %+v\n", resp) +} + +func readMempool(ctx context.Context, client *utxorpc.UtxorpcClient) { + req := connect.NewRequest(&submit.ReadMempoolRequest{}) + client.AddHeadersToRequest(req) + fmt.Println("Connecting to utxorpc host:", client.URL()) + resp, err := client.Submit.ReadMempool(ctx, req) + if err != nil { + utxorpc.HandleError(err) + } + fmt.Printf("Response: %+v\n", resp) +} + +func waitForTx(ctx context.Context, client *utxorpc.UtxorpcClient) { + req := connect.NewRequest(&submit.WaitForTxRequest{}) + client.AddHeadersToRequest(req) + fmt.Println("Connecting to utxorpc host:", client.URL()) + stream, err := client.Submit.WaitForTx(ctx, req) + if err != nil { + utxorpc.HandleError(err) + } + + fmt.Println("Connected to utxorpc host, watching mempool...") + for stream.Receive() { + resp := stream.Msg() + fmt.Printf("Stream response: %+v\n", resp) + } + + if err := stream.Err(); err != nil { + fmt.Println("Stream ended with error:", err) + } else { + fmt.Println("Stream ended normally.") + } +} + +func watchMempool(ctx context.Context, client *utxorpc.UtxorpcClient) { + req := connect.NewRequest(&submit.WatchMempoolRequest{}) + client.AddHeadersToRequest(req) + fmt.Println("Connecting to utxorpc host:", client.URL()) + stream, err := client.Submit.WatchMempool(ctx, req) + if err != nil { + utxorpc.HandleError(err) + } + + fmt.Println("Connected to utxorpc host, watching mempool...") + for stream.Receive() { + resp := stream.Msg() + fmt.Printf("Stream response: %+v\n", resp) + } + + if err := stream.Err(); err != nil { + fmt.Println("Stream ended with error:", err) + } else { + fmt.Println("Stream ended normally.") + } +} diff --git a/examples/sync/main.go b/examples/sync/main.go index 8ed703d..d19a8ae 100644 --- a/examples/sync/main.go +++ b/examples/sync/main.go @@ -2,67 +2,38 @@ package main import ( "context" - "crypto/tls" "encoding/hex" - "errors" "fmt" "log" - "net" - "net/http" "connectrpc.com/connect" sync "github.com/utxorpc/go-codegen/utxorpc/v1alpha/sync" utxorpc "github.com/utxorpc/go-sdk" - "golang.org/x/net/http2" ) func main() { ctx := context.Background() baseUrl := "https://preview.utxorpc-v0.demeter.run" - // set API key for demeter - apiKey := "dmtr_utxorpc1..." - client := createUtxoRPCClient(baseUrl) + client := utxorpc.CreateUtxoRPCClient(baseUrl, + // set API key for demeter + utxorpc.WithHeaders(map[string]string{ + "dmtr-api-key": "dmtr_utxorpc1...", + }), + ) - fetchBlock(ctx, client, apiKey) - followTip(ctx, client, apiKey, "230eeba5de6b0198f64a3e801f92fa1ebf0f3a42a74dbd1922187249ad3038e7") - followTip(ctx, client, apiKey, "") + fetchBlock(ctx, client) + followTip(ctx, client, "230eeba5de6b0198f64a3e801f92fa1ebf0f3a42a74dbd1922187249ad3038e7") + followTip(ctx, client, "") } -func createHttpClient() *http.Client { - return &http.Client{ - CheckRedirect: func(_ *http.Request, _ []*http.Request) error { - return http.ErrUseLastResponse - }, - Transport: &http2.Transport{ - AllowHTTP: true, - DialTLS: func(network, addr string, _ *tls.Config) (net.Conn, error) { - // If you're also using this client for non-h2c traffic, you may want - // to delegate to tls.Dial if the network isn't TCP or the addr isn't - // in an allowlist. - return net.Dial(network, addr) - }, - }, - } -} - -func createUtxoRPCClient(baseUrl string) *utxorpc.UtxorpcClient { - httpClient := createHttpClient() - client := utxorpc.NewClient(httpClient, baseUrl) - return &client -} - -func setAPIKeyHeader(req connect.AnyRequest, apiKey string) { - req.Header().Set("dmtr-api-key", apiKey) -} - -func fetchBlock(ctx context.Context, client *utxorpc.UtxorpcClient, apiKey string) { +func fetchBlock(ctx context.Context, client *utxorpc.UtxorpcClient) { req := connect.NewRequest(&sync.FetchBlockRequest{}) - setAPIKeyHeader(req, apiKey) + client.AddHeadersToRequest(req) fmt.Println("connecting to utxorpc host:", client.URL()) chainSync, err := client.ChainSync.FetchBlock(ctx, req) if err != nil { - handleError(err) + utxorpc.HandleError(err) } fmt.Println("connected to utxorpc...") for i, blockRef := range chainSync.Msg.Block { @@ -73,7 +44,7 @@ func fetchBlock(ctx context.Context, client *utxorpc.UtxorpcClient, apiKey strin } // FollowTipRequest with Intersect -func followTip(ctx context.Context, client *utxorpc.UtxorpcClient, apiKey string, blockHash string) { +func followTip(ctx context.Context, client *utxorpc.UtxorpcClient, blockHash string) { var req *connect.Request[sync.FollowTipRequest] if blockHash == "" { @@ -91,23 +62,12 @@ func followTip(ctx context.Context, client *utxorpc.UtxorpcClient, apiKey string Intersect: []*sync.BlockRef{blockRef}, }) } - - setAPIKeyHeader(req, apiKey) - + client.AddHeadersToRequest(req) fmt.Println("connecting to utxorpc host:", client.URL()) resp, err := client.ChainSync.FollowTip(ctx, req) if err != nil { - handleError(err) + utxorpc.HandleError(err) } fmt.Println("connected to utxorpc...") fmt.Printf("Response: %+v\n", resp) } - -func handleError(err error) { - fmt.Println(connect.CodeOf(err)) - if connectErr := new(connect.Error); errors.As(err, &connectErr) { - fmt.Println(connectErr.Message()) - fmt.Println(connectErr.Details()) - } - panic(err) -} diff --git a/main.go b/main.go index d97470a..b0ade54 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,10 @@ package sdk import ( + "crypto/tls" + "errors" + "fmt" + "net" "net/http" "connectrpc.com/connect" @@ -8,29 +12,40 @@ import ( "github.com/utxorpc/go-codegen/utxorpc/v1alpha/submit/submitconnect" "github.com/utxorpc/go-codegen/utxorpc/v1alpha/sync/syncconnect" "github.com/utxorpc/go-codegen/utxorpc/v1alpha/watch/watchconnect" + "golang.org/x/net/http2" ) type UtxorpcClient struct { httpClient connect.HTTPClient baseUrl string + headers map[string]string ChainSync syncconnect.ChainSyncServiceClient Query queryconnect.QueryServiceClient Submit submitconnect.SubmitServiceClient Watch watchconnect.WatchServiceClient } -func NewClient(httpClient *http.Client, baseUrl string) UtxorpcClient { - var client UtxorpcClient - chainSyncClient := syncconnect.NewChainSyncServiceClient(httpClient, baseUrl, connect.WithGRPC()) - queryClient := queryconnect.NewQueryServiceClient(httpClient, baseUrl, connect.WithGRPC()) - submitClient := submitconnect.NewSubmitServiceClient(httpClient, baseUrl, connect.WithGRPC()) - watchClient := watchconnect.NewWatchServiceClient(httpClient, baseUrl, connect.WithGRPC()) - client.httpClient = httpClient - client.baseUrl = baseUrl - client.ChainSync = chainSyncClient - client.Query = queryClient - client.Submit = submitClient - client.Watch = watchClient +type ClientOptinon func(*UtxorpcClient) + +func WithHeaders(headers map[string]string) ClientOptinon { + return func(client *UtxorpcClient) { + client.headers = headers + } +} + +func NewClient(httpClient *http.Client, baseUrl string, options ...ClientOptinon) *UtxorpcClient { + client := &UtxorpcClient{ + httpClient: httpClient, + baseUrl: baseUrl, + ChainSync: syncconnect.NewChainSyncServiceClient(httpClient, baseUrl, connect.WithGRPC()), + Query: queryconnect.NewQueryServiceClient(httpClient, baseUrl, connect.WithGRPC()), + Submit: submitconnect.NewSubmitServiceClient(httpClient, baseUrl, connect.WithGRPC()), + Watch: watchconnect.NewWatchServiceClient(httpClient, baseUrl, connect.WithGRPC()), + } + + for _, option := range options { + option(client) + } return client } @@ -41,3 +56,55 @@ func (u *UtxorpcClient) HTTPClient() connect.HTTPClient { func (u *UtxorpcClient) URL() string { return u.baseUrl } + +func createHttpClient() *http.Client { + return &http.Client{ + CheckRedirect: func(_ *http.Request, _ []*http.Request) error { + return http.ErrUseLastResponse + }, + Transport: &http2.Transport{ + AllowHTTP: true, + DialTLS: func(network, addr string, _ *tls.Config) (net.Conn, error) { + // If you're also using this client for non-h2c traffic, you may want + // to delegate to tls.Dial if the network isn't TCP or the addr isn't + // in an allowlist. + return net.Dial(network, addr) + }, + }, + } +} + +func CreateUtxoRPCClient(baseUrl string, options ...ClientOptinon) *UtxorpcClient { + httpClient := createHttpClient() + return NewClient(httpClient, baseUrl, options...) +} + +func (u *UtxorpcClient) SetHeader(key, value string) { + if u.headers == nil { + u.headers = make(map[string]string) + } + u.headers[key] = value +} + +func (u *UtxorpcClient) SetHeaders(headers map[string]string) { + u.headers = headers +} + +func (u *UtxorpcClient) RemoveHeader(key string) { + delete(u.headers, key) +} + +func (u *UtxorpcClient) AddHeadersToRequest(req connect.AnyRequest) { + for key, value := range u.headers { + req.Header().Set(key, value) + } +} + +func HandleError(err error) { + fmt.Println(connect.CodeOf(err)) + if connectErr := new(connect.Error); errors.As(err, &connectErr) { + fmt.Println(connectErr.Message()) + fmt.Println(connectErr.Details()) + } + panic(err) +}