forked from coinbase/waas-client-library-go
-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
WaaS client library for Public Preview
- Loading branch information
0 parents
commit 6878252
Showing
128 changed files
with
33,433 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# This workflow will build a golang project | ||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go | ||
|
||
name: Go | ||
|
||
on: | ||
push: | ||
branches: [ "main" ] | ||
pull_request: | ||
branches: [ "main" ] | ||
|
||
jobs: | ||
|
||
build: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
|
||
- name: Set up Go | ||
uses: actions/setup-go@v3 | ||
with: | ||
go-version: 1.18 | ||
|
||
- name: Build | ||
run: go build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
.proto/* | ||
|
||
.idea/* | ||
|
||
waas-client-library-go |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
Copyright (c) 2018-2023 Coinbase, Inc. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
# WaaS Go Client Library | ||
|
||
This repository contains the Protocol Buffer definitions for the Coinbase **Wallet-as-a-Service** (WaaS) | ||
APIs, as well as the Go client libraries generated from them. | ||
|
||
## Overview | ||
|
||
WaaS is Coinbase's suite of cloud APIs for creating, managing, and using Multi-Party Computation | ||
(MPC)-based crypto wallets. Designed for use by nimble start-ups and large enterprise clients alike, | ||
WaaS APIs are self-service and composable, allowing developers to easily create applications that | ||
interact with the blockchain and inherit the maximal security properties of MPC-based key management. | ||
|
||
## Documentation | ||
|
||
For full documentation, refer to [docs.cloud.coinbase.com/waas](https://docs.cloud.coinbase.com/waas/). | ||
|
||
## Prerequisites | ||
|
||
- [Golang 1.17+](https://go.dev/learn/) | ||
- [node 17+](https://nodejs.org/en/download/) | ||
- [yarn 1.22+](https://yarnpkg.com/getting-started/install) | ||
|
||
For iOS development: | ||
- [Xcode 14.0+](https://developer.apple.com/xcode/) | ||
- iOS15.2+ simulator (iPhone 14 recommended) | ||
- [CocoaPods](https://guides.cocoapods.org/using/getting-started.html) | ||
|
||
For Android development: | ||
- [Android Studio](https://developer.android.com/studio) | ||
- 64-bit Android emulator | ||
- [Android NDK 30+](https://developer.android.com/ndk) | ||
|
||
## Repository Structure | ||
- [`auth/`](./auth/) contains the authentication-related code for accessing WaaS APIs. | ||
- [`clients/`](./clients/) contains client instantiation helpers for WaaS APIs. | ||
- [`gen/`](./gen/) contains Go code generated from the Protocol Buffers. | ||
- [`protos/`](./protos/) contains the Protocol Buffers that define the WaaS APIs. | ||
|
||
## Module Installation | ||
``` | ||
go get github.com/coinbase/waas-client-library-go | ||
``` | ||
|
||
## Get Started | ||
To test that your API Key gives you access as expected to the WaaS APIs: | ||
|
||
1. Replace `apiKeyName` and `apiKeyPrivateKey` in [`example.go`](./example.go) with your API Key information. | ||
2. Run `go build`. | ||
3. Run `./waas-client-library-go`. | ||
4. You should see output like the following: | ||
``` | ||
2023/03/17 12:37:35 creating pool... | ||
2023/03/17 12:37:35 created pool: name:"pools/e08c1784-f2be-4b77-8307-a9ea2d8d0017" display_name:"My First Pool" | ||
2023/03/17 12:37:35 listing networks... | ||
2023/03/17 12:37:35 got network: name:"networks/ethereum-goerli" display_name:"Goerli Ethereum Testnet" native_asset:"networks/ethereum-goerli/assets/0c3569d3-b253-5128-a229-543e1e819430" protocol_family:"protocolFamilies/evm" type:TESTNET | ||
2023/03/17 12:37:35 listing first 5 assets on Ethereum Goerli... | ||
2023/03/17 12:37:35 got asset: name:"networks/ethereum-goerli/assets/0c3569d3-b253-5128-a229-543e1e819430" advertised_symbol:"ETH" decimals:18 definition:{asset_type:"native"} | ||
2023/03/17 12:37:35 got asset: name:"networks/ethereum-goerli/assets/adbf9e76-de39-51a0-9e53-5f8ef31b7925" advertised_symbol:"POLY" decimals:18 definition:{asset_type:"erc20" asset_group_id:"0x887CFe31C888EE0780795b7feFF46CE7f9AB556C"} | ||
2023/03/17 12:37:35 got asset: name:"networks/ethereum-goerli/assets/a055a425-fe93-51ae-9099-cf5495db6e79" advertised_symbol:"SIM" decimals:18 definition:{asset_type:"erc20" asset_group_id:"0x0E89BF4135acE3d4d67BF828707746D3855f3a25"} | ||
2023/03/17 12:37:35 got asset: name:"networks/ethereum-goerli/assets/145f3157-a45d-5a77-92be-6b2af8b7af12" advertised_symbol:"TERC20" decimals:18 definition:{asset_type:"erc20" asset_group_id:"0xea100Bec80418680e55D28b655da6CbEF427275f"} | ||
2023/03/17 12:37:35 got asset: name:"networks/ethereum-goerli/assets/20b2830e-53c1-5540-9d1b-0061df3555f6" advertised_symbol:"BETH" decimals:18 definition:{asset_type:"erc20" asset_group_id:"0xED6CCd7e5131073aE67221B1cA195db0fFacc940"} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
package auth | ||
|
||
import ( | ||
"errors" | ||
"os" | ||
) | ||
|
||
const ( | ||
// nameEnvVar is the environment variable for the Coinbase Cloud API Key name. | ||
nameEnvVar = "COINBASE_CLOUD_API_KEY_NAME" | ||
|
||
// privateKeyEnvVar is the environment variable for the Coinbase Cloud API Key private key. | ||
privateKeyEnvVar = "COINBASE_CLOUD_API_KEY_PRIVATE_KEY" | ||
) | ||
|
||
// APIKey represents a Coinbase Cloud API Key. | ||
type APIKey struct { | ||
Name string | ||
PrivateKey string | ||
} | ||
|
||
// apiKeyConfig contains configuration information for constructing the API Key. | ||
type apiKeyConfig struct { | ||
loadApiKeyFromEnv bool | ||
apiKeyName string | ||
apiKeyPrivateKey string | ||
} | ||
|
||
// apiKeyOption is a function that applies changes to a apiKeyConfig. | ||
type apiKeyOption func(t *apiKeyConfig) | ||
|
||
// WithAPIKeyName returns an option to set the API Key. | ||
func WithAPIKeyName(apiKeyName, apiKeyPrivateKey string) apiKeyOption { | ||
return func(t *apiKeyConfig) { | ||
t.apiKeyName = apiKeyName | ||
t.apiKeyPrivateKey = apiKeyPrivateKey | ||
} | ||
} | ||
|
||
// WithLoadAPIKeyFromEnv returns an option to set whether or not to load the API | ||
// Key from environment variables. If the API Key name and private key are both set, | ||
// they take precedence over the environment variables. | ||
func WithLoadAPIKeyFromEnv(loadAPIKeyFromEnv bool) apiKeyOption { | ||
return func(t *apiKeyConfig) { | ||
t.loadApiKeyFromEnv = loadAPIKeyFromEnv | ||
} | ||
} | ||
|
||
// NewAPIKey creates a new Coinbase Cloud API Key based on the provided options. | ||
func NewAPIKey(opts ...apiKeyOption) (*APIKey, error) { | ||
config := &apiKeyConfig{} | ||
|
||
for _, opt := range opts { | ||
opt(config) | ||
} | ||
|
||
if config.apiKeyName != "" && config.apiKeyPrivateKey != "" { | ||
return &APIKey{ | ||
Name: config.apiKeyName, | ||
PrivateKey: config.apiKeyPrivateKey, | ||
}, nil | ||
} | ||
|
||
return loadAPIKeyFromEnv() | ||
} | ||
|
||
// loadAPIKeyFromEnv loads a new Coinbase Cloud API Key from environment variables. | ||
func loadAPIKeyFromEnv() (*APIKey, error) { | ||
name := os.Getenv(nameEnvVar) | ||
if name == "" { | ||
return nil, errors.New("environment variable COINBASE_CLOUD_API_KEY_NAME must be set") | ||
} | ||
|
||
privateKey := os.Getenv(privateKeyEnvVar) | ||
if privateKey == "" { | ||
return nil, errors.New("environment variable COINBASE_CLOUD_API_KEY_PRIVATE_KEY must be set") | ||
} | ||
|
||
return &APIKey{ | ||
Name: name, | ||
PrivateKey: privateKey, | ||
}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package auth | ||
|
||
import ( | ||
"crypto/rand" | ||
"crypto/x509" | ||
"encoding/pem" | ||
"fmt" | ||
"math" | ||
"math/big" | ||
"time" | ||
|
||
"gopkg.in/square/go-jose.v2" | ||
"gopkg.in/square/go-jose.v2/jwt" | ||
) | ||
|
||
// Authenticator builds a JWT based on the APIKey. | ||
type Authenticator struct { | ||
apiKey *APIKey | ||
} | ||
|
||
// APIKeyClaims holds public claim values for a JWT, as well as a URI. | ||
type APIKeyClaims struct { | ||
*jwt.Claims | ||
URI string `json:"uri"` | ||
} | ||
|
||
// NewAuthenticator returns a new Authenticator. | ||
func NewAuthenticator(apiKey *APIKey) *Authenticator { | ||
return &Authenticator{ | ||
apiKey: apiKey, | ||
} | ||
} | ||
|
||
// 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)) | ||
if block == nil { | ||
return "", fmt.Errorf("jwt: Could not decode private key") | ||
} | ||
|
||
if block.Type != "ECDSA Private Key" { | ||
return "", fmt.Errorf("jwt: Bad private key type") | ||
} | ||
|
||
key, err := x509.ParseECPrivateKey(block.Bytes) | ||
if err != nil { | ||
return "", fmt.Errorf("jwt: %w", err) | ||
} | ||
|
||
sig, err := jose.NewSigner( | ||
jose.SigningKey{Algorithm: jose.ES256, Key: key}, | ||
(&jose.SignerOptions{NonceSource: nonceSource{}}).WithType("JWT").WithHeader("kid", a.apiKey.Name), | ||
) | ||
if err != nil { | ||
return "", fmt.Errorf("jwt: %w", err) | ||
} | ||
|
||
cl := &APIKeyClaims{ | ||
Claims: &jwt.Claims{ | ||
Subject: a.apiKey.Name, | ||
Issuer: "coinbase-cloud", | ||
NotBefore: jwt.NewNumericDate(time.Now()), | ||
Expiry: jwt.NewNumericDate(time.Now().Add(1 * time.Minute)), | ||
Audience: jwt.Audience{service}, | ||
}, | ||
URI: uri, | ||
} | ||
jwtString, err := jwt.Signed(sig).Claims(cl).CompactSerialize() | ||
if err != nil { | ||
return "", fmt.Errorf("jwt: %w", err) | ||
} | ||
return jwtString, nil | ||
} | ||
|
||
// nonceSource implements the jose.NonceSource interface. It is used for building | ||
// the JWT. | ||
type nonceSource struct{} | ||
|
||
// Nonce calculates and returns nonce as a string and an error, if any. | ||
func (n nonceSource) Nonce() (string, error) { | ||
r, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) | ||
if err != nil { | ||
return "", err | ||
} | ||
return r.String(), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package clients | ||
|
||
import ( | ||
"net/http" | ||
|
||
"github.com/googleapis/gax-go/v2/apierror" | ||
"google.golang.org/api/googleapi" | ||
) | ||
|
||
// HTTPCode attempts to return the HTTP status code for the given error. | ||
// If it cannot be determined, it returns a 500 (Internal Server Error). | ||
func HTTPCode(err error) int { | ||
if err == nil { | ||
return http.StatusOK | ||
} | ||
|
||
if googleapiErr, ok := err.(*googleapi.Error); ok { | ||
return googleapiErr.Code | ||
} | ||
|
||
return http.StatusInternalServerError | ||
} | ||
|
||
// UnwrapError attempts to unwrap the passed error and return a googleapi error. | ||
// It is expected that the passed error is of type apierror.APIError. | ||
// If it is not, it returns the passed error unchanged. | ||
func UnwrapError(err error) error { | ||
if err == nil { | ||
return nil | ||
} | ||
|
||
if apiErr, ok := err.(*apierror.APIError); ok { //nolint:errorlint | ||
// Unwrap the the `apierror` to extract the HTTP status code and original error message. | ||
unwrappedErr := apiErr.Unwrap() | ||
if googleapiErr, ok := unwrappedErr.(*googleapi.Error); ok { //nolint:errorlint | ||
return googleapiErr | ||
} | ||
} | ||
|
||
return err | ||
} |
Oops, something went wrong.