Skip to content

Commit

Permalink
A useWalletAccountsDetails() hook you can use to let the list of co…
Browse files Browse the repository at this point in the history
…nnected (authorized) accounts for a wallet
  • Loading branch information
steveluscher committed Apr 18, 2024
1 parent 7152bf8 commit 2dffcb0
Show file tree
Hide file tree
Showing 10 changed files with 103 additions and 15 deletions.
6 changes: 3 additions & 3 deletions packages/errors/src/codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,8 +248,8 @@ export const SOLANA_ERROR__TRANSACTION_ERROR__UNBALANCED_TRANSACTION = 7050036 a
// Wallet-related errors
// Reserve error codes in the range [7718000, 7718999]
export const SOLANA_ERROR__WALLET__CHAIN_UNSUPPORTED = 7718000 as const;
export const SOLANA_ERROR__WALLET__EXPECTED_CONNECTED_ACCOUNTS_FOR_CHAIN = 7718001 as const;
export const SOLANA_ERROR__WALLET__HAS_NO_CONNECTED_ACCOUNTS = 7718002 as const;
export const SOLANA_ERROR__WALLET__EXPECTED_CONNECTED_ACCOUNTS = 7718001 as const;
export const SOLANA_ERROR__WALLET__EXPECTED_CONNECTED_ACCOUNTS_FOR_CHAIN = 7718002 as const;
export const SOLANA_ERROR__WALLET__INVALID_SOLANA_CHAIN = 7718003 as const;

