Skip to content
This repository was archived by the owner on Sep 25, 2025. It is now read-only.

Commit 29ef7c0

Browse files
authored
Merge pull request #1271 from emeraldpay/sort-history-txs
solution: sort transaction history correctly
2 parents d4b41f8 + 4e608db commit 29ef7c0

12 files changed

Lines changed: 120 additions & 146 deletions

File tree

.eslintignore

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
# Always ignore node_modules.
44
**/node_modules/
55

6-
packages/*/lib
7-
packages/*/coverage
8-
packages/desktop/app
6+
packages/*/coverage/
7+
packages/*/lib/
8+
packages/desktop/app/

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828
"packages/*"
2929
],
3030
"resolutions": {
31-
"@emeraldpay/api": "https://artifacts.emerald.cash/builds/emerald-api-js/4da5396f/emeraldpay-api-v0.3.1.tgz",
32-
"@emeraldpay/api-node": "https://artifacts.emerald.cash/builds/emerald-api-js/4da5396f/emeraldpay-api-node-v0.3.1.tgz",
31+
"@emeraldpay/api": "https://artifacts.emerald.cash/builds/emerald-api-js/e089d955/emeraldpay-api-v0.3.2.tgz",
32+
"@emeraldpay/api-node": "https://artifacts.emerald.cash/builds/emerald-api-js/e089d955/emeraldpay-api-node-v0.3.2.tgz",
3333
"@emeraldpay/emerald-vault-core": "https://artifacts.emerald.cash/builds/vault-node/350c3905/emeraldpay-emerald-vault-core-v0.11.0-dev.tgz",
3434
"@emeraldpay/emerald-vault-native": "https://artifacts.emerald.cash/builds/vault-node/350c3905/emeraldpay-emerald-vault-native-v0.11.0-dev.tgz"
3535
}

packages/core/src/persistentState.ts

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -103,16 +103,11 @@ export interface Change {
103103
}
104104

