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

Add pepe #975

Merged
merged 2 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions pkg/assets/default_assets.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,5 +190,13 @@ const AssetsJSON = `[
"decimals": 18,
"MEXCSymbol": "USD",
"ChainID": 77658377
},
{
"address": "AS1nqHKXpnFXqhDExTskXmBbbVpVpUbCQVtNSXLCqUDSUXihdWRq",
"name": "PepeOnMassa",
"symbol": "POM",
"decimals": 18,
"MEXCSymbol": "",
"ChainID": 77658377
}
]`
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 @@
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
Loading