Workflows
The Orchestration API simplifies the management of staking transactions across various protocols with the help of its core feature: the workflow. A workflow represents a comprehensive backend process designed to manage all tasks required to complete any given staking action end-to-end.
This system is protocol-agnostic, meaning it can adapt to the specific needs of different protocols without requiring a need to introduce new interfaces every time we want to add a new protocol.
What does a workflow look like
Here's an example of a workflow created in order to stake partial eth (amounts less than 32 eth) using the Kiln integration:
How do we start a workflow
Every staking action is represented by a Workflow. To start a workflow, you need to only provide the following information:
-
Action name: The action you want to perform, and the protocol, network you want to do it on. Ex:
protocols/ethereum_kiln/networks/holesky/actions/stake
represents the action of "staking" "eth" on the "holesky" network. -
Staking parameters: The parameters required to perform the action. Ex: For partial eth staking, you need to provide the wallet address you want to stake from, and the amount of eth you want to stake. The details of the parameters required for each action are provided in the protocol specific guides.
Good news - we have 2 clients (Typescript & Go) which make the process of starting a workflow even simpler. Here's an example of what that looks like:
- Typescript
- Golang
import { StakingClient } from '@coinbase/staking-client-library-ts';
const client = new StakingClient();
// The ts client abstracts away the concept of workflows and exposes helper functions
// to expose intents such as stake, unstake etc.
client.Ethereum.stake(network, stakerAddress, amount);
req := &api.CreateWorkflowRequest{
Workflow: &api.Workflow{
Action: "protocols/ethereum_kiln/networks/holesky/actions/stake",
StakingParameters: &api.Workflow_EthereumKilnStakingParameters{
EthereumKilnStakingParameters: &api.EthereumKilnStakingParameters{
Parameters: &api.EthereumKilnStakingParameters_StakeParameters{
StakeParameters: &api.EthereumKilnStakeParameters{
StakerAddress: "0xdb816889F2a7362EF242E5a717dfD5B38Ae849FE",
Amount: &api.Amount{
Value: "20",
Currency: "ETH",
},
},
},
},
},
},
}
workflow, err := stakingClient.Orchestration.CreateWorkflow(ctx, req)
if err != nil {
log.Fatalf("couldn't create workflow: %s", err.Error())
}
How to interact with a workflow
A workflow is designed to consume the staking intent once and from there on independently manage the entire lifecycle of the staking transaction. The only time the workflow needs intervention is when it needs customer input, which typically is when we want a customer to sign and broadcast our unsigned txs.
Here's a rough algorithm of what a typical interaction with a workflow can look like:
-
Create a workflow by providing the action name and staking parameters.
-
Poll and get the workflow to know what the current workflow state is.
-
When the workflow state changes to
WAITING_FOR_EXT_BROADCAST
, you can read and consume the unsigned tx from the step currently being executed. -
If the unsigned tx is as per your needs, you can at this point sign and broadcast it.
-
The workflow has ways of inferencing whether our unsigned tx was indeed signed and broadcasted. If it was, the workflow will mark the current step has completed and move to the next step.
-
-
When there are no more steps left to execute, the workflow will move to the completed state.
Here's an example of a hypothetical workflow comprising 3 steps that are executed serially. The only time the workflow pauses is when it needs the customer to sign and broadcast the unsigned tx from step 1 - rest of the steps are executed independently by the workflow itself.
The Orchestration API as of today, expects the customer to sign and broadcast the unsigned txs generated by the API. If you are a developer, who is interested in broadcasting tx capabilities and would like to use an API developed by us then do reach out to us on our staking discord channel.
Workflow States
The workflow state is a high-level state that describes the current state of the workflow. The workflow state is different from the individual step state, which describes the state of the step that the workflow is currently executing.
State | Definition | Terminal State |
---|---|---|
STATE_IN_PROGRESS | The workflow is making progress and currently does not need any inputs | False |
STATE_WAITING_FOR_EXT_BROADCAST | The workflow is waiting for our generated unsigned tx to be signed and broadcasted | False |
STATE_COMPLETED | The workflow has successfully completed | True |
STATE_FAILED | The workflow has failed at a step - check step details for error | True |
Workflow Steps
Workflows consist of steps, each representing a distinct task that the API automates to facilitate the staking action. These steps can be of different types representing the various tasks that need to be done to complete a staking action. Here are some examples of the types of steps that can be found in a workflow:
- Tx Step
- Wait Step
- Provision Infra Step
Customer input needed: YES
This is the most commonly used step type and is responsible for constructing a non-custodial transactions in the required format. Users need to sign and broadcast this transaction, after which the API monitors to make sure it has landed onchain.
Here's what it looks like within a workflow:
{
"name": "stake tx",
"txStepOutput": {
"unsignedTx": "02f3824268068502540be40085041dad875c83061a8094a55416de5de61a0ac1aa8970a280e04388b1de4b7b843a4b66f1c0808080",
"txHash": "",
"state": "STATE_PENDING_EXT_BROADCAST",
"errorMessage": ""
}
}
The table below details the full list of tx step states that are available and what they mean:
State | Definition | Terminal State |
---|---|---|
STATE_NOT_CONSTRUCTED | The unsigned transaction is being constructed | False |
STATE_CONSTRUCTED | The unsigned transaction has been constructed, but not yet available for use | False |
STATE_PENDING_EXT_BROADCAST | The generated unsigned tx is waiting to be signed and broadcasted | False |
STATE_CONFIRMING | The transaction is waiting to be included in a block | False |
STATE_CONFIRMED | The transaction has been confirmed in a block, but is awaiting additional confirmations to be finalized | False |
STATE_FINALIZED | The transaction has the correct number of confirmations and is now considered finalized onchain | True |
STATE_FAILED | The transaction has failed for one or many reasons, with the specific error provided in the step payload | True |
Customer input needed: NO
This step provides a generic way for workflows to implement waiting. There are times when we need to wait for certain onchain periods such as cooldown etc before we can execute the next step. This step provides a way to do that.
Here's what it looks like within a workflow:
{
"name": "wait-for-cooldown-period",
"waitStepOutput": {
"start": 10000,
"current": 15000,
"target": 20000,
"unit": "WAIT_UNIT_CHECKPOINTS",
"state": "STATE_IN_PROGRESS"
}
}
The table below details the full list of wait step states that are available and what they mean:
State | Definition | Terminal State |
---|---|---|
STATE_NOT_STARTED | The waiting has not started yet | False |
STATE_IN_PROGRESS | The step is currently waiting on the corresponding unit such as Blocks, Checkpoints etc to reach the target state | False |
STATE_COMPLETED | The waiting period has finished | True |
Customer input needed: NO
This step provisions the necessary infrastructure for protocols like Ethereum, setting up an Ethereum validator, for example.
Here's what it looks like within a workflow:
{
"name": "provision-validator-infra",
"provisionInfraStepOutput": {
"state": "STATE_IN_PROGRESS"
}
}
The table below details the full list of provision infra step states that are available and what they mean:
State | Definition | Terminal State |
---|---|---|
STATE_IN_PROGRESS | Infrastructure is in the process of being provisioned | False |
STATE_COMPLETED | Infrastructure provisioning has completed successfully | True |
STATE_FAILED | Infrastructure provisioning has finished but failed | True |
End-to-End Staking Example
While this example deals with an individual person wanting to stake, the Staking API is designed in a way for developers to build staking into their applications.
Now that we have explored what Workflows are, how to start them and how to go about gating staking actions, we can look into a complete end-to-end staking example.
Case Study: User "Alice" loves Ethereum and would like to do her part of making the network stronger by staking some ETH. Staking makes the network happy and
also give her some rewards in return. But she realizes that as of today (May 2023) Ethereum only allows multiples of 32 ETH to be staked.
She unfortunately has only 15 ETH in her wallet but would like to be able to stake some part of that. But there's good news - she finds that Coinbase in partnership with Kiln
have launched the Partial ETH Staking
solution that allows her to stake any amount of ETH even lesser than 32 ETH. She's excited and wants to get started.
She finds that Coinbase has a Staking API precisely for this that she would like to leverage. She loves Typescript and finds that Coinbase supports a staking client for it.
Step 1: Set up the Staking Client
import { StakingClient } from '@coinbase/staking-client-library-ts';
const client = new StakingClient();
Step 2: Fetch Staking Context
Alice first needs to fetch her staking context to see if she has enough ETH to stake. Although in this case, she already knows she has 15 ETH, but while trying to programmatically implement an e2e staking action, it's always nice to have pre-checks and make sure all is good. For example, if she was trying to unstake her ETH, she wouldn't know exactly how much her ETH has grown and would need some API to tell her, her current total staked balance.
stakingContext = await client.Ethereum.viewStakingContext('alice-wallet-address', 'holesky');
She finds that she has 15 ETH in her wallet and is willing to stake 10 ETH from it.
Step 3: Start a "Stake" Workflow
Alice starts a workflow to stake 10 ETH.
workflow = await client.Ethereum.stake('holesky', 'alice-wallet-address', '10000000000000000000');
Step 3: Poll Workflow until workflow state is WAITING_FOR_EXT_BROADCAST
or COMPLETED
while (true) {
workflow = await client.getWorkflow(workflowId);
if (workflowWaitingForExternalBroadcast(workflow)) {
unsignedTx = workflow.steps![workflow.currentStepId!].txStepOutput?.unsignedTx || '';
console.log('Please sign and broadcast this unsigned tx %s ...', unsignedTx);
break;
} else if (workflowHasFinished(workflow)) {
console.log('Workflow completed with state %s ...', workflow.state);
break;
}
await new Promise((resolve) => setTimeout(resolve, 1000)); // sleep for 1 second
}
Step 4: Sign and Broadcast the unsigned tx
Alice signs and broadcasts the unsigned tx corresponding to the intent "stake 10 ETH" provided by the workflow.
Step 5: Workflow reaches "COMPLETED" state
Workflow marks the 1st tx step state as FINALIZED
after it finds that Alice's broadcasted tx has landed on chain and
is greater than a safe finalization depth.
Since there are no more steps to execute, the workflow reaches the "COMPLETED" state and the end result is that Alice's 10 ETH is successfully staked.
At this point, Alice can sit back, relax and watch her staked ETH grow and also earn rewards in the process.