Skip to content

Commit

Permalink
Merge pull request #2640 from Koniverse/koni/dev/issue-2628
Browse files Browse the repository at this point in the history
[Issue-2628] Adjust showing/validating address on Send fund
  • Loading branch information
saltict authored Feb 23, 2024
2 parents 41a7666 + 0b410ff commit 69462c7
Show file tree
Hide file tree
Showing 12 changed files with 190 additions and 42 deletions.
1 change: 1 addition & 0 deletions packages/extension-base/src/background/KoniTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1195,6 +1195,7 @@ export interface RequestTransferExistentialDeposit {

export interface RequestSaveRecentAccount {
accountId: string;
chain?: string;
}

export interface SubstrateNftTransaction {
Expand Down
1 change: 1 addition & 0 deletions packages/extension-base/src/background/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export interface AccountJson extends AbstractAddressJson {

export interface AddressJson extends AbstractAddressJson {
isRecent?: boolean;
recentChainSlugs?: string[];
}

// all Accounts and the address of the current Account
Expand Down
56 changes: 47 additions & 9 deletions packages/extension-base/src/koni/background/handlers/Extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { SWTransaction, SWTransactionResponse, SWTransactionResult, TransactionE
import { WALLET_CONNECT_EIP155_NAMESPACE } from '@subwallet/extension-base/services/wallet-connect-service/constants';
import { isProposalExpired, isSupportWalletConnectChain, isSupportWalletConnectNamespace } from '@subwallet/extension-base/services/wallet-connect-service/helpers';
import { ResultApproveWalletConnectSession, WalletConnectNotSupportRequest, WalletConnectSessionRequest } from '@subwallet/extension-base/services/wallet-connect-service/types';
import { AccountsStore } from '@subwallet/extension-base/stores';
import { BalanceJson, BuyServiceInfo, BuyTokenInfo, EarningRewardJson, NominationPoolInfo, OptimalYieldPathParams, RequestEarlyValidateYield, RequestGetYieldPoolTargets, RequestStakeCancelWithdrawal, RequestStakeClaimReward, RequestUnlockDotCheckCanMint, RequestUnlockDotSubscribeMintedData, RequestYieldLeave, RequestYieldStepSubmit, RequestYieldWithdrawal, ResponseGetYieldPoolTargets, ValidateYieldProcessParams, YieldPoolType } from '@subwallet/extension-base/types';
import { convertSubjectInfoToAddresses, isSameAddress, reformatAddress, uniqueStringArray } from '@subwallet/extension-base/utils';
import { calculateGasFeeParams, createTransactionFromRLP, signatureToHex, Transaction as QrTransaction } from '@subwallet/extension-base/utils/eth';
Expand All @@ -45,7 +46,7 @@ import { createPair } from '@subwallet/keyring';
import { KeyringPair, KeyringPair$Json, KeyringPair$Meta } from '@subwallet/keyring/types';
import { keyring } from '@subwallet/ui-keyring';
import { SubjectInfo } from '@subwallet/ui-keyring/observable/types';
import { KeyringAddress } from '@subwallet/ui-keyring/types';
import { KeyringAddress, KeyringJson$Meta } from '@subwallet/ui-keyring/types';
import { ProposalTypes } from '@walletconnect/types/dist/types/sign-client/proposal';
import { SessionTypes } from '@walletconnect/types/dist/types/sign-client/session';
import { getSdkError } from '@walletconnect/utils';
Expand Down Expand Up @@ -521,12 +522,19 @@ export default class KoniExtension {

private subscribeAddresses (id: string, port: chrome.runtime.Port): AddressBookInfo {
const _cb = createSubscription<'pri(accounts.subscribeAddresses)'>(id, port);
let old = '';

const subscription = this.#koniState.keyringService.addressesSubject.subscribe((subjectInfo: SubjectInfo): void => {
const addresses = convertSubjectInfoToAddresses(subjectInfo);
const _new = JSON.stringify(addresses);

_cb({
addresses: addresses
});
if (old !== _new) {
_cb({
addresses: addresses
});

old = _new;
}
});

this.createUnsubscriptionHandle(id, subscription.unsubscribe);
Expand All @@ -542,13 +550,43 @@ export default class KoniExtension {
};
}

private saveRecentAccount ({ accountId }: RequestSaveRecentAccount): KeyringAddress {
private saveRecentAccount ({ accountId, chain }: RequestSaveRecentAccount): KeyringAddress {
if (isAddress((accountId))) {
const address = reformatAddress(accountId);
const account = keyring.getAccount(address);
const contact = keyring.getAddress(address);
const contact = keyring.getAddress(address, 'address');

if (account) {
return account;
} else {
let metadata: KeyringJson$Meta;

if (contact) {
metadata = contact.meta;
} else {
const _new = keyring.saveRecent(address);

metadata = _new.json.meta;
}

if (contact && !metadata.isRecent) {
return contact;
}

const recentChainSlugs: string[] = (metadata.recentChainSlugs as string[]) || [];

if (chain) {
if (!recentChainSlugs.includes(chain)) {
recentChainSlugs.push(chain);
}
}

metadata.recentChainSlugs = recentChainSlugs;

return account || contact || { ...keyring.saveRecent(address).json, publicKey: decodeAddress(address) };
const result = keyring.addresses.add(new AccountsStore(), address, { address: address, meta: metadata });

return { ...result.json, publicKey: decodeAddress(address) };
}
} else {
throw Error(t('This is not an address'));
}
Expand Down Expand Up @@ -1735,7 +1773,7 @@ export default class KoniExtension {
if (new BigN(receiverBalance).plus(transferAmount.value).lt(minAmount)) {
const atLeast = new BigN(minAmount).minus(receiverBalance).plus((tokenInfo.decimals || 0) === 0 ? 0 : 1);

const atLeastStr = formatNumber(atLeast, tokenInfo.decimals || 0, balanceFormatter);
const atLeastStr = formatNumber(atLeast, tokenInfo.decimals || 0, balanceFormatter, { maxNumberFormat: tokenInfo.decimals || 6 });

inputTransaction.errors.push(new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('You must transfer at least {{amount}} {{symbol}} to keep the destination account alive', { replace: { amount: atLeastStr, symbol: tokenInfo.symbol } })));
}
Expand Down Expand Up @@ -1808,7 +1846,7 @@ export default class KoniExtension {

// Check ed for receiver
if (new BigN(value).lt(atLeast)) {
const atLeastStr = formatNumber(atLeast, destinationTokenInfo.decimals || 0, balanceFormatter);
const atLeastStr = formatNumber(atLeast, destinationTokenInfo.decimals || 0, balanceFormatter, { maxNumberFormat: destinationTokenInfo.decimals || 6 });

inputTransaction.errors.push(new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('You must transfer at least {{amount}} {{symbol}} to keep the destination account alive', { replace: { amount: atLeastStr, symbol: originTokenInfo.symbol } })));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -404,14 +404,15 @@ export default class TransactionService {
const transaction = this.getTransaction(id);
const extrinsicType = transaction.extrinsicType;

const chainInfo = this.state.chainService.getChainInfoByKey(transaction.chain);
const formattedTransactionAddress = reformatAddress(transaction.address);

const historyItem: TransactionHistoryItem = {
origin: 'app',
chain: transaction.chain,
direction: TransactionDirection.SEND,
type: transaction.extrinsicType,
from: formattedTransactionAddress,
from: transaction.address,
to: '',
chainType: transaction.chainType,
address: formattedTransactionAddress,
Expand All @@ -426,7 +427,6 @@ export default class TransactionService {
startBlock: startBlock || 0
};

const chainInfo = this.state.chainService.getChainInfoByKey(transaction.chain);
const nativeAsset = _getChainNativeTokenBasicInfo(chainInfo);
const baseNativeAmount = { value: '0', decimals: nativeAsset.decimals, symbol: nativeAsset.symbol };

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const Component: React.FC<Props> = (props: Props) => {
<MetaInfo.Account
address={data.recipientAddress}
label={t('Send to')}
networkPrefix={networkPrefix}
/>

<MetaInfo.Chain
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,17 @@ const Component: React.FC<Props> = ({ className, transaction }: Props) => {
[chainInfoMap, transaction.chain]
);

const receiveChain = useMemo(() => {
if (xcmData) {
return xcmData.destinationNetworkKey || transaction.chain;
} else {
return transaction.chain;
}
}, [transaction.chain, xcmData]);

const { decimals: chainDecimals, symbol: chainSymbol } = useGetNativeTokenBasicInfo(transaction.chain);
const senderPrefix = useGetChainPrefixBySlug(transaction.chain);
const receiverPrefix = useGetChainPrefixBySlug(receiveChain);

return (
<>
Expand All @@ -54,6 +63,7 @@ const Component: React.FC<Props> = ({ className, transaction }: Props) => {
<MetaInfo.Account
address={data.to}
label={t('Send to')}
networkPrefix={receiverPrefix}
/>

{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { useIsMantaPayEnabled } from '@subwallet/extension-koni-ui/hooks/account
import { getMaxTransfer, makeCrossChainTransfer, makeTransfer } from '@subwallet/extension-koni-ui/messaging';
import { RootState } from '@subwallet/extension-koni-ui/stores';
import { ChainItemType, FormCallbacks, Theme, ThemeProps, TransferParams } from '@subwallet/extension-koni-ui/types';
import { findAccountByAddress, formatBalance, isAccountAll, noop } from '@subwallet/extension-koni-ui/utils';
import { findAccountByAddress, formatBalance, isAccountAll, noop, reformatAddress } from '@subwallet/extension-koni-ui/utils';
import { findNetworkJsonByGenesisHash } from '@subwallet/extension-koni-ui/utils/chain/getNetworkJsonByGenesisHash';
import { Button, Form, Icon } from '@subwallet/react-ui';
import { Rule } from '@subwallet/react-ui/es/form';
Expand Down Expand Up @@ -325,6 +325,16 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement<Props> => {
return Promise.resolve();
}

if (!isEthereumAddress(_recipientAddress)) {
const destChainInfo = chainInfoMap[destChain];
const addressPrefix = destChainInfo?.substrateInfo?.addressPrefix ?? 42;
const _addressOnChain = reformatAddress(_recipientAddress, addressPrefix);

if (_addressOnChain !== _recipientAddress) {
return Promise.reject(t('Recipient should be a valid {{networkName}} address', { replace: { networkName: destChainInfo.name } }));
}
}

const isOnChain = chain === destChain;

const account = findAccountByAddress(accounts, _recipientAddress);
Expand Down Expand Up @@ -442,11 +452,11 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement<Props> => {
// Submit transaction
const onSubmit: FormCallbacks<TransferParams>['onFinish'] = useCallback((values: TransferParams) => {
setLoading(true);
const { asset, chain, destChain, from, to, value } = values;
const { asset, chain, destChain, from: _from, to, value } = values;

let sendPromise: Promise<SWTransactionResponse>;

const account = findAccountByAddress(accounts, from);
const account = findAccountByAddress(accounts, _from);

if (!account) {
setLoading(false);
Expand All @@ -458,6 +468,10 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement<Props> => {
return;
}

const chainInfo = chainInfoMap[chain];
const addressPrefix = chainInfo?.substrateInfo?.addressPrefix ?? 42;
const from = reformatAddress(_from, addressPrefix);

const isLedger = !!account.isHardware;
const isEthereum = isEthereumAddress(account.address);
const chainAsset = assetRegistry[asset];
Expand Down Expand Up @@ -519,7 +533,7 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement<Props> => {
})
;
}, 300);
}, [accounts, assetRegistry, notification, t, isTransferAll, onSuccess, onError]);
}, [accounts, chainInfoMap, assetRegistry, notification, t, isTransferAll, onSuccess, onError]);

const onFilterAccountFunc = useMemo(() => filterAccountFunc(chainInfoMap, assetRegistry, multiChainAssetMap, sendFundSlug), [assetRegistry, chainInfoMap, multiChainAssetMap, sendFundSlug]);

Expand Down Expand Up @@ -691,7 +705,8 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement<Props> => {
<AddressInput
addressPrefix={destChainNetworkPrefix}
allowDomain={true}
chain={chain}
chain={destChain}
fitNetwork={true}
label={t('Send to')}
networkGenesisHash={destChainGenesisHash}
placeholder={t('Account address')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { DataContext } from '@subwallet/extension-koni-ui/contexts/DataContext';
import { useFocusFormItem, useGetChainPrefixBySlug, useHandleSubmitTransaction, useInitValidateTransaction, usePreCheckAction, useRestoreTransaction, useSelector, useSetCurrentPage, useTransactionContext, useWatchTransaction } from '@subwallet/extension-koni-ui/hooks';
import { evmNftSubmitTransaction, substrateNftSubmitTransaction } from '@subwallet/extension-koni-ui/messaging';
import { FormCallbacks, FormFieldData, FormInstance, FormRule, SendNftParams, ThemeProps } from '@subwallet/extension-koni-ui/types';
import { findAccountByAddress, noop, simpleCheckForm } from '@subwallet/extension-koni-ui/utils';
import { findAccountByAddress, noop, reformatAddress, simpleCheckForm } from '@subwallet/extension-koni-ui/utils';
import { Button, Form, Icon, Image, Typography } from '@subwallet/react-ui';
import CN from 'classnames';
import { ArrowCircleRight } from 'phosphor-react';
Expand Down Expand Up @@ -103,6 +103,16 @@ const Component: React.FC = () => {
return Promise.reject(t('Invalid recipient address'));
}

if (!isEthereumAddress(_recipientAddress)) {
const chainInfo = chainInfoMap[chain];
const addressPrefix = chainInfo?.substrateInfo?.addressPrefix ?? 42;
const _addressOnChain = reformatAddress(_recipientAddress, addressPrefix);

if (_addressOnChain !== _recipientAddress) {
return Promise.reject(t('Recipient should be a valid {{networkName}} address', { replace: { networkName: chainInfo.name } }));
}
}

if (isSameAddress(_recipientAddress, from)) {
return Promise.reject(t('The recipient address can not be the same as the sender address'));
}
Expand Down Expand Up @@ -141,8 +151,11 @@ const Component: React.FC = () => {
// Submit transaction
const onSubmit: FormCallbacks<SendNftParams>['onFinish'] = useCallback(
(values: SendNftParams) => {
const isEthereumInterface = isEthereumAddress(from);
const { to } = values;
const { chain, from: _from, to } = values;
const isEthereumInterface = isEthereumAddress(_from);

const from = reformatAddress(_from, addressPrefix);

const params = nftParamsHandler(nftItem, chain);
let sendPromise: Promise<SWTransactionResponse>;

Expand Down Expand Up @@ -180,7 +193,7 @@ const Component: React.FC = () => {
});
}, 300);
},
[chain, from, nftItem, onError, onSuccess]
[nftItem, onError, onSuccess, addressPrefix]
);

const checkAction = usePreCheckAction(from);
Expand Down Expand Up @@ -242,6 +255,7 @@ const Component: React.FC = () => {
addressPrefix={addressPrefix}
allowDomain={true}
chain={chain}
fitNetwork={true}
label={t('Send to')}
networkGenesisHash={chainGenesisHash}
placeholder={t('Account address')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,33 @@ import AvatarGroup from '@subwallet/extension-koni-ui/components/Account/Info/Av
import AccountItemBase, { AccountItemBaseProps } from '@subwallet/extension-koni-ui/components/Account/Item/AccountItemBase';
import { isAccountAll, toShort } from '@subwallet/extension-koni-ui/utils';
import CN from 'classnames';
import React from 'react';
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import styled from 'styled-components';

interface Props extends AccountItemBaseProps {
direction?: 'vertical' | 'horizontal';
accounts?: AbstractAddressJson[];
fallbackName?: boolean;
}

const Component: React.FC<Props> = (props: Props) => {
const { accountName, accounts, address, addressPreLength = 4, addressSufLength = 4, direction = 'horizontal' } = props;
const { accountName, accounts, address, addressPreLength = 4, addressSufLength = 4, direction = 'horizontal', fallbackName = true } = props;
const isAll = isAccountAll(address);
const { t } = useTranslation();

const showFallback = useMemo(() => {
if (isAll) {
return false;
} else {
if (fallbackName) {
return true;
} else {
return !!accountName;
}
}
}, [accountName, fallbackName, isAll]);

return (
<AccountItemBase
{...props}
Expand All @@ -29,7 +42,7 @@ const Component: React.FC<Props> = (props: Props) => {
middleItem={(
<div className={CN('account-item-content-wrapper', `direction-${direction}`)}>
<div className={'account-item-name'}>{isAll ? t('All accounts') : (accountName || toShort(address, addressPreLength, addressSufLength))}</div>
{!isAll && <div className={'account-item-address-wrapper'}>{toShort(address, addressPreLength, addressSufLength)}</div>}
{showFallback && <div className={'account-item-address-wrapper'}>{toShort(address, addressPreLength, addressSufLength)}</div>}
</div>
)}
/>
Expand Down
Loading

1 comment on commit 69462c7

@saltict
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.