diff --git a/src/coinbase/address.ts b/src/coinbase/address.ts index 15ffc4ea..5065b6be 100644 --- a/src/coinbase/address.ts +++ b/src/coinbase/address.ts @@ -19,10 +19,48 @@ import { StakingReward } from "./staking_reward"; import { StakingBalance } from "./staking_balance"; import { Transaction } from "./transaction"; +export interface IAddress { + getNetworkId(): string; + getId(): string; + listBalances(): Promise; + getBalance(assetId: string): Promise; + listHistoricalBalances( + options: ListHistoricalBalancesOptions, + ): Promise; + listTransactions(options: ListTransactionsOptions): Promise; + stakingRewards( + assetId: string, + startTime: string, + endTime: string, + format: StakingRewardFormat, + ): Promise; + historicalStakingBalances( + assetId: string, + startTime: string, + endTime: string, + ): Promise; + stakeableBalance( + assetId: string, + mode: StakeOptionsMode, + options: { [key: string]: string }, + ): Promise; + unstakeableBalance( + assetId: string, + mode: StakeOptionsMode, + options: { [key: string]: string }, + ): Promise; + claimableBalance( + assetId: string, + mode: StakeOptionsMode, + options: { [key: string]: string }, + ): Promise; + faucet(assetId?: string): Promise; +} + /** * A representation of a blockchain address, which is a user-controlled account on a network. */ -export class Address { +export class Address implements IAddress { private static MAX_HISTORICAL_BALANCE = 1000; protected networkId: string; diff --git a/src/coinbase/address/external_address.ts b/src/coinbase/address/external_address.ts index 2b83d046..87f4e980 100644 --- a/src/coinbase/address/external_address.ts +++ b/src/coinbase/address/external_address.ts @@ -1,16 +1,37 @@ -import { Address } from "../address"; +import { Address, IAddress } from "../address"; import { Amount, StakeOptionsMode } from "../types"; import { Coinbase } from "../coinbase"; import Decimal from "decimal.js"; import { Asset } from "../asset"; -import { StakingOperation } from "../staking_operation"; +import { IStakingOperation, StakingOperation } from "../staking_operation"; + +export interface IExternalAddress extends IAddress { + buildStakeOperation( + amount: Amount, + assetId: string, + mode: StakeOptionsMode, + options: { [key: string]: string }, + ): Promise; + buildUnstakeOperation( + amount: Amount, + assetId: string, + mode: StakeOptionsMode, + options: { [key: string]: string }, + ): Promise; + buildClaimStakeOperation( + amount: Amount, + assetId: string, + mode: StakeOptionsMode, + options: { [key: string]: string }, + ): Promise; +} /** * A representation of a blockchain Address, which is a user-controlled account on a Network. Addresses are used to * send and receive Assets. An ExternalAddress is an Address that is not controlled by the developer, but is instead * controlled by the user. */ -export class ExternalAddress extends Address { +export class ExternalAddress extends Address implements IExternalAddress { /** * Builds a stake operation for the supplied asset. The stake operation * may take a few minutes to complete in the case when infrastructure is spun up. diff --git a/src/coinbase/address/wallet_address.ts b/src/coinbase/address/wallet_address.ts index 022f1c65..d9e56989 100644 --- a/src/coinbase/address/wallet_address.ts +++ b/src/coinbase/address/wallet_address.ts @@ -1,13 +1,13 @@ import { Decimal } from "decimal.js"; import { ethers } from "ethers"; import { Address as AddressModel, SmartContractType } from "../../client"; -import { Address } from "../address"; +import { Address, IAddress } from "../address"; import { Asset } from "../asset"; import { Coinbase } from "../coinbase"; import { ArgumentError } from "../errors"; -import { Trade } from "../trade"; -import { Transfer } from "../transfer"; -import { ContractInvocation } from "../contract_invocation"; +import { ITrade, Trade } from "../trade"; +import { ITransfer, Transfer } from "../transfer"; +import { ContractInvocation, IContractInvocation } from "../contract_invocation"; import { Amount, CreateTransferOptions, @@ -21,14 +21,56 @@ import { } from "../types"; import { delay } from "../utils"; import { Wallet as WalletClass } from "../wallet"; -import { StakingOperation } from "../staking_operation"; -import { PayloadSignature } from "../payload_signature"; -import { SmartContract } from "../smart_contract"; +import { IStakingOperation, StakingOperation } from "../staking_operation"; +import { IPayloadSignature, PayloadSignature } from "../payload_signature"; +import { ISmartContract, SmartContract } from "../smart_contract"; + +export interface IWalletAddress extends IAddress { + getWalletId(): string; + setKey(key: ethers.Wallet): void; + export(): string; + canSign(): boolean; + listTrades(): Promise; + listTransfers(): Promise; + createTransfer(options: CreateTransferOptions): Promise; + createTrade(options: CreateTradeOptions): Promise; + invokeContract(options: CreateContractInvocationOptions): Promise; + deployToken(options: CreateERC20Options): Promise; + deployNFT(options: CreateERC721Options): Promise; + deployMultiToken(options: CreateERC1155Options): Promise; + createStake( + amount: Amount, + assetId: string, + mode: StakeOptionsMode, + options: { [key: string]: string }, + timeoutSeconds: number, + intervalSeconds: number, + ): Promise; + createUnstake( + amount: Amount, + assetId: string, + mode: StakeOptionsMode, + options: { [key: string]: string }, + timeoutSeconds: number, + intervalSeconds: number, + ): Promise; + createClaimStake( + amount: Amount, + assetId: string, + mode: StakeOptionsMode, + options: { [key: string]: string }, + timeoutSeconds: number, + intervalSeconds: number, + ): Promise; + createPayloadSignature(payload: string): Promise; + getPayloadSignature(signatureId: string): Promise; + listPayloadSignatures(): Promise; +} /** * A representation of a blockchain address, which is a wallet-controlled account on a network. */ -export class WalletAddress extends Address { +export class WalletAddress extends Address implements IWalletAddress { private model: AddressModel; private key?: ethers.Wallet; @@ -139,8 +181,8 @@ export class WalletAddress extends Address { * * @returns The list of transfers. */ - public async listTransfers(): Promise { - const transfers: Transfer[] = []; + public async listTransfers(): Promise { + const transfers: ITransfer[] = []; const queue: string[] = [""]; while (queue.length > 0) { @@ -186,7 +228,7 @@ export class WalletAddress extends Address { assetId, destination, gasless = false, - }: CreateTransferOptions): Promise { + }: CreateTransferOptions): Promise { if (!Coinbase.useServerSigner && !this.key) { throw new Error("Cannot transfer from address without private key loaded"); } @@ -727,13 +769,17 @@ export class WalletAddress extends Address { if (typeof destination !== "string" && destination.getNetworkId() !== this.getNetworkId()) { throw new ArgumentError("Transfer must be on the same Network"); } - if (destination instanceof WalletClass) { - return [(await destination.getDefaultAddress()).getId(), destination.getNetworkId()]; + if (typeof destination === "string") { + return [destination, this.getNetworkId()]; + } + if ("getDefaultAddress" in destination) { + const defaultAddress = await destination.getDefaultAddress(); + return [defaultAddress.getId(), destination.getNetworkId()]; } - if (destination instanceof Address) { + if ("getId" in destination) { return [destination.getId(), destination.getNetworkId()]; } - return [destination, this.getNetworkId()]; + throw new ArgumentError("Invalid destination type"); } /** diff --git a/src/coinbase/asset.ts b/src/coinbase/asset.ts index 18b41abb..5aea1959 100644 --- a/src/coinbase/asset.ts +++ b/src/coinbase/asset.ts @@ -4,8 +4,19 @@ import { Coinbase } from "./coinbase"; import { GWEI_DECIMALS } from "./constants"; import { ArgumentError } from "./errors"; +export interface IAsset { + networkId: string; + assetId: string; + contractAddress: string; + decimals: number; + primaryDenomination(): string; + toAtomicAmount(wholeAmount: Decimal): bigint; + fromAtomicAmount(atomicAmount: Decimal): Decimal; + getAssetId(): string; +} + /** A representation of an Asset. */ -export class Asset { +export class Asset implements IAsset { public readonly networkId: string; public readonly assetId: string; public readonly contractAddress: string; @@ -128,20 +139,20 @@ export class Asset { } /** - * Returns a string representation of the Asset. + * Returns the Asset ID. * - * @returns a string representation of the Asset + * @returns The Asset ID. */ - toString(): string { - return `Asset{ networkId: ${this.networkId}, assetId: ${this.assetId}, contractAddress: ${this.contractAddress}, decimals: ${this.decimals} }`; + getAssetId(): string { + return this.assetId; } /** - * Returns the Asset ID. + * Returns a string representation of the Asset. * - * @returns The Asset ID. + * @returns a string representation of the Asset */ - getAssetId(): string { - return this.assetId; + toString(): string { + return `Asset{ networkId: ${this.networkId}, assetId: ${this.assetId}, contractAddress: ${this.contractAddress}, decimals: ${this.decimals} }`; } } diff --git a/src/coinbase/authenticator.ts b/src/coinbase/authenticator.ts index 9a84ad40..2a5bb951 100644 --- a/src/coinbase/authenticator.ts +++ b/src/coinbase/authenticator.ts @@ -6,10 +6,18 @@ import { version } from "../../package.json"; const pemHeader = "-----BEGIN EC PRIVATE KEY-----"; const pemFooter = "-----END EC PRIVATE KEY-----"; +export interface IAuthenticator { + authenticateRequest( + config: InternalAxiosRequestConfig, + debugging?: boolean, + ): Promise; + buildJWT(url: string, method?: string): Promise; +} + /** * A class that builds JWTs for authenticating with the Coinbase Platform APIs. */ -export class CoinbaseAuthenticator { +export class CoinbaseAuthenticator implements IAuthenticator { private apiKey: string; private privateKey: string; diff --git a/src/coinbase/balance.ts b/src/coinbase/balance.ts index 3aac5e03..b984c442 100644 --- a/src/coinbase/balance.ts +++ b/src/coinbase/balance.ts @@ -2,8 +2,14 @@ import Decimal from "decimal.js"; import { Balance as BalanceModel } from "../client"; import { Asset } from "./asset"; +export interface IBalance { + amount: Decimal; + assetId: string; + asset?: Asset; +} + /** A representation of a balance. */ -export class Balance { +export class Balance implements IBalance { public readonly amount: Decimal; public readonly assetId: string; public readonly asset?: Asset; diff --git a/src/coinbase/balance_map.ts b/src/coinbase/balance_map.ts index 62931352..afa0859b 100644 --- a/src/coinbase/balance_map.ts +++ b/src/coinbase/balance_map.ts @@ -1,4 +1,4 @@ -import { Balance } from "./balance"; +import { Balance, IBalance } from "./balance"; import { Balance as BalanceModel } from "../client"; import { Decimal } from "decimal.js"; @@ -24,9 +24,9 @@ export class BalanceMap extends Map { /** * Adds a balance to the map. * - * @param {Balance} balance - The balance to add to the map. + * @param {IBalance} balance - The balance to add to the map. */ - public add(balance: Balance): void { + public add(balance: IBalance): void { if (!(balance instanceof Balance)) { throw new Error("balance must be a Balance"); } diff --git a/src/coinbase/contract_event.ts b/src/coinbase/contract_event.ts index 3f1a19d0..af133a97 100644 --- a/src/coinbase/contract_event.ts +++ b/src/coinbase/contract_event.ts @@ -1,8 +1,25 @@ import { ContractEvent as ContractEventModel } from "../client"; + +export interface IContractEvent { + networkId(): string; + protocolName(): string; + contractName(): string; + eventName(): string; + sig(): string; + fourBytes(): string; + contractAddress(): string; + blockTime(): Date; + blockHeight(): number; + txHash(): string; + txIndex(): number; + eventIndex(): number; + data(): string; +} + /** * A representation of a single contract event. */ -export class ContractEvent { +export class ContractEvent implements IContractEvent { private model: ContractEventModel; /** diff --git a/src/coinbase/contract_invocation.ts b/src/coinbase/contract_invocation.ts index 1f10b264..d7d28df7 100644 --- a/src/coinbase/contract_invocation.ts +++ b/src/coinbase/contract_invocation.ts @@ -1,17 +1,44 @@ import { Decimal } from "decimal.js"; import { TransactionStatus } from "./types"; -import { Transaction } from "./transaction"; +import { ITransaction, Transaction } from "./transaction"; import { Coinbase } from "./coinbase"; import { ContractInvocation as ContractInvocationModel } from "../client/api"; import { ethers } from "ethers"; import { delay } from "./utils"; import { TimeoutError } from "./errors"; +export interface IContractInvocation { + getId(): string; + getNetworkId(): string; + getWalletId(): string; + getFromAddressId(): string; + getContractAddressId(): string; + getMethod(): string; + getArgs(): object; + getAbi(): object | undefined; + getAmount(): Decimal; + getTransactionHash(): string | undefined; + getRawTransaction(): ethers.Transaction; + sign(key: ethers.Wallet): Promise; + getStatus(): TransactionStatus | undefined; + getTransaction(): ITransaction; + getTransactionLink(): string; + broadcast(): Promise; + wait({ + intervalSeconds, + timeoutSeconds, + }: { + intervalSeconds: number; + timeoutSeconds: number; + }): Promise; + reload(): Promise; +} + /** * A representation of a ContractInvocation, which calls a smart contract method * onchain. The fee is assumed to be paid in the native Asset of the Network. */ -export class ContractInvocation { +export class ContractInvocation implements IContractInvocation { private model: ContractInvocationModel; /** diff --git a/src/coinbase/faucet_transaction.ts b/src/coinbase/faucet_transaction.ts index b18b0b0b..1446a2a2 100644 --- a/src/coinbase/faucet_transaction.ts +++ b/src/coinbase/faucet_transaction.ts @@ -1,9 +1,14 @@ import { FaucetTransaction as FaucetTransactionModel } from "../client"; +export interface IFaucetTransaction { + getTransactionHash(): string; + getTransactionLink(): string; +} + /** * Represents a transaction from a faucet. */ -export class FaucetTransaction { +export class FaucetTransaction implements IFaucetTransaction { private model: FaucetTransactionModel; /** diff --git a/src/coinbase/payload_signature.ts b/src/coinbase/payload_signature.ts index 9556d045..94088ab2 100644 --- a/src/coinbase/payload_signature.ts +++ b/src/coinbase/payload_signature.ts @@ -4,10 +4,21 @@ import { delay } from "./utils"; import { TimeoutError } from "./errors"; import { Coinbase } from "./coinbase"; +export interface IPayloadSignature { + getId(): string; + getWalletId(): string; + getAddressId(): string; + getUnsignedPayload(): string; + getSignature(): string | undefined; + getStatus(): PayloadSignatureStatus | undefined; + isTerminalState(): boolean; + wait(): Promise; +} + /** * A representation of a Payload Signature. */ -export class PayloadSignature { +export class PayloadSignature implements IPayloadSignature { private model: PayloadSignatureModel; /** diff --git a/src/coinbase/server_signer.ts b/src/coinbase/server_signer.ts index d0e37824..cd0eb39a 100644 --- a/src/coinbase/server_signer.ts +++ b/src/coinbase/server_signer.ts @@ -1,10 +1,15 @@ import { Coinbase } from "./coinbase"; import { ServerSigner as ServerSignerModel } from "../client/api"; +export interface IServerSigner { + getId(): string; + getWallets(): string[] | undefined; +} + /** * A representation of a Server-Signer. Server-Signers are assigned to sign transactions for a Wallet. */ -export class ServerSigner { +export class ServerSigner implements IServerSigner { private model: ServerSignerModel; /** diff --git a/src/coinbase/smart_contract.ts b/src/coinbase/smart_contract.ts index fe124d9d..ecc44ef1 100644 --- a/src/coinbase/smart_contract.ts +++ b/src/coinbase/smart_contract.ts @@ -7,7 +7,7 @@ import { TokenContractOptions as TokenContractOptionsModel, NFTContractOptions as NFTContractOptionsModel, } from "../client/api"; -import { Transaction } from "./transaction"; +import { ITransaction, Transaction } from "./transaction"; import { SmartContractOptions, SmartContractType, @@ -21,10 +21,23 @@ import { delay } from "./utils"; import { TimeoutError } from "./errors"; import { ContractEvent } from "./contract_event"; +export interface ISmartContract { + getId(): string; + getNetworkId(): string; + getWalletId(): string; + getContractAddress(): string; + getDeployerAddress(): string; + getType(): SmartContractType; + getOptions(): SmartContractOptions; + getAbi(): object; + getTransaction(): ITransaction; + sign(key: ethers.Wallet): Promise; + broadcast(): Promise; +} /** * A representation of a SmartContract on the blockchain. */ -export class SmartContract { +export class SmartContract implements ISmartContract { private model: SmartContractModel; /** diff --git a/src/coinbase/sponsored_send.ts b/src/coinbase/sponsored_send.ts index 8b1c2b9a..e6deea57 100644 --- a/src/coinbase/sponsored_send.ts +++ b/src/coinbase/sponsored_send.ts @@ -2,10 +2,24 @@ import { ethers } from "ethers"; import { SponsoredSend as SponsoredSendModel } from "../client/api"; import { SponsoredSendStatus } from "./types"; +/** + * Interface representing a Sponsored Send. + */ +export interface ISponsoredSend { + getTypedDataHash(): string; + getSignature(): string | undefined; + sign(key: ethers.Wallet): Promise; + isSigned(): boolean; + getStatus(): SponsoredSendStatus | undefined; + isTerminalState(): boolean; + getTransactionHash(): string | undefined; + getTransactionLink(): string | undefined; +} + /** * A representation of an onchain Sponsored Send. */ -export class SponsoredSend { +export class SponsoredSend implements ISponsoredSend { private model: SponsoredSendModel; /** diff --git a/src/coinbase/staking_balance.ts b/src/coinbase/staking_balance.ts index e56367be..434044bb 100644 --- a/src/coinbase/staking_balance.ts +++ b/src/coinbase/staking_balance.ts @@ -1,11 +1,19 @@ import { StakingBalance as StakingBalanceModel } from "../client"; -import { Balance } from "./balance"; +import { Balance, IBalance } from "./balance"; import { Coinbase } from "./coinbase"; +export interface IStakingBalance { + bondedStake(): IBalance; + unbondedBalance(): IBalance; + participantType(): string; + date(): Date; + address(): string; +} + /** * A representation of the staking balance for a given asset on a specific date. */ -export class StakingBalance { +export class StakingBalance implements IStakingBalance { private model: StakingBalanceModel; /** @@ -69,7 +77,7 @@ export class StakingBalance { * * @returns The Balance. */ - public bondedStake(): Balance { + public bondedStake(): IBalance { return Balance.fromModel(this.model.bonded_stake); } @@ -78,7 +86,7 @@ export class StakingBalance { * * @returns The Balance. */ - public unbondedBalance(): Balance { + public unbondedBalance(): IBalance { return Balance.fromModel(this.model.unbonded_balance); } diff --git a/src/coinbase/staking_operation.ts b/src/coinbase/staking_operation.ts index 0954a58f..4e289e65 100644 --- a/src/coinbase/staking_operation.ts +++ b/src/coinbase/staking_operation.ts @@ -3,15 +3,34 @@ import { StakingOperation as StakingOperationModel, StakingOperationStatusEnum, } from "../client/api"; -import { Transaction } from "./transaction"; +import { ITransaction, Transaction } from "./transaction"; import { Coinbase } from "./coinbase"; import { delay } from "./utils"; +export interface IStakingOperation { + getID(): string; + getStatus(): StakingOperationStatusEnum; + getWalletID(): string | undefined; + getAddressID(): string; + getNetworkID(): string; + isTerminalState(): boolean; + isFailedState(): boolean; + isCompleteState(): boolean; + getTransactions(): ITransaction[]; + getSignedVoluntaryExitMessages(): string[]; + reload(): Promise; + wait(options?: { + intervalSeconds?: number; + timeoutSeconds?: number; + }): Promise; + sign(key: ethers.Wallet): Promise; +} + /** * A representation of a staking operation (stake, unstake, claim stake, etc.). It * may have multiple steps with some being transactions to sign, and others to wait. */ -export class StakingOperation { +export class StakingOperation implements IStakingOperation { private model: StakingOperationModel; private readonly transactions: Transaction[]; diff --git a/src/coinbase/staking_reward.ts b/src/coinbase/staking_reward.ts index db2e0800..9f144246 100644 --- a/src/coinbase/staking_reward.ts +++ b/src/coinbase/staking_reward.ts @@ -4,10 +4,19 @@ import { Coinbase } from "./coinbase"; import { Asset } from "./asset"; import { Amount, StakingRewardFormat } from "./types"; +export interface IStakingReward { + date(): Date; + amount(): Amount; + addressId(): string; + usdValue(): Amount; + conversionPrice(): Amount; + conversionTime(): Date; +} + /** * A representation of a staking reward earned on a network for a given asset. */ -export class StakingReward { +export class StakingReward implements IStakingReward { private model: StakingRewardModel; private asset: Asset; private readonly format: StakingRewardFormat; diff --git a/src/coinbase/trade.ts b/src/coinbase/trade.ts index e2833da9..606f02f2 100644 --- a/src/coinbase/trade.ts +++ b/src/coinbase/trade.ts @@ -7,11 +7,29 @@ import { Transaction } from "./transaction"; import { TransactionStatus } from "./types"; import { delay } from "./utils"; +export interface ITrade { + getId(): string; + getNetworkId(): string; + getWalletId(): string; + getAddressId(): string; + getFromAssetId(): string; + getFromAmount(): Decimal; + getToAssetId(): string; + getToAmount(): Decimal; + getTransaction(): Transaction; + getApproveTransaction(): Transaction | undefined; + sign(key: ethers.Wallet): Promise; + broadcast(): Promise; + getStatus(): TransactionStatus | undefined; + wait(options: { intervalSeconds: number; timeoutSeconds: number }): Promise; + reload(): Promise; +} + /** * A representation of a Trade, which trades an amount of an Asset to another Asset on a Network. * The fee is assumed to be paid in the native Asset of the Network. */ -export class Trade { +export class Trade implements ITrade { private model: CoinbaseTrade; private transaction?: Transaction; private approveTransaction?: Transaction; diff --git a/src/coinbase/transaction.ts b/src/coinbase/transaction.ts index d7c6d3e3..41076c21 100644 --- a/src/coinbase/transaction.ts +++ b/src/coinbase/transaction.ts @@ -3,10 +3,21 @@ import { Transaction as TransactionModel, EthereumTransaction } from "../client/ import { TransactionStatus } from "./types"; import { parseUnsignedPayload } from "./utils"; +export interface ITransaction { + getUnsignedPayload(): string; + getSignedPayload(): string | undefined; + getTransactionHash(): string | undefined; + getStatus(): TransactionStatus | undefined; + fromAddressId(): string; + toAddressId(): string | undefined; + blockHeight(): string | undefined; + blockHash(): string | undefined; + content(): EthereumTransaction | undefined; +} /** * A representation of an onchain Transaction. */ -export class Transaction { +export class Transaction implements ITransaction { private model: TransactionModel; private raw?: ethers.Transaction; diff --git a/src/coinbase/transfer.ts b/src/coinbase/transfer.ts index 80b9cbab..270af6a1 100644 --- a/src/coinbase/transfer.ts +++ b/src/coinbase/transfer.ts @@ -8,12 +8,27 @@ import { ethers } from "ethers"; import { delay } from "./utils"; import { TimeoutError } from "./errors"; +export interface ITransfer { + getTransactionHash(): string | undefined; + getTransactionLink(): string | undefined; + getStatus(): TransferStatus | undefined; + getTransaction(): Transaction | undefined; + getSponsoredSend(): SponsoredSend | undefined; + getSendTransactionDelegate(): Transaction | SponsoredSend | undefined; + getId(): string; + getNetworkId(): string; + sign(key: ethers.Wallet): Promise; + broadcast(): Promise; + wait(options?: { intervalSeconds?: number; timeoutSeconds?: number }): Promise; + reload(): Promise; +} + /** * A representation of a Transfer, which moves an Amount of an Asset from * a user-controlled Wallet to another Address. The fee is assumed to be paid * in the native Asset of the Network. */ -export class Transfer { +export class Transfer implements ITransfer { private model: TransferModel; /** @@ -209,7 +224,7 @@ export class Transfer { * @returns The Transfer object * @throws {APIError} if the API request to broadcast a Transfer fails. */ - public async broadcast(): Promise { + public async broadcast(): Promise { if (!this.getSendTransactionDelegate()?.isSigned()) throw new Error("Cannot broadcast unsigned Transfer"); diff --git a/src/coinbase/types.ts b/src/coinbase/types.ts index abf830db..a03c3c85 100644 --- a/src/coinbase/types.ts +++ b/src/coinbase/types.ts @@ -55,8 +55,8 @@ import { WebhookEventTypeFilter, CreateWalletWebhookRequest, } from "./../client/api"; -import { Address } from "./address"; -import { Wallet } from "./wallet"; +import { IAddress } from "./address"; +import { IWallet } from "./wallet"; import { HistoricalBalance } from "./historical_balance"; import { Transaction } from "./transaction"; @@ -810,7 +810,7 @@ export type Amount = number | bigint | Decimal; /** * Destination type definition. */ -export type Destination = string | Address | Wallet; +export type Destination = string | IAddress | IWallet; /** * ServerSigner status type definition. diff --git a/src/coinbase/validator.ts b/src/coinbase/validator.ts index 621e7244..a6bc79cb 100644 --- a/src/coinbase/validator.ts +++ b/src/coinbase/validator.ts @@ -2,10 +2,14 @@ import { Coinbase } from "./coinbase"; import { Validator as ValidatorModel, ValidatorStatus as APIValidatorStatus } from "../client/api"; import { ValidatorStatus } from "./types"; +export interface IValidator { + getValidatorId(): string; + getStatus(): string; +} /** * A representation of a validator onchain. */ -export class Validator { +export class Validator implements IValidator { private model: ValidatorModel; /** diff --git a/src/coinbase/wallet.ts b/src/coinbase/wallet.ts index bd9c1baf..af220e82 100644 --- a/src/coinbase/wallet.ts +++ b/src/coinbase/wallet.ts @@ -5,41 +5,120 @@ import { ethers } from "ethers"; import * as fs from "fs"; import * as secp256k1 from "secp256k1"; import { Address as AddressModel, Wallet as WalletModel } from "../client"; -import { Address } from "./address"; -import { WalletAddress } from "./address/wallet_address"; +import { IContractInvocation } from "../coinbase/contract_invocation"; +import { IAddress } from "./address"; +import { IWalletAddress, WalletAddress } from "./address/wallet_address"; import { Asset } from "./asset"; import { Balance } from "./balance"; import { BalanceMap } from "./balance_map"; import { Coinbase } from "./coinbase"; import { ArgumentError } from "./errors"; -import { FaucetTransaction } from "./faucet_transaction"; -import { Trade } from "./trade"; -import { Transfer } from "./transfer"; +import { FaucetTransaction, IFaucetTransaction } from "./faucet_transaction"; +import { IPayloadSignature } from "./payload_signature"; +import { ISmartContract } from "./smart_contract"; +import { IStakingBalance, StakingBalance } from "./staking_balance"; +import { IStakingOperation } from "./staking_operation"; +import { IStakingReward, StakingReward } from "./staking_reward"; +import { ITrade } from "./trade"; +import { ITransfer } from "./transfer"; import { Amount, - StakingRewardFormat, CreateContractInvocationOptions, - CreateTransferOptions, + CreateERC1155Options, + CreateERC20Options, + CreateERC721Options, CreateTradeOptions, + CreateTransferOptions, ListHistoricalBalancesOptions, ListHistoricalBalancesResult, SeedData, ServerSignerStatus, StakeOptionsMode, + StakingRewardFormat, WalletCreateOptions, WalletData, - CreateERC20Options, - CreateERC721Options, - CreateERC1155Options, } from "./types"; import { convertStringToHex, delay, formatDate, getWeekBackDate } from "./utils"; -import { StakingOperation } from "./staking_operation"; -import { StakingReward } from "./staking_reward"; -import { StakingBalance } from "./staking_balance"; -import { PayloadSignature } from "./payload_signature"; -import { ContractInvocation } from "../coinbase/contract_invocation"; -import { SmartContract } from "./smart_contract"; -import { Webhook } from "./webhook"; +import { IWebhook, Webhook } from "./webhook"; + +export interface IWallet { + getId(): string; + getDefaultAddress(): Promise; + listAddresses(): Promise; + getAddress(addressId: string): Promise; + createAddress(): Promise; + setSeed(seed: string): void; + createTrade(options: CreateTradeOptions): Promise; + stakeableBalance( + asset_id: string, + mode?: StakeOptionsMode, + options?: { [key: string]: string }, + ): Promise; + unstakeableBalance( + asset_id: string, + mode?: StakeOptionsMode, + options?: { [key: string]: string }, + ): Promise; + claimableBalance( + asset_id: string, + mode?: StakeOptionsMode, + options?: { [key: string]: string }, + ): Promise; + stakingRewards( + assetId: string, + startTime?: string, + endTime?: string, + format?: StakingRewardFormat, + ): Promise; + historicalStakingBalances( + assetId: string, + startTime?: string, + endTime?: string, + ): Promise; + listHistoricalBalances( + options: ListHistoricalBalancesOptions, + ): Promise; + listBalances(): Promise; + getBalance(assetId: string): Promise; + getNetworkId(): string; + getServerSignerStatus(): ServerSignerStatus | undefined; + saveSeed(filePath: string, encrypt?: boolean): string; + loadSeed(filePath: string): Promise; + canSign(): boolean; + faucet(assetId?: string): Promise; + createTransfer(options: CreateTransferOptions): Promise; + createStake( + amount: Amount, + assetId: string, + mode?: StakeOptionsMode, + options?: { [key: string]: string }, + timeoutSeconds?: number, + intervalSeconds?: number, + ): Promise; + createUnstake( + amount: Amount, + assetId: string, + mode?: StakeOptionsMode, + options?: { [key: string]: string }, + timeoutSeconds?: number, + intervalSeconds?: number, + ): Promise; + createClaimStake( + amount: Amount, + assetId: string, + mode?: StakeOptionsMode, + options?: { [key: string]: string }, + timeoutSeconds?: number, + intervalSeconds?: number, + ): Promise; + invokeContract(options: CreateContractInvocationOptions): Promise; + export(): WalletData; + createWebhook(notificationUri: string): Promise; + deployToken(options: CreateERC20Options): Promise; + deployNFT(options: CreateERC721Options): Promise; + deployMultiToken(options: CreateERC1155Options): Promise; + createPayloadSignature(unsignedPayload: string): Promise; +} /** * A representation of a Wallet. Wallets come with a single default Address, but can expand to have a set of Addresses, @@ -49,7 +128,7 @@ import { Webhook } from "./webhook"; * Existing wallets can be imported with a seed using `Wallet.import`. * Wallets backed by a Server Signer can be fetched with `Wallet.fetch` and used for signing operations immediately. */ -export class Wallet { +export class Wallet implements IWallet { static MAX_ADDRESSES = 20; private model: WalletModel; @@ -79,8 +158,8 @@ export class Wallet { * * @returns The list of Wallets. */ - public static async listWallets(): Promise { - const walletList: Wallet[] = []; + public static async listWallets(): Promise { + const walletList: IWallet[] = []; const queue: string[] = [""]; while (queue.length > 0) { @@ -109,7 +188,7 @@ export class Wallet { * @param wallet_id - The ID of the Wallet to fetch * @returns The fetched Wallet */ - public static async fetch(wallet_id: string): Promise { + public static async fetch(wallet_id: string): Promise { const response = await Coinbase.apiClients.wallet!.getWallet(wallet_id); return Wallet.init(response.data!, ""); } @@ -125,7 +204,7 @@ export class Wallet { * @throws {ArgumentError} If the seed is not provided. * @throws {APIError} If the request fails. */ - public static async import(data: WalletData): Promise { + public static async import(data: WalletData): Promise { if (!data.walletId) { throw new ArgumentError("Wallet ID must be provided"); } @@ -155,7 +234,7 @@ export class Wallet { networkId = Coinbase.networks.BaseSepolia, timeoutSeconds = 20, intervalSeconds = 0.2, - }: WalletCreateOptions = {}): Promise { + }: WalletCreateOptions = {}): Promise { const result = await Coinbase.apiClients.wallet!.createWallet({ wallet: { network_id: networkId, @@ -215,7 +294,7 @@ export class Wallet { * @returns The new Address. * @throws {APIError} - If the address creation fails. */ - public async createAddress(): Promise
{ + public async createAddress(): Promise { let payload, key; if (!Coinbase.useServerSigner) { // TODO: Coordinate this value with concurrent calls to createAddress. @@ -317,7 +396,7 @@ export class Wallet { * @throws {Error} If the private key is not loaded, or if the asset IDs are unsupported, or if there are insufficient funds. * @returns The created Trade object. */ - public async createTrade(options: CreateTradeOptions): Promise { + public async createTrade(options: CreateTradeOptions): Promise { return (await this.getDefaultAddress()).createTrade(options); } @@ -448,7 +527,7 @@ export class Wallet { options: { [key: string]: string } = {}, timeoutSeconds = 60, intervalSeconds = 0.2, - ): Promise { + ): Promise { return (await this.getDefaultAddress()).createStake( amount, assetId, @@ -478,7 +557,7 @@ export class Wallet { options: { [key: string]: string } = {}, timeoutSeconds = 60, intervalSeconds = 0.2, - ): Promise { + ): Promise { return (await this.getDefaultAddress()).createUnstake( amount, assetId, @@ -508,7 +587,7 @@ export class Wallet { options: { [key: string]: string } = {}, timeoutSeconds = 60, intervalSeconds = 0.2, - ): Promise { + ): Promise { return (await this.getDefaultAddress()).createClaimStake( amount, assetId, @@ -575,7 +654,7 @@ export class Wallet { * * @returns The wallet ID. */ - public getId(): string | undefined { + public getId(): string { return this.model.id; } @@ -685,7 +764,7 @@ export class Wallet { * * @returns The default address */ - public async getDefaultAddress(): Promise { + public async getDefaultAddress(): Promise { if (this.model.default_address === undefined) { throw new Error("WalletModel default address not set"); } @@ -735,7 +814,7 @@ export class Wallet { * @throws {APIError} if the API request to create a Transfer fails. * @throws {APIError} if the API request to broadcast a Transfer fails. */ - public async createTransfer(options: CreateTransferOptions): Promise { + public async createTransfer(options: CreateTransferOptions): Promise { return (await this.getDefaultAddress()).createTransfer(options); } @@ -747,7 +826,7 @@ export class Wallet { * @throws {APIError} if the API request to create a Payload Signature fails. * @throws {Error} if the default address is not found. */ - public async createPayloadSignature(unsignedPayload: string): Promise { + public async createPayloadSignature(unsignedPayload: string): Promise { return (await this.getDefaultAddress()).createPayloadSignature(unsignedPayload); } @@ -758,7 +837,7 @@ export class Wallet { * * @returns The newly created webhook instance. */ - public async createWebhook(notificationUri: string): Promise { + public async createWebhook(notificationUri: string): Promise { const result = await Coinbase.apiClients.webhook!.createWalletWebhook(this.getId(), { notification_uri: notificationUri, }); @@ -783,7 +862,7 @@ export class Wallet { */ public async invokeContract( options: CreateContractInvocationOptions, - ): Promise { + ): Promise { return (await this.getDefaultAddress()).invokeContract(options); } @@ -797,7 +876,7 @@ export class Wallet { * @returns A Promise that resolves to the deployed SmartContract object. * @throws {Error} If the private key is not loaded when not using server signer. */ - public async deployToken(options: CreateERC20Options): Promise { + public async deployToken(options: CreateERC20Options): Promise { return (await this.getDefaultAddress()).deployToken(options); } @@ -811,7 +890,7 @@ export class Wallet { * @returns A Promise that resolves to the deployed SmartContract object. * @throws {Error} If the private key is not loaded when not using server signer. */ - public async deployNFT(options: CreateERC721Options): Promise { + public async deployNFT(options: CreateERC721Options): Promise { return (await this.getDefaultAddress()).deployNFT(options); } @@ -825,7 +904,7 @@ export class Wallet { * @returns A Promise that resolves to the deployed SmartContract object. * @throws {Error} If the private key is not loaded when not using server signer. */ - public async deployMultiToken(options: CreateERC1155Options): Promise { + public async deployMultiToken(options: CreateERC1155Options): Promise { return (await this.getDefaultAddress()).deployMultiToken(options); } diff --git a/src/coinbase/webhook.ts b/src/coinbase/webhook.ts index bbaf1252..331563fe 100644 --- a/src/coinbase/webhook.ts +++ b/src/coinbase/webhook.ts @@ -7,11 +7,22 @@ import { import { Coinbase } from "./coinbase"; import { CreateWebhookOptions } from "./types"; +export interface IWebhook { + getId(): string | undefined; + getNetworkId(): string | undefined; + getNotificationURI(): string | undefined; + getEventType(): WebhookEventType | undefined; + getEventTypeFilter(): WebhookEventTypeFilter | undefined; + getEventFilters(): Array | undefined; + getSignatureHeader(): string | undefined; + update(notificationUri: string): Promise; + delete(): Promise; +} /** * A representation of a Webhook, * which provides methods to create, list, update, and delete webhooks that are used to receive notifications of specific events. */ -export class Webhook { +export class Webhook implements IWebhook { private model: WebhookModel | null; /** @@ -168,7 +179,7 @@ export class Webhook { * @param notificationUri - The new URI for webhook notifications. * @returns A promise that resolves to the updated Webhook object. */ - public async update(notificationUri: string): Promise { + public async update(notificationUri: string): Promise { const result = await Coinbase.apiClients.webhook!.updateWebhook(this.getId()!, { notification_uri: notificationUri, event_filters: this.getEventFilters()!, diff --git a/src/tests/e2e.ts b/src/tests/e2e.ts index f0b36c3e..426469ff 100644 --- a/src/tests/e2e.ts +++ b/src/tests/e2e.ts @@ -31,8 +31,7 @@ describe("Coinbase SDK E2E Test", () => { console.log("Creating new wallet..."); const wallet = await Wallet.create(); - expect(wallet.toString()).toBeDefined(); - expect(wallet?.getId()).toBeDefined(); + expect(wallet.getId()).toBeDefined(); console.log( `Created new wallet with ID: ${wallet.getId()}, default address: ${wallet.getDefaultAddress()}`, ); diff --git a/src/tests/wallet_test.ts b/src/tests/wallet_test.ts index 82ce573d..bd4610b5 100644 --- a/src/tests/wallet_test.ts +++ b/src/tests/wallet_test.ts @@ -5,7 +5,7 @@ import { ethers } from "ethers"; import { APIError } from "../coinbase/api_error"; import { Coinbase } from "../coinbase/coinbase"; import { ArgumentError } from "../coinbase/errors"; -import { Wallet } from "../coinbase/wallet"; +import { IWallet, Wallet } from "../coinbase/wallet"; import { Transfer } from "../coinbase/transfer"; import { ServerSignerStatus, StakeOptionsMode, TransferStatus } from "../coinbase/types"; import { @@ -70,7 +70,7 @@ import { SmartContract } from "../coinbase/smart_contract"; import { Webhook } from "../coinbase/webhook"; describe("Wallet Class", () => { - let wallet: Wallet; + let wallet: IWallet; let walletModel: WalletModel; let walletId: string; const apiResponses = {}; @@ -801,7 +801,7 @@ describe("Wallet Class", () => { describe("when using a server signer", () => { const walletId = crypto.randomUUID(); - let wallet: Wallet; + let wallet: IWallet; beforeEach(async () => { jest.clearAllMocks(); Coinbase.useServerSigner = true; @@ -844,7 +844,7 @@ describe("Wallet Class", () => { }); describe(".init", () => { - let wallet: Wallet; + let wallet: IWallet; let addressList: AddressModel[]; let walletModel: WalletModel; const existingSeed = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"; @@ -1339,7 +1339,7 @@ describe("Wallet Class", () => { }); describe("#createWebhook", () => { - let wallet: Wallet; + let wallet: IWallet; let addressList: AddressModel[]; let walletModel: WalletModel; const existingSeed = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f";