// Codec-related errors.
Expand Down Expand Up @@ -540,5 +540,5 @@ export type SolanaErrorCode =
| typeof SOLANA_ERROR__TRANSACTION_ERROR__WOULD_EXCEED_MAX_VOTE_COST_LIMIT
| typeof SOLANA_ERROR__WALLET__CHAIN_UNSUPPORTED
| typeof SOLANA_ERROR__WALLET__EXPECTED_CONNECTED_ACCOUNTS_FOR_CHAIN
| typeof SOLANA_ERROR__WALLET__HAS_NO_CONNECTED_ACCOUNTS
| typeof SOLANA_ERROR__WALLET__EXPECTED_CONNECTED_ACCOUNTS
| typeof SOLANA_ERROR__WALLET__INVALID_SOLANA_CHAIN;
8 changes: 4 additions & 4 deletions packages/errors/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ import {
SOLANA_ERROR__TRANSACTION_ERROR__PROGRAM_EXECUTION_TEMPORARILY_RESTRICTED,
SOLANA_ERROR__TRANSACTION_ERROR__UNKNOWN,
SOLANA_ERROR__WALLET__CHAIN_UNSUPPORTED,
SOLANA_ERROR__WALLET__EXPECTED_CONNECTED_ACCOUNTS,
SOLANA_ERROR__WALLET__EXPECTED_CONNECTED_ACCOUNTS_FOR_CHAIN,
SOLANA_ERROR__WALLET__HAS_NO_CONNECTED_ACCOUNTS,
SOLANA_ERROR__WALLET__INVALID_SOLANA_CHAIN,
SolanaErrorCode,
} from './codes';
Expand Down Expand Up @@ -586,11 +586,11 @@ export type SolanaErrorContext = DefaultUnspecifiedErrorContextToUndefined<
chain: `${string}:${string}`;
walletName: string;
};
[SOLANA_ERROR__WALLET__EXPECTED_CONNECTED_ACCOUNTS_FOR_CHAIN]: {
chain: `${string}:${string}`;
[SOLANA_ERROR__WALLET__EXPECTED_CONNECTED_ACCOUNTS]: {
walletName: string;
};
[SOLANA_ERROR__WALLET__HAS_NO_CONNECTED_ACCOUNTS]: {
[SOLANA_ERROR__WALLET__EXPECTED_CONNECTED_ACCOUNTS_FOR_CHAIN]: {
chain: `${string}:${string}`;
walletName: string;
};
[SOLANA_ERROR__WALLET__INVALID_SOLANA_CHAIN]: {
Expand Down
4 changes: 2 additions & 2 deletions packages/errors/src/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,8 @@ import {
SOLANA_ERROR__TRANSACTION_ERROR__WOULD_EXCEED_MAX_BLOCK_COST_LIMIT,
SOLANA_ERROR__TRANSACTION_ERROR__WOULD_EXCEED_MAX_VOTE_COST_LIMIT,
SOLANA_ERROR__WALLET__CHAIN_UNSUPPORTED,
SOLANA_ERROR__WALLET__EXPECTED_CONNECTED_ACCOUNTS,
SOLANA_ERROR__WALLET__EXPECTED_CONNECTED_ACCOUNTS_FOR_CHAIN,
SOLANA_ERROR__WALLET__HAS_NO_CONNECTED_ACCOUNTS,
SOLANA_ERROR__WALLET__INVALID_SOLANA_CHAIN,
SolanaErrorCode,
} from './codes';
Expand Down Expand Up @@ -595,8 +595,8 @@ export const SolanaErrorMessages: Readonly<{
'Transaction version must be in the range [0, 127]. `$actualVersion` given',
[SOLANA_ERROR__WALLET__CHAIN_UNSUPPORTED]:
"The wallet '$walletName' does not support connecting to the chain `$chain`",
[SOLANA_ERROR__WALLET__EXPECTED_CONNECTED_ACCOUNTS]: "The wallet '$walletName' has no connected accounts",
[SOLANA_ERROR__WALLET__EXPECTED_CONNECTED_ACCOUNTS_FOR_CHAIN]:
"The wallet '$walletName' has no connected accounts for the chain `$chain`",
[SOLANA_ERROR__WALLET__HAS_NO_CONNECTED_ACCOUNTS]: "The wallet '$walletName' has no connected accounts",
[SOLANA_ERROR__WALLET__INVALID_SOLANA_CHAIN]: 'The chain `$chain` is not supported',
};
1 change: 1 addition & 0 deletions packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"@solana/addresses": "workspace:*",
"@solana/errors": "workspace:*",
"@solana/wallet-standard-chains": "^1.1.0",
"@solana/wallet-standard-features": "^1.2.0",
"@wallet-standard/base": "^1",
"@wallet-standard/features": "^1"
},
Expand Down
56 changes: 56 additions & 0 deletions packages/react/src/__tests__/useWalletAccounts-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { address } from '@solana/addresses';
import { Wallet } from '@wallet-standard/base';

import { renderHook } from '../test-renderer';
import { useWalletAccounts } from '../useWalletAccounts';
import { useWalletAccountsForCluster_INTERNAL_ONLY_DO_NOT_EXPORT } from '../useWalletAccountsForCluster_INTERNAL_ONLY_DO_NOT_EXPORT';

jest.mock('../useWalletAccountsForCluster_INTERNAL_ONLY_DO_NOT_EXPORT');

describe('useWalletAccounts', () => {
beforeEach(() => {
jest.mocked(useWalletAccountsForCluster_INTERNAL_ONLY_DO_NOT_EXPORT).mockReturnValue([
{
address: address('Httx5rAMNW3zA6NtXbgpnq22RdS9qK6rRBiNi8Msoc8a'),
chains: ['solana:devnet'],
features: ['solana:signMessage', 'solana:signAndSendTransaction'],
icon: 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEAAAAALAAAAAABAAEAAAIBAAA=',
label: 'My Test Account',
publicKey: new Uint8Array([
251, 6, 90, 16, 167, 85, 10, 206, 169, 88, 60, 180, 238, 49, 109, 108, 152, 101, 243, 178, 93, 190,
195, 73, 206, 97, 76, 131, 200, 38, 175, 179,
]),
},
{
address: address('Bho2jw9KVthJ4eXHu91gFT4pmTHWBP8kXnB1DkxvB9gx'),
chains: ['solana:devnet', 'solana:mainnet', 'solana:testnet'],
features: ['solana:signMessage', 'solana:signAndSendTransaction'],
icon: 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEAAAAALAAAAAABAAEAAAIBAAA=',
label: 'My Personal Account',
publicKey: new Uint8Array([
159, 8, 37, 221, 244, 25, 37, 131, 55, 40, 233, 211, 111, 235, 4, 250, 61, 170, 129, 95, 102, 117,
14, 137, 115, 154, 196, 5, 68, 224, 212, 45,
]),
},
]);
// Suppresses console output when an `ErrorBoundary` is hit.
// See https://stackoverflow.com/a/72632884/802047
jest.spyOn(console, 'error').mockImplementation();
jest.spyOn(console, 'warn').mockImplementation();
});
it("returns an array of display details about the wallet's accounts", () => {
const { result } = renderHook(() => useWalletAccounts([] as unknown as Wallet, 'devnet'));
expect(result.current).toStrictEqual([
{
address: address('Httx5rAMNW3zA6NtXbgpnq22RdS9qK6rRBiNi8Msoc8a'),
icon: 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEAAAAALAAAAAABAAEAAAIBAAA=',
label: 'My Test Account',
},
{
address: address('Bho2jw9KVthJ4eXHu91gFT4pmTHWBP8kXnB1DkxvB9gx'),
icon: 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEAAAAALAAAAAABAAEAAAIBAAA=',
label: 'My Personal Account',
},
]);
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { address } from '@solana/addresses';
import { SOLANA_ERROR__WALLET__HAS_NO_CONNECTED_ACCOUNTS, SolanaError } from '@solana/errors';
import { SOLANA_ERROR__WALLET__EXPECTED_CONNECTED_ACCOUNTS, SolanaError } from '@solana/errors';
import { SOLANA_CHAINS } from '@solana/wallet-standard-chains';
import { Wallet } from '@wallet-standard/base';
import { StandardEvents, StandardEventsListeners } from '@wallet-standard/features';
Expand Down Expand Up @@ -60,7 +60,7 @@ describe('useWalletAccounts_INTERNAL_ONLY_DO_NOT_EXPORT', () => {
);
expect(result.__type).toBe('error');
expect(result.current).toEqual(
new SolanaError(SOLANA_ERROR__WALLET__HAS_NO_CONNECTED_ACCOUNTS, { walletName: 'Mock Wallet' }),
new SolanaError(SOLANA_ERROR__WALLET__EXPECTED_CONNECTED_ACCOUNTS, { walletName: 'Mock Wallet' }),
);
});
it('updates when the accounts change', () => {
Expand Down Expand Up @@ -96,7 +96,7 @@ describe('useWalletAccounts_INTERNAL_ONLY_DO_NOT_EXPORT', () => {
});
expect(result.__type).toBe('error');
expect(result.current).toEqual(
new SolanaError(SOLANA_ERROR__WALLET__HAS_NO_CONNECTED_ACCOUNTS, { walletName: 'Mock Wallet' }),
new SolanaError(SOLANA_ERROR__WALLET__EXPECTED_CONNECTED_ACCOUNTS, { walletName: 'Mock Wallet' }),
);
});
});
2 changes: 1 addition & 1 deletion packages/react/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export default {};
export * from './useWalletAccounts';
20 changes: 20 additions & 0 deletions packages/react/src/useWalletAccounts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Address, address } from '@solana/addresses';
import { SolanaChain } from '@solana/wallet-standard-chains';
import { Wallet, WalletAccount } from '@wallet-standard/base';

import { ChainToCluster } from './chain';
import { useWalletAccountsForCluster_INTERNAL_ONLY_DO_NOT_EXPORT } from './useWalletAccountsForCluster_INTERNAL_ONLY_DO_NOT_EXPORT';

type WalletAccountDetails = Pick<WalletAccount, 'icon' | 'label'> & Readonly<{ address: Address }>;

export function useWalletAccounts<TWallet extends Wallet>(
wallet: TWallet,
cluster: ChainToCluster<SolanaChain & TWallet['chains'][number]>,
): readonly WalletAccountDetails[] {
const accounts = useWalletAccountsForCluster_INTERNAL_ONLY_DO_NOT_EXPORT(wallet, cluster);
return accounts.map(({ address: rawAddress, icon, label }) => ({
address: address(rawAddress),
icon,
label,
}));
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SOLANA_ERROR__WALLET__HAS_NO_CONNECTED_ACCOUNTS, SolanaError } from '@solana/errors';
import { SOLANA_ERROR__WALLET__EXPECTED_CONNECTED_ACCOUNTS, SolanaError } from '@solana/errors';
import { Wallet, WalletAccount, WalletWithFeatures } from '@wallet-standard/base';
import { StandardEvents, StandardEventsFeature } from '@wallet-standard/features';
import { useCallback, useRef, useSyncExternalStore } from 'react';
Expand Down Expand Up @@ -36,7 +36,7 @@ export function useWalletAccounts_INTERNAL_ONLY_DO_NOT_EXPORT<TWallet extends Wa
/* getServerSnapshot */ getAccountsServerSnapshot,
);
if (!accounts.length) {
throw new SolanaError(SOLANA_ERROR__WALLET__HAS_NO_CONNECTED_ACCOUNTS, {
throw new SolanaError(SOLANA_ERROR__WALLET__EXPECTED_CONNECTED_ACCOUNTS, {
walletName: wallet.name,
});
}
Expand Down
11 changes: 11 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 2dffcb0

Please sign in to comment.