Skip to content
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

feat: add listAccountTransactions method #41

Open
wants to merge 28 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
71c6132
feat: add `listAccountTransactions` method
danroc Sep 18, 2024
b0b91da
feat: add pagination when listing transactions
danroc Oct 9, 2024
49dfa67
feat: use CAIP-19 asset type and `StringNumber`
danroc Oct 10, 2024
30ef191
chore: move `Pagination` to `./utils`
danroc Oct 10, 2024
2816853
chore: dedupe `yarn.lock`
danroc Oct 10, 2024
76dcb15
feat: add `listAccountTransactions` to client
danroc Oct 10, 2024
f5cbdbc
feat: support both fungible and non-fungible assets
danroc Oct 10, 2024
3fd2755
feat: add `listAccountTransactions` to RPC handler
danroc Oct 10, 2024
dd7f43f
fix: don't use union in pagination
danroc Oct 10, 2024
a13ddb6
feat: add `TransactionType` and `TransactionStatus`
danroc Oct 10, 2024
3c762ca
test: add type test for `Transaction`
danroc Oct 11, 2024
0bc4dc4
chore: add missing period
danroc Oct 11, 2024
d034bf1
chore: update asset example in comment
danroc Oct 11, 2024
50a32af
chore: use different address in example
danroc Oct 11, 2024
cf36004
chore: update asset example in comment
danroc Oct 11, 2024
eab4dbd
chore: update comment
danroc Oct 11, 2024
71c436b
chore: add example of non-fungible token
danroc Oct 11, 2024
c925f93
chore: add example on how to use the `fungible` field
danroc Oct 11, 2024
4f8d6ef
chore: allow the pagination `next` to be `null`
danroc Oct 15, 2024
0e6f776
chore: use the `Paginated` type
danroc Oct 15, 2024
3b116ba
chore: add jsdoc
danroc Oct 16, 2024
c9f6df4
chore: add jsdoc
danroc Oct 16, 2024
ee4b6c9
test: remove invalid test
danroc Oct 16, 2024
189e7f3
doc: add description of the transaction types
danroc Oct 16, 2024
b96a9a7
docs: add jsdocs to Paginated
danroc Oct 18, 2024
ffb1c3a
test: add unit tests
danroc Oct 19, 2024
e2f92c5
chore: remove extra spaces
danroc Oct 19, 2024
f1735e2
chore: rename `TypedInfer` to `InferEquals`
danroc Oct 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/keyring-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"dependencies": {
"@metamask/snaps-sdk": "^6.7.0",
"@metamask/superstruct": "^3.1.0",
"@metamask/utils": "^9.2.1",
"@metamask/utils": "^9.3.0",
"@types/uuid": "^9.0.8",
"bech32": "^2.0.0",
"uuid": "^9.0.1",
Expand Down
178 changes: 178 additions & 0 deletions packages/keyring-api/src/KeyringClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,184 @@ describe('KeyringClient', () => {
});
});

