Skip to content

Commit e53c0ad

Browse files
authored
chore: balance sdk method refactoring (#56)
* chore: fixed the type that is returned for the getEarningBalance method of SDK * chore: changeset * chore: fix
1 parent d22d73a commit e53c0ad

File tree

12 files changed

+184
-125
lines changed

12 files changed

+184
-125
lines changed

.changeset/spotty-crabs-listen.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@mycelium-sdk/core': major
3+
---
4+
5+
## What was changed?
6+
7+
- Introduced AddressBalance: balance is now returned as:{ overall: OverallAddressBalance; perVault: VaultBalance[] } instead of VaultBalance[]
8+
- getBalances() (SparkProtocol, ProxyProtocol) now returns AddressBalance (overall totals plus per-vault list)
9+
- getEarnBalances() on the wallet now returns AddressBalance instead of VaultBalance[]
10+
- AddressBalance is exported from the SDK public types
11+
12+
## Why was changed/added?
13+
14+
- Mycelium Cloud started to return a different format of data for the balance
15+
- Callers need both an aggregated view (overall balance) and per-vault breakdown; a single type with overall and perVault supports both without extra requests
16+
- Aligns protocol and wallet APIs and keeps balance shape consistent across the SDK
17+
18+
## How to use the change?
19+
20+
Use the new return type: result.overall for aggregated numbers, result.perVault for the list of vault balances (same structure as before, but under perVault). Example: const { overall, perVault } = await wallet.getEarnBalances(); and then use perVault[0].balance, perVault[0].vaultInfo, etc. Replace any code that treated the result as a plain array (e.g. result[0]) with result.perVault[0] and use result.overall when you need totals

packages/cli/src/cli.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import {
44
type MyceliumSDKConfig,
55
type ProxyBalance,
66
type SmartWallet,
7-
type VaultBalance,
7+
type AddressBalance,
88
type Vaults,
9+
type VaultBalance,
910
} from '@mycelium-sdk/core';
1011
import { formatBalancesToDisplay, formatVaultInfoToDisplay, getEnv } from './utils/formatters';
1112
import { WalletDatabase } from './libs/database';
@@ -241,16 +242,16 @@ export class CLI {
241242
return;
242243
}
243244

244-
const earningBalances = (await this.wallet.getEarnBalances()) as VaultBalance[];
245+
const earningBalances = (await this.wallet.getEarnBalances()) as AddressBalance;
245246

246247
if (!earningBalances) {
247248
logError('No earning balances found. You have not deposited any funds yet');
248249
return;
249250
}
250251

251-
const formattedBalances = earningBalances
252-
.filter((balance) => balance.balance !== null)
253-
.map((balance) => {
252+
const formattedBalances = earningBalances.perVault
253+
.filter((balance: VaultBalance) => balance.balance !== null)
254+
.map((balance: VaultBalance) => {
254255
const currentBalance = balance.balance as ProxyBalance;
255256
return {
256257
vaultInfo: balance.vaultInfo,
@@ -371,7 +372,7 @@ export class CLI {
371372
return;
372373
}
373374

374-
const balances = [...earningBalances].map((vault, index) => {
375+
const balances = earningBalances.perVault.map((vault: VaultBalance, index: number) => {
375376
const currentBalance = vault.balance as ProxyBalance;
376377
return {
377378
optionId: index + 1,

packages/sdk/src/protocols/base/BaseProtocol.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ import type { SupportedChainId } from '@/constants/chains';
1515
import type { SmartWallet } from '@/wallet/base/wallets/SmartWallet';
1616
import type {
1717
VaultInfo,
18-
VaultBalance,
1918
VaultTxnResult,
2019
Vaults,
2120
ProtocolsSecurityConfig,
21+
AddressBalance,
2222
} from '@/types/protocols/general';
2323
import type { ApiClient } from '@/tools/ApiClient';
2424

@@ -108,7 +108,7 @@ export abstract class BaseProtocol {
108108
* @param protocolId Protocol ID to get balances for
109109
* @returns Balance of deposited funds
110110
*/
111-
abstract getBalances(walletAddress: Address, protocolId?: string): Promise<VaultBalance[]>;
111+
abstract getBalances(walletAddress: Address, protocolId?: string): Promise<AddressBalance>;
112112

113113
/**
114114
* Approve a token for protocol use

packages/sdk/src/protocols/implementations/ProxyProtocol.spec.ts

Lines changed: 41 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { ChainManager } from '@mycelium-sdk/core/tools/ChainManager';
66
import type { ApiClient } from '@mycelium-sdk/core/tools/ApiClient';
77
import type { SmartWallet } from '@mycelium-sdk/core/wallet/base/wallets/SmartWallet';
88
import type {
9+
AddressBalance,
910
ProtocolsSecurityConfig,
1011
VaultInfo,
1112
VaultBalance,
@@ -63,6 +64,14 @@ describe('ProxyProtocol integration tests', () => {
6364
earned90dUpdatedAt: '2024-01-01T00:00:00Z',
6465
};
6566

67+
const createMockAddressBalance = (perVault: VaultBalance[]): AddressBalance => ({
68+
overall: {
69+
currentBalance: 1000,
70+
actualCurrentBalance: 1000,
71+
},
72+
perVault,
73+
});
74+
6675
beforeEach(() => {
6776
vi.clearAllMocks();
6877
vi.resetAllMocks();
@@ -380,14 +389,9 @@ describe('ProxyProtocol integration tests', () => {
380389
});
381390

382391
it('should withdraw specified amount from vault', async () => {
383-
const mockEarningBalances: VaultBalance[] = [
384-
{
385-
vaultInfo: mockVaultInfo,
386-
balance: mockProxyBalance,
387-
},
388-
];
389-
390-
vi.mocked(smartWallet.getEarnBalances).mockResolvedValue(mockEarningBalances);
392+
vi.mocked(smartWallet.getEarnBalances).mockResolvedValue(
393+
createMockAddressBalance([{ vaultInfo: mockVaultInfo, balance: mockProxyBalance }]),
394+
);
391395

392396
const mockOperationData: TransactionData = {
393397
to: mockVaultInfo.vaultAddress,
@@ -419,14 +423,9 @@ describe('ProxyProtocol integration tests', () => {
419423
});
420424

421425
it('should withdraw all balance when amount is not specified', async () => {
422-
const mockEarningBalances: VaultBalance[] = [
423-
{
424-
vaultInfo: mockVaultInfo,
425-
balance: mockProxyBalance,
426-
},
427-
];
428-
429-
vi.mocked(smartWallet.getEarnBalances).mockResolvedValue(mockEarningBalances);
426+
vi.mocked(smartWallet.getEarnBalances).mockResolvedValue(
427+
createMockAddressBalance([{ vaultInfo: mockVaultInfo, balance: mockProxyBalance }]),
428+
);
430429

431430
const mockOperationData: TransactionData = {
432431
to: mockVaultInfo.vaultAddress,
@@ -464,29 +463,21 @@ describe('ProxyProtocol integration tests', () => {
464463
});
465464

466465
it('should throw error when vault balance not found in earning balances', async () => {
467-
const mockEarningBalances: VaultBalance[] = [
468-
{
469-
vaultInfo: { ...mockVaultInfo, id: 'different-vault' },
470-
balance: mockProxyBalance,
471-
},
472-
];
473-
474-
vi.mocked(smartWallet.getEarnBalances).mockResolvedValue(mockEarningBalances);
466+
vi.mocked(smartWallet.getEarnBalances).mockResolvedValue(
467+
createMockAddressBalance([
468+
{ vaultInfo: { ...mockVaultInfo, id: 'different-vault' }, balance: mockProxyBalance },
469+
]),
470+
);
475471

476472
await expect(proxyProtocol.withdraw(mockVaultInfo, smartWallet, '500')).rejects.toThrow(
477473
'No earning balance found',
478474
);
479475
});
480476

481477
it('should throw error when API fails to return withdraw operations', async () => {
482-
const mockEarningBalances: VaultBalance[] = [
483-
{
484-
vaultInfo: mockVaultInfo,
485-
balance: mockProxyBalance,
486-
},
487-
];
488-
489-
vi.mocked(smartWallet.getEarnBalances).mockResolvedValue(mockEarningBalances);
478+
vi.mocked(smartWallet.getEarnBalances).mockResolvedValue(
479+
createMockAddressBalance([{ vaultInfo: mockVaultInfo, balance: mockProxyBalance }]),
480+
);
490481

491482
(apiClient.sendRequest as ReturnType<typeof vi.fn>).mockResolvedValue({
492483
success: false,
@@ -499,14 +490,9 @@ describe('ProxyProtocol integration tests', () => {
499490
});
500491

501492
it('should log operation after successful withdrawal', async () => {
502-
const mockEarningBalances: VaultBalance[] = [
503-
{
504-
vaultInfo: mockVaultInfo,
505-
balance: mockProxyBalance,
506-
},
507-
];
508-
509-
vi.mocked(smartWallet.getEarnBalances).mockResolvedValue(mockEarningBalances);
493+
vi.mocked(smartWallet.getEarnBalances).mockResolvedValue(
494+
createMockAddressBalance([{ vaultInfo: mockVaultInfo, balance: mockProxyBalance }]),
495+
);
510496

511497
const mockOperationData: TransactionData = {
512498
to: mockVaultInfo.vaultAddress,
@@ -541,14 +527,10 @@ describe('ProxyProtocol integration tests', () => {
541527
it('should withdraw with paymaster token when paymaster token equals withdraw token', async () => {
542528
const paymasterToken = mockVaultInfo.tokenAddress;
543529
const mockPublicClient = chainManager.getPublicClient(8453);
544-
const mockEarningBalances: VaultBalance[] = [
545-
{
546-
vaultInfo: mockVaultInfo,
547-
balance: mockProxyBalance,
548-
},
549-
];
550530

551-
vi.mocked(smartWallet.getEarnBalances).mockResolvedValue(mockEarningBalances);
531+
vi.mocked(smartWallet.getEarnBalances).mockResolvedValue(
532+
createMockAddressBalance([{ vaultInfo: mockVaultInfo, balance: mockProxyBalance }]),
533+
);
552534

553535
// Mock balance check for gas reserve
554536
vi.mocked(mockPublicClient.readContract as ReturnType<typeof vi.fn>).mockResolvedValue(
@@ -591,14 +573,10 @@ describe('ProxyProtocol integration tests', () => {
591573
it('should throw error when wallet balance is insufficient for gas payment', async () => {
592574
const paymasterToken = mockVaultInfo.tokenAddress;
593575
const mockPublicClient = chainManager.getPublicClient(8453);
594-
const mockEarningBalances: VaultBalance[] = [
595-
{
596-
vaultInfo: mockVaultInfo,
597-
balance: mockProxyBalance,
598-
},
599-
];
600576

601-
vi.mocked(smartWallet.getEarnBalances).mockResolvedValue(mockEarningBalances);
577+
vi.mocked(smartWallet.getEarnBalances).mockResolvedValue(
578+
createMockAddressBalance([{ vaultInfo: mockVaultInfo, balance: mockProxyBalance }]),
579+
);
602580

603581
// Mock balance that's too low for gas reserve
604582
vi.mocked(mockPublicClient.readContract as ReturnType<typeof vi.fn>).mockResolvedValue(
@@ -618,16 +596,13 @@ describe('ProxyProtocol integration tests', () => {
618596

619597
it('should fetch and return earning balances of a user by a provided address', async () => {
620598
const walletAddress = '0x1234567890123456789012345678901234567890' as Address;
621-
const mockBalances: VaultBalance[] = [
622-
{
623-
vaultInfo: mockVaultInfo,
624-
balance: mockProxyBalance,
625-
},
626-
];
599+
const mockAddressBalance = createMockAddressBalance([
600+
{ vaultInfo: mockVaultInfo, balance: mockProxyBalance },
601+
]);
627602

628603
(apiClient.sendRequest as ReturnType<typeof vi.fn>).mockResolvedValue({
629604
success: true,
630-
data: mockBalances,
605+
data: mockAddressBalance,
631606
});
632607

633608
const result = await proxyProtocol.getBalances(walletAddress);
@@ -638,21 +613,18 @@ describe('ProxyProtocol integration tests', () => {
638613
userAddress: walletAddress,
639614
});
640615

641-
expect(result).toEqual(mockBalances);
616+
expect(result).toEqual(mockAddressBalance);
642617
});
643618

644619
it('should fetch balances for specific protocol ID', async () => {
645620
const walletAddress = '0x1234567890123456789012345678901234567890' as Address;
646-
const mockBalances: VaultBalance[] = [
647-
{
648-
vaultInfo: mockVaultInfo,
649-
balance: mockProxyBalance,
650-
},
651-
];
621+
const mockAddressBalance = createMockAddressBalance([
622+
{ vaultInfo: mockVaultInfo, balance: mockProxyBalance },
623+
]);
652624

653625
(apiClient.sendRequest as ReturnType<typeof vi.fn>).mockResolvedValue({
654626
success: true,
655-
data: mockBalances,
627+
data: mockAddressBalance,
656628
});
657629

658630
await proxyProtocol.getBalances(walletAddress, 'spark');

packages/sdk/src/protocols/implementations/ProxyProtocol.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { BaseProtocol } from '@/protocols/base/BaseProtocol';
22
import type { ChainManager } from '@/tools/ChainManager';
33
import type {
4+
AddressBalance,
45
ProtocolsSecurityConfig,
5-
VaultBalance,
66
VaultInfo,
77
Vaults,
88
VaultTxnResult,
@@ -259,7 +259,9 @@ export class ProxyProtocol extends BaseProtocol {
259259
throw new Error('No earning balances found');
260260
}
261261

262-
const earningBalance = earningBalances.find((balance) => balance.vaultInfo.id === vaultInfo.id);
262+
const earningBalance = earningBalances.perVault.find(
263+
(balance) => balance.vaultInfo.id === vaultInfo.id,
264+
);
263265

264266
if (!earningBalance) {
265267
throw new Error('No earning balance found');
@@ -320,7 +322,7 @@ export class ProxyProtocol extends BaseProtocol {
320322
* @param protocolId Protocol ID to get the balances for. Optional, default is undefined
321323
* @returns Balances of the user in the protocol vaults
322324
*/
323-
async getBalances(walletAddress: Address, protocolId?: string): Promise<VaultBalance[]> {
325+
async getBalances(walletAddress: Address, protocolId?: string): Promise<AddressBalance> {
324326
const pathParams = {
325327
chain_id: this.getSelectedChainId().toString(),
326328
protocol_id: protocolId || '',
@@ -333,7 +335,7 @@ export class ProxyProtocol extends BaseProtocol {
333335
throw new Error(apiResponse.error || 'Failed to get balances');
334336
}
335337

336-
const balances: VaultBalance[] = apiResponse.data as unknown as VaultBalance[];
338+
const balances: AddressBalance = apiResponse.data as unknown as AddressBalance;
337339

338340
return balances;
339341
}

packages/sdk/src/protocols/implementations/SparkProtocol.spec.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -488,14 +488,15 @@ describe('SparkProtocol integration tests', () => {
488488

489489
expect(formatUnits).toHaveBeenCalledWith(mockAssets, mockVaultInfo.tokenDecimals);
490490

491-
expect(result).toHaveLength(1);
492-
expect(result[0]?.vaultInfo.id).toBe('sUSDC');
493-
expect(result[0]?.balance).toBe('1050.0');
494-
expect(result[0]?.vaultInfo.metadata?.apy).toBeDefined();
495-
expect(typeof result[0]?.vaultInfo.metadata?.apy).toBe('number');
491+
expect(result.overall).toEqual({ currentBalance: 1050, actualCurrentBalance: 1050 });
492+
expect(result.perVault).toHaveLength(1);
493+
expect(result.perVault[0]?.vaultInfo.id).toBe('sUSDC');
494+
expect(result.perVault[0]?.balance).toBe('1050.0');
495+
expect(result.perVault[0]?.vaultInfo.metadata?.apy).toBeDefined();
496+
expect(typeof result.perVault[0]?.vaultInfo.metadata?.apy).toBe('number');
496497
});
497498

498-
it('should return null balance when wallet has no shares', async () => {
499+
it('should return zero overall and perVault balance when wallet has no shares', async () => {
499500
const walletAddress = '0x1234567890123456789012345678901234567890' as Address;
500501
const mockPublicClient = chainManager.getPublicClient(8453);
501502
const mockSSR = BigInt('1050000000000000000000000000');
@@ -512,11 +513,12 @@ describe('SparkProtocol integration tests', () => {
512513
functionName: 'getSSR',
513514
});
514515

515-
expect(result).toHaveLength(1);
516-
expect(result[0]?.balance).toBeNull();
517-
expect(result[0]?.vaultInfo.id).toBe('sUSDC');
518-
expect(result[0]?.vaultInfo.metadata?.apy).toBeDefined();
519-
expect(typeof result[0]?.vaultInfo.metadata?.apy).toBe('number');
516+
expect(result.overall).toEqual({ currentBalance: 0, actualCurrentBalance: 0 });
517+
expect(result.perVault).toHaveLength(1);
518+
expect(result.perVault[0]?.balance).toBe('0');
519+
expect(result.perVault[0]?.vaultInfo.id).toBe('sUSDC');
520+
expect(result.perVault[0]?.vaultInfo.metadata?.apy).toBeDefined();
521+
expect(typeof result.perVault[0]?.vaultInfo.metadata?.apy).toBe('number');
520522
});
521523

522524
it('should throw error when public client is not initialized', async () => {

0 commit comments

Comments
 (0)