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

Tm/feature/sign steps #54

Merged
merged 17 commits into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
},
"peerDependencies": {
"@multiversx/sdk-core": ">= 13.5.0",
"@multiversx/sdk-dapp-utils": ">= 1.0.0",
"@multiversx/sdk-dapp-utils": ">= 1.0.2",
"@multiversx/sdk-web-wallet-cross-window-provider": ">= 2.0.4",
"axios": ">=1.6.5",
"bignumber.js": "9.x",
Expand All @@ -65,7 +65,7 @@
"devDependencies": {
"@eslint/js": "9.15.0",
"@multiversx/sdk-core": ">= 13.5.0",
"@multiversx/sdk-dapp-utils": "1.0.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",
Expand Down
4 changes: 3 additions & 1 deletion src/apiCalls/account/getAccountFromApi.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { ACCOUNTS_ENDPOINT } from 'apiCalls/endpoints';
import { axiosInstance } from 'apiCalls/utils/axiosInstance';
import { getCleanApiAddress } from 'apiCalls/utils/getCleanApiAddress';
import { TIMEOUT } from 'constants/network.constants';
import { AccountType } from 'types/account.types';

export const accountFetcher = (address: string | null) => {
const apiAddress = getCleanApiAddress();
const url = `${apiAddress}/${ACCOUNTS_ENDPOINT}/${address}?withGuardianInfo=true`;
// we need to get it with an axios instance because of cross-window user interaction issues
return axiosInstance.get(url, {
baseURL: apiAddress
baseURL: apiAddress,
timeout: TIMEOUT
});
};

Expand Down
18 changes: 18 additions & 0 deletions src/apiCalls/account/getScamAddressData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import axios from 'axios';
import { getCleanApiAddress } from 'apiCalls/utils';
import { TIMEOUT } from 'constants/index';
import { ScamInfoType } from 'types/account.types';
import { ACCOUNTS_ENDPOINT } from '../endpoints';

export async function getScamAddressData(addressToVerify: string) {
const apiAddress = getCleanApiAddress();

const { data } = await axios.get<{
scamInfo?: ScamInfoType;
code?: string;
}>(`${apiAddress}/${ACCOUNTS_ENDPOINT}/${addressToVerify}`, {
timeout: TIMEOUT
});

return data;
}
21 changes: 20 additions & 1 deletion src/apiCalls/configuration/getServerConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,26 @@ export async function getServerConfiguration(apiAddress: string) {

try {
const { data } = await axios.get<NetworkType>(configUrl);
return data;
if (data != null) {
// TODO: egldDenomination will be removed from API when dapp-core v1 will be discontinued
const egldDenomination = 'egldDenomination';
if (egldDenomination in data) {
const {
[egldDenomination]: decimals,
decimals: digits,
...rest
} = data as NetworkType & {
[egldDenomination]: string;
};
const networkConfig: NetworkType = {
...rest,
decimals,
digits
};
return networkConfig;
}
return data;
}
} catch (_err) {
console.error('error fetching configuration for ', configUrl);
}
Expand Down
25 changes: 25 additions & 0 deletions src/apiCalls/economics/getEconomics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import axios from 'axios';
import { ECONOMICS_ENDPOINT } from 'apiCalls/endpoints';
import { getCleanApiAddress } from 'apiCalls/utils/getCleanApiAddress';

export interface EconomicsInfoType {
totalSupply: number;
circulatingSupply: number;
staked: number;
price: number;
marketCap: number;
apr: number;
topUpApr: number;
}

export async function getEconomics(url = ECONOMICS_ENDPOINT) {
const apiAddress = getCleanApiAddress();
const configUrl = `${apiAddress}/${url}`;
try {
const { data } = await axios.get<EconomicsInfoType>(configUrl);
return data;
} catch (err) {
console.error('err fetching economics info', err);
return null;
}
}
25 changes: 25 additions & 0 deletions src/apiCalls/tokens/getPersistedToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { getCleanApiAddress } from 'apiCalls/utils';
import { axiosInstance } from 'apiCalls/utils/axiosInstance';
import { TIMEOUT } from 'constants/network.constants';
import { tokenDataStorage } from './tokenDataStorage';

export async function getPersistedToken<T>(url: string): Promise<T> {
const apiAddress = getCleanApiAddress();

const config = {
baseURL: apiAddress,
timeout: TIMEOUT
};

const cachedToken: T | null = await tokenDataStorage.getItem(url);

if (cachedToken) {
return cachedToken;
}

const response = await axiosInstance.get<T>(url, config);

await tokenDataStorage.setItem(url, response.data);

return response.data;
}
104 changes: 104 additions & 0 deletions src/apiCalls/tokens/getPersistedTokenDetails.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { NFTS_ENDPOINT, TOKENS_ENDPOINT } from 'apiCalls/endpoints';
import { getPersistedToken } from 'apiCalls/tokens/getPersistedToken';
import { networkSelector } from 'store/selectors/networkSelectors';
import { getState } from 'store/store';

import { NftEnumType } from 'types/tokens.types';
import { getIdentifierType } from 'utils/validation/getIdentifierType';

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;
tokenImageUrl: string;
assets?: TokenAssets;
type?: NftEnumType;
error?: string;
esdtPrice?: number;
ticker?: string;
identifier?: string;
name?: string;
}

interface TokenInfoResponse {
identifier: string;
name: string;
ticker: string;
decimals: number;
type?: NftEnumType;
assets: TokenAssets;
media?: TokenMediaType[];
price: number;
}

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

const noData = {
tokenDecimals: Number(network.decimals),
tokenLabel: '',
tokenImageUrl: ''
};

const { isNft } = getIdentifierType(tokenId);

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

if (!tokenIdentifier) {
return noData;
}

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

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

return {
tokenDecimals: tokenDecimals,
tokenLabel,
type: selectedToken?.type,
tokenImageUrl,
identifier: selectedToken?.identifier,
assets: selectedToken?.assets,
esdtPrice: selectedToken?.price,
ticker: selectedToken?.ticker,
name: selectedToken?.name
};
} catch (error) {
return {
...noData,
error: `${error}`
};
}
}
33 changes: 33 additions & 0 deletions src/apiCalls/tokens/tokenDataStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
let memoryCache: Record<string, string> = {};

