Skip to content

Commit

Permalink
add an option to initialize client with an api key name and private k…
Browse files Browse the repository at this point in the history
…ey instead of completely relying on api-key file
  • Loading branch information
drohit-cb committed May 8, 2024
1 parent 9463c02 commit d69e145
Show file tree
Hide file tree
Showing 7 changed files with 171 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
14 changes: 9 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,7 @@ 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);

Check failure on line 87 in examples/ethereum/create-and-process-workflow.ts

View workflow job for this annotation

GitHub Actions / lint

Replace `walletPrivateKey,·unsignedTx` with `⏎········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
14 changes: 9 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,7 @@ 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);

Check failure on line 90 in examples/solana/create-and-process-workflow.ts

View workflow job for this annotation

GitHub Actions / lint

Replace `walletPrivateKey,·unsignedTx` with `⏎········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 d69e145

Please sign in to comment.