@coinbase/coinbase-sdk
Advanced tools
Comparing version 0.0.4 to 0.0.6
@@ -33,2 +33,5 @@ import type { Configuration } from "./configuration"; | ||
} | ||
export interface BroadcastTradeRequest { | ||
signed_payload: string; | ||
} | ||
export interface BroadcastTransferRequest { | ||
@@ -38,5 +41,14 @@ signed_payload: string; | ||
export interface CreateAddressRequest { | ||
public_key: string; | ||
attestation: string; | ||
public_key?: string; | ||
attestation?: string; | ||
} | ||
export interface CreateServerSignerRequest { | ||
server_signer_id: string; | ||
enrollment_data: string; | ||
} | ||
export interface CreateTradeRequest { | ||
amount: string; | ||
from_asset_id: string; | ||
to_asset_id: string; | ||
} | ||
export interface CreateTransferRequest { | ||
@@ -49,4 +61,8 @@ amount: string; | ||
export interface CreateWalletRequest { | ||
wallet: Wallet; | ||
wallet: CreateWalletRequestWallet; | ||
} | ||
export interface CreateWalletRequestWallet { | ||
network_id: string; | ||
use_server_signer?: boolean; | ||
} | ||
export interface FaucetTransaction { | ||
@@ -59,2 +75,81 @@ transaction_hash: string; | ||
} | ||
export interface SeedCreationEvent { | ||
wallet_id: string; | ||
wallet_user_id: string; | ||
} | ||
export interface SeedCreationEventResult { | ||
wallet_id: string; | ||
wallet_user_id: string; | ||
extended_public_key: string; | ||
seed_id: string; | ||
} | ||
export interface ServerSigner { | ||
server_signer_id: string; | ||
wallets?: Array<string>; | ||
} | ||
export interface ServerSignerEvent { | ||
server_signer_id: string; | ||
event: ServerSignerEventEvent; | ||
} | ||
export type ServerSignerEventEvent = SeedCreationEvent | SignatureCreationEvent; | ||
export interface ServerSignerEventList { | ||
data: Array<ServerSignerEvent>; | ||
has_more: boolean; | ||
next_page: string; | ||
total_count: number; | ||
} | ||
export interface SignatureCreationEvent { | ||
seed_id: string; | ||
wallet_id: string; | ||
wallet_user_id: string; | ||
address_id: string; | ||
address_index: number; | ||
signing_payload: string; | ||
transaction_type: TransactionType; | ||
transaction_id: string; | ||
} | ||
export interface SignatureCreationEventResult { | ||
wallet_id: string; | ||
wallet_user_id: string; | ||
address_id: string; | ||
transaction_type: TransactionType; | ||
transaction_id: string; | ||
signature: string; | ||
} | ||
export interface Trade { | ||
network_id: string; | ||
wallet_id: string; | ||
address_id: string; | ||
trade_id: string; | ||
from_amount: string; | ||
from_asset: Asset; | ||
to_amount: string; | ||
to_asset: Asset; | ||
transaction: Transaction; | ||
} | ||
export interface TradeList { | ||
data: Array<Trade>; | ||
has_more: boolean; | ||
next_page: string; | ||
total_count: number; | ||
} | ||
export interface Transaction { | ||
network_id: string; | ||
from_address_id: string; | ||
unsigned_payload: string; | ||
signed_payload?: string; | ||
transaction_hash?: string; | ||
status: TransactionStatusEnum; | ||
} | ||
export declare const TransactionStatusEnum: { | ||
readonly Pending: "pending"; | ||
readonly Broadcast: "broadcast"; | ||
readonly Complete: "complete"; | ||
readonly Failed: "failed"; | ||
}; | ||
export type TransactionStatusEnum = (typeof TransactionStatusEnum)[keyof typeof TransactionStatusEnum]; | ||
export declare const TransactionType: { | ||
readonly Transfer: "transfer"; | ||
}; | ||
export type TransactionType = (typeof TransactionType)[keyof typeof TransactionType]; | ||
export interface Transfer { | ||
@@ -91,6 +186,12 @@ network_id: string; | ||
export interface Wallet { | ||
id?: string; | ||
id: string; | ||
network_id: string; | ||
default_address?: Address; | ||
server_signer_status?: WalletServerSignerStatusEnum; | ||
} | ||
export declare const WalletServerSignerStatusEnum: { | ||
readonly PendingSeedCreation: "pending_seed_creation"; | ||
readonly ActiveSeed: "active_seed"; | ||
}; | ||
export type WalletServerSignerStatusEnum = (typeof WalletServerSignerStatusEnum)[keyof typeof WalletServerSignerStatusEnum]; | ||
export interface WalletList { | ||
@@ -126,3 +227,11 @@ data: Array<Wallet>; | ||
}; | ||
export declare class AddressesApi extends BaseAPI { | ||
export interface AddressesApiInterface { | ||
createAddress(walletId: string, createAddressRequest?: CreateAddressRequest, options?: RawAxiosRequestConfig): AxiosPromise<Address>; | ||
getAddress(walletId: string, addressId: string, options?: RawAxiosRequestConfig): AxiosPromise<Address>; | ||
getAddressBalance(walletId: string, addressId: string, assetId: string, options?: RawAxiosRequestConfig): AxiosPromise<Balance>; | ||
listAddressBalances(walletId: string, addressId: string, page?: string, options?: RawAxiosRequestConfig): AxiosPromise<AddressBalanceList>; | ||
listAddresses(walletId: string, limit?: number, page?: string, options?: RawAxiosRequestConfig): AxiosPromise<AddressList>; | ||
requestFaucetFunds(walletId: string, addressId: string, options?: RawAxiosRequestConfig): AxiosPromise<FaucetTransaction>; | ||
} | ||
export declare class AddressesApi extends BaseAPI implements AddressesApiInterface { | ||
createAddress(walletId: string, createAddressRequest?: CreateAddressRequest, options?: RawAxiosRequestConfig): Promise<import("axios").AxiosResponse<Address, any>>; | ||
@@ -135,2 +244,72 @@ getAddress(walletId: string, addressId: string, options?: RawAxiosRequestConfig): Promise<import("axios").AxiosResponse<Address, any>>; | ||
} | ||
export declare const ServerSignersApiAxiosParamCreator: (configuration?: Configuration) => { | ||
createServerSigner: (createServerSignerRequest?: CreateServerSignerRequest, options?: RawAxiosRequestConfig) => Promise<RequestArgs>; | ||
getServerSigner: (serverSignerId: string, options?: RawAxiosRequestConfig) => Promise<RequestArgs>; | ||
listServerSignerEvents: (serverSignerId: string, limit?: number, page?: string, options?: RawAxiosRequestConfig) => Promise<RequestArgs>; | ||
listServerSigners: (options?: RawAxiosRequestConfig) => Promise<RequestArgs>; | ||
submitServerSignerSeedEventResult: (serverSignerId: string, seedCreationEventResult?: SeedCreationEventResult, options?: RawAxiosRequestConfig) => Promise<RequestArgs>; | ||
submitServerSignerSignatureEventResult: (serverSignerId: string, signatureCreationEventResult?: SignatureCreationEventResult, options?: RawAxiosRequestConfig) => Promise<RequestArgs>; | ||
}; | ||
export declare const ServerSignersApiFp: (configuration?: Configuration) => { | ||
createServerSigner(createServerSignerRequest?: CreateServerSignerRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ServerSigner>>; | ||
getServerSigner(serverSignerId: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ServerSigner>>; | ||
listServerSignerEvents(serverSignerId: string, limit?: number, page?: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ServerSignerEventList>>; | ||
listServerSigners(options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ServerSigner>>; | ||
submitServerSignerSeedEventResult(serverSignerId: string, seedCreationEventResult?: SeedCreationEventResult, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<SeedCreationEventResult>>; | ||
submitServerSignerSignatureEventResult(serverSignerId: string, signatureCreationEventResult?: SignatureCreationEventResult, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<SignatureCreationEventResult>>; | ||
}; | ||
export declare const ServerSignersApiFactory: (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) => { | ||
createServerSigner(createServerSignerRequest?: CreateServerSignerRequest, options?: any): AxiosPromise<ServerSigner>; | ||
getServerSigner(serverSignerId: string, options?: any): AxiosPromise<ServerSigner>; | ||
listServerSignerEvents(serverSignerId: string, limit?: number, page?: string, options?: any): AxiosPromise<ServerSignerEventList>; | ||
listServerSigners(options?: any): AxiosPromise<ServerSigner>; | ||
submitServerSignerSeedEventResult(serverSignerId: string, seedCreationEventResult?: SeedCreationEventResult, options?: any): AxiosPromise<SeedCreationEventResult>; | ||
submitServerSignerSignatureEventResult(serverSignerId: string, signatureCreationEventResult?: SignatureCreationEventResult, options?: any): AxiosPromise<SignatureCreationEventResult>; | ||
}; | ||
export interface ServerSignersApiInterface { | ||
createServerSigner(createServerSignerRequest?: CreateServerSignerRequest, options?: RawAxiosRequestConfig): AxiosPromise<ServerSigner>; | ||
getServerSigner(serverSignerId: string, options?: RawAxiosRequestConfig): AxiosPromise<ServerSigner>; | ||
listServerSignerEvents(serverSignerId: string, limit?: number, page?: string, options?: RawAxiosRequestConfig): AxiosPromise<ServerSignerEventList>; | ||
listServerSigners(options?: RawAxiosRequestConfig): AxiosPromise<ServerSigner>; | ||
submitServerSignerSeedEventResult(serverSignerId: string, seedCreationEventResult?: SeedCreationEventResult, options?: RawAxiosRequestConfig): AxiosPromise<SeedCreationEventResult>; | ||
submitServerSignerSignatureEventResult(serverSignerId: string, signatureCreationEventResult?: SignatureCreationEventResult, options?: RawAxiosRequestConfig): AxiosPromise<SignatureCreationEventResult>; | ||
} | ||
export declare class ServerSignersApi extends BaseAPI implements ServerSignersApiInterface { | ||
createServerSigner(createServerSignerRequest?: CreateServerSignerRequest, options?: RawAxiosRequestConfig): Promise<import("axios").AxiosResponse<ServerSigner, any>>; | ||
getServerSigner(serverSignerId: string, options?: RawAxiosRequestConfig): Promise<import("axios").AxiosResponse<ServerSigner, any>>; | ||
listServerSignerEvents(serverSignerId: string, limit?: number, page?: string, options?: RawAxiosRequestConfig): Promise<import("axios").AxiosResponse<ServerSignerEventList, any>>; | ||
listServerSigners(options?: RawAxiosRequestConfig): Promise<import("axios").AxiosResponse<ServerSigner, any>>; | ||
submitServerSignerSeedEventResult(serverSignerId: string, seedCreationEventResult?: SeedCreationEventResult, options?: RawAxiosRequestConfig): Promise<import("axios").AxiosResponse<SeedCreationEventResult, any>>; | ||
submitServerSignerSignatureEventResult(serverSignerId: string, signatureCreationEventResult?: SignatureCreationEventResult, options?: RawAxiosRequestConfig): Promise<import("axios").AxiosResponse<SignatureCreationEventResult, any>>; | ||
} | ||
export declare const TradesApiAxiosParamCreator: (configuration?: Configuration) => { | ||
broadcastTrade: (walletId: string, addressId: string, tradeId: string, broadcastTradeRequest: BroadcastTradeRequest, options?: RawAxiosRequestConfig) => Promise<RequestArgs>; | ||
createTrade: (walletId: string, addressId: string, createTradeRequest: CreateTradeRequest, options?: RawAxiosRequestConfig) => Promise<RequestArgs>; | ||
getTrade: (walletId: string, addressId: string, tradeId: string, options?: RawAxiosRequestConfig) => Promise<RequestArgs>; | ||
listTrades: (walletId: string, addressId: string, limit?: number, page?: string, options?: RawAxiosRequestConfig) => Promise<RequestArgs>; | ||
}; | ||
export declare const TradesApiFp: (configuration?: Configuration) => { | ||
broadcastTrade(walletId: string, addressId: string, tradeId: string, broadcastTradeRequest: BroadcastTradeRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Trade>>; | ||
createTrade(walletId: string, addressId: string, createTradeRequest: CreateTradeRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Trade>>; | ||
getTrade(walletId: string, addressId: string, tradeId: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Trade>>; | ||
listTrades(walletId: string, addressId: string, limit?: number, page?: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<TradeList>>; | ||
}; | ||
export declare const TradesApiFactory: (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) => { | ||
broadcastTrade(walletId: string, addressId: string, tradeId: string, broadcastTradeRequest: BroadcastTradeRequest, options?: any): AxiosPromise<Trade>; | ||
createTrade(walletId: string, addressId: string, createTradeRequest: CreateTradeRequest, options?: any): AxiosPromise<Trade>; | ||
getTrade(walletId: string, addressId: string, tradeId: string, options?: any): AxiosPromise<Trade>; | ||
listTrades(walletId: string, addressId: string, limit?: number, page?: string, options?: any): AxiosPromise<TradeList>; | ||
}; | ||
export interface TradesApiInterface { | ||
broadcastTrade(walletId: string, addressId: string, tradeId: string, broadcastTradeRequest: BroadcastTradeRequest, options?: RawAxiosRequestConfig): AxiosPromise<Trade>; | ||
createTrade(walletId: string, addressId: string, createTradeRequest: CreateTradeRequest, options?: RawAxiosRequestConfig): AxiosPromise<Trade>; | ||
getTrade(walletId: string, addressId: string, tradeId: string, options?: RawAxiosRequestConfig): AxiosPromise<Trade>; | ||
listTrades(walletId: string, addressId: string, limit?: number, page?: string, options?: RawAxiosRequestConfig): AxiosPromise<TradeList>; | ||
} | ||
export declare class TradesApi extends BaseAPI implements TradesApiInterface { | ||
broadcastTrade(walletId: string, addressId: string, tradeId: string, broadcastTradeRequest: BroadcastTradeRequest, options?: RawAxiosRequestConfig): Promise<import("axios").AxiosResponse<Trade, any>>; | ||
createTrade(walletId: string, addressId: string, createTradeRequest: CreateTradeRequest, options?: RawAxiosRequestConfig): Promise<import("axios").AxiosResponse<Trade, any>>; | ||
getTrade(walletId: string, addressId: string, tradeId: string, options?: RawAxiosRequestConfig): Promise<import("axios").AxiosResponse<Trade, any>>; | ||
listTrades(walletId: string, addressId: string, limit?: number, page?: string, options?: RawAxiosRequestConfig): Promise<import("axios").AxiosResponse<TradeList, any>>; | ||
} | ||
export declare const TransfersApiAxiosParamCreator: (configuration?: Configuration) => { | ||
@@ -154,3 +333,9 @@ broadcastTransfer: (walletId: string, addressId: string, transferId: string, broadcastTransferRequest: BroadcastTransferRequest, options?: RawAxiosRequestConfig) => Promise<RequestArgs>; | ||
}; | ||
export declare class TransfersApi extends BaseAPI { | ||
export interface TransfersApiInterface { | ||
broadcastTransfer(walletId: string, addressId: string, transferId: string, broadcastTransferRequest: BroadcastTransferRequest, options?: RawAxiosRequestConfig): AxiosPromise<Transfer>; | ||
createTransfer(walletId: string, addressId: string, createTransferRequest: CreateTransferRequest, options?: RawAxiosRequestConfig): AxiosPromise<Transfer>; | ||
getTransfer(walletId: string, addressId: string, transferId: string, options?: RawAxiosRequestConfig): AxiosPromise<Transfer>; | ||
listTransfers(walletId: string, addressId: string, limit?: number, page?: string, options?: RawAxiosRequestConfig): AxiosPromise<TransferList>; | ||
} | ||
export declare class TransfersApi extends BaseAPI implements TransfersApiInterface { | ||
broadcastTransfer(walletId: string, addressId: string, transferId: string, broadcastTransferRequest: BroadcastTransferRequest, options?: RawAxiosRequestConfig): Promise<import("axios").AxiosResponse<Transfer, any>>; | ||
@@ -170,3 +355,6 @@ createTransfer(walletId: string, addressId: string, createTransferRequest: CreateTransferRequest, options?: RawAxiosRequestConfig): Promise<import("axios").AxiosResponse<Transfer, any>>; | ||
}; | ||
export declare class UsersApi extends BaseAPI { | ||
export interface UsersApiInterface { | ||
getCurrentUser(options?: RawAxiosRequestConfig): AxiosPromise<User>; | ||
} | ||
export declare class UsersApi extends BaseAPI implements UsersApiInterface { | ||
getCurrentUser(options?: RawAxiosRequestConfig): Promise<import("axios").AxiosResponse<User, any>>; | ||
@@ -195,3 +383,10 @@ } | ||
}; | ||
export declare class WalletsApi extends BaseAPI { | ||
export interface WalletsApiInterface { | ||
createWallet(createWalletRequest?: CreateWalletRequest, options?: RawAxiosRequestConfig): AxiosPromise<Wallet>; | ||
getWallet(walletId: string, options?: RawAxiosRequestConfig): AxiosPromise<Wallet>; | ||
getWalletBalance(walletId: string, assetId: string, options?: RawAxiosRequestConfig): AxiosPromise<Balance>; | ||
listWalletBalances(walletId: string, options?: RawAxiosRequestConfig): AxiosPromise<AddressBalanceList>; | ||
listWallets(limit?: number, page?: string, options?: RawAxiosRequestConfig): AxiosPromise<WalletList>; | ||
} | ||
export declare class WalletsApi extends BaseAPI implements WalletsApiInterface { | ||
createWallet(createWalletRequest?: CreateWalletRequest, options?: RawAxiosRequestConfig): Promise<import("axios").AxiosResponse<Wallet, any>>; | ||
@@ -198,0 +393,0 @@ getWallet(walletId: string, options?: RawAxiosRequestConfig): Promise<import("axios").AxiosResponse<Wallet, any>>; |
@@ -23,6 +23,3 @@ "use strict"; | ||
if (configuration && (configuration.username || configuration.password)) { | ||
object["auth"] = { | ||
username: configuration.username, | ||
password: configuration.password, | ||
}; | ||
object["auth"] = { username: configuration.username, password: configuration.password }; | ||
} | ||
@@ -29,0 +26,0 @@ }; |
@@ -54,3 +54,13 @@ "use strict"; | ||
async getBalance(assetId) { | ||
const response = await coinbase_1.Coinbase.apiClients.address.getAddressBalance(this.model.wallet_id, this.model.address_id, assetId); | ||
const normalizedAssetId = (() => { | ||
switch (assetId) { | ||
case coinbase_1.Coinbase.assets.Gwei: | ||
return coinbase_1.Coinbase.assets.Eth; | ||
case coinbase_1.Coinbase.assets.Wei: | ||
return coinbase_1.Coinbase.assets.Eth; | ||
default: | ||
return assetId; | ||
} | ||
})(); | ||
const response = await coinbase_1.Coinbase.apiClients.address.getAddressBalance(this.model.wallet_id, this.model.address_id, normalizedAssetId); | ||
if (!response.data) { | ||
@@ -65,3 +75,3 @@ return new decimal_js_1.Decimal(0); | ||
async createTransfer(amount, assetId, destination, intervalSeconds = 0.2, timeoutSeconds = 10) { | ||
if (!this.key) { | ||
if (!coinbase_1.Coinbase.useServerSigner && !this.key) { | ||
throw new errors_1.InternalError("Cannot transfer from address without private key loaded"); | ||
@@ -96,2 +106,3 @@ } | ||
case coinbase_1.Coinbase.assets.Gwei: | ||
return coinbase_1.Coinbase.assets.Eth; | ||
case coinbase_1.Coinbase.assets.Wei: | ||
@@ -110,16 +121,19 @@ return coinbase_1.Coinbase.assets.Eth; | ||
let response = await coinbase_1.Coinbase.apiClients.transfer.createTransfer(this.getWalletId(), this.getId(), createTransferRequest); | ||
const transfer = transfer_1.Transfer.fromModel(response.data); | ||
const transaction = transfer.getTransaction(); | ||
let signedPayload = await this.key.signTransaction(transaction); | ||
signedPayload = signedPayload.slice(2); | ||
const broadcastTransferRequest = { | ||
signed_payload: signedPayload, | ||
}; | ||
response = await coinbase_1.Coinbase.apiClients.transfer.broadcastTransfer(this.getWalletId(), this.getId(), transfer.getId(), broadcastTransferRequest); | ||
const updatedTransfer = transfer_1.Transfer.fromModel(response.data); | ||
let transfer = transfer_1.Transfer.fromModel(response.data); | ||
if (!coinbase_1.Coinbase.useServerSigner) { | ||
const transaction = transfer.getTransaction(); | ||
let signedPayload = await this.key.signTransaction(transaction); | ||
signedPayload = signedPayload.slice(2); | ||
const broadcastTransferRequest = { | ||
signed_payload: signedPayload, | ||
}; | ||
response = await coinbase_1.Coinbase.apiClients.transfer.broadcastTransfer(this.getWalletId(), this.getId(), transfer.getId(), broadcastTransferRequest); | ||
transfer = transfer_1.Transfer.fromModel(response.data); | ||
} | ||
const startTime = Date.now(); | ||
while (Date.now() - startTime < timeoutSeconds * 1000) { | ||
const status = await updatedTransfer.getStatus(); | ||
await transfer.reload(); | ||
const status = transfer.getStatus(); | ||
if (status === types_1.TransferStatus.COMPLETE || status === types_1.TransferStatus.FAILED) { | ||
return updatedTransfer; | ||
return transfer; | ||
} | ||
@@ -126,0 +140,0 @@ await (0, utils_1.delay)(intervalSeconds); |
@@ -45,3 +45,3 @@ "use strict"; | ||
sub: this.apiKey, | ||
iss: "coinbase-cloud", | ||
iss: "cdp", | ||
aud: ["cdp_service"], | ||
@@ -48,0 +48,0 @@ nbf: Math.floor(Date.now() / 1000), |
@@ -1,2 +0,2 @@ | ||
import { ApiClients } from "./types"; | ||
import { ApiClients, CoinbaseConfigureFromJsonOptions, CoinbaseOptions } from "./types"; | ||
import { User } from "./user"; | ||
@@ -15,7 +15,7 @@ export declare class Coinbase { | ||
static apiClients: ApiClients; | ||
static backupFilePath: string; | ||
static apiKeyPrivateKey: string; | ||
constructor(apiKeyName: string, privateKey: string, debugging?: boolean, basePath?: string, backupFilePath?: string); | ||
static configureFromJson(filePath?: string, debugging?: boolean, basePath?: string, backupFilePath?: string): Coinbase; | ||
static useServerSigner: boolean; | ||
constructor({ apiKeyName, privateKey, useServerSigner, debugging, basePath, }: CoinbaseOptions); | ||
static configureFromJson({ filePath, useServerSigner, debugging, basePath, }: CoinbaseConfigureFromJsonOptions): Coinbase; | ||
getDefaultUser(): Promise<User>; | ||
} |
@@ -33,3 +33,2 @@ "use strict"; | ||
const client_1 = require("../client"); | ||
const ethers_1 = require("ethers"); | ||
const base_1 = require("./../client/base"); | ||
@@ -41,4 +40,5 @@ const configuration_1 = require("./../client/configuration"); | ||
const utils_1 = require("./utils"); | ||
const os = __importStar(require("os")); | ||
class Coinbase { | ||
constructor(apiKeyName, privateKey, debugging = false, basePath = base_1.BASE_PATH, backupFilePath) { | ||
constructor({ apiKeyName = "", privateKey = "", useServerSigner = false, debugging = false, basePath = base_1.BASE_PATH, }) { | ||
if (apiKeyName === "") { | ||
@@ -56,11 +56,11 @@ throw new errors_1.InternalError("Invalid configuration: apiKeyName is empty"); | ||
(0, utils_1.registerAxiosInterceptors)(axiosInstance, config => coinbaseAuthenticator.authenticateRequest(config, debugging), response => (0, utils_1.logApiResponse)(response, debugging)); | ||
Coinbase.apiClients.user = (0, client_1.UsersApiFactory)(config, base_1.BASE_PATH, axiosInstance); | ||
Coinbase.apiClients.wallet = (0, client_1.WalletsApiFactory)(config, base_1.BASE_PATH, axiosInstance); | ||
Coinbase.apiClients.address = (0, client_1.AddressesApiFactory)(config, base_1.BASE_PATH, axiosInstance); | ||
Coinbase.apiClients.transfer = (0, client_1.TransfersApiFactory)(config, base_1.BASE_PATH, axiosInstance); | ||
Coinbase.apiClients.baseSepoliaProvider = new ethers_1.ethers.JsonRpcProvider("https://sepolia.base.org"); | ||
Coinbase.backupFilePath = backupFilePath ? backupFilePath : Coinbase.backupFilePath; | ||
Coinbase.apiClients.user = (0, client_1.UsersApiFactory)(config, basePath, axiosInstance); | ||
Coinbase.apiClients.wallet = (0, client_1.WalletsApiFactory)(config, basePath, axiosInstance); | ||
Coinbase.apiClients.address = (0, client_1.AddressesApiFactory)(config, basePath, axiosInstance); | ||
Coinbase.apiClients.transfer = (0, client_1.TransfersApiFactory)(config, basePath, axiosInstance); | ||
Coinbase.apiKeyPrivateKey = privateKey; | ||
Coinbase.useServerSigner = useServerSigner; | ||
} | ||
static configureFromJson(filePath = "coinbase_cloud_api_key.json", debugging = false, basePath = base_1.BASE_PATH, backupFilePath) { | ||
static configureFromJson({ filePath = "coinbase_cloud_api_key.json", useServerSigner = false, debugging = false, basePath = base_1.BASE_PATH, }) { | ||
filePath = filePath.startsWith("~") ? filePath.replace("~", os.homedir()) : filePath; | ||
if (!fs.existsSync(filePath)) { | ||
@@ -75,3 +75,9 @@ throw new errors_1.InvalidConfiguration(`Invalid configuration: file not found at ${filePath}`); | ||
} | ||
return new Coinbase(config.name, config.privateKey, debugging, basePath, backupFilePath); | ||
return new Coinbase({ | ||
apiKeyName: config.name, | ||
privateKey: config.privateKey, | ||
useServerSigner: useServerSigner, | ||
debugging: debugging, | ||
basePath: basePath, | ||
}); | ||
} | ||
@@ -104,2 +110,1 @@ catch (e) { | ||
Coinbase.apiClients = {}; | ||
Coinbase.backupFilePath = "seed.json"; |
@@ -40,2 +40,3 @@ "use strict"; | ||
const transfer_1 = require("../transfer"); | ||
const types_1 = require("../types"); | ||
describe("Address", () => { | ||
@@ -100,3 +101,3 @@ const transactionHash = (0, utils_1.generateRandomHash)(); | ||
expect(ethBalance).toEqual(new decimal_js_1.default("1000000000")); | ||
expect(coinbase_1.Coinbase.apiClients.address.getAddressBalance).toHaveBeenCalledWith(address.getWalletId(), address.getId(), assetId); | ||
expect(coinbase_1.Coinbase.apiClients.address.getAddressBalance).toHaveBeenCalledWith(address.getWalletId(), address.getId(), coinbase_1.Coinbase.assets.Eth); | ||
expect(coinbase_1.Coinbase.apiClients.address.getAddressBalance).toHaveBeenCalledTimes(1); | ||
@@ -109,3 +110,3 @@ }); | ||
expect(ethBalance).toEqual(new decimal_js_1.default("1000000000000000000")); | ||
expect(coinbase_1.Coinbase.apiClients.address.getAddressBalance).toHaveBeenCalledWith(address.getWalletId(), address.getId(), assetId); | ||
expect(coinbase_1.Coinbase.apiClients.address.getAddressBalance).toHaveBeenCalledWith(address.getWalletId(), address.getId(), coinbase_1.Coinbase.assets.Eth); | ||
expect(coinbase_1.Coinbase.apiClients.address.getAddressBalance).toHaveBeenCalledTimes(1); | ||
@@ -156,6 +157,2 @@ }); | ||
let walletId, id; | ||
const mockProvider = new ethers_1.ethers.JsonRpcProvider("https://sepolia.base.org"); | ||
mockProvider.getTransaction = jest.fn(); | ||
mockProvider.getTransactionReceipt = jest.fn(); | ||
coinbase_1.Coinbase.apiClients.baseSepoliaProvider = mockProvider; | ||
beforeEach(() => { | ||
@@ -187,11 +184,10 @@ weiAmount = new decimal_js_1.default("500000000000000000"); | ||
}); | ||
mockProvider.getTransaction.mockResolvedValueOnce({ | ||
blockHash: "0xdeadbeef", | ||
coinbase_1.Coinbase.apiClients.transfer.getTransfer = (0, utils_1.mockReturnValue)({ | ||
...utils_1.VALID_TRANSFER_MODEL, | ||
status: types_1.TransferStatus.COMPLETE, | ||
}); | ||
mockProvider.getTransactionReceipt.mockResolvedValueOnce({ | ||
status: 1, | ||
}); | ||
const transfer = await address.createTransfer(weiAmount, coinbase_1.Coinbase.assets.Wei, destination, intervalSeconds, timeoutSeconds); | ||
await address.createTransfer(weiAmount, coinbase_1.Coinbase.assets.Wei, destination, intervalSeconds, timeoutSeconds); | ||
expect(coinbase_1.Coinbase.apiClients.transfer.createTransfer).toHaveBeenCalledTimes(1); | ||
expect(coinbase_1.Coinbase.apiClients.transfer.broadcastTransfer).toHaveBeenCalledTimes(1); | ||
expect(coinbase_1.Coinbase.apiClients.transfer.getTransfer).toHaveBeenCalledTimes(1); | ||
}); | ||
@@ -217,2 +213,6 @@ it("should throw an APIError if the createTransfer API call fails", async () => { | ||
}); | ||
coinbase_1.Coinbase.apiClients.transfer.getTransfer = (0, utils_1.mockReturnValue)({ | ||
...utils_1.VALID_TRANSFER_MODEL, | ||
status: types_1.TransferStatus.BROADCAST, | ||
}); | ||
intervalSeconds = 0.000002; | ||
@@ -226,2 +226,13 @@ timeoutSeconds = 0.000002; | ||
}); | ||
it("should successfully create and complete a transfer when using server signer", async () => { | ||
coinbase_1.Coinbase.useServerSigner = true; | ||
coinbase_1.Coinbase.apiClients.transfer.createTransfer = (0, utils_1.mockReturnValue)(utils_1.VALID_TRANSFER_MODEL); | ||
coinbase_1.Coinbase.apiClients.transfer.getTransfer = (0, utils_1.mockReturnValue)({ | ||
...utils_1.VALID_TRANSFER_MODEL, | ||
status: types_1.TransferStatus.COMPLETE, | ||
}); | ||
await address.createTransfer(weiAmount, coinbase_1.Coinbase.assets.Wei, destination, intervalSeconds, timeoutSeconds); | ||
expect(coinbase_1.Coinbase.apiClients.transfer.createTransfer).toHaveBeenCalledTimes(1); | ||
expect(coinbase_1.Coinbase.apiClients.transfer.getTransfer).toHaveBeenCalledTimes(1); | ||
}); | ||
afterEach(() => { | ||
@@ -228,0 +239,0 @@ jest.restoreAllMocks(); |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const os = __importStar(require("os")); | ||
const fs = __importStar(require("fs")); | ||
const crypto_1 = require("crypto"); | ||
@@ -8,24 +36,39 @@ const api_error_1 = require("../api_error"); | ||
const ethers_1 = require("ethers"); | ||
const path_1 = __importDefault(require("path")); | ||
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_1.Coinbase("", "test")).toThrow("Invalid configuration: apiKeyName is empty"); | ||
expect(() => new coinbase_1.Coinbase("test", "")).toThrow("Invalid configuration: privateKey is empty"); | ||
expect(() => new coinbase_1.Coinbase({ privateKey: "test" })).toThrow("Invalid configuration: apiKeyName is empty"); | ||
expect(() => new coinbase_1.Coinbase({ apiKeyName: "test" })).toThrow("Invalid configuration: privateKey is empty"); | ||
}); | ||
it("should throw an error if the file does not exist", () => { | ||
expect(() => coinbase_1.Coinbase.configureFromJson(`${PATH_PREFIX}/does-not-exist.json`)).toThrow("Invalid configuration: file not found at ./src/coinbase/tests/config/does-not-exist.json"); | ||
expect(() => coinbase_1.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_1.Coinbase.configureFromJson(`${PATH_PREFIX}/coinbase_cloud_api_key.json`); | ||
const cbInstance = coinbase_1.Coinbase.configureFromJson({ | ||
filePath: `${PATH_PREFIX}/coinbase_cloud_api_key.json`, | ||
}); | ||
expect(cbInstance).toBeInstanceOf(coinbase_1.Coinbase); | ||
}); | ||
it("should throw an error if there is an issue reading the file or parsing the JSON data", () => { | ||
expect(() => coinbase_1.Coinbase.configureFromJson(`${PATH_PREFIX}/invalid.json`)).toThrow("Invalid configuration: missing configuration values"); | ||
expect(() => coinbase_1.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_1.Coinbase.configureFromJson(`${PATH_PREFIX}/not_parseable.json`)).toThrow("Not able to parse the configuration file"); | ||
expect(() => coinbase_1.Coinbase.configureFromJson({ filePath: `${PATH_PREFIX}/not_parseable.json` })).toThrow("Not able to parse the configuration file"); | ||
}); | ||
it("should expand the tilde to the home directory", () => { | ||
const configuration = fs.readFileSync(`${PATH_PREFIX}/coinbase_cloud_api_key.json`, "utf8"); | ||
const homeDir = os.homedir(); | ||
const relativePath = "~/test_config.json"; | ||
const expandedPath = path_1.default.join(homeDir, "test_config.json"); | ||
fs.writeFileSync(expandedPath, configuration, "utf8"); | ||
const cbInstance = coinbase_1.Coinbase.configureFromJson({ filePath: relativePath }); | ||
expect(cbInstance).toBeInstanceOf(coinbase_1.Coinbase); | ||
fs.unlinkSync(expandedPath); | ||
}); | ||
describe("should able to interact with the API", () => { | ||
let user, walletId, publicKey, addressId, transactionHash; | ||
const cbInstance = coinbase_1.Coinbase.configureFromJson(`${PATH_PREFIX}/coinbase_cloud_api_key.json`, true); | ||
const cbInstance = coinbase_1.Coinbase.configureFromJson({ | ||
filePath: `${PATH_PREFIX}/coinbase_cloud_api_key.json`, | ||
}); | ||
beforeAll(async () => { | ||
@@ -69,3 +112,5 @@ coinbase_1.Coinbase.apiClients = { | ||
it("should raise an error if the user is not found", async () => { | ||
const cbInstance = coinbase_1.Coinbase.configureFromJson(`${PATH_PREFIX}/coinbase_cloud_api_key.json`); | ||
const cbInstance = coinbase_1.Coinbase.configureFromJson({ | ||
filePath: `${PATH_PREFIX}/coinbase_cloud_api_key.json`, | ||
}); | ||
coinbase_1.Coinbase.apiClients.user.getCurrentUser = (0, utils_1.mockReturnRejectedValue)(new api_error_1.APIError("User not found")); | ||
@@ -72,0 +117,0 @@ await expect(cbInstance.getDefaultUser()).rejects.toThrow(api_error_1.APIError); |
@@ -16,6 +16,2 @@ "use strict"; | ||
const transactionHash = "0x6c087c1676e8269dd81e0777244584d0cbfd39b6997b3477242a008fa9349e11"; | ||
const mockProvider = new ethers_1.ethers.JsonRpcProvider("https://sepolia.base.org"); | ||
mockProvider.getTransaction = jest.fn(); | ||
mockProvider.getTransactionReceipt = jest.fn(); | ||
coinbase_1.Coinbase.apiClients.baseSepoliaProvider = mockProvider; | ||
describe("Transfer Class", () => { | ||
@@ -25,2 +21,3 @@ let transferModel; | ||
beforeEach(() => { | ||
coinbase_1.Coinbase.apiClients.transfer = utils_1.transfersApiMock; | ||
transferModel = utils_1.VALID_TRANSFER_MODEL; | ||
@@ -118,46 +115,58 @@ transfer = transfer_1.Transfer.fromModel(transferModel); | ||
it("should return PENDING when the transaction has not been created", async () => { | ||
const status = await transfer.getStatus(); | ||
const status = transfer.getStatus(); | ||
expect(status).toEqual(types_1.TransferStatus.PENDING); | ||
}); | ||
it("should return PENDING when the transaction has been created but not broadcast", async () => { | ||
transferModel.transaction_hash = transactionHash; | ||
transfer = transfer_1.Transfer.fromModel(transferModel); | ||
mockProvider.getTransaction.mockResolvedValueOnce(null); | ||
const status = await transfer.getStatus(); | ||
const status = transfer.getStatus(); | ||
expect(status).toEqual(types_1.TransferStatus.PENDING); | ||
}); | ||
it("should return BROADCAST when the transaction has been broadcast but not included in a block", async () => { | ||
transferModel.transaction_hash = transactionHash; | ||
transferModel.status = types_1.TransferStatus.BROADCAST; | ||
transfer = transfer_1.Transfer.fromModel(transferModel); | ||
mockProvider.getTransaction.mockResolvedValueOnce({ | ||
blockHash: null, | ||
}); | ||
const status = await transfer.getStatus(); | ||
const status = transfer.getStatus(); | ||
expect(status).toEqual(types_1.TransferStatus.BROADCAST); | ||
}); | ||
it("should return COMPLETE when the transaction has confirmed", async () => { | ||
transferModel.transaction_hash = transactionHash; | ||
transferModel.status = types_1.TransferStatus.COMPLETE; | ||
transfer = transfer_1.Transfer.fromModel(transferModel); | ||
mockProvider.getTransaction.mockResolvedValueOnce({ | ||
blockHash: "0xdeadbeef", | ||
}); | ||
mockProvider.getTransactionReceipt.mockResolvedValueOnce({ | ||
status: 1, | ||
}); | ||
const status = await transfer.getStatus(); | ||
const status = transfer.getStatus(); | ||
expect(status).toEqual(types_1.TransferStatus.COMPLETE); | ||
}); | ||
it("should return FAILED when the transaction has failed", async () => { | ||
transferModel.transaction_hash = transactionHash; | ||
transferModel.status = types_1.TransferStatus.FAILED; | ||
transfer = transfer_1.Transfer.fromModel(transferModel); | ||
mockProvider.getTransaction.mockResolvedValueOnce({ | ||
blockHash: "0xdeadbeef", | ||
const status = transfer.getStatus(); | ||
expect(status).toEqual(types_1.TransferStatus.FAILED); | ||
}); | ||
}); | ||
describe("reload", () => { | ||
it("should return PENDING when the trnasaction has not been created", async () => { | ||
coinbase_1.Coinbase.apiClients.transfer.getTransfer = (0, utils_1.mockReturnValue)({ | ||
...utils_1.VALID_TRANSFER_MODEL, | ||
status: types_1.TransferStatus.PENDING, | ||
}); | ||
mockProvider.getTransactionReceipt.mockResolvedValueOnce({ | ||
status: 0, | ||
await transfer.reload(); | ||
expect(transfer.getStatus()).toEqual(types_1.TransferStatus.PENDING); | ||
expect(coinbase_1.Coinbase.apiClients.transfer.getTransfer).toHaveBeenCalledTimes(1); | ||
}); | ||
it("should return COMPLETE when the trnasaction is complete", async () => { | ||
coinbase_1.Coinbase.apiClients.transfer.getTransfer = (0, utils_1.mockReturnValue)({ | ||
...utils_1.VALID_TRANSFER_MODEL, | ||
status: types_1.TransferStatus.COMPLETE, | ||
}); | ||
const status = await transfer.getStatus(); | ||
expect(status).toEqual(types_1.TransferStatus.FAILED); | ||
await transfer.reload(); | ||
expect(transfer.getStatus()).toEqual(types_1.TransferStatus.COMPLETE); | ||
expect(coinbase_1.Coinbase.apiClients.transfer.getTransfer).toHaveBeenCalledTimes(1); | ||
}); | ||
it("should return FAILED when the trnasaction has failed", async () => { | ||
coinbase_1.Coinbase.apiClients.transfer.getTransfer = (0, utils_1.mockReturnValue)({ | ||
...utils_1.VALID_TRANSFER_MODEL, | ||
status: types_1.TransferStatus.FAILED, | ||
}); | ||
await transfer.reload(); | ||
expect(transfer.getStatus()).toEqual(types_1.TransferStatus.FAILED); | ||
expect(coinbase_1.Coinbase.apiClients.transfer.getTransfer).toHaveBeenCalledTimes(1); | ||
}); | ||
}); | ||
}); |
@@ -30,3 +30,2 @@ "use strict"; | ||
const crypto = __importStar(require("crypto")); | ||
const fs = __importStar(require("fs")); | ||
const errors_1 = require("../errors"); | ||
@@ -104,189 +103,2 @@ const coinbase_1 = require("./../coinbase"); | ||
}); | ||
describe(".saveWallet", () => { | ||
let seed; | ||
let walletId; | ||
let mockSeedWallet; | ||
let savedWallet; | ||
beforeAll(async () => { | ||
walletId = crypto.randomUUID(); | ||
seed = "86fc9fba421dcc6ad42747f14132c3cd975bd9fb1454df84ce5ea554f2542fbe"; | ||
const { address1, wallet1PrivateKey } = (0, utils_1.generateWalletFromSeed)(seed); | ||
mockAddressModel = { | ||
address_id: address1, | ||
wallet_id: walletId, | ||
public_key: wallet1PrivateKey, | ||
network_id: coinbase_1.Coinbase.networkList.BaseSepolia, | ||
}; | ||
mockWalletModel = { | ||
id: walletId, | ||
network_id: coinbase_1.Coinbase.networkList.BaseSepolia, | ||
default_address: mockAddressModel, | ||
}; | ||
user = new user_1.User(mockUserModel); | ||
coinbase_1.Coinbase.apiClients.address = utils_1.addressesApiMock; | ||
coinbase_1.Coinbase.apiClients.address.getAddress = (0, utils_1.mockReturnValue)(mockAddressModel); | ||
coinbase_1.Coinbase.backupFilePath = crypto.randomUUID() + ".json"; | ||
coinbase_1.Coinbase.apiKeyPrivateKey = crypto.generateKeyPairSync("ec", { | ||
namedCurve: "prime256v1", | ||
privateKeyEncoding: { type: "pkcs8", format: "pem" }, | ||
publicKeyEncoding: { type: "spki", format: "pem" }, | ||
}).privateKey; | ||
mockSeedWallet = await wallet_1.Wallet.init(mockWalletModel, seed, [mockAddressModel]); | ||
}); | ||
afterEach(async () => { | ||
fs.unlinkSync(coinbase_1.Coinbase.backupFilePath); | ||
}); | ||
it("should save the Wallet data when encryption is false", async () => { | ||
savedWallet = user.saveWallet(mockSeedWallet); | ||
expect(savedWallet).toBe(mockSeedWallet); | ||
const storedSeedData = fs.readFileSync(coinbase_1.Coinbase.backupFilePath); | ||
const walletSeedData = JSON.parse(storedSeedData.toString()); | ||
expect(walletSeedData[walletId].encrypted).toBe(false); | ||
expect(walletSeedData[walletId].iv).toBe(""); | ||
expect(walletSeedData[walletId].authTag).toBe(""); | ||
expect(walletSeedData[walletId].seed).toBe(seed); | ||
}); | ||
it("should save the Wallet data when encryption is true", async () => { | ||
savedWallet = user.saveWallet(mockSeedWallet, true); | ||
expect(savedWallet).toBe(mockSeedWallet); | ||
const storedSeedData = fs.readFileSync(coinbase_1.Coinbase.backupFilePath); | ||
const walletSeedData = JSON.parse(storedSeedData.toString()); | ||
expect(walletSeedData[walletId].encrypted).toBe(true); | ||
expect(walletSeedData[walletId].iv).toBeTruthy(); | ||
expect(walletSeedData[walletId].authTag).toBeTruthy(); | ||
expect(walletSeedData[walletId].seed).not.toBe(seed); | ||
}); | ||
it("should throw an error when the existing file is malformed", async () => { | ||
fs.writeFileSync(coinbase_1.Coinbase.backupFilePath, JSON.stringify({ malformed: "test" }, null, 2), "utf8"); | ||
expect(() => user.saveWallet(mockSeedWallet)).toThrow(errors_1.ArgumentError); | ||
}); | ||
}); | ||
describe(".loadWallets", () => { | ||
let mockUserModel; | ||
let user; | ||
let walletId; | ||
let addressModel; | ||
let walletModelWithDefaultAddress; | ||
let addressListModel; | ||
let initialSeedData; | ||
let malformedSeedData; | ||
let seedDataWithoutSeed; | ||
let seedDataWithoutIv; | ||
let seedDataWithoutAuthTag; | ||
beforeAll(() => { | ||
walletId = crypto.randomUUID(); | ||
addressModel = (0, utils_1.newAddressModel)(walletId); | ||
addressModel.address_id = "0xB1666C6cDDB29468f721f3A4881a6e95CC963849"; | ||
walletModelWithDefaultAddress = { | ||
id: walletId, | ||
network_id: coinbase_1.Coinbase.networkList.BaseSepolia, | ||
default_address: addressModel, | ||
}; | ||
addressListModel = { | ||
data: [addressModel], | ||
has_more: false, | ||
next_page: "", | ||
total_count: 1, | ||
}; | ||
coinbase_1.Coinbase.apiClients.wallet = utils_1.walletsApiMock; | ||
coinbase_1.Coinbase.apiClients.address = utils_1.addressesApiMock; | ||
coinbase_1.Coinbase.backupFilePath = `${crypto.randomUUID()}.json`; | ||
coinbase_1.Coinbase.apiKeyPrivateKey = crypto.generateKeyPairSync("ec", { | ||
namedCurve: "prime256v1", | ||
privateKeyEncoding: { type: "pkcs8", format: "pem" }, | ||
publicKeyEncoding: { type: "spki", format: "pem" }, | ||
}).privateKey; | ||
mockUserModel = { | ||
id: "12345", | ||
}; | ||
initialSeedData = { | ||
[walletId]: { | ||
seed: "86fc9fba421dcc6ad42747f14132c3cd975bd9fb1454df84ce5ea554f2542fbe", | ||
encrypted: false, | ||
iv: "", | ||
authTag: "", | ||
}, | ||
}; | ||
malformedSeedData = { | ||
[walletId]: "test", | ||
}; | ||
seedDataWithoutSeed = { | ||
[walletId]: { | ||
seed: "", | ||
encrypted: false, | ||
}, | ||
}; | ||
seedDataWithoutIv = { | ||
[walletId]: { | ||
seed: "86fc9fba421dcc6ad42747f14132c3cd975bd9fb1454df84ce5ea554f2542fbe", | ||
encrypted: true, | ||
iv: "", | ||
auth_tag: "0x111", | ||
}, | ||
}; | ||
seedDataWithoutAuthTag = { | ||
[walletId]: { | ||
seed: "86fc9fba421dcc6ad42747f14132c3cd975bd9fb1454df84ce5ea554f2542fbe", | ||
encrypted: true, | ||
iv: "0x111", | ||
auth_tag: "", | ||
}, | ||
}; | ||
}); | ||
beforeEach(() => { | ||
user = new user_1.User(mockUserModel); | ||
fs.writeFileSync(coinbase_1.Coinbase.backupFilePath, JSON.stringify(initialSeedData, null, 2)); | ||
}); | ||
afterEach(() => { | ||
if (fs.existsSync(coinbase_1.Coinbase.backupFilePath)) { | ||
fs.unlinkSync(coinbase_1.Coinbase.backupFilePath); | ||
} | ||
}); | ||
it("loads the Wallet from backup", async () => { | ||
const seed = "86fc9fba421dcc6ad42747f14132c3cd975bd9fb1454df84ce5ea554f2542fbe"; | ||
const { address1, address2 } = (0, utils_1.generateWalletFromSeed)(seed); | ||
const addressModel1 = (0, utils_1.newAddressModel)(walletId, address1); | ||
const addressModel2 = (0, utils_1.newAddressModel)(walletId, address2); | ||
walletModelWithDefaultAddress = { | ||
id: walletId, | ||
network_id: coinbase_1.Coinbase.networkList.BaseSepolia, | ||
default_address: addressModel1, | ||
}; | ||
addressListModel = { | ||
data: [addressModel1, addressModel2], | ||
has_more: false, | ||
next_page: "", | ||
total_count: 2, | ||
}; | ||
coinbase_1.Coinbase.apiClients.wallet = utils_1.walletsApiMock; | ||
coinbase_1.Coinbase.apiClients.wallet.getWallet = (0, utils_1.mockReturnValue)(walletModelWithDefaultAddress); | ||
coinbase_1.Coinbase.apiClients.address = utils_1.addressesApiMock; | ||
coinbase_1.Coinbase.apiClients.address.listAddresses = (0, utils_1.mockReturnValue)(addressListModel); | ||
const wallets = await user.loadWallets(); | ||
const wallet = wallets[walletId]; | ||
expect(wallet).not.toBeNull(); | ||
expect(wallet.getId()).toBe(walletId); | ||
expect(wallet.getDefaultAddress()?.getId()).toBe(addressModel1.address_id); | ||
}); | ||
it("throws an error when the backup file is absent", async () => { | ||
fs.unlinkSync(coinbase_1.Coinbase.backupFilePath); | ||
await expect(user.loadWallets()).rejects.toThrow(new errors_1.ArgumentError("Backup file not found")); | ||
}); | ||
it("throws an error when the backup file is corrupted", async () => { | ||
fs.writeFileSync(coinbase_1.Coinbase.backupFilePath, JSON.stringify(malformedSeedData, null, 2)); | ||
await expect(user.loadWallets()).rejects.toThrow(new errors_1.ArgumentError("Malformed backup data")); | ||
}); | ||
it("throws an error when backup does not contain seed", async () => { | ||
fs.writeFileSync(coinbase_1.Coinbase.backupFilePath, JSON.stringify(seedDataWithoutSeed, null, 2)); | ||
await expect(user.loadWallets()).rejects.toThrow(new errors_1.ArgumentError("Malformed backup data")); | ||
}); | ||
it("throws an error when backup does not contain iv", async () => { | ||
fs.writeFileSync(coinbase_1.Coinbase.backupFilePath, JSON.stringify(seedDataWithoutIv, null, 2)); | ||
await expect(user.loadWallets()).rejects.toThrow(new errors_1.ArgumentError("Malformed backup data")); | ||
}); | ||
it("throws an error when backup does not contain auth_tag", async () => { | ||
fs.writeFileSync(coinbase_1.Coinbase.backupFilePath, JSON.stringify(seedDataWithoutAuthTag, null, 2)); | ||
await expect(user.loadWallets()).rejects.toThrow(new errors_1.ArgumentError("Malformed backup data")); | ||
}); | ||
}); | ||
describe(".listWallets", () => { | ||
@@ -349,4 +161,4 @@ let user; | ||
}); | ||
const wallets = await user.listWallets(); | ||
expect(wallets.length).toBe(0); | ||
const result = await user.listWallets(); | ||
expect(result.wallets.length).toBe(0); | ||
expect(coinbase_1.Coinbase.apiClients.wallet.listWallets).toHaveBeenCalledTimes(1); | ||
@@ -361,11 +173,12 @@ expect(coinbase_1.Coinbase.apiClients.address.listAddresses).toHaveBeenCalledTimes(0); | ||
has_more: false, | ||
next_page: "", | ||
next_page: "nextPageToken", | ||
total_count: 1, | ||
}); | ||
coinbase_1.Coinbase.apiClients.address.listAddresses = (0, utils_1.mockReturnValue)(addressListModel); | ||
const wallets = await user.listWallets(); | ||
expect(wallets[0]).toBeInstanceOf(wallet_1.Wallet); | ||
expect(wallets.length).toBe(1); | ||
expect(wallets[0].getId()).toBe(walletId); | ||
expect(wallets[0].listAddresses().length).toBe(2); | ||
const result = await user.listWallets(); | ||
expect(result.wallets[0]).toBeInstanceOf(wallet_1.Wallet); | ||
expect(result.wallets.length).toBe(1); | ||
expect(result.wallets[0].getId()).toBe(walletId); | ||
expect(result.wallets[0].listAddresses().length).toBe(2); | ||
expect(result.nextPageToken).toBe("nextPageToken"); | ||
expect(coinbase_1.Coinbase.apiClients.wallet.listWallets).toHaveBeenCalledTimes(1); | ||
@@ -385,4 +198,5 @@ expect(coinbase_1.Coinbase.apiClients.address.listAddresses).toHaveBeenCalledTimes(1); | ||
}); | ||
coinbase_1.Coinbase.apiClients.address.listAddresses = (0, utils_1.mockReturnValue)(addressListModel); | ||
const [unhydratedWallet] = await user.listWallets(); | ||
coinbase_1.Coinbase.apiClients.address.listAddresses = (0, utils_1.mockReturnValue)(mockAddressList); | ||
const result = await user.listWallets(); | ||
const unhydratedWallet = result.wallets[0]; | ||
expect(unhydratedWallet.canSign()).toBe(false); | ||
@@ -403,4 +217,5 @@ await unhydratedWallet.setSeed(seed); | ||
}); | ||
coinbase_1.Coinbase.apiClients.address.listAddresses = (0, utils_1.mockReturnValue)(addressListModel); | ||
const [unhydratedWallet] = await user.listWallets(); | ||
coinbase_1.Coinbase.apiClients.address.listAddresses = (0, utils_1.mockReturnValue)(mockAddressList); | ||
const result = await user.listWallets(); | ||
const unhydratedWallet = result.wallets[0]; | ||
expect(() => unhydratedWallet.export()).toThrow(new errors_1.InternalError("Cannot export Wallet without loaded seed")); | ||
@@ -440,3 +255,4 @@ await expect(unhydratedWallet.createAddress()).rejects.toThrow(errors_1.InternalError); | ||
}); | ||
const [wallet] = await user.listWallets(); | ||
const result = await user.listWallets(); | ||
const wallet = result.wallets[0]; | ||
expect(wallet.getId()).toBe(walletId); | ||
@@ -456,2 +272,65 @@ expect(wallet.canSign()).toBe(false); | ||
}); | ||
describe(".getWallet", () => { | ||
let user; | ||
let walletId; | ||
let walletModelWithDefaultAddress; | ||
let addressListModel; | ||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
walletId = crypto.randomUUID(); | ||
const seed = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"; | ||
const { address1 } = (0, utils_1.generateWalletFromSeed)(seed); | ||
mockAddressModel = (0, utils_1.newAddressModel)(walletId, address1); | ||
const addressModel1 = (0, utils_1.newAddressModel)(walletId); | ||
const addressModel2 = (0, utils_1.newAddressModel)(walletId); | ||
walletModelWithDefaultAddress = { | ||
id: walletId, | ||
network_id: coinbase_1.Coinbase.networkList.BaseSepolia, | ||
default_address: addressModel1, | ||
}; | ||
addressListModel = { | ||
data: [addressModel1, addressModel2], | ||
has_more: false, | ||
next_page: "", | ||
total_count: 1, | ||
}; | ||
coinbase_1.Coinbase.apiClients.wallet = utils_1.walletsApiMock; | ||
coinbase_1.Coinbase.apiClients.address = utils_1.addressesApiMock; | ||
const mockUserModel = { | ||
id: "12345", | ||
}; | ||
user = new user_1.User(mockUserModel); | ||
}); | ||
it("should raise an error when the Wallet API call fails", async () => { | ||
coinbase_1.Coinbase.apiClients.wallet.getWallet = (0, utils_1.mockReturnRejectedValue)(new Error("API Error")); | ||
await expect(user.getWallet(walletId)).rejects.toThrow(new Error("API Error")); | ||
expect(coinbase_1.Coinbase.apiClients.wallet.getWallet).toHaveBeenCalledTimes(1); | ||
expect(coinbase_1.Coinbase.apiClients.wallet.getWallet).toHaveBeenLastCalledWith(walletId); | ||
}); | ||
it("should raise an error when the Address API call fails", async () => { | ||
coinbase_1.Coinbase.apiClients.wallet.getWallet = (0, utils_1.mockReturnValue)(walletModelWithDefaultAddress); | ||
coinbase_1.Coinbase.apiClients.address.listAddresses = (0, utils_1.mockReturnRejectedValue)(new Error("API Error")); | ||
await expect(user.getWallet(walletId)).rejects.toThrow(new Error("API Error")); | ||
expect(coinbase_1.Coinbase.apiClients.wallet.getWallet).toHaveBeenCalledTimes(1); | ||
expect(coinbase_1.Coinbase.apiClients.wallet.getWallet).toHaveBeenLastCalledWith(walletId); | ||
expect(coinbase_1.Coinbase.apiClients.address.listAddresses).toHaveBeenCalledTimes(1); | ||
expect(coinbase_1.Coinbase.apiClients.address.listAddresses).toHaveBeenLastCalledWith(walletId); | ||
}); | ||
it("should return the Wallet", async () => { | ||
const seed = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"; | ||
const { address1 } = (0, utils_1.generateWalletFromSeed)(seed); | ||
mockAddressModel = (0, utils_1.newAddressModel)(walletId, address1); | ||
coinbase_1.Coinbase.apiClients.wallet.getWallet = (0, utils_1.mockReturnValue)(walletModelWithDefaultAddress); | ||
coinbase_1.Coinbase.apiClients.address.listAddresses = (0, utils_1.mockReturnValue)(addressListModel); | ||
const result = await user.getWallet(walletId); | ||
expect(result).toBeInstanceOf(wallet_1.Wallet); | ||
expect(result.getId()).toBe(walletId); | ||
expect(result.listAddresses().length).toBe(2); | ||
expect(result.canSign()).toBe(false); | ||
expect(coinbase_1.Coinbase.apiClients.wallet.getWallet).toHaveBeenCalledTimes(1); | ||
expect(coinbase_1.Coinbase.apiClients.address.listAddresses).toHaveBeenCalledTimes(1); | ||
expect(coinbase_1.Coinbase.apiClients.address.listAddresses).toHaveBeenCalledWith(walletId); | ||
expect(coinbase_1.Coinbase.apiClients.wallet.getWallet).toHaveBeenCalledWith(walletId); | ||
}); | ||
}); | ||
}); |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
@@ -6,2 +29,3 @@ return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const fs = __importStar(require("fs")); | ||
const crypto_1 = __importDefault(require("crypto")); | ||
@@ -16,2 +40,3 @@ const decimal_js_1 = __importDefault(require("decimal.js")); | ||
const wallet_1 = require("../wallet"); | ||
const types_1 = require("../types"); | ||
const utils_1 = require("./utils"); | ||
@@ -45,9 +70,8 @@ describe("Wallet Class", () => { | ||
}); | ||
beforeEach(async () => { | ||
coinbase_1.Coinbase.useServerSigner = false; | ||
}); | ||
describe(".createTransfer", () => { | ||
let weiAmount, destination, intervalSeconds, timeoutSeconds; | ||
let balanceModel; | ||
const mockProvider = new ethers_1.ethers.JsonRpcProvider("https://sepolia.base.org"); | ||
mockProvider.getTransaction = jest.fn(); | ||
mockProvider.getTransactionReceipt = jest.fn(); | ||
coinbase_1.Coinbase.apiClients.baseSepoliaProvider = mockProvider; | ||
beforeEach(() => { | ||
@@ -78,11 +102,10 @@ const key = ethers_1.ethers.Wallet.createRandom(); | ||
}); | ||
mockProvider.getTransaction.mockResolvedValueOnce({ | ||
blockHash: "0xdeadbeef", | ||
coinbase_1.Coinbase.apiClients.transfer.getTransfer = (0, utils_1.mockReturnValue)({ | ||
...utils_1.VALID_TRANSFER_MODEL, | ||
status: types_1.TransferStatus.COMPLETE, | ||
}); | ||
mockProvider.getTransactionReceipt.mockResolvedValueOnce({ | ||
status: 1, | ||
}); | ||
await wallet.createTransfer(weiAmount, coinbase_1.Coinbase.assets.Wei, destination, intervalSeconds, timeoutSeconds); | ||
expect(coinbase_1.Coinbase.apiClients.transfer.createTransfer).toHaveBeenCalledTimes(1); | ||
expect(coinbase_1.Coinbase.apiClients.transfer.broadcastTransfer).toHaveBeenCalledTimes(1); | ||
expect(coinbase_1.Coinbase.apiClients.transfer.getTransfer).toHaveBeenCalledTimes(1); | ||
}); | ||
@@ -104,2 +127,6 @@ it("should throw an APIError if the createTransfer API call fails", async () => { | ||
}); | ||
coinbase_1.Coinbase.apiClients.transfer.getTransfer = (0, utils_1.mockReturnValue)({ | ||
...utils_1.VALID_TRANSFER_MODEL, | ||
status: types_1.TransferStatus.BROADCAST, | ||
}); | ||
intervalSeconds = 0.000002; | ||
@@ -113,2 +140,13 @@ timeoutSeconds = 0.000002; | ||
}); | ||
it("should successfully create and complete a transfer when using server signer", async () => { | ||
coinbase_1.Coinbase.useServerSigner = true; | ||
coinbase_1.Coinbase.apiClients.transfer.createTransfer = (0, utils_1.mockReturnValue)(utils_1.VALID_TRANSFER_MODEL); | ||
coinbase_1.Coinbase.apiClients.transfer.getTransfer = (0, utils_1.mockReturnValue)({ | ||
...utils_1.VALID_TRANSFER_MODEL, | ||
status: types_1.TransferStatus.COMPLETE, | ||
}); | ||
await wallet.createTransfer(weiAmount, coinbase_1.Coinbase.assets.Wei, destination, intervalSeconds, timeoutSeconds); | ||
expect(coinbase_1.Coinbase.apiClients.transfer.createTransfer).toHaveBeenCalledTimes(1); | ||
expect(coinbase_1.Coinbase.apiClients.transfer.getTransfer).toHaveBeenCalledTimes(1); | ||
}); | ||
afterEach(() => { | ||
@@ -156,2 +194,38 @@ jest.restoreAllMocks(); | ||
}); | ||
describe("when using a server signer", () => { | ||
let walletId = crypto_1.default.randomUUID(); | ||
let wallet; | ||
beforeEach(async () => { | ||
jest.clearAllMocks(); | ||
coinbase_1.Coinbase.useServerSigner = true; | ||
}); | ||
it("should return a Wallet instance", async () => { | ||
coinbase_1.Coinbase.apiClients.wallet.createWallet = (0, utils_1.mockReturnValue)({ | ||
...utils_1.VALID_WALLET_MODEL, | ||
server_signer_status: types_1.ServerSignerStatus.PENDING, | ||
}); | ||
coinbase_1.Coinbase.apiClients.wallet.getWallet = (0, utils_1.mockReturnValue)({ | ||
...utils_1.VALID_WALLET_MODEL, | ||
server_signer_status: types_1.ServerSignerStatus.ACTIVE, | ||
}); | ||
coinbase_1.Coinbase.apiClients.address.createAddress = (0, utils_1.mockReturnValue)((0, utils_1.newAddressModel)(walletId)); | ||
wallet = await wallet_1.Wallet.create(); | ||
expect(wallet).toBeInstanceOf(wallet_1.Wallet); | ||
expect(wallet.getServerSignerStatus()).toBe(types_1.ServerSignerStatus.ACTIVE); | ||
expect(coinbase_1.Coinbase.apiClients.wallet.createWallet).toHaveBeenCalledTimes(1); | ||
expect(coinbase_1.Coinbase.apiClients.wallet.getWallet).toHaveBeenCalledTimes(2); | ||
expect(coinbase_1.Coinbase.apiClients.address.createAddress).toHaveBeenCalledTimes(1); | ||
}); | ||
it("should throw an Error if the Wallet times out waiting on a not active server signer", async () => { | ||
const intervalSeconds = 0.000002; | ||
const timeoutSeconds = 0.000002; | ||
coinbase_1.Coinbase.apiClients.wallet.getWallet = (0, utils_1.mockReturnValue)({ | ||
...utils_1.VALID_WALLET_MODEL, | ||
server_signer_status: types_1.ServerSignerStatus.PENDING, | ||
}); | ||
await expect(wallet_1.Wallet.create(intervalSeconds, timeoutSeconds)).rejects.toThrow("Wallet creation timed out. Check status of your Server-Signer"); | ||
expect(coinbase_1.Coinbase.apiClients.wallet.createWallet).toHaveBeenCalledTimes(1); | ||
expect(coinbase_1.Coinbase.apiClients.wallet.getWallet).toHaveBeenCalled(); | ||
}); | ||
}); | ||
}); | ||
@@ -346,2 +420,117 @@ describe(".init", () => { | ||
}); | ||
describe(".saveSeed", () => { | ||
const seed = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"; | ||
let apiPrivateKey; | ||
const filePath = "seeds.json"; | ||
let seedWallet; | ||
beforeEach(async () => { | ||
apiPrivateKey = coinbase_1.Coinbase.apiKeyPrivateKey; | ||
coinbase_1.Coinbase.apiKeyPrivateKey = crypto_1.default.generateKeyPairSync("ec", { | ||
namedCurve: "prime256v1", | ||
privateKeyEncoding: { type: "pkcs8", format: "pem" }, | ||
publicKeyEncoding: { type: "spki", format: "pem" }, | ||
}).privateKey; | ||
fs.writeFileSync(filePath, JSON.stringify({}), "utf8"); | ||
seedWallet = await wallet_1.Wallet.init(walletModel, seed); | ||
}); | ||
afterEach(async () => { | ||
fs.unlinkSync(filePath); | ||
coinbase_1.Coinbase.apiKeyPrivateKey = apiPrivateKey; | ||
}); | ||
it("should save the seed when encryption is false", async () => { | ||
seedWallet.saveSeed(filePath, false); | ||
const storedSeedData = fs.readFileSync(filePath); | ||
const walletSeedData = JSON.parse(storedSeedData.toString()); | ||
expect(walletSeedData[walletId].encrypted).toBe(false); | ||
expect(walletSeedData[walletId].iv).toBe(""); | ||
expect(walletSeedData[walletId].authTag).toBe(""); | ||
expect(walletSeedData[walletId].seed).toBe(seed); | ||
}); | ||
it("should save the seed when encryption is true", async () => { | ||
seedWallet.saveSeed(filePath, true); | ||
const storedSeedData = fs.readFileSync(filePath); | ||
const walletSeedData = JSON.parse(storedSeedData.toString()); | ||
expect(walletSeedData[walletId].encrypted).toBe(true); | ||
expect(walletSeedData[walletId].iv).not.toBe(""); | ||
expect(walletSeedData[walletId].authTag).not.toBe(""); | ||
expect(walletSeedData[walletId].seed).not.toBe(seed); | ||
}); | ||
it("should throw an error when the wallet is seedless", async () => { | ||
const seedlessWallet = await wallet_1.Wallet.init(walletModel, "", [(0, utils_1.newAddressModel)(walletId)]); | ||
expect(() => seedlessWallet.saveSeed(filePath, false)).toThrow(errors_1.InternalError); | ||
}); | ||
}); | ||
describe(".loadSeed", () => { | ||
const seed = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"; | ||
let apiPrivateKey; | ||
const filePath = "seeds.json"; | ||
let seedWallet; | ||
let seedlessWallet; | ||
const addressModel = (0, utils_1.newAddressModel)(walletId, "0x919538116b4F25f1CE01429fd9Ed7964556bf565"); | ||
beforeEach(async () => { | ||
apiPrivateKey = coinbase_1.Coinbase.apiKeyPrivateKey; | ||
coinbase_1.Coinbase.apiKeyPrivateKey = crypto_1.default.generateKeyPairSync("ec", { | ||
namedCurve: "prime256v1", | ||
privateKeyEncoding: { type: "pkcs8", format: "pem" }, | ||
publicKeyEncoding: { type: "spki", format: "pem" }, | ||
}).privateKey; | ||
const initialSeedData = { | ||
[walletId]: { | ||
encrypted: false, | ||
iv: "", | ||
authTag: "", | ||
seed, | ||
}, | ||
}; | ||
fs.writeFileSync(filePath, JSON.stringify(initialSeedData), "utf8"); | ||
seedWallet = await wallet_1.Wallet.init(walletModel, seed, [addressModel]); | ||
seedlessWallet = await wallet_1.Wallet.init(walletModel, "", [addressModel]); | ||
}); | ||
afterEach(async () => { | ||
fs.unlinkSync(filePath); | ||
coinbase_1.Coinbase.apiKeyPrivateKey = apiPrivateKey; | ||
}); | ||
it("loads the seed from the file", async () => { | ||
seedlessWallet.loadSeed(filePath); | ||
expect(seedlessWallet.canSign()).toBe(true); | ||
}); | ||
it("loads the encrypted seed from the file", async () => { | ||
seedWallet.saveSeed(filePath, true); | ||
seedlessWallet.loadSeed(filePath); | ||
expect(seedlessWallet.canSign()).toBe(true); | ||
}); | ||
it("loads the encrypted seed from the file with multiple seeds", async () => { | ||
seedWallet.saveSeed(filePath, true); | ||
const otherModel = { | ||
id: crypto_1.default.randomUUID(), | ||
network_id: coinbase_1.Coinbase.networkList.BaseSepolia, | ||
}; | ||
const otherWallet = await wallet_1.Wallet.init(otherModel); | ||
otherWallet.saveSeed(filePath, true); | ||
seedlessWallet.loadSeed(filePath); | ||
expect(seedlessWallet.canSign()).toBe(true); | ||
}); | ||
it("raises an error if the wallet is already hydrated", async () => { | ||
expect(() => seedWallet.loadSeed(filePath)).toThrow(errors_1.InternalError); | ||
}); | ||
it("raises an error when file contains different wallet data", async () => { | ||
const otherSeedData = { | ||
[crypto_1.default.randomUUID()]: { | ||
encrypted: false, | ||
iv: "", | ||
authTag: "", | ||
seed, | ||
}, | ||
}; | ||
fs.writeFileSync(filePath, JSON.stringify(otherSeedData), "utf8"); | ||
expect(() => seedlessWallet.loadSeed(filePath)).toThrow(errors_1.ArgumentError); | ||
}); | ||
it("raises an error when the file is absent", async () => { | ||
expect(() => seedlessWallet.loadSeed("non-file.json")).toThrow(errors_1.ArgumentError); | ||
}); | ||
it("raises an error when the file is corrupted", async () => { | ||
fs.writeFileSync(filePath, "corrupted data", "utf8"); | ||
expect(() => seedlessWallet.loadSeed(filePath)).toThrow(errors_1.ArgumentError); | ||
}); | ||
}); | ||
}); |
@@ -22,5 +22,6 @@ import { Decimal } from "decimal.js"; | ||
setSignedTransaction(transaction: ethers.Transaction): void; | ||
getStatus(): Promise<TransferStatus>; | ||
getStatus(): TransferStatus | undefined; | ||
getTransactionLink(): string; | ||
reload(): Promise<void>; | ||
toString(): Promise<string>; | ||
} |
@@ -87,13 +87,15 @@ "use strict"; | ||
} | ||
async getStatus() { | ||
const transactionHash = this.getTransactionHash(); | ||
if (!transactionHash) | ||
return types_1.TransferStatus.PENDING; | ||
const onchainTransaction = await coinbase_1.Coinbase.apiClients.baseSepoliaProvider.getTransaction(transactionHash); | ||
if (!onchainTransaction) | ||
return types_1.TransferStatus.PENDING; | ||
if (!onchainTransaction.blockHash) | ||
return types_1.TransferStatus.BROADCAST; | ||
const transactionReceipt = await coinbase_1.Coinbase.apiClients.baseSepoliaProvider.getTransactionReceipt(transactionHash); | ||
return transactionReceipt?.status ? types_1.TransferStatus.COMPLETE : types_1.TransferStatus.FAILED; | ||
getStatus() { | ||
switch (this.model.status) { | ||
case types_1.TransferStatus.PENDING: | ||
return types_1.TransferStatus.PENDING; | ||
case types_1.TransferStatus.BROADCAST: | ||
return types_1.TransferStatus.BROADCAST; | ||
case types_1.TransferStatus.COMPLETE: | ||
return types_1.TransferStatus.COMPLETE; | ||
case types_1.TransferStatus.FAILED: | ||
return types_1.TransferStatus.FAILED; | ||
default: | ||
return undefined; | ||
} | ||
} | ||
@@ -103,10 +105,13 @@ getTransactionLink() { | ||
} | ||
async reload() { | ||
const result = await coinbase_1.Coinbase.apiClients.transfer.getTransfer(this.getWalletId(), this.getFromAddressId(), this.getId()); | ||
this.model = result?.data; | ||
} | ||
async toString() { | ||
const status = await this.getStatus(); | ||
return (`Transfer{transferId: '${this.getId()}', networkId: '${this.getNetworkId()}', ` + | ||
`fromAddressId: '${this.getFromAddressId()}', destinationAddressId: '${this.getDestinationAddressId()}', ` + | ||
`assetId: '${this.getAssetId()}', amount: '${this.getAmount()}', transactionHash: '${this.getTransactionHash()}', ` + | ||
`transactionLink: '${this.getTransactionLink()}', status: '${status}'}`); | ||
`transactionLink: '${this.getTransactionLink()}', status: '${this.getStatus()}'}`); | ||
} | ||
} | ||
exports.Transfer = Transfer; |
import { Decimal } from "decimal.js"; | ||
import { AxiosPromise, AxiosRequestConfig, RawAxiosRequestConfig } from "axios"; | ||
import { ethers } from "ethers"; | ||
import { Address as AddressModel, AddressList, AddressBalanceList, Balance, CreateAddressRequest, CreateWalletRequest, BroadcastTransferRequest, CreateTransferRequest, TransferList, User as UserModel, Wallet as WalletModel, Transfer as TransferModel, WalletList } from "./../client/api"; | ||
@@ -41,9 +40,8 @@ import { Address } from "./address"; | ||
transfer?: TransferAPIClient; | ||
baseSepoliaProvider?: ethers.Provider; | ||
}; | ||
export declare enum TransferStatus { | ||
PENDING = "PENDING", | ||
BROADCAST = "BROADCAST", | ||
COMPLETE = "COMPLETE", | ||
FAILED = "FAILED" | ||
PENDING = "pending", | ||
BROADCAST = "broadcast", | ||
COMPLETE = "complete", | ||
FAILED = "failed" | ||
} | ||
@@ -62,1 +60,18 @@ export type WalletData = { | ||
export type Destination = string | Address | Wallet; | ||
export declare enum ServerSignerStatus { | ||
PENDING = "pending_seed_creation", | ||
ACTIVE = "active_seed" | ||
} | ||
export type CoinbaseOptions = { | ||
apiKeyName?: string; | ||
privateKey?: string; | ||
useServerSigner?: boolean; | ||
debugging?: boolean; | ||
basePath?: string; | ||
}; | ||
export type CoinbaseConfigureFromJsonOptions = { | ||
filePath: string; | ||
useServerSigner?: boolean; | ||
debugging?: boolean; | ||
basePath?: string; | ||
}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.TransferStatus = void 0; | ||
exports.ServerSignerStatus = exports.TransferStatus = void 0; | ||
var TransferStatus; | ||
(function (TransferStatus) { | ||
TransferStatus["PENDING"] = "PENDING"; | ||
TransferStatus["BROADCAST"] = "BROADCAST"; | ||
TransferStatus["COMPLETE"] = "COMPLETE"; | ||
TransferStatus["FAILED"] = "FAILED"; | ||
TransferStatus["PENDING"] = "pending"; | ||
TransferStatus["BROADCAST"] = "broadcast"; | ||
TransferStatus["COMPLETE"] = "complete"; | ||
TransferStatus["FAILED"] = "failed"; | ||
})(TransferStatus || (exports.TransferStatus = TransferStatus = {})); | ||
var ServerSignerStatus; | ||
(function (ServerSignerStatus) { | ||
ServerSignerStatus["PENDING"] = "pending_seed_creation"; | ||
ServerSignerStatus["ACTIVE"] = "active_seed"; | ||
})(ServerSignerStatus || (exports.ServerSignerStatus = ServerSignerStatus = {})); |
@@ -9,11 +9,9 @@ import { WalletData } from "./types"; | ||
getId(): string; | ||
saveWallet(wallet: Wallet, encrypt?: boolean): Wallet; | ||
listWallets(pageSize?: number, nextPageToken?: string): Promise<Wallet[]>; | ||
loadWallets(): Promise<{ | ||
[key: string]: Wallet; | ||
listWallets(pageSize?: number, nextPageToken?: string): Promise<{ | ||
wallets: Wallet[]; | ||
nextPageToken: string; | ||
}>; | ||
getWallet(wallet_id: string): Promise<Wallet>; | ||
importWallet(data: WalletData): Promise<Wallet>; | ||
private getExistingSeeds; | ||
private storeEncryptionKey; | ||
toString(): string; | ||
} |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.User = void 0; | ||
const fs = __importStar(require("fs")); | ||
const crypto = __importStar(require("crypto")); | ||
const wallet_1 = require("./wallet"); | ||
const coinbase_1 = require("./coinbase"); | ||
const errors_1 = require("./errors"); | ||
class User { | ||
@@ -42,26 +16,2 @@ constructor(user) { | ||
} | ||
saveWallet(wallet, encrypt = false) { | ||
const existingSeedsInStore = this.getExistingSeeds(); | ||
const data = wallet.export(); | ||
let seedToStore = data.seed; | ||
let authTag = ""; | ||
let iv = ""; | ||
if (encrypt) { | ||
const ivBytes = crypto.randomBytes(12); | ||
const sharedSecret = this.storeEncryptionKey(); | ||
const cipher = crypto.createCipheriv("aes-256-gcm", crypto.createHash("sha256").update(sharedSecret).digest(), ivBytes); | ||
const encryptedData = Buffer.concat([cipher.update(data.seed, "utf8"), cipher.final()]); | ||
authTag = cipher.getAuthTag().toString("hex"); | ||
seedToStore = encryptedData.toString("hex"); | ||
iv = ivBytes.toString("hex"); | ||
} | ||
existingSeedsInStore[data.walletId] = { | ||
seed: seedToStore, | ||
encrypted: encrypt, | ||
authTag: authTag, | ||
iv: iv, | ||
}; | ||
fs.writeFileSync(coinbase_1.Coinbase.backupFilePath, JSON.stringify(existingSeedsInStore, null, 2), "utf8"); | ||
return wallet; | ||
} | ||
async listWallets(pageSize = 10, nextPageToken) { | ||
@@ -78,34 +28,13 @@ const addressModelMap = {}; | ||
} | ||
return Promise.all(walletsModels.map(async (wallet) => { | ||
return await wallet_1.Wallet.init(wallet, "", addressModelMap[wallet.id]); | ||
const wallets = await Promise.all(walletsModels.map(async (wallet) => { | ||
const walletId = wallet.id; | ||
const addressModels = addressModelMap[walletId]; | ||
return await wallet_1.Wallet.init(wallet, "", addressModels); | ||
})); | ||
return { wallets: wallets, nextPageToken: walletList.data.next_page }; | ||
} | ||
async loadWallets() { | ||
const existingSeedsInStore = this.getExistingSeeds(); | ||
if (Object.keys(existingSeedsInStore).length === 0) { | ||
throw new errors_1.ArgumentError("Backup file not found"); | ||
} | ||
const wallets = {}; | ||
for (const [walletId, seedData] of Object.entries(existingSeedsInStore)) { | ||
let seed = seedData.seed; | ||
if (!seed) { | ||
throw new Error("Malformed backup data"); | ||
} | ||
if (seedData.encrypted) { | ||
const sharedSecret = this.storeEncryptionKey(); | ||
if (!seedData.iv || !seedData.authTag) { | ||
throw new Error("Malformed encrypted seed data"); | ||
} | ||
const decipher = crypto.createDecipheriv("aes-256-gcm", crypto.createHash("sha256").update(sharedSecret).digest(), Buffer.from(seedData.iv, "hex")); | ||
decipher.setAuthTag(Buffer.from(seedData.authTag, "hex")); | ||
const decryptedData = Buffer.concat([ | ||
decipher.update(Buffer.from(seed, "hex")), | ||
decipher.final(), | ||
]); | ||
seed = decryptedData.toString("utf8"); | ||
} | ||
const data = { walletId, seed }; | ||
wallets[walletId] = await this.importWallet(data); | ||
} | ||
return wallets; | ||
async getWallet(wallet_id) { | ||
const walletModel = await coinbase_1.Coinbase.apiClients.wallet.getWallet(wallet_id); | ||
const addressList = await coinbase_1.Coinbase.apiClients.address.listAddresses(wallet_id); | ||
return wallet_1.Wallet.init(walletModel.data, "", addressList.data.data); | ||
} | ||
@@ -117,34 +46,2 @@ async importWallet(data) { | ||
} | ||
getExistingSeeds() { | ||
try { | ||
const data = fs.readFileSync(coinbase_1.Coinbase.backupFilePath, "utf8"); | ||
if (!data) { | ||
return {}; | ||
} | ||
const seedData = JSON.parse(data); | ||
if (!Object.entries(seedData).every(([key, value]) => typeof key === "string" && | ||
typeof value.authTag === "string" && | ||
typeof value.encrypted === "boolean" && | ||
typeof value.iv === "string" && | ||
typeof value.seed === "string")) { | ||
throw new errors_1.ArgumentError("Malformed backup data"); | ||
} | ||
return seedData; | ||
} | ||
catch (error) { | ||
if (error.code === "ENOENT") { | ||
return {}; | ||
} | ||
throw new errors_1.ArgumentError("Malformed backup data"); | ||
} | ||
} | ||
storeEncryptionKey() { | ||
const privateKey = crypto.createPrivateKey(coinbase_1.Coinbase.apiKeyPrivateKey); | ||
const publicKey = crypto.createPublicKey(coinbase_1.Coinbase.apiKeyPrivateKey); | ||
const sharedSecret = crypto.diffieHellman({ | ||
privateKey, | ||
publicKey, | ||
}); | ||
return sharedSecret; | ||
} | ||
toString() { | ||
@@ -151,0 +48,0 @@ return `User{ userId: ${this.model.id} }`; |
import { Address as AddressModel, Wallet as WalletModel } from "../client"; | ||
import { Address } from "./address"; | ||
import { Transfer } from "./transfer"; | ||
import { Amount, Destination, WalletData } from "./types"; | ||
import { Amount, Destination, WalletData, ServerSignerStatus } from "./types"; | ||
import { FaucetTransaction } from "./faucet_transaction"; | ||
@@ -18,4 +18,5 @@ import { BalanceMap } from "./balance_map"; | ||
private constructor(); | ||
static create(): Promise<Wallet>; | ||
static init(model: WalletModel, seed: string | undefined, addressModels?: AddressModel[]): Promise<Wallet>; | ||
static create(intervalSeconds?: number, timeoutSeconds?: number): Promise<Wallet>; | ||
private waitForSigner; | ||
static init(model: WalletModel, seed?: string | undefined, addressModels?: AddressModel[]): Promise<Wallet>; | ||
export(): WalletData; | ||
@@ -30,3 +31,3 @@ private deriveKey; | ||
private cacheAddress; | ||
setSeed(seed: string): Promise<void>; | ||
setSeed(seed: string): void; | ||
getAddress(addressId: string): Address | undefined; | ||
@@ -37,3 +38,6 @@ listAddresses(): Address[]; | ||
getNetworkId(): string; | ||
getServerSignerStatus(): ServerSignerStatus | undefined; | ||
getId(): string | undefined; | ||
saveSeed(filePath: string, encrypt?: boolean): string; | ||
loadSeed(filePath: string): string; | ||
getDefaultAddress(): Address | undefined; | ||
@@ -46,2 +50,4 @@ canSign(): boolean; | ||
private static getSeedAndMasterKey; | ||
private getExistingSeeds; | ||
private getEncryptionKey; | ||
} |
@@ -33,2 +33,3 @@ "use strict"; | ||
const crypto = __importStar(require("crypto")); | ||
const fs = __importStar(require("fs")); | ||
const ethers_1 = require("ethers"); | ||
@@ -39,2 +40,3 @@ const secp256k1 = __importStar(require("secp256k1")); | ||
const errors_1 = require("./errors"); | ||
const types_1 = require("./types"); | ||
const utils_1 = require("./utils"); | ||
@@ -55,9 +57,13 @@ const balance_map_1 = require("./balance_map"); | ||
} | ||
static async create() { | ||
const walletData = await coinbase_1.Coinbase.apiClients.wallet.createWallet({ | ||
static async create(intervalSeconds = 0.2, timeoutSeconds = 20) { | ||
const result = await coinbase_1.Coinbase.apiClients.wallet.createWallet({ | ||
wallet: { | ||
network_id: coinbase_1.Coinbase.networkList.BaseSepolia, | ||
use_server_signer: coinbase_1.Coinbase.useServerSigner, | ||
}, | ||
}); | ||
const wallet = await Wallet.init(walletData.data, undefined, []); | ||
const wallet = await Wallet.init(result.data, undefined, []); | ||
if (coinbase_1.Coinbase.useServerSigner) { | ||
await wallet.waitForSigner(wallet.getId(), intervalSeconds, timeoutSeconds); | ||
} | ||
await wallet.createAddress(); | ||
@@ -67,4 +73,18 @@ await wallet.reload(); | ||
} | ||
async waitForSigner(walletId, intervalSeconds = 0.2, timeoutSeconds = 20) { | ||
const startTime = Date.now(); | ||
while (Date.now() - startTime < timeoutSeconds * 1000) { | ||
const response = await coinbase_1.Coinbase.apiClients.wallet.getWallet(walletId); | ||
if (response?.data.server_signer_status === types_1.ServerSignerStatus.ACTIVE) { | ||
return; | ||
} | ||
await (0, utils_1.delay)(intervalSeconds); | ||
} | ||
throw new Error("Wallet creation timed out. Check status of your Server-Signer"); | ||
} | ||
static async init(model, seed, addressModels = []) { | ||
this.validateSeedAndAddressModels(seed, addressModels); | ||
if (coinbase_1.Coinbase.useServerSigner) { | ||
return new Wallet(model, undefined, undefined, addressModels); | ||
} | ||
const seedAndMaster = this.getSeedAndMasterKey(seed); | ||
@@ -89,10 +109,13 @@ const wallet = new Wallet(model, seedAndMaster.master, seedAndMaster.seed, addressModels); | ||
async createAddress() { | ||
const hdKey = this.deriveKey(); | ||
const attestation = this.createAttestation(hdKey); | ||
const publicKey = (0, utils_1.convertStringToHex)(hdKey.publicKey); | ||
const key = new ethers_1.ethers.Wallet((0, utils_1.convertStringToHex)(hdKey.privateKey)); | ||
const payload = { | ||
public_key: publicKey, | ||
attestation: attestation, | ||
}; | ||
let payload, key; | ||
if (!coinbase_1.Coinbase.useServerSigner) { | ||
const hdKey = this.deriveKey(); | ||
const attestation = this.createAttestation(hdKey); | ||
const publicKey = (0, utils_1.convertStringToHex)(hdKey.publicKey); | ||
key = new ethers_1.ethers.Wallet((0, utils_1.convertStringToHex)(hdKey.privateKey)); | ||
payload = { | ||
public_key: publicKey, | ||
attestation: attestation, | ||
}; | ||
} | ||
const response = await coinbase_1.Coinbase.apiClients.address.createAddress(this.model.id, payload); | ||
@@ -153,6 +176,18 @@ this.cacheAddress(response.data, key); | ||
} | ||
async setSeed(seed) { | ||
if (this.master === undefined) { | ||
setSeed(seed) { | ||
if (this.master === undefined && (this.seed === undefined || this.seed === "")) { | ||
this.master = bip32_1.HDKey.fromMasterSeed(Buffer.from(seed, "hex")); | ||
this.addresses = []; | ||
this.addressModels.map((addressModel) => { | ||
const derivedKey = this.deriveKey(); | ||
const etherKey = new ethers_1.ethers.Wallet((0, utils_1.convertStringToHex)(derivedKey.privateKey)); | ||
if (etherKey.address != addressModel.address_id) { | ||
throw new errors_1.InternalError(`Seed does not match wallet; cannot find address ${etherKey.address}`); | ||
} | ||
this.cacheAddress(addressModel, etherKey); | ||
}); | ||
} | ||
else { | ||
throw new errors_1.InternalError("Cannot set seed on Wallet with existing seed"); | ||
} | ||
} | ||
@@ -182,5 +217,71 @@ getAddress(addressId) { | ||
} | ||
getServerSignerStatus() { | ||
switch (this.model.server_signer_status) { | ||
case types_1.ServerSignerStatus.PENDING: | ||
return types_1.ServerSignerStatus.PENDING; | ||
case types_1.ServerSignerStatus.ACTIVE: | ||
return types_1.ServerSignerStatus.ACTIVE; | ||
default: | ||
return undefined; | ||
} | ||
} | ||
getId() { | ||
return this.model.id; | ||
} | ||
saveSeed(filePath, encrypt = false) { | ||
if (!this.master) { | ||
throw new errors_1.InternalError("Cannot save Wallet without loaded seed"); | ||
} | ||
const existingSeedsInStore = this.getExistingSeeds(filePath); | ||
const data = this.export(); | ||
let seedToStore = data.seed; | ||
let authTag = ""; | ||
let iv = ""; | ||
if (encrypt) { | ||
const ivBytes = crypto.randomBytes(12); | ||
const sharedSecret = this.getEncryptionKey(); | ||
const cipher = crypto.createCipheriv("aes-256-gcm", crypto.createHash("sha256").update(sharedSecret).digest(), ivBytes); | ||
const encryptedData = Buffer.concat([cipher.update(data.seed, "utf8"), cipher.final()]); | ||
authTag = cipher.getAuthTag().toString("hex"); | ||
seedToStore = encryptedData.toString("hex"); | ||
iv = ivBytes.toString("hex"); | ||
} | ||
existingSeedsInStore[data.walletId] = { | ||
seed: seedToStore, | ||
encrypted: encrypt, | ||
authTag: authTag, | ||
iv: iv, | ||
}; | ||
fs.writeFileSync(filePath, JSON.stringify(existingSeedsInStore, null, 2), "utf8"); | ||
return `Successfully saved seed for ${data.walletId} to ${filePath}.`; | ||
} | ||
loadSeed(filePath) { | ||
const existingSeedsInStore = this.getExistingSeeds(filePath); | ||
if (Object.keys(existingSeedsInStore).length === 0) { | ||
throw new errors_1.ArgumentError(`File ${filePath} does not contain any seed data`); | ||
} | ||
if (existingSeedsInStore[this.getId()] === undefined) { | ||
throw new errors_1.ArgumentError(`File ${filePath} does not contain seed data for wallet ${this.getId()}`); | ||
} | ||
const seedData = existingSeedsInStore[this.getId()]; | ||
let seed = seedData.seed; | ||
if (!seed) { | ||
throw new errors_1.ArgumentError("Seed data is malformed"); | ||
} | ||
if (seedData.encrypted) { | ||
const sharedSecret = this.getEncryptionKey(); | ||
if (!seedData.iv || !seedData.authTag) { | ||
throw new errors_1.ArgumentError("Encrypted seed data is malformed"); | ||
} | ||
const decipher = crypto.createDecipheriv("aes-256-gcm", crypto.createHash("sha256").update(sharedSecret).digest(), Buffer.from(seedData.iv, "hex")); | ||
decipher.setAuthTag(Buffer.from(seedData.authTag, "hex")); | ||
const decryptedData = Buffer.concat([ | ||
decipher.update(Buffer.from(seed, "hex")), | ||
decipher.final(), | ||
]); | ||
seed = decryptedData.toString("utf8"); | ||
} | ||
this.setSeed(seed); | ||
return `Successfully loaded seed for wallet ${this.getId()} from ${filePath}.`; | ||
} | ||
getDefaultAddress() { | ||
@@ -243,4 +344,36 @@ return this.addresses.find(address => address.getId() === this.model.default_address?.address_id); | ||
} | ||
getExistingSeeds(filePath) { | ||
try { | ||
const data = fs.readFileSync(filePath, "utf8"); | ||
if (!data) { | ||
return {}; | ||
} | ||
const seedData = JSON.parse(data); | ||
if (!Object.entries(seedData).every(([key, value]) => typeof key === "string" && | ||
typeof value.authTag === "string" && | ||
typeof value.encrypted === "boolean" && | ||
typeof value.iv === "string" && | ||
typeof value.seed === "string")) { | ||
throw new errors_1.ArgumentError("Malformed backup data"); | ||
} | ||
return seedData; | ||
} | ||
catch (error) { | ||
if (error.code === "ENOENT") { | ||
return {}; | ||
} | ||
throw new errors_1.ArgumentError("Malformed backup data"); | ||
} | ||
} | ||
getEncryptionKey() { | ||
const privateKey = crypto.createPrivateKey(coinbase_1.Coinbase.apiKeyPrivateKey); | ||
const publicKey = crypto.createPublicKey(coinbase_1.Coinbase.apiKeyPrivateKey); | ||
const encryptionKey = crypto.diffieHellman({ | ||
privateKey, | ||
publicKey, | ||
}); | ||
return encryptionKey; | ||
} | ||
} | ||
exports.Wallet = Wallet; | ||
Wallet.MAX_ADDRESSES = 20; |
@@ -7,3 +7,3 @@ { | ||
"repository": "https://github.com/coinbase/coinbase-sdk-nodejs", | ||
"version": "0.0.4", | ||
"version": "0.0.6", | ||
"main": "dist/index.js", | ||
@@ -17,3 +17,5 @@ "types": "dist/index.d.ts", | ||
"check": "tsc --noEmit", | ||
"test": "npx jest --no-cache", | ||
"test": "npx jest --no-cache --testMatch=**/*_test.ts", | ||
"test:dry-run": "npm install && npm ci && npm publish --dry-run", | ||
"test:e2e": "npx jest --no-cache --testMatch=**/e2e.ts", | ||
"clean": "rm -rf dist/*", | ||
@@ -33,2 +35,3 @@ "build": "tsc", | ||
"decimal.js": "^10.4.3", | ||
"dotenv": "^16.4.5", | ||
"ethers": "^6.12.1", | ||
@@ -75,5 +78,4 @@ "node-jose": "^2.2.0", | ||
"verbose": true, | ||
"testRegex": ".test.ts$", | ||
"maxWorkers": 1 | ||
} | ||
} |
@@ -18,7 +18,32 @@ # Coinbase Node.js SDK | ||
## Requirements | ||
The Coinbase server-side SDK requires Node.js version 18 or higher and npm version 9.7.2 or higher. To view your currently installed versions of Node.js, run the following from the command-line: | ||
```bash | ||
node -v | ||
npm -v | ||
``` | ||
We recommend installing and managing Node.js and npm versions with `nvm`. See [Installing and Updating](https://github.com/nvm-sh/nvm?tab=readme-ov-file#installing-and-updating) in the `nvm` README for instructions on how to install `nvm`. | ||
Once `nvm` has been installed, you can install and use the latest versions of Node.js and npm by running the following commands: | ||
```bash | ||
nvm install node # "node" is an alias for the latest version | ||
nvm use node | ||
``` | ||
## Installation | ||
### In Your Node.js Project | ||
Optional: Initialize the npm | ||
This command initializes a new npm project with default settings and configures it to use ES modules by setting the type field to "module" in the package.json file. | ||
```bash | ||
npm init -y; npm pkg set type="module" | ||
``` | ||
#### You can import the SDK as follows | ||
```bash | ||
npm install @coinbase/coinbase-sdk | ||
@@ -33,17 +58,19 @@ ``` | ||
### In the ts-node REPL | ||
## Usage | ||
After running `npx ts-node` to start the REPL, you can import the SDK as follows: | ||
### Initialization | ||
```typescript | ||
import { Coinbase } from "@coinbase/coinbase-sdk"; | ||
``` | ||
#### You can import the SDK as follows: | ||
### Requirements | ||
CommonJs: | ||
- Node.js 18 or higher | ||
```javascript | ||
const { Coinbase } = require("@coinbase/coinbase-sdk"); | ||
``` | ||
## Usage | ||
ES modules: | ||
### Initialization | ||
```typescript | ||
import { Coinbase } from "@coinbase/coinbase-sdk"; | ||
``` | ||
@@ -55,11 +82,16 @@ 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: | ||
const apiKeyPrivateKey = "Copy your API Key's private key here."; | ||
const privateKey = "Copy your API Key's private key here."; | ||
const coinbase = new Coinbase(apiKeyName, apiKeyPrivateKey); | ||
const coinbase = new Coinbase({ apiKeyName: apiKeyName, privateKey: privateKey }); | ||
``` | ||
If you are using a CDP Server-Signer to manage your private keys, enable it with the constuctor option: | ||
```typescript | ||
const coinbase = new Coinbase({ apiKeyName: apiKeyName, privateKey: apiKeyPrivateKey, useServerSigner: true }) | ||
``` | ||
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({ filePath: "path/to/your/api-key.json" }); | ||
``` | ||
@@ -69,4 +101,19 @@ | ||
CommonJs: | ||
```javascript | ||
const { Coinbase } = require("@coinbase/coinbase-sdk"); | ||
const coinbase = Coinbase.configureFromJson("path/to/your/api-key.json"); | ||
coinbase.getDefaultUser().then(user => { | ||
console.log(user); | ||
}); | ||
``` | ||
Or using ES modules and async/await: | ||
```typescript | ||
import { Coinbase } from "@coinbase/coinbase-sdk"; | ||
const coinbase = Coinbase.configureFromJson("path/to/your/api-key.json"); | ||
const user = await coinbase.getDefaultUser(); | ||
console.log(user); | ||
``` | ||
@@ -125,12 +172,12 @@ | ||
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. | ||
For convenience during testing, we provide a `saveSeed` method that stores the wallet's seed 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); | ||
wallet.saveSeed(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`. | ||
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 `saveSeed` and `loadSeed`. | ||
```typescript | ||
user.saveWallet(wallet, true); | ||
wallet.saveSeed(wallet, true); | ||
``` | ||
@@ -145,8 +192,8 @@ | ||
To import Wallets that were persisted to your local file system using `saveWallet`, use the below code. | ||
To import Wallets that were persisted to your local file system using `saveSeed`, 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()]; | ||
const w = await user.getWallet(w.getId()); | ||
w.loadSeed(filePath); | ||
``` | ||
@@ -201,9 +248,6 @@ | ||
``` | ||
To run e2e tests, run: | ||
### REPL | ||
The repository is equipped with a REPL to allow developers to play with the SDK. To start it, run: | ||
```bash | ||
npx ts-node | ||
npm run test:dry-run && NAME="placeholder" PRIVATE_KEY="placeholder" WALLET_DATA="placeholder" && npm run test:e2e | ||
``` | ||
@@ -210,0 +254,0 @@ |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 3 instances in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
287851
69
5379
255
9
12
+ Addeddotenv@^16.4.5
+ Addeddotenv@16.4.7(transitive)