Collectibles utils
This commit is contained in:
parent
433fe9be79
commit
878c32c4a3
|
@ -0,0 +1,68 @@
|
|||
import { getTransferMethodByContractAddress } from 'src/logic/collectibles/utils'
|
||||
|
||||
jest.mock('src/config', () => {
|
||||
// Require the original module to not be mocked...
|
||||
const originalModule = jest.requireActual('src/config')
|
||||
|
||||
return {
|
||||
__esModule: true, // Use it when dealing with esModules
|
||||
...originalModule,
|
||||
getNetworkId: jest.fn().mockReturnValue(4),
|
||||
}
|
||||
})
|
||||
|
||||
describe('getTransferMethodByContractAddress', () => {
|
||||
const config = require('src/config')
|
||||
|
||||
afterAll(() => {
|
||||
jest.unmock('src/config')
|
||||
})
|
||||
|
||||
it(`should return "transfer" method, if CK address is provided for MAINNET`, () => {
|
||||
// Given
|
||||
config.getNetworkId.mockReturnValue(1)
|
||||
const contractAddress = '0x06012c8cf97bead5deae237070f9587f8e7a266d'
|
||||
|
||||
// When
|
||||
const selectedMethod = getTransferMethodByContractAddress(contractAddress)
|
||||
|
||||
// Then
|
||||
expect(selectedMethod).toBe('transfer')
|
||||
})
|
||||
|
||||
it(`should return "transfer" method, if CK address is provided for RINKEBY`, () => {
|
||||
// Given
|
||||
config.getNetworkId.mockReturnValue(4)
|
||||
const contractAddress = '0x16baf0de678e52367adc69fd067e5edd1d33e3bf'
|
||||
|
||||
// When
|
||||
const selectedMethod = getTransferMethodByContractAddress(contractAddress)
|
||||
|
||||
// Then
|
||||
expect(selectedMethod).toBe('transfer')
|
||||
})
|
||||
|
||||
it(`should return "0x42842e0e" method, if CK address is provided any other network`, () => {
|
||||
// Given
|
||||
config.getNetworkId.mockReturnValue(100)
|
||||
const contractAddress = '0x06012c8cf97bead5deae237070f9587f8e7a266d'
|
||||
|
||||
// When
|
||||
const selectedMethod = getTransferMethodByContractAddress(contractAddress)
|
||||
|
||||
// Then
|
||||
expect(selectedMethod).toBe('0x42842e0e')
|
||||
})
|
||||
|
||||
it(`should return "0x42842e0e" method, if non-CK address is provided`, () => {
|
||||
// Given
|
||||
config.getNetworkId.mockReturnValue(4)
|
||||
const contractAddress = '0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85'
|
||||
|
||||
// When
|
||||
const selectedMethod = getTransferMethodByContractAddress(contractAddress)
|
||||
|
||||
// Then
|
||||
expect(selectedMethod).toBe('0x42842e0e')
|
||||
})
|
||||
})
|
|
@ -0,0 +1,143 @@
|
|||
import { getNetworkId, getNetworkInfo } from 'src/config'
|
||||
import { ETHEREUM_NETWORK } from 'src/config/networks/network.d'
|
||||
import { nftAssetsListAddressesSelector } from 'src/logic/collectibles/store/selectors'
|
||||
import { TxServiceModel } from 'src/logic/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions'
|
||||
import { TOKEN_TRANSFER_METHODS_NAMES } from 'src/logic/safe/store/models/types/transactions.d'
|
||||
import { getERC721TokenContract, getStandardTokenContract } from 'src/logic/tokens/store/actions/fetchTokens'
|
||||
import { sameAddress } from 'src/logic/wallets/ethAddresses'
|
||||
import { CollectibleTx } from 'src/routes/safe/components/Balances/SendModal/screens/ReviewCollectible'
|
||||
import { store } from 'src/store'
|
||||
import { sameString } from 'src/utils/strings'
|
||||
|
||||
// CryptoKitties Contract Addresses by network
|
||||
// This is an exception made for a popular NFT that's not ERC721 standard-compatible,
|
||||
// so we can allow the user to transfer the assets by using `transferFrom` instead of
|
||||
// the standard `safeTransferFrom` method.
|
||||
export const CK_ADDRESS = {
|
||||
[ETHEREUM_NETWORK.MAINNET]: '0x06012c8cf97bead5deae237070f9587f8e7a266d',
|
||||
[ETHEREUM_NETWORK.RINKEBY]: '0x16baf0de678e52367adc69fd067e5edd1d33e3bf',
|
||||
}
|
||||
|
||||
// Note: xDAI ENS is missing, once we have it we need to add it here
|
||||
const ENS_CONTRACT_ADDRESS = {
|
||||
[ETHEREUM_NETWORK.MAINNET]: '0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85',
|
||||
[ETHEREUM_NETWORK.RINKEBY]: '0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85',
|
||||
[ETHEREUM_NETWORK.ENERGY_WEB_CHAIN]: '0x0A6d64413c07E10E890220BBE1c49170080C6Ca0',
|
||||
[ETHEREUM_NETWORK.VOLTA]: '0xd7CeF70Ba7efc2035256d828d5287e2D285CD1ac',
|
||||
}
|
||||
|
||||
// safeTransferFrom(address,address,uint256)
|
||||
export const SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH = '42842e0e'
|
||||
|
||||
/**
|
||||
* Verifies that a tx received by the transaction service is an ERC721 token-related transaction
|
||||
* @param {TxServiceModel} tx
|
||||
* @returns boolean
|
||||
*/
|
||||
export const isSendERC721Transaction = (tx: TxServiceModel): boolean => {
|
||||
let hasERC721Transfer = false
|
||||
|
||||
if (tx.dataDecoded && sameString(tx.dataDecoded.method, TOKEN_TRANSFER_METHODS_NAMES.SAFE_TRANSFER_FROM)) {
|
||||
hasERC721Transfer = tx.dataDecoded.parameters.findIndex((param) => sameString(param.name, 'tokenId')) !== -1
|
||||
}
|
||||
|
||||
// Note: this is only valid with our current case (client rendering), if we move to server side rendering we need to refactor this
|
||||
const state = store.getState()
|
||||
const knownAssets = nftAssetsListAddressesSelector(state)
|
||||
return knownAssets.includes(tx.to) || hasERC721Transfer
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the symbol of the provided ERC721 contract
|
||||
* @param {string} contractAddress
|
||||
* @returns Promise<string>
|
||||
*/
|
||||
export const getERC721Symbol = async (contractAddress: string): Promise<string> => {
|
||||
let tokenSymbol = 'UNKNOWN'
|
||||
|
||||
try {
|
||||
const ERC721token = await getERC721TokenContract()
|
||||
const tokenInstance = await ERC721token.at(contractAddress)
|
||||
tokenSymbol = await tokenInstance.symbol()
|
||||
} catch (err) {
|
||||
// If the contract address is an ENS token contract, we know that the ERC721 standard is not proper implemented
|
||||
// The method symbol() is missing
|
||||
if (isENSContract(contractAddress)) {
|
||||
return 'ENS'
|
||||
}
|
||||
console.error(`Failed to retrieve token symbol for ERC721 token ${contractAddress}`)
|
||||
}
|
||||
|
||||
return tokenSymbol
|
||||
}
|
||||
|
||||
export const isENSContract = (contractAddress: string): boolean => {
|
||||
const { id } = getNetworkInfo()
|
||||
return sameAddress(contractAddress, ENS_CONTRACT_ADDRESS[id])
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if the provided contract is a valid ERC721
|
||||
* @param {string} contractAddress
|
||||
* @returns boolean
|
||||
*/
|
||||
export const isERC721Contract = async (contractAddress: string): Promise<boolean> => {
|
||||
const ERC721Token = await getStandardTokenContract()
|
||||
let isERC721 = false
|
||||
|
||||
try {
|
||||
await ERC721Token.at(contractAddress)
|
||||
isERC721 = true
|
||||
} catch (error) {
|
||||
console.warn('Asset not found')
|
||||
}
|
||||
|
||||
return isERC721
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a method identifier based on the asset specified and the current network
|
||||
* @param {string} contractAddress
|
||||
* @returns string
|
||||
*/
|
||||
export const getTransferMethodByContractAddress = (contractAddress: string): string => {
|
||||
if (sameAddress(contractAddress, CK_ADDRESS[getNetworkId()])) {
|
||||
// on mainnet `transferFrom` seems to work fine but we can assure that `transfer` will work on both networks
|
||||
// so that's the reason why we're falling back to `transfer` for CryptoKitties
|
||||
return 'transfer'
|
||||
}
|
||||
|
||||
return `0x${SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the encodedABI data for the transfer of an NFT token
|
||||
* @param {CollectibleTx} tx
|
||||
* @param {string} safeAddress
|
||||
* @returns Promise<string>
|
||||
*/
|
||||
export const generateERC721TransferTxData = async (
|
||||
tx: CollectibleTx,
|
||||
safeAddress: string | undefined,
|
||||
): Promise<string> => {
|
||||
if (!safeAddress) {
|
||||
throw new Error('Failed to build NFT transfer tx data. SafeAddress is not defined.')
|
||||
}
|
||||
|
||||
const methodToCall = getTransferMethodByContractAddress(tx.assetAddress)
|
||||
let transferParams = [tx.recipientAddress, tx.nftTokenId]
|
||||
let NFTTokenContract
|
||||
|
||||
if (methodToCall.includes(SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH)) {
|
||||
// we add the `from` param for the `safeTransferFrom` method call
|
||||
transferParams = [safeAddress, ...transferParams]
|
||||
NFTTokenContract = await getERC721TokenContract()
|
||||
} else {
|
||||
// we fallback to an ERC20 Token contract whose ABI implements the `transfer` method
|
||||
NFTTokenContract = await getStandardTokenContract()
|
||||
}
|
||||
|
||||
const tokenInstance = await NFTTokenContract.at(tx.assetAddress)
|
||||
|
||||
return tokenInstance.contract.methods[methodToCall](...transferParams).encodeABI()
|
||||
}
|
Loading…
Reference in New Issue