Skip to content

Commit

Permalink
web-front: upgrade massa-web3 to latest
Browse files Browse the repository at this point in the history
  • Loading branch information
peterjah committed Nov 19, 2024
1 parent e5b65b6 commit 7377bf7
Show file tree
Hide file tree
Showing 14 changed files with 450 additions and 692 deletions.
481 changes: 198 additions & 283 deletions web-frontend/package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions web-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@
"prepare": "cd .. && husky install web-frontend/.husky"
},
"dependencies": {
"@massalabs/massa-web3": "^4.0.2-dev",
"@massalabs/react-ui-kit": "^0.0.5-dev",
"@massalabs/wallet-provider": "^2.0.1-dev",
"@massalabs/massa-web3": "^5.0.1-dev",
"@massalabs/react-ui-kit": "^1.0.1-dev",
"@massalabs/wallet-provider": "^3.0.1-dev",
"@tanstack/react-query": "^4.29.5",
"axios": "^1.6.0",
"currency.js": "^2.0.4",
Expand Down
283 changes: 56 additions & 227 deletions web-frontend/src/custom/smart-contract/useFTTransfer.tsx
Original file line number Diff line number Diff line change
@@ -1,67 +1,83 @@
import { useCallback, useState } from 'react';
import { useCallback } from 'react';

import {
Client,
EOperationStatus,
ICallData,
MAX_GAS_CALL,
Args,
strToBytes,
STORAGE_BYTE_COST,
fromMAS,
} from '@massalabs/massa-web3';
import { ToastContent, parseAmount, toast } from '@massalabs/react-ui-kit';
import { OperationToast } from '@massalabs/react-ui-kit/src/lib/ConnectMassaWallets/components/OperationToast';
import { logSmartContractEvents } from '@massalabs/react-ui-kit/src/lib/massa-react/utils';
import { Args, bytes, Mas, Provider, strToBytes } from '@massalabs/massa-web3';
import { parseAmount, useWriteSmartContract } from '@massalabs/react-ui-kit';

import { usePrepareScCall } from '../usePrepareScCall';
import { useProvider } from '../useProvider';
import Intl from '@/i18n/i18n';
import { useMassaWeb3Store } from '@/store/store';

export function useFTTransfer() {
const { isMainnet } = useMassaWeb3Store();
const { client } = usePrepareScCall();
const BALANCE_KEY_PREFIX = 'BALANCE';

function balanceKey(address: string): Uint8Array {
return strToBytes(BALANCE_KEY_PREFIX + address);
}

async function estimateCoinsCost(
provider: Provider,
tokenAddress: string,
recipient: string,
): Promise<bigint> {
const allKeys = await provider.getStorageKeys(
tokenAddress,
BALANCE_KEY_PREFIX,
);
const key = balanceKey(recipient);
const foundKey = allKeys.some((k) => {
return JSON.stringify(k) === JSON.stringify(key);
});

if (foundKey) {
return 0n;
}

const storage =
4 + // space of a key/value in the datastore
key.length + // key length
32; // length of the value of the balance

return bytes(storage);
}

export function useFTTransfer() {
const { provider, isMainnet } = useProvider();
const {
opId,
isPending,
isOpPending,
isSuccess,
isError,
callSmartContract,
} = useWriteSmartContract(client, isMainnet);
} = useWriteSmartContract(provider!, isMainnet);

Check warning on line 50 in web-frontend/src/custom/smart-contract/useFTTransfer.tsx

View workflow job for this annotation

GitHub Actions / lint-web-frontend

Forbidden non-null assertion

const transfer = useCallback(
(
async (
recipient: string,
tokenAddress: string,
amount: string,
decimals: number,
fees: string,
) => {
if (!client) {
throw new Error('Massa client not found');
if (!callSmartContract || !provider) {
return;
}

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,
fees,
);
});
const coins = await estimateCoinsCost(provider, tokenAddress, recipient);

await 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,
Mas.fromString(fees),
);
},
[client, callSmartContract],
[provider, callSmartContract],
);

return {
Expand All @@ -74,190 +90,3 @@ export function useFTTransfer() {
isMainnet,
};
}

