-
Notifications
You must be signed in to change notification settings - Fork 42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implementing getBalances, getBalance, getAddress and canSign methods #24
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,22 @@ | ||
import { randomUUID } from "crypto"; | ||
import { Coinbase } from "../coinbase"; | ||
import { Wallet } from "../wallet"; | ||
import { Address as AddressModel, Wallet as WalletModel } from "./../../client"; | ||
import { addressesApiMock, mockFn, newAddressModel, walletsApiMock } from "./utils"; | ||
import { | ||
AddressBalanceList, | ||
Address as AddressModel, | ||
Wallet as WalletModel, | ||
Balance as BalanceModel, | ||
} from "./../../client"; | ||
import { ArgumentError } from "../errors"; | ||
import { | ||
addressesApiMock, | ||
mockFn, | ||
mockReturnValue, | ||
newAddressModel, | ||
walletsApiMock, | ||
} from "./utils"; | ||
import { Address } from "../address"; | ||
import Decimal from "decimal.js"; | ||
|
||
describe("Wallet Class", () => { | ||
let wallet, walletModel, walletId; | ||
|
@@ -171,4 +184,156 @@ describe("Wallet Class", () => { | |
expect(newWallet).toBeInstanceOf(Wallet); | ||
}); | ||
}); | ||
|
||
describe(".defaultAddress", () => { | ||
let wallet, walletId; | ||
beforeEach(async () => { | ||
jest.clearAllMocks(); | ||
walletId = randomUUID(); | ||
const mockAddressModel: AddressModel = newAddressModel(walletId); | ||
const walletMockWithDefaultAddress: WalletModel = { | ||
id: walletId, | ||
network_id: Coinbase.networkList.BaseSepolia, | ||
default_address: mockAddressModel, | ||
}; | ||
Coinbase.apiClients.wallet = walletsApiMock; | ||
Coinbase.apiClients.address = addressesApiMock; | ||
Coinbase.apiClients.wallet!.createWallet = mockReturnValue(walletMockWithDefaultAddress); | ||
Coinbase.apiClients.wallet!.getWallet = mockReturnValue(walletMockWithDefaultAddress); | ||
Coinbase.apiClients.address!.createAddress = mockReturnValue(mockAddressModel); | ||
wallet = await Wallet.create(); | ||
}); | ||
|
||
it("should return the correct address", async () => { | ||
const defaultAddress = wallet.defaultAddress(); | ||
const address = wallet.getAddress(defaultAddress?.getId()); | ||
expect(address).toBeInstanceOf(Address); | ||
expect(address?.getId()).toBe(address.getId()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could also have this just assert on the |
||
}); | ||
|
||
describe(".walletId", () => { | ||
it("should return the correct wallet ID", async () => { | ||
expect(wallet.getId()).toBe(walletId); | ||
}); | ||
}); | ||
|
||
describe(".networkId", () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, I will have a PR for this changes (PSDK-165) |
||
it("should return the correct network ID", async () => { | ||
expect(wallet.getNetworkId()).toBe(Coinbase.networkList.BaseSepolia); | ||
}); | ||
}); | ||
}); | ||
|
||
describe(".listBalances", () => { | ||
beforeEach(() => { | ||
const mockBalanceResponse: AddressBalanceList = { | ||
data: [ | ||
{ | ||
amount: "1000000000000000000", | ||
asset: { | ||
asset_id: Coinbase.assetList.Eth, | ||
network_id: Coinbase.networkList.BaseSepolia, | ||
decimals: 18, | ||
}, | ||
}, | ||
{ | ||
amount: "5000000", | ||
asset: { | ||
asset_id: "usdc", | ||
network_id: Coinbase.networkList.BaseSepolia, | ||
decimals: 6, | ||
}, | ||
}, | ||
], | ||
has_more: false, | ||
next_page: "", | ||
total_count: 2, | ||
}; | ||
Coinbase.apiClients.wallet!.listWalletBalances = mockReturnValue(mockBalanceResponse); | ||
}); | ||
|
||
it("should return a hash with an ETH and USDC balance", async () => { | ||
const balanceMap = await wallet.listBalances(); | ||
expect(balanceMap.get("eth")).toEqual(new Decimal(1)); | ||
expect(balanceMap.get("usdc")).toEqual(new Decimal(5)); | ||
expect(Coinbase.apiClients.wallet!.listWalletBalances).toHaveBeenCalledTimes(1); | ||
expect(Coinbase.apiClients.wallet!.listWalletBalances).toHaveBeenCalledWith(walletId); | ||
}); | ||
}); | ||
|
||
describe(".getBalance", () => { | ||
beforeEach(() => { | ||
const mockWalletBalance: BalanceModel = { | ||
amount: "5000000000000000000", | ||
asset: { | ||
asset_id: Coinbase.assetList.Eth, | ||
network_id: Coinbase.networkList.BaseSepolia, | ||
decimals: 18, | ||
}, | ||
}; | ||
Coinbase.apiClients.wallet!.getWalletBalance = mockReturnValue(mockWalletBalance); | ||
}); | ||
|
||
it("should return the correct ETH balance", async () => { | ||
const balanceMap = await wallet.getBalance(Coinbase.assetList.Eth); | ||
expect(balanceMap).toEqual(new Decimal(5)); | ||
expect(Coinbase.apiClients.wallet!.getWalletBalance).toHaveBeenCalledTimes(1); | ||
expect(Coinbase.apiClients.wallet!.getWalletBalance).toHaveBeenCalledWith( | ||
walletId, | ||
Coinbase.assetList.Eth, | ||
); | ||
}); | ||
|
||
it("should return the correct GWEI balance", async () => { | ||
const balance = await wallet.getBalance(Coinbase.assetList.Gwei); | ||
expect(balance).toEqual(new Decimal((BigInt(5) * Coinbase.GWEI_PER_ETHER).toString())); | ||
expect(Coinbase.apiClients.wallet!.getWalletBalance).toHaveBeenCalledTimes(1); | ||
expect(Coinbase.apiClients.wallet!.getWalletBalance).toHaveBeenCalledWith( | ||
walletId, | ||
Coinbase.assetList.Gwei, | ||
); | ||
}); | ||
|
||
it("should return the correct WEI balance", async () => { | ||
const balance = await wallet.getBalance(Coinbase.assetList.Wei); | ||
expect(balance).toEqual(new Decimal((BigInt(5) * Coinbase.WEI_PER_ETHER).toString())); | ||
expect(Coinbase.apiClients.wallet!.getWalletBalance).toHaveBeenCalledTimes(1); | ||
expect(Coinbase.apiClients.wallet!.getWalletBalance).toHaveBeenCalledWith( | ||
walletId, | ||
Coinbase.assetList.Wei, | ||
); | ||
}); | ||
|
||
it("should return 0 when the balance is not found", async () => { | ||
Coinbase.apiClients.wallet!.getWalletBalance = mockReturnValue({}); | ||
const balance = await wallet.getBalance(Coinbase.assetList.Wei); | ||
expect(balance).toEqual(new Decimal(0)); | ||
expect(Coinbase.apiClients.wallet!.getWalletBalance).toHaveBeenCalledTimes(1); | ||
expect(Coinbase.apiClients.wallet!.getWalletBalance).toHaveBeenCalledWith( | ||
walletId, | ||
Coinbase.assetList.Wei, | ||
); | ||
}); | ||
}); | ||
|
||
describe(".canSign", () => { | ||
let wallet; | ||
beforeAll(async () => { | ||
const mockAddressModel = newAddressModel(walletId); | ||
const mockWalletModel = { | ||
id: walletId, | ||
network_id: Coinbase.networkList.BaseSepolia, | ||
default_address: mockAddressModel, | ||
}; | ||
Coinbase.apiClients.wallet = walletsApiMock; | ||
Coinbase.apiClients.address = addressesApiMock; | ||
Coinbase.apiClients.wallet!.createWallet = mockReturnValue(mockWalletModel); | ||
Coinbase.apiClients.wallet!.getWallet = mockReturnValue(mockWalletModel); | ||
Coinbase.apiClients.address!.createAddress = mockReturnValue(mockAddressModel); | ||
wallet = await Wallet.create(); | ||
}); | ||
it("should return true when the wallet initialized ", () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we test when the wallet does not have a seed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @alex-stone Based on my understanding, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Synced offline: Let's add that when we add listing wallets support |
||
expect(wallet.canSign()).toBe(true); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,9 @@ import { ArgumentError, InternalError } from "./errors"; | |
import { FaucetTransaction } from "./faucet_transaction"; | ||
import { WalletData } from "./types"; | ||
import { convertStringToHex } from "./utils"; | ||
import { BalanceMap } from "./balance_map"; | ||
import Decimal from "decimal.js"; | ||
import { Balance } from "./balance"; | ||
|
||
/** | ||
* A representation of a Wallet. Wallets come with a single default Address, but can expand to have a set of Addresses, | ||
|
@@ -207,6 +210,43 @@ export class Wallet { | |
this.addressIndex++; | ||
} | ||
|
||
/** | ||
* Returns the Address with the given ID. | ||
* | ||
* @param addressId - The ID of the Address to retrieve. | ||
* @returns The Address. | ||
*/ | ||
public getAddress(addressId: string): Address | undefined { | ||
return this.addresses.find(address => { | ||
return address.getId() === addressId; | ||
}); | ||
} | ||
|
||
/** | ||
* Returns the list of balances of this Wallet. Balances are aggregated across all Addresses in the Wallet. | ||
* | ||
* @returns The list of balances. The key is the Asset ID, and the value is the balance. | ||
*/ | ||
public async listBalances(): Promise<BalanceMap> { | ||
const response = await Coinbase.apiClients.wallet!.listWalletBalances(this.model.id!); | ||
return BalanceMap.fromBalances(response.data.data); | ||
} | ||
|
||
/** | ||
* Returns the balance of the provided Asset. Balances are aggregated across all Addresses in the Wallet. | ||
* | ||
* @param assetId - The ID of the Asset to retrieve the balance for. | ||
* @returns The balance of the Asset. | ||
*/ | ||
public async getBalance(assetId: string): Promise<Decimal> { | ||
const response = await Coinbase.apiClients.wallet!.getWalletBalance(this.model.id!, assetId); | ||
if (!response.data.amount) { | ||
return new Decimal(0); | ||
} | ||
const balance = Balance.fromModelAndAssetId(response.data, assetId); | ||
return balance.amount; | ||
} | ||
|
||
/** | ||
* Returns the Network ID of the Wallet. | ||
* | ||
|
@@ -234,6 +274,15 @@ export class Wallet { | |
return this.model.default_address ? new Address(this.model.default_address) : undefined; | ||
} | ||
|
||
/** | ||
* Returns whether the Wallet has a seed with which to derive keys and sign transactions. | ||
* | ||
* @returns Whether the Wallet has a seed with which to derive keys and sign transactions. | ||
*/ | ||
public canSign(): boolean { | ||
return this.master.publicKey !== undefined; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this just be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
} | ||
|
||
/** | ||
* Requests funds from the faucet for the Wallet's default address and returns the faucet transaction. | ||
* This is only supported on testnet networks. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we're aiming for consistency with ruby and are using the
.
prefixes, we should do:and
E.g.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I'm going to update this. I will also update all test files. (tracking with PSDK-165)