diff --git a/packages/sysweb3-keyring/src/transactions/ethereum.ts b/packages/sysweb3-keyring/src/transactions/ethereum.ts index dd3080f0..1a609140 100644 --- a/packages/sysweb3-keyring/src/transactions/ethereum.ts +++ b/packages/sysweb3-keyring/src/transactions/ethereum.ts @@ -42,6 +42,7 @@ import { createContractUsingAbi, getErc20Abi, getErc21Abi, + getErc55Abi, } from '@pollum-io/sysweb3-utils'; export class EthereumTransactions implements IEthereumTransactions { @@ -932,6 +933,162 @@ export class EthereumTransactions implements IEthereumTransactions { } }; + sendSignedErc1155Transaction = async ({ + receiver, + tokenAddress, + tokenId, + isLegacy, + maxPriorityFeePerGas, + maxFeePerGas, + gasPrice, + gasLimit, + }: ISendSignedErcTransactionProps): Promise => { + const { decryptedPrivateKey } = this.getDecryptedPrivateKey(); + const { accounts, activeAccountType, activeAccountId, activeNetwork } = + this.getState(); + const { address: activeAccountAddress } = + accounts[activeAccountType][activeAccountId]; + + const sendERC1155Token = async () => { + const currentWallet = new ethers.Wallet(decryptedPrivateKey); + const walletSigned = currentWallet.connect(this.web3Provider); + let transferMethod; + try { + const _contract = new ethers.Contract( + tokenAddress, + getErc55Abi(), + walletSigned + ); + + if (isLegacy) { + transferMethod = await _contract.safeTransferFrom( + walletSigned.address, + receiver, + tokenId as number, + 1, + [] + ); + } else { + transferMethod = await _contract.safeTransferFrom( + walletSigned.address, + receiver, + tokenId as number, + 1, + [] + ); + } + + return transferMethod; + } catch (error) { + throw error; + } + }; + + const sendERC1155TokenOnTrezor = async () => { + const signer = this.web3Provider.getSigner(activeAccountAddress); + const transactionNonce = await this.getRecommendedNonce( + activeAccountAddress + ); + try { + const _contract = new ethers.Contract( + tokenAddress, + getErc55Abi(), + signer + ); + const txData = _contract.interface.encodeFunctionData( + 'safeTransferFrom', + [activeAccountAddress, receiver, tokenId, 1, []] + ); + let txToBeSignedByTrezor; + if (isLegacy) { + txToBeSignedByTrezor = { + to: tokenAddress, + value: '0x0', + // @ts-ignore + gasLimit: `${gasLimit.toHexString()}`, + // @ts-ignore + gasPrice: `${gasPrice}`, + nonce: this.toBigNumber(transactionNonce)._hex, + chainId: activeNetwork.chainId, + data: txData, + }; + } else { + txToBeSignedByTrezor = { + to: tokenAddress, + value: '0x0', + // @ts-ignore + gasLimit: `${gasLimit.toHexString()}`, + // @ts-ignore + maxFeePerGas: `${maxFeePerGas.toHexString()}`, + // @ts-ignore + maxPriorityFeePerGas: `${maxPriorityFeePerGas.toHexString()}`, + nonce: this.toBigNumber(transactionNonce)._hex, + chainId: activeNetwork.chainId, + data: txData, + }; + } + + const signature = await this.trezorSigner.signEthTransaction({ + index: `${activeAccountId}`, + tx: txToBeSignedByTrezor, + }); + + if (signature.success) { + try { + let txFormattedForEthers; + if (isLegacy) { + txFormattedForEthers = { + to: tokenAddress, + value: '0x0', + gasLimit, + gasPrice, + data: txData, + nonce: transactionNonce, + chainId: activeNetwork.chainId, + type: 0, + }; + } else { + txFormattedForEthers = { + to: tokenAddress, + value: '0x0', + gasLimit, + maxFeePerGas, + maxPriorityFeePerGas, + data: txData, + nonce: transactionNonce, + chainId: activeNetwork.chainId, + type: 2, + }; + } + signature.payload.v = parseInt(signature.payload.v, 16); //v parameter must be a number by ethers standards + const signedTx = ethers.utils.serializeTransaction( + txFormattedForEthers, + signature.payload + ); + const finalTx = await this.web3Provider.sendTransaction(signedTx); + + return finalTx as any; + } catch (error) { + console.log({ error }); + throw error; + } + } else { + throw new Error(`Transaction Signature Failed. Error: ${signature}`); + } + } catch (error) { + console.log({ error }); + throw error; + } + }; + + switch (activeAccountType) { + case KeyringAccountType.Trezor: + return await sendERC1155TokenOnTrezor(); + default: + return await sendERC1155Token(); + } + }; + getRecommendedNonce = async (address: string) => { try { return await this.web3Provider.getTransactionCount(address, 'pending'); diff --git a/packages/sysweb3-keyring/src/types.ts b/packages/sysweb3-keyring/src/types.ts index c22e6e53..57406a2d 100644 --- a/packages/sysweb3-keyring/src/types.ts +++ b/packages/sysweb3-keyring/src/types.ts @@ -105,6 +105,17 @@ export interface IEthereumTransactions { tokenId, }: ISendSignedErcTransactionProps) => Promise; + sendSignedErc1155Transaction: ({ + receiver, + tokenAddress, + tokenId, + isLegacy, + gasPrice, + gasLimit, + maxFeePerGas, + maxPriorityFeePerGas, + }: ISendSignedErcTransactionProps) => Promise; + getBalance: (address: string) => Promise; getErc20TokensByAddress?: ( address: string, diff --git a/packages/sysweb3-utils/src/tokens.ts b/packages/sysweb3-utils/src/tokens.ts index 84526b06..3fab8c79 100644 --- a/packages/sysweb3-utils/src/tokens.ts +++ b/packages/sysweb3-utils/src/tokens.ts @@ -4,9 +4,9 @@ import { ethers as ethersModule } from 'ethers'; import sys from 'syscoinjs-lib'; import { createContractUsingAbi } from '.'; +import ABI1155 from './abi/erc1155.json'; import abi20 from './abi/erc20.json'; import ABI721 from './abi/erc721.json'; -// import ABI1155 from './abi/erc1155.json' import tokens from './tokens.json'; import type { @@ -173,6 +173,46 @@ export const fetchBalanceOfERC721Contract = async ( return fetchBalanceOfValue; }; +export const fetchBalanceOfERC1155Contract = async ( + contractAddress: string, + address: string, + config: EthersFetcherConfigEthersLoaded, + tokenId: number +): Promise => { + const contract = new config.ethers.Contract( + contractAddress, + ABI1155, + config.provider + ) as NftContract; + + const fetchBalanceOfValue = await contract.balanceOf(address, tokenId); + + return fetchBalanceOfValue; +}; + +export const getERC1155StandardBalance = async ( + contractAddress: string, + address: string, + provider: JsonRpcProvider, + tokenId: number +) => { + try { + const config = { provider, ethers: ethersModule }; + const loaded = await loadEthers(config); + + return await fetchBalanceOfERC1155Contract( + contractAddress, + address, + loaded, + tokenId + ); + } catch (error) { + throw new Error( + `Verify current network or the contract address. Set the same network of token contract. Error: ${error}` + ); + } +}; + export const getERC721StandardBalance = async ( contractAddress: string, address: string, @@ -192,28 +232,22 @@ export const getERC721StandardBalance = async ( export const fetchStandardNftContractData = async ( contractAddress: Address, - tokenId: string, config: EthersFetcherConfigEthersLoaded ): Promise => { const contract = new config.ethers.Contract( contractAddress, - ABI, + ABI1155, config.provider ) as NftContract; - const [metadataUrl, owner] = await Promise.all([ - url(contract, tokenId), - contract.ownerOf(tokenId).catch(() => ''), + const [name, symbol] = await Promise.all([ + contract.name(), + contract.symbol(), ]); - const metadata = await fetchMetadata(metadataUrl); - const imageType = urlExtensionType(metadata.image); - return { - ...metadata, - imageType, - metadataUrl, - owner, + name, + symbol, }; }; @@ -350,49 +384,6 @@ export const reversePromise = (promise: Promise): Promise => export const IMAGE_EXT_RE = /\.(?:png|svg|jpg|jepg|gif|webp|jxl|avif)$/; export const VIDEO_EXT_RE = /\.(?:mp4|mov|webm|ogv)$/; -// Guess a file type from the extension used in a URL -export const urlExtensionType = (url: string): NftMetadata['imageType'] => { - if (IMAGE_EXT_RE.test(url)) return 'image'; - if (VIDEO_EXT_RE.test(url)) return 'video'; - - return 'unknown'; -}; - -export const fetchMetadata = async (url: string): Promise => { - const response = await fetch(url); - - if (!response.ok) { - throw new Error(`Error when trying to request ${url}`); - } - - let rawData; - - try { - rawData = (await response.json()) as Record; - } catch (error) { - rawData = { name: '', description: '', image: url }; - } - - let data = { ...rawData }; - - if (isNftMetadataMixedInJsonSchema(data)) { - data = fixNftMetadataMixedInJsonSchema(data); - } - - data = fixIncorrectImageField(data); - - if (!isNftMetadata(data)) { - throw new Error('Invalid data received'); - } - - return { - description: data.description || '', - image: data.image || '', - name: data.name || '', - rawData, - }; -}; - export const getTokenIconBySymbol = async (symbol: string): Promise => { symbol = symbol.toUpperCase(); const searchResults = await getSearch(symbol); @@ -458,6 +449,22 @@ export const getTokenStandardMetadata = async ( } }; +export const getNftStandardMetadata = async ( + contractAddress: string, + provider: JsonRpcProvider +) => { + try { + const config = { provider, ethers: ethersModule }; + const loaded = await loadEthers(config); + + return await fetchStandardNftContractData(contractAddress, loaded); + } catch (error) { + throw new Error( + `Verify current network. Set the same network of NFT token contract. Error: ${error}` + ); + } +}; + /** * Converts a token to a fiat value * @@ -764,13 +771,8 @@ export interface IEtherscanNFT { } export interface NftMetadata { - description: string; - image: string; - imageType: 'image' | 'video' | 'unknown'; - metadataUrl: string; name: string; - owner: Address; - rawData: Record | null; + symbol: string; } export type IErc20Token = { name: string;