Skip to content

Commit

Permalink
Add another option to provide auth details (#25)
Browse files Browse the repository at this point in the history
## Description Of Change

Add an option to initialize client with the api key name and private key
instead of completely relying on api-key file.

This PR will enable these 2 styles of interacting with the API and make
option 1 as the default in our examples and README.

Option 1: Requires client initialize to pass the details such as API key
name and private key.

```
const apiKeyName: string = 'your-api-key-name';
const apiPrivateKey: string = 'your-api-private-key';

const client = new StakingClient(apiKeyName, apiPrivateKey);

client.Ethereum.stake(network, stakerAddress, amount);
```

Option 2: Need a api key of name `.coinbase_cloud_api_key.json` present
in the root dir.

```
const client = new StakingClient();

client.Ethereum.stake(network, stakerAddress, amount);
```

## Testing Procedure

Manually ran through the README to execute and make sure all is good.
  • Loading branch information
drohit-cb authored May 9, 2024
1 parent 9463c02 commit 29ff1a0
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 46 deletions.
37 changes: 26 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. <br>
These will be used in order to set up our client later in the example code. <br>
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
```
Expand All @@ -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) => {
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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) => {
Expand Down
17 changes: 12 additions & 5 deletions examples/ethereum/create-and-process-workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
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',
);
}

Expand Down Expand Up @@ -80,7 +84,10 @@ async function stakePartialEth(): Promise<void> {
}

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 ...',
Expand Down
6 changes: 5 additions & 1 deletion examples/ethereum/create-workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
if (stakerAddress === '') {
Expand Down
17 changes: 12 additions & 5 deletions examples/solana/create-and-process-workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
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',
);
}

Expand Down Expand Up @@ -83,7 +87,10 @@ async function stakeSolana(): Promise<void> {
}

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 ...',
Expand Down
6 changes: 5 additions & 1 deletion examples/solana/create-workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
if (walletAddress === '') {
Expand Down
35 changes: 25 additions & 10 deletions src/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> => {
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 {
Expand All @@ -35,7 +50,7 @@ export const buildJWT = async (

const header = {
alg: 'ES256',
kid: apiKey.name,
kid: keyName,
typ: 'JWT',
nonce: nonce(),
};
Expand All @@ -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
Expand Down Expand Up @@ -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 (
Expand Down
Loading

0 comments on commit 29ff1a0

Please sign in to comment.