Skip to content

Commit

Permalink
fix: improve the process of initializing token information
Browse files Browse the repository at this point in the history
jinoosss committed Nov 11, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent a9c7f11 commit 8830023
Showing 9 changed files with 244 additions and 74 deletions.
12 changes: 9 additions & 3 deletions packages/adena-extension/src/App/use-app.ts
Original file line number Diff line number Diff line change
@@ -26,10 +26,16 @@ const useApp = (): void => {
}, [key]);

useEffect(() => {
if (currentAccount && currentNetwork) {
initTokenMetainfos(true);
if (!currentAccount?.id) {
return;
}
}, [currentAccount, currentNetwork]);

if (!currentNetwork?.networkId) {
return;
}

initTokenMetainfos();
}, [currentAccount?.id, currentNetwork.networkId]);

useEffect(() => {
initAccountNames(wallet?.accounts ?? []);
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Wallet } from 'adena-module';
import React, { createContext, useEffect, useState } from 'react';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { Wallet } from 'adena-module';

import { NetworkState, TokenState, WalletState } from '@states';
import { useAdenaContext } from '@hooks/use-context';
import { NetworkState, TokenState, WalletState } from '@states';
import { NetworkMetainfo, StateType, TokenModel } from '@types';
import { GnoProvider } from '../gno/gno-provider';
import { TokenModel, NetworkMetainfo, StateType } from '@types';

export interface WalletContextProps {
wallet: Wallet | null;
@@ -31,7 +31,7 @@ export const WalletProvider: React.FC<React.PropsWithChildren<unknown>> = ({ chi

const [walletStatus, setWalletStatus] = useRecoilState(WalletState.state);

const [tokenMetainfos] = useRecoilState(TokenState.tokenMetainfos);
const [tokenMetainfos, setTokenMetainfos] = useRecoilState(TokenState.tokenMetainfos);

const [networkMetainfos, setNetworkMetainfos] = useRecoilState(NetworkState.networkMetainfos);

@@ -95,6 +95,7 @@ export const WalletProvider: React.FC<React.PropsWithChildren<unknown>> = ({ chi
if (currentAccount) {
setCurrentAccount(currentAccount);
await accountService.changeCurrentAccount(currentAccount);
await initTokenMetainfos(currentAccount.id);
}
return true;
}
@@ -123,6 +124,13 @@ export const WalletProvider: React.FC<React.PropsWithChildren<unknown>> = ({ chi
return true;
}

async function initTokenMetainfos(accountId: string): Promise<void> {
await tokenService.initAccountTokenMetainfos(accountId);
const tokenMetainfos = await tokenService.getTokenMetainfosByAccountId(accountId);
setTokenMetainfos(tokenMetainfos);
balanceService.setTokenMetainfos(tokenMetainfos);
}

async function changeNetwork(networkMetainfo: NetworkMetainfo): Promise<NetworkMetainfo> {
const rpcUrl = networkMetainfo.rpcUrl;
const gnoProvider = new GnoProvider(rpcUrl, networkMetainfo.networkId);
42 changes: 32 additions & 10 deletions packages/adena-extension/src/hooks/use-token-balance.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { useEffect, useMemo } from 'react';
import { useQuery } from '@tanstack/react-query';
import { Account } from 'adena-module';
import { useEffect, useMemo } from 'react';

import { isGRC20TokenModel, isNativeTokenModel } from '@common/validation/validation-token';
import { TokenModel, Amount, TokenBalanceType } from '@types';
import { Amount, TokenBalanceType, TokenModel } from '@types';

import { useNetwork } from './use-network';
import { useCurrentAccount } from './use-current-account';
import { useAdenaContext, useWalletContext } from './use-context';
import { useCurrentAccount } from './use-current-account';
import { useGRC20Tokens } from './use-grc20-tokens';
import { useNetwork } from './use-network';
import { useTokenMetainfo } from './use-token-metainfo';
import { useWallet } from './use-wallet';
import { useGRC20Tokens } from './use-grc20-tokens';

export const useTokenBalance = (): {
mainTokenBalance: Amount | null;
@@ -22,6 +22,7 @@ export const useTokenBalance = (): {
const { isFetched: isFetchedGRC20Tokens } = useGRC20Tokens();
const {
currentTokenMetainfos: tokenMetainfos,
tokenLogoMap,
updateTokenMetainfos,
getTokenAmount,
} = useTokenMetainfo();
@@ -35,12 +36,26 @@ export const useTokenBalance = (): {
balanceService.setTokenMetainfos(tokenMetainfos);
}, [tokenMetainfos, balanceService]);

const availableBalanceFetching = useMemo(() => {
if (!existWallet || lockedWallet) {
return false;
}

if (!isFetchedGRC20Tokens || tokenMetainfos.length === 0) {
return false;
}

return true;
}, [existWallet, lockedWallet, tokenMetainfos, isFetchedGRC20Tokens]);

const { data: balances = [] } = useQuery<TokenBalanceType[]>(
[
'balances',
currentAddress,
currentNetwork.chainId,
tokenMetainfos.map((token) => token.tokenId),
isFetchedGRC20Tokens,
tokenLogoMap,
],
() => {
if (currentAddress === null || nativeToken == null) {
@@ -50,21 +65,25 @@ export const useTokenBalance = (): {
tokenMetainfos.map((tokenModel) => fetchBalanceBy(currentAddress, tokenModel)),
);
},
{ refetchInterval: 5000, enabled: existWallet && !lockedWallet },
{
refetchInterval: 5000,
enabled: availableBalanceFetching,
},
);

const { data: accountNativeBalanceMap = {} } = useQuery<Record<string, TokenBalanceType>>(
[
'accountNativeBalanceMap',
wallet?.accounts,
currentNetwork.chainId,
tokenMetainfos,
tokenMetainfos.map((token) => token.tokenId),
isFetchedGRC20Tokens,
],
() => {
if (wallet === null || wallet.accounts === null || nativeToken == null) {
return {};
}

return Promise.all(
wallet.accounts.map(async (account) => {
const address = await account.getAddress(currentNetwork.addressPrefix);
@@ -79,7 +98,10 @@ export const useTokenBalance = (): {
}, {}),
);
},
{ refetchInterval: 5000, enabled: existWallet && !lockedWallet && isFetchedGRC20Tokens },
{
refetchInterval: 5000,
enabled: availableBalanceFetching,
},
);

const currentBalances = useMemo((): TokenBalanceType[] => {
@@ -133,14 +155,14 @@ export const useTokenBalance = (): {
const balanceAmount = isNativeTokenModel(token)
? await balanceService.getGnotTokenBalance(address)
: isGRC20TokenModel(token)
? await balanceService.getGRC20TokenBalance(address, token.pkgPath)
? await balanceService.getGRC20TokenBalance(address, token.pkgPath, token.decimals)
: null;

return {
...token,
amount: getTokenAmount({
value: `${balanceAmount || 0}`,
denom: isGRC20TokenModel(token) ? token.pkgPath : token.symbol,
denom: token.symbol,
}),
};
}
69 changes: 40 additions & 29 deletions packages/adena-extension/src/hooks/use-token-metainfo.tsx
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ import { useNetwork } from './use-network';

import { TokenState } from '@states';
import { useQuery } from '@tanstack/react-query';
import { GRC20TokenModel, TokenModel } from '@types';
import { GRC20TokenModel, GRC721CollectionModel, TokenModel } from '@types';
import { Account } from 'adena-module';
import BigNumber from 'bignumber.js';
import { useCallback, useMemo } from 'react';
@@ -30,7 +30,7 @@ export type UseTokenMetainfoReturn = {
currentTokenMetainfos: TokenModel[];
tokenLogoMap: Record<string, string | null>;
getTokenAmount: (amount: { value: string; denom: string }) => { value: string; denom: string };
initTokenMetainfos: (withTransferEvents?: boolean) => Promise<void>;
initTokenMetainfos: () => Promise<void>;
updateTokenMetainfos: (account: Account, tokenMetainfos: TokenModel[]) => Promise<void>;
addTokenMetainfo: (tokenMetainfo: GRC20TokenModel) => Promise<boolean>;
addGRC20TokenMetainfo: ({
@@ -137,40 +137,51 @@ export const useTokenMetainfo = (): UseTokenMetainfoReturn => {
}, {});
}, [allTokenMetainfos]);

const initTokenMetainfos = async (withTransferEvents?: boolean): Promise<void> => {
const initTokenMetainfos = async (): Promise<void> => {
if (!currentAccount) {
return;
}

if (withTransferEvents) {
await setTokenMetainfo([]);
const currentAddress = await currentAccount.getAddress(currentNetwork.addressPrefix);
const transferTokens = await fetchTransferTokens(currentAddress).catch(() => null);
if (transferTokens) {
const storedGRC20Tokens = await tokenService.getTokenMetainfosByAccountId(
currentAccount.id,
);
const storedCollections = await tokenService.getAccountGRC721Collections(
currentAccount.id,
currentNetwork.chainId,
);
await setTokenMetainfo([]);
const currentAddress = await currentAccount.getAddress(currentNetwork.addressPrefix);

/**
* For accounts with no transfer events, initialize the state with the list of stored tokens.
*/
const transferTokens = await fetchTransferTokens(currentAddress).catch(() => null);
if (!transferTokens) {
await tokenService.initAccountTokenMetainfos(currentAccount.id);
const tokenMetainfos = await tokenService.getTokenMetainfosByAccountId(currentAccount.id);
setTokenMetainfo([...tokenMetainfos]);
return;
}

const storedGRC20Packages = storedGRC20Tokens.map((grc20Token) => grc20Token.tokenId);
const storedGRC721Packages = storedCollections.map(
(grc721Token) => grc721Token.packagePath,
);
/**
* When there is a transfer event, verify that the token is new and not part of the list of tokens stored in the existing account.
* The new tokens are added to the account's token information.
*/
const storedGRC20Tokens = await tokenService.getTokenMetainfosByAccountId(currentAccount.id);
const storedCollections = await tokenService.getAccountGRC721Collections(
currentAccount.id,
currentNetwork.chainId,
);

const filteredGRC20Packages = (transferTokens.grc20Packages || []).filter(
(grc20Token) => !storedGRC20Packages.includes(grc20Token.tokenId),
);
const filteredGRC721Packages = (transferTokens.grc721Packages || []).filter(
(grc721Token) => !storedGRC721Packages.includes(grc721Token.packagePath),
);
const storedGRC20Packages = storedGRC20Tokens
.filter((token: TokenModel) => token.networkId === currentNetwork.networkId)
.map((grc20Token) => grc20Token.tokenId);
const storedGRC721Packages = storedCollections
.filter((token: GRC721CollectionModel) => token.networkId === currentNetwork.networkId)
.map((grc721Token) => grc721Token.packagePath);

await addTokenMetainfos(filteredGRC20Packages);
await addCollections(filteredGRC721Packages);
}
}
const filteredGRC20Packages = (transferTokens.grc20Packages || []).filter(
(grc20Token) => !storedGRC20Packages.includes(grc20Token.tokenId),
);
const filteredGRC721Packages = (transferTokens.grc721Packages || []).filter(
(grc721Token) => !storedGRC721Packages.includes(grc721Token.packagePath),
);

await addTokenMetainfos(filteredGRC20Packages);
await addCollections(filteredGRC721Packages);

await tokenService.initAccountTokenMetainfos(currentAccount.id);
const tokenMetainfos = await tokenService.getTokenMetainfosByAccountId(currentAccount.id);
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import React, { useCallback, useEffect, useMemo } from 'react';

import { CommonFullContentLayout } from '@components/atoms';
import ChangeNetwork from '@components/pages/change-network/change-network/change-network';
import useAppNavigate from '@hooks/use-app-navigate';
import { useNetwork } from '@hooks/use-network';
import { useTokenMetainfo } from '@hooks/use-token-metainfo';
import { RoutePath } from '@types';
import { CommonFullContentLayout } from '@components/atoms';
import useAppNavigate from '@hooks/use-app-navigate';

const ChangeNetworkContainer: React.FC = () => {
const { navigate, goBack } = useAppNavigate();
const { modified, currentNetwork, networks, setModified, changeNetwork } = useNetwork();
const { initTokenMetainfos } = useTokenMetainfo();

useEffect(() => {
if (modified) {
@@ -46,7 +44,6 @@ const ChangeNetworkContainer: React.FC = () => {

if (networkId) {
await changeNetwork(networkId);
await initTokenMetainfos();
navigate(RoutePath.Wallet);
}
};
Original file line number Diff line number Diff line change
@@ -75,8 +75,11 @@ const ManageTokenAddedContainer: React.FC = () => {
}

const isRegistered = tokenMetainfos.some((tokenMetaInfo) => {
if (tokenMetaInfo.tokenId === manualTokenPath) {
return true;
if (
tokenMetaInfo.tokenId !== manualTokenPath ||
tokenMetaInfo.networkId !== currentNetwork.networkId
) {
return false;
}

if (isGRC20TokenModel(tokenMetaInfo)) {
80 changes: 80 additions & 0 deletions packages/adena-extension/src/repositories/common/token.queries.ts
Original file line number Diff line number Diff line change
@@ -26,6 +26,52 @@ export const makeAllRealmsQuery = (): string => `
`;

export const makeGRC721TransferEventsQuery = (packagePath: string, address: string): string => `
{
transactions(
filter: {
success: true
events: {
type: "Transfer"
pkg_path: "${packagePath}"
attrs: [{
key: "from"
value: "${address}"
}, {
key:"to"
value: "${address}"
}]
}
messages: [
{
type_url: exec
}
]
}
) {
hash
index
success
block_height
response {
events {
...on GnoEvent {
type
pkg_path
func
attrs {
key
value
}
}
}
}
}
}
`;
export const makeGRC721TransferEventsQueryWithCursor = (
packagePath: string,
address: string,
): string => `
{
transactions(
filter: {
@@ -81,6 +127,40 @@ export const makeGRC721TransferEventsQuery = (packagePath: string, address: stri
`;

export const makeAllTransferEventsQueryBy = (address: string): string => `
{
transactions(
filter: {
success: true
events: {
type: "Transfer"
attrs: [{
key: "to"
value: "${address}"
}]
}
}
) {
hash
index
success
block_height
response {
events {
...on GnoEvent {
type
pkg_path
func
attrs {
key
value
}
}
}
}
}
}`;

export const makeAllTransferEventsQueryWithCursorBy = (address: string): string => `
{
transactions(
filter: {
75 changes: 56 additions & 19 deletions packages/adena-extension/src/repositories/common/token.ts
Original file line number Diff line number Diff line change
@@ -35,6 +35,7 @@ import {
makeAllRealmsQuery,
makeAllTransferEventsQueryBy,
makeGRC721TransferEventsQuery,
makeGRC721TransferEventsQueryWithCursor,
} from './token.queries';
import { ITokenRepository } from './types';

@@ -268,6 +269,7 @@ export class TokenRepository implements ITokenRepository {
.map((message: any) =>
mapGRC20TokenModel(this.networkMetainfo?.networkId || '', message),
)
.filter((tokenInfo: GRC20TokenModel | null) => !!tokenInfo)
: [],
);
};
@@ -323,15 +325,12 @@ export class TokenRepository implements ITokenRepository {
.map((message: any) =>
mapGRC721CollectionModel(this.networkMetainfo?.networkId || '', message),
)
.filter((collection: GRC721CollectionModel | null) => !!collection)
: [],
);
}

public async fetchAllTransferPackagesBy(address: string): Promise<string[]> {
if (!this.apiUrl || !this.queryUrl) {
return [];
}

if (this.apiUrl) {
const packages = await TokenRepository.postRPCRequest<{
result: string[];
@@ -349,19 +348,23 @@ export class TokenRepository implements ITokenRepository {
return packages;
}

if (!this.queryUrl) {
return [];
}

const transferEventsQuery = makeAllTransferEventsQueryBy(address);
return TokenRepository.postGraphQuery(
this.networkInstance,
this.queryUrl,
transferEventsQuery,
).then((result) => {
const edges = result?.data?.transactions?.edges;
if (!edges) {
const transactions = result?.data?.transactions;
if (!transactions) {
return [];
}

const packagePaths: string[] = edges
.flatMap((edge: any) => edge?.transaction?.response?.events || [])
const packagePaths: string[] = transactions
.flatMap((transaction: any) => transaction?.response?.events || [])
.filter((event: any) => {
const eventType = event?.type;
const eventAttributes = event?.attrs || [];
@@ -460,25 +463,59 @@ export class TokenRepository implements ITokenRepository {
}

public async fetchGRC721TokensBy(packagePath: string, address: string): Promise<GRC721Model[]> {
if (!this.queryUrl) {
if (!this.apiUrl && !this.queryUrl) {
return [];
}

const grc721TransferEventsQuery = makeGRC721TransferEventsQuery(packagePath, address);
const events: {
type: string;
pkg_path: string;
func: string;
attrs: { [key in string]: string }[];
}[] = await TokenRepository.postGraphQuery(
this.networkInstance,
this.queryUrl,
grc721TransferEventsQuery,
).then((result) =>
result?.data?.transactions
? result?.data?.transactions?.edges.flatMap((edge: any) => edge.transaction.response.events)
: [],
);
}[] = [];

if (this.apiUrl) {
const grc721TransferEventsQuery = makeGRC721TransferEventsQueryWithCursor(
packagePath,
address,
);
const resultEvents: {
type: string;
pkg_path: string;
func: string;
attrs: { [key in string]: string }[];
}[] = await TokenRepository.postGraphQuery(
this.networkInstance,
this.queryUrl || this.apiUrl,
grc721TransferEventsQuery,
).then((result) =>
result?.data?.transactions
? result?.data?.transactions?.edges.flatMap(
(edge: any) => edge.transaction.response.events,
)
: [],
);

events.push(...resultEvents);
} else {
const grc721TransferEventsQuery = makeGRC721TransferEventsQuery(packagePath, address);
const resultEvents: {
type: string;
pkg_path: string;
func: string;
attrs: { [key in string]: string }[];
}[] = await TokenRepository.postGraphQuery(
this.networkInstance,
this.queryUrl || '',
grc721TransferEventsQuery,
).then((result) =>
result?.data?.transactions
? result?.data?.transactions?.flatMap((transaction: any) => transaction?.response?.events)
: [],
);

events.push(...resultEvents);
}

const receivedTokenIds: string[] = [];
const sendedTokenIds: string[] = [];
10 changes: 8 additions & 2 deletions packages/adena-extension/src/services/wallet/wallet-balance.ts
Original file line number Diff line number Diff line change
@@ -48,15 +48,21 @@ export class WalletBalanceService {
.catch(() => null);
}

public async getGRC20TokenBalance(address: string, packagePath: string): Promise<number | null> {
public async getGRC20TokenBalance(
address: string,
packagePath: string,
decimals = 6,
): Promise<number | null> {
const gnoProvider = this.getGnoProvider();
return gnoProvider
.getValueByEvaluateExpression(packagePath, 'BalanceOf', [address])
.then((result) => {
if (result === null || !BigNumber(result).isInteger()) {
return null;
}
return BigNumber(result).toNumber();
return BigNumber(result)
.shiftedBy(decimals * -1)
.toNumber();
})
.catch(() => null);
}

0 comments on commit 8830023

Please sign in to comment.