diff --git a/README.md b/README.md
index 6c11cf8..fd8ac2b 100644
--- a/README.md
+++ b/README.md
@@ -22,14 +22,17 @@ Prerequisite: [Node 20+](https://www.npmjs.com/package/node/v/20.11.1)
```shell
npm install @coinbase/staking-client-library-ts
```
-2. Create and download an API key from the [Coinbase Developer Platform](https://portal.cdp.coinbase.com/access/api).
-3. Place the key named `.coinbase_cloud_api_key.json` at the root of this repository.
-4. Install necessary Typescript dependencies:
+
+2. Install necessary Typescript dependencies:
```shell
- npm install -g ts-node
- npm install -g typescript
- ```
-5. Copy and paste one of the code samples below or any of our [provided examples](./examples/) into an `example.ts` file and run it with `ts-node` :rocket:
+ npm install -g ts-node typescript
+ ```
+
+3. Get your API keys info such as api key name and api private key from here: https://portal.cdp.coinbase.com/access/api.
+ These will be used in order to set up our client later in the example code.
+ For detailed instructions refer to our api key setup guide [here](https://docs.cdp.coinbase.com/developer-platform/docs/cdp-keys).
+
+4. Copy and paste one of the code samples below or any of our [provided examples](./examples/) into an `example.ts` file and run it with `ts-node` :rocket:
```shell
ts-node example.ts
```
@@ -45,7 +48,11 @@ This code sample creates an ETH staking workflow. View the full code sample [her
// examples/ethereum/create-workflow.ts
import { StakingClient } from "@coinbase/staking-client-library-ts";
-const client = new StakingClient();
+// Set your api key name and private key here. Get your keys from here: https://portal.cdp.coinbase.com/access/api
+const apiKeyName: string = 'your-api-key-name';
+const apiPrivateKey: string = 'your-api-private-key';
+
+const client = new StakingClient(apiKeyName, apiPrivateKey);
client.Ethereum.stake('holesky', '0xdb816889F2a7362EF242E5a717dfD5B38Ae849FE', '123')
.then((workflow) => {
@@ -108,7 +115,11 @@ This code sample creates a SOL staking workflow. View the full code sample [here
// examples/solana/create-workflow.ts
import { StakingClient } from "@coinbase/staking-client-library-ts";
-const client = new StakingClient();
+// Set your api key name and private key here. Get your keys from here: https://portal.cdp.coinbase.com/access/api
+const apiKeyName: string = 'your-api-key-name';
+const apiPrivateKey: string = 'your-api-private-key';
+
+const client = new StakingClient(apiKeyName, apiPrivateKey);
client.Solana.stake('devnet', '8rMGARtkJY5QygP1mgvBFLsE9JrvXByARJiyNfcSE5Z', '100000000')
.then((workflow) => {
@@ -175,13 +186,17 @@ This code sample returns rewards for an Ethereum validator address. View the ful
// examples/ethereum/list-rewards.ts
import { StakingClient } from "@coinbase/staking-client-library-ts";
+// Set your api key name and private key here. Get your keys from here: https://portal.cdp.coinbase.com/access/api
+const apiKeyName: string = 'your-api-key-name';
+const apiPrivateKey: string = 'your-api-private-key';
+
+const client = new StakingClient(apiKeyName, apiPrivateKey);
+
// Defines which address and rewards we want to see
const address: string =
'0xac53512c39d0081ca4437c285305eb423f474e6153693c12fbba4a3df78bcaa3422b31d800c5bea71c1b017168a60474';
const filter: string = `address='${address}' AND period_end_time > '2024-02-25T00:00:00Z' AND period_end_time < '2024-02-27T00:00:00Z'`;
-const client = new StakingClient();
-
// Loops through rewards array and prints each reward
client.Ethereum.listRewards(filter).then((resp) => {
resp.rewards!.forEach((reward) => {
diff --git a/examples/ethereum/create-and-process-workflow.ts b/examples/ethereum/create-and-process-workflow.ts
index e58fe4e..268a72b 100644
--- a/examples/ethereum/create-and-process-workflow.ts
+++ b/examples/ethereum/create-and-process-workflow.ts
@@ -9,19 +9,23 @@ import {
import { Workflow } from '../../src/gen/coinbase/staking/orchestration/v1/workflow.pb';
import { calculateTimeDifference } from '../../src/utils/date';
-const privateKey: string = ''; // replace with your private key
+const walletPrivateKey: string = 'your-wallet-private-key'; // replace with your wallet's private key
const stakerAddress: string = '0xdb816889F2a7362EF242E5a717dfD5B38Ae849FE'; // replace with your staker address
const amount: string = '123'; // replace with your amount
const network: string = 'holesky'; // replace with your network
-const client = new StakingClient();
+// Set your api key name and private key here. Get your keys from here: https://portal.cdp.coinbase.com/access/api
+const apiKeyName: string = 'your-api-key-name';
+const apiPrivateKey: string = 'your-api-private-key';
+
+const client = new StakingClient(apiKeyName, apiPrivateKey);
const signer = TxSignerFactory.getSigner('ethereum');
async function stakePartialEth(): Promise {
- if (privateKey === '' || stakerAddress === '') {
+ if (walletPrivateKey === '' || stakerAddress === '') {
throw new Error(
- 'Please set the privateKey and stakerAddress variables in this file',
+ 'Please set the walletPrivateKey and stakerAddress variables in this file',
);
}
@@ -80,7 +84,10 @@ async function stakePartialEth(): Promise {
}
console.log('Signing unsigned tx %s ...', unsignedTx);
- const signedTx = await signer.signTransaction(privateKey, unsignedTx);
+ const signedTx = await signer.signTransaction(
+ walletPrivateKey,
+ unsignedTx,
+ );
console.log(
'Please broadcast this signed tx %s externally and return back the tx hash via the PerformWorkflowStep API ...',
diff --git a/examples/ethereum/create-workflow.ts b/examples/ethereum/create-workflow.ts
index ef80354..9102818 100644
--- a/examples/ethereum/create-workflow.ts
+++ b/examples/ethereum/create-workflow.ts
@@ -5,7 +5,11 @@ const stakerAddress: string = '0xdb816889F2a7362EF242E5a717dfD5B38Ae849FE'; // r
const amount: string = '123'; // replace with your amount
const network: string = 'holesky'; // replace with your network
-const client = new StakingClient();
+// Set your api key name and private key here. Get your keys from here: https://portal.cdp.coinbase.com/access/api
+const apiKeyName: string = 'your-api-key-name';
+const apiPrivateKey: string = 'your-api-private-key';
+
+const client = new StakingClient(apiKeyName, apiPrivateKey);
async function stakePartialEth(): Promise {
if (stakerAddress === '') {
diff --git a/examples/solana/create-and-process-workflow.ts b/examples/solana/create-and-process-workflow.ts
index 0e8e7ed..0bd1cb9 100644
--- a/examples/solana/create-and-process-workflow.ts
+++ b/examples/solana/create-and-process-workflow.ts
@@ -9,19 +9,23 @@ import {
import { Workflow } from '../../src/gen/coinbase/staking/orchestration/v1/workflow.pb';
import { calculateTimeDifference } from '../../src/utils/date';
-const privateKey: string = ''; // replace with your private key
+const walletPrivateKey: string = 'your-wallet-private-key'; // replace with your wallet's private key
const walletAddress: string = ''; // replace with your wallet address
const amount: string = '100000000'; // replace with your amount. For solana it should be >= 0.1 SOL
const network: string = 'mainnet'; // replace with your network
-const client = new StakingClient();
+// Set your api key name and private key here. Get your keys from here: https://portal.cdp.coinbase.com/access/api
+const apiKeyName: string = 'your-api-key-name';
+const apiPrivateKey: string = 'your-api-private-key';
+
+const client = new StakingClient(apiKeyName, apiPrivateKey);
const signer = TxSignerFactory.getSigner('solana');
async function stakeSolana(): Promise {
- if (privateKey === '' || walletAddress === '') {
+ if (walletPrivateKey === '' || walletAddress === '') {
throw new Error(
- 'Please set the privateKey and walletAddress variables in this file',
+ 'Please set the walletPrivateKey and walletAddress variables in this file',
);
}
@@ -83,7 +87,10 @@ async function stakeSolana(): Promise {
}
console.log('Signing unsigned tx %s ...', unsignedTx);
- const signedTx = await signer.signTransaction(privateKey, unsignedTx);
+ const signedTx = await signer.signTransaction(
+ walletPrivateKey,
+ unsignedTx,
+ );
console.log(
'Please broadcast this signed tx %s externally and return back the tx hash via the PerformWorkflowStep API ...',
diff --git a/examples/solana/create-workflow.ts b/examples/solana/create-workflow.ts
index 2cb97bb..8f007f5 100644
--- a/examples/solana/create-workflow.ts
+++ b/examples/solana/create-workflow.ts
@@ -5,7 +5,11 @@ const walletAddress: string = '9NL2SkpcsdyZwsG8NmHGNra4i4NSyKbJTVd9fUQ7kJHR'; //
const amount: string = '100000000'; // replace with your amount. For solana it should be >= 0.1 SOL
const network: string = 'mainnet'; // replace with your network
-const client = new StakingClient();
+// Set your api key name and private key here. Get your keys from here: https://portal.cdp.coinbase.com/access/api
+const apiKeyName: string = 'your-api-key-name';
+const apiPrivateKey: string = 'your-api-private-key';
+
+const client = new StakingClient(apiKeyName, apiPrivateKey);
async function stakeSolana(): Promise {
if (walletAddress === '') {
diff --git a/src/auth/index.ts b/src/auth/index.ts
index 3b22df7..20023a6 100644
--- a/src/auth/index.ts
+++ b/src/auth/index.ts
@@ -8,20 +8,35 @@ const pemFooter = '-----END EC PRIVATE KEY-----';
/**
* Build a JWT for the specified service and URI.
- * @param service The name of the service.
- * @param uri The URI for which the JWT is to be generated.
* @returns The generated JWT.
+ * @param url The URL for which the JWT is to be generated.
+ * @param method The HTTP method for the request.
+ * @param apiKeyName The name of the API key.
+ * @param apiPrivateKey The private key present in the API key downloaded from platform.
*/
export const buildJWT = async (
url: string,
method = 'GET',
+ apiKeyName?: string,
+ apiPrivateKey?: string,
): Promise => {
- const keyFile = readFileSync('.coinbase_cloud_api_key.json', {
- encoding: 'utf8',
- });
- const apiKey: APIKey = JSON.parse(keyFile);
+ let pemPrivateKey: string;
+ let keyName: string;
+ let apiKey: APIKey;
+
+ if (apiKeyName && apiPrivateKey) {
+ pemPrivateKey = extractPemKey(apiPrivateKey);
+ keyName = apiKeyName;
+ } else {
+ const keyFile = readFileSync('.coinbase_cloud_api_key.json', {
+ encoding: 'utf8',
+ });
+
+ apiKey = JSON.parse(keyFile);
+ pemPrivateKey = extractPemKey(apiKey.privateKey);
+ keyName = apiKey.name;
+ }
- const pemPrivateKey = extractPemKey(apiKey.privateKey);
let privateKey: JWK.Key;
try {
@@ -35,7 +50,7 @@ export const buildJWT = async (
const header = {
alg: 'ES256',
- kid: apiKey.name,
+ kid: keyName,
typ: 'JWT',
nonce: nonce(),
};
@@ -44,7 +59,7 @@ export const buildJWT = async (
const uri = `${method} ${url.substring(8)}`;
const claims: APIKeyClaims = {
- sub: apiKey.name,
+ sub: keyName,
iss: 'coinbase-cloud',
nbf: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 60, // +1 minute
@@ -103,7 +118,7 @@ interface APIKeyClaims {
*/
const extractPemKey = (privateKeyString: string): string => {
// Remove all newline characters
- privateKeyString = privateKeyString.replace(/\n/g, '');
+ privateKeyString = privateKeyString.replace(/\\n|\n/g, '');
// If the string starts with the standard PEM header and footer, return as is.
if (
diff --git a/src/client/staking-client.ts b/src/client/staking-client.ts
index eb5f53c..dd82571 100644
--- a/src/client/staking-client.ts
+++ b/src/client/staking-client.ts
@@ -47,17 +47,27 @@ const DEFAULT_URL = 'https://api.developer.coinbase.com/staking';
export class StakingClient {
readonly baseURL: string;
+ readonly apiKeyName: string | undefined;
+ readonly apiPrivateKey: string | undefined;
readonly Ethereum: Ethereum;
readonly Solana: Solana;
readonly Cosmos: Cosmos;
- constructor(baseURL?: string) {
+ constructor(apiKeyName?: string, apiPrivateKey?: string, baseURL?: string) {
if (baseURL) {
this.baseURL = baseURL;
} else {
this.baseURL = DEFAULT_URL;
}
+ if (apiKeyName) {
+ this.apiKeyName = apiKeyName;
+ }
+
+ if (apiPrivateKey) {
+ this.apiPrivateKey = apiPrivateKey;
+ }
+
this.Ethereum = new Ethereum(this);
this.Solana = new Solana(this);
this.Cosmos = new Cosmos(this);
@@ -70,7 +80,13 @@ export class StakingClient {
const url: string = this.baseURL + '/orchestration';
// Generate the JWT token and get the auth details as a initReq object.
- const initReq = await getAuthDetails(url, path, method);
+ const initReq = await getAuthDetails(
+ url,
+ path,
+ method,
+ this.apiKeyName,
+ this.apiPrivateKey,
+ );
const req: ListProtocolsRequest = {};
@@ -85,7 +101,13 @@ export class StakingClient {
const url: string = this.baseURL + '/orchestration';
// Generate the JWT token and get the auth details as a initReq object.
- const initReq = await getAuthDetails(url, path, method);
+ const initReq = await getAuthDetails(
+ url,
+ path,
+ method,
+ this.apiKeyName,
+ this.apiPrivateKey,
+ );
const req: ListNetworksRequest = {
parent: parent,
@@ -105,8 +127,14 @@ export class StakingClient {
const url: string = this.baseURL + '/orchestration';
// Generate the JWT token and get the auth details as a initReq object.
- const initReq = await getAuthDetails(url, path, method);
-
+ // Generate the JWT token and get the auth details as a initReq object.
+ const initReq = await getAuthDetails(
+ url,
+ path,
+ method,
+ this.apiKeyName,
+ this.apiPrivateKey,
+ );
const req: ListActionsRequest = {
parent: parent,
};
@@ -125,7 +153,14 @@ export class StakingClient {
const url: string = this.baseURL + '/orchestration';
// Generate the JWT token and get the auth details as a initReq object.
- const initReq = await getAuthDetails(url, path, method);
+ // Generate the JWT token and get the auth details as a initReq object.
+ const initReq = await getAuthDetails(
+ url,
+ path,
+ method,
+ this.apiKeyName,
+ this.apiPrivateKey,
+ );
return StakingService.ViewStakingContext(req, initReq);
}
@@ -138,7 +173,14 @@ export class StakingClient {
const url: string = this.baseURL + '/orchestration';
// Generate the JWT token and get the auth details as a initReq object.
- const initReq = await getAuthDetails(url, path, method);
+ // Generate the JWT token and get the auth details as a initReq object.
+ const initReq = await getAuthDetails(
+ url,
+ path,
+ method,
+ this.apiKeyName,
+ this.apiPrivateKey,
+ );
return StakingService.CreateWorkflow(req, initReq);
}
@@ -151,7 +193,14 @@ export class StakingClient {
const url: string = this.baseURL + '/orchestration';
// Generate the JWT token and get the auth details as a initReq object.
- const initReq = await getAuthDetails(url, path, method);
+ // Generate the JWT token and get the auth details as a initReq object.
+ const initReq = await getAuthDetails(
+ url,
+ path,
+ method,
+ this.apiKeyName,
+ this.apiPrivateKey,
+ );
const req: GetWorkflowRequest = {
name: name,
@@ -172,7 +221,14 @@ export class StakingClient {
const url: string = this.baseURL + '/orchestration';
// Generate the JWT token and get the auth details as a initReq object.
- const initReq = await getAuthDetails(url, path, method);
+ // Generate the JWT token and get the auth details as a initReq object.
+ const initReq = await getAuthDetails(
+ url,
+ path,
+ method,
+ this.apiKeyName,
+ this.apiPrivateKey,
+ );
const req: PerformWorkflowStepRequest = {
name: name,
@@ -193,7 +249,14 @@ export class StakingClient {
const url: string = this.baseURL + '/orchestration';
// Generate the JWT token and get the auth details as a initReq object.
- const initReq = await getAuthDetails(url, path, method);
+ // Generate the JWT token and get the auth details as a initReq object.
+ const initReq = await getAuthDetails(
+ url,
+ path,
+ method,
+ this.apiKeyName,
+ this.apiPrivateKey,
+ );
const req: ListWorkflowsRequest = {
pageSize: pageSize,
@@ -214,7 +277,14 @@ export class StakingClient {
const url: string = this.baseURL + '/rewards';
// Generate the JWT token and get the auth details as a initReq object.
- const initReq = await getAuthDetails(url, path, method);
+ // Generate the JWT token and get the auth details as a initReq object.
+ const initReq = await getAuthDetails(
+ url,
+ path,
+ method,
+ this.apiKeyName,
+ this.apiPrivateKey,
+ );
return RewardService.ListRewards(req, initReq);
}
@@ -230,7 +300,14 @@ export class StakingClient {
const url: string = this.baseURL + '/rewards';
// Generate the JWT token and get the auth details as a initReq object.
- const initReq = await getAuthDetails(url, path, method);
+ // Generate the JWT token and get the auth details as a initReq object.
+ const initReq = await getAuthDetails(
+ url,
+ path,
+ method,
+ this.apiKeyName,
+ this.apiPrivateKey,
+ );
return RewardService.ListStakes(req, initReq);
}
@@ -265,9 +342,11 @@ async function getAuthDetails(
url: string,
path: string,
method: string,
+ apiKeyName?: string,
+ apiPrivateKey?: string,
): Promise {
// Generate the JWT token
- const token = await buildJWT(url + path, method);
+ const token = await buildJWT(url + path, method, apiKeyName, apiPrivateKey);
return {
pathPrefix: url,