Skip to content

Commit 3688426

Browse files
authored
feat(provider): add getAccount method (#231)
* feat(provider): add getAccount method Signed-off-by: Norman <[email protected]> * chore: improve jsdoc format Signed-off-by: Norman <[email protected]> * chore: prettier Signed-off-by: Norman <[email protected]> --------- Signed-off-by: Norman <[email protected]>
1 parent dcc065c commit 3688426

File tree

6 files changed

+140
-0
lines changed

6 files changed

+140
-0
lines changed

src/provider/jsonrpc/jsonrpc.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,4 +374,45 @@ describe('JSON-RPC Provider', () => {
374374
}
375375
});
376376
});
377+
378+
describe('getAccount', () => {
379+
const validAccount: ABCIAccount = {
380+
BaseAccount: {
381+
address: 'random address',
382+
coins: '',
383+
public_key: { '@type': 'pktype', value: 'pk' },
384+
account_number: '10',
385+
sequence: '42',
386+
},
387+
};
388+
389+
test.each([
390+
[JSON.stringify(validAccount), validAccount], // account exists
391+
['null', null], // account doesn't exist
392+
])('case %#', async (response, expected) => {
393+
const mockABCIResponse: ABCIResponse = mock<ABCIResponse>();
394+
mockABCIResponse.response.ResponseBase = {
395+
Log: '',
396+
Info: '',
397+
Data: stringToBase64(response),
398+
Error: null,
399+
Events: null,
400+
};
401+
402+
mockedAxios.post.mockResolvedValue({
403+
data: newResponse<ABCIResponse>(mockABCIResponse),
404+
});
405+
406+
try {
407+
// Create the provider
408+
const provider = new JSONRPCProvider(mockURL);
409+
const account = await provider.getAccount('address');
410+
411+
expect(axios.post).toHaveBeenCalled();
412+
expect(account).toStrictEqual(expected);
413+
} catch (e) {
414+
expect((e as Error).message).toContain('account is not initialized');
415+
}
416+
});
417+
});
377418
});

