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

[Issue-2628] Adjust showing/validating address on Send fund #2640

Merged
merged 9 commits into from
Feb 23, 2024
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 @@ -228,10 +228,10 @@
externalUrl?: string;
rarity?: string;
description?: string;
properties?: Record<any, any> | null;

Check warning on line 231 in packages/extension-base/src/background/KoniTypes.ts

View workflow job for this annotation

GitHub Actions / Build Development Preview

Unexpected any. Specify a different type

Check warning on line 231 in packages/extension-base/src/background/KoniTypes.ts

View workflow job for this annotation

GitHub Actions / Build Development Preview

Unexpected any. Specify a different type
type?: _AssetType.ERC721 | _AssetType.PSP34 | RMRK_VER; // for sending
rmrk_ver?: RMRK_VER;
onChainOption?: any; // for sending PSP-34 tokens, should be done better

Check warning on line 234 in packages/extension-base/src/background/KoniTypes.ts

View workflow job for this annotation

GitHub Actions / Build Development Preview

Unexpected any. Specify a different type
}

export interface NftCollection {
Expand Down Expand Up @@ -525,7 +525,7 @@
[ExtrinsicType.STAKING_COMPOUNDING]: RequestTuringStakeCompound,
[ExtrinsicType.STAKING_CANCEL_COMPOUNDING]: RequestTuringCancelStakeCompound,
[ExtrinsicType.STAKING_CANCEL_UNSTAKE]: RequestStakeCancelWithdrawal,
[ExtrinsicType.STAKING_POOL_WITHDRAW]: any,

Check warning on line 528 in packages/extension-base/src/background/KoniTypes.ts

View workflow job for this annotation

GitHub Actions / Build Development Preview

Unexpected any. Specify a different type

// Yield
[ExtrinsicType.JOIN_YIELD_POOL]: RequestYieldStepSubmit,
Expand Down Expand Up @@ -554,8 +554,8 @@
[ExtrinsicType.TOKEN_APPROVE]: TokenApproveData,

[ExtrinsicType.EVM_EXECUTE]: TransactionConfig,
[ExtrinsicType.CROWDLOAN]: any,

Check warning on line 557 in packages/extension-base/src/background/KoniTypes.ts

View workflow job for this annotation

GitHub Actions / Build Development Preview

Unexpected any. Specify a different type
[ExtrinsicType.UNKNOWN]: any

Check warning on line 558 in packages/extension-base/src/background/KoniTypes.ts

View workflow job for this annotation

GitHub Actions / Build Development Preview

Unexpected any. Specify a different type
}

export enum ExtrinsicStatus {
Expand Down Expand Up @@ -648,7 +648,7 @@
// : T extends ExtrinsicType.MINT_VDOT
// ? Pick<SubmitBifrostLiquidStaking, 'rewardTokenSlug' | 'estimatedAmountReceived'>
// : undefined;
export interface TransactionHistoryItem<ET extends ExtrinsicType = ExtrinsicType.TRANSFER_BALANCE> {

Check warning on line 651 in packages/extension-base/src/background/KoniTypes.ts

View workflow job for this annotation

GitHub Actions / Build Development Preview

'ET' is defined but never used
origin?: 'app' | 'migration' | 'subsquid' | 'subscan', // 'app' or history source
callhash?: string,
signature?: string,
Expand All @@ -673,7 +673,7 @@
tip?: AmountData,
fee?: AmountData,
explorerUrl?: string,
additionalInfo?: any,

Check warning on line 676 in packages/extension-base/src/background/KoniTypes.ts

View workflow job for this annotation

GitHub Actions / Build Development Preview

Unexpected any. Specify a different type
startBlock?: number,
nonce?: number,
}
Expand Down Expand Up @@ -1116,12 +1116,12 @@
recipientAddress: string,

nftItemName?: string, // Use for confirmation view only
params: Record<string, any>,

Check warning on line 1119 in packages/extension-base/src/background/KoniTypes.ts

View workflow job for this annotation

GitHub Actions / Build Development Preview

Unexpected any. Specify a different type
nftItem: NftItem
}

export interface EvmNftTransaction extends ValidateTransactionResponse {
tx: Record<string, any> | null;

Check warning on line 1124 in packages/extension-base/src/background/KoniTypes.ts

View workflow job for this annotation

GitHub Actions / Build Development Preview

Unexpected any. Specify a different type
}

export interface EvmNftSubmitTransaction extends BaseRequestSign {
Expand Down Expand Up @@ -1195,6 +1195,7 @@

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
Loading