Skip to content
This repository has been archived by the owner on Jul 15, 2024. It is now read-only.

Add support for compressed NFTs #480

Merged
merged 25 commits into from
May 3, 2023

Conversation

jnwng
Copy link
Contributor

@jnwng jnwng commented Feb 22, 2023

there's a lot of gaps to cover, so i figured it'd be worth getting a spot-check on the structure of this support before going too much further. this PR includes:

  • the base-level requirements for metaplex.nfts().create({ tree: <insert-tree-address>}) to mint a compressed NFT into an initialized Merkle tree and into a collection.
  • a thin wrapper around implementations of the Metaplex Read API for metaplex.nfts().findByAssetId

open questions that would be valuable to answer:

  1. should the Read API wrapper be in this package? or perhaps in a different sub-package?

known things that are missing that i want to add, provided there's good feedback on the approach so far:

  1. The ability to transfer NFTs
  2. More Read API methods like findByCreator, findByOwner
  3. Helper functions for initializing trees
  4. NFTs without collections
  5. Tests

this package was tested with a script that looks roughly like:

  const connection = new ReadApiConnection(<insert-RPC-URL>);
  const metaplex = Metaplex.make(connection).use(keypairIdentity(wallet));

  const { tree } = await createTree(metaplex);

  const response = await metaplex.nfts().create({
    uri,
    name: 'The first!',
    sellerFeeBasisPoints: 500,
    tree: new PublicKey('Diz7dzSm9tknCUMhHDJnbBzQRpQtFoNjaxykKtzR9JFA'),
    collection: new PublicKey('3DJYB7uaGf5BjZLBasNu1WVrZwT8bZyptQKSshFDMAU3'),
    collectionAuthority: wallet,
  });

here's a sample transaction that this code ran

  const nft = await metaplex.nfts().findByAssetId({
    assetId: new PublicKey('BF8bhBVTcei1kLfQuSLCHt8n34KwGDqTLZiCjBT3Z3kC'),
  });

works too

@changeset-bot
Copy link

changeset-bot bot commented Feb 22, 2023

🦋 Changeset detected

Latest commit: 1364d8d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@metaplex-foundation/js Minor
@metaplex-foundation/js-plugin-aws Minor
@metaplex-foundation/js-plugin-nft-storage Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@jnwng
Copy link
Contributor Author

jnwng commented Feb 22, 2023

/cc @lorisleiva

i will continue to add support for some of the other functions while you're taking a peek at this, but let me know if you have any big changes you'd want to see here

@jnwng jnwng marked this pull request as ready for review March 22, 2023 17:45
@jnwng jnwng changed the title [WIP] Add support for compressed NFTs Add support for compressed NFTs Mar 22, 2023
Copy link
Contributor

@lorisleiva lorisleiva left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks both of you for your great work on this!

I've added a few questions/comments.

Additionally, all existing tests are passing except for the one that ensures the JS SDK can be imported as an ESM module. It seems that the @solana/spl-account-compression library is CJS only which can be imported to ESM modules but differently. See error below.

file:///home/runner/work/js/js/packages/js/dist/esm/plugins/nftModule/operations/createCompressedNft.mjs:3
import { SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, SPL_NOOP_PROGRAM_ID } from '@solana/spl-account-compression';
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SyntaxError: Named export 'SPL_ACCOUNT_COMPRESSION_PROGRAM_ID' not found. The requested module '@solana/spl-account-compression' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from '@solana/spl-account-compression';
const { SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, SPL_NOOP_PROGRAM_ID } = pkg;

The issue is, if we do change the way we import it as suggested, we break CJS modules. I've had this issue with the Bundlr library in the past and ended up having to create a helper function that removed the double default attribute from the package (See code below).

/**
* This method is necessary to import the Bundlr package on both ESM and CJS modules.
* Without this, we get a different structure on each module:
* - CJS: { default: [Getter], WebBundlr: [Getter] }
* - ESM: { default: { default: [Getter], WebBundlr: [Getter] } }
* This method fixes this by ensure there is not double default in the imported package.
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
function _removeDoubleDefault(pkg: any) {
if (
pkg &&
typeof pkg === 'object' &&
'default' in pkg &&
'default' in pkg.default
) {
return pkg.default;
}
return pkg;
}

Would either of you mind taking a look at that?

packages/js/src/plugins/nftModule/models/Nft.ts Outdated Show resolved Hide resolved
packages/js/src/plugins/nftModule/NftClient.ts Outdated Show resolved Hide resolved
packages/js/src/plugins/nftModule/plugin.ts Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see a lot of TODOs in this file. Is this operation ready enough to be merged as-is?

packages/js/package.json Outdated Show resolved Hide resolved
@lorisleiva
Copy link
Contributor

I’ve tried fixing the issue regarding the import of @solana/spl-account-compression but unfortunately the package did not play nicely.

The issue is that @solana/spl-account-compression exports both a CJS and an ESM module but do not do anything to identify them as such. The recommended approach is to either:

  • use the .cjs and .mjs file extensions for each file in the exported /cjs and /esm directories.
  • or add a tiny package.json with { type: "commonjs" } or { type: "module" } inside these directories.

Right now, the JS SDK correctly imports /cjs when exporting CJS and /esm when exporting ESM but the /esm directory will be wrongly treated as a CJS module. As such the end user will end up having an error caused by the @solana/spl-account-compression (which is what the failing tests are asserting).

Additionally, even if that was right, the ESM module exported by @solana/spl-account-compression is not a valid vanilla ESM module as it exports folder directly. I tried fixing it locally by tweaking the node_modules folder and got the following error.

Error [ERR_UNSUPPORTED_DIR_IMPORT]: Directory import '[...]/node_modules/@solana/spl-account-compression/dist/esm/generated' is not supported resolving ES modules imported from [...]/node_modules/@solana/spl-account-compression/dist/esm/index.js

So the possible solutions are:

  • Fix the exported ESM module in the @solana/spl-account-compression package. This will likely require a bundler like rollup in order to export a valid ESM module as the TypeScript compiler is purposefully not a full-blown bundler. This is what I use in the JS SDK to export valid ESM modules. That being said, @solana/spl-token does manage to export a valid ESM module using tsc only so maybe something to investigate here.
  • Stop exporting the ESM module in the @solana/spl-account-compression package. That way you can keep using the TypeScript compiler as-is and CJS modules can be imported in ESM modules.

Co-authored-by: Loris Leiva <loris.leiva@gmail.com>
packages/js/src/utils/readApiConnection.ts Show resolved Hide resolved
packages/js/src/utils/readApiConnection.ts Show resolved Hide resolved
compression: input.compression,

// TODO(jon): Read API doesn't return this info
collectionDetails: null,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a restriction from the Read API standpoint, we should definitely get grouping info from the API

packages/js/src/utils/readApiConnection.ts Show resolved Hide resolved
packages/js/src/utils/readApiConnection.ts Show resolved Hide resolved
@lorisleiva lorisleiva merged commit 16a3875 into metaplex-foundation:main May 3, 2023
@nickfrosty nickfrosty deleted the jw/compressed-nfts branch May 3, 2023 14:32
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants