import{AnchorProvider,Wallet}from"@coral-xyz/anchor";import{Connection,Keypair,PublicKey}from"@solana/web3.js";import{getLeafAssetId}from"@tensor-hq/tensor-common";import{findListStatePda,findTreeAuthorityPda,TCompSDK,}from"@tensor-oss/tcomp-sdk";importBNfrom"bn.js";constconn=newConnection("https://api.mainnet-beta.solana.com");constprovider=newAnchorProvider(conn,newWallet(Keypair.generate()),AnchorProvider.defaultOptions());consttcompSdk=newTCompSDK({ provider });// Listing cNFTconst{tx: { ixs },}=awaittcompSdk.list({// Retrieve these fields from DAS API
merkleTree,
root,
canopyDepth,
index,
proof,
dataHash,
creatorsHash,delegate: delegatePk,owner: ownerPk,rentPayer: rentPayerPk,// optional: payer and recipient of listing rentamount: newBN(priceLamports),// in lamportscurrency: null,// optional: list for SOL or SPL tokensexpireInSec: expireIn ? newBN(expireIn) : null,// seconds until listing expiresprivateTaker: takerPk,// optional: only this wallet can buy this listing});// Fetching the listingconstnonce=newBN(index);const[treeAuthority]=findTreeAuthorityPda({ merkleTree });constassetId=getLeafAssetId(merkleTree,nonce);constlistState=findListStatePda({ assetId });const{assetId: listStateAssetId,
owner,
amount,
currency,
expiry,
privateTaker,
makerBroker,
rentPayer,}=awaittcompSdk.fetchListState(listState);// Buying cNFTconst{tx: { ixs },}=awaittcompSdk.buy({// Retrieve these fields from DAS API
merkleTree,
root,
canopyDepth,
index,
proof,
sellerFeeBasisPoints,// For constructing metaHash, see example below
metaHash,
creators,payer: payerPk,buyer: buyerPk,owner: ownerPk,
makerBroker,
rentDest,maxAmount: newBN(priceLamports),optionalRoyaltyPct: 100,// currently required to be 100% (enforced)});// Bidding on a single cNFTconst{tx: { ixs },}=awaittcompSdk.bid({owner: ownerPk,rentPayer: rentPayerPK,// optional: payer and recipient of bid rentamount: newBN(priceLamports),expireInSec: expireIn ? newBN(expireIn) : null,// seconds until listing expiresprivateTaker: takerPk,// optional: only this wallet can sell into this bidbidId: newPublicKey(assetId),// asset ID of nft to bid ontargetId: newPublicKey(assetId),// asset ID of nft to bid ontarget: Target.AssetId,quantity: 1,// Ignore these for now (advanced usage)margin: null,field: null,fieldId: null,});
Constructing metaHash
constaxios=require('axios');constBN=require('bn.js');const{ PublicKey }=require("@solana/web3.js");const{ keccak_256 }=require('js-sha3');const{ computeMetadataArgsHash }=require("@tensor-hq/tensor-common");const{ TokenStandard }=require('@metaplex-foundation/mpl-bubblegum');consturl=`https://mainnet.helius-rpc.com/?api-key=<YOUR_HELIUS_API_KEY>`constconstructMetaHash=async(mint)=>{// query DAS API for asset infoconstassetRes=awaitaxios.post(url,{jsonrpc: '2.0',id: '0',method: 'getAsset',params: {id: mint}});const{
compression,
content,
royalty,
creators,
uses,
grouping,
supply,ownership: { owner, delegate },
mutable,}=assetRes.data.result;constcoll=grouping.find((group)=>group.group_key==="collection")?.group_value;consttokenStandard=content.metadata.token_standardconstdataHashBuffer=newPublicKey(compression.data_hash).toBuffer();// construct metadataArgs to hash later// ordering follows https://docs.metaplex.com/programs/token-metadata/accountsvarmetadataArgs={name: content?.metadata?.name??"",symbol: content?.metadata?.symbol??" ",uri: content?.json_uri??"",sellerFeeBasisPoints: royalty.basis_points,creators: creators.map((creator)=>({address: newPublicKey(creator.address),share: creator.share,verified: creator.verified,})),primarySaleHappened: royalty.primary_sale_happened,isMutable: mutable,editionNonce: supply?.edition_nonce!=null ? supply!.edition_nonce : null,tokenStandard: tokenStandard==="Fungible" ? TokenStandard.Fungible :
tokenStandard==="NonFungibleEdition" ? TokenStandard.NonFungibleEdition :
tokenStandard==="FungibleAsset" ? TokenStandard.FungibleAsset :
TokenStandard.NonFungible,// if Helius shows a collection in groupings for a cNFT then it's verifiedcollection: coll ? {key: newPublicKey(coll),verified: true} : null,uses: uses
? {useMethod: uses.use_method==="Burn" ? 0 : uses.use_method==="Multiple" ? 1 : 2,remaining: uses.remaining,total: uses.total,}
: null,// currently always Original (Token2022 not supported yet)tokenProgramVersion: 0,};constoriginalMetadata={ ...metadataArgs};constsellerFeeBasisPointsBuffer=newBN(royalty.basis_points).toBuffer("le",2);// hash function on top of candidate metaHash to compare against data_hashconstmakeDataHash=(metadataArgs)=>Buffer.from(keccak_256.digest(Buffer.concat([newPublicKey(computeMetadataArgsHash(metadataArgs)).toBuffer(),sellerFeeBasisPointsBuffer,])));// try original metadataArgsvarhash=makeDataHash(metadataArgs);if(hash.equals(dataHashBuffer))returncomputeMetadataArgsHash(metadataArgs);// try tokenStandard = nullmetadataArgs.tokenStandard=null;hash=makeDataHash(metadataArgs);if(hash.equals(dataHashBuffer))returncomputeMetadataArgsHash(metadataArgs);// try name + uri = "", tokenStandard = nullmetadataArgs.name="";metadataArgs.uri="";hash=makeDataHash(metadataArgs);if(hash.equals(dataHashBuffer))returncomputeMetadataArgsHash(metadataArgs);// try name + uri = "", tokenStandard = 0metadataArgs.tokenStandard=0;hash=makeDataHash(metadataArgs);if(hash.equals(dataHashBuffer))returncomputeMetadataArgsHash(metadataArgs);// try reversing creatorsmetadataArgs.creators.reverse();metadataArgs.name=originalMetadata.name;metadataArgs.uri=originalMetadata.uri;metadataArgs.tokenStandard=originalMetadata.tokenStandard;hash=makeDataHash(metadataArgs);if(hash.equals(dataHashBuffer))returncomputeMetadataArgsHash(metadataArgs);// can't match - return nullreturnnull;};constructMetaHash("8H6C2tGh5Yu5jGXUbFtZ17FQTDyBAEQ4LfGA527QLXYQ");