Skip to content

Commit

Permalink
Merge pull request #23 from coinbase/psdk-111-wallet-import-export-store
Browse files Browse the repository at this point in the history
[PSDK-111] Wallet Import/Export/Save/Load
  • Loading branch information
John-peterson-coinbase authored May 22, 2024
2 parents 5ab03a9 + 8f717d7 commit 055c8dd
Show file tree
Hide file tree
Showing 8 changed files with 601 additions and 19 deletions.
67 changes: 53 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
# Coinbase Node.js SDK

The Coinbase Node.js SDK enables the simple integration of crypto into your app.
By calling Coinbase's Platform APIs, the SDK allows you to provision crypto wallets,
send crypto into/out of those wallets, track wallet balances, and trade crypto from
one asset into another.
The Coinbase Node.js SDK enables the simple integration of crypto into your app. By calling Coinbase's Platform APIs, the SDK allows you to provision crypto wallets, send crypto into/out of those wallets, track wallet balances, and trade crypto from one asset into another.

The SDK currently supports Customer-custodied Wallets on the Base Sepolia test network.

Expand All @@ -12,8 +9,7 @@ The SDK currently supports Customer-custodied Wallets on the Base Sepolia test n
- **may make backwards-incompatible changes between releases**
- **should not be used on Mainnet (i.e. with real funds)**

Currently, the SDK is intended for use on testnet for quick bootstrapping of crypto wallets at
hackathons, code academies, and other development settings.
Currently, the SDK is intended for use on testnet for quick bootstrapping of crypto wallets at hackathons, code academies, and other development settings.

## Documentation

Expand All @@ -38,8 +34,9 @@ yarn install @coinbase/coinbase-sdk
After running `npx ts-node` to start the REPL, you can import the SDK as follows:

```typescript
import { Coinbase } from '@coinbase/coinbase-sdk';
import { Coinbase } from "@coinbase/coinbase-sdk";
```

### Requirements