105105
export interface UnconfirmedTransaction {
106-
107106
blockchain: number;
108-
txId: string;
109-
version?: number;
110-
111107
/**
112108
* Changes and references to the user wallets
113109
*/
114110
changes: Change[];
115-
116111
/**
117112
* Application state of the transaction. In general, it's PREPARED->SUBMITTED->CONFIRMED, but have other states
118113
*/
@@ -127,6 +122,8 @@ export interface UnconfirmedTransaction {
127122
* It may be OK or FAILED. Before the inclusion it's UNKNOWN
128123
*/
129124
status: Status;
125+
txId: string;
126+
version?: number;
130127
}
131128

132129
export interface TransactionConfirmation {
@@ -152,10 +149,9 @@ export type ConfirmedTransaction = UnconfirmedTransaction & TransactionConfirmat
152149
export type Transaction = UnconfirmedTransaction | ConfirmedTransaction;
153150

154151
export function isConfirmed(tx: Transaction): tx is ConfirmedTransaction {
155-
return typeof tx == "object" && Object.keys(tx).indexOf("block") >= 0;
152+
return typeof tx === 'object' && Object.keys(tx).indexOf('block') >= 0;
156153
}
157154

158-
159155
/**
160156
* Criteria to select transactions when queried
161157
*/
@@ -342,7 +338,6 @@ export interface Balance {
342338
* Timestamp when it was cached. Set automatically when added to the persistent state.
343339
*/
344340
timestamp?: Date | undefined;
345-
346341
/**
347342
* Individual components of the balance for the current address.
348343
* Must contain only _unspent_ transactions and the total amount MUST equal the main `amount` field of the `Balance` object.
@@ -357,9 +352,9 @@ export interface Balance {
357352
* Bitcoin UTXO details
358353
*/
359354
export interface Utxo {
355+
amount: string;
360356
txid: string;
361357
vout: number;
362-
amount: string;
363358
}
364359

365360
/**
@@ -390,14 +385,12 @@ export interface Cache {
390385
* @param ttl_seconds (optionally) a Time To Live for that value. Note that the cache purges the data only occasionally and data may live longer in the cache
391386
*/
392387
put(id: string, value: string, ttl_seconds?: number | undefined): Promise<void>;
393-
394388
/**
395389
* Get known value. Return null if cache has no `value` for the specified `id`
396390
*
397391
* @param id id for the data
398392
*/
399393
get(id: string): Promise<string | null>;
400-
401394
/**
402395
* Remove a value for the specified `id` from the cache
403396
*
@@ -415,6 +408,10 @@ export interface PersistentState {
415408
* Manage current balance cache
416409
*/
417410
balances: Balances;
411+
/**
412+
* Generic cache
413+
*/
414+
cache: Cache;
418415
/**
419416
* Manage Transaction History
420417
*/
@@ -427,9 +424,4 @@ export interface PersistentState {
427424
* Manage XPub position
428425
*/
429426
xpubpos: XPubPosition;
430-
431-
/**
432-
* Generic cache
433-
*/
434-
cache: Cache;
435427
}

packages/electron-app/src/application/ipc/application.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,9 @@ export function setupApiIpc(app: Application, apiAccess: EmeraldApiAccess): void
112112
ipcMain.handle(IpcCommands.BROADCAST_TX, (event, blockchain: BlockchainCode, tx: string) => {
113113
if (isEthereum(blockchain)) {
114114
return app.rpc.chain(blockchain).eth.sendRawTransaction(tx);
115-
} else if (isBitcoin(blockchain)) {
115+
}
116+
117+
if (isBitcoin(blockchain)) {
116118
return new Promise((resolve, reject) => {
117119
apiAccess.blockchainClient
118120
.nativeCall(blockchainCodeToId(blockchain), [
@@ -122,24 +124,24 @@ export function setupApiIpc(app: Application, apiAccess: EmeraldApiAccess): void
122124
payload: [tx],
123125
},
124126
])
125-
.onData((resp) => {
126-
if (isNativeCallResponse(resp)) {
127-
const hash = resp.payload as string;
127+
.onData((response) => {
128+
if (isNativeCallResponse(response)) {
129+
const hash = response.payload as string;
128130

129131
log.info(`Broadcast transaction: ${hash}`);
130132

131133
resolve(hash);
132-
} else if (isNativeCallError(resp)) {
133-
reject(resp.message);
134+
} else if (isNativeCallError(response)) {
135+
reject(response.message);
134136
} else {
135137
reject('Invalid response from API');
136138
}
137139
})
138140
.onError((err) => reject(err.message));
139141
});
140-
} else {
141-
log.error('Invalid blockchain: ' + blockchain);
142142
}
143+
144+
log.error(`Invalid blockchain: ${blockchain}`);
143145
});
144146

145147
ipcMain.handle(IpcCommands.GET_BALANCE, (event, blockchain: BlockchainCode, address: string, tokens: string[]) => {

packages/react-app/src/create-account/UnlockSeed.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
import { Uuid } from '@emeraldpay/emerald-vault-core';
22
import { accounts } from '@emeraldwallet/store';
33
import { Button, PasswordInput } from '@emeraldwallet/ui';
4-
import { Box, Grid, Typography } from '@material-ui/core';
4+
import { Box, Grid, Typography, createStyles, makeStyles } from '@material-ui/core';
55
import { Alert } from '@material-ui/lab';
66
import * as React from 'react';
77
import { connect } from 'react-redux';
88

9+
const useStyles = makeStyles(
10+
createStyles({
11+
alert: {
12+
marginTop: 20,
13+
},
14+
}),
15+
);
16+
917
interface OwnProps {
1018
seedId: Uuid;
1119
onUnlock(password: string): void;
@@ -16,6 +24,8 @@ interface DispatchProps {
1624
}
1725

1826
const Component: React.FC<OwnProps & DispatchProps> = ({ seedId, onUnlock, verifyPassword }) => {
27+
const styles = useStyles();
28+
1929
const [password, setPassword] = React.useState('');
2030

2131
const [invalid, setInvalid] = React.useState(false);
@@ -60,7 +70,9 @@ const Component: React.FC<OwnProps & DispatchProps> = ({ seedId, onUnlock, verif
6070
</Grid>
6171
{invalid && (
6272
<Grid item xs={12}>
63-
<Alert severity="error">Invalid password</Alert>
73+
<Alert className={styles.alert} severity="error">
74+
Invalid password
75+
</Alert>
6476
</Grid>
6577
)}
6678
</Grid>

packages/react-app/src/transaction/CreateBitcoinTransaction/Confirm.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const useStyles = makeStyles(
2121

2222
interface OwnProps {
2323
blockchain: BlockchainCode;
24+
disabled?: boolean;
2425
entryId: EntryId;
2526
rawTx: string;
2627
onConfirm(): void;
@@ -30,7 +31,14 @@ interface DispatchProps {
3031
onCancel(): void;
3132
}
3233

33-
const Confirm: React.FC<OwnProps & DispatchProps> = ({ entryId, blockchain, rawTx, onCancel, onConfirm }) => {
34+
const Confirm: React.FC<OwnProps & DispatchProps> = ({
35+
entryId,
36+
blockchain,
37+
rawTx,
38+
onCancel,
39+
onConfirm,
40+
disabled = false,
41+
}) => {
3442
const styles = useStyles();
3543

3644
const [showRaw, setShowRaw] = React.useState(false);
@@ -58,7 +66,7 @@ const Confirm: React.FC<OwnProps & DispatchProps> = ({ entryId, blockchain, rawT
5866
<FormRow last>
5967
<ButtonGroup classes={{ container: styles.buttons }}>
6068
<Button label="Cancel" onClick={onCancel} />
61-
<Button label="Send" primary onClick={onConfirm} />
69+
<Button label="Send" primary disabled={disabled} onClick={onConfirm} />
6270
</ButtonGroup>
6371
</FormRow>
6472
</>

packages/react-app/src/transaction/CreateBitcoinTransaction/CreateBitcoinTransaction.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ const CreateBitcoinTransaction: React.FC<OwnProps & StateProps & DispatchProps>
154154
}
155155

156156
return (
157-
<Page title={'Create Bitcoin Transaction'} leftIcon={<Back onClick={onCancel} />}>
157+
<Page title="Create Bitcoin Transaction" leftIcon={<Back onClick={onCancel} />}>
158158
{content}
159159
</Page>
160160
);

packages/react-app/src/transaction/ModifyBitcoinTransaction/ModifyBitcoinTransaction.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ const ModifyBitcoinTransaction: React.FC<OwnProps & DispatchProps> = ({
154154
const onSignTransaction = async (): Promise<void> => {
155155
setPasswordError(undefined);
156156

157+
if (tx.state > State.SUBMITTED) {
158+
return;
159+
}
160+
157161
if (restoredTx != null) {
158162
if (isHardware) {
159163
signTransaction(restoredTx.create(), (txId, signed) => {
@@ -306,7 +310,7 @@ const ModifyBitcoinTransaction: React.FC<OwnProps & DispatchProps> = ({
306310
<Button label="Cancel" onClick={goBack} />
307311
<Button
308312
primary
309-
disabled={initializing && tx.state > State.SUBMITTED}
313+
disabled={initializing || tx.state > State.SUBMITTED}
310314
label="Create Transaction"
311315
onClick={() => setStage(Stages.SIGN)}
312316
/>
@@ -334,7 +338,7 @@ const ModifyBitcoinTransaction: React.FC<OwnProps & DispatchProps> = ({
334338
{!isHardware && (
335339
<Button
336340
primary
337-
disabled={password.length === 0}
341+
disabled={password.length === 0 || tx.state > State.SUBMITTED}
338342
label="Sign Transaction"
339343
onClick={onSignTransaction}
340344
/>
@@ -346,6 +350,7 @@ const ModifyBitcoinTransaction: React.FC<OwnProps & DispatchProps> = ({
346350
{stage === Stages.CONFIRM && entryId != null && signedRawTx != null && signedTxId != null && (
347351
<Confirm
348352
blockchain={blockchainCode}
353+
disabled={tx.state > State.SUBMITTED}
349354
entryId={entryId}
350355
rawTx={signedRawTx}
351356
onConfirm={() =>

packages/react-app/src/transactions/TxHistory/List/List.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,17 @@ const TransactionsList: React.FC<OwnProps> = ({
7272
};
7373

7474
React.useEffect(() => {
75-
if (transactions.length > initialTxCount.current) {
76-
setTxIndex(undefined);
77-
}
78-
7975
const { current: list } = listElement;
8076

8177
if (list != null) {
8278
Array.from({ length: transactions.length }).forEach((item, index) => list.recomputeRowHeights(index));
8379
}
80+
}, [transactions]);
81+
82+
React.useEffect(() => {
83+
if (transactions.length > initialTxCount.current) {
84+
setTxIndex(undefined);
85+
}
8486
}, [transactions.length]);
8587

8688
return transactions.length > 0 ? (

packages/services/src/services/transactions/TxService.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -185,22 +185,18 @@ export class TxService implements IService {
185185
log.error(`Error while removing transaction for address ${identifier} from state:`, error),
186186
);
187187
} else {
188-
189188
let confirmation: PersistentState.TransactionConfirmation | null = null;
189+
190190
const now = new Date();
191191

192192
if (tx.block != null) {
193193
const { height, timestamp, hash: blockId } = tx.block;
194194

195195
confirmation = {
196-
block: {
197-
blockId,
198-
height,
199-
timestamp,
200-
},
201-
blockPos: 0, //TODO get transaction index inside the block
196+
block: { blockId, height, timestamp },
197+
blockPos: 0, // TODO Get transaction index inside the block
202198
confirmTimestamp: tx.block.timestamp ?? now,
203-
}
199+
};
204200
}
205201

206202
const blockchainCode = blockchainIdToCode(blockchain);
@@ -211,7 +207,7 @@ export class TxService implements IService {
211207
.catch((error) => log.error(`Error while set xPub position for address ${identifier}:`, error));
212208
}
213209

214-
let baseTransaction: PersistentState.UnconfirmedTransaction = {
210+
const transaction: PersistentState.UnconfirmedTransaction = {
215211
blockchain,
216212
changes: tx.changes.map<PersistentState.Change>((change) => {
217213
const asset =
@@ -232,10 +228,10 @@ export class TxService implements IService {
232228
state: tx.mempool ? State.SUBMITTED : State.CONFIRMED,
233229
status: tx.failed ? Status.FAILED : Status.OK,
234230
txId: tx.txId,
235-
}
231+
};
236232

237233
this.persistentState.txhistory
238-
.submit({...baseTransaction, ...confirmation})
234+
.submit({ ...confirmation, ...transaction })
239235
.then((merged) => {
240236
if (tx.cursor != null && tx.cursor.length > 0) {
241237
this.persistentState.txhistory

0 commit comments

Comments
 (0)