Skip to content

Commit

Permalink
use configuration option types for constructors
Browse files Browse the repository at this point in the history
  • Loading branch information
John-peterson-coinbase committed Jun 2, 2024
1 parent 98adff2 commit 0c8cacf
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 48 deletions.
61 changes: 28 additions & 33 deletions src/coinbase/coinbase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@ import {
AddressesApiFactory,
WalletsApiFactory,
} from "../client";
import { ethers } from "ethers";
import { BASE_PATH } from "./../client/base";
import { Configuration } from "./../client/configuration";
import { CoinbaseAuthenticator } from "./authenticator";
import { InternalError, InvalidAPIKeyFormat, InvalidConfiguration } from "./errors";
import { ApiClients } from "./types";
import { ApiClients, CoinbaseConfigureFromJsonOptions, CoinbaseOptions } from "./types";
import { User } from "./user";
import { logApiResponse, registerAxiosInterceptors } from "./utils";

Expand Down Expand Up @@ -62,28 +61,24 @@ export class Coinbase {
* Initializes the Coinbase SDK.
*
* @class
* @param apiKeyName - The API key name.
* @param privateKey - The private key associated with the API key.
* @param useServerSigner - Whether to use server signer or not.
* @param debugging - If true, logs API requests and responses to the console.
* @param basePath - The base path for the API.
* @param options - The constructor options.
* @throws {InternalError} If the configuration is invalid.
* @throws {InvalidAPIKeyFormat} If not able to create JWT token.
*/
constructor(
apiKeyName: string,
privateKey: string,
useServerSigner: boolean = false,
debugging = false,
basePath: string = BASE_PATH,
) {
constructor(options: CoinbaseOptions) {
const apiKeyName = options.apiKeyName ?? "";
const privateKey = options.privateKey ?? "";
const useServerSigner = options.useServerSigner === true;
const debugging = options.debugging === true;
const basePath = options.basePath ?? BASE_PATH;

if (apiKeyName === "") {
throw new InternalError("Invalid configuration: apiKeyName is empty");
}
if (privateKey === "") {
throw new InternalError("Invalid configuration: privateKey is empty");
}
const coinbaseAuthenticator = new CoinbaseAuthenticator(apiKeyName, privateKey);
const coinbaseAuthenticator = new CoinbaseAuthenticator(apiKeyName!, privateKey!);
const config = new Configuration({
basePath: basePath,
});
Expand All @@ -94,35 +89,29 @@ export class Coinbase {
response => logApiResponse(response, debugging),
);

Coinbase.apiClients.user = UsersApiFactory(config, BASE_PATH, axiosInstance);
Coinbase.apiClients.wallet = WalletsApiFactory(config, BASE_PATH, axiosInstance);
Coinbase.apiClients.address = AddressesApiFactory(config, BASE_PATH, axiosInstance);
Coinbase.apiClients.transfer = TransfersApiFactory(config, BASE_PATH, axiosInstance);
Coinbase.apiClients.baseSepoliaProvider = new ethers.JsonRpcProvider(
"https://sepolia.base.org",
);
Coinbase.apiClients.user = UsersApiFactory(config, basePath, axiosInstance);
Coinbase.apiClients.wallet = WalletsApiFactory(config, basePath, axiosInstance);
Coinbase.apiClients.address = AddressesApiFactory(config, basePath, axiosInstance);
Coinbase.apiClients.transfer = TransfersApiFactory(config, basePath, axiosInstance);
Coinbase.apiKeyPrivateKey = privateKey;
Coinbase.useServerSigner = useServerSigner;
}

/**
* Reads the API key and private key from a JSON file and initializes the Coinbase SDK.
*
* @param filePath - The path to the JSON file containing the API key and private key.
* @param useServerSigner - Whether to use server signer or not.
* @param debugging - If true, logs API requests and responses to the console.
* @param basePath - The base path for the API.
* @param options - The configuration options.
* @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.
* @throws {InvalidAPIKeyFormat} If not able to create JWT token.
*/
static configureFromJson(
filePath: string = "coinbase_cloud_api_key.json",
useServerSigner: boolean = false,
debugging: boolean = false,
basePath: string = BASE_PATH,
): Coinbase {
static configureFromJson(options: CoinbaseConfigureFromJsonOptions): Coinbase {
const filePath = options.filePath ?? "coinbase_cloud_api_key.json";
const useServerSigner = options.useServerSigner === true;
const debugging = options.debugging === true;
const basePath = options.basePath ?? BASE_PATH;

if (!fs.existsSync(filePath)) {
throw new InvalidConfiguration(`Invalid configuration: file not found at ${filePath}`);
}
Expand All @@ -133,7 +122,13 @@ export class Coinbase {
throw new InvalidAPIKeyFormat("Invalid configuration: missing configuration values");
}

return new Coinbase(config.name, config.privateKey, useServerSigner, debugging, basePath);
return new Coinbase({
apiKeyName: config.name,
privateKey: config.privateKey,
useServerSigner: useServerSigner,
debugging: debugging,
basePath: basePath,
});
} catch (e) {
if (e instanceof SyntaxError) {
throw new InvalidAPIKeyFormat("Not able to parse the configuration file");
Expand Down
35 changes: 22 additions & 13 deletions src/coinbase/tests/coinbase_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,39 +16,46 @@ const PATH_PREFIX = "./src/coinbase/tests/config";

describe("Coinbase tests", () => {
it("should throw an error if the API key name or private key is empty", () => {
expect(() => new Coinbase("", "test")).toThrow("Invalid configuration: apiKeyName is empty");
expect(() => new Coinbase("test", "")).toThrow("Invalid configuration: privateKey is empty");
expect(() => new Coinbase({ privateKey: "test" })).toThrow(
"Invalid configuration: apiKeyName is empty",
);
expect(() => new Coinbase({ apiKeyName: "test" })).toThrow(
"Invalid configuration: privateKey is empty",
);
});

it("should throw an error if the file does not exist", () => {
expect(() => Coinbase.configureFromJson(`${PATH_PREFIX}/does-not-exist.json`)).toThrow(
expect(() =>
Coinbase.configureFromJson({ filePath: `${PATH_PREFIX}/does-not-exist.json` }),
).toThrow(
"Invalid configuration: file not found at ./src/coinbase/tests/config/does-not-exist.json",
);
});

it("should initialize the Coinbase SDK from a JSON file", () => {
const cbInstance = Coinbase.configureFromJson(`${PATH_PREFIX}/coinbase_cloud_api_key.json`);
const cbInstance = Coinbase.configureFromJson({
filePath: `${PATH_PREFIX}/coinbase_cloud_api_key.json`,
});
expect(cbInstance).toBeInstanceOf(Coinbase);
});

it("should throw an error if there is an issue reading the file or parsing the JSON data", () => {
expect(() => Coinbase.configureFromJson(`${PATH_PREFIX}/invalid.json`)).toThrow(
expect(() => Coinbase.configureFromJson({ filePath: `${PATH_PREFIX}/invalid.json` })).toThrow(
"Invalid configuration: missing configuration values",
);
});

it("should throw an error if the JSON file is not parseable", () => {
expect(() => Coinbase.configureFromJson(`${PATH_PREFIX}/not_parseable.json`)).toThrow(
"Not able to parse the configuration file",
);
expect(() =>
Coinbase.configureFromJson({ filePath: `${PATH_PREFIX}/not_parseable.json` }),
).toThrow("Not able to parse the configuration file");
});

describe("should able to interact with the API", () => {
let user, walletId, publicKey, addressId, transactionHash;
const cbInstance = Coinbase.configureFromJson(
`${PATH_PREFIX}/coinbase_cloud_api_key.json`,
true,
);
const cbInstance = Coinbase.configureFromJson({
filePath: `${PATH_PREFIX}/coinbase_cloud_api_key.json`,
});

beforeAll(async () => {
Coinbase.apiClients = {
Expand Down Expand Up @@ -96,7 +103,9 @@ describe("Coinbase tests", () => {
});

it("should raise an error if the user is not found", async () => {
const cbInstance = Coinbase.configureFromJson(`${PATH_PREFIX}/coinbase_cloud_api_key.json`);
const cbInstance = Coinbase.configureFromJson({
filePath: `${PATH_PREFIX}/coinbase_cloud_api_key.json`,
});
Coinbase.apiClients.user!.getCurrentUser = mockReturnRejectedValue(
new APIError("User not found"),
);
Expand Down
54 changes: 52 additions & 2 deletions src/coinbase/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Decimal } from "decimal.js";
import { AxiosPromise, AxiosRequestConfig, RawAxiosRequestConfig } from "axios";
import { ethers } from "ethers";
import {
Address as AddressModel,
AddressList,
Expand Down Expand Up @@ -298,7 +297,6 @@ export type ApiClients = {
wallet?: WalletAPIClient;
address?: AddressAPIClient;
transfer?: TransferAPIClient;
baseSepoliaProvider?: ethers.Provider;
};

/**
Expand Down Expand Up @@ -347,3 +345,55 @@ export enum ServerSignerStatus {
PENDING = "pending_seed_creation",
ACTIVE = "active_seed",
}

/**
* CoinbaseOptions type definition.
*/
export type CoinbaseOptions = {
/**
* The API key name.
*/
apiKeyName?: string;

/**
* The private key associated with the API key.
*/
privateKey?: string;

/**
* Whether to use a Server-Signer or not.
*/
useServerSigner?: boolean;

/**
* If true, logs API requests and responses to the console.
*/
debugging?: boolean;

/**
* The base path for the API.
*/
basePath?: string;
};

export type CoinbaseConfigureFromJsonOptions = {
/**
* The path to the JSON file containing the API key and private key.
*/
filePath: string;

/**
* Whether to use a Server-Signer or not.
*/
useServerSigner?: boolean;

/**
* If true, logs API requests and responses to the console.
*/
debugging?: boolean;

/**
* The base path for the API.
*/
basePath?: string;
};

0 comments on commit 0c8cacf

Please sign in to comment.