diff --git a/src/coinbase/tests/address_test.ts b/src/coinbase/tests/address_test.ts index 764659ee..a575cb0d 100644 --- a/src/coinbase/tests/address_test.ts +++ b/src/coinbase/tests/address_test.ts @@ -18,14 +18,14 @@ import Decimal from "decimal.js"; const newEthAddress = ethers.Wallet.createRandom(); -const VALID_ADDRESS_MODEL: AddressModel = { +export const VALID_ADDRESS_MODEL: AddressModel = { address_id: newEthAddress.address, network_id: Coinbase.networkList.BaseSepolia, public_key: newEthAddress.publicKey, wallet_id: randomUUID(), }; -const VALID_BALANCE_MODEL: BalanceModel = { +export const ETH_BALANCE_MODEL: BalanceModel = { amount: "1000000000000000000", asset: { asset_id: Coinbase.assetList.Eth, @@ -33,33 +33,26 @@ const VALID_BALANCE_MODEL: BalanceModel = { }, }; -const VALID_ADDRESS_BALANCE_LIST: AddressBalanceList = { - data: [ - { - amount: "1000000000000000000", - asset: { - asset_id: Coinbase.assetList.Eth, - network_id: Coinbase.networkList.BaseSepolia, - decimals: 18, - }, - }, - { - amount: "5000000000", - asset: { - asset_id: "usdc", - network_id: Coinbase.networkList.BaseSepolia, - decimals: 6, - }, - }, - { - amount: "3000000000000000000", - asset: { - asset_id: "weth", - network_id: Coinbase.networkList.BaseSepolia, - decimals: 6, - }, - }, - ], +export const USDC_BALANCE_MODEL: BalanceModel = { + amount: "5000000000", + asset: { + asset_id: "usdc", + network_id: Coinbase.networkList.BaseSepolia, + decimals: 6, + }, +}; + +export const WETH_BALANCE_MODEL: BalanceModel = { + amount: "3000000000000000000", + asset: { + asset_id: "weth", + network_id: Coinbase.networkList.BaseSepolia, + decimals: 6, + }, +}; + +export const VALID_ADDRESS_BALANCE_LIST: AddressBalanceList = { + data: [ETH_BALANCE_MODEL, USDC_BALANCE_MODEL, WETH_BALANCE_MODEL], has_more: false, next_page: "", total_count: 3, @@ -104,21 +97,21 @@ describe("Address", () => { }); it("should return the correct ETH balance", async () => { - axiosMock.onGet().reply(200, VALID_BALANCE_MODEL); + axiosMock.onGet().reply(200, ETH_BALANCE_MODEL); const ethBalance = await address.getBalance(Coinbase.assetList.Eth); expect(ethBalance).toBeInstanceOf(Decimal); expect(ethBalance).toEqual(new Decimal(1)); }); it("should return the correct Gwei balance", async () => { - axiosMock.onGet().reply(200, VALID_BALANCE_MODEL); + axiosMock.onGet().reply(200, ETH_BALANCE_MODEL); const ethBalance = await address.getBalance("gwei"); expect(ethBalance).toBeInstanceOf(Decimal); expect(ethBalance).toEqual(new Decimal(1000000000)); }); it("should return the correct Wei balance", async () => { - axiosMock.onGet().reply(200, VALID_BALANCE_MODEL); + axiosMock.onGet().reply(200, ETH_BALANCE_MODEL); const ethBalance = await address.getBalance("wei"); expect(ethBalance).toBeInstanceOf(Decimal); expect(ethBalance).toEqual(new Decimal(1000000000000000000)); diff --git a/src/coinbase/tests/wallet_test.ts b/src/coinbase/tests/wallet_test.ts index b82240fc..d21fdb4d 100644 --- a/src/coinbase/tests/wallet_test.ts +++ b/src/coinbase/tests/wallet_test.ts @@ -6,6 +6,9 @@ import { Coinbase } from "../coinbase"; import { ArgumentError } from "../errors"; import { Wallet } from "../wallet"; import { createAxiosMock } from "./utils"; +import { ETH_BALANCE_MODEL, VALID_ADDRESS_BALANCE_LIST, VALID_ADDRESS_MODEL } from "./address_test"; +import Decimal from "decimal.js"; +import { APIError } from "../api_error"; const walletId = randomUUID(); export const VALID_WALLET_MODEL = { @@ -20,7 +23,7 @@ export const VALID_WALLET_MODEL = { }; describe("Wallet Class", () => { - let wallet, axiosMock; + let wallet: Wallet, axiosMock: MockAdapter; const seed = bip39.generateMnemonic(); const [axiosInstance, configuration, BASE_PATH] = createAxiosMock(); @@ -31,7 +34,11 @@ describe("Wallet Class", () => { beforeAll(async () => { axiosMock = new MockAdapter(axiosInstance); - axiosMock.onPost().reply(200, VALID_WALLET_MODEL).onGet().reply(200, VALID_WALLET_MODEL); + axiosMock + .onPost(/v1\/wallets/) + .replyOnce(200, VALID_WALLET_MODEL) + .onGet(/v1\/wallets\/.+\/addresses\/.+/) + .reply(200, VALID_ADDRESS_MODEL); wallet = await Wallet.init(VALID_WALLET_MODEL, client, seed, 2); }); @@ -39,7 +46,7 @@ describe("Wallet Class", () => { axiosMock.reset(); }); - describe("should initializes a new Wallet", () => { + describe("should initialize a new Wallet", () => { it("should return a Wallet instance", async () => { expect(wallet).toBeInstanceOf(Wallet); }); @@ -54,7 +61,50 @@ describe("Wallet Class", () => { }); it("should derive the correct number of addresses", async () => { - expect(wallet.addresses.length).toBe(2); + expect(wallet.listAddresses().length).toBe(2); + }); + it("should return the correct address", () => { + const defaultAddress = wallet.defaultAddress(); + expect(wallet.getAddress(defaultAddress!.getId())).toEqual(wallet.listAddresses()[0]); + }); + + it("should return a BalanceMap with ETH, USDC, and WETH balances", async () => { + axiosMock.onGet().reply(200, VALID_ADDRESS_BALANCE_LIST); + const balances = await wallet.listBalances(); + expect(balances.get(Coinbase.assetList.Eth)).toEqual(new Decimal(1)); + expect(balances.get("usdc")).toEqual(new Decimal(5000)); + expect(balances.get("weth")).toEqual(new Decimal(3)); + }); + + it("should return the correct ETH balance", async () => { + axiosMock.onGet().reply(200, ETH_BALANCE_MODEL); + const ethBalance = await wallet.getBalance(Coinbase.assetList.Eth); + expect(ethBalance).toBeInstanceOf(Decimal); + expect(ethBalance).toEqual(new Decimal(1)); + }); + + it("should return the correct Gwei balance", async () => { + axiosMock.onGet().reply(200, ETH_BALANCE_MODEL); + const ethBalance = await wallet.getBalance("gwei"); + expect(ethBalance).toBeInstanceOf(Decimal); + expect(ethBalance).toEqual(new Decimal(1000000000)); + }); + + it("should return the correct Wei balance", async () => { + axiosMock.onGet().reply(200, ETH_BALANCE_MODEL); + const ethBalance = await wallet.getBalance("wei"); + expect(ethBalance).toBeInstanceOf(Decimal); + expect(ethBalance).toEqual(new Decimal(1000000000000000000)); + }); + + it("should return an error for an unsupported asset", async () => { + axiosMock.onGet().reply(404, null); + try { + await wallet.getBalance("unsupported-asset"); + fail("Expect 404 to be thrown"); + } catch (error) { + expect(error).toBeInstanceOf(APIError); + } }); it("should return the correct string representation", async () => { diff --git a/src/coinbase/wallet.ts b/src/coinbase/wallet.ts index 01731d41..ba589bfc 100644 --- a/src/coinbase/wallet.ts +++ b/src/coinbase/wallet.ts @@ -215,9 +215,28 @@ export class Wallet { * @returns The default address */ public defaultAddress(): Address | undefined { - return this.model.default_address - ? new Address(this.model.default_address, this.client.address!) - : undefined; + if (this.model.default_address?.address_id !== undefined) { + return this.getAddress(this.model.default_address?.address_id); + } + } + + /** + * Lists the addresses in the Wallet. + * + * @returns {Address[]} The list of addresses in the wallet + */ + public listAddresses(): Address[] { + return this.addresses; + } + + /** + * Gets the address with the specified ID in the Wallet. + * + * @param {string} addressId - The ID of the address to fetch. + * @returns {Address} The address with the specified ID. + */ + public getAddress(addressId: string): Address | undefined { + return this.addresses.find(address => address.getId() === addressId); } /**