diff --git a/components/SendNft.tsx b/components/SendNft.tsx index a0e15abad..4577196c9 100644 --- a/components/SendNft.tsx +++ b/components/SendNft.tsx @@ -146,21 +146,23 @@ const SendNft = ({ const fromOwner = tryParseKey(fromOwnerString) // should be impossible, but stuff isn't typed if (fromOwner === null) throw new Error() - + const transferIx = await createIx_transferNft( connection, fromOwner, toOwner, - nftMint, + nft, fromOwner, nativeTreasury, ) + if (!transferIx) throw new Error('failed to create transfer instruction'); + return { serializedInstruction: serializeInstructionToBase64(transferIx), isValid: true, prerequisiteInstructions: - destinationAtaQueried === null + destinationAtaQueried === null && nft.interface !== 'MplCoreAsset' ? [ Token.createAssociatedTokenAccountInstruction( ASSOCIATED_TOKEN_PROGRAM_ID, // always ASSOCIATED_TOKEN_PROGRAM_ID diff --git a/hooks/queries/digitalAssets.ts b/hooks/queries/digitalAssets.ts index fcbdcab2c..57a302c5a 100644 --- a/hooks/queries/digitalAssets.ts +++ b/hooks/queries/digitalAssets.ts @@ -93,6 +93,7 @@ export type DasNftObject = { verified: boolean }[] id: string + interface?: string } /*** Here is an example item from the DAS Api, since it's not typed and the docs dont give the full schema. diff --git a/package.json b/package.json index 1a76a4186..11d595956 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,11 @@ "@mean-dao/payment-streaming": "4.0.3", "@metaplex-foundation/beet": "0.7.2", "@metaplex-foundation/js": "0.19.4", + "@metaplex-foundation/mpl-core": "1.7.0", "@metaplex-foundation/mpl-token-metadata": "2.10.0", + "@metaplex-foundation/umi": "1.4.1", + "@metaplex-foundation/umi-bundle-defaults": "1.4.1", + "@metaplex-foundation/umi-web3js-adapters": "1.4.1", "@multifarm/solana-realms": "1.0.6", "@next/bundle-analyzer": "12.1.5", "@nivo/bar": "0.79.1", diff --git a/utils/metaplex.ts b/utils/metaplex.ts index 0ad1667df..29d25cfd8 100644 --- a/utils/metaplex.ts +++ b/utils/metaplex.ts @@ -1,14 +1,19 @@ +import { DasNftObject } from '@hooks/queries/digitalAssets' import { fetchNFTbyMint } from '@hooks/queries/nft' import { Metaplex } from '@metaplex-foundation/js' import { Connection, PublicKey } from '@solana/web3.js' +import { transferV1 } from "@metaplex-foundation/mpl-core" +import { createUmi } from '@metaplex-foundation/umi-bundle-defaults' +import { generateSigner, publicKey, signerIdentity, signerPayer } from '@metaplex-foundation/umi' +import { toWeb3JsInstruction } from '@metaplex-foundation/umi-web3js-adapters' export const createIx_transferNft = async ( connection: Connection, fromOwner: PublicKey, toOwner: PublicKey, - mint: PublicKey, + nft: DasNftObject, authority: PublicKey, - payer: PublicKey, + payer: PublicKey ) => { const metaplex = new Metaplex( connection, @@ -21,27 +26,52 @@ export const createIx_transferNft = async ( //metaplex.identity = () => ({ publicKey: fromOwner } as any) // you need to do this to set payer and authority. I love OOP!! // except the payer might not be the same person. great! - const nft = await fetchNFTbyMint(connection, mint) - if (!nft.result) throw 'failed to fetch nft' + const mint = new PublicKey(nft.id); + const umi = createUmi(connection.rpcEndpoint) + const signer = generateSigner(umi); + umi.use(signerIdentity(signer)); - const tokenStandard = nft.result.tokenStandard - const ruleSet = nft.result.programmableConfig?.ruleSet + try { + const nft = await fetchNFTbyMint(connection, mint) + if (!nft.result) throw 'failed to fetch nft' - const ix = metaplex - .nfts() - .builders() - .transfer({ - nftOrSft: { - address: mint, - tokenStandard, - }, - authorizationDetails: ruleSet ? { rules: ruleSet } : undefined, - toOwner, - fromOwner, - }) - .getInstructions()[0] + const tokenStandard = nft.result.tokenStandard + const ruleSet = nft.result.programmableConfig?.ruleSet - ix.keys[9].pubkey = authority - ix.keys[10].pubkey = payer - return ix + const ix = metaplex + .nfts() + .builders() + .transfer({ + nftOrSft: { + address: mint, + tokenStandard, + }, + authorizationDetails: ruleSet ? { rules: ruleSet } : undefined, + toOwner, + fromOwner, + }) + .getInstructions()[0] + + ix.keys[9].pubkey = authority + ix.keys[10].pubkey = payer + return ix + } catch(error) { + if (nft.interface === 'MplCoreAsset') { + // fallback for mpl core assets + const ixMpl = transferV1(umi, { + asset: publicKey(nft.id), + newOwner: publicKey(toOwner), + collection: nft.grouping.some((g) => g.group_key === 'collection') + ? publicKey( + nft.grouping.find((g) => g.group_key === 'collection')!.group_value + ) : undefined, + }) + + const ix = toWeb3JsInstruction(ixMpl.items[0].instruction); + ix.keys[2].pubkey = fromOwner; + return ix; + } else { + throw new Error('failed to create transfer instruction') + } + } }