src/provider/jsonrpc/jsonrpc.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
} from '../endpoints';
1010
import { Provider } from '../provider';
1111
import {
12+
ABCIAccount,
1213
ABCIErrorKey,
1314
ABCIResponse,
1415
BlockInfo,
@@ -23,6 +24,7 @@ import {
2324
TxResult,
2425
} from '../types';
2526
import {
27+
extractAccountFromResponse,
2628
extractAccountNumberFromResponse,
2729
extractBalanceFromResponse,
2830
extractSequenceFromResponse,
@@ -160,6 +162,22 @@ export class JSONRPCProvider implements Provider {
160162
);
161163
}
162164

165+
async getAccount(address: string, height?: number): Promise<ABCIAccount> {
166+
const abciResponse: ABCIResponse = await RestService.post<ABCIResponse>(
167+
this.baseURL,
168+
{
169+
request: newRequest(ABCIEndpoint.ABCI_QUERY, [
170+
`auth/accounts/${address}`,
171+
'',
172+
'0', // Height; not supported > 0 for now
173+
false,
174+
]),
175+
}
176+
);
177+
178+
return extractAccountFromResponse(abciResponse.response.ResponseBase.Data);
179+
}
180+
163181
async getStatus(): Promise<Status> {
164182
return await RestService.post<Status>(this.baseURL, {
165183
request: newRequest(CommonEndpoint.STATUS),

src/provider/provider.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
ABCIAccount,
23
BlockInfo,
34
BlockResult,
45
BroadcastAsGeneric,
@@ -33,6 +34,7 @@ export interface Provider {
3334
* @param {string} address the bech32 address of the account
3435
* @param {number} [height=0] the height for querying.
3536
* If omitted, the latest height is used.
37+
* @deprecated use {@link getAccount} instead
3638
*/
3739
getAccountSequence(address: string, height?: number): Promise<number>;
3840

@@ -42,9 +44,19 @@ export interface Provider {
4244
* @param {string} address the bech32 address of the account
4345
* @param {number} [height=0] the height for querying.
4446
* If omitted, the latest height is used
47+
* @deprecated use {@link getAccount} instead
4548
*/
4649
getAccountNumber(address: string, height?: number): Promise<number>;
4750

51+
/**
52+
* Fetches the account. Errors out if the account
53+
* is not initialized
54+
* @param {string} address the bech32 address of the account
55+
* @param {number} [height=0] the height for querying.
56+
* If omitted, the latest height is used
57+
*/
58+
getAccount(address: string, height?: number): Promise<ABCIAccount>;
59+
4860
/**
4961
* Fetches the block at the specific height, if any
5062
* @param {number} height the height for querying

src/provider/utility/provider.utility.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,28 @@ export const extractAccountNumberFromResponse = (
9696
}
9797
};
9898

99+
/**
100+
* Extracts the account from the ABCI response
101+
* @param {string | null} abciData the base64-encoded ABCI data
102+
*/
103+
export const extractAccountFromResponse = (
104+
abciData: string | null
105+
): ABCIAccount => {
106+
// Make sure the response is initialized
107+
if (!abciData) {
108+
throw new Error('account is not initialized');
109+
}
110+
111+
try {
112+
// Parse the account
113+
const account: ABCIAccount = parseABCI<ABCIAccount>(abciData);
114+
115+
return account;
116+
} catch (e) {
117+
throw new Error('account is not initialized');
118+
}
119+
};
120+
99121
/**
100122
* Extracts the simulate transaction response from the ABCI response value
101123
* @param {string | null} abciData the base64-encoded ResponseDeliverTx proto message

src/provider/websocket/ws.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,35 @@ describe('WS Provider', () => {
260260
});
261261
});
262262

263+
describe('getAccount', () => {
264+
const validAccount: ABCIAccount = {
265+
BaseAccount: {
266+
address: 'random address',
267+
coins: '',
268+
public_key: null,
269+
account_number: '10',
270+
sequence: '0',
271+
},
272+
};
273+
274+
test.each([
275+
[JSON.stringify(validAccount), validAccount], // account exists
276+
['null', null], // account doesn't exist
277+
])('case %#', async (response, expected) => {
278+
const mockResponse: ABCIResponse = mockABCIResponse(response);
279+
280+
// Set the response
281+
await setHandler<ABCIResponse>(mockResponse);
282+
283+
try {
284+
const account: ABCIAccount = await wsProvider.getAccount('address');
285+
expect(account).toStrictEqual(expected);
286+
} catch (e) {
287+
expect((e as Error).message).toContain('account is not initialized');
288+
}
289+
});
290+
});
291+
263292
describe('getBalance', () => {
264293
const denomination = 'atom';
265294
test.each([

src/provider/websocket/ws.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
} from '../endpoints';
99
import { Provider } from '../provider';
1010
import {
11+
ABCIAccount,
1112
ABCIErrorKey,
1213
ABCIResponse,
1314
BlockInfo,
@@ -23,6 +24,7 @@ import {
2324
TxResult,
2425
} from '../types';
2526
import {
27+
extractAccountFromResponse,
2628
extractAccountNumberFromResponse,
2729
extractBalanceFromResponse,
2830
extractSequenceFromResponse,
@@ -274,6 +276,22 @@ export class WSProvider implements Provider {
274276
);
275277
}
276278

279+
async getAccount(address: string, height?: number): Promise<ABCIAccount> {
280+
const response = await this.sendRequest<ABCIResponse>(
281+
newRequest(ABCIEndpoint.ABCI_QUERY, [
282+
`auth/accounts/${address}`,
283+
'',
284+
'0', // Height; not supported > 0 for now
285+
false,
286+
])
287+
);
288+
289+
// Parse the response
290+
const abciResponse = this.parseResponse<ABCIResponse>(response);
291+
292+
return extractAccountFromResponse(abciResponse.response.ResponseBase.Data);
293+
}
294+
277295
async getStatus(): Promise<Status> {
278296
const response = await this.sendRequest<Status>(
279297
newRequest(CommonEndpoint.STATUS)

0 commit comments

Comments
 (0)