export let tokenDataStorage = {
setItem: async <T>(key: string, tokenData: T) => {
try {
memoryCache[key] = JSON.stringify(tokenData);
} catch (e) {
console.error('tokenDataStorage unable to serialize', e);
}
},
getItem: async (key: string) => {
if (!memoryCache[key]) {
return null;
}
try {
return JSON.parse(memoryCache[key]);
} catch (e) {
console.error('tokenDataStorage unable to parse', e);
}
},
clear: async () => {
memoryCache = {};
},
removeItem: async (key: string) => {
delete memoryCache[key];
}
};

export const setTokenDataStorage = (
tokenDataCacheStorage: typeof tokenDataStorage
) => {
tokenDataStorage = tokenDataCacheStorage;
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import {
ISignTransactionsModalData,
SignEventsEnum
} from './types/signTransactionsModal.types';
import { NftEnumType } from 'types/tokens.types';
import { FungibleTransactionType, ISignTransactionsModalData, SignEventsEnum, TokenType } from './types/signTransactionsModal.types';

interface IEventBus {
publish(event: string, data: any): void;
Expand All @@ -10,19 +8,21 @@ interface IEventBus {
const notInitializedError = () => new Error('Event bus not initialized');

export class SignTransactionsStateManager<T extends IEventBus = IEventBus> {
private static instance: SignTransactionsStateManager<IEventBus> | null =
null;
private static instance: SignTransactionsStateManager<IEventBus> | null = null;
public readonly addressesPerPage = 10;

private eventBus: T = {
publish: notInitializedError,
subscribe: notInitializedError,
unsubscribe: notInitializedError
unsubscribe: notInitializedError,
} as unknown as T;

// whole data to be sent on update events
private initialData: ISignTransactionsModalData = {
transaction: null
commonData: { transactionsCount: 0, egldLabel: '', currentIndex: 0 },
tokenTransaction: null,
nftTransaction: null,
sftTransaction: null,
};

private data: ISignTransactionsModalData = { ...this.initialData };
Expand All @@ -32,24 +32,20 @@ export class SignTransactionsStateManager<T extends IEventBus = IEventBus> {
this.resetData();
}

public static getInstance<U extends IEventBus>(
eventBus?: U
): SignTransactionsStateManager<U> | null {
public static getInstance<U extends IEventBus>(eventBus?: U): SignTransactionsStateManager<U> | null {
if (!eventBus) {
return null;
}
if (!SignTransactionsStateManager.instance) {
SignTransactionsStateManager.instance = new SignTransactionsStateManager(
eventBus
);
SignTransactionsStateManager.instance = new SignTransactionsStateManager(eventBus);
}
return SignTransactionsStateManager.instance as SignTransactionsStateManager<U>;
}

public updateTransaction(members: Partial<ISignTransactionsModalData>): void {
this.data = {
...this.data,
...members
public updateCommonData(members: Partial<ISignTransactionsModalData['commonData']>): void {
this.data.commonData = {
...this.data.commonData,
...members,
};
this.notifyDataUpdate();
}
Expand All @@ -67,4 +63,31 @@ export class SignTransactionsStateManager<T extends IEventBus = IEventBus> {
private notifyDataUpdate(): void {
this.eventBus.publish(SignEventsEnum.DATA_UPDATE, this.data);
}

public updateTokenTransaction(tokenData: ISignTransactionsModalData['tokenTransaction']): void {
this.data.tokenTransaction = tokenData;
this.data.sftTransaction = null;
this.data.nftTransaction = null;

this.notifyDataUpdate();
}

public updateFungibleTransaction(type: TokenType, fungibleData: FungibleTransactionType): void {
switch (type) {
case NftEnumType.NonFungibleESDT:
this.data.nftTransaction = fungibleData;
this.data.tokenTransaction = null;
this.data.sftTransaction = null;
break;
case NftEnumType.SemiFungibleESDT:
this.data.sftTransaction = fungibleData;
this.data.nftTransaction = null;
this.data.tokenTransaction = null;
break;
default:
break;
}

this.notifyDataUpdate();
}
}
Loading
Loading