From eeae81de24f6f73e5c67a7d4800a7432e04e3831 Mon Sep 17 00:00:00 2001 From: Nathan Seva Date: Tue, 7 May 2024 12:06:34 -0500 Subject: [PATCH 1/3] transfer FT: user pay for storage cost --- .../custom/smart-contract/useFTTransfer.tsx | 53 +++++++++++++++---- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/web-frontend/src/custom/smart-contract/useFTTransfer.tsx b/web-frontend/src/custom/smart-contract/useFTTransfer.tsx index 3a78788ba..7466bb0f8 100644 --- a/web-frontend/src/custom/smart-contract/useFTTransfer.tsx +++ b/web-frontend/src/custom/smart-contract/useFTTransfer.tsx @@ -8,6 +8,8 @@ import { Args, ClientFactory, MAINNET_CHAIN_ID, + strToBytes, + STORAGE_BYTE_COST, } from '@massalabs/massa-web3'; import { ToastContent, parseAmount, toast } from '@massalabs/react-ui-kit'; import { providers } from '@massalabs/wallet-provider'; @@ -70,17 +72,19 @@ export function useFTTransfer(nickname: string) { const rawAmount = parseAmount(amount, decimals); const args = new Args().addString(recipient).addU256(rawAmount); - callSmartContract( - 'transfer', - tokenAddress, - args.serialize(), - { - pending: Intl.t('send-coins.steps.ft-transfer-pending'), - success: Intl.t('send-coins.steps.ft-transfer-success'), - error: Intl.t('send-coins.steps.ft-transfer-failed'), - }, - BigInt(0), - ); + estimateCoinsCost(client, tokenAddress, recipient).then((coins) => { + callSmartContract( + 'transfer', + tokenAddress, + args.serialize(), + { + pending: Intl.t('send-coins.steps.ft-transfer-pending'), + success: Intl.t('send-coins.steps.ft-transfer-success'), + error: Intl.t('send-coins.steps.ft-transfer-failed'), + }, + coins, + ); + }); }; return { @@ -249,3 +253,30 @@ function useWriteSmartContract(client?: Client, isMainnet?: boolean) { callSmartContract, }; } +async function estimateCoinsCost( + client: Client, + tokenAddress: string, + recipient: string, +): Promise { + const addrInfo = await client.publicApi().getAddresses([tokenAddress]); + const allKeys = addrInfo[0].candidate_datastore_keys; + const key = balanceKey(recipient); + const foundKey = allKeys.find((k) => k === key); + + if (foundKey) { + return 0n; + } + + const storage = + 4n + // space of a key/value in the datastore + BigInt(key.length) + // key length + 32n; // length of the value of the balance + + return STORAGE_BYTE_COST * storage; +} + +function balanceKey(address: string): number[] { + const BALANCE_KEY_PREFIX = 'BALANCE'; + + return Array.from(strToBytes(BALANCE_KEY_PREFIX + address)); +} From 77e20503feaa66bdd859ce69f9a5969e9713ff61 Mon Sep 17 00:00:00 2001 From: Nathan Seva Date: Tue, 7 May 2024 12:54:58 -0500 Subject: [PATCH 2/3] fix key comparison --- web-frontend/src/custom/smart-contract/useFTTransfer.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web-frontend/src/custom/smart-contract/useFTTransfer.tsx b/web-frontend/src/custom/smart-contract/useFTTransfer.tsx index 7466bb0f8..b6d109b6e 100644 --- a/web-frontend/src/custom/smart-contract/useFTTransfer.tsx +++ b/web-frontend/src/custom/smart-contract/useFTTransfer.tsx @@ -261,7 +261,9 @@ async function estimateCoinsCost( const addrInfo = await client.publicApi().getAddresses([tokenAddress]); const allKeys = addrInfo[0].candidate_datastore_keys; const key = balanceKey(recipient); - const foundKey = allKeys.find((k) => k === key); + const foundKey = allKeys.find((k) => { + return JSON.stringify(k) === JSON.stringify(key); + }); if (foundKey) { return 0n; From 4127f0e5874cb5713586187149160c3456215ca5 Mon Sep 17 00:00:00 2001 From: Nathan Seva Date: Tue, 7 May 2024 13:01:43 -0500 Subject: [PATCH 3/3] prevent client not found --- .../custom/smart-contract/useFTTransfer.tsx | 55 ++++++++++--------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/web-frontend/src/custom/smart-contract/useFTTransfer.tsx b/web-frontend/src/custom/smart-contract/useFTTransfer.tsx index b6d109b6e..421a28028 100644 --- a/web-frontend/src/custom/smart-contract/useFTTransfer.tsx +++ b/web-frontend/src/custom/smart-contract/useFTTransfer.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { Client, @@ -59,33 +59,36 @@ export function useFTTransfer(nickname: string) { callSmartContract, } = useWriteSmartContract(client, isMainnet); - const transfer = ( - recipient: string, - tokenAddress: string, - amount: string, - decimals: number, - ) => { - if (!client) { - throw new Error('Massa client not found'); - } + const transfer = useCallback( + ( + recipient: string, + tokenAddress: string, + amount: string, + decimals: number, + ) => { + if (!client) { + throw new Error('Massa client not found'); + } - const rawAmount = parseAmount(amount, decimals); - const args = new Args().addString(recipient).addU256(rawAmount); + const rawAmount = parseAmount(amount, decimals); + const args = new Args().addString(recipient).addU256(rawAmount); - estimateCoinsCost(client, tokenAddress, recipient).then((coins) => { - callSmartContract( - 'transfer', - tokenAddress, - args.serialize(), - { - pending: Intl.t('send-coins.steps.ft-transfer-pending'), - success: Intl.t('send-coins.steps.ft-transfer-success'), - error: Intl.t('send-coins.steps.ft-transfer-failed'), - }, - coins, - ); - }); - }; + estimateCoinsCost(client, tokenAddress, recipient).then((coins) => { + callSmartContract( + 'transfer', + tokenAddress, + args.serialize(), + { + pending: Intl.t('send-coins.steps.ft-transfer-pending'), + success: Intl.t('send-coins.steps.ft-transfer-success'), + error: Intl.t('send-coins.steps.ft-transfer-failed'), + }, + coins, + ); + }); + }, + [client, callSmartContract], + ); return { opId,