From 139aa3f08add25eca1dc75346a0db3fdb0f686e9 Mon Sep 17 00:00:00 2001
From: Rohit Durvasula <88731568+drohit-cb@users.noreply.github.com>
Date: Thu, 2 May 2024 15:08:48 -0700
Subject: [PATCH] Remove staking target requirement (#18)
This PR is counterpart to
https://github.com/coinbase/staking-client-library-ts/pull/20
It helps remove the requirement to provide an explicit staking target
(integrator contract address for ethereum partial staking and validator
address for sol) while making staking api calls.
As we move to a self-service world, we want customers to be able to just
come and stake to default staking targets i.e. default integration
contracts on both Holesky and Mainnet in the case of Partial ETH staking
and default coinbase validators on both Devnet and Mainnet in the case
of sol staking.
As a result, we have updated the examples to not take integrator
contract or validator address as required inputs.
---
.github/workflows/{test.yaml => ci.yaml} | 11 +-
.github/workflows/lint.yaml | 28 ---
.github/workflows/release.yaml | 47 ++++++
.version | 1 +
README.md | 78 ++++++++-
.../create-and-process-workflow/main.go | 10 +-
examples/ethereum/create-workflow/main.go | 12 +-
.../create-and-process-workflow/main.go | 159 ++++++++++++++++++
examples/solana/create-workflow/main.go | 115 ++-----------
9 files changed, 320 insertions(+), 141 deletions(-)
rename .github/workflows/{test.yaml => ci.yaml} (76%)
delete mode 100644 .github/workflows/lint.yaml
create mode 100644 .github/workflows/release.yaml
create mode 100644 .version
create mode 100644 examples/solana/create-and-process-workflow/main.go
diff --git a/.github/workflows/test.yaml b/.github/workflows/ci.yaml
similarity index 76%
rename from .github/workflows/test.yaml
rename to .github/workflows/ci.yaml
index 4c06f2f..f14e9ca 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/ci.yaml
@@ -1,4 +1,4 @@
-name: Go Test
+name: CI
on:
push:
@@ -12,7 +12,8 @@ permissions:
contents: read
jobs:
- test:
+ ci:
+ name: ci
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -25,5 +26,11 @@ jobs:
- name: Build
run: go build -v ./...
+ - name: Lint
+ uses: golangci/golangci-lint-action@v4
+ with:
+ version: v1.54.2
+ args: --timeout=3m
+
- name: Test
uses: robherley/go-test-action@v0
diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml
deleted file mode 100644
index 610eff2..0000000
--- a/.github/workflows/lint.yaml
+++ /dev/null
@@ -1,28 +0,0 @@
-name: Go Lint
-
-on:
- push:
- branches:
- - master
- # We only run tests when we open PRs (and not for ex: on every commit)
- # to avoid running workflows too frequently and incurring costs
- pull_request:
-
-permissions:
- contents: read
-
-jobs:
- lint:
- name: lint
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-go@v5
- with:
- go-version: "1.20"
- cache: false
- - name: golangci-lint
- uses: golangci/golangci-lint-action@v4
- with:
- version: v1.54.2
- args: --timeout=3m
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
new file mode 100644
index 0000000..f9c60fd
--- /dev/null
+++ b/.github/workflows/release.yaml
@@ -0,0 +1,47 @@
+name: Version 🔖
+
+on:
+ push:
+ branches:
+ - main
+ paths:
+ - '.version'
+
+concurrency: ${{ github.workflow }}-${{ github.ref }}
+
+jobs:
+ release:
+ runs-on: ubuntu-latest
+ environment: release
+ permissions:
+ contents: write
+ id-token: write
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Get version from .version file
+ id: release_version
+ run: echo "VERSION=$(cat .version")" >> $GITHUB_OUTPUT
+
+ - name: Check if tag exists
+ id: check_tag
+ run: |
+ git fetch --tags
+ if git rev-parse "v${{ steps.release_version.outputs.VERSION }}" >/dev/null 2>&1; then
+ echo "::set-output name=EXISTS::true"
+ fi
+
+ - name: Create Release
+ if: steps.check_tag.outputs.EXISTS != 'true'
+ uses: softprops/action-gh-release@v2
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ tag_name: v${{ steps.release_version.outputs.VERSION }}
+ name: Release v${{ steps.release_version.outputs.VERSION }}
+ draft: false
+ prerelease: false
+ generate_release_notes: true
+ make_latest: true
diff --git a/.version b/.version
new file mode 100644
index 0000000..8f0916f
--- /dev/null
+++ b/.version
@@ -0,0 +1 @@
+0.5.0
diff --git a/README.md b/README.md
index 87323ff..3538a20 100644
--- a/README.md
+++ b/README.md
@@ -64,7 +64,6 @@ func main() {
Parameters: &api.EthereumKilnStakingParameters_StakeParameters{
StakeParameters: &api.EthereumKilnStakeParameters{
StakerAddress: "0xdb816889F2a7362EF242E5a717dfD5B38Ae849FE",
- IntegratorContractAddress: "0xA55416de5DE61A0AC1aa8970a280E04388B1dE4b",
Amount: &api.Amount{
Value: "20",
Currency: "ETH",
@@ -91,7 +90,82 @@ func main() {
Output
```text
- 2024/03/28 11:43:49 Workflow created: projects/62376b2f-3f24-42c9-9025-d576a3c06d6f/workflows/ffbf9b45-c57b-49cb-a4d5-fdab66d8cb25
+ 2024/03/28 11:43:49 Workflow created: workflows/ffbf9b45-c57b-49cb-a4d5-fdab66d8cb25
+ ```
+
+
+
+### Stake SOL :diamond_shape_with_a_dot_inside:
+
+This code sample creates an SOL staking workflow. View the full code sample [here](examples/solana/create-workflow/main.go)
+
+
+ Code Sample
+
+```golang
+// examples/solana/create-workflow/main.go
+package main
+
+import (
+ "context"
+ "log"
+
+ "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"
+)
+
+func main() {
+ ctx := context.Background()
+
+ // Loads the API key from the default location.
+ apiKey, err := auth.NewAPIKey(auth.WithLoadAPIKeyFromFile(true))
+ if err != nil {
+ log.Fatalf("error loading API key: %s", err.Error())
+ }
+
+ // Creates the Coinbase Staking API client.
+ stakingClient, err := client.New(ctx, options.WithAPIKey(apiKey))
+ if err != nil {
+ log.Fatalf("error instantiating staking client: %s", err.Error())
+ }
+
+ req := &api.CreateWorkflowRequest{
+ Workflow: &api.Workflow{
+ Action: "protocols/solana/networks/devnet/actions/stake",
+ StakingParameters: &api.Workflow_SolanaStakingParameters{
+ SolanaStakingParameters: &api.SolanaStakingParameters{
+ Parameters: &api.SolanaStakingParameters_StakeParameters{
+ StakeParameters: &api.SolanaStakeParameters{
+ WalletAddress: walletAddress,
+ Amount: &api.Amount{
+ Value: amount,
+ Currency: currency,
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ workflow, err := stakingClient.Orchestration.CreateWorkflow(ctx, req)
+ if err != nil {
+ log.Fatalf("couldn't create workflow: %s", err.Error())
+ }
+
+ log.Printf("Workflow created: %s", workflow.Name)
+}
+```
+
+
+
+
+ Output
+
+ ```text
+ 2024/03/28 11:43:49 Workflow created: workflows/6bd9fd82-8b9d-4a49-9039-f95bb850a7a2
```
diff --git a/examples/ethereum/create-and-process-workflow/main.go b/examples/ethereum/create-and-process-workflow/main.go
index fbf0fff..b772285 100644
--- a/examples/ethereum/create-and-process-workflow/main.go
+++ b/examples/ethereum/create-and-process-workflow/main.go
@@ -25,10 +25,9 @@ const (
privateKey = ""
// TODO: Replace with your staker addresses and amount.
- stakerAddress = ""
- integratorContractAddress = "0xA55416de5DE61A0AC1aa8970a280E04388B1dE4b"
- amount = "11"
- currency = "ETH"
+ stakerAddress = ""
+ amount = "11"
+ currency = "ETH"
)
// An example function to demonstrate how to use the staking client libraries.
@@ -59,8 +58,7 @@ func main() {
EthereumKilnStakingParameters: &api.EthereumKilnStakingParameters{
Parameters: &api.EthereumKilnStakingParameters_StakeParameters{
StakeParameters: &api.EthereumKilnStakeParameters{
- StakerAddress: stakerAddress,
- IntegratorContractAddress: integratorContractAddress,
+ StakerAddress: stakerAddress,
Amount: &api.Amount{
Value: amount,
Currency: currency,
diff --git a/examples/ethereum/create-workflow/main.go b/examples/ethereum/create-workflow/main.go
index 216f365..eb48f6b 100644
--- a/examples/ethereum/create-workflow/main.go
+++ b/examples/ethereum/create-workflow/main.go
@@ -8,6 +8,8 @@ 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"
@@ -36,8 +38,7 @@ func main() {
EthereumKilnStakingParameters: &api.EthereumKilnStakingParameters{
Parameters: &api.EthereumKilnStakingParameters_StakeParameters{
StakeParameters: &api.EthereumKilnStakeParameters{
- StakerAddress: "0xdb816889F2a7362EF242E5a717dfD5B38Ae849FE",
- IntegratorContractAddress: "0xA55416de5DE61A0AC1aa8970a280E04388B1dE4b",
+ StakerAddress: "0xdb816889F2a7362EF242E5a717dfD5B38Ae849FE",
Amount: &api.Amount{
Value: "20",
Currency: "ETH",
@@ -54,5 +55,10 @@ func main() {
log.Fatalf("couldn't create workflow: %s", err.Error())
}
- log.Printf("Workflow created: %s", workflow.Name)
+ marshaled, err := protojson.MarshalOptions{Indent: " ", Multiline: true}.Marshal(workflow)
+ if err != nil {
+ log.Fatalf("error marshaling reward: %s", err.Error())
+ }
+
+ log.Printf("Workflow created: \n%s", marshaled)
}
diff --git a/examples/solana/create-and-process-workflow/main.go b/examples/solana/create-and-process-workflow/main.go
new file mode 100644
index 0000000..9ef5115
--- /dev/null
+++ b/examples/solana/create-and-process-workflow/main.go
@@ -0,0 +1,159 @@
+/*
+ * This example code, demonstrates staking client library usage for performing e2e staking on Solana.
+ */
+
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "os"
+ "time"
+
+ "github.com/coinbase/staking-client-library-go/auth"
+ "github.com/coinbase/staking-client-library-go/client"
+ stakingerrors "github.com/coinbase/staking-client-library-go/client/errors"
+ "github.com/coinbase/staking-client-library-go/client/options"
+ "github.com/coinbase/staking-client-library-go/client/orchestration"
+ api "github.com/coinbase/staking-client-library-go/gen/go/coinbase/staking/orchestration/v1"
+ "github.com/coinbase/staking-client-library-go/internal/signer"
+)
+
+const (
+ // TODO: Replace with your private key.
+ privateKey = ""
+
+ // TODO: Replace with your wallet addresses and amount.
+ walletAddress = ""
+ amount = "100000000"
+ currency = "SOL"
+)
+
+// An example function to demonstrate how to use the staking client libraries.
+func main() {
+ ctx := context.Background()
+
+ apiKey, err := auth.NewAPIKey(auth.WithLoadAPIKeyFromFile(true))
+ 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)
+ if err != nil {
+ log.Fatalf("error instantiating staking client: %s", err.Error())
+ }
+
+ if privateKey == "" || walletAddress == "" {
+ log.Fatalf("privateKey and walletAddress must be set")
+ }
+
+ req := &api.CreateWorkflowRequest{
+ Workflow: &api.Workflow{
+ Action: "protocols/solana/networks/devnet/actions/stake",
+ StakingParameters: &api.Workflow_SolanaStakingParameters{
+ SolanaStakingParameters: &api.SolanaStakingParameters{
+ Parameters: &api.SolanaStakingParameters_StakeParameters{
+ StakeParameters: &api.SolanaStakeParameters{
+ WalletAddress: walletAddress,
+ Amount: &api.Amount{
+ Value: amount,
+ Currency: currency,
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ workflow, err := stakingClient.Orchestration.CreateWorkflow(ctx, req)
+ if err != nil {
+ sae := stakingerrors.FromError(err)
+ _ = sae.Print()
+ os.Exit(1)
+ }
+
+ log.Printf("Workflow created %s ...\n", workflow.Name)
+
+ // Run loop until workflow reaches a terminal state
+ for {
+ // Get the latest workflow state
+ workflow, err = stakingClient.Orchestration.GetWorkflow(ctx, &api.GetWorkflowRequest{Name: workflow.Name})
+ if err != nil {
+ log.Fatalf(fmt.Errorf("error getting workflow: %w", err).Error())
+ }
+
+ printWorkflowProgressDetails(workflow)
+
+ // If workflow is in WAITING_FOR_EXT_BROADCAST state, sign, broadcast the transaction and update the workflow.
+ if orchestration.WorkflowWaitingForExternalBroadcast(workflow) {
+ unsignedTx := workflow.Steps[workflow.GetCurrentStepId()].GetTxStepOutput().GetUnsignedTx()
+
+ // 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)})
+ if err != nil {
+ log.Fatalf(fmt.Errorf("error signing transaction: %w", err).Error())
+ }
+
+ // Add logic to broadcast the tx here.
+ fmt.Printf("Please broadcast this signed tx %s externally and return back the tx hash via the PerformWorkflowStep API ...\n", signedTx)
+ break
+ } else if orchestration.WorkflowFinished(workflow) {
+ break
+ }
+
+ // Sleep for 1 second before polling for workflow status again
+ time.Sleep(1 * time.Second)
+ }
+}
+
+func printWorkflowProgressDetails(workflow *api.Workflow) {
+ if len(workflow.GetSteps()) <= 0 {
+ fmt.Println("Waiting for steps to be created ...")
+ time.Sleep(2 * time.Second)
+ }
+
+ step := workflow.Steps[workflow.GetCurrentStepId()]
+
+ createTime := workflow.GetCreateTime().AsTime()
+ updateTime := workflow.GetUpdateTime().AsTime()
+ runtime := updateTime.Sub(createTime)
+
+ var stepDetails string
+
+ switch step.GetOutput().(type) {
+ case *api.WorkflowStep_TxStepOutput:
+ stepDetails = fmt.Sprintf("state: %s tx hash: %s",
+ step.GetTxStepOutput().GetState().String(),
+ step.GetTxStepOutput().GetTxHash(),
+ )
+ case *api.WorkflowStep_WaitStepOutput:
+ stepDetails = fmt.Sprintf("state: %s current: %d target: %d",
+ step.GetWaitStepOutput().GetState().String(),
+ step.GetWaitStepOutput().GetCurrent(),
+ step.GetWaitStepOutput().GetTarget(),
+ )
+ }
+
+ if orchestration.WorkflowFinished(workflow) {
+ log.Printf("Workflow reached end state - step name: %s %s workflow state: %s runtime: %v\n",
+ step.GetName(),
+ stepDetails,
+ workflow.GetState().String(),
+ runtime,
+ )
+ } else {
+ log.Printf("Waiting for workflow to finish - step name: %s %s workflow state: %s runtime: %v\n",
+ step.GetName(),
+ stepDetails,
+ workflow.GetState().String(),
+ runtime,
+ )
+ }
+}
diff --git a/examples/solana/create-workflow/main.go b/examples/solana/create-workflow/main.go
index e1dd815..da39e6d 100644
--- a/examples/solana/create-workflow/main.go
+++ b/examples/solana/create-workflow/main.go
@@ -6,61 +6,49 @@ package main
import (
"context"
- "fmt"
"log"
- "os"
- "time"
+
+ "google.golang.org/protobuf/encoding/protojson"
"github.com/coinbase/staking-client-library-go/auth"
"github.com/coinbase/staking-client-library-go/client"
stakingerrors "github.com/coinbase/staking-client-library-go/client/errors"
"github.com/coinbase/staking-client-library-go/client/options"
- "github.com/coinbase/staking-client-library-go/client/orchestration"
api "github.com/coinbase/staking-client-library-go/gen/go/coinbase/staking/orchestration/v1"
- "github.com/coinbase/staking-client-library-go/internal/signer"
+ "os"
)
const (
- // TODO: Replace with your private key.
- privateKey = ""
-
// TODO: Replace with your wallet addresses and amount.
- walletAddress = ""
- validatorAddress = "GkqYQysEGmuL6V2AJoNnWZUz2ZBGWhzQXsJiXm2CLKAN"
- amount = "100000000"
- currency = "SOL"
+ walletAddress = "8rMGARtkJY5QygP1mgvBFLsE9JrvXByARJiyNfcSE5Z"
+ amount = "100000000"
+ currency = "SOL"
)
// An example function to demonstrate how to use the staking client libraries.
func main() {
ctx := context.Background()
+ // Loads the API key from the default location.
apiKey, err := auth.NewAPIKey(auth.WithLoadAPIKeyFromFile(true))
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)
+ // Creates the Coinbase Staking API client.
+ 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")
- }
-
req := &api.CreateWorkflowRequest{
Workflow: &api.Workflow{
- Action: "protocols/solana/networks/testnet/actions/stake",
+ Action: "protocols/solana/networks/devnet/actions/stake",
StakingParameters: &api.Workflow_SolanaStakingParameters{
SolanaStakingParameters: &api.SolanaStakingParameters{
Parameters: &api.SolanaStakingParameters_StakeParameters{
StakeParameters: &api.SolanaStakeParameters{
- WalletAddress: walletAddress,
- ValidatorAddress: validatorAddress,
+ WalletAddress: walletAddress,
Amount: &api.Amount{
Value: amount,
Currency: currency,
@@ -79,83 +67,10 @@ func main() {
os.Exit(1)
}
- log.Printf("Workflow created %s ...\n", workflow.Name)
-
- // Run loop until workflow reaches a terminal state
- for {
- // Get the latest workflow state
- workflow, err = stakingClient.Orchestration.GetWorkflow(ctx, &api.GetWorkflowRequest{Name: workflow.Name})
- if err != nil {
- log.Fatalf(fmt.Errorf("error getting workflow: %w", err).Error())
- }
-
- printWorkflowProgressDetails(workflow)
-
- // If workflow is in WAITING_FOR_EXT_BROADCAST state, sign, broadcast the transaction and update the workflow.
- if orchestration.WorkflowWaitingForExternalBroadcast(workflow) {
- unsignedTx := workflow.Steps[workflow.GetCurrentStepId()].GetTxStepOutput().GetUnsignedTx()
-
- // 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)})
- if err != nil {
- log.Fatalf(fmt.Errorf("error signing transaction: %w", err).Error())
- }
-
- // Add logic to broadcast the tx here.
- fmt.Printf("Please broadcast this signed tx %s externally and return back the tx hash via the PerformWorkflowStep API ...\n", signedTx)
- break
- } else if orchestration.WorkflowFinished(workflow) {
- break
- }
-
- // Sleep for 1 second before polling for workflow status again
- time.Sleep(1 * time.Second)
- }
-}
-
-func printWorkflowProgressDetails(workflow *api.Workflow) {
- if len(workflow.GetSteps()) <= 0 {
- fmt.Println("Waiting for steps to be created ...")
- time.Sleep(2 * time.Second)
- }
-
- step := workflow.Steps[workflow.GetCurrentStepId()]
-
- createTime := workflow.GetCreateTime().AsTime()
- updateTime := workflow.GetUpdateTime().AsTime()
- runtime := updateTime.Sub(createTime)
-
- var stepDetails string
-
- switch step.GetOutput().(type) {
- case *api.WorkflowStep_TxStepOutput:
- stepDetails = fmt.Sprintf("state: %s tx hash: %s",
- step.GetTxStepOutput().GetState().String(),
- step.GetTxStepOutput().GetTxHash(),
- )
- case *api.WorkflowStep_WaitStepOutput:
- stepDetails = fmt.Sprintf("state: %s current: %d target: %d",
- step.GetWaitStepOutput().GetState().String(),
- step.GetWaitStepOutput().GetCurrent(),
- step.GetWaitStepOutput().GetTarget(),
- )
+ marshaled, err := protojson.MarshalOptions{Indent: " ", Multiline: true}.Marshal(workflow)
+ if err != nil {
+ log.Fatalf("error marshaling reward: %s", err.Error())
}
- if orchestration.WorkflowFinished(workflow) {
- log.Printf("Workflow reached end state - step name: %s %s workflow state: %s runtime: %v\n",
- step.GetName(),
- stepDetails,
- workflow.GetState().String(),
- runtime,
- )
- } else {
- log.Printf("Waiting for workflow to finish - step name: %s %s workflow state: %s runtime: %v\n",
- step.GetName(),
- stepDetails,
- workflow.GetState().String(),
- runtime,
- )
- }
+ log.Printf("Workflow created: \n%s", marshaled)
}