vega-frontend-monorepo/libs/wallet/src/use-vega-transaction-store.tsx

227 lines
7.7 KiB
TypeScript

import produce from 'immer';
import type { Transaction } from './connectors';
import {
isWithdrawTransaction,
isOrderSubmissionTransaction,
isOrderCancellationTransaction,
isOrderAmendmentTransaction,
isBatchMarketInstructionsTransaction,
isTransferTransaction,
isStopOrdersSubmissionTransaction,
isStopOrdersCancellationTransaction,
} from './connectors';
import { determineId } from './utils';
import { create } from 'zustand';
import type {
TransactionEventFieldsFragment,
WithdrawalBusEventFieldsFragment,
OrderTxUpdateFieldsFragment,
} from './__generated__/TransactionResult';
import type { WithdrawalApprovalQuery } from './__generated__/WithdrawalApproval';
import { subscribeWithSelector } from 'zustand/middleware';
import type { VegaTxState } from './types';
import { VegaTxStatus } from './types';
export interface VegaStoredTxState extends VegaTxState {
id: number;
createdAt: Date;
updatedAt: Date;
body: Transaction;
transactionResult?: TransactionEventFieldsFragment;
withdrawal?: WithdrawalBusEventFieldsFragment;
withdrawalApproval?: WithdrawalApprovalQuery['erc20WithdrawalApproval'];
order?: OrderTxUpdateFieldsFragment;
}
export interface VegaTransactionStore {
transactions: (VegaStoredTxState | undefined)[];
create: (tx: Transaction, order?: OrderTxUpdateFieldsFragment) => number;
update: (
index: number,
update: Partial<
Pick<VegaStoredTxState, 'status' | 'txHash' | 'signature' | 'error'>
>
) => void;
dismiss: (index: number) => void;
delete: (index: number) => void;
updateWithdrawal: (
withdrawal: NonNullable<VegaStoredTxState['withdrawal']>,
withdrawalApproval: NonNullable<VegaStoredTxState['withdrawalApproval']>
) => void;
updateOrder: (order: OrderTxUpdateFieldsFragment) => void;
updateTransactionResult: (
transactionResult: TransactionEventFieldsFragment
) => void;
}
export const useVegaTransactionStore = create<VegaTransactionStore>()(
subscribeWithSelector((set, get) => ({
transactions: [] as VegaStoredTxState[],
create: (body: Transaction, order?: OrderTxUpdateFieldsFragment) => {
const transactions = get().transactions;
const now = new Date();
const transaction: VegaStoredTxState = {
id: transactions.length,
createdAt: now,
updatedAt: now,
body,
error: null,
txHash: null,
signature: null,
status: VegaTxStatus.Requested,
dialogOpen: true,
order,
};
set({ transactions: transactions.concat(transaction) });
return transaction.id;
},
update: (index: number, update: Partial<VegaStoredTxState>) => {
set(
produce((state: VegaTransactionStore) => {
const transaction = state.transactions[index];
if (transaction) {
Object.assign(transaction, update);
transaction.dialogOpen = true;
transaction.updatedAt = new Date();
}
})
);
},
dismiss: (index: number) => {
set(
produce((state: VegaTransactionStore) => {
const transaction = state.transactions[index];
if (transaction) {
transaction.dialogOpen = false;
transaction.updatedAt = new Date();
}
})
);
},
delete: (index: number) => {
set(
produce((state: VegaTransactionStore) => {
delete state.transactions[index];
})
);
},
updateWithdrawal: (
withdrawal: NonNullable<VegaStoredTxState['withdrawal']>,
withdrawalApproval: NonNullable<VegaStoredTxState['withdrawalApproval']>
) => {
set(
produce((state: VegaTransactionStore) => {
const transaction = state.transactions.find(
(transaction) =>
transaction &&
transaction.status === VegaTxStatus.Pending &&
transaction.signature &&
isWithdrawTransaction(transaction?.body) &&
withdrawal.id === determineId(transaction.signature)
);
if (transaction) {
transaction.withdrawal = withdrawal;
transaction.withdrawalApproval = withdrawalApproval;
transaction.status = VegaTxStatus.Complete;
transaction.dialogOpen = true;
transaction.updatedAt = new Date();
}
})
);
},
updateOrder: (order) => {
set(
produce((state: VegaTransactionStore) => {
const transaction = state.transactions.find((transaction) => {
if (!transaction || transaction.status !== VegaTxStatus.Pending) {
return false;
}
if (
isOrderSubmissionTransaction(transaction?.body) &&
transaction.signature &&
order.id === determineId(transaction.signature)
) {
return true;
}
if (
isOrderCancellationTransaction(transaction?.body) &&
order.id === transaction.body.orderCancellation.orderId
) {
return true;
}
if (
isOrderAmendmentTransaction(transaction?.body) &&
order.id === transaction.body.orderAmendment.orderId
) {
return true;
}
if (
isBatchMarketInstructionsTransaction(transaction?.body) &&
transaction.signature &&
order.id === determineId(transaction.signature)
) {
return true;
}
return false;
});
if (transaction) {
// TODO: handle multiple orders from batch market instructions
// Note: If multiple orders are submitted the first order ID is determined by hashing the signature of the transaction
// (see determineId function). For each subsequent order's ID, a hash of the previous orders ID is used
transaction.order =
isOrderAmendmentTransaction(transaction?.body) &&
transaction.order
? transaction.order // the transaction.order would be the original order amended
: order;
transaction.status = VegaTxStatus.Complete;
transaction.dialogOpen = true;
transaction.updatedAt = new Date();
}
})
);
},
updateTransactionResult: (
transactionResult: TransactionEventFieldsFragment
) => {
set(
produce((state: VegaTransactionStore) => {
const transaction = state.transactions.find(
(transaction) =>
transaction?.txHash &&
transaction.txHash.toLowerCase() ===
transactionResult.hash.toLowerCase()
);
if (transaction) {
transaction.transactionResult = transactionResult;
const isConfirmedOrderCancellation =
isOrderCancellationTransaction(transaction.body) &&
!transaction.body.orderCancellation.orderId;
const isConfirmedTransfer = isTransferTransaction(transaction.body);
const isConfirmedStopOrderCancellation =
isStopOrdersCancellationTransaction(transaction.body);
const isConfirmedStopOrderSubmission =
isStopOrdersSubmissionTransaction(transaction.body);
if (
(isConfirmedOrderCancellation ||
isConfirmedTransfer ||
isConfirmedStopOrderCancellation ||
isConfirmedStopOrderSubmission) &&
!transactionResult.error &&
transactionResult.status
) {
transaction.status = VegaTxStatus.Complete;
}
transaction.dialogOpen = true;
transaction.updatedAt = new Date();
}
})
);
},
}))
);