diff --git a/.version b/.version index 8f0916f..a918a2a 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -0.5.0 +0.6.0 diff --git a/README.md b/README.md index f983e7c..d8c5077 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,10 @@ A traditional infrastructure-heavy staking integration can take months. Coinbase Prerequisite: [Go 1.21+](https://go.dev/doc/install) 1. In a fresh directory, copy and paste one of the code samples below or any of our [provided examples](./examples) into an `example.go` file. -2. Create and download an API key from the [Cloud Platform](https://portal.cloud.coinbase.com/access/api). -3. Place the key named `.coinbase_cloud_api_key.json` at the root of your repository. -4. Setup a Go project and run the example :rocket: + +2. Create a new API Key in the [portal](https://portal.cdp.coinbase.com/access/api) and paste the `apiKeyName` and `apiPrivateKey` into the `example.go` file. + +3. Setup a Go project and run the example :rocket: ```shell go mod init example @@ -52,11 +53,16 @@ import ( api "github.com/coinbase/staking-client-library-go/gen/go/coinbase/staking/orchestration/v1" ) +var ( + apiKeyName = "your-api-key-name" + apiPrivateKey = "your-api-private-key" +) + func main() { ctx := context.Background() - // Loads the API key from the default location. - apiKey, err := auth.NewAPIKey(auth.WithLoadAPIKeyFromFile(true)) + // Loads the API key. + apiKey, err := auth.NewAPIKey(auth.WithAPIKeyName(apiKeyName, apiPrivateKey)) if err != nil { log.Fatalf("error loading API key: %s", err.Error()) } @@ -159,13 +165,18 @@ import ( api "github.com/coinbase/staking-client-library-go/gen/go/coinbase/staking/orchestration/v1" ) +var ( + apiKeyName = "your-api-key-name" + apiPrivateKey = "your-api-private-key" +) + func main() { ctx := context.Background() - // Loads the API key from the default location. - apiKey, err := auth.NewAPIKey(auth.WithLoadAPIKeyFromFile(true)) + // Loads the API key. + apiKey, err := auth.NewAPIKey(auth.WithAPIKeyName(apiKeyName, apiPrivateKey)) if err != nil { - log.Fatalf("error loading API key: %s", err.Error()) + log.Fatalf("error loading API key: %s", err.Error()) } // Creates the Coinbase Staking API client. @@ -273,6 +284,9 @@ import ( ) const ( + apiKeyName = "your-api-key-name" + apiPrivateKey = "your-api-private-key" + // https://beaconcha.in/validator/1 address = "0xa1d1ad0714035353258038e964ae9675dc0252ee22cea896825c01458e1807bfad2f9969338798548d9858a571f7425c" ) @@ -280,10 +294,10 @@ const ( func main() { ctx := context.Background() - // Loads the API key from the default location. - apiKey, err := auth.NewAPIKey(auth.WithLoadAPIKeyFromFile(true)) + // Loads the API key. + apiKey, err := auth.NewAPIKey(auth.WithAPIKeyName(apiKeyName, apiPrivateKey)) if err != nil { - log.Fatalf("error loading API key: %s", err.Error()) + log.Fatalf("error loading API key: %s", err.Error()) } // Creates the Coinbase Staking API client @@ -388,7 +402,7 @@ func main() { ## Documentation -There are numerous examples in the [`examples directory`](./examples) to help get you started. For even more, refer to our [documentation website](https://docs.cdp.coinbase.com/staking/docs/welcome) for detailed definitions, API specifications, integration guides, and more! +There are numerous examples in the [`examples directory`](./examples) to help get you started. For even more, refer to our [documentation website](https://docs.cdp.coinbase.com/staking/docs/welcome) for detailed definitions, [API specification](https://docs.cdp.coinbase.com/staking/reference), integration guides, and more! ## Contributing diff --git a/auth/api_key.go b/auth/api_key.go index af08c13..3948193 100644 --- a/auth/api_key.go +++ b/auth/api_key.go @@ -55,9 +55,9 @@ func WithLoadAPIKeyFromEnv(loadAPIKeyFromEnv bool) APIKeyOption { // Key from file directly. If the API Key name and private key are both set, // they take precedence over the environment variables. Next if the env vars are // set they take precedence or else the file is used if set. -func WithLoadAPIKeyFromFile(loadAPIKeyFromFile bool) APIKeyOption { +func WithLoadAPIKeyFromFile() APIKeyOption { return func(t *apiKeyConfig) { - t.loadAPIKeyFromFile = loadAPIKeyFromFile + t.loadAPIKeyFromFile = true } } diff --git a/auth/authenticator.go b/auth/authenticator.go index 62fa0aa..61ae2f7 100644 --- a/auth/authenticator.go +++ b/auth/authenticator.go @@ -7,6 +7,7 @@ import ( "fmt" "math" "math/big" + "strings" "time" "gopkg.in/square/go-jose.v2" @@ -33,7 +34,12 @@ func NewAuthenticator(apiKey *APIKey) *Authenticator { // BuildJWT constructs and returns the JWT as a string along with an error, if any. func (a *Authenticator) BuildJWT(service, uri string) (string, error) { - block, _ := pem.Decode([]byte(a.apiKey.PrivateKey)) + privateKey := a.apiKey.PrivateKey + + // Replace escaped newline sequence + privateKey = strings.ReplaceAll(privateKey, "\\n", "\n") + + block, _ := pem.Decode([]byte(privateKey)) if block == nil { return "", fmt.Errorf("could not decode private key") } diff --git a/examples/cosmos/list-rewards/main.go b/examples/cosmos/list-rewards/main.go index 247d195..90b31da 100644 --- a/examples/cosmos/list-rewards/main.go +++ b/examples/cosmos/list-rewards/main.go @@ -23,6 +23,9 @@ import ( */ const ( + apiKeyName = "your-api-key-name" + apiPrivateKey = "your-api-private-key" + // https://atomscan.com/validators/cosmosvaloper1crqm3598z6qmyn2kkcl9dz7uqs4qdqnr6s8jdn address = "cosmos1crqm3598z6qmyn2kkcl9dz7uqs4qdqnrlyn8pq" ) @@ -30,15 +33,14 @@ const ( func main() { ctx := context.Background() - apiKey, err := auth.NewAPIKey(auth.WithLoadAPIKeyFromFile(true)) + // Loads the API key. + apiKey, err := auth.NewAPIKey(auth.WithAPIKeyName(apiKeyName, apiPrivateKey)) if err != nil { log.Fatalf("error loading API key: %s", err.Error()) } - authOpt := options.WithAPIKey(apiKey) - // Create a staking client. - stakingClient, err := client.New(ctx, authOpt) + stakingClient, err := client.New(ctx, options.WithAPIKey(apiKey)) if err != nil { log.Fatalf("error instantiating staking client: %s", err.Error()) } diff --git a/examples/cosmos/list-stakes/main.go b/examples/cosmos/list-stakes/main.go index 6f3d76e..63dca50 100644 --- a/examples/cosmos/list-stakes/main.go +++ b/examples/cosmos/list-stakes/main.go @@ -22,6 +22,9 @@ import ( */ const ( + apiKeyName = "your-api-key-name" + apiPrivateKey = "your-api-private-key" + // https://atomscan.com/validators/cosmosvaloper1crqm3598z6qmyn2kkcl9dz7uqs4qdqnr6s8jdn address = "cosmos1crqm3598z6qmyn2kkcl9dz7uqs4qdqnrlyn8pq" ) @@ -29,15 +32,14 @@ const ( func main() { ctx := context.Background() - apiKey, err := auth.NewAPIKey(auth.WithLoadAPIKeyFromFile(true)) + // Loads the API key. + apiKey, err := auth.NewAPIKey(auth.WithAPIKeyName(apiKeyName, apiPrivateKey)) if err != nil { log.Fatalf("error loading API key: %s", err.Error()) } - authOpt := options.WithAPIKey(apiKey) - // Create a staking client. - stakingClient, err := client.New(ctx, authOpt) + stakingClient, err := client.New(ctx, options.WithAPIKey(apiKey)) if err != nil { log.Fatalf("error instantiating staking client: %s", err.Error()) } diff --git a/examples/ethereum/create-and-process-workflow/main.go b/examples/ethereum/create-and-process-workflow/main.go index b772285..64a9f1a 100644 --- a/examples/ethereum/create-and-process-workflow/main.go +++ b/examples/ethereum/create-and-process-workflow/main.go @@ -21,8 +21,11 @@ import ( ) const ( - // TODO: Replace with your private key. - privateKey = "" + apiKeyName = "your-api-key-name" + apiPrivateKey = "your-api-private-key" + + // TODO: Replace with your wallet's private key. + walletPrivateKey = "" // TODO: Replace with your staker addresses and amount. stakerAddress = "" @@ -34,21 +37,20 @@ const ( func main() { ctx := context.Background() - apiKey, err := auth.NewAPIKey(auth.WithLoadAPIKeyFromFile(true)) + // Loads the API key. + apiKey, err := auth.NewAPIKey(auth.WithAPIKeyName(apiKeyName, apiPrivateKey)) if err != nil { log.Fatalf("error loading API key: %s", err.Error()) } - authOpt := options.WithAPIKey(apiKey) - // Create a staking client. - stakingClient, err := client.New(ctx, authOpt) + stakingClient, err := client.New(ctx, options.WithAPIKey(apiKey)) if err != nil { log.Fatalf("error instantiating staking client: %s", err.Error()) } - if privateKey == "" || stakerAddress == "" { - log.Fatalf("privateKey stakerAddress must be set") + if walletPrivateKey == "" || stakerAddress == "" { + log.Fatalf("walletPrivateKey stakerAddress must be set") } req := &api.CreateWorkflowRequest{ @@ -96,7 +98,7 @@ func main() { // Logic to sign the transaction. This can be substituted with any other signing mechanism. log.Printf("Signing unsigned tx %s ...\n", unsignedTx) - signedTx, err := signer.New("ethereum_kiln").SignTransaction([]string{privateKey}, &signer.UnsignedTx{Data: []byte(unsignedTx)}) + signedTx, err := signer.New("ethereum_kiln").SignTransaction([]string{walletPrivateKey}, &signer.UnsignedTx{Data: []byte(unsignedTx)}) if err != nil { log.Fatalf(fmt.Errorf("error signing transaction: %w", err).Error()) } diff --git a/examples/ethereum/create-workflow/main.go b/examples/ethereum/create-workflow/main.go index 86b32ed..c9c07af 100644 --- a/examples/ethereum/create-workflow/main.go +++ b/examples/ethereum/create-workflow/main.go @@ -8,18 +8,24 @@ import ( "context" "log" + "google.golang.org/protobuf/encoding/protojson" + "github.com/coinbase/staking-client-library-go/auth" "github.com/coinbase/staking-client-library-go/client" "github.com/coinbase/staking-client-library-go/client/options" api "github.com/coinbase/staking-client-library-go/gen/go/coinbase/staking/orchestration/v1" - "google.golang.org/protobuf/encoding/protojson" +) + +var ( + apiKeyName = "your-api-key-name" + apiPrivateKey = "your-api-private-key" ) func main() { ctx := context.Background() - // Loads the API key from the default location. - apiKey, err := auth.NewAPIKey(auth.WithLoadAPIKeyFromFile(true)) + // Loads the API key. + apiKey, err := auth.NewAPIKey(auth.WithAPIKeyName(apiKeyName, apiPrivateKey)) if err != nil { log.Fatalf("error loading API key: %s", err.Error()) } diff --git a/examples/ethereum/list-rewards/main.go b/examples/ethereum/list-rewards/main.go index ee9b1f0..da115bf 100644 --- a/examples/ethereum/list-rewards/main.go +++ b/examples/ethereum/list-rewards/main.go @@ -27,6 +27,9 @@ import ( */ const ( + apiKeyName = "your-api-key-name" + apiPrivateKey = "your-api-private-key" + // https://beaconcha.in/validator/1 address = "0xa1d1ad0714035353258038e964ae9675dc0252ee22cea896825c01458e1807bfad2f9969338798548d9858a571f7425c" ) @@ -34,8 +37,8 @@ const ( func main() { ctx := context.Background() - // Loads the API key from the default location. - apiKey, err := auth.NewAPIKey(auth.WithLoadAPIKeyFromFile(true)) + // Loads the API key. + apiKey, err := auth.NewAPIKey(auth.WithAPIKeyName(apiKeyName, apiPrivateKey)) if err != nil { log.Fatalf("error loading API key: %s", err.Error()) } diff --git a/examples/ethereum/list-stakes/main.go b/examples/ethereum/list-stakes/main.go index 5a13d22..6940999 100644 --- a/examples/ethereum/list-stakes/main.go +++ b/examples/ethereum/list-stakes/main.go @@ -26,6 +26,9 @@ import ( */ const ( + apiKeyName = "your-api-key-name" + apiPrivateKey = "your-api-private-key" + // https://beaconcha.in/validator/1 address = "0xa1d1ad0714035353258038e964ae9675dc0252ee22cea896825c01458e1807bfad2f9969338798548d9858a571f7425c" ) @@ -33,15 +36,14 @@ const ( func main() { ctx := context.Background() - apiKey, err := auth.NewAPIKey(auth.WithLoadAPIKeyFromFile(true)) + // Loads the API key. + apiKey, err := auth.NewAPIKey(auth.WithAPIKeyName(apiKeyName, apiPrivateKey)) if err != nil { log.Fatalf("error loading API key: %s", err.Error()) } - authOpt := options.WithAPIKey(apiKey) - // Create a staking client. - stakingClient, err := client.New(ctx, authOpt) + stakingClient, err := client.New(ctx, options.WithAPIKey(apiKey)) if err != nil { log.Fatalf("error instantiating staking client: %s", err.Error()) } diff --git a/examples/hello-world/main.go b/examples/hello-world/main.go index 53f123c..d7e3874 100644 --- a/examples/hello-world/main.go +++ b/examples/hello-world/main.go @@ -16,19 +16,23 @@ import ( "google.golang.org/api/iterator" ) +var ( + apiKeyName = "your-api-key-name" + apiPrivateKey = "your-api-private-key" +) + // An example function that verifies the API key has been configured correctly and you can connect to the Coinbase Staking API. func main() { ctx := context.Background() - apiKey, err := auth.NewAPIKey(auth.WithLoadAPIKeyFromFile(true)) + // Loads the API key. + apiKey, err := auth.NewAPIKey(auth.WithAPIKeyName(apiKeyName, apiPrivateKey)) if err != nil { log.Fatalf("error loading API key: %s", err.Error()) } - authOpt := options.WithAPIKey(apiKey) - // Create a staking client. - stakingClient, err := client.New(ctx, authOpt) + stakingClient, err := client.New(ctx, options.WithAPIKey(apiKey)) if err != nil { log.Fatalf("error instantiating staking client: %s", err.Error()) } diff --git a/examples/list-workflows/main.go b/examples/list-workflows/main.go index 0e914f2..d789627 100644 --- a/examples/list-workflows/main.go +++ b/examples/list-workflows/main.go @@ -16,19 +16,23 @@ import ( "google.golang.org/api/iterator" ) +var ( + apiKeyName = "your-api-key-name" + apiPrivateKey = "your-api-private-key" +) + // An example function to demonstrate how to use the staking client libraries. func main() { ctx := context.Background() - apiKey, err := auth.NewAPIKey(auth.WithLoadAPIKeyFromFile(true)) + // Loads the API key. + apiKey, err := auth.NewAPIKey(auth.WithAPIKeyName(apiKeyName, apiPrivateKey)) if err != nil { log.Fatalf("error loading API key: %s", err.Error()) } - authOpt := options.WithAPIKey(apiKey) - // Create a staking client. - stakingClient, err := client.New(ctx, authOpt) + stakingClient, err := client.New(ctx, options.WithAPIKey(apiKey)) if err != nil { log.Fatalf("error instantiating staking client: %s", err.Error()) } diff --git a/examples/solana/create-and-process-workflow/main.go b/examples/solana/create-and-process-workflow/main.go index 9ef5115..a126f75 100644 --- a/examples/solana/create-and-process-workflow/main.go +++ b/examples/solana/create-and-process-workflow/main.go @@ -21,8 +21,11 @@ import ( ) const ( - // TODO: Replace with your private key. - privateKey = "" + apiKeyName = "your-api-key-name" + apiPrivateKey = "your-api-private-key" + + // TODO: Replace with your wallet's private key. + walletPrivateKey = "" // TODO: Replace with your wallet addresses and amount. walletAddress = "" @@ -34,21 +37,20 @@ const ( func main() { ctx := context.Background() - apiKey, err := auth.NewAPIKey(auth.WithLoadAPIKeyFromFile(true)) + // Loads the API key. + apiKey, err := auth.NewAPIKey(auth.WithAPIKeyName(apiKeyName, apiPrivateKey)) if err != nil { log.Fatalf("error loading API key: %s", err.Error()) } - authOpt := options.WithAPIKey(apiKey) - // Create a staking client. - stakingClient, err := client.New(ctx, authOpt) + stakingClient, err := client.New(ctx, options.WithAPIKey(apiKey)) if err != nil { log.Fatalf("error instantiating staking client: %s", err.Error()) } - if privateKey == "" || walletAddress == "" { - log.Fatalf("privateKey and walletAddress must be set") + if walletPrivateKey == "" || walletAddress == "" { + log.Fatalf("walletPrivateKey and walletAddress must be set") } req := &api.CreateWorkflowRequest{ @@ -96,7 +98,7 @@ func main() { // Logic to sign the transaction. This can be substituted with any other signing mechanism. log.Printf("Signing unsigned tx %s ...\n", unsignedTx) - signedTx, err := signer.New("solana").SignTransaction([]string{privateKey}, &signer.UnsignedTx{Data: []byte(unsignedTx)}) + signedTx, err := signer.New("solana").SignTransaction([]string{walletPrivateKey}, &signer.UnsignedTx{Data: []byte(unsignedTx)}) if err != nil { log.Fatalf(fmt.Errorf("error signing transaction: %w", err).Error()) } diff --git a/examples/solana/create-workflow/main.go b/examples/solana/create-workflow/main.go index b9e9c5f..b2f87c4 100644 --- a/examples/solana/create-workflow/main.go +++ b/examples/solana/create-workflow/main.go @@ -18,6 +18,9 @@ import ( ) const ( + apiKeyName = "your-api-key-name" + apiPrivateKey = "your-api-private-key" + // TODO: Replace with your wallet addresses and amount. walletAddress = "8rMGARtkJY5QygP1mgvBFLsE9JrvXByARJiyNfcSE5Z" amount = "100000000" @@ -28,8 +31,8 @@ const ( func main() { ctx := context.Background() - // Loads the API key from the default location. - apiKey, err := auth.NewAPIKey(auth.WithLoadAPIKeyFromFile(true)) + // Loads the API key. + apiKey, err := auth.NewAPIKey(auth.WithAPIKeyName(apiKeyName, apiPrivateKey)) if err != nil { log.Fatalf("error loading API key: %s", err.Error()) } diff --git a/examples/solana/list-rewards/main.go b/examples/solana/list-rewards/main.go index 5554357..c2c3341 100644 --- a/examples/solana/list-rewards/main.go +++ b/examples/solana/list-rewards/main.go @@ -28,6 +28,9 @@ import ( */ const ( + apiKeyName = "your-api-key-name" + apiPrivateKey = "your-api-private-key" + // https://solanabeach.io/validator/6D2jqw9hyVCpppZexquxa74Fn33rJzzBx38T58VucHx9 address = "6D2jqw9hyVCpppZexquxa74Fn33rJzzBx38T58VucHx9" ) @@ -35,15 +38,14 @@ const ( func main() { ctx := context.Background() - apiKey, err := auth.NewAPIKey(auth.WithLoadAPIKeyFromFile(true)) + // Loads the API key. + apiKey, err := auth.NewAPIKey(auth.WithAPIKeyName(apiKeyName, apiPrivateKey)) if err != nil { log.Fatalf("error loading API key: %s", err.Error()) } - authOpt := options.WithAPIKey(apiKey) - // Create a staking client. - stakingClient, err := client.New(ctx, authOpt) + stakingClient, err := client.New(ctx, options.WithAPIKey(apiKey)) if err != nil { log.Fatalf("error instantiating staking client: %s", err.Error()) }