마케팅 팀에서 우리의 NFT가 MetaMask 지갑에서 이미지가 제대로
노출되지 않는다고 지속적으로 문의를 해왔다.
약간의 시간적 여유가생겨서 Metamask Github Source를 분석해보 았다.
왜 우리것만 안나오냐??!
시작 : https://github.com/MetaMask/metamask-mobile
일단 contract의 tokenURI 를 호출하는 코드를 찾았다.
해당부분은 core 프로젝트에 구현되어 있었다.
코어 프로젝트 : https://github.com/MetaMask/core.git
// TOKEN INTERFACE IDS
export const ERC721_INTERFACE_ID = '0x80ac58cd';
export const ERC721_METADATA_INTERFACE_ID = '0x5b5e139f';
export const ERC721_ENUMERABLE_INTERFACE_ID = '0x780e9d63';
export const ERC1155_INTERFACE_ID = '0xd9b67a26';
export const ERC1155_METADATA_URI_INTERFACE_ID = '0x0e89341c';
export const ERC1155_TOKEN_RECEIVER_INTERFACE_ID = '0x4e2312e0';
/**
* Query for tokenURI for a given asset.
*
* @param address - ERC721 asset contract address.
* @param tokenId - ERC721 asset identifier.
* @returns Promise resolving to the 'tokenURI'.
*/
getTokenURI = async (address: string, tokenId: string): Promise<string> => {
const contract = new Contract(address, abiERC721, this.provider);
const supportsMetadata = await this.contractSupportsMetadataInterface(
address,
);
if (!supportsMetadata) {
throw new Error('Contract does not support ERC721 metadata interface.');
}
return contract.tokenURI(tokenId);
};
contractSupportsURIMetadataInterface = async (
address: string,
): Promise<boolean> => {
return this.contractSupportsInterface(
address,
ERC1155_METADATA_URI_INTERFACE_ID,
);
};
/**
* Query for owner for a given ERC721 asset.
*
* @param address - ERC721 asset contract address.
* @param tokenId - ERC721 asset identifier.
* @returns Promise resolving to the owner address.
*/
async getOwnerOf(address: string, tokenId: string): Promise<string> {
const contract = new Contract(address, abiERC721, this.provider);
return contract.ownerOf(tokenId);
}
/**
* Query if a contract implements an interface.
*
* @param address - Asset contract address.
* @param interfaceId - Interface identifier.
* @returns Promise resolving to whether the contract implements `interfaceID`.
*/
private contractSupportsInterface = async (
address: string,
interfaceId: string,
): Promise<boolean> => {
const contract = new Contract(address, abiERC721, this.provider);
try {
return await contract.supportsInterface(interfaceId);
} catch (err: any) {
// Mirror previous implementation
if (err.message.includes('call revert exception')) {
return false;
}
throw err;
}
};
contract.supportsInterface (ERC721_METADATA_INTERFACE_ID) 에서 실마리를 찾았다.
우리 NFT가 저부분에서 문제가 발생한것으로 예상!
우리 프로젝트로 이동~
https://github.com/MonoVerseSys/fruttidino-nftsale-contract
수정전
function supportsInterface(bytes4 interfaceId) public view virtual
override(AccessControlUpgradeable, ERC721EnumerableUpgradeable) returns (bool) {
return interfaceId == type(IERC165Upgradeable).interfaceId;
}
수정후
function supportsInterface(bytes4 interfaceId) public view virtual
override(AccessControlUpgradeable, ERC721EnumerableUpgradeable) returns (bool) {
return super.supportsInterface(interfaceId);
}
아 이 부분이닷!. super를 호출해야 했는데 저걸 초기 개발할 때
큰 생각없이 저렇게 짜버렸던것이 화근!
Mestamask mobile wallet에서는 supportsInterface 를 통해서 "ERC721_METADATA_INTERFACE_ID"를 체크하고 false가 반환되면 그냥 throw를 해버렸던것이다.
그래서 아무리 tokenURI api를 고치고 고쳐봐도 소용이 없었던것이었다.
메타마스크 미어!
contract proxy upgrade!
contract 개발자라면 대부분 알겠지만 업그레이드 가능한 proxy 패턴의 배포가 있다.
우리는 proxy 배포를 했기때문에 업그레이드가 가능하다.
proxy패턴은 storage와 logic contract가 분리되어 있는 형태다.
storage는 그대로 냅두고 logic contract만 교체되는 방식으로
사용자들이 바라보는 계약의 주소는 변경되지 않는다.
proxy 패턴에게 축복을!
캬 잘나온다 ~