From 28dccca70e5005ac75daab0e52ded57c43b60955 Mon Sep 17 00:00:00 2001
From: Matej Lubej
Date: Wed, 5 Jun 2024 11:55:17 +0200
Subject: [PATCH 01/12] Add getTransaction to explorer API
---
src/vendors/monitor.ts | 19 +-
src/vendors/oasisscan.ts | 31 ++-
.../oasisscan/.openapi-generator/FILES | 7 +-
src/vendors/oasisscan/apis/AccountsApi.ts | 18 +-
.../oasisscan/apis/OperationsEntityApi.ts | 62 ++++++
.../oasisscan/apis/OperationsListApi.ts | 12 +-
src/vendors/oasisscan/apis/RuntimeApi.ts | 12 +-
src/vendors/oasisscan/apis/index.ts | 1 +
.../oasisscan/models/InlineResponse2002.ts | 16 +-
.../oasisscan/models/InlineResponse2003.ts | 16 +-
.../models/InlineResponse2003Data.ts | 62 +++---
.../oasisscan/models/InlineResponse2004.ts | 16 +-
.../models/InlineResponse2005Data.ts | 16 +-
.../oasisscan/models/InlineResponse2006.ts | 72 +++++++
.../models/InlineResponse2006Data.ts | 96 ++++++++++
.../oasisscan/models/OperationsEntity.ts | 181 ++++++++++++++++++
src/vendors/oasisscan/models/index.ts | 6 +-
src/vendors/oasisscan/swagger.yml | 93 +++++++++
18 files changed, 635 insertions(+), 101 deletions(-)
create mode 100644 src/vendors/oasisscan/apis/OperationsEntityApi.ts
create mode 100644 src/vendors/oasisscan/models/InlineResponse2006.ts
create mode 100644 src/vendors/oasisscan/models/InlineResponse2006Data.ts
create mode 100644 src/vendors/oasisscan/models/OperationsEntity.ts
diff --git a/src/vendors/monitor.ts b/src/vendors/monitor.ts
index 0443c22016..612a3787e0 100644
--- a/src/vendors/monitor.ts
+++ b/src/vendors/monitor.ts
@@ -38,11 +38,16 @@ export function getMonitorAPIs(url: string | 'https://monitor.oasis.dev') {
return parseValidatorsList(validators)
}
- async function getTransactionsList(params: { accountId: string; limit: number }): Promise {
+ async function getTransaction({ hash }: { hash: string }) {
+ throw new Error('Not implemented')
+ }
+
+ async function getTransactionsList(params: { accountId: string; limit: number }) {
const transactions = await operations.getTransactionsList({
accountId: params.accountId,
limit: params.limit,
})
+
return parseTransactionsList(transactions)
}
@@ -64,7 +69,15 @@ export function getMonitorAPIs(url: string | 'https://monitor.oasis.dev') {
}
}
- return { accounts, blocks, getAccount, getAllValidators, getTransactionsList, getDelegations }
+ return {
+ accounts,
+ blocks,
+ getAccount,
+ getAllValidators,
+ getTransaction,
+ getTransactionsList,
+ getDelegations,
+ }
}
export function parseAccount(account: AccountsRow): Account {
@@ -79,6 +92,7 @@ export function parseAccount(account: AccountsRow): Account {
BigInt(account.delegations_balance) +
BigInt(account.debonding_delegations_balance)
).toString(),
+ nonce: BigInt(account.nonce ?? 0).toString(),
}
}
@@ -163,6 +177,7 @@ export function parseTransactionsList(transactionsList: OperationsRow[]): Transa
runtimeName: undefined,
runtimeId: undefined,
round: undefined,
+ nonce: undefined,
}
return parsed
})
diff --git a/src/vendors/oasisscan.ts b/src/vendors/oasisscan.ts
index 000cc3411e..baf6fef4c1 100644
--- a/src/vendors/oasisscan.ts
+++ b/src/vendors/oasisscan.ts
@@ -16,6 +16,8 @@ import {
OperationsRowMethodEnum,
ParaTimeCtxRowMethodEnum,
RuntimeTransactionInfoRow,
+ OperationsEntityApi,
+ OperationsEntity,
} from 'vendors/oasisscan/index'
import { throwAPIErrors } from './helpers'
@@ -28,6 +30,7 @@ export function getOasisscanAPIs(url: string | 'https://api.oasisscan.com/mainne
const accounts = new AccountsApi(explorerConfig)
const operations = new OperationsListApi(explorerConfig)
+ const operationsEntity = new OperationsEntityApi(explorerConfig)
const runtime = new RuntimeApi(explorerConfig)
async function getAccount(address: string): Promise {
@@ -50,7 +53,16 @@ export function getOasisscanAPIs(url: string | 'https://api.oasisscan.com/mainne
return data
}
- async function getTransactionsList(params: { accountId: string; limit: number }): Promise {
+ async function getTransaction({ hash }: { hash: string }) {
+ const transaction = await operationsEntity.getTransaction({
+ hash,
+ })
+
+ const [parsedTx] = parseTransactionsList([transaction.data])
+ return parsedTx
+ }
+
+ async function getTransactionsList(params: { accountId: string; limit: number }) {
const transactionsList = await operations.getTransactionsList({
address: params.accountId,
size: params.limit,
@@ -80,7 +92,15 @@ export function getOasisscanAPIs(url: string | 'https://api.oasisscan.com/mainne
}
}
- return { accounts, operations, getAccount, getAllValidators, getTransactionsList, getDelegations }
+ return {
+ accounts,
+ operations,
+ getAccount,
+ getAllValidators,
+ getTransaction,
+ getTransactionsList,
+ getDelegations,
+ }
}
export function parseAccount(account: AccountsRow): Account {
@@ -94,6 +114,7 @@ export function parseAccount(account: AccountsRow): Account {
delegations: parseRoseStringToBaseUnitString(account.escrow),
debonding: parseRoseStringToBaseUnitString(account.debonding),
total: parseRoseStringToBaseUnitString(account.total),
+ nonce: BigInt(account.nonce ?? 0).toString(),
}
}
@@ -150,7 +171,9 @@ export const transactionMethodMap: {
[ParaTimeCtxRowMethodEnum.ConsensusAccount]: TransactionType.ConsensusAccount,
}
-export function parseTransactionsList(list: (OperationsRow | RuntimeTransactionInfoRow)[]): Transaction[] {
+export function parseTransactionsList(
+ list: (OperationsRow | RuntimeTransactionInfoRow | OperationsEntity)[],
+): Transaction[] {
return list.map(t => {
if ('ctx' in t) {
const parsed: Transaction = {
@@ -166,6 +189,7 @@ export function parseTransactionsList(list: (OperationsRow | RuntimeTransactionI
runtimeName: t.runtimeName,
runtimeId: t.runtimeId,
round: t.round,
+ nonce: undefined,
}
return parsed
} else {
@@ -182,6 +206,7 @@ export function parseTransactionsList(list: (OperationsRow | RuntimeTransactionI
runtimeName: undefined,
runtimeId: undefined,
round: undefined,
+ nonce: BigInt((t as OperationsEntity).nonce ?? 0).toString()
}
return parsed
}
diff --git a/src/vendors/oasisscan/.openapi-generator/FILES b/src/vendors/oasisscan/.openapi-generator/FILES
index 7c34faa8fe..fd45adf79f 100644
--- a/src/vendors/oasisscan/.openapi-generator/FILES
+++ b/src/vendors/oasisscan/.openapi-generator/FILES
@@ -1,4 +1,5 @@
apis/AccountsApi.ts
+apis/OperationsEntityApi.ts
apis/OperationsListApi.ts
apis/RuntimeApi.ts
apis/index.ts
@@ -11,12 +12,14 @@ models/InlineResponse200.ts
models/InlineResponse2001.ts
models/InlineResponse2001Data.ts
models/InlineResponse2002.ts
-models/InlineResponse2002Data.ts
models/InlineResponse2003.ts
+models/InlineResponse2003Data.ts
models/InlineResponse2004.ts
-models/InlineResponse2004Data.ts
models/InlineResponse2005.ts
models/InlineResponse2005Data.ts
+models/InlineResponse2006.ts
+models/InlineResponse2006Data.ts
+models/OperationsEntity.ts
models/OperationsRow.ts
models/ParaTimeCtxRow.ts
models/RuntimeTransactionInfoRow.ts
diff --git a/src/vendors/oasisscan/apis/AccountsApi.ts b/src/vendors/oasisscan/apis/AccountsApi.ts
index 0a42162c93..b3e0dca525 100644
--- a/src/vendors/oasisscan/apis/AccountsApi.ts
+++ b/src/vendors/oasisscan/apis/AccountsApi.ts
@@ -21,12 +21,12 @@ import {
InlineResponse2001,
InlineResponse2001FromJSON,
InlineResponse2001ToJSON,
- InlineResponse2004,
- InlineResponse2004FromJSON,
- InlineResponse2004ToJSON,
InlineResponse2005,
InlineResponse2005FromJSON,
InlineResponse2005ToJSON,
+ InlineResponse2006,
+ InlineResponse2006FromJSON,
+ InlineResponse2006ToJSON,
} from '../models';
export interface GetAccountRequest {
@@ -87,7 +87,7 @@ export class AccountsApi extends runtime.BaseAPI {
/**
*/
- async getDebondingDelegationsRaw(requestParameters: GetDebondingDelegationsRequest): Promise> {
+ async getDebondingDelegationsRaw(requestParameters: GetDebondingDelegationsRequest): Promise> {
const queryParameters: any = {};
if (requestParameters.size !== undefined) {
@@ -115,19 +115,19 @@ export class AccountsApi extends runtime.BaseAPI {
query: queryParameters,
});
- return new runtime.JSONApiResponse(response, (jsonValue) => InlineResponse2005FromJSON(jsonValue));
+ return new runtime.JSONApiResponse(response, (jsonValue) => InlineResponse2006FromJSON(jsonValue));
}
/**
*/
- async getDebondingDelegations(requestParameters: GetDebondingDelegationsRequest): Promise {
+ async getDebondingDelegations(requestParameters: GetDebondingDelegationsRequest): Promise {
const response = await this.getDebondingDelegationsRaw(requestParameters);
return await response.value();
}
/**
*/
- async getDelegationsRaw(requestParameters: GetDelegationsRequest): Promise> {
+ async getDelegationsRaw(requestParameters: GetDelegationsRequest): Promise> {
const queryParameters: any = {};
if (requestParameters.size !== undefined) {
@@ -159,12 +159,12 @@ export class AccountsApi extends runtime.BaseAPI {
query: queryParameters,
});
- return new runtime.JSONApiResponse(response, (jsonValue) => InlineResponse2004FromJSON(jsonValue));
+ return new runtime.JSONApiResponse(response, (jsonValue) => InlineResponse2005FromJSON(jsonValue));
}
/**
*/
- async getDelegations(requestParameters: GetDelegationsRequest): Promise {
+ async getDelegations(requestParameters: GetDelegationsRequest): Promise {
const response = await this.getDelegationsRaw(requestParameters);
return await response.value();
}
diff --git a/src/vendors/oasisscan/apis/OperationsEntityApi.ts b/src/vendors/oasisscan/apis/OperationsEntityApi.ts
new file mode 100644
index 0000000000..3444c8151e
--- /dev/null
+++ b/src/vendors/oasisscan/apis/OperationsEntityApi.ts
@@ -0,0 +1,62 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Oasisscan API
+ * https://github.com/bitcat365/oasisscan-backend#readme https://api.oasisscan.com/mainnet/swagger-ui/#/ https://api.oasisscan.com/mainnet/v2/api-docs
+ *
+ * The version of the OpenAPI document: 1
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+import * as runtime from '../runtime';
+import {
+ InlineResponse2002,
+ InlineResponse2002FromJSON,
+ InlineResponse2002ToJSON,
+} from '../models';
+
+export interface GetTransactionRequest {
+ hash: string;
+}
+
+/**
+ *
+ */
+export class OperationsEntityApi extends runtime.BaseAPI {
+
+ /**
+ * TransactionDetail
+ */
+ async getTransactionRaw(requestParameters: GetTransactionRequest): Promise> {
+ if (requestParameters.hash === null || requestParameters.hash === undefined) {
+ throw new runtime.RequiredError('hash','Required parameter requestParameters.hash was null or undefined when calling getTransaction.');
+ }
+
+ const queryParameters: any = {};
+
+ const headerParameters: runtime.HTTPHeaders = {};
+
+ const response = await this.request({
+ path: `/chain/transaction/{hash}`.replace(`{${"hash"}}`, encodeURIComponent(String(requestParameters.hash))),
+ method: 'GET',
+ headers: headerParameters,
+ query: queryParameters,
+ });
+
+ return new runtime.JSONApiResponse(response, (jsonValue) => InlineResponse2002FromJSON(jsonValue));
+ }
+
+ /**
+ * TransactionDetail
+ */
+ async getTransaction(requestParameters: GetTransactionRequest): Promise {
+ const response = await this.getTransactionRaw(requestParameters);
+ return await response.value();
+ }
+
+}
diff --git a/src/vendors/oasisscan/apis/OperationsListApi.ts b/src/vendors/oasisscan/apis/OperationsListApi.ts
index c0e1cfb79a..832f615d75 100644
--- a/src/vendors/oasisscan/apis/OperationsListApi.ts
+++ b/src/vendors/oasisscan/apis/OperationsListApi.ts
@@ -15,9 +15,9 @@
import * as runtime from '../runtime';
import {
- InlineResponse2002,
- InlineResponse2002FromJSON,
- InlineResponse2002ToJSON,
+ InlineResponse2003,
+ InlineResponse2003FromJSON,
+ InlineResponse2003ToJSON,
} from '../models';
export interface GetTransactionsListRequest {
@@ -36,7 +36,7 @@ export class OperationsListApi extends runtime.BaseAPI {
/**
*/
- async getTransactionsListRaw(requestParameters: GetTransactionsListRequest): Promise> {
+ async getTransactionsListRaw(requestParameters: GetTransactionsListRequest): Promise> {
if (requestParameters.runtime === null || requestParameters.runtime === undefined) {
throw new runtime.RequiredError('runtime','Required parameter requestParameters.runtime was null or undefined when calling getTransactionsList.');
}
@@ -76,12 +76,12 @@ export class OperationsListApi extends runtime.BaseAPI {
query: queryParameters,
});
- return new runtime.JSONApiResponse(response, (jsonValue) => InlineResponse2002FromJSON(jsonValue));
+ return new runtime.JSONApiResponse(response, (jsonValue) => InlineResponse2003FromJSON(jsonValue));
}
/**
*/
- async getTransactionsList(requestParameters: GetTransactionsListRequest): Promise {
+ async getTransactionsList(requestParameters: GetTransactionsListRequest): Promise {
const response = await this.getTransactionsListRaw(requestParameters);
return await response.value();
}
diff --git a/src/vendors/oasisscan/apis/RuntimeApi.ts b/src/vendors/oasisscan/apis/RuntimeApi.ts
index e1f4f9840e..8cb2b456c6 100644
--- a/src/vendors/oasisscan/apis/RuntimeApi.ts
+++ b/src/vendors/oasisscan/apis/RuntimeApi.ts
@@ -15,9 +15,9 @@
import * as runtime from '../runtime';
import {
- InlineResponse2003,
- InlineResponse2003FromJSON,
- InlineResponse2003ToJSON,
+ InlineResponse2004,
+ InlineResponse2004FromJSON,
+ InlineResponse2004ToJSON,
} from '../models';
export interface GetRuntimeTransactionInfoRequest {
@@ -33,7 +33,7 @@ export class RuntimeApi extends runtime.BaseAPI {
/**
*/
- async getRuntimeTransactionInfoRaw(requestParameters: GetRuntimeTransactionInfoRequest): Promise> {
+ async getRuntimeTransactionInfoRaw(requestParameters: GetRuntimeTransactionInfoRequest): Promise> {
if (requestParameters.id === null || requestParameters.id === undefined) {
throw new runtime.RequiredError('id','Required parameter requestParameters.id was null or undefined when calling getRuntimeTransactionInfo.');
}
@@ -65,12 +65,12 @@ export class RuntimeApi extends runtime.BaseAPI {
query: queryParameters,
});
- return new runtime.JSONApiResponse(response, (jsonValue) => InlineResponse2003FromJSON(jsonValue));
+ return new runtime.JSONApiResponse(response, (jsonValue) => InlineResponse2004FromJSON(jsonValue));
}
/**
*/
- async getRuntimeTransactionInfo(requestParameters: GetRuntimeTransactionInfoRequest): Promise {
+ async getRuntimeTransactionInfo(requestParameters: GetRuntimeTransactionInfoRequest): Promise {
const response = await this.getRuntimeTransactionInfoRaw(requestParameters);
return await response.value();
}
diff --git a/src/vendors/oasisscan/apis/index.ts b/src/vendors/oasisscan/apis/index.ts
index 21abe73b40..27bdb9f218 100644
--- a/src/vendors/oasisscan/apis/index.ts
+++ b/src/vendors/oasisscan/apis/index.ts
@@ -1,3 +1,4 @@
export * from './AccountsApi'
+export * from './OperationsEntityApi'
export * from './OperationsListApi'
export * from './RuntimeApi'
diff --git a/src/vendors/oasisscan/models/InlineResponse2002.ts b/src/vendors/oasisscan/models/InlineResponse2002.ts
index 42ebf07b78..d5188003d2 100644
--- a/src/vendors/oasisscan/models/InlineResponse2002.ts
+++ b/src/vendors/oasisscan/models/InlineResponse2002.ts
@@ -14,10 +14,10 @@
import { exists, mapValues } from '../runtime';
import {
- InlineResponse2002Data,
- InlineResponse2002DataFromJSON,
- InlineResponse2002DataFromJSONTyped,
- InlineResponse2002DataToJSON,
+ OperationsEntity,
+ OperationsEntityFromJSON,
+ OperationsEntityFromJSONTyped,
+ OperationsEntityToJSON,
} from './';
/**
@@ -34,10 +34,10 @@ export interface InlineResponse2002 {
code: number;
/**
*
- * @type {InlineResponse2002Data}
+ * @type {OperationsEntity}
* @memberof InlineResponse2002
*/
- data: InlineResponse2002Data;
+ data: OperationsEntity;
}
export function InlineResponse2002FromJSON(json: any): InlineResponse2002 {
@@ -51,7 +51,7 @@ export function InlineResponse2002FromJSONTyped(json: any, ignoreDiscriminator:
return {
'code': json['code'],
- 'data': InlineResponse2002DataFromJSON(json['data']),
+ 'data': OperationsEntityFromJSON(json['data']),
};
}
@@ -65,7 +65,7 @@ export function InlineResponse2002ToJSON(value?: InlineResponse2002 | null): any
return {
'code': value.code,
- 'data': InlineResponse2002DataToJSON(value.data),
+ 'data': OperationsEntityToJSON(value.data),
};
}
diff --git a/src/vendors/oasisscan/models/InlineResponse2003.ts b/src/vendors/oasisscan/models/InlineResponse2003.ts
index c20fbe54c4..43a693d5c6 100644
--- a/src/vendors/oasisscan/models/InlineResponse2003.ts
+++ b/src/vendors/oasisscan/models/InlineResponse2003.ts
@@ -14,10 +14,10 @@
import { exists, mapValues } from '../runtime';
import {
- RuntimeTransactionInfoRow,
- RuntimeTransactionInfoRowFromJSON,
- RuntimeTransactionInfoRowFromJSONTyped,
- RuntimeTransactionInfoRowToJSON,
+ InlineResponse2003Data,
+ InlineResponse2003DataFromJSON,
+ InlineResponse2003DataFromJSONTyped,
+ InlineResponse2003DataToJSON,
} from './';
/**
@@ -34,10 +34,10 @@ export interface InlineResponse2003 {
code: number;
/**
*
- * @type {RuntimeTransactionInfoRow}
+ * @type {InlineResponse2003Data}
* @memberof InlineResponse2003
*/
- data: RuntimeTransactionInfoRow;
+ data: InlineResponse2003Data;
}
export function InlineResponse2003FromJSON(json: any): InlineResponse2003 {
@@ -51,7 +51,7 @@ export function InlineResponse2003FromJSONTyped(json: any, ignoreDiscriminator:
return {
'code': json['code'],
- 'data': json['data'],
+ 'data': InlineResponse2003DataFromJSON(json['data']),
};
}
@@ -65,7 +65,7 @@ export function InlineResponse2003ToJSON(value?: InlineResponse2003 | null): any
return {
'code': value.code,
- 'data': value.data,
+ 'data': InlineResponse2003DataToJSON(value.data),
};
}
diff --git a/src/vendors/oasisscan/models/InlineResponse2003Data.ts b/src/vendors/oasisscan/models/InlineResponse2003Data.ts
index 1f99da7c7b..a1c0a93480 100644
--- a/src/vendors/oasisscan/models/InlineResponse2003Data.ts
+++ b/src/vendors/oasisscan/models/InlineResponse2003Data.ts
@@ -2,7 +2,7 @@
/* eslint-disable */
/**
* Oasisscan API
- * https://github.com/bitcat365/oasisscan-backend#readme
+ * https://github.com/bitcat365/oasisscan-backend#readme https://api.oasisscan.com/mainnet/swagger-ui/#/ https://api.oasisscan.com/mainnet/v2/api-docs
*
* The version of the OpenAPI document: 1
*
@@ -14,10 +14,10 @@
import { exists, mapValues } from '../runtime';
import {
- ParaTimeCtxRow,
- ParaTimeCtxRowFromJSON,
- ParaTimeCtxRowFromJSONTyped,
- ParaTimeCtxRowToJSON,
+ OperationsRow,
+ OperationsRowFromJSON,
+ OperationsRowFromJSONTyped,
+ OperationsRowToJSON,
} from './';
/**
@@ -28,46 +28,34 @@ import {
export interface InlineResponse2003Data {
/**
*
- * @type {ParaTimeCtxRow}
+ * @type {Array}
* @memberof InlineResponse2003Data
*/
- ctx: ParaTimeCtxRow;
- /**
- *
- * @type {string}
- * @memberof InlineResponse2003Data
- */
- runtimeName: string;
- /**
- *
- * @type {string}
- * @memberof InlineResponse2003Data
- */
- runtimeId: string;
+ list: Array;
/**
*
* @type {number}
* @memberof InlineResponse2003Data
*/
- round: number;
+ page: number;
/**
*
* @type {number}
* @memberof InlineResponse2003Data
*/
- timestamp?: number;
+ size: number;
/**
*
- * @type {string}
+ * @type {number}
* @memberof InlineResponse2003Data
*/
- txHash?: string;
+ maxPage: number;
/**
*
- * @type {boolean}
+ * @type {number}
* @memberof InlineResponse2003Data
*/
- result?: boolean;
+ totalSize: number;
}
export function InlineResponse2003DataFromJSON(json: any): InlineResponse2003Data {
@@ -80,13 +68,11 @@ export function InlineResponse2003DataFromJSONTyped(json: any, ignoreDiscriminat
}
return {
- 'ctx': ParaTimeCtxRowFromJSON(json['ctx']),
- 'runtimeName': json['runtimeName'],
- 'runtimeId': json['runtimeId'],
- 'round': json['round'],
- 'timestamp': !exists(json, 'timestamp') ? undefined : json['timestamp'],
- 'txHash': !exists(json, 'txHash') ? undefined : json['txHash'],
- 'result': !exists(json, 'result') ? undefined : json['result'],
+ 'list': ((json['list'] as Array).map(OperationsRowFromJSON)),
+ 'page': json['page'],
+ 'size': json['size'],
+ 'maxPage': json['maxPage'],
+ 'totalSize': json['totalSize'],
};
}
@@ -99,13 +85,11 @@ export function InlineResponse2003DataToJSON(value?: InlineResponse2003Data | nu
}
return {
- 'ctx': ParaTimeCtxRowToJSON(value.ctx),
- 'runtimeName': value.runtimeName,
- 'runtimeId': value.runtimeId,
- 'round': value.round,
- 'timestamp': value.timestamp,
- 'txHash': value.txHash,
- 'result': value.result,
+ 'list': ((value.list as Array).map(OperationsRowToJSON)),
+ 'page': value.page,
+ 'size': value.size,
+ 'maxPage': value.maxPage,
+ 'totalSize': value.totalSize,
};
}
diff --git a/src/vendors/oasisscan/models/InlineResponse2004.ts b/src/vendors/oasisscan/models/InlineResponse2004.ts
index 89548cba12..f9c5cc44a1 100644
--- a/src/vendors/oasisscan/models/InlineResponse2004.ts
+++ b/src/vendors/oasisscan/models/InlineResponse2004.ts
@@ -14,10 +14,10 @@
import { exists, mapValues } from '../runtime';
import {
- InlineResponse2004Data,
- InlineResponse2004DataFromJSON,
- InlineResponse2004DataFromJSONTyped,
- InlineResponse2004DataToJSON,
+ RuntimeTransactionInfoRow,
+ RuntimeTransactionInfoRowFromJSON,
+ RuntimeTransactionInfoRowFromJSONTyped,
+ RuntimeTransactionInfoRowToJSON,
} from './';
/**
@@ -34,10 +34,10 @@ export interface InlineResponse2004 {
code: number;
/**
*
- * @type {InlineResponse2004Data}
+ * @type {RuntimeTransactionInfoRow}
* @memberof InlineResponse2004
*/
- data: InlineResponse2004Data;
+ data: RuntimeTransactionInfoRow;
}
export function InlineResponse2004FromJSON(json: any): InlineResponse2004 {
@@ -51,7 +51,7 @@ export function InlineResponse2004FromJSONTyped(json: any, ignoreDiscriminator:
return {
'code': json['code'],
- 'data': InlineResponse2004DataFromJSON(json['data']),
+ 'data': json['data'],
};
}
@@ -65,7 +65,7 @@ export function InlineResponse2004ToJSON(value?: InlineResponse2004 | null): any
return {
'code': value.code,
- 'data': InlineResponse2004DataToJSON(value.data),
+ 'data': value.data,
};
}
diff --git a/src/vendors/oasisscan/models/InlineResponse2005Data.ts b/src/vendors/oasisscan/models/InlineResponse2005Data.ts
index 73723153b9..3f07379568 100644
--- a/src/vendors/oasisscan/models/InlineResponse2005Data.ts
+++ b/src/vendors/oasisscan/models/InlineResponse2005Data.ts
@@ -14,10 +14,10 @@
import { exists, mapValues } from '../runtime';
import {
- DebondingDelegationRow,
- DebondingDelegationRowFromJSON,
- DebondingDelegationRowFromJSONTyped,
- DebondingDelegationRowToJSON,
+ DelegationRow,
+ DelegationRowFromJSON,
+ DelegationRowFromJSONTyped,
+ DelegationRowToJSON,
} from './';
/**
@@ -28,10 +28,10 @@ import {
export interface InlineResponse2005Data {
/**
*
- * @type {Array}
+ * @type {Array}
* @memberof InlineResponse2005Data
*/
- list: Array;
+ list: Array;
/**
*
* @type {number}
@@ -68,7 +68,7 @@ export function InlineResponse2005DataFromJSONTyped(json: any, ignoreDiscriminat
}
return {
- 'list': ((json['list'] as Array).map(DebondingDelegationRowFromJSON)),
+ 'list': ((json['list'] as Array).map(DelegationRowFromJSON)),
'page': json['page'],
'size': json['size'],
'maxPage': json['maxPage'],
@@ -85,7 +85,7 @@ export function InlineResponse2005DataToJSON(value?: InlineResponse2005Data | nu
}
return {
- 'list': ((value.list as Array).map(DebondingDelegationRowToJSON)),
+ 'list': ((value.list as Array).map(DelegationRowToJSON)),
'page': value.page,
'size': value.size,
'maxPage': value.maxPage,
diff --git a/src/vendors/oasisscan/models/InlineResponse2006.ts b/src/vendors/oasisscan/models/InlineResponse2006.ts
new file mode 100644
index 0000000000..939b0b6448
--- /dev/null
+++ b/src/vendors/oasisscan/models/InlineResponse2006.ts
@@ -0,0 +1,72 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Oasisscan API
+ * https://github.com/bitcat365/oasisscan-backend#readme https://api.oasisscan.com/mainnet/swagger-ui/#/ https://api.oasisscan.com/mainnet/v2/api-docs
+ *
+ * The version of the OpenAPI document: 1
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+import { exists, mapValues } from '../runtime';
+import {
+ InlineResponse2006Data,
+ InlineResponse2006DataFromJSON,
+ InlineResponse2006DataFromJSONTyped,
+ InlineResponse2006DataToJSON,
+} from './';
+
+/**
+ *
+ * @export
+ * @interface InlineResponse2006
+ */
+export interface InlineResponse2006 {
+ /**
+ *
+ * @type {number}
+ * @memberof InlineResponse2006
+ */
+ code: number;
+ /**
+ *
+ * @type {InlineResponse2006Data}
+ * @memberof InlineResponse2006
+ */
+ data: InlineResponse2006Data;
+}
+
+export function InlineResponse2006FromJSON(json: any): InlineResponse2006 {
+ return InlineResponse2006FromJSONTyped(json, false);
+}
+
+export function InlineResponse2006FromJSONTyped(json: any, ignoreDiscriminator: boolean): InlineResponse2006 {
+ if ((json === undefined) || (json === null)) {
+ return json;
+ }
+ return {
+
+ 'code': json['code'],
+ 'data': InlineResponse2006DataFromJSON(json['data']),
+ };
+}
+
+export function InlineResponse2006ToJSON(value?: InlineResponse2006 | null): any {
+ if (value === undefined) {
+ return undefined;
+ }
+ if (value === null) {
+ return null;
+ }
+ return {
+
+ 'code': value.code,
+ 'data': InlineResponse2006DataToJSON(value.data),
+ };
+}
+
+
diff --git a/src/vendors/oasisscan/models/InlineResponse2006Data.ts b/src/vendors/oasisscan/models/InlineResponse2006Data.ts
new file mode 100644
index 0000000000..8f752b7d29
--- /dev/null
+++ b/src/vendors/oasisscan/models/InlineResponse2006Data.ts
@@ -0,0 +1,96 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Oasisscan API
+ * https://github.com/bitcat365/oasisscan-backend#readme https://api.oasisscan.com/mainnet/swagger-ui/#/ https://api.oasisscan.com/mainnet/v2/api-docs
+ *
+ * The version of the OpenAPI document: 1
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+import { exists, mapValues } from '../runtime';
+import {
+ DebondingDelegationRow,
+ DebondingDelegationRowFromJSON,
+ DebondingDelegationRowFromJSONTyped,
+ DebondingDelegationRowToJSON,
+} from './';
+
+/**
+ *
+ * @export
+ * @interface InlineResponse2006Data
+ */
+export interface InlineResponse2006Data {
+ /**
+ *
+ * @type {Array}
+ * @memberof InlineResponse2006Data
+ */
+ list: Array;
+ /**
+ *
+ * @type {number}
+ * @memberof InlineResponse2006Data
+ */
+ page: number;
+ /**
+ *
+ * @type {number}
+ * @memberof InlineResponse2006Data
+ */
+ size: number;
+ /**
+ *
+ * @type {number}
+ * @memberof InlineResponse2006Data
+ */
+ maxPage: number;
+ /**
+ *
+ * @type {number}
+ * @memberof InlineResponse2006Data
+ */
+ totalSize: number;
+}
+
+export function InlineResponse2006DataFromJSON(json: any): InlineResponse2006Data {
+ return InlineResponse2006DataFromJSONTyped(json, false);
+}
+
+export function InlineResponse2006DataFromJSONTyped(json: any, ignoreDiscriminator: boolean): InlineResponse2006Data {
+ if ((json === undefined) || (json === null)) {
+ return json;
+ }
+ return {
+
+ 'list': ((json['list'] as Array).map(DebondingDelegationRowFromJSON)),
+ 'page': json['page'],
+ 'size': json['size'],
+ 'maxPage': json['maxPage'],
+ 'totalSize': json['totalSize'],
+ };
+}
+
+export function InlineResponse2006DataToJSON(value?: InlineResponse2006Data | null): any {
+ if (value === undefined) {
+ return undefined;
+ }
+ if (value === null) {
+ return null;
+ }
+ return {
+
+ 'list': ((value.list as Array).map(DebondingDelegationRowToJSON)),
+ 'page': value.page,
+ 'size': value.size,
+ 'maxPage': value.maxPage,
+ 'totalSize': value.totalSize,
+ };
+}
+
+
diff --git a/src/vendors/oasisscan/models/OperationsEntity.ts b/src/vendors/oasisscan/models/OperationsEntity.ts
new file mode 100644
index 0000000000..ab7158a0c6
--- /dev/null
+++ b/src/vendors/oasisscan/models/OperationsEntity.ts
@@ -0,0 +1,181 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Oasisscan API
+ * https://github.com/bitcat365/oasisscan-backend#readme https://api.oasisscan.com/mainnet/swagger-ui/#/ https://api.oasisscan.com/mainnet/v2/api-docs
+ *
+ * The version of the OpenAPI document: 1
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+import { exists, mapValues } from '../runtime';
+/**
+ *
+ * @export
+ * @interface OperationsEntity
+ */
+export interface OperationsEntity {
+ /**
+ *
+ * @type {string}
+ * @memberof OperationsEntity
+ */
+ txHash: string;
+ /**
+ *
+ * @type {number}
+ * @memberof OperationsEntity
+ */
+ height: number;
+ /**
+ *
+ * @type {string}
+ * @memberof OperationsEntity
+ */
+ method: OperationsEntityMethodEnum;
+ /**
+ *
+ * @type {string}
+ * @memberof OperationsEntity
+ */
+ fee: string;
+ /**
+ *
+ * @type {string}
+ * @memberof OperationsEntity
+ */
+ amount: string | null;
+ /**
+ *
+ * @type {string}
+ * @memberof OperationsEntity
+ */
+ shares?: string | null;
+ /**
+ *
+ * @type {number}
+ * @memberof OperationsEntity
+ */
+ timestamp: number;
+ /**
+ *
+ * @type {number}
+ * @memberof OperationsEntity
+ */
+ time: number;
+ /**
+ *
+ * @type {boolean}
+ * @memberof OperationsEntity
+ */
+ status: boolean;
+ /**
+ *
+ * @type {string}
+ * @memberof OperationsEntity
+ */
+ from: string;
+ /**
+ *
+ * @type {string}
+ * @memberof OperationsEntity
+ */
+ to: string | null;
+ /**
+ *
+ * @type {string}
+ * @memberof OperationsEntity
+ */
+ errorMessage?: string | null;
+ /**
+ *
+ * @type {number}
+ * @memberof OperationsEntity
+ */
+ nonce: number;
+}
+
+/**
+* @export
+* @enum {string}
+*/
+export enum OperationsEntityMethodEnum {
+ StakingTransfer = 'staking.Transfer',
+ StakingAddEscrow = 'staking.AddEscrow',
+ StakingReclaimEscrow = 'staking.ReclaimEscrow',
+ StakingAmendCommissionSchedule = 'staking.AmendCommissionSchedule',
+ StakingAllow = 'staking.Allow',
+ StakingWithdraw = 'staking.Withdraw',
+ StakingBurn = 'staking.Burn',
+ RoothashExecutorCommit = 'roothash.ExecutorCommit',
+ RoothashExecutorProposerTimeout = 'roothash.ExecutorProposerTimeout',
+ RoothashSubmitMsg = 'roothash.SubmitMsg',
+ RegistryDeregisterEntity = 'registry.DeregisterEntity',
+ RegistryRegisterEntity = 'registry.RegisterEntity',
+ RegistryRegisterNode = 'registry.RegisterNode',
+ RegistryRegisterRuntime = 'registry.RegisterRuntime',
+ RegistryUnfreezeNode = 'registry.UnfreezeNode',
+ GovernanceCastVote = 'governance.CastVote',
+ GovernanceSubmitProposal = 'governance.SubmitProposal',
+ BeaconPvssCommit = 'beacon.PVSSCommit',
+ BeaconPvssReveal = 'beacon.PVSSReveal',
+ BeaconVrfProve = 'beacon.VRFProve',
+ ConsensusMeta = 'consensus.Meta'
+}
+
+export function OperationsEntityFromJSON(json: any): OperationsEntity {
+ return OperationsEntityFromJSONTyped(json, false);
+}
+
+export function OperationsEntityFromJSONTyped(json: any, ignoreDiscriminator: boolean): OperationsEntity {
+ if ((json === undefined) || (json === null)) {
+ return json;
+ }
+ return {
+
+ 'txHash': json['txHash'],
+ 'height': json['height'],
+ 'method': json['method'],
+ 'fee': json['fee'],
+ 'amount': json['amount'],
+ 'shares': !exists(json, 'shares') ? undefined : json['shares'],
+ 'timestamp': json['timestamp'],
+ 'time': json['time'],
+ 'status': json['status'],
+ 'from': json['from'],
+ 'to': json['to'],
+ 'errorMessage': !exists(json, 'errorMessage') ? undefined : json['errorMessage'],
+ 'nonce': json['nonce'],
+ };
+}
+
+export function OperationsEntityToJSON(value?: OperationsEntity | null): any {
+ if (value === undefined) {
+ return undefined;
+ }
+ if (value === null) {
+ return null;
+ }
+ return {
+
+ 'txHash': value.txHash,
+ 'height': value.height,
+ 'method': value.method,
+ 'fee': value.fee,
+ 'amount': value.amount,
+ 'shares': value.shares,
+ 'timestamp': value.timestamp,
+ 'time': value.time,
+ 'status': value.status,
+ 'from': value.from,
+ 'to': value.to,
+ 'errorMessage': value.errorMessage,
+ 'nonce': value.nonce,
+ };
+}
+
+
diff --git a/src/vendors/oasisscan/models/index.ts b/src/vendors/oasisscan/models/index.ts
index 8d8a0b17f7..2654186f6b 100644
--- a/src/vendors/oasisscan/models/index.ts
+++ b/src/vendors/oasisscan/models/index.ts
@@ -6,12 +6,14 @@ export * from './InlineResponse200'
export * from './InlineResponse2001'
export * from './InlineResponse2001Data'
export * from './InlineResponse2002'
-export * from './InlineResponse2002Data'
export * from './InlineResponse2003'
+export * from './InlineResponse2003Data'
export * from './InlineResponse2004'
-export * from './InlineResponse2004Data'
export * from './InlineResponse2005'
export * from './InlineResponse2005Data'
+export * from './InlineResponse2006'
+export * from './InlineResponse2006Data'
+export * from './OperationsEntity'
export * from './OperationsRow'
export * from './ParaTimeCtxRow'
export * from './RuntimeTransactionInfoRow'
diff --git a/src/vendors/oasisscan/swagger.yml b/src/vendors/oasisscan/swagger.yml
index 46df381e89..7b7aea3a5f 100644
--- a/src/vendors/oasisscan/swagger.yml
+++ b/src/vendors/oasisscan/swagger.yml
@@ -68,6 +68,32 @@ paths:
type: array
items:
$ref: '#/components/schemas/ValidatorRow'
+ '/chain/transaction/{hash}':
+ get:
+ tags:
+ - OperationsEntity
+ summary: TransactionDetail
+ operationId: getTransaction
+ parameters:
+ - name: hash
+ in: path
+ description: hash
+ required: true
+ schema:
+ type: string
+ responses:
+ '200':
+ description: OK
+ content:
+ application/json:
+ schema:
+ type: object
+ required: [ 'code', 'data' ]
+ properties:
+ code:
+ type: integer
+ data:
+ "$ref": '#/components/schemas/OperationsEntity'
'/chain/transactions':
get:
tags:
@@ -494,6 +520,73 @@ components:
- status
- from
- to
+ OperationsEntity:
+ type: object
+ properties:
+ txHash:
+ type: string
+ height:
+ type: integer
+ method:
+ type: string
+ enum:
+ - staking.Transfer
+ - staking.AddEscrow
+ - staking.ReclaimEscrow
+ - staking.AmendCommissionSchedule
+ - staking.Allow
+ - staking.Withdraw
+ - staking.Burn
+ - roothash.ExecutorCommit
+ - roothash.ExecutorProposerTimeout
+ - roothash.SubmitMsg
+ - registry.DeregisterEntity
+ - registry.RegisterEntity
+ - registry.RegisterNode
+ - registry.RegisterRuntime
+ - registry.UnfreezeNode
+ - governance.CastVote
+ - governance.SubmitProposal
+ - beacon.PVSSCommit
+ - beacon.PVSSReveal
+ - beacon.VRFProve
+ - consensus.Meta
+ fee:
+ type: string
+ amount:
+ type: string
+ nullable: true
+ shares:
+ type: string
+ nullable: true
+ timestamp:
+ type: integer
+ time:
+ type: integer
+ status:
+ type: boolean
+ from:
+ type: string
+ to:
+ type: string
+ nullable: true
+ errorMessage:
+ type: string
+ nullable: true
+ nonce:
+ type: integer
+ required:
+ - timestamp
+ - txHash
+ - height
+ - method
+ - fee
+ - amount
+ - time
+ - status
+ - from
+ - to
+ - nonce
DelegationRow:
type: object
properties:
From 1e123da5ef586a202460cb8dae285bd9d3ef7b51 Mon Sep 17 00:00:00 2001
From: Matej Lubej
Date: Mon, 27 May 2024 08:22:59 +0200
Subject: [PATCH 02/12] Pending transactions
---
.../Transaction/__tests__/index.test.tsx | 2 +
src/app/components/Transaction/index.tsx | 36 +++++++---
src/app/lib/getAccountBalanceWithFallback.ts | 1 +
.../Features/TransactionHistory/index.tsx | 70 +++++++++++++++++--
src/app/state/account/index.ts | 40 +++++++++--
src/app/state/account/saga.ts | 29 +++++++-
src/app/state/account/selectors.ts | 42 +++++++++++
src/app/state/account/types.ts | 14 ++++
src/app/state/persist/syncTabs.ts | 1 +
src/app/state/transaction/saga.ts | 42 ++++++++++-
src/app/state/transaction/types.ts | 2 +
src/config.ts | 4 ++
src/locales/en/translation.json | 7 +-
src/utils/__fixtures__/test-inputs.ts | 12 ++++
src/vendors/oasisscan.ts | 2 +-
15 files changed, 277 insertions(+), 27 deletions(-)
diff --git a/src/app/components/Transaction/__tests__/index.test.tsx b/src/app/components/Transaction/__tests__/index.test.tsx
index a7b22e621e..0d7e7ffbcc 100644
--- a/src/app/components/Transaction/__tests__/index.test.tsx
+++ b/src/app/components/Transaction/__tests__/index.test.tsx
@@ -108,6 +108,7 @@ describe('', () => {
runtimeName: undefined,
runtimeId: undefined,
round: undefined,
+ nonce: 0n.toString(),
},
network,
)
@@ -131,6 +132,7 @@ describe('', () => {
runtimeName: undefined,
runtimeId: undefined,
round: undefined,
+ nonce: 0n.toString(),
},
network,
)
diff --git a/src/app/components/Transaction/index.tsx b/src/app/components/Transaction/index.tsx
index 51951dc6e2..74aa6249d2 100644
--- a/src/app/components/Transaction/index.tsx
+++ b/src/app/components/Transaction/index.tsx
@@ -462,9 +462,11 @@ export function Transaction(props: TransactionProps) {
-
- {intlDateTimeFormat(transaction.timestamp!)}
-
+ {transaction.timestamp && (
+
+ {intlDateTimeFormat(transaction.timestamp)}
+
+ )}
{!transaction.runtimeId && transaction.level && (
@@ -486,15 +488,31 @@ export function Transaction(props: TransactionProps) {
{
+ switch (transaction.status) {
+ case TransactionStatus.Successful:
+ return 'successful-label'
+ case TransactionStatus.Pending:
+ return 'status-warning'
+ case TransactionStatus.Failed:
+ default:
+ return 'status-error'
+ }
+ })()}
size="small"
weight="bold"
>
- {transaction.status === TransactionStatus.Successful ? (
- {t('account.transaction.successful', 'Successful')}
- ) : (
- {t('account.transaction.failed', 'Failed')}
- )}
+ {(() => {
+ switch (transaction.status) {
+ case TransactionStatus.Successful:
+ return {t('account.transaction.successful', 'Successful')}
+ case TransactionStatus.Pending:
+ return {t('account.transaction.pending', 'Pending')}
+ case TransactionStatus.Failed:
+ default:
+ return {t('account.transaction.failed', 'Failed')}
+ }
+ })()}
diff --git a/src/app/lib/getAccountBalanceWithFallback.ts b/src/app/lib/getAccountBalanceWithFallback.ts
index 1c5d03115f..5c73e8e539 100644
--- a/src/app/lib/getAccountBalanceWithFallback.ts
+++ b/src/app/lib/getAccountBalanceWithFallback.ts
@@ -14,6 +14,7 @@ function* getBalanceGRPC(address: string) {
delegations: null,
debonding: null,
total: null,
+ nonce: account.general?.nonce?.toString() ?? '0',
}
}
diff --git a/src/app/pages/AccountPage/Features/TransactionHistory/index.tsx b/src/app/pages/AccountPage/Features/TransactionHistory/index.tsx
index 6c30a309a4..698179cdc6 100644
--- a/src/app/pages/AccountPage/Features/TransactionHistory/index.tsx
+++ b/src/app/pages/AccountPage/Features/TransactionHistory/index.tsx
@@ -11,36 +11,94 @@ import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import {
+ hasAccountUnknownPendingTransactions,
selectAccountAddress,
+ selectPendingTransactionForAccount,
selectTransactions,
selectTransactionsError,
} from 'app/state/account/selectors'
import { selectSelectedNetwork } from 'app/state/network/selectors'
import { ErrorFormatter } from 'app/components/ErrorFormatter'
-
-interface Props {}
+import { AlertBox } from '../../../../components/AlertBox'
+import { Anchor } from 'grommet/es6/components/Anchor'
+import { Text } from 'grommet/es6/components/Text'
+import { FormNext } from 'grommet-icons/es6/icons/FormNext'
+import { config } from '../../../../../config'
+import { backend } from '../../../../../vendors/backend'
/**
* Displays the past transactions from the state for a given account
*/
-export function TransactionHistory(props: Props) {
+export function TransactionHistory() {
const { t } = useTranslation()
const allTransactions = useSelector(selectTransactions)
const transactionsError = useSelector(selectTransactionsError)
const address = useSelector(selectAccountAddress)
+ const pendingTransactions = useSelector(selectPendingTransactionForAccount)
+ const hasUnknownPendingTransactions = useSelector(hasAccountUnknownPendingTransactions)
const network = useSelector(selectSelectedNetwork)
- const transactionComponents = allTransactions.map((t, i) => (
-
+ const backendLinks = config[network][backend()]
+ const transactionComponents = allTransactions.map(t => (
+
))
+ const pendingTransactionComponents = pendingTransactions
+ .filter(({ hash: pendingTxHash }) => !allTransactions.some(({ hash }) => hash === pendingTxHash))
+ .map(t => )
return (
{transactionsError && (
- {t('account.transaction.loadingError', "Couldn't load transactions.")}{' '}
+ {t('account.transaction.loadingError', `Couldn't load transactions.`)}{' '}
)}
+ {(!!pendingTransactionComponents.length || hasUnknownPendingTransactions) && (
+ <>
+ {t('account.summary.pendingTransactions', 'Pending transactions')}
+
+ {backendLinks.blockExplorerAccount && (
+
+
+ {t('account.summary.viewAccountTxs', 'View account transactions')}
+
+
+
+ )}
+
+ }
+ >
+
+ {t(
+ 'account.summary.someTxsInPendingState',
+ 'Some transactions are currently in a pending state.',
+ )}
+
+
+
+ {pendingTransactionComponents.length ? (
+ // eslint-disable-next-line no-restricted-syntax -- pendingTransactionComponents is not a plain text node
+ pendingTransactionComponents
+ ) : (
+ <>>
+ )}
+
+ {t('account.summary.activity', 'Activity')}
+ >
+ )}
{allTransactions.length ? (
// eslint-disable-next-line no-restricted-syntax -- transactionComponents is not a plain text node
transactionComponents
diff --git a/src/app/state/account/index.ts b/src/app/state/account/index.ts
index 3f6fe82ae1..3522076559 100644
--- a/src/app/state/account/index.ts
+++ b/src/app/state/account/index.ts
@@ -1,8 +1,7 @@
import { PayloadAction } from '@reduxjs/toolkit'
-import { Transaction } from 'app/state/transaction/types'
import { ErrorPayload } from 'types/errors'
import { createSlice } from 'utils/@reduxjs/toolkit'
-import { AccountState, Account } from './types'
+import { AccountState, Account, PendingTransactionPayload, TransactionsLoadedPayload } from './types'
export const initialState: AccountState = {
address: '',
@@ -15,6 +14,12 @@ export const initialState: AccountState = {
transactions: [],
transactionsError: undefined,
loading: true,
+ pendingTransactions: {
+ local: [],
+ testnet: [],
+ mainnet: [],
+ },
+ nonce: '0',
}
export const accountSlice = createSlice({
@@ -23,7 +28,7 @@ export const accountSlice = createSlice({
reducers: {
openAccountPage(state, action: PayloadAction) {},
closeAccountPage(state) {},
- clearAccount(state, action: PayloadAction) {
+ clearAccount(state, action: PayloadAction) {
Object.assign(state, initialState)
},
fetchAccount(state, action: PayloadAction) {},
@@ -34,9 +39,20 @@ export const accountSlice = createSlice({
accountError(state, action: PayloadAction) {
state.accountError = action.payload
},
- transactionsLoaded(state, action: PayloadAction) {
+ transactionsLoaded(state, action: PayloadAction) {
+ const {
+ payload: { networkType, transactions },
+ } = action
+
state.transactionsError = undefined
- state.transactions = action.payload
+ state.transactions = transactions
+
+ state.pendingTransactions = {
+ ...state.pendingTransactions,
+ [networkType]: state.pendingTransactions[networkType].filter(
+ ({ hash: pendingTxHash }) => !transactions.some(({ hash }) => pendingTxHash === hash),
+ ),
+ }
},
transactionsError(state, action: PayloadAction) {
state.transactionsError = action.payload
@@ -46,6 +62,20 @@ export const accountSlice = createSlice({
setLoading(state, action: PayloadAction) {
state.loading = action.payload
},
+ addPendingTransaction(state, action: PayloadAction) {
+ const {
+ payload: { from, networkType, transaction },
+ } = action
+
+ if (from !== state.address) {
+ return
+ }
+
+ state.pendingTransactions = {
+ ...state.pendingTransactions,
+ [networkType]: [transaction, ...state.pendingTransactions[networkType]],
+ }
+ },
},
})
diff --git a/src/app/state/account/saga.ts b/src/app/state/account/saga.ts
index 53126ba466..26f1be73ea 100644
--- a/src/app/state/account/saga.ts
+++ b/src/app/state/account/saga.ts
@@ -13,6 +13,8 @@ import { selectAddress } from '../wallet/selectors'
import { selectAccountAddress, selectAccount } from './selectors'
import { getAccountBalanceWithFallback } from '../../lib/getAccountBalanceWithFallback'
import { walletActions } from '../wallet'
+import { selectSelectedNetwork } from '../network/selectors'
+import { Transaction } from '../transaction/types'
const ACCOUNT_REFETCHING_INTERVAL = process.env.REACT_APP_E2E_TEST ? 5 * 1000 : 30 * 1000
const TRANSACTIONS_UPDATE_DELAY = 35 * 1000 // Measured between 8 and 31 second additional delay after balance updates
@@ -22,7 +24,7 @@ export function* fetchAccount(action: PayloadAction) {
const address = action.payload
yield* put(accountActions.setLoading(true))
- const { getTransactionsList } = yield* call(getExplorerAPIs)
+ const { getTransactionsList, getTransaction } = yield* call(getExplorerAPIs)
yield* all([
join(
@@ -47,12 +49,33 @@ export function* fetchAccount(action: PayloadAction) {
),
join(
yield* fork(function* () {
+ const networkType = yield* select(selectSelectedNetwork)
+
try {
- const transactions = yield* call(getTransactionsList, {
+ const transactions: Transaction[] = yield* call(getTransactionsList, {
accountId: address,
limit: TRANSACTIONS_LIMIT,
})
- yield* put(accountActions.transactionsLoaded(transactions))
+
+ const detailedTransactions = yield* call(() =>
+ Promise.allSettled(transactions.map(({ hash }) => getTransaction({ hash }))),
+ )
+ const transactionsWithDetails = transactions.map((t, i) => {
+ const { status, value } = detailedTransactions[i] as PromiseFulfilledResult
+ // Skip failed txs
+ if (status === 'fulfilled') {
+ return {
+ ...t,
+ ...value,
+ }
+ }
+
+ return t
+ })
+
+ yield* put(
+ accountActions.transactionsLoaded({ networkType, transactions: transactionsWithDetails }),
+ )
} catch (e: any) {
console.error('get transactions list failed, continuing without updated list.', e)
if (e instanceof WalletError) {
diff --git a/src/app/state/account/selectors.ts b/src/app/state/account/selectors.ts
index 880604077a..9bbde4e7c4 100644
--- a/src/app/state/account/selectors.ts
+++ b/src/app/state/account/selectors.ts
@@ -2,6 +2,7 @@ import { createSelector } from '@reduxjs/toolkit'
import { RootState } from 'types'
import { initialState } from '.'
+import { selectSelectedNetwork } from '../network/selectors'
const selectSlice = (state: RootState) => state.account || initialState
@@ -12,3 +13,44 @@ export const selectAccountAddress = createSelector([selectAccount], account => a
export const selectAccountAvailableBalance = createSelector([selectAccount], account => account.available)
export const selectAccountIsLoading = createSelector([selectAccount], account => account.loading)
export const selectAccountAllowances = createSelector([selectAccount], account => account.allowances)
+export const selectPendingTransactions = createSelector(
+ [selectAccount],
+ account => account.pendingTransactions,
+)
+export const selectAccountNonce = createSelector([selectAccount], account => account.nonce ?? '0')
+export const selectPendingTransactionForAccount = createSelector(
+ [selectPendingTransactions, selectSelectedNetwork],
+ (pendingTransactions, networkType) => pendingTransactions[networkType] ?? [],
+)
+export const hasAccountUnknownPendingTransactions = createSelector(
+ [selectAccountNonce, selectTransactions, selectAccountAddress],
+ (accountNonce, transactions, accountAddress) => {
+ // TODO: This logic fails, in case transaction are not from the initial page of account's transactions
+ const filteredTransactions = transactions.filter(({ from }) => from && from === accountAddress)
+
+ if (filteredTransactions.length === 0 && BigInt(accountNonce) === 0n) return false
+
+ const maxNonceFromTxs = filteredTransactions.reduce(
+ (acc, { nonce }) => {
+ if (!nonce) {
+ return acc
+ }
+
+ const bigNonce = BigInt(nonce)
+
+ if (!acc) {
+ return bigNonce
+ }
+
+ return bigNonce > acc ? bigNonce : acc
+ },
+ undefined as bigint | undefined,
+ )
+
+ if (!maxNonceFromTxs) {
+ return BigInt(accountNonce) > 0n
+ }
+
+ return BigInt(maxNonceFromTxs) + BigInt(1) < BigInt(accountNonce)
+ },
+)
diff --git a/src/app/state/account/types.ts b/src/app/state/account/types.ts
index fa6132001e..15a2f20ef9 100644
--- a/src/app/state/account/types.ts
+++ b/src/app/state/account/types.ts
@@ -1,6 +1,7 @@
import { Transaction } from 'app/state/transaction/types'
import { ErrorPayload } from 'types/errors'
import { StringifiedBigInt } from 'types/StringifiedBigInt'
+import { NetworkType } from '../network/types'
export interface BalanceDetails {
available: StringifiedBigInt | null
@@ -20,6 +21,7 @@ export interface Allowance {
export interface Account extends BalanceDetails {
address: string
allowances?: Allowance[]
+ nonce: StringifiedBigInt
}
/* --- STATE --- */
@@ -28,4 +30,16 @@ export interface AccountState extends Account {
accountError?: ErrorPayload
transactions: Transaction[]
transactionsError?: ErrorPayload
+ pendingTransactions: Record
+}
+
+export interface PendingTransactionPayload {
+ from: string
+ networkType: NetworkType
+ transaction: Transaction
+}
+
+export interface TransactionsLoadedPayload {
+ transactions: Transaction[]
+ networkType: NetworkType
}
diff --git a/src/app/state/persist/syncTabs.ts b/src/app/state/persist/syncTabs.ts
index edf7cacc8d..9e28ebd31b 100644
--- a/src/app/state/persist/syncTabs.ts
+++ b/src/app/state/persist/syncTabs.ts
@@ -79,6 +79,7 @@ export const whitelistTabSyncActions: Record = {
[rootSlices.account.actions.setLoading.type]: false,
[rootSlices.account.actions.transactionsError.type]: false,
[rootSlices.account.actions.transactionsLoaded.type]: false,
+ [rootSlices.account.actions.addPendingTransaction.type]: false,
[rootSlices.createWallet.actions.clear.type]: false,
[rootSlices.createWallet.actions.generateMnemonic.type]: false,
[rootSlices.createWallet.actions.setChecked.type]: false,
diff --git a/src/app/state/transaction/saga.ts b/src/app/state/transaction/saga.ts
index 8b10ccf824..3723695950 100644
--- a/src/app/state/transaction/saga.ts
+++ b/src/app/state/transaction/saga.ts
@@ -11,11 +11,12 @@ import { transactionActions } from '.'
import { sign } from '../importaccounts/saga'
import { getOasisNic } from '../network/saga'
import { selectAccountAddress, selectAccountAllowances } from '../account/selectors'
-import { selectChainContext } from '../network/selectors'
+import { selectChainContext, selectSelectedNetwork } from '../network/selectors'
import { selectActiveWallet } from '../wallet/selectors'
import { Wallet, WalletType } from '../wallet/types'
-import { TransactionPayload, TransactionStep } from './types'
+import { Transaction, TransactionPayload, TransactionStatus, TransactionStep, TransactionType } from './types'
import { ParaTimeTransaction, Runtime, TransactionTypes } from '../paratimes/types'
+import { accountActions } from '../account'
export function* transactionSaga() {
yield* takeEvery(transactionActions.sendTransaction, doTransaction)
@@ -137,6 +138,7 @@ export function* doTransaction(action: PayloadAction) {
const wallet = yield* select(selectActiveWallet)
const nic = yield* call(getOasisNic)
const chainContext = yield* select(selectChainContext)
+ const networkType = yield* select(selectSelectedNetwork)
try {
yield* setStep(TransactionStep.Building)
@@ -201,6 +203,42 @@ export function* doTransaction(action: PayloadAction) {
// Notify that the transaction was a success
yield* put(transactionActions.transactionSent(action.payload))
+
+ const hash = yield* call([tw, tw.hash])
+
+ const transaction: Transaction = {
+ hash,
+ type: tw.transaction.method as TransactionType,
+ from: activeWallet.address,
+ amount: action.payload.amount,
+ to: undefined,
+ ...(action.payload.type === 'transfer'
+ ? {
+ to: action.payload.to,
+ }
+ : {}),
+ ...(action.payload.type === 'addEscrow'
+ ? {
+ to: action.payload.validator,
+ }
+ : {}),
+ ...(action.payload.type === 'reclaimEscrow'
+ ? {
+ to: action.payload.validator,
+ }
+ : {}),
+ status: TransactionStatus.Pending,
+ fee: undefined,
+ level: undefined,
+ round: undefined,
+ runtimeId: undefined,
+ runtimeName: undefined,
+ timestamp: undefined,
+ nonce: undefined,
+ }
+
+ // TODO: Handle ParaTime transactions in similar way
+ yield* put(accountActions.addPendingTransaction({ transaction, from: activeWallet.address, networkType }))
} catch (e: any) {
let payload: ErrorPayload
if (e instanceof WalletError) {
diff --git a/src/app/state/transaction/types.ts b/src/app/state/transaction/types.ts
index 01bfb9dbeb..e10bd512b6 100644
--- a/src/app/state/transaction/types.ts
+++ b/src/app/state/transaction/types.ts
@@ -36,6 +36,7 @@ export enum TransactionType {
export enum TransactionStatus {
Failed,
Successful,
+ Pending,
}
export interface Transaction {
@@ -53,6 +54,7 @@ export interface Transaction {
runtimeName: string | undefined
runtimeId: string | undefined
round: number | undefined
+ nonce: StringifiedBigInt | undefined
}
/* --- STATE --- */
diff --git a/src/config.ts b/src/config.ts
index 6ed5df1332..48f0eaba1b 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -12,6 +12,7 @@ type BackendApiUrls = {
explorer: string
blockExplorer: string
blockExplorerParatimes?: string
+ blockExplorerAccount?: string
}
type BackendProviders = {
@@ -39,6 +40,7 @@ export const config: BackendConfig = {
explorer: 'https://api.oasisscan.com/mainnet',
blockExplorer: 'https://oasisscan.com/transactions/{{txHash}}',
blockExplorerParatimes: 'https://oasisscan.com/paratimes/transactions/{{txHash}}?runtime={{runtimeId}}',
+ blockExplorerAccount: 'https://www.oasisscan.com/accounts/detail/{{address}}',
},
},
testnet: {
@@ -54,6 +56,7 @@ export const config: BackendConfig = {
blockExplorer: 'https://testnet.oasisscan.com/transactions/{{txHash}}',
blockExplorerParatimes:
'https://testnet.oasisscan.com/paratimes/transactions/{{txHash}}?runtime={{runtimeId}}',
+ blockExplorerAccount: 'https://testnet.oasisscan.com/accounts/detail/{{address}}',
},
},
local: {
@@ -69,6 +72,7 @@ export const config: BackendConfig = {
blockExplorer: 'http://localhost:9001/data/transactions?operation_id={{txHash}}',
blockExplorerParatimes:
'http://localhost:9001/data/paratimes/transactions/{{txHash}}?runtime={{runtimeId}}',
+ blockExplorerAccount: 'http://localhost:9001/data/accounts/detail/{{address}}',
},
},
}
diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json
index 2f7cf19b84..9df67cc88c 100644
--- a/src/locales/en/translation.json
+++ b/src/locales/en/translation.json
@@ -40,6 +40,7 @@
"validators": "Validators"
},
"summary": {
+ "activity": "Activity",
"balance": {
"available": "Available",
"debonding": "Debonding",
@@ -48,7 +49,10 @@
},
"noTransactionFound": "No transactions found.",
"noWalletIsOpen": "To send, receive, stake and swap {{ticker}} tokens, open your wallet.",
- "notYourAccount": "This is not your account."
+ "notYourAccount": "This is not your account.",
+ "pendingTransactions": "Pending transactions",
+ "someTxsInPendingState": "Some transactions are currently in a pending state.",
+ "viewAccountTxs": "View account transactions"
},
"transaction": {
"addEscrow": {
@@ -70,6 +74,7 @@
"header": "Method '{{method}}'"
},
"loadingError": "Couldn't load transactions.",
+ "pending": "Pending",
"reclaimEscrow": {
"received": " reclaimed by delegator",
"sent": "Reclaimed from validator"
diff --git a/src/utils/__fixtures__/test-inputs.ts b/src/utils/__fixtures__/test-inputs.ts
index 1eca4d07f0..b1e273b0b6 100644
--- a/src/utils/__fixtures__/test-inputs.ts
+++ b/src/utils/__fixtures__/test-inputs.ts
@@ -49,6 +49,12 @@ export const privateKeyUnlockedState = {
transactions: [],
transactionsError: undefined,
loading: false,
+ pendingTransactions: {
+ local: [],
+ testnet: [],
+ mainnet: [],
+ },
+ nonce: '0',
},
contacts: {},
evmAccounts: {},
@@ -229,6 +235,12 @@ export const walletExtensionV0UnlockedState = {
transactions: [],
loading: false,
allowances: [],
+ pendingTransactions: {
+ local: [],
+ testnet: [],
+ mainnet: [],
+ },
+ nonce: '0',
},
contacts: {
oasis1qq3xrq0urs8qcffhvmhfhz4p0mu7ewc8rscnlwxe: {
diff --git a/src/vendors/oasisscan.ts b/src/vendors/oasisscan.ts
index baf6fef4c1..e0881f3e17 100644
--- a/src/vendors/oasisscan.ts
+++ b/src/vendors/oasisscan.ts
@@ -206,7 +206,7 @@ export function parseTransactionsList(
runtimeName: undefined,
runtimeId: undefined,
round: undefined,
- nonce: BigInt((t as OperationsEntity).nonce ?? 0).toString()
+ nonce: BigInt((t as OperationsEntity).nonce ?? 0).toString(),
}
return parsed
}
From 83819c1e0f6bdad7363ba41ee13bee512bee9360 Mon Sep 17 00:00:00 2001
From: Matej Lubej
Date: Wed, 5 Jun 2024 13:06:37 +0200
Subject: [PATCH 03/12] Update test to include pending txs changes
---
.../pages/AccountPage/__tests__/index.test.tsx | 5 +++++
.../__tests__/__snapshots__/monitor.test.ts.snap | 14 ++++++++++++++
.../__snapshots__/oasisscan.test.ts.snap | 15 +++++++++++++++
src/vendors/oasisscan.ts | 2 +-
4 files changed, 35 insertions(+), 1 deletion(-)
diff --git a/src/app/pages/AccountPage/__tests__/index.test.tsx b/src/app/pages/AccountPage/__tests__/index.test.tsx
index f512c55ddd..1e9308a4b1 100644
--- a/src/app/pages/AccountPage/__tests__/index.test.tsx
+++ b/src/app/pages/AccountPage/__tests__/index.test.tsx
@@ -53,6 +53,11 @@ describe('', () => {
transactions: [],
accountError: undefined,
transactionsError: undefined,
+ pendingTransactions: {
+ local: [],
+ testnet: [],
+ mainnet: [],
+ },
},
staking: {
delegations: [{ amount: 1111n.toString() }],
diff --git a/src/vendors/__tests__/__snapshots__/monitor.test.ts.snap b/src/vendors/__tests__/__snapshots__/monitor.test.ts.snap
index ba235c41bf..088015022a 100644
--- a/src/vendors/__tests__/__snapshots__/monitor.test.ts.snap
+++ b/src/vendors/__tests__/__snapshots__/monitor.test.ts.snap
@@ -6,6 +6,7 @@ exports[`monitor parse account 1`] = `
"available": "7791547645377",
"debonding": "0",
"delegations": "312240929087242",
+ "nonce": "89",
"total": "320032476732619",
}
`;
@@ -38,6 +39,7 @@ exports[`monitor parse transaction list 1`] = `
"from": "oasis1qz086axf5hreqpehv5hlgmtw7sfem79gz55v68wp",
"hash": "b831c4b2aa3188058717250cba279795d907e581bb4d7d40d9dc358d37a56254",
"level": 7381105,
+ "nonce": undefined,
"round": undefined,
"runtimeId": undefined,
"runtimeName": undefined,
@@ -52,6 +54,7 @@ exports[`monitor parse transaction list 1`] = `
"from": "oasis1qz0rx0h3v8fyukfrr0npldrrzvpdg4wj2qxvg0kj",
"hash": "e67c4331aa79c85736c4d96cd7b1f3eaad80301bb8d5c181c67482e57ebf0565",
"level": 7381138,
+ "nonce": undefined,
"round": undefined,
"runtimeId": undefined,
"runtimeName": undefined,
@@ -66,6 +69,7 @@ exports[`monitor parse transaction list 1`] = `
"from": "oasis1qzah5wn48ekakmq5405qvcg4czp8hjvrcvcywvhp",
"hash": "0558d39e2c5ebe187fc2802ab442faa548013b247b5de1fb1ef75862dad4fb23",
"level": 7380979,
+ "nonce": undefined,
"round": undefined,
"runtimeId": undefined,
"runtimeName": undefined,
@@ -80,6 +84,7 @@ exports[`monitor parse transaction list 1`] = `
"from": "oasis1qzl58e7v7pk50h66s2tv3u9rzf87twp7pcv7hul6",
"hash": "ba8e25c66ae31fa0a0837a414359bc2318c6c849515ca3dc1ffa9eb0a1ab92b3",
"level": 7361579,
+ "nonce": undefined,
"round": undefined,
"runtimeId": undefined,
"runtimeName": undefined,
@@ -94,6 +99,7 @@ exports[`monitor parse transaction list 1`] = `
"from": "oasis1qq3833fnmkqe94h0ca6w8qa84sq8pu92qsjmfayj",
"hash": "8894b8e9866f66efe291155646f1c09d69d7221449a8d9f758ad1d31f504df03",
"level": 7381163,
+ "nonce": undefined,
"round": undefined,
"runtimeId": undefined,
"runtimeName": undefined,
@@ -108,6 +114,7 @@ exports[`monitor parse transaction list 1`] = `
"from": "oasis1qzr9p9fpjqekr8dev66wuaedcpq5n09hwvpkd4pg",
"hash": "d6298496fc19fd95fa1e2b245d1c33661b9ebd7ffb184280c363a31d13210c2a",
"level": 7381204,
+ "nonce": undefined,
"round": undefined,
"runtimeId": undefined,
"runtimeName": undefined,
@@ -122,6 +129,7 @@ exports[`monitor parse transaction list 1`] = `
"from": "oasis1qz6k3gky5d43h70xh2c5vk5fztmzmxmmhc6rh72x",
"hash": "46583095fd80becc2683aacc67684170de8d6bc6eca5103d7ac543106d729a8f",
"level": 7381052,
+ "nonce": undefined,
"round": undefined,
"runtimeId": undefined,
"runtimeName": undefined,
@@ -136,6 +144,7 @@ exports[`monitor parse transaction list 1`] = `
"from": "oasis1qpdlqz373hcqvafadd3lxptj42x84sws35s02r4r",
"hash": "5378750685efed957417abea41e7d96804264cb51d85dcee45494ef0ca2f31c7",
"level": 7368263,
+ "nonce": undefined,
"round": undefined,
"runtimeId": undefined,
"runtimeName": undefined,
@@ -150,6 +159,7 @@ exports[`monitor parse transaction list 1`] = `
"from": "oasis1qzwl8jlxzwjgz2m6d3ns0vt9hzfp2h63qsxs76ys",
"hash": "86a303d9891bbefb0984b82dcc0a51ec190d383248b536dcb8bc9ca0404824f4",
"level": 7381231,
+ "nonce": undefined,
"round": undefined,
"runtimeId": undefined,
"runtimeName": undefined,
@@ -164,6 +174,7 @@ exports[`monitor parse transaction list 1`] = `
"from": "oasis1qrvmxhcjpjvgel9dqfs6zrnza3hqjpa6ug2arc0d",
"hash": "2ac0a88ab2c85cef69905c8c9b3f639c5b3b15b969c334df5dcc4fa54f183a8a",
"level": 6251849,
+ "nonce": undefined,
"round": undefined,
"runtimeId": undefined,
"runtimeName": undefined,
@@ -178,6 +189,7 @@ exports[`monitor parse transaction list 1`] = `
"from": "oasis1qp3rhyfjagkj65cnn6lt8ej305gh3kamsvzspluq",
"hash": "a62ebc4d30bc045f129f33a14d4019ec88e48c150980ed388d5f64b6e9476059",
"level": 4726356,
+ "nonce": undefined,
"round": undefined,
"runtimeId": undefined,
"runtimeName": undefined,
@@ -192,6 +204,7 @@ exports[`monitor parse transaction list 1`] = `
"from": "oasis1qrd64zucfaugv677fwkhynte4dz450yffgp0k06t",
"hash": "f42c704c38ddbab62e83d787e9ff1097c703eac0bcf5587388dd208abae9b888",
"level": 7380719,
+ "nonce": undefined,
"round": undefined,
"runtimeId": undefined,
"runtimeName": undefined,
@@ -206,6 +219,7 @@ exports[`monitor parse transaction list 1`] = `
"from": "oasis1qptn9zmdn5ksvq85vxg2mg84e9m6jp2875dyfl73",
"hash": "9c9fd0d2588a0108ec5f277f476483f17e2d8429e947c83f0096b0a7c351aa51",
"level": 7381114,
+ "nonce": undefined,
"round": undefined,
"runtimeId": undefined,
"runtimeName": undefined,
diff --git a/src/vendors/__tests__/__snapshots__/oasisscan.test.ts.snap b/src/vendors/__tests__/__snapshots__/oasisscan.test.ts.snap
index bad7647c66..c11f32c6f3 100644
--- a/src/vendors/__tests__/__snapshots__/oasisscan.test.ts.snap
+++ b/src/vendors/__tests__/__snapshots__/oasisscan.test.ts.snap
@@ -12,6 +12,7 @@ exports[`oasisscan parse account 1`] = `
"available": "7791547645364",
"debonding": "0",
"delegations": "312240929087243",
+ "nonce": "89",
"total": "320032476732607",
}
`;
@@ -44,6 +45,7 @@ exports[`oasisscan parse transaction list 1`] = `
"from": "oasis1qqnk4au603zs94k0d0n7c0hkx8t4p6r87s60axru",
"hash": "25b84ca4582f6e3140c384ad60b98415d2c3079d1fc5f2221e45da7b13c70817",
"level": undefined,
+ "nonce": undefined,
"round": 997775,
"runtimeId": "000000000000000000000000000000000000000000000000e2eaa99fc008f87f",
"runtimeName": "Emerald",
@@ -58,6 +60,7 @@ exports[`oasisscan parse transaction list 1`] = `
"from": "oasis1qz086axf5hreqpehv5hlgmtw7sfem79gz55v68wp",
"hash": "b831c4b2aa3188058717250cba279795d907e581bb4d7d40d9dc358d37a56254",
"level": 7381105,
+ "nonce": undefined,
"round": undefined,
"runtimeId": undefined,
"runtimeName": undefined,
@@ -72,6 +75,7 @@ exports[`oasisscan parse transaction list 1`] = `
"from": "oasis1qz0rx0h3v8fyukfrr0npldrrzvpdg4wj2qxvg0kj",
"hash": "e67c4331aa79c85736c4d96cd7b1f3eaad80301bb8d5c181c67482e57ebf0565",
"level": 7381138,
+ "nonce": undefined,
"round": undefined,
"runtimeId": undefined,
"runtimeName": undefined,
@@ -86,6 +90,7 @@ exports[`oasisscan parse transaction list 1`] = `
"from": "oasis1qzah5wn48ekakmq5405qvcg4czp8hjvrcvcywvhp",
"hash": "0558d39e2c5ebe187fc2802ab442faa548013b247b5de1fb1ef75862dad4fb23",
"level": 7380979,
+ "nonce": undefined,
"round": undefined,
"runtimeId": undefined,
"runtimeName": undefined,
@@ -100,6 +105,7 @@ exports[`oasisscan parse transaction list 1`] = `
"from": "oasis1qzl58e7v7pk50h66s2tv3u9rzf87twp7pcv7hul6",
"hash": "ba8e25c66ae31fa0a0837a414359bc2318c6c849515ca3dc1ffa9eb0a1ab92b3",
"level": 7361579,
+ "nonce": undefined,
"round": undefined,
"runtimeId": undefined,
"runtimeName": undefined,
@@ -114,6 +120,7 @@ exports[`oasisscan parse transaction list 1`] = `
"from": "oasis1qq3833fnmkqe94h0ca6w8qa84sq8pu92qsjmfayj",
"hash": "8894b8e9866f66efe291155646f1c09d69d7221449a8d9f758ad1d31f504df03",
"level": 7381163,
+ "nonce": undefined,
"round": undefined,
"runtimeId": undefined,
"runtimeName": undefined,
@@ -128,6 +135,7 @@ exports[`oasisscan parse transaction list 1`] = `
"from": "oasis1qzr9p9fpjqekr8dev66wuaedcpq5n09hwvpkd4pg",
"hash": "d6298496fc19fd95fa1e2b245d1c33661b9ebd7ffb184280c363a31d13210c2a",
"level": 7381204,
+ "nonce": undefined,
"round": undefined,
"runtimeId": undefined,
"runtimeName": undefined,
@@ -142,6 +150,7 @@ exports[`oasisscan parse transaction list 1`] = `
"from": "oasis1qz6k3gky5d43h70xh2c5vk5fztmzmxmmhc6rh72x",
"hash": "46583095fd80becc2683aacc67684170de8d6bc6eca5103d7ac543106d729a8f",
"level": 7381052,
+ "nonce": undefined,
"round": undefined,
"runtimeId": undefined,
"runtimeName": undefined,
@@ -156,6 +165,7 @@ exports[`oasisscan parse transaction list 1`] = `
"from": "oasis1qpdlqz373hcqvafadd3lxptj42x84sws35s02r4r",
"hash": "5378750685efed957417abea41e7d96804264cb51d85dcee45494ef0ca2f31c7",
"level": 7368263,
+ "nonce": undefined,
"round": undefined,
"runtimeId": undefined,
"runtimeName": undefined,
@@ -170,6 +180,7 @@ exports[`oasisscan parse transaction list 1`] = `
"from": "oasis1qzwl8jlxzwjgz2m6d3ns0vt9hzfp2h63qsxs76ys",
"hash": "86a303d9891bbefb0984b82dcc0a51ec190d383248b536dcb8bc9ca0404824f4",
"level": 7381231,
+ "nonce": undefined,
"round": undefined,
"runtimeId": undefined,
"runtimeName": undefined,
@@ -184,6 +195,7 @@ exports[`oasisscan parse transaction list 1`] = `
"from": "oasis1qrvmxhcjpjvgel9dqfs6zrnza3hqjpa6ug2arc0d",
"hash": "2ac0a88ab2c85cef69905c8c9b3f639c5b3b15b969c334df5dcc4fa54f183a8a",
"level": 6251849,
+ "nonce": undefined,
"round": undefined,
"runtimeId": undefined,
"runtimeName": undefined,
@@ -198,6 +210,7 @@ exports[`oasisscan parse transaction list 1`] = `
"from": "oasis1qp3rhyfjagkj65cnn6lt8ej305gh3kamsvzspluq",
"hash": "a62ebc4d30bc045f129f33a14d4019ec88e48c150980ed388d5f64b6e9476059",
"level": 4726356,
+ "nonce": undefined,
"round": undefined,
"runtimeId": undefined,
"runtimeName": undefined,
@@ -212,6 +225,7 @@ exports[`oasisscan parse transaction list 1`] = `
"from": "oasis1qrd64zucfaugv677fwkhynte4dz450yffgp0k06t",
"hash": "09bfc632625d44fe96d4d31bacd12ed889231f77ed898cbcccf0dea7527a6237",
"level": 7396874,
+ "nonce": undefined,
"round": undefined,
"runtimeId": undefined,
"runtimeName": undefined,
@@ -226,6 +240,7 @@ exports[`oasisscan parse transaction list 1`] = `
"from": "oasis1qptn9zmdn5ksvq85vxg2mg84e9m6jp2875dyfl73",
"hash": "9c9fd0d2588a0108ec5f277f476483f17e2d8429e947c83f0096b0a7c351aa51",
"level": 7381114,
+ "nonce": undefined,
"round": undefined,
"runtimeId": undefined,
"runtimeName": undefined,
diff --git a/src/vendors/oasisscan.ts b/src/vendors/oasisscan.ts
index e0881f3e17..22addf8e6f 100644
--- a/src/vendors/oasisscan.ts
+++ b/src/vendors/oasisscan.ts
@@ -206,7 +206,7 @@ export function parseTransactionsList(
runtimeName: undefined,
runtimeId: undefined,
round: undefined,
- nonce: BigInt((t as OperationsEntity).nonce ?? 0).toString(),
+ nonce: (t as OperationsEntity).nonce ? BigInt((t as OperationsEntity).nonce).toString() : undefined,
}
return parsed
}
From ef94c2dbc79cf24a49eb17a430ab46556634ee07 Mon Sep 17 00:00:00 2001
From: Matej Lubej
Date: Wed, 5 Jun 2024 13:06:46 +0200
Subject: [PATCH 04/12] Add changelog
---
.changelog/1954.feature.md | 7 +++++++
1 file changed, 7 insertions(+)
create mode 100644 .changelog/1954.feature.md
diff --git a/.changelog/1954.feature.md b/.changelog/1954.feature.md
new file mode 100644
index 0000000000..9a26096a2b
--- /dev/null
+++ b/.changelog/1954.feature.md
@@ -0,0 +1,7 @@
+Pending transactions
+
+Introduces a section for pending transactions within the transaction history
+interface. It is designed to display transactions currently in a pending
+state that are made within the wallet. The section will also show up in case
+there is a discrepancy between transaction history nonce and wallet nonce,
+indicating that some transactions are currently in pending state.
From d2b734dbe7a6494bd31acaac1a9a8618842765ed Mon Sep 17 00:00:00 2001
From: Matej Lubej
Date: Thu, 6 Jun 2024 10:49:36 +0200
Subject: [PATCH 05/12] Remove transactions fetch delay and decrease time to
fetch balance
---
src/app/state/account/saga.ts | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/app/state/account/saga.ts b/src/app/state/account/saga.ts
index 26f1be73ea..d552e021aa 100644
--- a/src/app/state/account/saga.ts
+++ b/src/app/state/account/saga.ts
@@ -10,14 +10,13 @@ import { fetchAccount as stakingFetchAccount } from '../staking/saga'
import { refreshAccount as walletRefreshAccount } from '../wallet/saga'
import { transactionActions } from '../transaction'
import { selectAddress } from '../wallet/selectors'
-import { selectAccountAddress, selectAccount } from './selectors'
+import { selectAccountAddress, selectAccount, hasAccountUnknownPendingTransactions } from './selectors'
import { getAccountBalanceWithFallback } from '../../lib/getAccountBalanceWithFallback'
import { walletActions } from '../wallet'
import { selectSelectedNetwork } from '../network/selectors'
import { Transaction } from '../transaction/types'
-const ACCOUNT_REFETCHING_INTERVAL = process.env.REACT_APP_E2E_TEST ? 5 * 1000 : 30 * 1000
-const TRANSACTIONS_UPDATE_DELAY = 35 * 1000 // Measured between 8 and 31 second additional delay after balance updates
+const ACCOUNT_REFETCHING_INTERVAL = process.env.REACT_APP_E2E_TEST ? 5 * 1000 : 10 * 1000
const TRANSACTIONS_LIMIT = 20
export function* fetchAccount(action: PayloadAction) {
@@ -149,13 +148,14 @@ export function* fetchingOnAccountPage() {
}
const staleBalances = yield* select(selectAccount)
+ const hasPendingTxs = yield* select(hasAccountUnknownPendingTransactions)
if (
staleBalances.available !== refreshedAccount.available ||
staleBalances.delegations !== refreshedAccount.delegations ||
- staleBalances.debonding !== refreshedAccount.debonding
+ staleBalances.debonding !== refreshedAccount.debonding ||
+ hasPendingTxs
) {
// Wait for oasisscan to update transactions (it updates balances faster)
- yield* delay(TRANSACTIONS_UPDATE_DELAY)
yield* call(fetchAccount, startAction)
yield* call(stakingFetchAccount, startAction)
yield* call(walletRefreshAccount, address)
From 1c92a96ec4b87eca2840925fd3c5b732e6a2cd22 Mon Sep 17 00:00:00 2001
From: Matej Lubej
Date: Thu, 6 Jun 2024 10:50:24 +0200
Subject: [PATCH 06/12] Show Loading account only on initial load
- needs better UX in case it updates, like flash or some animation
---
src/app/pages/AccountPage/index.tsx | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/src/app/pages/AccountPage/index.tsx b/src/app/pages/AccountPage/index.tsx
index fcafa23ed4..e1171099be 100644
--- a/src/app/pages/AccountPage/index.tsx
+++ b/src/app/pages/AccountPage/index.tsx
@@ -26,9 +26,7 @@ import { walletActions } from 'app/state/wallet'
import { AccountSummary } from './Features/AccountSummary'
import { AccountPageParams } from './validateAccountPageRoute'
-interface AccountPageProps {}
-
-export function AccountPage(props: AccountPageProps) {
+export function AccountPage() {
const { t } = useTranslation()
const address = useParams().address!
const dispatch = useDispatch()
@@ -64,10 +62,14 @@ export function AccountPage(props: AccountPageProps) {
}
}, [dispatch, address, selectedNetwork])
+ const isStakeInitialLoading = stake.loading && stake.delegations === null
+ const isAccountInitialLoading = account.loading && !account.address
+
return (
{active && }
- {(stake.loading || account.loading) && (
+ {/* Prevent showing Loading account popup unless initial load */}
+ {(isStakeInitialLoading || isAccountInitialLoading) && (
From e0f48564d1ffc5d1ffa50e446251f61b6f4dc4bd Mon Sep 17 00:00:00 2001
From: Matej Lubej
Date: Mon, 10 Jun 2024 07:26:45 +0200
Subject: [PATCH 07/12] Add unit tests
---
.../__snapshots__/index.test.tsx.snap | 3 -
.../__tests__/index.test.tsx | 136 +++++++++++++++++-
.../Features/TransactionHistory/index.tsx | 32 ++---
3 files changed, 147 insertions(+), 24 deletions(-)
delete mode 100644 src/app/pages/AccountPage/Features/TransactionHistory/__tests__/__snapshots__/index.test.tsx.snap
diff --git a/src/app/pages/AccountPage/Features/TransactionHistory/__tests__/__snapshots__/index.test.tsx.snap b/src/app/pages/AccountPage/Features/TransactionHistory/__tests__/__snapshots__/index.test.tsx.snap
deleted file mode 100644
index 252c97822b..0000000000
--- a/src/app/pages/AccountPage/Features/TransactionHistory/__tests__/__snapshots__/index.test.tsx.snap
+++ /dev/null
@@ -1,3 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[` should match snapshot 1`] = ``;
diff --git a/src/app/pages/AccountPage/Features/TransactionHistory/__tests__/index.test.tsx b/src/app/pages/AccountPage/Features/TransactionHistory/__tests__/index.test.tsx
index 5787a519dc..38004f7011 100644
--- a/src/app/pages/AccountPage/Features/TransactionHistory/__tests__/index.test.tsx
+++ b/src/app/pages/AccountPage/Features/TransactionHistory/__tests__/index.test.tsx
@@ -1,11 +1,139 @@
import * as React from 'react'
-import { render } from '@testing-library/react'
+import { render, screen } from '@testing-library/react'
import { TransactionHistory } from '..'
+import { configureAppStore } from '../../../../../../store/configureStore'
+import { Provider, useDispatch } from 'react-redux'
+import { DeepPartialRootState, RootState } from '../../../../../../types/RootState'
+import { Transaction, TransactionStatus, TransactionType } from 'app/state/transaction/types'
+
+jest.mock('react-redux', () => ({
+ ...jest.requireActual('react-redux'),
+ useDispatch: jest.fn(),
+}))
+
+const renderCmp = (store: ReturnType) =>
+ render(
+
+
+ ,
+ )
+
+const getPendingTx = (hash: string): Transaction => ({
+ hash,
+ type: TransactionType.StakingTransfer,
+ from: 'oasis1qz0k5q8vjqvu4s4nwxyj406ylnflkc4vrcjghuwk',
+ amount: 1n.toString(),
+ to: 'oasis1qz0k5q8vjqvu4s4nwxyj406ylnflkc4vrcjghuww',
+ status: undefined,
+ fee: undefined,
+ level: undefined,
+ round: undefined,
+ runtimeId: undefined,
+ runtimeName: undefined,
+ timestamp: undefined,
+ nonce: undefined,
+})
+
+const getTx = (hash: string, nonce: bigint): Transaction => ({
+ ...getPendingTx(hash),
+ status: TransactionStatus.Successful,
+ nonce: nonce.toString(),
+})
+
+const getState = ({
+ accountNonce = 0n,
+ pendingLocalTxs = [],
+ accountTxs = [],
+}: { accountNonce?: bigint; pendingLocalTxs?: Transaction[]; accountTxs?: Transaction[] } = {}) => {
+ const state: DeepPartialRootState = {
+ account: {
+ loading: false,
+ address: 'oasis1qz0k5q8vjqvu4s4nwxyj406ylnflkc4vrcjghuwk',
+ available: 100000000000n.toString(),
+ delegations: null,
+ debonding: null,
+ total: null,
+ transactions: [...accountTxs],
+ accountError: undefined,
+ transactionsError: undefined,
+ pendingTransactions: {
+ local: [...pendingLocalTxs],
+ testnet: [],
+ mainnet: [],
+ },
+ nonce: accountNonce.toString(),
+ },
+ staking: {
+ delegations: [],
+ debondingDelegations: [],
+ },
+ wallet: {
+ selectedWallet: 'oasis1qz0k5q8vjqvu4s4nwxyj406ylnflkc4vrcjghuwk',
+ wallets: {
+ oasis1qz0k5q8vjqvu4s4nwxyj406ylnflkc4vrcjghuwk: {
+ address: 'oasis1qz0k5q8vjqvu4s4nwxyj406ylnflkc4vrcjghuwk',
+ },
+ },
+ },
+ }
+ return configureAppStore(state as Partial)
+}
describe('', () => {
- it.skip('should match snapshot', () => {
- const component = render()
- expect(component.container.firstChild).toMatchSnapshot()
+ beforeEach(() => {
+ // Ignore dispatches to fetch account from AccountPage
+ jest.mocked(useDispatch).mockImplementation(() => jest.fn())
+ })
+
+ it('should not display any pending or completed txs', async () => {
+ renderCmp(getState())
+
+ expect(() => screen.getByTestId('pending-txs')).toThrow()
+ expect(() => screen.getByTestId('completed-txs')).toThrow()
+
+ expect(screen.queryByText('account.summary.someTxsInPendingState')).not.toBeInTheDocument()
+ expect(await screen.findByText('account.summary.noTransactionFound')).toBeInTheDocument()
+ })
+
+ it('should display pending txs alert and no transactions', async () => {
+ renderCmp(getState({ accountNonce: 1n }))
+
+ expect(() => screen.getByTestId('pending-txs')).toThrow()
+ expect(() => screen.getByTestId('completed-txs')).toThrow()
+
+ expect(await screen.findByText('account.summary.someTxsInPendingState')).toBeInTheDocument()
+ expect(await screen.findByText('account.summary.noTransactionFound')).toBeInTheDocument()
+ expect(await screen.findByRole('link')).toHaveAttribute(
+ 'href',
+ 'http://localhost:9001/data/accounts/detail/oasis1qz0k5q8vjqvu4s4nwxyj406ylnflkc4vrcjghuwk',
+ )
+ })
+
+ it('should display pending txs alert with single pending tx and no completed transactions', async () => {
+ renderCmp(getState({ accountNonce: 0n, pendingLocalTxs: [getPendingTx('txHash1')] }))
+
+ expect(screen.getByTestId('pending-txs').childElementCount).toBe(1)
+ expect(() => screen.getByTestId('completed-txs')).toThrow()
+
+ expect(await screen.findByText('account.summary.someTxsInPendingState')).toBeInTheDocument()
+ expect(await screen.findByText('account.summary.noTransactionFound')).toBeInTheDocument()
+ expect(await screen.findByText('txHash1')).toBeInTheDocument()
+ })
+
+ it('should display single pending and completed tx', async () => {
+ renderCmp(
+ getState({
+ accountNonce: 2n,
+ accountTxs: [getTx('txHash1', 0n)],
+ pendingLocalTxs: [getPendingTx('txHash2')],
+ }),
+ )
+
+ expect(screen.getByTestId('pending-txs').childElementCount).toBe(1)
+ expect(screen.getByTestId('completed-txs').childElementCount).toBe(1)
+
+ expect(await screen.findByText('txHash1')).toBeInTheDocument()
+ expect(await screen.findByText('txHash2')).toBeInTheDocument()
})
})
diff --git a/src/app/pages/AccountPage/Features/TransactionHistory/index.tsx b/src/app/pages/AccountPage/Features/TransactionHistory/index.tsx
index 698179cdc6..bdf624051d 100644
--- a/src/app/pages/AccountPage/Features/TransactionHistory/index.tsx
+++ b/src/app/pages/AccountPage/Features/TransactionHistory/index.tsx
@@ -46,13 +46,14 @@ export function TransactionHistory() {
.map(t => )
return (
-
+
{transactionsError && (
{t('account.transaction.loadingError', `Couldn't load transactions.`)}{' '}
)}
+ {/* eslint-disable no-restricted-syntax */}
{(!!pendingTransactionComponents.length || hasUnknownPendingTransactions) && (
<>
{t('account.summary.pendingTransactions', 'Pending transactions')}
@@ -81,27 +82,24 @@ export function TransactionHistory() {
}
>
-
- {t(
- 'account.summary.someTxsInPendingState',
- 'Some transactions are currently in a pending state.',
- )}
-
-
-
- {pendingTransactionComponents.length ? (
- // eslint-disable-next-line no-restricted-syntax -- pendingTransactionComponents is not a plain text node
- pendingTransactionComponents
- ) : (
- <>>
+ {t(
+ 'account.summary.someTxsInPendingState',
+ 'Some transactions are currently in a pending state.',
)}
-
+
+ {!!pendingTransactionComponents.length && (
+
+ {pendingTransactionComponents}
+
+ )}
{t('account.summary.activity', 'Activity')}
>
)}
+ {/* eslint-enable no-restricted-syntax */}
{allTransactions.length ? (
- // eslint-disable-next-line no-restricted-syntax -- transactionComponents is not a plain text node
- transactionComponents
+
+ {transactionComponents}
+
) : (
Date: Wed, 19 Jun 2024 06:30:34 +0200
Subject: [PATCH 08/12] Add E2E test for pending transaction section
---
playwright/tests/transfer.spec.ts | 13 +++++++++++++
playwright/utils/mockApi.ts | 18 ++++++++++++++++++
src/app/lib/getAccountBalanceWithFallback.ts | 11 +++++------
src/app/state/account/types.ts | 3 ++-
src/app/state/wallet/saga.ts | 12 ++++++++----
src/utils/__fixtures__/test-inputs.ts | 7 ++++---
src/vendors/oasisscan.ts | 8 ++++----
7 files changed, 54 insertions(+), 18 deletions(-)
diff --git a/playwright/tests/transfer.spec.ts b/playwright/tests/transfer.spec.ts
index 508cb7829d..188123f5cc 100644
--- a/playwright/tests/transfer.spec.ts
+++ b/playwright/tests/transfer.spec.ts
@@ -33,3 +33,16 @@ test('Scrolling on amount input field should preserve value', async ({ page }) =
await page.mouse.wheel(0, 10)
await expect(input).toHaveValue('1111')
})
+
+test('Should show pending transactions section', async ({ page }) => {
+ await page.getByTestId('nav-myaccount').click()
+
+ await page.getByPlaceholder('Enter an address').fill('oasis1qrf4y7aelwuusc270e8qx04ysr45w3q0zyavrpdk')
+ await page.getByPlaceholder('Enter an amount').fill('0.1')
+
+ await page.getByRole('button', { name: /Send/i }).click()
+ await page.getByRole('button', { name: /Confirm/i }).click()
+
+ await expect(page.getByRole('heading', { name: 'Pending transactions' })).toBeVisible()
+ await expect(page.getByText('Some transactions are currently in a pending state.')).toBeVisible()
+})
diff --git a/playwright/utils/mockApi.ts b/playwright/utils/mockApi.ts
index f5a30f5302..9fc67ba396 100644
--- a/playwright/utils/mockApi.ts
+++ b/playwright/utils/mockApi.ts
@@ -75,6 +75,24 @@ export async function mockApi(context: BrowserContext | Page, balance: number) {
body: 'AAAAAAGggAAAAB5ncnBjLXN0YXR1czowDQpncnBjLW1lc3NhZ2U6DQo=',
})
})
+ await context.route('**/oasis-core.Consensus/GetSignerNonce', route => {
+ route.fulfill({
+ contentType: 'application/grpc-web-text+proto',
+ body: 'AAAAAAIYKQ==gAAAAB5ncnBjLXN0YXR1czowDQpncnBjLW1lc3NhZ2U6DQo=',
+ })
+ })
+ await context.route('**/oasis-core.Consensus/EstimateGas', route => {
+ route.fulfill({
+ contentType: 'application/grpc-web-text+proto',
+ body: 'AAAAAAMZBPE=gAAAAB5ncnBjLXN0YXR1czowDQpncnBjLW1lc3NhZ2U6DQo=',
+ })
+ })
+ await context.route('**/oasis-core.Consensus/SubmitTx', route => {
+ route.fulfill({
+ contentType: 'application/grpc-web-text+proto',
+ body: 'AAAAAAA=gAAAAB5ncnBjLXN0YXR1czowDQpncnBjLW1lc3NhZ2U6DQo=',
+ })
+ })
// Inside Transak iframe
await context.route('https://sentry.io/**', route => route.fulfill({ body: '' }))
diff --git a/src/app/lib/getAccountBalanceWithFallback.ts b/src/app/lib/getAccountBalanceWithFallback.ts
index 5c73e8e539..c6f70df719 100644
--- a/src/app/lib/getAccountBalanceWithFallback.ts
+++ b/src/app/lib/getAccountBalanceWithFallback.ts
@@ -3,7 +3,7 @@ import { call } from 'typed-redux-saga'
import { getExplorerAPIs, getOasisNic } from '../state/network/saga'
import { Account } from '../state/account/types'
-function* getBalanceGRPC(address: string) {
+function* getBalanceGRPC(address: string, { includeNonce = true } = {}) {
const nic = yield* call(getOasisNic)
const publicKey = yield* call(addressToPublicKey, address)
const account = yield* call([nic, nic.stakingAccount], { owner: publicKey, height: 0 })
@@ -14,20 +14,19 @@ function* getBalanceGRPC(address: string) {
delegations: null,
debonding: null,
total: null,
- nonce: account.general?.nonce?.toString() ?? '0',
+ ...(includeNonce ? { nonce: account.general?.nonce?.toString() ?? '0' } : {}),
}
}
-export function* getAccountBalanceWithFallback(address: string) {
+export function* getAccountBalanceWithFallback(address: string, { includeNonce = true } = {}) {
const { getAccount } = yield* call(getExplorerAPIs)
-
try {
- const account: Account = yield* call(getAccount, address)
+ const account: Account = yield* call(getAccount, address, { includeNonce })
return account
} catch (apiError: any) {
console.error('get account failed, continuing to RPC fallback.', apiError)
try {
- const account: Account = yield* call(getBalanceGRPC, address)
+ const account: Account = yield* call(getBalanceGRPC, address, { includeNonce })
return account
} catch (rpcError) {
console.error('get account with RPC failed, continuing without updated account.', rpcError)
diff --git a/src/app/state/account/types.ts b/src/app/state/account/types.ts
index 15a2f20ef9..cd3a7b5011 100644
--- a/src/app/state/account/types.ts
+++ b/src/app/state/account/types.ts
@@ -11,6 +11,7 @@ export interface BalanceDetails {
delegations: StringifiedBigInt | null
/** This is delayed in getAccount by 20 seconds on oasisscan and 5 seconds on oasismonitor. */
total: StringifiedBigInt | null
+ nonce?: StringifiedBigInt | null
}
export interface Allowance {
@@ -21,7 +22,7 @@ export interface Allowance {
export interface Account extends BalanceDetails {
address: string
allowances?: Allowance[]
- nonce: StringifiedBigInt
+ nonce?: StringifiedBigInt
}
/* --- STATE --- */
diff --git a/src/app/state/wallet/saga.ts b/src/app/state/wallet/saga.ts
index 09c95f1b41..76f7a0afa2 100644
--- a/src/app/state/wallet/saga.ts
+++ b/src/app/state/wallet/saga.ts
@@ -48,11 +48,13 @@ export function* openWalletsFromLedger({ payload }: PayloadAction) {
const wallet = action.payload
- const balance = yield* call(getAccountBalanceWithFallback, wallet.address)
+ const balance = yield* call(getAccountBalanceWithFallback, wallet.address, { includeNonce: false })
yield* put(
walletActions.updateBalance({
address: wallet.address,
diff --git a/src/utils/__fixtures__/test-inputs.ts b/src/utils/__fixtures__/test-inputs.ts
index b1e273b0b6..260f991fbb 100644
--- a/src/utils/__fixtures__/test-inputs.ts
+++ b/src/utils/__fixtures__/test-inputs.ts
@@ -54,7 +54,7 @@ export const privateKeyUnlockedState = {
testnet: [],
mainnet: [],
},
- nonce: '0',
+ nonce: '1',
},
contacts: {},
evmAccounts: {},
@@ -121,6 +121,7 @@ export const privateKeyUnlockedState = {
available: '0',
debonding: '0',
delegations: '0',
+ nonce: '1',
total: '0',
validator: {
escrow: '0',
@@ -240,7 +241,7 @@ export const walletExtensionV0UnlockedState = {
testnet: [],
mainnet: [],
},
- nonce: '0',
+ nonce: '1',
},
contacts: {
oasis1qq3xrq0urs8qcffhvmhfhz4p0mu7ewc8rscnlwxe: {
@@ -289,7 +290,6 @@ export const walletExtensionV0UnlockedState = {
ethPrivateKeyRaw: '',
feeAmount: '',
feeGas: '',
- paraTime: undefined,
recipient: '',
type: undefined,
},
@@ -385,6 +385,7 @@ export const walletExtensionV0UnlockedState = {
debonding: '0',
delegations: '0',
total: '0',
+ nonce: '0',
},
name: 'short privatekey',
privateKey:
diff --git a/src/vendors/oasisscan.ts b/src/vendors/oasisscan.ts
index 22addf8e6f..fc473a2a51 100644
--- a/src/vendors/oasisscan.ts
+++ b/src/vendors/oasisscan.ts
@@ -33,10 +33,10 @@ export function getOasisscanAPIs(url: string | 'https://api.oasisscan.com/mainne
const operationsEntity = new OperationsEntityApi(explorerConfig)
const runtime = new RuntimeApi(explorerConfig)
- async function getAccount(address: string): Promise {
+ async function getAccount(address: string, { includeNonce = true } = {}): Promise {
const account = await accounts.getAccount({ accountId: address })
if (!account || account.code !== 0) throw new Error('Wrong response code') // TODO
- return parseAccount(account.data)
+ return parseAccount(account.data, { includeNonce })
}
async function getAllValidators(): Promise {
@@ -103,7 +103,7 @@ export function getOasisscanAPIs(url: string | 'https://api.oasisscan.com/mainne
}
}
-export function parseAccount(account: AccountsRow): Account {
+export function parseAccount(account: AccountsRow, { includeNonce = true } = {}): Account {
return {
address: account.address,
allowances: account.allowances.map(allowance => ({
@@ -114,7 +114,7 @@ export function parseAccount(account: AccountsRow): Account {
delegations: parseRoseStringToBaseUnitString(account.escrow),
debonding: parseRoseStringToBaseUnitString(account.debonding),
total: parseRoseStringToBaseUnitString(account.total),
- nonce: BigInt(account.nonce ?? 0).toString(),
+ ...(includeNonce ? { nonce: BigInt(account.nonce ?? 0).toString() } : {}),
}
}
From d344b48918ca6d2cceda3b5b68964125662f3a40 Mon Sep 17 00:00:00 2001
From: Matej Lubej
Date: Thu, 20 Jun 2024 07:58:21 +0200
Subject: [PATCH 09/12] Merge only nonce from tx detail request
---
src/app/state/account/saga.ts | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/app/state/account/saga.ts b/src/app/state/account/saga.ts
index d552e021aa..811f5a9fe6 100644
--- a/src/app/state/account/saga.ts
+++ b/src/app/state/account/saga.ts
@@ -59,13 +59,13 @@ export function* fetchAccount(action: PayloadAction) {
const detailedTransactions = yield* call(() =>
Promise.allSettled(transactions.map(({ hash }) => getTransaction({ hash }))),
)
- const transactionsWithDetails = transactions.map((t, i) => {
+ const transactionsWithUpdatedNonce = transactions.map((t, i) => {
const { status, value } = detailedTransactions[i] as PromiseFulfilledResult
- // Skip failed txs
+ // Skip in case transaction detail request failed
if (status === 'fulfilled') {
return {
...t,
- ...value,
+ nonce: value.nonce,
}
}
@@ -73,7 +73,7 @@ export function* fetchAccount(action: PayloadAction) {
})
yield* put(
- accountActions.transactionsLoaded({ networkType, transactions: transactionsWithDetails }),
+ accountActions.transactionsLoaded({ networkType, transactions: transactionsWithUpdatedNonce }),
)
} catch (e: any) {
console.error('get transactions list failed, continuing without updated list.', e)
From 2ee55c9b9c7d5953b0b36505663c17df291d0ccd Mon Sep 17 00:00:00 2001
From: Matej Lubej
Date: Thu, 27 Jun 2024 07:47:02 +0200
Subject: [PATCH 10/12] Fix transaction detail nonce parsing
---
.../__tests__/index.test.tsx | 38 +++++++++++++++++--
src/app/state/account/selectors.ts | 2 +-
src/vendors/oasisscan.ts | 3 +-
3 files changed, 38 insertions(+), 5 deletions(-)
diff --git a/src/app/pages/AccountPage/Features/TransactionHistory/__tests__/index.test.tsx b/src/app/pages/AccountPage/Features/TransactionHistory/__tests__/index.test.tsx
index 38004f7011..65523a5ffc 100644
--- a/src/app/pages/AccountPage/Features/TransactionHistory/__tests__/index.test.tsx
+++ b/src/app/pages/AccountPage/Features/TransactionHistory/__tests__/index.test.tsx
@@ -35,9 +35,9 @@ const getPendingTx = (hash: string): Transaction => ({
nonce: undefined,
})
-const getTx = (hash: string, nonce: bigint): Transaction => ({
+const getTx = ({ hash = '', nonce = 0n, status = TransactionStatus.Successful } = {}): Transaction => ({
...getPendingTx(hash),
- status: TransactionStatus.Successful,
+ status,
nonce: nonce.toString(),
})
@@ -125,7 +125,7 @@ describe('', () => {
renderCmp(
getState({
accountNonce: 2n,
- accountTxs: [getTx('txHash1', 0n)],
+ accountTxs: [getTx({ hash: 'txHash1', nonce: 0n })],
pendingLocalTxs: [getPendingTx('txHash2')],
}),
)
@@ -136,4 +136,36 @@ describe('', () => {
expect(await screen.findByText('txHash1')).toBeInTheDocument()
expect(await screen.findByText('txHash2')).toBeInTheDocument()
})
+
+ it('should not display pending section in case of failed tx', async () => {
+ renderCmp(
+ getState({
+ accountNonce: 1n,
+ accountTxs: [getTx({ hash: 'txHash1', nonce: 0n, status: TransactionStatus.Failed })],
+ }),
+ )
+
+ expect(() => screen.getByTestId('pending-txs')).toThrow()
+ expect(screen.getByTestId('completed-txs').childElementCount).toBe(1)
+
+ expect(await screen.findByText('txHash1')).toBeInTheDocument()
+
+ expect(() => screen.getByText('account.summary.someTxsInPendingState')).toThrow()
+ })
+
+ it('should not display pending section on initial load', async () => {
+ renderCmp(
+ getState({
+ accountNonce: 1n,
+ accountTxs: [getTx({ hash: 'txHash1', nonce: 0n, status: TransactionStatus.Successful })],
+ }),
+ )
+
+ expect(() => screen.getByTestId('pending-txs')).toThrow()
+ expect(screen.getByTestId('completed-txs').childElementCount).toBe(1)
+
+ expect(await screen.findByText('txHash1')).toBeInTheDocument()
+
+ expect(() => screen.getByText('account.summary.someTxsInPendingState')).toThrow()
+ })
})
diff --git a/src/app/state/account/selectors.ts b/src/app/state/account/selectors.ts
index 9bbde4e7c4..a112c88549 100644
--- a/src/app/state/account/selectors.ts
+++ b/src/app/state/account/selectors.ts
@@ -47,7 +47,7 @@ export const hasAccountUnknownPendingTransactions = createSelector(
undefined as bigint | undefined,
)
- if (!maxNonceFromTxs) {
+ if (maxNonceFromTxs === undefined) {
return BigInt(accountNonce) > 0n
}
diff --git a/src/vendors/oasisscan.ts b/src/vendors/oasisscan.ts
index fc473a2a51..51614d630d 100644
--- a/src/vendors/oasisscan.ts
+++ b/src/vendors/oasisscan.ts
@@ -206,7 +206,8 @@ export function parseTransactionsList(
runtimeName: undefined,
runtimeId: undefined,
round: undefined,
- nonce: (t as OperationsEntity).nonce ? BigInt((t as OperationsEntity).nonce).toString() : undefined,
+ nonce:
+ (t as OperationsEntity).nonce >= 0 ? BigInt((t as OperationsEntity).nonce).toString() : undefined,
}
return parsed
}
From 5e8f2fd35070ea5dc9d94c1456bfc8e44b332893 Mon Sep 17 00:00:00 2001
From: Matej Lubej
Date: Thu, 27 Jun 2024 09:02:46 +0200
Subject: [PATCH 11/12] Fix pending section showing up on initial account load
---
.../pages/AccountPage/Features/TransactionHistory/index.tsx | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/app/pages/AccountPage/Features/TransactionHistory/index.tsx b/src/app/pages/AccountPage/Features/TransactionHistory/index.tsx
index bdf624051d..6a33c09217 100644
--- a/src/app/pages/AccountPage/Features/TransactionHistory/index.tsx
+++ b/src/app/pages/AccountPage/Features/TransactionHistory/index.tsx
@@ -13,6 +13,7 @@ import { useSelector } from 'react-redux'
import {
hasAccountUnknownPendingTransactions,
selectAccountAddress,
+ selectAccountIsLoading,
selectPendingTransactionForAccount,
selectTransactions,
selectTransactionsError,
@@ -34,6 +35,7 @@ export function TransactionHistory() {
const allTransactions = useSelector(selectTransactions)
const transactionsError = useSelector(selectTransactionsError)
const address = useSelector(selectAccountAddress)
+ const accountIsLoading = useSelector(selectAccountIsLoading)
const pendingTransactions = useSelector(selectPendingTransactionForAccount)
const hasUnknownPendingTransactions = useSelector(hasAccountUnknownPendingTransactions)
const network = useSelector(selectSelectedNetwork)
@@ -45,6 +47,8 @@ export function TransactionHistory() {
.filter(({ hash: pendingTxHash }) => !allTransactions.some(({ hash }) => hash === pendingTxHash))
.map(t => )
+ const showPendingSection = !accountIsLoading && !!address
+
return (
{transactionsError && (
@@ -54,7 +58,7 @@ export function TransactionHistory() {
)}
{/* eslint-disable no-restricted-syntax */}
- {(!!pendingTransactionComponents.length || hasUnknownPendingTransactions) && (
+ {showPendingSection && (!!pendingTransactionComponents.length || hasUnknownPendingTransactions) && (
<>
{t('account.summary.pendingTransactions', 'Pending transactions')}
Date: Thu, 4 Jul 2024 09:10:46 +0200
Subject: [PATCH 12/12] Unify Account and WalletBalance interface to always
include nonce
---
.../Features/Account/__tests__/Account.test.tsx | 2 +-
.../AccountSelector/__tests__/index.test.tsx | 2 +-
src/app/lib/getAccountBalanceWithFallback.ts | 10 +++++-----
src/app/pages/AccountPage/index.tsx | 1 +
.../__tests__/index.test.tsx | 6 +++---
src/app/state/account/types.ts | 3 +--
src/app/state/wallet/saga.ts | 8 ++------
src/utils/__fixtures__/test-inputs.ts | 6 ++++++
.../__snapshots__/walletExtensionV0.test.ts.snap | 7 +++++++
src/utils/walletExtensionV0.ts | 3 +++
src/vendors/oasisscan.ts | 12 +++++++-----
11 files changed, 37 insertions(+), 23 deletions(-)
diff --git a/src/app/components/Toolbar/Features/Account/__tests__/Account.test.tsx b/src/app/components/Toolbar/Features/Account/__tests__/Account.test.tsx
index d7d2e955aa..5af4e9ee45 100644
--- a/src/app/components/Toolbar/Features/Account/__tests__/Account.test.tsx
+++ b/src/app/components/Toolbar/Features/Account/__tests__/Account.test.tsx
@@ -7,7 +7,7 @@ import { Account, AccountProps } from '../Account'
const props = {
address: 'oasis1qq3xrq0urs8qcffhvmhfhz4p0mu7ewc8rscnlwxe',
- balance: { available: '200', debonding: '0', delegations: '800', total: '1000' },
+ balance: { available: '200', debonding: '0', delegations: '800', total: '1000', nonce: '0' },
onClick: () => {},
isActive: false,
displayBalance: true,
diff --git a/src/app/components/Toolbar/Features/AccountSelector/__tests__/index.test.tsx b/src/app/components/Toolbar/Features/AccountSelector/__tests__/index.test.tsx
index 165eaf5667..f0830caf73 100644
--- a/src/app/components/Toolbar/Features/AccountSelector/__tests__/index.test.tsx
+++ b/src/app/components/Toolbar/Features/AccountSelector/__tests__/index.test.tsx
@@ -28,7 +28,7 @@ describe('', () => {
wallets: {
oasis1qq3xrq0urs8qcffhvmhfhz4p0mu7ewc8rscnlwxe: {
address: 'oasis1qq3xrq0urs8qcffhvmhfhz4p0mu7ewc8rscnlwxe',
- balance: { available: '100', debonding: '0', delegations: '0', total: '100' },
+ balance: { available: '100', debonding: '0', delegations: '0', total: '100', nonce: '0' },
publicKey: '00',
type: WalletType.UsbLedger,
},
diff --git a/src/app/lib/getAccountBalanceWithFallback.ts b/src/app/lib/getAccountBalanceWithFallback.ts
index c6f70df719..a9144e308e 100644
--- a/src/app/lib/getAccountBalanceWithFallback.ts
+++ b/src/app/lib/getAccountBalanceWithFallback.ts
@@ -3,7 +3,7 @@ import { call } from 'typed-redux-saga'
import { getExplorerAPIs, getOasisNic } from '../state/network/saga'
import { Account } from '../state/account/types'
-function* getBalanceGRPC(address: string, { includeNonce = true } = {}) {
+function* getBalanceGRPC(address: string) {
const nic = yield* call(getOasisNic)
const publicKey = yield* call(addressToPublicKey, address)
const account = yield* call([nic, nic.stakingAccount], { owner: publicKey, height: 0 })
@@ -14,19 +14,19 @@ function* getBalanceGRPC(address: string, { includeNonce = true } = {}) {
delegations: null,
debonding: null,
total: null,
- ...(includeNonce ? { nonce: account.general?.nonce?.toString() ?? '0' } : {}),
+ nonce: account.general?.nonce?.toString() ?? '0',
}
}
-export function* getAccountBalanceWithFallback(address: string, { includeNonce = true } = {}) {
+export function* getAccountBalanceWithFallback(address: string) {
const { getAccount } = yield* call(getExplorerAPIs)
try {
- const account: Account = yield* call(getAccount, address, { includeNonce })
+ const account: Account = yield* call(getAccount, address)
return account
} catch (apiError: any) {
console.error('get account failed, continuing to RPC fallback.', apiError)
try {
- const account: Account = yield* call(getBalanceGRPC, address, { includeNonce })
+ const account: Account = yield* call(getBalanceGRPC, address)
return account
} catch (rpcError) {
console.error('get account with RPC failed, continuing without updated account.', rpcError)
diff --git a/src/app/pages/AccountPage/index.tsx b/src/app/pages/AccountPage/index.tsx
index e1171099be..e0207ace2c 100644
--- a/src/app/pages/AccountPage/index.tsx
+++ b/src/app/pages/AccountPage/index.tsx
@@ -52,6 +52,7 @@ export function AccountPage() {
account.available == null || balanceDelegations == null || balanceDebondingDelegations == null
? null
: (BigInt(account.available) + balanceDelegations + balanceDebondingDelegations).toString(),
+ nonce: account.nonce,
}
// Restart fetching account balances if address or network changes
diff --git a/src/app/pages/OpenWalletPage/Features/ImportAccountsSelectionModal/__tests__/index.test.tsx b/src/app/pages/OpenWalletPage/Features/ImportAccountsSelectionModal/__tests__/index.test.tsx
index 10269e5291..44cb2ae264 100644
--- a/src/app/pages/OpenWalletPage/Features/ImportAccountsSelectionModal/__tests__/index.test.tsx
+++ b/src/app/pages/OpenWalletPage/Features/ImportAccountsSelectionModal/__tests__/index.test.tsx
@@ -52,7 +52,7 @@ describe('', () => {
importAccountsActions.accountsListed([
{
address: 'oasis1qzyqaxestzlum26e2vdgvkerm6d9qgdp7gh2pxqe',
- balance: { available: '0', debonding: '0', delegations: '0', total: '0' },
+ balance: { available: '0', debonding: '0', delegations: '0', total: '0', nonce: '0' },
path: [44, 474, 0],
pathDisplay: `m/44'/474'/0'`,
publicKey: '00',
@@ -77,7 +77,7 @@ describe('', () => {
importAccountsActions.accountsListed([
{
address: 'oasis1qzyqaxestzlum26e2vdgvkerm6d9qgdp7gh2pxqe',
- balance: { available: '0', debonding: '0', delegations: '0', total: '0' },
+ balance: { available: '0', debonding: '0', delegations: '0', total: '0', nonce: '0' },
path: [44, 474, 0],
pathDisplay: `m/44'/474'/0'`,
publicKey: '00',
@@ -86,7 +86,7 @@ describe('', () => {
},
{
address: 'oasis1qqv25adrld8jjquzxzg769689lgf9jxvwgjs8tha',
- balance: { available: '0', debonding: '0', delegations: '0', total: '0' },
+ balance: { available: '0', debonding: '0', delegations: '0', total: '0', nonce: '0' },
path: [44, 474, 1],
pathDisplay: `m/44'/474'/1'`,
publicKey: '00',
diff --git a/src/app/state/account/types.ts b/src/app/state/account/types.ts
index cd3a7b5011..46f0554fbc 100644
--- a/src/app/state/account/types.ts
+++ b/src/app/state/account/types.ts
@@ -11,7 +11,7 @@ export interface BalanceDetails {
delegations: StringifiedBigInt | null
/** This is delayed in getAccount by 20 seconds on oasisscan and 5 seconds on oasismonitor. */
total: StringifiedBigInt | null
- nonce?: StringifiedBigInt | null
+ nonce: StringifiedBigInt
}
export interface Allowance {
@@ -22,7 +22,6 @@ export interface Allowance {
export interface Account extends BalanceDetails {
address: string
allowances?: Allowance[]
- nonce?: StringifiedBigInt
}
/* --- STATE --- */
diff --git a/src/app/state/wallet/saga.ts b/src/app/state/wallet/saga.ts
index 76f7a0afa2..7f82b19c69 100644
--- a/src/app/state/wallet/saga.ts
+++ b/src/app/state/wallet/saga.ts
@@ -48,13 +48,11 @@ export function* openWalletsFromLedger({ payload }: PayloadAction {
+ async function getAccount(address: string): Promise {
const account = await accounts.getAccount({ accountId: address })
if (!account || account.code !== 0) throw new Error('Wrong response code') // TODO
- return parseAccount(account.data, { includeNonce })
+ return parseAccount(account.data)
}
async function getAllValidators(): Promise {
@@ -103,7 +103,7 @@ export function getOasisscanAPIs(url: string | 'https://api.oasisscan.com/mainne
}
}
-export function parseAccount(account: AccountsRow, { includeNonce = true } = {}): Account {
+export function parseAccount(account: AccountsRow): Account {
return {
address: account.address,
allowances: account.allowances.map(allowance => ({
@@ -114,7 +114,7 @@ export function parseAccount(account: AccountsRow, { includeNonce = true } = {})
delegations: parseRoseStringToBaseUnitString(account.escrow),
debonding: parseRoseStringToBaseUnitString(account.debonding),
total: parseRoseStringToBaseUnitString(account.total),
- ...(includeNonce ? { nonce: BigInt(account.nonce ?? 0).toString() } : {}),
+ nonce: BigInt(account.nonce ?? 0).toString(),
}
}
@@ -207,7 +207,9 @@ export function parseTransactionsList(
runtimeId: undefined,
round: undefined,
nonce:
- (t as OperationsEntity).nonce >= 0 ? BigInt((t as OperationsEntity).nonce).toString() : undefined,
+ (t as OperationsEntity).nonce == null
+ ? undefined
+ : BigInt((t as OperationsEntity).nonce).toString(),
}
return parsed
}