interface ToasterMessage {
pending: string;
success: string;
error: string;
timeout?: string;
}

function minBigInt(a: bigint, b: bigint) {
return a < b ? a : b;
}

function useWriteSmartContract(client?: Client, isMainnet?: boolean) {
const [isPending, setIsPending] = useState(false);
const [isOpPending, setIsOpPending] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
const [isError, setIsError] = useState(false);
const [opId, setOpId] = useState<string | undefined>(undefined);

function callSmartContract(
targetFunction: string,
targetAddress: string,
parameter: number[],
messages: ToasterMessage,
coins = BigInt(0),
fees: string,
) {
if (!client) {
throw new Error('Massa client not found');
}
if (isOpPending) {
throw new Error('Operation is already pending');
}
setIsSuccess(false);
setIsError(false);
setIsOpPending(false);
setIsPending(true);
let operationId: string | undefined;
let toastId: string | undefined;

const callData = {
targetAddress,
targetFunction,
parameter,
coins,
fee: fromMAS(fees),
} as ICallData;

client
.smartContracts()
.readSmartContract({
...callData,
callerAddress: client.wallet().getBaseAccount()?.address(),
})
.then((response) => {
const gasCost = BigInt(response.info.gas_cost);
return minBigInt(gasCost + (gasCost * 20n) / 100n, MAX_GAS_CALL);
})
.then((maxGas: bigint) => {
callData.maxGas = maxGas;
return client.smartContracts().callSmartContract(callData);
})
.then((opId) => {
operationId = opId;
setOpId(operationId);
setIsOpPending(true);
toastId = toast.loading(
(t) => (
<ToastContent t={t}>
<OperationToast
isMainnet={isMainnet}
title={messages.pending}
operationId={operationId}
/>
</ToastContent>
),
{
duration: Infinity,
},
);
return client
.smartContracts()
.awaitMultipleRequiredOperationStatus(operationId, [
EOperationStatus.SPECULATIVE_ERROR,
EOperationStatus.SPECULATIVE_SUCCESS,
]);
})
.then((status: EOperationStatus) => {
if (status !== EOperationStatus.SPECULATIVE_SUCCESS) {
throw new Error('Operation failed', { cause: { status } });
}
setIsSuccess(true);
setIsOpPending(false);
setIsPending(false);
toast.dismiss(toastId);
toast.success((t) => (
<ToastContent t={t}>
<OperationToast
isMainnet={isMainnet}
title={messages.success}
operationId={operationId}
/>
</ToastContent>
));
})
.catch((error) => {
console.error(error);
toast.dismiss(toastId);
setIsError(true);
setIsOpPending(false);
setIsPending(false);

if (!operationId) {
console.error('Operation ID not found');
toast.error((t) => (
<ToastContent t={t}>
<OperationToast isMainnet={isMainnet} title={messages.error} />
</ToastContent>
));
return;
}

if (error.cause?.status === EOperationStatus.SPECULATIVE_ERROR) {
toast.error((t) => (
<ToastContent t={t}>
<OperationToast
isMainnet={isMainnet}
title={messages.error}
operationId={operationId}
/>
</ToastContent>
));
logSmartContractEvents(client, operationId);
} else {
toast.error((t) => (
<ToastContent t={t}>
<OperationToast
isMainnet={isMainnet}
title={
messages.timeout || Intl.t('send-coins.steps.failed-timeout')
}
operationId={operationId}
/>
</ToastContent>
));
}
});
}

return {
opId,
isOpPending,
isPending,
isSuccess,
isError,
callSmartContract,
};
}
async function estimateCoinsCost(
client: Client,
tokenAddress: string,
recipient: string,
): Promise<bigint> {
const addrInfo = await client.publicApi().getAddresses([tokenAddress]);
const allKeys = addrInfo[0].candidate_datastore_keys;
const key = balanceKey(recipient);
const foundKey = allKeys.find((k) => {
return JSON.stringify(k) === JSON.stringify(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));
}
Loading

0 comments on commit 7377bf7

Please sign in to comment.