From 2aa56777f3e23ee8b90960628493791d925f7f18 Mon Sep 17 00:00:00 2001 From: arobsn <87387688+arobsn@users.noreply.github.com> Date: Sat, 14 Sep 2024 15:44:05 -0300 Subject: [PATCH 01/28] move pending transactions fetching to `walletStore` --- .../ergo/transaction/interpreter/utils.ts | 55 ++++++++- src/stores/walletStore.ts | 27 +++++ src/types/transactions.ts | 21 ++++ src/views/TransactionHistory.vue | 105 ++---------------- 4 files changed, 114 insertions(+), 94 deletions(-) create mode 100644 src/types/transactions.ts diff --git a/src/chains/ergo/transaction/interpreter/utils.ts b/src/chains/ergo/transaction/interpreter/utils.ts index 4b9addfe..93e82164 100644 --- a/src/chains/ergo/transaction/interpreter/utils.ts +++ b/src/chains/ergo/transaction/interpreter/utils.ts @@ -1,8 +1,16 @@ -import { Amount, TokenAmount } from "@fleet-sdk/common"; +import { Amount, BoxSummary, TokenAmount, utxoDiff } from "@fleet-sdk/common"; +import { + ChainProviderConfirmedTransaction, + ChainProviderUnconfirmedTransaction +} from "@fleet-sdk/blockchain-providers"; +import { FEE_CONTRACT } from "@fleet-sdk/core"; +import { BigNumber } from "bignumber.js"; import { OutputAsset } from "@/chains/ergo/transaction/interpreter/outputInterpreter"; import { Token } from "@/types/connector"; import { AssetsMetadataMap } from "@/types/internal"; import { bn, decimalize } from "@/common/bigNumber"; +import { ERG_DECIMALS, ERG_TOKEN_ID } from "@/constants/ergo"; +import { ConfirmedTransactionSummary, UnconfirmedTransactionSummary } from "@/types/transactions"; export const tokensToOutputAssets = ( tokens: Token[], @@ -30,3 +38,48 @@ export const tokenAmountToToken = ( name: metadata.get(tokenAmount.tokenId)?.name }; }; + +export function summarizeTransaction( + transaction: ChainProviderConfirmedTransaction, + ergoTrees: Set +): ConfirmedTransactionSummary; +export function summarizeTransaction( + transaction: ChainProviderUnconfirmedTransaction, + ergoTrees: Set +): UnconfirmedTransactionSummary; +export function summarizeTransaction( + transaction: + | ChainProviderConfirmedTransaction + | ChainProviderUnconfirmedTransaction, + ergoTrees: Set +): ConfirmedTransactionSummary | UnconfirmedTransactionSummary { + const ownInputs = transaction.inputs.filter((x) => ergoTrees.has(x.ergoTree)); + const ownOutputs = transaction.outputs.filter((x) => ergoTrees.has(x.ergoTree)); + + const summary = { + transactionId: transaction.transactionId, + timestamp: transaction.timestamp, + fee: decimalize( + new BigNumber(transaction.outputs.find((x) => x.ergoTree === FEE_CONTRACT)?.value ?? 0), + ERG_DECIMALS + ), + delta: mapDelta(utxoDiff(ownOutputs, ownInputs)), + confirmed: transaction.confirmed, + height: "height" in transaction ? transaction.height : undefined + }; + + return transaction.confirmed + ? (summary as ConfirmedTransactionSummary) + : ({ ...summary, transaction } as UnconfirmedTransactionSummary); +} + +function mapDelta(utxoSummary: BoxSummary): TokenAmount[] { + const tokens = utxoSummary.tokens.map((x) => ({ + tokenId: x.tokenId, + amount: bn(x.amount.toString()) + })); + + return utxoSummary.nanoErgs === 0n + ? tokens + : [{ tokenId: ERG_TOKEN_ID, amount: bn(utxoSummary.nanoErgs.toString()) }, ...tokens]; +} diff --git a/src/stores/walletStore.ts b/src/stores/walletStore.ts index b4e2d8c3..45a6e762 100644 --- a/src/stores/walletStore.ts +++ b/src/stores/walletStore.ts @@ -3,6 +3,7 @@ import { computed, ref, shallowRef, toRaw, watch } from "vue"; import { groupBy, maxBy } from "lodash-es"; import type { BigNumber } from "bignumber.js"; import { useRouter } from "vue-router"; +import { ErgoAddress } from "@fleet-sdk/core"; import { bn, decimalize, sumBy } from "../common/bigNumber"; import { MIN_SYNC_INTERVAL } from "../constants/intervals"; import { useAppStore } from "./appStore"; @@ -26,6 +27,8 @@ import { hdKeyPool } from "@/common/objectPool"; import HdKey, { IndexedAddress } from "@/chains/ergo/hdKey"; import { graphQLService } from "@/chains/ergo/services/graphQlService"; import { patchArray } from "@/common/reactivity"; +import { UnconfirmedTransactionSummary } from "@/types/transactions"; +import { summarizeTransaction } from "@/chains/ergo/transaction/interpreter/utils"; export type StateAssetSummary = { tokenId: string; @@ -35,6 +38,7 @@ export type StateAssetSummary = { }; const usePrivateStateStore = defineStore("_wallet", () => { + const pendingTransactions = shallowRef([]); const addresses = shallowRef([]); const assets = shallowRef([]); @@ -60,6 +64,7 @@ const usePrivateStateStore = defineStore("_wallet", () => { chainCode: ref(""), lastSynced: ref(0), hasOldUtxos: ref(false), + pendingTransactions, addresses, assets, patchAddresses, @@ -224,6 +229,10 @@ export const useWalletStore = defineStore("wallet", () => { const artworkBalance = computed(() => balance.value.filter(artwork)); const nonArtworkBalance = computed(() => balance.value.filter((x) => !artwork(x))); + const addressesStr = computed(() => addresses.value.map((x) => x.script)); + const ergoTrees = computed( + () => new Set(addresses.value.map((x) => ErgoAddress.decodeUnsafe(x.script).ergoTree)) + ); // #region public actions async function load(walletId: number, opt = { syncInBackground: true }) { @@ -238,6 +247,7 @@ export const useWalletStore = defineStore("wallet", () => { privateState.chainCode = wlt.chainCode; privateState.lastSynced = wlt.lastSynced ?? 0; privateState.hasOldUtxos = false; + privateState.pendingTransactions = []; name.value = wlt.name; settings.value = wlt.settings; @@ -261,6 +271,7 @@ export const useWalletStore = defineStore("wallet", () => { } else { await sync(); } + fetchUnconfirmedTransactions(); setLoading(false); } @@ -370,6 +381,21 @@ export const useWalletStore = defineStore("wallet", () => { setSyncing(false); } + async function fetchUnconfirmedTransactions() { + if (addressesStr.value.length === 0) return; + + const response = await graphQLService.getUnconfirmedTransactions({ + where: { addresses: addressesStr.value } + }); + + const txns = response.map((x) => summarizeTransaction(x, ergoTrees.value)); + privateState.pendingTransactions = txns; + + if (txns.length > 0) { + assetsStore.loadMetadata(txns.flatMap((x) => x.delta.map((y) => y.tokenId))); + } + } + function setSyncing(value: boolean) { privateState.syncing = value; } @@ -385,6 +411,7 @@ export const useWalletStore = defineStore("wallet", () => { chainCode: computed(() => privateState.chainCode), loading: computed(() => privateState.loading), syncing: computed(() => privateState.syncing), + pendingTransactions: computed(() => privateState.pendingTransactions), health, name, settings, diff --git a/src/types/transactions.ts b/src/types/transactions.ts new file mode 100644 index 00000000..63871074 --- /dev/null +++ b/src/types/transactions.ts @@ -0,0 +1,21 @@ +import { ChainProviderUnconfirmedTransaction } from "@fleet-sdk/blockchain-providers"; +import { BigNumber } from "bignumber.js"; +import { TokenAmount } from "@fleet-sdk/common"; +import { BasicAssetMetadata } from "./internal"; + +export type TransactionSummary = { + transactionId: string; + timestamp: number; + fee: BigNumber; + delta: (TokenAmount & { metadata?: BasicAssetMetadata })[]; +}; + +export type ConfirmedTransactionSummary = TransactionSummary & { + confirmed: true; + height: number; +}; + +export type UnconfirmedTransactionSummary = TransactionSummary & { + confirmed: false; + transaction: ChainProviderUnconfirmedTransaction; +}; diff --git a/src/views/TransactionHistory.vue b/src/views/TransactionHistory.vue index 8a393262..cab0b666 100644 --- a/src/views/TransactionHistory.vue +++ b/src/views/TransactionHistory.vue @@ -1,40 +1,20 @@