@subwallet/extension-base 1.3.8-0 → 1.3.9-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,6 +13,6 @@ const packageInfo = {
13
13
  name: '@subwallet/extension-base',
14
14
  path: typeof __dirname === 'string' ? __dirname : 'auto',
15
15
  type: 'cjs',
16
- version: '1.3.8-0'
16
+ version: '1.3.9-0'
17
17
  };
18
18
  exports.packageInfo = packageInfo;
@@ -17,6 +17,7 @@ var _utils2 = require("@subwallet/extension-base/utils");
17
17
  var _keyring = require("@subwallet/keyring");
18
18
  var _types2 = require("@subwallet/keyring/types");
19
19
  var _uiKeyring = _interopRequireDefault(require("@subwallet/ui-keyring"));
20
+ var _bignumber = _interopRequireDefault(require("bignumber.js"));
20
21
  var _i18next = require("i18next");
21
22
  var _rxjs = require("rxjs");
22
23
  var _util = require("@polkadot/util");
@@ -170,7 +171,10 @@ class BalanceService {
170
171
  }
171
172
 
172
173
  /** Subscribe token free balance of an address on chain */
173
- async subscribeTransferableBalance(address, chain, tokenSlug, extrinsicType, callback) {
174
+ async subscribeBalance(address, chain, tokenSlug) {
175
+ let balanceType = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'transferable';
176
+ let extrinsicType = arguments.length > 4 ? arguments[4] : undefined;
177
+ let callback = arguments.length > 5 ? arguments[5] : undefined;
174
178
  const chainInfo = this.state.chainService.getChainInfoByKey(chain);
175
179
  const chainState = this.state.chainService.getChainStateByKey(chain);
176
180
  if (!chainInfo || !chainState || !chainState.active) {
@@ -199,10 +203,18 @@ class BalanceService {
199
203
  let unsub = _util.noop;
200
204
  unsub = (0, _helpers.subscribeBalance)([address], [chain], [tSlug], assetMap, chainInfoMap, substrateApiMap, evmApiMap, tonApiMap, result => {
201
205
  const rs = result[0];
206
+ let value;
207
+ switch (balanceType) {
208
+ case 'total':
209
+ value = new _bignumber.default(rs.free).plus(new _bignumber.default(rs.locked)).toFixed();
210
+ break;
211
+ default:
212
+ value = rs.free;
213
+ }
202
214
  if (rs.tokenSlug === tSlug && rs.state !== _KoniTypes.APIItemState.PENDING) {
203
215
  hasError = false;
204
216
  const balance = {
205
- value: rs.free,
217
+ value,
206
218
  decimals: tokenInfo.decimals || 0,
207
219
  symbol: tokenInfo.symbol,
208
220
  metadata: rs.metadata
@@ -226,6 +238,12 @@ class BalanceService {
226
238
  }, 9999);
227
239
  });
228
240
  }
241
+ async subscribeTransferableBalance(address, chain, tokenSlug, extrinsicType, callback) {
242
+ return this.subscribeBalance(address, chain, tokenSlug, 'transferable', extrinsicType, callback);
243
+ }
244
+ async subscribeTotalBalance(address, chain, tokenSlug, extrinsicType, callback) {
245
+ return this.subscribeBalance(address, chain, tokenSlug, 'total', extrinsicType, callback);
246
+ }
229
247
 
230
248
  /**
231
249
  * @public
@@ -242,6 +260,10 @@ class BalanceService {
242
260
  const [, balance] = await this.subscribeTransferableBalance(address, chain, tokenSlug, extrinsicType);
243
261
  return balance;
244
262
  }
263
+ async getTotalBalance(address, chain, tokenSlug, extrinsicType) {
264
+ const [, balance] = await this.subscribeTotalBalance(address, chain, tokenSlug, extrinsicType);
265
+ return balance;
266
+ }
245
267
 
246
268
  /** Remove balance from the subject object by addresses */
247
269
  removeBalanceByAddresses(addresses) {
@@ -15,6 +15,7 @@ var _utils2 = require("@subwallet/extension-base/koni/api/contract-handler/wasm/
15
15
  var _constants = require("@subwallet/extension-base/services/chain-service/constants");
16
16
  var _utils3 = require("@subwallet/extension-base/services/fee-service/utils");
17
17
  var _bignumber = _interopRequireDefault(require("bignumber.js"));
18
+ var _i18next = require("i18next");
18
19
  // Copyright 2019-2022 @subwallet/extension-base
19
20
  // SPDX-License-Identifier: Apache-2.0
20
21
 
@@ -88,11 +89,21 @@ async function getERC721Transaction(web3Api, chain, contractAddress, senderAddre
88
89
  var _priority$maxFeePerGa3, _priority$maxPriority3;
89
90
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
90
91
  const contract = new web3Api.api.eth.Contract(_utils._ERC721_ABI, contractAddress);
91
- const [gasLimit, priority] = await Promise.all([
92
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
93
- contract.methods.safeTransferFrom(senderAddress, recipientAddress, tokenId).estimateGas({
94
- from: senderAddress
95
- }), (0, _utils3.calculateGasFeeParams)(web3Api, chain)]);
92
+ let gasLimit;
93
+ let priority;
94
+ try {
95
+ [gasLimit, priority] = await Promise.all([
96
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
97
+ contract.methods.safeTransferFrom(senderAddress, recipientAddress, tokenId).estimateGas({
98
+ from: senderAddress
99
+ }), (0, _utils3.calculateGasFeeParams)(web3Api, chain)]);
100
+ } catch (e) {
101
+ const error = e;
102
+ if (error.message.includes('transfer to non ERC721Receiver implementer')) {
103
+ error.message = (0, _i18next.t)('Unable to send. NFT not supported on recipient address');
104
+ }
105
+ throw error;
106
+ }
96
107
  return {
97
108
  from: senderAddress,
98
109
  gasPrice: priority.gasPrice,
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports._ZK_ASSET_PREFIX = exports._XCM_TYPE = exports._XCM_CHAIN_GROUP = exports._TRANSFER_NOT_SUPPORTED_CHAINS = exports._TRANSFER_CHAIN_GROUP = exports._SUBSTRATE_DEFAULT_INFLATION_PARAMS = exports._STAKING_ERA_LENGTH_MAP = exports._PURE_EVM_CHAINS = exports._PREDEFINED_SINGLE_MODES = exports._PARACHAIN_INFLATION_DISTRIBUTION = exports._NFT_CHAIN_GROUP = exports._MULTI_CHAIN_ASSET_SRC = exports._MANTA_ZK_CHAIN_GROUP = exports._KNOWN_CHAIN_INFLATION_PARAMS = exports._EXPECTED_BLOCK_TIME = exports._DEFAULT_MANTA_ZK_CHAIN = exports._DEFAULT_ACTIVE_CHAINS = exports._CHAIN_LOGO_MAP_SRC = exports._CHAIN_INFO_SRC = exports._CHAIN_ASSET_SRC = exports._BALANCE_TOKEN_GROUP = exports._BALANCE_PARSING_CHAIN_GROUP = exports._BALANCE_CHAIN_GROUP = exports._ASSET_REF_SRC = exports._ASSET_LOGO_MAP_SRC = exports._API_OPTIONS_CHAIN_GROUP = exports.LATEST_CHAIN_DATA_FETCHING_INTERVAL = exports.EVM_REFORMAT_DECIMALS = exports.EVM_PASS_CONNECT_STATUS = exports.API_MAX_RETRY = exports.API_CONNECT_TIMEOUT = exports.API_AUTO_CONNECT_MS = void 0;
6
+ exports._ZK_ASSET_PREFIX = exports._XCM_TYPE = exports._XCM_CHAIN_GROUP = exports._TRANSFER_NOT_SUPPORTED_CHAINS = exports._TRANSFER_CHAIN_GROUP = exports._SUBSTRATE_DEFAULT_INFLATION_PARAMS = exports._STAKING_ERA_LENGTH_MAP = exports._PURE_EVM_CHAINS = exports._PREDEFINED_SINGLE_MODES = exports._PARACHAIN_INFLATION_DISTRIBUTION = exports._NFT_CHAIN_GROUP = exports._MULTI_CHAIN_ASSET_SRC = exports._MANTA_ZK_CHAIN_GROUP = exports._KNOWN_CHAIN_INFLATION_PARAMS = exports._EXPECTED_BLOCK_TIME = exports._DEFAULT_MANTA_ZK_CHAIN = exports._DEFAULT_ACTIVE_CHAINS = exports._CHAIN_LOGO_MAP_SRC = exports._CHAIN_INFO_SRC = exports._CHAIN_ASSET_SRC = exports._BALANCE_TOKEN_GROUP = exports._BALANCE_PARSING_CHAIN_GROUP = exports._BALANCE_CHAIN_GROUP = exports._ASSET_REF_SRC = exports._ASSET_LOGO_MAP_SRC = exports._API_OPTIONS_CHAIN_GROUP = exports.SUFFICIENT_CHAIN = exports.LATEST_CHAIN_DATA_FETCHING_INTERVAL = exports.EVM_REFORMAT_DECIMALS = exports.EVM_PASS_CONNECT_STATUS = exports.API_MAX_RETRY = exports.API_CONNECT_TIMEOUT = exports.API_AUTO_CONNECT_MS = void 0;
7
7
  var _chainList = require("@subwallet/chain-list");
8
8
  var _types = require("@subwallet/chain-list/types");
9
9
  var _KoniTypes = require("@subwallet/extension-base/background/KoniTypes");
@@ -292,6 +292,8 @@ const _XCM_CHAIN_GROUP = {
292
292
  // default is xTokens pallet
293
293
  };
294
294
  exports._XCM_CHAIN_GROUP = _XCM_CHAIN_GROUP;
295
+ const SUFFICIENT_CHAIN = ['astar', 'calamari', 'parallel', 'darwinia2', 'crabParachain', 'pangolin', 'statemint', 'moonriver', 'shiden', 'moonbeam', 'statemine', 'liberland', 'dentnet', 'phala', 'crust', 'dbcchain', 'rococo_assethub'];
296
+ exports.SUFFICIENT_CHAIN = SUFFICIENT_CHAIN;
295
297
  const _XCM_TYPE = {
296
298
  RP: `${_types._SubstrateChainType.RELAYCHAIN}-${_types._SubstrateChainType.PARACHAIN}`,
297
299
  // DMP
@@ -50,4 +50,5 @@ exports.TransferTxErrorType = TransferTxErrorType;
50
50
  TransferTxErrorType["INVALID_TOKEN"] = "INVALID_TOKEN";
51
51
  TransferTxErrorType["TRANSFER_ERROR"] = "TRANSFER_ERROR";
52
52
  TransferTxErrorType["RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT"] = "RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT";
53
+ TransferTxErrorType["RECEIVER_ACCOUNT_INACTIVE"] = "RECEIVER_ACCOUNT_INACTIVE";
53
54
  })(TransferTxErrorType || (exports.TransferTxErrorType = TransferTxErrorType = {}));
@@ -2,12 +2,13 @@ import { _ChainAsset, _ChainInfo } from '@subwallet/chain-list/types';
2
2
  import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError';
3
3
  import { _Address, AmountData, ExtrinsicType, FeeData } from '@subwallet/extension-base/background/KoniTypes';
4
4
  import { TransactionWarning } from '@subwallet/extension-base/background/warnings/TransactionWarning';
5
+ import { FrameSystemAccountInfo } from '@subwallet/extension-base/core/substrate/types';
5
6
  import { _EvmApi, _TonApi } from '@subwallet/extension-base/services/chain-service/types';
6
7
  import { OptionalSWTransaction, SWTransactionInput, SWTransactionResponse } from '@subwallet/extension-base/services/transaction-service/types';
7
8
  import { KeyringPair } from '@subwallet/keyring/types';
8
9
  import BigN from 'bignumber.js';
9
10
  export declare function validateTransferRequest(tokenInfo: _ChainAsset, from: _Address, to: _Address, value: string | undefined, transferAll: boolean | undefined): [TransactionError[], KeyringPair | undefined, BigN | undefined];
10
- export declare function additionalValidateTransfer(tokenInfo: _ChainAsset, nativeTokenInfo: _ChainAsset, extrinsicType: ExtrinsicType, receiverTransferTokenFreeBalance: string, transferAmount: string, senderTransferTokenTransferable?: string, receiverNativeTransferable?: string): [TransactionWarning[], TransactionError[]];
11
+ export declare function additionalValidateTransferForRecipient(sendingTokenInfo: _ChainAsset, nativeTokenInfo: _ChainAsset, extrinsicType: ExtrinsicType, receiverSendingTokenKeepAliveBalance: bigint, transferAmount: bigint, senderSendingTokenTransferable?: bigint, receiverSystemAccountInfo?: FrameSystemAccountInfo, isSendingTokenSufficient?: boolean): [TransactionWarning[], TransactionError[]];
11
12
  export declare function validateXcmTransferRequest(destTokenInfo: _ChainAsset | undefined, sender: _Address, sendingValue: string): [TransactionError[], KeyringPair | undefined, BigN | undefined];
12
13
  export declare function additionalValidateXcmTransfer(originTokenInfo: _ChainAsset, destinationTokenInfo: _ChainAsset, sendingAmount: string, senderTransferable: string, receiverNativeBalance: string, destChainInfo: _ChainInfo, isSnowBridge?: boolean): [TransactionWarning | undefined, TransactionError | undefined];
13
14
  export declare function checkSupportForFeature(validationResponse: SWTransactionResponse, blockedFeaturesList: string[], chainInfo: _ChainInfo): void;
@@ -5,10 +5,10 @@ import { TransactionError } from '@subwallet/extension-base/background/errors/Tr
5
5
  import { ExtrinsicType } from '@subwallet/extension-base/background/KoniTypes';
6
6
  import { TransactionWarning } from '@subwallet/extension-base/background/warnings/TransactionWarning';
7
7
  import { LEDGER_SIGNING_COMPATIBLE_MAP, SIGNING_COMPATIBLE_MAP, XCM_MIN_AMOUNT_RATIO } from '@subwallet/extension-base/constants';
8
- import { _canAccountBeReaped } from '@subwallet/extension-base/core/substrate/system-pallet';
8
+ import { _canAccountBeReaped, _isAccountActive } from '@subwallet/extension-base/core/substrate/system-pallet';
9
9
  import { isBounceableAddress } from '@subwallet/extension-base/services/balance-service/helpers/subscribe/ton/utils';
10
10
  import { _TRANSFER_CHAIN_GROUP } from '@subwallet/extension-base/services/chain-service/constants';
11
- import { _getChainExistentialDeposit, _getChainNativeTokenBasicInfo, _getContractAddressOfToken, _getTokenMinAmount, _isNativeToken, _isTokenEvmSmartContract, _isTokenTonSmartContract } from '@subwallet/extension-base/services/chain-service/utils';
11
+ import { _getAssetDecimals, _getChainExistentialDeposit, _getChainNativeTokenBasicInfo, _getContractAddressOfToken, _getTokenMinAmount, _isNativeToken, _isTokenEvmSmartContract, _isTokenTonSmartContract } from '@subwallet/extension-base/services/chain-service/utils';
12
12
  import { calculateGasFeeParams } from '@subwallet/extension-base/services/fee-service/utils';
13
13
  import { isSubstrateTransaction, isTonTransaction } from '@subwallet/extension-base/services/transaction-service/helpers';
14
14
  import { AccountSignMode, BasicTxErrorType, BasicTxWarningCode, TransferTxErrorType } from '@subwallet/extension-base/types';
@@ -43,44 +43,55 @@ export function validateTransferRequest(tokenInfo, from, to, value, transferAll)
43
43
  }
44
44
  return [errors, keypair, transferValue];
45
45
  }
46
- export function additionalValidateTransfer(tokenInfo, nativeTokenInfo, extrinsicType, receiverTransferTokenFreeBalance, transferAmount, senderTransferTokenTransferable, receiverNativeTransferable) {
47
- const minAmount = _getTokenMinAmount(tokenInfo);
48
- const nativeMinAmount = _getTokenMinAmount(nativeTokenInfo);
46
+ export function additionalValidateTransferForRecipient(sendingTokenInfo, nativeTokenInfo, extrinsicType, receiverSendingTokenKeepAliveBalance, transferAmount, senderSendingTokenTransferable, receiverSystemAccountInfo, isSendingTokenSufficient) {
47
+ const sendingTokenMinAmount = BigInt(_getTokenMinAmount(sendingTokenInfo));
48
+ const nativeTokenMinAmount = _getTokenMinAmount(nativeTokenInfo);
49
49
  const warnings = [];
50
50
  const errors = [];
51
-
52
- // Check ed of not native token for sender after sending
53
- if (extrinsicType === ExtrinsicType.TRANSFER_TOKEN && senderTransferTokenTransferable) {
54
- if (new BigN(senderTransferTokenTransferable).minus(transferAmount).lt(minAmount)) {
51
+ const remainingSendingTokenOfSenderEnoughED = senderSendingTokenTransferable ? senderSendingTokenTransferable - transferAmount >= sendingTokenMinAmount : false;
52
+ const isReceiverAliveByNativeToken = receiverSystemAccountInfo ? _isAccountActive(receiverSystemAccountInfo) : false;
53
+ const isReceivingAmountPassED = receiverSendingTokenKeepAliveBalance + transferAmount >= sendingTokenMinAmount;
54
+ if (extrinsicType === ExtrinsicType.TRANSFER_TOKEN) {
55
+ if (!remainingSendingTokenOfSenderEnoughED) {
55
56
  const warning = new TransactionWarning(BasicTxWarningCode.NOT_ENOUGH_EXISTENTIAL_DEPOSIT);
56
57
  warnings.push(warning);
57
58
  }
58
- }
59
-
60
- // Check ed for receiver before sending
61
- if (extrinsicType === ExtrinsicType.TRANSFER_TOKEN && receiverNativeTransferable) {
62
- if (new BigN(receiverNativeTransferable).lt(nativeMinAmount)) {
63
- const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('The recipient account has {{amount}} {{nativeSymbol}} which can lead to your {{localSymbol}} being lost. Change recipient account and try again', {
59
+ if (!isReceiverAliveByNativeToken && !isSendingTokenSufficient) {
60
+ const balanceKeepAlive = formatNumber(nativeTokenMinAmount, _getAssetDecimals(nativeTokenInfo), balanceFormatter, {
61
+ maxNumberFormat: _getAssetDecimals(nativeTokenInfo) || 6
62
+ });
63
+ const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('The recipient account has less than {{amount}} {{nativeSymbol}}, which can lead to your {{localSymbol}} being lost. Change recipient account and try again', {
64
64
  replace: {
65
- amount: receiverNativeTransferable,
65
+ amount: balanceKeepAlive,
66
66
  nativeSymbol: nativeTokenInfo.symbol,
67
- localSymbol: tokenInfo.symbol
67
+ localSymbol: sendingTokenInfo.symbol
68
+ }
69
+ }));
70
+ errors.push(error);
71
+ }
72
+ if (!isReceivingAmountPassED) {
73
+ const atLeast = sendingTokenMinAmount - receiverSendingTokenKeepAliveBalance;
74
+ const atLeastStr = formatNumber(atLeast.toString(), _getAssetDecimals(sendingTokenInfo), balanceFormatter, {
75
+ maxNumberFormat: _getAssetDecimals(sendingTokenInfo) || 6
76
+ });
77
+ const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('You must transfer at least {{amount}} {{symbol}} to avoid losing funds on the recipient account. Increase amount and try again', {
78
+ replace: {
79
+ amount: atLeastStr,
80
+ symbol: sendingTokenInfo.symbol
68
81
  }
69
82
  }));
70
83
  errors.push(error);
71
84
  }
72
85
  }
73
-
74
- // Check ed for receiver after sending
75
- if (new BigN(receiverTransferTokenFreeBalance).plus(transferAmount).lt(minAmount)) {
76
- const atLeast = new BigN(minAmount).minus(receiverTransferTokenFreeBalance).plus((tokenInfo.decimals || 0) === 0 ? 0 : 1);
77
- const atLeastStr = formatNumber(atLeast, tokenInfo.decimals || 0, balanceFormatter, {
78
- maxNumberFormat: tokenInfo.decimals || 6
86
+ if (!isReceivingAmountPassED) {
87
+ const atLeast = sendingTokenMinAmount - receiverSendingTokenKeepAliveBalance;
88
+ const atLeastStr = formatNumber(atLeast.toString(), _getAssetDecimals(sendingTokenInfo), balanceFormatter, {
89
+ maxNumberFormat: _getAssetDecimals(sendingTokenInfo) || 6
79
90
  });
80
- const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('You must transfer at least {{amount}} {{symbol}} to keep the destination account alive', {
91
+ const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('You must transfer at least {{amount}} {{symbol}} to keep the recipient account alive. Increase amount and try again', {
81
92
  replace: {
82
93
  amount: atLeastStr,
83
- symbol: tokenInfo.symbol
94
+ symbol: sendingTokenInfo.symbol
84
95
  }
85
96
  }));
86
97
  errors.push(error);
@@ -20,7 +20,7 @@ export function _canAccountBeReaped(accountInfo) {
20
20
  }
21
21
 
22
22
  export function _isAccountActive(accountInfo) {
23
- return accountInfo.providers === 0 && accountInfo.consumers === 0;
23
+ return !(accountInfo.consumers === 0 && accountInfo.providers === 0 && accountInfo.sufficients === 0);
24
24
  }
25
25
  export function _getSystemPalletTotalBalance(accountInfo) {
26
26
  if (isV1(accountInfo)) {
package/core/types.d.ts CHANGED
@@ -23,4 +23,5 @@ export interface ValidateRecipientParams {
23
23
  account: AccountJson | null;
24
24
  actionType: ActionType;
25
25
  autoFormatValue?: boolean;
26
+ allowLedgerGenerics: string[];
26
27
  }
package/core/utils.js CHANGED
@@ -2,7 +2,6 @@
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
4
  import { ExtrinsicType } from '@subwallet/extension-base/background/KoniTypes';
5
- import { LEDGER_GENERIC_ALLOW_NETWORKS } from '@subwallet/extension-base/core/consts';
6
5
  import { BalanceAccountType } from '@subwallet/extension-base/core/substrate/types';
7
6
  import { tonAddressInfo } from '@subwallet/extension-base/services/balance-service/helpers/subscribe/ton/utils';
8
7
  import { _isChainEvmCompatible, _isChainSubstrateCompatible, _isChainTonCompatible } from '@subwallet/extension-base/services/chain-service/utils';
@@ -99,6 +98,7 @@ export function _isNotDuplicateAddress(validateRecipientParams) {
99
98
  export function _isSupportLedgerAccount(validateRecipientParams) {
100
99
  const {
101
100
  account,
101
+ allowLedgerGenerics,
102
102
  destChainInfo
103
103
  } = validateRecipientParams;
104
104
  if (account !== null && account !== void 0 && account.isHardware) {
@@ -113,7 +113,7 @@ export function _isSupportLedgerAccount(validateRecipientParams) {
113
113
  } else {
114
114
  // For ledger generic
115
115
  const ledgerCheck = ledgerMustCheckNetwork(account);
116
- if (ledgerCheck !== 'unnecessary' && !LEDGER_GENERIC_ALLOW_NETWORKS.includes(destChainInfo.slug)) {
116
+ if (ledgerCheck !== 'unnecessary' && !allowLedgerGenerics.includes(destChainInfo.slug)) {
117
117
  return `Ledger ${ledgerCheck === 'polkadot' ? 'Polkadot' : 'Migration'} address is not supported for this transfer`;
118
118
  }
119
119
  }
@@ -129,9 +129,11 @@ export default class KoniExtension {
129
129
  private recoverDotSamaApi;
130
130
  private validateERC721Token;
131
131
  private upsertCustomToken;
132
+ private isSufficientToken;
132
133
  private deleteCustomAsset;
133
134
  private validateCustomAsset;
134
135
  private getAddressTransferableBalance;
136
+ private getAddressTotalBalance;
135
137
  private getMaxTransferable;
136
138
  private getXcmMaxTransferable;
137
139
  private getNativeTokenMaxTransferable;
@@ -9,7 +9,7 @@ import { withErrorLog } from '@subwallet/extension-base/background/handlers/help
9
9
  import { createSubscription } from '@subwallet/extension-base/background/handlers/subscriptions';
10
10
  import { CampaignDataType, ChainType, ExternalRequestPromiseStatus, ExtrinsicType, MantaPayEnableMessage, StakingType } from '@subwallet/extension-base/background/KoniTypes';
11
11
  import { ALL_ACCOUNT_KEY, LATEST_SESSION, XCM_FEE_RATIO } from '@subwallet/extension-base/constants';
12
- import { additionalValidateTransfer, additionalValidateXcmTransfer, validateTransferRequest, validateXcmTransferRequest } from '@subwallet/extension-base/core/logic-validation/transfer';
12
+ import { additionalValidateTransferForRecipient, additionalValidateXcmTransfer, validateTransferRequest, validateXcmTransferRequest } from '@subwallet/extension-base/core/logic-validation/transfer';
13
13
  import { _isSnowBridgeXcm } from '@subwallet/extension-base/core/substrate/xcm-parser';
14
14
  import { ALLOWED_PATH } from '@subwallet/extension-base/defaults';
15
15
  import { getERC20SpendingApprovalTx } from '@subwallet/extension-base/koni/api/contract-handler/evm/web3';
@@ -29,12 +29,11 @@ import { createTonTransaction } from '@subwallet/extension-base/services/balance
29
29
  import { createAvailBridgeExtrinsicFromAvail, createAvailBridgeTxFromEth, createPolygonBridgeExtrinsic, createSnowBridgeExtrinsic, createXcmExtrinsic, getXcmMockTxFee } from '@subwallet/extension-base/services/balance-service/transfer/xcm';
30
30
  import { getClaimTxOnAvail, getClaimTxOnEthereum, isAvailChainBridge } from '@subwallet/extension-base/services/balance-service/transfer/xcm/availBridge';
31
31
  import { _isPolygonChainBridge, getClaimPolygonBridge, isClaimedPolygonBridge } from '@subwallet/extension-base/services/balance-service/transfer/xcm/polygonBridge';
32
- import { _API_OPTIONS_CHAIN_GROUP, _DEFAULT_MANTA_ZK_CHAIN, _MANTA_ZK_CHAIN_GROUP, _ZK_ASSET_PREFIX } from '@subwallet/extension-base/services/chain-service/constants';
32
+ import { _API_OPTIONS_CHAIN_GROUP, _DEFAULT_MANTA_ZK_CHAIN, _MANTA_ZK_CHAIN_GROUP, _ZK_ASSET_PREFIX, SUFFICIENT_CHAIN } from '@subwallet/extension-base/services/chain-service/constants';
33
33
  import { _ChainConnectionStatus } from '@subwallet/extension-base/services/chain-service/types';
34
- import { _getAssetDecimals, _getAssetSymbol, _getChainNativeTokenBasicInfo, _getContractAddressOfToken, _getEvmChainId, _isAssetSmartContractNft, _isChainEvmCompatible, _isChainTonCompatible, _isCustomAsset, _isLocalToken, _isMantaZkAsset, _isNativeToken, _isPureEvmChain, _isTokenEvmSmartContract, _isTokenTransferredByEvm, _isTokenTransferredByTon } from '@subwallet/extension-base/services/chain-service/utils';
34
+ import { _getAssetDecimals, _getAssetSymbol, _getChainNativeTokenBasicInfo, _getContractAddressOfToken, _getEvmChainId, _getTokenOnChainAssetId, _getXcmAssetMultilocation, _isAssetSmartContractNft, _isBridgedToken, _isChainEvmCompatible, _isChainSubstrateCompatible, _isChainTonCompatible, _isCustomAsset, _isLocalToken, _isMantaZkAsset, _isNativeToken, _isPureEvmChain, _isTokenEvmSmartContract, _isTokenTransferredByEvm, _isTokenTransferredByTon } from '@subwallet/extension-base/services/chain-service/utils';
35
35
  import { EXTENSION_REQUEST_URL } from '@subwallet/extension-base/services/request-service/constants';
36
36
  import { DEFAULT_AUTO_LOCK_TIME } from '@subwallet/extension-base/services/setting-service/constants';
37
- import { WALLET_CONNECT_EIP155_NAMESPACE } from '@subwallet/extension-base/services/wallet-connect-service/constants';
38
37
  import { isProposalExpired, isSupportWalletConnectChain, isSupportWalletConnectNamespace } from '@subwallet/extension-base/services/wallet-connect-service/helpers';
39
38
  import { SWStorage } from '@subwallet/extension-base/storage';
40
39
  import { AccountsStore } from '@subwallet/extension-base/stores';
@@ -1179,35 +1178,41 @@ export default class KoniExtension {
1179
1178
  }
1180
1179
  const transferNativeAmount = isTransferNativeToken ? transferAmount.value : '0';
1181
1180
  const additionalValidator = async inputTransaction => {
1182
- let senderTransferTokenTransferable;
1183
- let receiverNativeTransferable;
1181
+ let senderSendingTokenTransferable;
1182
+ let receiverSystemAccountInfo;
1183
+ if (!_isChainSubstrateCompatible(chainInfo)) {
1184
+ return undefined;
1185
+ }
1184
1186
 
1185
1187
  // Check ed for sender
1186
1188
  if (!isTransferNativeToken) {
1187
- const [_senderTransferTokenTransferable, _receiverNativeTransferable] = await Promise.all([this.getAddressTransferableBalance({
1189
+ const [_senderSendingTokenTransferable, _receiverNativeTotal] = await Promise.all([this.getAddressTransferableBalance({
1188
1190
  address: from,
1189
1191
  networkKey,
1190
1192
  token: tokenSlug,
1191
1193
  extrinsicType
1192
- }), this.getAddressTransferableBalance({
1194
+ }), this.getAddressTotalBalance({
1193
1195
  address: to,
1194
1196
  networkKey,
1195
1197
  token: nativeTokenSlug,
1196
1198
  extrinsicType: ExtrinsicType.TRANSFER_BALANCE
1197
1199
  })]);
1198
- senderTransferTokenTransferable = _senderTransferTokenTransferable.value;
1199
- receiverNativeTransferable = _receiverNativeTransferable.value;
1200
+ senderSendingTokenTransferable = BigInt(_senderSendingTokenTransferable.value);
1201
+ receiverSystemAccountInfo = _receiverNativeTotal.metadata;
1200
1202
  }
1201
1203
  const {
1202
- value: receiverTransferTokenTransferable
1203
- } = await this.getAddressTransferableBalance({
1204
+ value: _receiverSendingTokenKeepAliveBalance
1205
+ } = await this.getAddressTotalBalance({
1204
1206
  address: to,
1205
1207
  networkKey,
1206
1208
  token: tokenSlug,
1207
1209
  extrinsicType
1208
1210
  }); // todo: shouldn't be just transferable, locked also counts
1209
-
1210
- const [warnings, errors] = additionalValidateTransfer(transferTokenInfo, nativeTokenInfo, extrinsicType, receiverTransferTokenTransferable, transferAmount.value, senderTransferTokenTransferable, receiverNativeTransferable);
1211
+ const receiverSendingTokenKeepAliveBalance = BigInt(_receiverSendingTokenKeepAliveBalance);
1212
+ const amount = BigInt(transferAmount.value);
1213
+ const substrateApi = this.#koniState.getSubstrateApi(networkKey);
1214
+ const isSendingTokenSufficient = await this.isSufficientToken(transferTokenInfo, substrateApi);
1215
+ const [warnings, errors] = additionalValidateTransferForRecipient(transferTokenInfo, nativeTokenInfo, extrinsicType, receiverSendingTokenKeepAliveBalance, amount, senderSendingTokenTransferable, receiverSystemAccountInfo, isSendingTokenSufficient);
1211
1216
  warnings.length && inputTransaction.warnings.push(...warnings);
1212
1217
  errors.length && inputTransaction.errors.push(...errors);
1213
1218
  };
@@ -1466,6 +1471,25 @@ export default class KoniExtension {
1466
1471
  };
1467
1472
  }
1468
1473
  }
1474
+ async isSufficientToken(tokenInfo, substrateApi) {
1475
+ let metadata;
1476
+ if (SUFFICIENT_CHAIN.includes(tokenInfo.originChain) && tokenInfo.assetType !== _AssetType.NATIVE) {
1477
+ const assetId = _isBridgedToken(tokenInfo) ? _getXcmAssetMultilocation(tokenInfo) : _getTokenOnChainAssetId(tokenInfo);
1478
+ const queryParams = {
1479
+ section: 'query',
1480
+ module: 'foreignAssets',
1481
+ method: 'asset',
1482
+ args: [assetId]
1483
+ };
1484
+ if (!_isBridgedToken(tokenInfo)) {
1485
+ queryParams.module = 'assets';
1486
+ }
1487
+ metadata = await substrateApi.makeRpcQuery(queryParams);
1488
+ } else {
1489
+ return false;
1490
+ }
1491
+ return metadata.isSufficient;
1492
+ }
1469
1493
  async deleteCustomAsset(assetSlug) {
1470
1494
  const assetInfo = this.#koniState.getAssetBySlug(assetSlug);
1471
1495
  if (assetInfo && _isCustomAsset(assetSlug)) {
@@ -1495,6 +1519,14 @@ export default class KoniExtension {
1495
1519
  }
1496
1520
  return await this.#koniState.balanceService.getTransferableBalance(address, networkKey, token, extrinsicType);
1497
1521
  }
1522
+ async getAddressTotalBalance({
1523
+ address,
1524
+ extrinsicType,
1525
+ networkKey,
1526
+ token
1527
+ }) {
1528
+ return await this.#koniState.balanceService.getTotalBalance(address, networkKey, token, extrinsicType);
1529
+ }
1498
1530
  async getMaxTransferable({
1499
1531
  address,
1500
1532
  destChain,
@@ -2615,11 +2647,11 @@ export default class KoniExtension {
2615
2647
  });
2616
2648
  Object.entries(availableNamespaces).forEach(([key, namespace]) => {
2617
2649
  if (namespace.chains) {
2618
- const accounts = [];
2619
- const chains = uniqueStringArray(namespace.chains);
2620
- chains.forEach(chain => {
2621
- accounts.push(...selectedAccounts.filter(address => isEthereumAddress(address) === (key === WALLET_CONNECT_EIP155_NAMESPACE)).map(address => `${chain}:${address}`));
2650
+ const accounts = selectedAccounts.filter(address => {
2651
+ const [_namespace] = address.split(':');
2652
+ return _namespace === key;
2622
2653
  });
2654
+ const chains = uniqueStringArray(namespace.chains);
2623
2655
  namespaces[key] = {
2624
2656
  accounts,
2625
2657
  methods: namespace.methods,
package/package.json CHANGED
@@ -17,7 +17,7 @@
17
17
  "./cjs/detectPackage.js"
18
18
  ],
19
19
  "type": "module",
20
- "version": "1.3.8-0",
20
+ "version": "1.3.9-0",
21
21
  "main": "./cjs/index.js",
22
22
  "module": "./index.js",
23
23
  "types": "./index.d.ts",
@@ -2492,10 +2492,10 @@
2492
2492
  "@sora-substrate/type-definitions": "^1.17.7",
2493
2493
  "@substrate/connect": "^0.8.9",
2494
2494
  "@subwallet/chain-list": "0.2.95-beta.1",
2495
- "@subwallet/extension-base": "^1.3.8-0",
2496
- "@subwallet/extension-chains": "^1.3.8-0",
2497
- "@subwallet/extension-dapp": "^1.3.8-0",
2498
- "@subwallet/extension-inject": "^1.3.8-0",
2495
+ "@subwallet/extension-base": "^1.3.9-0",
2496
+ "@subwallet/extension-chains": "^1.3.9-0",
2497
+ "@subwallet/extension-dapp": "^1.3.9-0",
2498
+ "@subwallet/extension-inject": "^1.3.9-0",
2499
2499
  "@subwallet/keyring": "^0.1.8-beta.0",
2500
2500
  "@subwallet/ui-keyring": "^0.1.8-beta.0",
2501
2501
  "@ton/core": "^0.56.3",
package/packageInfo.js CHANGED
@@ -7,5 +7,5 @@ export const packageInfo = {
7
7
  name: '@subwallet/extension-base',
8
8
  path: (import.meta && import.meta.url) ? new URL(import.meta.url).pathname.substring(0, new URL(import.meta.url).pathname.lastIndexOf('/') + 1) : 'auto',
9
9
  type: 'esm',
10
- version: '1.3.8-0'
10
+ version: '1.3.9-0'
11
11
  };
@@ -47,7 +47,9 @@ export declare class BalanceService implements StoppableServiceInterface {
47
47
  getBalanceDetectCache(update: (value: DetectBalanceCache) => void): void;
48
48
  setBalanceDetectCache(addresses: string[]): void;
49
49
  /** Subscribe token free balance of an address on chain */
50
+ subscribeBalance(address: string, chain: string, tokenSlug: string | undefined, balanceType?: 'transferable' | 'total' | 'keepAlive', extrinsicType?: ExtrinsicType, callback?: (rs: AmountData) => void): Promise<[() => void, AmountData]>;
50
51
  subscribeTransferableBalance(address: string, chain: string, tokenSlug: string | undefined, extrinsicType?: ExtrinsicType, callback?: (rs: AmountData) => void): Promise<[() => void, AmountData]>;
52
+ subscribeTotalBalance(address: string, chain: string, tokenSlug: string | undefined, extrinsicType?: ExtrinsicType, callback?: (rs: AmountData) => void): Promise<[() => void, AmountData]>;
51
53
  /**
52
54
  * @public
53
55
  * @async
@@ -60,6 +62,7 @@ export declare class BalanceService implements StoppableServiceInterface {
60
62
  * @return {Promise<AmountData>} - Free token balance of address on chain
61
63
  */
62
64
  getTransferableBalance(address: string, chain: string, tokenSlug?: string, extrinsicType?: ExtrinsicType): Promise<AmountData>;
65
+ getTotalBalance(address: string, chain: string, tokenSlug?: string, extrinsicType?: ExtrinsicType): Promise<AmountData>;
63
66
  /** Remove balance from the subject object by addresses */
64
67
  removeBalanceByAddresses(addresses: string[]): void;
65
68
  /** Remove inactive asset from the balance map */
@@ -13,6 +13,7 @@ import { addLazy, createPromiseHandler, isAccountAll, waitTimeout } from '@subwa
13
13
  import { getKeypairTypeByAddress } from '@subwallet/keyring';
14
14
  import { EthereumKeypairTypes, SubstrateKeypairTypes } from '@subwallet/keyring/types';
15
15
  import keyring from '@subwallet/ui-keyring';
16
+ import BigN from 'bignumber.js';
16
17
  import { t } from 'i18next';
17
18
  import { BehaviorSubject } from 'rxjs';
18
19
  import { noop } from '@polkadot/util';
@@ -164,7 +165,7 @@ export class BalanceService {
164
165
  }
165
166
 
166
167
  /** Subscribe token free balance of an address on chain */
167
- async subscribeTransferableBalance(address, chain, tokenSlug, extrinsicType, callback) {
168
+ async subscribeBalance(address, chain, tokenSlug, balanceType = 'transferable', extrinsicType, callback) {
168
169
  const chainInfo = this.state.chainService.getChainInfoByKey(chain);
169
170
  const chainState = this.state.chainService.getChainStateByKey(chain);
170
171
  if (!chainInfo || !chainState || !chainState.active) {
@@ -193,10 +194,18 @@ export class BalanceService {
193
194
  let unsub = noop;
194
195
  unsub = subscribeBalance([address], [chain], [tSlug], assetMap, chainInfoMap, substrateApiMap, evmApiMap, tonApiMap, result => {
195
196
  const rs = result[0];
197
+ let value;
198
+ switch (balanceType) {
199
+ case 'total':
200
+ value = new BigN(rs.free).plus(new BigN(rs.locked)).toFixed();
201
+ break;
202
+ default:
203
+ value = rs.free;
204
+ }
196
205
  if (rs.tokenSlug === tSlug && rs.state !== APIItemState.PENDING) {
197
206
  hasError = false;
198
207
  const balance = {
199
- value: rs.free,
208
+ value,
200
209
  decimals: tokenInfo.decimals || 0,
201
210
  symbol: tokenInfo.symbol,
202
211
  metadata: rs.metadata
@@ -220,6 +229,12 @@ export class BalanceService {
220
229
  }, 9999);
221
230
  });
222
231
  }
232
+ async subscribeTransferableBalance(address, chain, tokenSlug, extrinsicType, callback) {
233
+ return this.subscribeBalance(address, chain, tokenSlug, 'transferable', extrinsicType, callback);
234
+ }
235
+ async subscribeTotalBalance(address, chain, tokenSlug, extrinsicType, callback) {
236
+ return this.subscribeBalance(address, chain, tokenSlug, 'total', extrinsicType, callback);
237
+ }
223
238
 
224
239
  /**
225
240
  * @public
@@ -236,6 +251,10 @@ export class BalanceService {
236
251
  const [, balance] = await this.subscribeTransferableBalance(address, chain, tokenSlug, extrinsicType);
237
252
  return balance;
238
253
  }
254
+ async getTotalBalance(address, chain, tokenSlug, extrinsicType) {
255
+ const [, balance] = await this.subscribeTotalBalance(address, chain, tokenSlug, extrinsicType);
256
+ return balance;
257
+ }
239
258
 
240
259
  /** Remove balance from the subject object by addresses */
241
260
  removeBalanceByAddresses(addresses) {
@@ -8,6 +8,7 @@ import { getWasmContractGasLimit } from '@subwallet/extension-base/koni/api/cont
8
8
  import { EVM_REFORMAT_DECIMALS } from '@subwallet/extension-base/services/chain-service/constants';
9
9
  import { calculateGasFeeParams } from '@subwallet/extension-base/services/fee-service/utils';
10
10
  import BigN from 'bignumber.js';
11
+ import { t } from 'i18next';
11
12
  export async function getEVMTransactionObject(chainInfo, from, to, value, transferAll, web3Api) {
12
13
  var _priority$maxFeePerGa, _priority$maxPriority;
13
14
  const networkKey = chainInfo.slug;
@@ -78,11 +79,21 @@ export async function getERC721Transaction(web3Api, chain, contractAddress, send
78
79
  var _priority$maxFeePerGa3, _priority$maxPriority3;
79
80
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
80
81
  const contract = new web3Api.api.eth.Contract(_ERC721_ABI, contractAddress);
81
- const [gasLimit, priority] = await Promise.all([
82
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
83
- contract.methods.safeTransferFrom(senderAddress, recipientAddress, tokenId).estimateGas({
84
- from: senderAddress
85
- }), calculateGasFeeParams(web3Api, chain)]);
82
+ let gasLimit;
83
+ let priority;
84
+ try {
85
+ [gasLimit, priority] = await Promise.all([
86
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
87
+ contract.methods.safeTransferFrom(senderAddress, recipientAddress, tokenId).estimateGas({
88
+ from: senderAddress
89
+ }), calculateGasFeeParams(web3Api, chain)]);
90
+ } catch (e) {
91
+ const error = e;
92
+ if (error.message.includes('transfer to non ERC721Receiver implementer')) {
93
+ error.message = t('Unable to send. NFT not supported on recipient address');
94
+ }
95
+ throw error;
96
+ }
86
97
  return {
87
98
  from: senderAddress,
88
99
  gasPrice: priority.gasPrice,
@@ -82,6 +82,7 @@ export declare const _XCM_CHAIN_GROUP: {
82
82
  polkadotXcmSpecialCases: string[];
83
83
  xcmPallet: string[];
84
84
  };
85
+ export declare const SUFFICIENT_CHAIN: string[];
85
86
  export declare const _XCM_TYPE: {
86
87
  RP: string;
87
88
  PP: string;
@@ -270,6 +270,7 @@ export const _XCM_CHAIN_GROUP = {
270
270
  // default is xTokens pallet
271
271
  };
272
272
 
273
+ export const SUFFICIENT_CHAIN = ['astar', 'calamari', 'parallel', 'darwinia2', 'crabParachain', 'pangolin', 'statemint', 'moonriver', 'shiden', 'moonbeam', 'statemine', 'liberland', 'dentnet', 'phala', 'crust', 'dbcchain', 'rococo_assethub'];
273
274
  export const _XCM_TYPE = {
274
275
  RP: `${_SubstrateChainType.RELAYCHAIN}-${_SubstrateChainType.PARACHAIN}`,
275
276
  // DMP
@@ -34,6 +34,7 @@ export declare enum TransferTxErrorType {
34
34
  NOT_ENOUGH_FEE = "NOT_ENOUGH_FEE",
35
35
  INVALID_TOKEN = "INVALID_TOKEN",
36
36
  TRANSFER_ERROR = "TRANSFER_ERROR",
37
- RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT = "RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT"
37
+ RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT = "RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT",
38
+ RECEIVER_ACCOUNT_INACTIVE = "RECEIVER_ACCOUNT_INACTIVE"
38
39
  }
39
40
  export declare type TransactionErrorType = BasicTxErrorType | TransferTxErrorType | StakingTxErrorType | YieldValidationStatus | SwapErrorType;
@@ -41,4 +41,5 @@ export let TransferTxErrorType;
41
41
  TransferTxErrorType["INVALID_TOKEN"] = "INVALID_TOKEN";
42
42
  TransferTxErrorType["TRANSFER_ERROR"] = "TRANSFER_ERROR";
43
43
  TransferTxErrorType["RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT"] = "RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT";
44
+ TransferTxErrorType["RECEIVER_ACCOUNT_INACTIVE"] = "RECEIVER_ACCOUNT_INACTIVE";
44
45
  })(TransferTxErrorType || (TransferTxErrorType = {}));