import { BigNumber , Contract , providers } from 'ethers' ; import { getAddress , hexDataSlice } from 'ethers/lib/utils' ; import { OPENSEA_REGISTRY } from '../common/abis' ; import { ADDRESS_ZERO , DUMMY_ADDRESS , DUMMY_ADDRESS_2 , OPENSEA_REGISTRY_ADDRESS } from '../common/constants' ; import { TokenMapping } from '../common/interfaces' ; import { Allowance } from './interfaces' ; import { addressToAppName as addressToAppNameBase , getDappListName , lookupEnsName , unpackResult , withFallback } from '../common/util' ; export async function getLimitedAllowancesFromApprovals ( contract , approvals ) { const deduplicatedApprovals = approvals . filter ( ( approval , i ) => i === approvals . findIndex ( other => approval . topics [ 2 ] === other . topics [ 2 ] ) ) ; const allowances = await Promise . all ( deduplicatedApprovals . map ( ( approval ) => getLimitedAllowanceFromApproval ( contract , approval ) ) ) ; return allowances ; } async function getLimitedAllowanceFromApproval ( multicallContract , approval ) { try { const tokenId = approval . topics . length === 4 ? BigNumber . from ( approval . topics [ 3 ] ) . toString ( ) : BigNumber . from ( approval . data ) . toString ( ) ; const [ spender ] = await multicallContract . functions . getApproved ( tokenId ) ; if ( spender === ADDRESS_ZERO ) return undefined ; return { spender , tokenId } ; } catch { return undefined ; } } export async function getUnlimitedAllowancesFromApprovals ( contract , ownerAddress , approvals ) { const deduplicatedApprovals = approvals . filter ( ( approval , i ) => i === approvals . findIndex ( other => approval . topics [ 2 ] === other . topics [ 2 ] ) ) ; const allowances = await Promise . all ( deduplicatedApprovals . map ( ( approval ) => getUnlimitedAllowanceFromApproval ( contract , ownerAddress , approval ) ) ) ; return allowances ; } async function getUnlimitedAllowanceFromApproval ( multicallContract , ownerAddress , approval ) { const spender = getAddress ( hexDataSlice ( approval . topics [ 2 ] , 12 ) ) ; const [ isApprovedForAll ] = await multicallContract . functions . isApprovedForAll ( ownerAddress , spender ) ; if ( ! isApprovedForAll ) return undefined ; return { spender } ; } export async function addDisplayAddressesToAllowances ( allowances , provider , chainId , openSeaProxyAddress ? ) { return await Promise . all ( allowances . map ( ( allowance ) => addDisplayAddresses ( allowance , provider , chainId , openSeaProxyAddress ) ) ) ; } async function addDisplayAddresses ( allowance , provider , chainId , openSeaProxyAddress ? ) { const ensSpender = await lookupEnsName ( allowance . spender , provider ) ; const dappListNetworkName = getDappListName ( chainId ) ; const spenderAppName = await addressToAppName ( allowance . spender , dappListNetworkName , openSeaProxyAddress ) ; return { ... allowance , ensSpender , spenderAppName } ; } export async function getTokenData ( contract , ownerAddress , tokenMapping = { } ) { const tokenData = tokenMapping [ getAddress ( contract . address ) ] ; const [ balance , symbol ] = await Promise . all ( [ unpackResult ( contract . functions . balanceOf ( ownerAddress ) ) , tokenData ?. name ?? withFallback ( unpackResult ( contract . functions . name ( ) ) , '???' ) , throwIfNotErc721 ( contract ) , ] ) ; return { symbol , balance } ; } export async function addressToAppName ( address , networkName ? , openseaProxyAddress ? ) { if ( address === openseaProxyAddress ) return 'OpenSea' ; return addressToAppNameBase ( address , networkName ) ; } export async function getOpenSeaProxyAddress ( userAddress , provider ) { try { const contract = new Contract ( OPENSEA_REGISTRY_ADDRESS , OPENSEA_REGISTRY , provider ) ; const [ proxyAddress ] = await contract . functions . proxies ( userAddress ) ; if ( ! proxyAddress || proxyAddress === ADDRESS_ZERO ) return undefined ; return proxyAddress ; } catch { return undefined ; } } async function throwIfNotErc721 ( contract ) { const [ isApprovedForAll ] = await contract . functions . isApprovedForAll ( DUMMY_ADDRESS , DUMMY_ADDRESS_2 ) ; if ( isApprovedForAll !== false ) { throw new Error ( 'Response to isApprovedForAll was not false, indicating that this is not an ERC721 contract' ) ; } } export function formatAllowance ( tokenId ? ) { if ( ! tokenId ) return 'all tokens' ; return `token ID ${ tokenId }` ; }