describe('listAccountTransactions', () => {
it('returns an empty list of transactions', async () => {
const id = '49116980-0712-4fa5-b045-e4294f1d440e';
const expectedResponse = {
data: [],
next: null,
};

mockSender.send.mockResolvedValue(expectedResponse);
const transactions = await keyring.listAccountTransactions(id, {
limit: 10,
});

expect(mockSender.send).toHaveBeenCalledWith({
jsonrpc: '2.0',
id: expect.any(String),
method: 'keyring_listAccountTransactions',
params: { id, pagination: { limit: 10 } },
});

expect(transactions).toStrictEqual(expectedResponse);
});

it('returns a single page of transactions', async () => {
const id = '7bd967bd-9c4a-47cc-9725-75f0d2d8df9d';
const expectedResponse = {
data: [
{
id: 'c3d2e7a5-7c3c-4c1b-8d2e-7a57c3c41b8d',
account: '03a16e94-df42-46e6-affc-789bd58cf478',
chain: 'eip155:1',
type: 'send',
status: 'confirmed',
timestamp: 1716367781,
from: [],
to: [],
fee: {
amount: '0.001',
asset: {
fungible: true,
type: 'eip155:1/slip44:60',
unit: 'ETH',
},
},
},
{
id: '774a9423-9dd4-4b63-81a0-26884be90a35',
account: '03a16e94-df42-46e6-affc-789bd58cf478',
chain: 'eip155:1',
type: 'receive',
status: 'submitted',
timestamp: null,
from: [],
to: [],
fee: {
amount: '0',
asset: {
fungible: true,
type: 'eip155:1/slip44:60',
unit: 'ETH',
},
},
},
],
next: null,
};

mockSender.send.mockResolvedValue(expectedResponse);
const transactions = await keyring.listAccountTransactions(id, {
limit: 2,
});

expect(mockSender.send).toHaveBeenCalledWith({
jsonrpc: '2.0',
id: expect.any(String),
method: 'keyring_listAccountTransactions',
params: { id, pagination: { limit: 2 } },
});

expect(transactions).toStrictEqual(expectedResponse);
});

it('returns a page of transactions with next', async () => {
const id = '7bd967bd-9c4a-47cc-9725-75f0d2d8df9d';
const expectedResponse = {
data: [
{
id: 'c3d2e7a5-7c3c-4c1b-8d2e-7a57c3c41b8d',
account: '03a16e94-df42-46e6-affc-789bd58cf478',
chain: 'eip155:1',
type: 'send',
status: 'confirmed',
timestamp: 1716367781,
from: [],
to: [],
fee: {
amount: '0.001',
asset: {
fungible: true,
type: 'eip155:1/slip44:60',
unit: 'ETH',
},
},
},
{
id: '774a9423-9dd4-4b63-81a0-26884be90a35',
account: '03a16e94-df42-46e6-affc-789bd58cf478',
chain: 'eip155:1',
type: 'receive',
status: 'submitted',
timestamp: null,
from: [],
to: [],
fee: {
amount: '0',
asset: {
fungible: true,
type: 'eip155:1/slip44:60',
unit: 'ETH',
},
},
},
],
next: 'some-cursor',
};

mockSender.send.mockResolvedValue(expectedResponse);
const transactions = await keyring.listAccountTransactions(id, {
limit: 2,
});

expect(mockSender.send).toHaveBeenCalledWith({
jsonrpc: '2.0',
id: expect.any(String),
method: 'keyring_listAccountTransactions',
params: { id, pagination: { limit: 2 } },
});

expect(transactions).toStrictEqual(expectedResponse);
});

it('throwns an error when the fee has an invalid amount', async () => {
const id = '7bd967bd-9c4a-47cc-9725-75f0d2d8df9d';
const expectedResponse = {
data: [
{
id: 'c3d2e7a5-7c3c-4c1b-8d2e-7a57c3c41b8d',
account: '03a16e94-df42-46e6-affc-789bd58cf478',
chain: 'eip155:1',
type: 'send',
status: 'confirmed',
timestamp: 1716367781,
from: [],
to: [],
fee: {
amount: 'invalid-amount', // Should be a numeric string
asset: {
fungible: true,
type: 'eip155:1/slip44:60',
unit: 'ETH',
},
},
},
],
next: null,
};

mockSender.send.mockResolvedValue(expectedResponse);
await expect(
keyring.listAccountTransactions(id, {
limit: 2,
}),
).rejects.toThrow(
'At path: data.0.fee.amount -- Expected a value of type `StringNumber`, but received: `"invalid-amount"`',
);
});
});

describe('getAccountBalances', () => {
it('returns a valid response', async () => {
const assets = ['bip122:000000000019d6689c085ae165831e93/slip44:0'];
Expand Down
16 changes: 16 additions & 0 deletions packages/keyring-api/src/KeyringClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
KeyringResponse,
CaipAssetType,
Balance,
TransactionsPage,
} from './api';
import {
ApproveRequestResponseStruct,
Expand All @@ -21,6 +22,7 @@ import {
GetAccountResponseStruct,
GetRequestResponseStruct,
ListAccountsResponseStruct,
ListAccountTransactionsResponseStruct,
ListRequestsResponseStruct,
RejectRequestResponseStruct,
SubmitRequestResponseStruct,
Expand All @@ -29,6 +31,7 @@ import {
import { KeyringRpcMethod } from './internal/rpc';
import type { JsonRpcRequest } from './JsonRpcRequest';
import { strictMask } from './superstruct';
import type { Pagination } from './utils';

export type Sender = {
send(request: JsonRpcRequest): Promise<Json>;
Expand Down Expand Up @@ -104,6 +107,19 @@ export class KeyringClient implements Keyring {
);
}

async listAccountTransactions(
id: string,
pagination: Pagination,
): Promise<TransactionsPage> {
return strictMask(
await this.#send({
method: KeyringRpcMethod.ListAccountTransactions,
params: { id, pagination },
}),
ListAccountTransactionsResponseStruct,
);
}

async filterAccountChains(id: string, chains: string[]): Promise<string[]> {
return strictMask(
await this.#send({
Expand Down
1 change: 1 addition & 0 deletions packages/keyring-api/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './export';
export type * from './keyring';
export * from './request';
export * from './response';
export * from './transaction';
18 changes: 18 additions & 0 deletions packages/keyring-api/src/api/keyring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import type { CaipAssetType } from './caip';
import type { KeyringAccountData } from './export';
import type { KeyringRequest } from './request';
import type { KeyringResponse } from './response';
import type { Transaction } from './transaction';
import type { Paginated, Pagination } from '../utils';

/**
* Keyring interface.
Expand Down Expand Up @@ -46,6 +48,22 @@ export type Keyring = {
*/
createAccount(options?: Record<string, Json>): Promise<KeyringAccount>;

/**
* Lists the transactions of an account, paginated and ordered by the most
* recent first.
*
* The pagination options are used to limit the number of transactions in the
* response and to iterate over the results.
*
* @param id - The ID of the account to list the transactions for.
* @param pagination - The pagination options.
* @returns A promise that resolves to the next page of transactions.
*/
listAccountTransactions?(
id: string,
pagination: Pagination,
): Promise<Paginated<Transaction>>;

/**
* Retrieve the balances of a given account.
*
Expand Down
Loading
Loading