Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transaction interpretation helpers #55

Merged
merged 11 commits into from
Dec 23, 2024
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [[0.0.0-alpha.10](https://github.com/multiversx/mx-sdk-dapp-core/pull/56)] - 2024-12-23

- [Added transactions interpretation helpers](https://github.com/multiversx/mx-sdk-dapp-core/pull/55)
- [Added transaction toasts](https://github.com/multiversx/mx-sdk-dapp-core/pull/53)
- [Added transactions helpers](https://github.com/multiversx/mx-sdk-dapp-core/pull/52)
- [Added transactions tracking](https://github.com/multiversx/mx-sdk-dapp-core/pull/51)
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@multiversx/sdk-dapp-core",
"version": "0.0.0-alpha.11",
"version": "0.0.0-alpha.10",
"main": "out/index.js",
"module": "out/index.js",
"types": "out/index.d.ts",
Expand Down Expand Up @@ -33,13 +33,16 @@
},
"dependencies": {
"@lifeomic/axios-fetch": "3.0.1",
"@multiversx/sdk-core": ">= 13.5.0",
"@multiversx/sdk-dapp-utils": ">= 1.0.2",
"@multiversx/sdk-extension-provider": "4.0.0",
"@multiversx/sdk-hw-provider": "7.0.0",
"@multiversx/sdk-metamask-provider": "1.0.0",
"@multiversx/sdk-native-auth-client": "^1.0.8",
"@multiversx/sdk-opera-provider": "1.0.0-alpha.1",
"@multiversx/sdk-wallet": "4.5.1",
"@multiversx/sdk-wallet-connect-provider": "5.0.1",
"@multiversx/sdk-web-wallet-cross-window-provider": ">= 2.0.4",
"@multiversx/sdk-web-wallet-iframe-provider": "2.0.1",
"@multiversx/sdk-web-wallet-provider": "3.2.1",
"isomorphic-fetch": "3.0.0",
Expand All @@ -64,9 +67,6 @@
},
"devDependencies": {
"@eslint/js": "9.15.0",
"@multiversx/sdk-core": ">= 13.5.0",
"@multiversx/sdk-dapp-utils": ">= 1.0.2",
"@multiversx/sdk-web-wallet-cross-window-provider": ">= 2.0.4",
"@swc/core": "^1.4.17",
"@swc/jest": "^0.2.36",
"@types/jest": "29.5.13",
Expand Down
2 changes: 1 addition & 1 deletion src/apiCalls/configuration/getNetworkConfigFromApi.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import axios from 'axios';
import { ApiNetworkConfigType } from 'types/network.types';
import { NETWORK_CONFIG_ENDPOINT } from '../endpoints';
import { getCleanApiAddress } from '../utils/getCleanApiAddress';
import { getCleanApiAddress } from '../utils';
razvantomegea marked this conversation as resolved.
Show resolved Hide resolved

const urlIsValid = (url: string) => {
try {
Expand Down
4 changes: 4 additions & 0 deletions src/apiCalls/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
export * from './account';
export * from './configuration';
export * from './endpoints';
export * from './tokens';
arhtudormorar marked this conversation as resolved.
Show resolved Hide resolved
export * from './utils';
export * from './websocket';
60 changes: 60 additions & 0 deletions src/apiCalls/tokens/getTokenDetails.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { NFTS_ENDPOINT, TOKENS_ENDPOINT } from 'apiCalls/endpoints';
import { axiosInstance } from 'apiCalls/utils/axiosInstance';
import { networkSelector } from 'store/selectors';
import { getState } from 'store/store';
import { TokenOptionType, TokenInfoResponse } from 'types/tokens.types';
import { getIdentifierType } from 'utils/validation/getIdentifierType';

export async function getTokenDetails({
tokenId
}: {
tokenId: string;
}): Promise<TokenOptionType> {
const network = networkSelector(getState());
const { isNft } = getIdentifierType(tokenId);

const tokenIdentifier = tokenId;
const tokenEndpoint = isNft ? NFTS_ENDPOINT : TOKENS_ENDPOINT;

if (!tokenIdentifier) {
return {
tokenDecimals: Number(network.decimals),
tokenLabel: '',
tokenAvatar: ''
};
}

try {
const { data: selectedToken } = await axiosInstance.get<TokenInfoResponse>(
`${network.apiAddress}/${tokenEndpoint}/${tokenIdentifier}`
);

const tokenDecimals = selectedToken
? selectedToken?.decimals
: Number(network.decimals);
const tokenLabel = selectedToken ? selectedToken?.name : '';
const tokenAvatar = selectedToken
? selectedToken?.assets?.svgUrl ?? selectedToken?.media?.[0]?.thumbnailUrl
: '';

return {
tokenDecimals: tokenDecimals,
tokenLabel,
type: selectedToken?.type,
tokenAvatar,
identifier: selectedToken?.identifier,
assets: selectedToken?.assets,
esdtPrice: selectedToken?.price,
ticker: selectedToken?.ticker,
name: selectedToken?.name
};
} catch (error: any) {
return {
error: `Error fetching token ${tokenIdentifier}: ${error.toString()}`,
tokenDecimals: Number(network.decimals),
tokenLabel: '',
tokenAvatar: '',
identifier: tokenIdentifier
};
}
}
1 change: 1 addition & 0 deletions src/apiCalls/tokens/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './getTokenDetails';
2 changes: 1 addition & 1 deletion src/apiCalls/transactions/getTransactionByHash.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import axios from 'axios';
import { TRANSACTIONS_ENDPOINT } from 'apiCalls/endpoints';
import axios from 'axios';
import { networkSelector } from 'store/selectors';
import { getState } from 'store/store';
import { ServerTransactionType } from 'types/serverTransactions.types';
Expand Down
2 changes: 1 addition & 1 deletion src/apiCalls/transactions/getTransactions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import axios from 'axios';
import { TRANSACTIONS_ENDPOINT } from 'apiCalls/endpoints';
import axios from 'axios';
import { TransactionServerStatusesEnum } from 'types/enums.types';
import { ServerTransactionType } from 'types/serverTransactions.types';

Expand Down
2 changes: 1 addition & 1 deletion src/apiCalls/transactions/getTransactionsByHashes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import axios from 'axios';
import { TRANSACTIONS_ENDPOINT } from 'apiCalls/endpoints';
import axios from 'axios';

import { networkSelector } from 'store/selectors';
import { getState } from 'store/store';
Expand Down
1 change: 1 addition & 0 deletions src/constants/mvx.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export const VERSION = 1;
export const LEDGER_CONTRACT_DATA_ENABLED_VALUE = 1;
export const METACHAIN_SHARD_ID = 4294967295;
export const ALL_SHARDS_SHARD_ID = 4294967280;
export const REFUNDED_GAS = 'refundedGas';
2 changes: 1 addition & 1 deletion src/constants/webWalletProvider.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ export {
WALLET_PROVIDER_SIGN_MESSAGE_URL,
WALLET_PROVIDER_CALLBACK_PARAM,
WALLET_PROVIDER_CALLBACK_PARAM_TX_SIGNED
} from '@multiversx/sdk-web-wallet-provider';
} from '@multiversx/sdk-web-wallet-provider';
2 changes: 1 addition & 1 deletion src/lib/sdkDappUtils.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { formatAmount } from '@multiversx/sdk-dapp-utils/out/helpers/formatAmount';
export { formatAmount } from '@multiversx/sdk-dapp-utils/out/helpers';
44 changes: 44 additions & 0 deletions src/types/tokens.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,47 @@ export interface NftType {
}[];
scamInfo?: ScamInfoType;
}

export interface TokenAssets {
description: string;
status: string;
svgUrl: string;
website?: string;
pngUrl?: string;
social?: any;
extraTokens?: string[];
lockedAccounts?: { [key: string]: string };
}

export interface TokenMediaType {
url?: string;
originalUrl?: string;
thumbnailUrl?: string;
fileType?: string;
fileSize?: number;
}

export interface TokenOptionType {
tokenLabel: string;
tokenDecimals: number;
tokenAvatar: string;
assets?: TokenAssets;
type?: NftEnumType;
error?: string;
esdtPrice?: number;
ticker?: string;
identifier?: string;
name?: string;
isLoading?: boolean;
}

export interface TokenInfoResponse {
identifier: string;
name: string;
ticker: string;
decimals: number;
type?: NftEnumType;
assets: TokenAssets;
media?: TokenMediaType[];
price: number;
}
21 changes: 21 additions & 0 deletions src/types/transactions.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import {
TransactionServerStatusesEnum,
TransactionTypesEnum
} from 'types/enums.types';
import {
ServerTransactionType,
TokenArgumentType
} from './serverTransactions.types';

export interface SignedTransactionType extends IPlainTransactionObject {
hash: string;
Expand Down Expand Up @@ -90,3 +94,20 @@ export interface SmartContractResult {
miniBlockHash: string;
returnMessage: string;
}

export enum TransactionDirectionEnum {
SELF = 'Self',
INTERNAL = 'Internal',
IN = 'In',
OUT = 'Out'
}

export enum TransactionTypesEnum {
MultiESDTNFTTransfer = 'MultiESDTNFTTransfer',
ESDTTransfer = 'ESDTTransfer',
ESDTNFTBurn = 'ESDTNFTBurn',
ESDTNFTTransfer = 'ESDTNFTTransfer',
esdtTransaction = 'esdtTransaction',
nftTransaction = 'nftTransaction',
scCall = 'scCall'
}
1 change: 1 addition & 0 deletions src/utils/account/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './fetchAccount';
export * from './refreshAccount';
export * from './trimUsernameDomain';
12 changes: 12 additions & 0 deletions src/utils/account/trimUsernameDomain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export const trimUsernameDomain = (username?: string) => {
if (!username) {
return;
}

const elrondSuffixExists = username.lastIndexOf('.elrond') > 0;
const trimmedPartBeforeLastDot = elrondSuffixExists
? username.substring(0, username.lastIndexOf('.'))
: username;

return trimmedPartBeforeLastDot;
};
33 changes: 33 additions & 0 deletions src/utils/operations/tests/timeRemaining.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { timeRemaining } from '../timeRemaining';

describe('timeRemaining tests - short time format', () => {
const entries: [number, string][] = [
[1076, '17 min'],
[3976, '1 hr'],
[5286, '1 hr']
];

for (let i = 0; i < entries.length; i++) {
const [input, output] = entries[i];
test(`parse ${input} -> ${output}`, () => {
const result = timeRemaining(input);
expect(result).toStrictEqual(output);
});
}
});

describe('timeRemaining tests - long time format', () => {
const entries: [number, string][] = [
[1076, '17 min 56 sec'],
[3976, '1 hr 6 min'],
[5286, '1 hr 28 min']
];

for (let i = 0; i < entries.length; i++) {
const [input, output] = entries[i];
test(`parse ${input} -> ${output}`, () => {
const result = timeRemaining(input, false);
expect(result).toStrictEqual(output);
});
}
});
88 changes: 88 additions & 0 deletions src/utils/operations/timeRemaining.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
export function getRemainingTime(ms: number) {
const days = Math.floor(ms / (24 * 60 * 60 * 1000));
const daysms = ms % (24 * 60 * 60 * 1000);
const hrs = Math.floor(daysms / (60 * 60 * 1000));
const hrsms = daysms % (60 * 60 * 1000);
const mins = Math.floor(hrsms / (60 * 1000));
const minsms = hrsms % (60 * 1000);
const secs = Math.floor(minsms / 1000);

let secsString = secs + ' sec';
let minsString = mins + ' min';
let hrsString = hrs + ' hr';
let daysString = days + ' day';

if (secs > 1) {
secsString = secs + ' sec';
}
if (mins > 1) {
minsString = mins + ' min';
}
if (hrs > 1) {
hrsString = hrs + ' hrs';
}
if (days > 1) {
daysString = days + ' days';
}

if (days >= 1) {
return daysString + ' ' + hrsString;
}
if (hrs >= 1) {
const minutesString = mins === 0 ? '' : ' ' + minsString;
return hrsString + minutesString;
}
if (mins >= 1) {
const secString = secs === 0 ? '' : ' ' + secsString;
return minsString + secString;
}

return secsString;
}

function getShortDateTimeFormat(datetime: string) {
const parts = datetime.split(' ');
if (parts.length > 1) {
return `${parts[0]} ${parts[1]}`;
}
return datetime;
}

const getUTCDateNow = (date = new Date(), extendedSeconds = 0) =>
Date.UTC(
date.getUTCFullYear(),
date.getUTCMonth(),
date.getUTCDate(),
date.getUTCHours(),
date.getUTCMinutes(),
date.getUTCSeconds() + extendedSeconds,
date.getUTCMilliseconds()
);

function getUTCdiffInMs(duration: number) {
const date = new Date();
const startDate = getUTCDateNow(date);
const endDate = getUTCDateNow(date, duration);
const diffInMs = Math.max(endDate - startDate, 0);
return diffInMs;
}

export function timeRemaining(duration: number, short = true) {
const diffInMs = getUTCdiffInMs(duration);
const remaining = getRemainingTime(diffInMs);
return short ? getShortDateTimeFormat(remaining) : remaining;
}

function getDifferenceInMs(timestamp: number) {
const dateNow = new Date().getTime();
const difference = dateNow - timestamp;
const diffInMs = Math.max(difference, 0);
return diffInMs;
}

export function timeAgo(timestamp: number, short = true) {
const diffInMs = getDifferenceInMs(timestamp);
const remaining = getRemainingTime(diffInMs);

return short ? getShortDateTimeFormat(remaining) : remaining;
}
Loading
Loading