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

feat: add a feature to manually add custom tokens #604

Merged
merged 9 commits into from
Oct 25, 2024
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
import React, { createContext, useMemo } from 'react';
import axios from 'axios';
import { AdenaStorage } from '@common/storage';
import { useWindowSize } from '@hooks/use-window-size';
import { ChainRepository } from '@repositories/common';
import { TokenRepository } from '@repositories/common/token';
import { FaucetRepository } from '@repositories/faucet/faucet';
import { TransactionHistoryRepository } from '@repositories/transaction';
import {
WalletAccountRepository,
WalletAddressRepository,
WalletEstablishRepository,
WalletRepository,
} from '@repositories/wallet';
import { FaucetService } from '@services/faucet';
import { ChainService, TokenService } from '@services/resource';
import { TransactionHistoryService, TransactionService } from '@services/transaction';
import {
WalletAccountService,
WalletAddressBookService,
WalletBalanceService,
WalletEstablishService,
WalletService,
} from '@services/wallet';
import { ChainService, TokenService } from '@services/resource';
import { ChainRepository } from '@repositories/common';
import { TransactionHistoryService, TransactionService } from '@services/transaction';
import { TokenRepository } from '@repositories/common/token';
import { TransactionHistoryRepository } from '@repositories/transaction';
import { FaucetService } from '@services/faucet';
import { FaucetRepository } from '@repositories/faucet/faucet';
import { useWindowSize } from '@hooks/use-window-size';
import { useRecoilValue } from 'recoil';
import { NetworkState } from '@states';
import axios from 'axios';
import React, { createContext, useMemo } from 'react';
import { useRecoilValue } from 'recoil';
import { GnoProvider } from '../gno/gno-provider';

