diff --git a/CHANGELOG.md b/CHANGELOG.md index b4bc506..ef83cfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### [0.11.0](https://github.com/useElven/core/releases/tag/v0.11.0) (2023-11-04) +- add useMultiEsdtTransfer +- some changes in the useTransaction hook. Now you can also pass the whole Transaction object to triggerTx function (in such a case other param are not required). This is optional. Previous functionality stays the same. In some cases, it can be a breaking change. + ### [0.10.3](https://github.com/useElven/core/releases/tag/v0.10.3) (2023-11-01) - update MulitversX sdk-core lib diff --git a/package-lock.json b/package-lock.json index 60efe5b..d3ba1c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@useelven/core", - "version": "0.10.3", + "version": "0.11.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@useelven/core", - "version": "0.10.3", + "version": "0.11.0", "license": "MIT", "dependencies": { "@multiversx/sdk-core": "12.12.0", @@ -18,15 +18,15 @@ "@multiversx/sdk-web-wallet-provider": "3.1.0", "lodash.clonedeep": "4.5.0", "swr": "2.2.4", - "valtio": "1.11.3" + "valtio": "1.12.0" }, "devDependencies": { "@types/lodash.clonedeep": "4.5.8", "@types/node": "20.8.10", - "@types/react": "18.2.33", + "@types/react": "18.2.35", "@typescript-eslint/eslint-plugin": "6.9.1", "@typescript-eslint/parser": "6.9.1", - "eslint": "8.52.0", + "eslint": "8.53.0", "eslint-config-prettier": "9.0.0", "eslint-plugin-prettier": "5.0.1", "eslint-plugin-react": "7.33.2", @@ -430,9 +430,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -453,9 +453,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz", - "integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", + "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1092,9 +1092,9 @@ "devOptional": true }, "node_modules/@types/react": { - "version": "18.2.33", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.33.tgz", - "integrity": "sha512-v+I7S+hu3PIBoVkKGpSYYpiBT1ijqEzWpzQD62/jm4K74hPpSP7FF9BnKG6+fg2+62weJYkkBWDJlZt5JO/9hg==", + "version": "18.2.35", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.35.tgz", + "integrity": "sha512-LG3xpFZ++rTndV+/XFyX5vUP7NI9yxyk+MQvBDq+CVs8I9DLSc3Ymwb1Vmw5YDoeNeHN4PDZa3HylMKJYT9PNQ==", "devOptional": true, "dependencies": { "@types/prop-types": "*", @@ -2188,6 +2188,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/derive-valtio": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/derive-valtio/-/derive-valtio-0.1.0.tgz", + "integrity": "sha512-OCg2UsLbXK7GmmpzMXhYkdO64vhJ1ROUUGaTFyHjVwEdMEcTTRj7W1TxLbSBxdY8QLBPCcp66MTyaSy0RpO17A==", + "peerDependencies": { + "valtio": "*" + } + }, "node_modules/detect-browser": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-5.3.0.tgz", @@ -2413,15 +2421,15 @@ } }, "node_modules/eslint": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz", - "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", + "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.52.0", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.53.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -5874,10 +5882,11 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/valtio": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/valtio/-/valtio-1.11.3.tgz", - "integrity": "sha512-HL50LlM6YrYfai5H9QKSU0HJYOkghyGvZeRMCJSI6q79DWSng0PdCzT3S3M2UAcvtqBSteThqmhh6jHYyxPUTg==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/valtio/-/valtio-1.12.0.tgz", + "integrity": "sha512-co8NkCHeY0NsL0XsL/cSICt5VhTjwZlYT8mi50dYY5thx3r3w1D15A04Lvs9WL/y/Rf98vUKY5PAAJCTLHvkJw==", "dependencies": { + "derive-valtio": "0.1.0", "proxy-compare": "2.5.1", "use-sync-external-store": "1.2.0" }, diff --git a/package.json b/package.json index 233b24a..142ec71 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@useelven/core", - "version": "0.10.3", + "version": "0.11.0", "description": "Core React hooks for MultiversX DApps", "license": "MIT", "author": "Julian Ćwirko ", @@ -73,15 +73,15 @@ "@multiversx/sdk-web-wallet-provider": "3.1.0", "lodash.clonedeep": "4.5.0", "swr": "2.2.4", - "valtio": "1.11.3" + "valtio": "1.12.0" }, "devDependencies": { "@types/lodash.clonedeep": "4.5.8", "@types/node": "20.8.10", - "@types/react": "18.2.33", + "@types/react": "18.2.35", "@typescript-eslint/eslint-plugin": "6.9.1", "@typescript-eslint/parser": "6.9.1", - "eslint": "8.52.0", + "eslint": "8.53.0", "eslint-config-prettier": "9.0.0", "eslint-plugin-prettier": "5.0.1", "eslint-plugin-react": "7.33.2", diff --git a/src/hooks/useMultiTokenTransfer.tsx b/src/hooks/useMultiTokenTransfer.tsx new file mode 100644 index 0000000..ab58fd1 --- /dev/null +++ b/src/hooks/useMultiTokenTransfer.tsx @@ -0,0 +1,127 @@ +import { + Address, + TokenTransfer, + TransferTransactionsFactory, + GasEstimator, +} from '@multiversx/sdk-core'; +import { useTransaction, TransactionArgs } from './useTransaction'; +import { useAccount } from './useAccount'; +import { apiCall } from 'src/utils/apiCall'; +import { useConfig } from './useConfig'; + +export enum MultiTransferTokenType { + FungibleESDT = 'FungibleESDT', + MetaESDT = 'MetaESDT', + NonFungibleESDT = 'NonFungibleESDT', + SemiFungibleESDT = 'SemiFungibleESDT', +} + +export interface MultiTransferToken { + type: MultiTransferTokenType; + tokenId: string; + amount: string; +} + +export interface MultiTokenTransferArgs { + tokens: MultiTransferToken[]; + receiver: string; +} + +export interface MultiTokenTransferHookProps { + webWalletRedirectUrl?: TransactionArgs['webWalletRedirectUrl']; + cb?: TransactionArgs['cb']; +} + +export const useMultiTokenTransfer = ( + { webWalletRedirectUrl, cb }: MultiTokenTransferHookProps = { + webWalletRedirectUrl: undefined, + cb: undefined, + } +) => { + const { address: accountAddress } = useAccount(); + const { shortId } = useConfig(); + const { nonce } = useAccount(); + const { triggerTx, pending, transaction, txResult, error } = useTransaction({ + webWalletRedirectUrl, + cb, + }); + + const transfer = async ({ tokens, receiver }: MultiTokenTransferArgs) => { + const transfers: TokenTransfer[] = []; + + for (const token of tokens) { + let result; + if (token.type === MultiTransferTokenType.FungibleESDT) { + try { + result = await apiCall.get(`/tokens/${token.tokenId.trim()}`); + transfers.push( + TokenTransfer.fungibleFromAmount( + token.tokenId, + token.amount, + result.decimals + ) + ); + } catch (e) { + console.log((e as Error)?.message); + } + } + + if ( + [ + MultiTransferTokenType.NonFungibleESDT, + MultiTransferTokenType.MetaESDT, + MultiTransferTokenType.SemiFungibleESDT, + ].includes(token.type) + ) { + try { + result = await apiCall.get(`/nfts/${token.tokenId.trim()}`); + } catch (e) { + console.log((e as Error)?.message); + } + } + + if (token.type === MultiTransferTokenType.NonFungibleESDT) { + transfers.push(TokenTransfer.nonFungible(result.ticker, result.nonce)); + } + if (token.type === MultiTransferTokenType.SemiFungibleESDT) { + transfers.push( + TokenTransfer.semiFungible( + result.ticker, + result.nonce, + parseInt(token.amount, 10) + ) + ); + } + if (token.type === MultiTransferTokenType.MetaESDT) { + transfers.push( + TokenTransfer.metaEsdtFromAmount( + result.ticker, + result.nonce, + parseFloat(token.amount), + result.decimals + ) + ); + } + } + + const factory = new TransferTransactionsFactory(new GasEstimator()); + + const tx = factory.createMultiESDTNFTTransfer({ + tokenTransfers: transfers, + nonce, + sender: new Address(accountAddress), + destination: new Address(receiver), + chainID: shortId || 'D', + }); + + triggerTx({ tx }); + }; + + return { + transfer, + pending, + transaction, + txResult, + error, + }; +}; diff --git a/src/hooks/useTransaction.tsx b/src/hooks/useTransaction.tsx index 43dde75..1e58deb 100644 --- a/src/hooks/useTransaction.tsx +++ b/src/hooks/useTransaction.tsx @@ -21,10 +21,11 @@ import { useAccount } from './useAccount'; import { useNetwork } from './useNetwork'; export interface TransactionParams { - address: string; - gasLimit: IGasLimit; + address?: string; + gasLimit?: IGasLimit; data?: ITransactionPayload; value?: ITransactionValue; + tx?: Transaction; // When provided other params are not needed } export interface TransactionArgs { @@ -56,11 +57,17 @@ export function useTransaction( data, gasLimit, value, + tx, }: TransactionParams) => { setTransaction(null); setTxResult(null); setError(''); + if (!tx && !gasLimit) { + setError('You need to provide the gas limit in the triggerTx function!'); + return; + } + if ( networkStateSnap.dappProvider && networkStateSnap.apiNetworkProvider && @@ -73,19 +80,22 @@ export function useTransaction( const sender = new Address(accountSnap.address); const activeGuardianAddress = accountSnap.activeGuardianAddress; - const tx = new Transaction({ - nonce: currentNonce, - receiver: new Address(address), - gasLimit, - chainID: configStateSnap.shortId || 'D', - data, - value: value || 0, - sender, - }); + // You can pass whole Transaction object or you can create it + const transaction = + tx || + new Transaction({ + nonce: currentNonce, + receiver: new Address(address), + gasLimit: gasLimit!, + chainID: configStateSnap.shortId || 'D', + data, + value: value || 0, + sender, + }); signAndSendTxOperations( networkStateSnap.dappProvider as DappProvider, - tx, + transaction, loginInfoSnap, networkStateSnap.apiNetworkProvider as ApiNetworkProvider, setTransaction, diff --git a/src/index.tsx b/src/index.tsx index 937a328..e2acee0 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -14,6 +14,7 @@ export * from './hooks/useAccount'; export * from './hooks/useConfig'; export * from './hooks/useNetwork'; export * from './hooks/useTokenTransfer'; +export * from './hooks/useMultiTokenTransfer'; export { useApiCall } from './hooks/useApiCall'; export { useScQuery } from './hooks/useScQuery';