- Node.js 18 or higher
Expand All @@ -51,18 +48,17 @@ import { Coinbase } from '@coinbase/coinbase-sdk';
To start, [create a CDP API Key](https://portal.cdp.coinbase.com/access/api). Then, initialize the Platform SDK by passing your API Key name and API Key's private key via the `Coinbase` constructor:

```typescript
const apiKeyName = 'Copy your API Key name here.';
const apiKeyName = "Copy your API Key name here.";

const apiKeyPrivateKey = 'Copy your API Key\'s private key here.';
const apiKeyPrivateKey = "Copy your API Key's private key here.";

const coinbase = new Coinbase(apiKeyName, apiKeyPrivateKey);
```

Another way to initialize the SDK is by sourcing the API key from the json file that contains your API key,
downloaded from CDP portal.
Another way to initialize the SDK is by sourcing the API key from the json file that contains your API key, downloaded from CDP portal.

```typescript
const coinbase = Coinbase.configureFromJson('path/to/your/api-key.json');
const coinbase = Coinbase.configureFromJson("path/to/your/api-key.json");
```

This will allow you to authenticate with the Platform APIs and get access to the default `User`.
Expand Down Expand Up @@ -105,6 +101,50 @@ const anotherWallet = await user.createWallet();
const transfer = await wallet.createTransfer(0.00001, Coinbase.assetList.Eth, anotherWallet);
```

### Re-Instantiating Wallets

The SDK creates Wallets with developer managed keys, which means you are responsible for securely storing the keys required to re-instantiate Wallets. The below code walks you through how to export a Wallet and store it in a secure location.

```typescript
// Export the data required to re-instantiate the Wallet.
const data = wallet.export();
```

In order to persist the data for the Wallet, you will need to implement a store method to store the data export in a secure location. If you do not store the Wallet in a secure location you will lose access to the Wallet and all of the funds on it.

```typescript
// At this point, you should implement your own "store" method to securely persist
// the data required to re-instantiate the Wallet at a later time.
await store(data);
```

For convenience during testing, we provide a `saveWallet` method that stores the Wallet data in your local file system. This is an insecure method of storing wallet seeds and should only be used for development purposes.

```typescript
user.saveWallet(wallet);
```

To encrypt the saved data, set encrypt to true. Note that your CDP API key also serves as the encryption key for the data persisted locally. To re-instantiate wallets with encrypted data, ensure that your SDK is configured with the same API key when invoking `saveWallet` and `loadWallets`.

```typescript
user.saveWallet(wallet, true);
```

The below code demonstrates how to re-instantiate a Wallet from the data export.

```typescript
// The Wallet can be re-instantiated using the exported data.
const importedWallet = await user.importWallet(data);
```

To import Wallets that were persisted to your local file system using `saveWallet`, use the below code.

```typescript
// The Wallet can be re-instantiated using the exported data.
const wallets = await user.loadWallets();
const reinitWallet = wallets[wallet.getId()];
```

## Development

### Node.js Version
Expand Down Expand Up @@ -155,8 +195,7 @@ npx jest ./src/coinbase/tests/wallet_test.ts

### REPL

The repository is equipped with a REPL to allow developers to play with the SDK. To start
it, run:
The repository is equipped with a REPL to allow developers to play with the SDK. To start it, run:

```bash
npx ts-node
Expand Down
24 changes: 22 additions & 2 deletions src/coinbase/coinbase.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import globalAxios from "axios";
import fs from "fs";
import * as fs from "fs";
import {
AddressesApiFactory,
User as UserModel,
Expand Down Expand Up @@ -51,6 +51,20 @@ export class Coinbase {
*/
static readonly WEI_PER_ETHER: bigint = BigInt("1000000000000000000");

/**
* The backup file path for Wallet seeds.
*
* @constant
*/
static backupFilePath: string = "seed.json";

/**
* The CDP API key Private Key.
*
* @constant
*/
static apiKeyPrivateKey: string;

/**
* Initializes the Coinbase SDK.
*
Expand All @@ -59,6 +73,7 @@ export class Coinbase {
* @param privateKey - The private key associated with the API key.
* @param debugging - If true, logs API requests and responses to the console.
* @param basePath - The base path for the API.
* @param backupFilePath - The path to the file containing the Wallet backup data.
* @throws {InternalError} If the configuration is invalid.
* @throws {InvalidAPIKeyFormat} If not able to create JWT token.
*/
Expand All @@ -67,6 +82,7 @@ export class Coinbase {
privateKey: string,
debugging = false,
basePath: string = BASE_PATH,
backupFilePath?: string,
) {
if (apiKeyName === "") {
throw new InternalError("Invalid configuration: apiKeyName is empty");
Expand All @@ -92,6 +108,8 @@ export class Coinbase {
Coinbase.apiClients.baseSepoliaProvider = new ethers.JsonRpcProvider(
"https://sepolia.base.org",
);
Coinbase.backupFilePath = backupFilePath ? backupFilePath : Coinbase.backupFilePath;
Coinbase.apiKeyPrivateKey = privateKey;
}

/**
Expand All @@ -100,6 +118,7 @@ export class Coinbase {
* @param filePath - The path to the JSON file containing the API key and private key.
* @param debugging - If true, logs API requests and responses to the console.
* @param basePath - The base path for the API.
* @param backupFilePath - The path to the file containing the Wallet backup data.
* @returns A new instance of the Coinbase SDK.
* @throws {InvalidAPIKeyFormat} If the file does not exist or the configuration values are missing/invalid.
* @throws {InvalidConfiguration} If the configuration is invalid.
Expand All @@ -109,6 +128,7 @@ export class Coinbase {
filePath: string = "coinbase_cloud_api_key.json",
debugging: boolean = false,
basePath: string = BASE_PATH,
backupFilePath?: string,
): Coinbase {
if (!fs.existsSync(filePath)) {
throw new InvalidConfiguration(`Invalid configuration: file not found at ${filePath}`);
Expand All @@ -120,7 +140,7 @@ export class Coinbase {
throw new InvalidAPIKeyFormat("Invalid configuration: missing configuration values");
}

return new Coinbase(config.name, config.privateKey, debugging, basePath);
return new Coinbase(config.name, config.privateKey, debugging, basePath, backupFilePath);
} catch (e) {
if (e instanceof SyntaxError) {
throw new InvalidAPIKeyFormat("Not able to parse the configuration file");
Expand Down
Loading

0 comments on commit 055c8dd

Please sign in to comment.