diff --git a/packages/@core-js/src/index.ts b/packages/@core-js/src/index.ts index 75a296256..fe716c8b4 100644 --- a/packages/@core-js/src/index.ts +++ b/packages/@core-js/src/index.ts @@ -2,6 +2,7 @@ export { TonAPI, useTonAPI, TonAPIProvider } from './TonAPI'; export * from './formatters/Address'; export * from './formatters/DNS'; +export * from './utils/constants'; export * from './utils/AmountFormatter/FiatCurrencyConfig'; export * from './utils/AmountFormatter'; export * from './utils/network'; diff --git a/packages/@core-js/src/utils/constants.ts b/packages/@core-js/src/utils/constants.ts new file mode 100644 index 000000000..461ac0482 --- /dev/null +++ b/packages/@core-js/src/utils/constants.ts @@ -0,0 +1,4 @@ +import { toNano } from '@ton/core'; + +export const ONE_TON = toNano('1'); +export const BASE_FORWARD_AMOUNT = toNano('0.05'); diff --git a/packages/mobile/src/core/ModalContainer/InsufficientFunds/InsufficientFunds.tsx b/packages/mobile/src/core/ModalContainer/InsufficientFunds/InsufficientFunds.tsx index 3ab65d2d4..24db50659 100644 --- a/packages/mobile/src/core/ModalContainer/InsufficientFunds/InsufficientFunds.tsx +++ b/packages/mobile/src/core/ModalContainer/InsufficientFunds/InsufficientFunds.tsx @@ -150,7 +150,7 @@ export async function checkIsInsufficient(amount: string | number) { return { insufficient: false, balance: null }; } -export const openInsufficientFundsModal = async (params: InsufficientFundsParams) => { +export const openInsufficientFundsModal = (params: InsufficientFundsParams) => { push('SheetsProvider', { $$action: SheetActions.ADD, component: InsufficientFundsModal, diff --git a/packages/mobile/src/core/NFTSend/NFTSend.tsx b/packages/mobile/src/core/NFTSend/NFTSend.tsx index dbb7aad49..e942379ba 100644 --- a/packages/mobile/src/core/NFTSend/NFTSend.tsx +++ b/packages/mobile/src/core/NFTSend/NFTSend.tsx @@ -12,15 +12,17 @@ import { NFTSendSteps } from '$core/NFTSend/types'; import { ConfirmStep } from '$core/NFTSend/steps/ConfirmStep/ConfirmStep'; import { useNFT } from '$hooks/useNFT'; import { + BASE_FORWARD_AMOUNT, ContractService, contractVersionsMap, + ONE_TON, TransactionService, } from '@tonkeeper/core'; import { tk, tonapi } from '@tonkeeper/shared/tonkeeper'; -import { getWalletSeqno } from '@tonkeeper/shared/utils/wallet'; +import { getWalletSeqno, setBalanceForEmulation } from '@tonkeeper/shared/utils/wallet'; import { Buffer } from 'buffer'; import { MessageConsequences } from '@tonkeeper/core/src/TonAPI'; -import { internal, MessageRelaxed, toNano } from '@ton/core'; +import { internal, toNano } from '@ton/core'; import BigNumber from 'bignumber.js'; import { Ton } from '$libs/Ton'; import { delay } from '$utils'; @@ -28,6 +30,11 @@ import { Toast } from '$store'; import axios from 'axios'; import { useWallet } from '$hooks/useWallet'; import { useUnlockVault } from '$core/ModalContainer/NFTOperations/useUnlockVault'; +import { + checkIsInsufficient, + openInsufficientFundsModal, +} from '$core/ModalContainer/InsufficientFunds/InsufficientFunds'; +import { CanceledActionError } from '$core/Send/steps/ConfirmStep/ActionErrors'; import { Keyboard } from 'react-native'; interface Props { @@ -69,7 +76,6 @@ export const NFTSend: FC = (props) => { const [recipient, setRecipient] = useState(null); const [recipientAccountInfo, setRecipientAccountInfo] = useState(null); - const messages = useRef([]); const handleBack = useCallback(() => stepViewRef.current?.goBack(), []); @@ -95,7 +101,7 @@ export const NFTSend: FC = (props) => { const nftTransferMessages = [ internal({ to: nftAddress, - value: toNano('1'), + value: ONE_TON, body: ContractService.createNftTransferBody({ queryId: Date.now(), newOwnerAddress: recipient!.address, @@ -116,8 +122,12 @@ export const NFTSend: FC = (props) => { seqno: await getWalletSeqno(), secretKey: Buffer.alloc(64), }); - const response = await tonapi.wallet.emulateMessageToWallet({ boc }); - messages.current = nftTransferMessages; + + const response = await tonapi.wallet.emulateMessageToWallet({ + boc, + params: [setBalanceForEmulation(toNano('2'))], // Emulate with higher balance to calculate fair amount to send + }); + setConsequences(response); Keyboard.dismiss(); @@ -137,6 +147,12 @@ export const NFTSend: FC = (props) => { } }, [comment, nftAddress, recipient, wallet.ton]); + const total = useMemo(() => { + const extra = new BigNumber(Ton.fromNano(consequences?.event.extra ?? 0)); + + return { amount: extra.abs().toString(), isRefund: !extra.isNegative() }; + }, [consequences?.event.extra]); + const sendTx = useCallback(async () => { try { setSending(true); @@ -144,12 +160,42 @@ export const NFTSend: FC = (props) => { const vault = await unlockVault(); const privateKey = await vault.getTonPrivateKey(); + const totalAmount = total.isRefund + ? BASE_FORWARD_AMOUNT + : BigInt(Math.abs(consequences?.event.extra!)) + BASE_FORWARD_AMOUNT; + + const checkResult = await checkIsInsufficient(totalAmount.toString()); + if (checkResult.insufficient) { + openInsufficientFundsModal({ + totalAmount: totalAmount.toString(), + balance: checkResult.balance, + }); + + await delay(200); + throw new CanceledActionError(); + } + + const nftTransferMessages = [ + internal({ + to: nftAddress, + value: totalAmount, + body: ContractService.createNftTransferBody({ + queryId: Date.now(), + newOwnerAddress: recipient!.address, + excessesAddress: tk.wallet.address.ton.raw, + forwardBody: comment, + }), + bounce: true, + }), + ]; + const contract = ContractService.getWalletContract( - contractVersionsMap[vault.getVersion() ?? 'v4R2'], - Buffer.from(vault.tonPublicKey), + contractVersionsMap[wallet.ton.version ?? 'v4R2'], + Buffer.from(await wallet.ton.getTonPublicKey()), ); + const boc = TransactionService.createTransfer(contract, { - messages: messages.current, + messages: nftTransferMessages, seqno: await getWalletSeqno(), sendMode: 3, secretKey: Buffer.from(privateKey), @@ -166,13 +212,15 @@ export const NFTSend: FC = (props) => { } finally { setSending(false); } - }, [unlockVault]); - - const total = useMemo(() => { - const fee = new BigNumber(Ton.fromNano(consequences?.event.extra ?? 0)); - - return { amount: fee.abs().toString(), isRefund: !fee.isNegative() }; - }, [consequences?.event.extra]); + }, [ + comment, + consequences?.event.extra, + nftAddress, + recipient, + total.isRefund, + unlockVault, + wallet.ton, + ]); return ( <> diff --git a/packages/shared/utils/wallet.ts b/packages/shared/utils/wallet.ts index 11494ad8a..e48c5de4b 100644 --- a/packages/shared/utils/wallet.ts +++ b/packages/shared/utils/wallet.ts @@ -8,3 +8,7 @@ export async function getWalletSeqno() { return 0; } } + +export function setBalanceForEmulation(balance: bigint) { + return { balance: Number(balance), address: tk.wallet.address.ton.raw }; +}