export interface AdenaContextProps {
Expand Down Expand Up @@ -83,7 +83,7 @@ export const AdenaProvider: React.FC<React.PropsWithChildren<unknown>> = ({ chil
);

const tokenRepository = useMemo(
() => new TokenRepository(localStorage, axiosInstance, currentNetwork),
() => new TokenRepository(localStorage, axiosInstance, currentNetwork, gnoProvider),
[localStorage, axiosInstance, currentNetwork],
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { GnoJSONRPCProvider } from '@gnolang/gno-js-client';
import {
BlockInfo,
base64ToUint8Array,
newRequest,
ABCIEndpoint,
ABCIResponse,
RPCResponse,
parseABCI,
base64ToUint8Array,
BlockInfo,
BroadcastTxCommitResult,
BroadcastTxSyncResult,
newRequest,
parseABCI,
RPCResponse,
TransactionEndpoint,
} from '@gnolang/tm2-js-client';
import fetchAdapter from '@vespaiach/axios-fetch-adapter';
Expand Down
62 changes: 62 additions & 0 deletions packages/adena-extension/src/common/utils/parse-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
export const parseGRC20ByABCIRender = (
response: string,
): {
tokenName: string;
tokenSymbol: string;
tokenDecimals: number;
totalSupply: bigint;
knownAccounts: bigint;
} => {
const regex =
/#\s(?<tokenName>.+)\s\(\$(?<tokenSymbol>.+)\)\s*\* \*\*Decimals\*\*: (?<tokenDecimals>\d+)\s*\* \*\*Total supply\*\*: (?<totalSupply>\d+)\s*\* \*\*Known accounts\*\*: (?<knownAccounts>\d+)/;

const match = response.match(regex);

if (!match || !match.groups) {
throw new Error('failed parse grc20 token render');
}

return {
tokenName: match.groups.tokenName,
tokenSymbol: match.groups.tokenSymbol,
tokenDecimals: parseInt(match.groups.tokenDecimals, 10),
totalSupply: BigInt(match.groups.totalSupply),
knownAccounts: BigInt(match.groups.knownAccounts),
};
};

/**
* realm's path format: {Domain}/{Type}/{Namespace}/{Remain Path...}
*/
export const parseReamPathItemsByPath = (
realmPath: string,
): {
domain: string;
type: string;
namespace: string;
remainPath: string;
} => {
const pathItems = realmPath.split('/');
if (pathItems.length < 4) {
throw new Error('not available realm path, path size less than 4');
}

const [domain, type, namespace, ...remainPathItems] = pathItems;

const availableDomains = ['gno.land'];
if (!availableDomains.includes(domain)) {
throw new Error('not available realm path, domain is ' + domain);
}

const availableTypes = ['p', 'r'];
if (!availableTypes.includes(type)) {
throw new Error('not available realm path, type is ' + type);
}

return {
domain,
type,
namespace,
remainPath: remainPathItems.join('/'),
};
};
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import React from 'react';

import { GlobalPopupStyle } from '@styles/global-style';
import theme from '@styles/theme';
import { render } from '@testing-library/react';
import { RecoilRoot } from 'recoil';
import { ThemeProvider } from 'styled-components';
import { render } from '@testing-library/react';
import theme from '@styles/theme';
import { GlobalPopupStyle } from '@styles/global-style';
import AdditionalTokenInfo, { AdditionalTokenInfoProps } from './additional-token-info';

describe('AdditionalTokenInfo Component', () => {
it('AdditionalTokenInfo render', () => {
const args: AdditionalTokenInfoProps = {
isLoading: false,
symbol: 'GNOT',
path: 'gno.land/gnot',
decimals: '6',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,50 +1,54 @@
import { Loading } from '@components/atoms';
import React from 'react';
import { AdditionalTokenInfoWrapper, AdditionalTokenInfoItemWrapper } from './additional-token-info.styles';
import {
AdditionalTokenInfoItemWrapper,
AdditionalTokenInfoWrapper,
} from './additional-token-info.styles';

export interface AdditionalTokenInfoProps {
isLoading: boolean;
symbol: string;
path: string;
decimals: string;
}

export interface AdditionalTokenInfoBlockProps {
isLoading: boolean;
title: string;
value: string;
}

const AdditionalTokenInfoBlock: React.FC<AdditionalTokenInfoBlockProps> = ({
title,
value,
isLoading,
}) => {
return (
<AdditionalTokenInfoItemWrapper>
<span className='title'>{title}:</span>
<span className='value'>{value}</span>

{isLoading ? (
<Loading.Round width='40px' height='10px' radius='24px' />
) : (
<span className='value'>{value}</span>
)}
</AdditionalTokenInfoItemWrapper>
);
};

const AdditionalTokenInfo: React.FC<AdditionalTokenInfoProps> = ({
isLoading,
symbol,
path,
decimals,
}) => {
return (
<AdditionalTokenInfoWrapper>
<AdditionalTokenInfoBlock
title='Token Symbol'
value={symbol}
/>
<AdditionalTokenInfoBlock
title='Token Path'
value={path}
/>
<AdditionalTokenInfoBlock
title='Token Decimals'
value={decimals}
/>
<AdditionalTokenInfoBlock title='Token Symbol' value={symbol} isLoading={isLoading} />
<AdditionalTokenInfoBlock title='Token Path' value={path} isLoading={isLoading} />
<AdditionalTokenInfoBlock title='Token Decimals' value={decimals} isLoading={isLoading} />
</AdditionalTokenInfoWrapper>
);
};

export default AdditionalTokenInfo;
export default AdditionalTokenInfo;
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';

import { render } from '@testing-library/react';
import { RecoilRoot } from 'recoil';
import { ThemeProvider } from 'styled-components';

import { GlobalPopupStyle } from '@styles/global-style';
import theme from '@styles/theme';

import AdditionalTokenPathInput, {
AdditionalTokenPathInputProps,
} from './additional-token-path-input';

describe('AdditionalTokenPathInput Component', () => {
it('AdditionalTokenPathInput render', () => {
const args: AdditionalTokenPathInputProps = {
keyword: '',
onChangeKeyword: () => {
return;
},
errorMessage: 'error',
};

render(
<RecoilRoot>
<GlobalPopupStyle />
<ThemeProvider theme={theme}>
<AdditionalTokenPathInput {...args} />
</ThemeProvider>
</RecoilRoot>,
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Meta, StoryObj } from '@storybook/react';
import AdditionalTokenPathInput, {
type AdditionalTokenPathInputProps,
} from './additional-token-path-input';

export default {
title: 'components/additional-token/AdditionalTokenPathInput',
component: AdditionalTokenPathInput,
} as Meta<typeof AdditionalTokenPathInput>;

export const Default: StoryObj<AdditionalTokenPathInputProps> = {
args: {},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import styled from 'styled-components';

import { View } from '@components/atoms';
import { fonts, getTheme } from '@styles/theme';

export const AdditionalTokenPathInputWrapper = styled(View)`
width: 100%;

.search-input {
height: 48px;
padding: 13px 16px;
background-color: ${getTheme('neutral', '_9')};
border: 1px solid ${getTheme('neutral', '_7')};
color: ${getTheme('neutral', '_1')};
border-radius: 30px;
${fonts.body2Reg};

&.error {
border-color: ${getTheme('red', '_5')};
}
}

.error-message {
padding: 0 8px;
height: 18px;
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Text } from '@components/atoms';
import theme from '@styles/theme';
import React, { useMemo } from 'react';
import { AdditionalTokenPathInputWrapper } from './additional-token-path-input.styles';

export interface AdditionalTokenPathInputProps {
keyword: string;
onChangeKeyword: (keyword: string) => void;
errorMessage: string | null;
}

const AdditionalTokenPathInput: React.FC<AdditionalTokenPathInputProps> = ({
keyword,
onChangeKeyword,
errorMessage,
}) => {
const hasError = useMemo(() => {
return !!errorMessage;
}, [errorMessage]);

return (
<AdditionalTokenPathInputWrapper>
<input
className={hasError ? 'search-input error' : 'search-input'}
value={keyword}
onChange={(event): void => onChangeKeyword(event.target.value)}
placeholder='Search'
/>

{hasError && (
<Text className='error-message' type='body2Reg' color={theme.red._5}>
{errorMessage}
</Text>
)}
</AdditionalTokenPathInputWrapper>
);
};

export default AdditionalTokenPathInput;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './additional-token-path-input';
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';

import { GlobalPopupStyle } from '@styles/global-style';
import theme from '@styles/theme';
import { render } from '@testing-library/react';
import { RecoilRoot } from 'recoil';
import { ThemeProvider } from 'styled-components';
import AdditionalTokenTypeSelector, {
AddingType,
AdditionalTokenTypeSelectorProps,
} from './additional-token-type-selector';

describe('AdditionalTokenTypeSelector Component', () => {
it('AdditionalTokenTypeSelector render', () => {
const args: AdditionalTokenTypeSelectorProps = {
setType: () => {
return;
},
type: AddingType.MANUAL,
};

render(
<RecoilRoot>
<GlobalPopupStyle />
<ThemeProvider theme={theme}>
<AdditionalTokenTypeSelector {...args} />
</ThemeProvider>
</RecoilRoot>,
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Meta, StoryObj } from '@storybook/react';
import AdditionalTokenTypeSelector, {
type AdditionalTokenTypeSelectorProps,
} from './additional-token-type-selector';

export default {
title: 'components/manage-token/AdditionalTokenTypeSelector',
component: AdditionalTokenTypeSelector,
} as Meta<typeof AdditionalTokenTypeSelector>;

export const Default: StoryObj<AdditionalTokenTypeSelectorProps> = {
args: {},
};
Loading
Loading