@infinixjoyd/metaplex-auth-service
Version:
A client library for nft.storage designed for metaplex NFT uploads
247 lines (246 loc) • 12.8 kB
JavaScript
import { makeMetaplexUploadToken, MetaplexAuthWithSecretKey, MetaplexAuthWithSigner, } from './auth.js';
import { NFTStorage } from 'nft.storage';
import { loadNFTFromFilesystem } from './nft/index.js';
import { isBrowser } from './utils.js';
const DEFAULT_ENDPOINT = new URL('https://api.nft.storage');
/**
* A bespoke client for [NFT.Storage](https://nft.storage) that uses Solana private keys
* to authenticate uploads of NFT assets and metadata for Metaplex NFT creators.
*
* This client uses a metaplex-specific endpoint (https://api.nft.storage/metaplex/upload)
* that requires a request-specific JWT token. See SPEC.md in this repo for more details.
*
*/
export class NFTStorageMetaplexor {
constructor({ auth, endpoint }) {
this.auth = auth;
this.endpoint = endpoint || DEFAULT_ENDPOINT;
}
// Overrides the default NFTStorage.auth function to set
// an 'x-web3auth' header instead of 'Authorization'.
// Must be called before calling NFTStorage.storeCar
static init() {
if (this._initialized) {
return;
}
// @ts-ignore
NFTStorage.auth = (token) => ({
'x-web3auth': `Metaplex ${token}`,
});
this._initialized = true;
}
/**
* Creates a new instance of NFTStorageMetaplexor using the given secret signing key.
*
* @param key - an Ed25519 private key
* @param opts
* @param opts.mintingAgent - the "user agent" or tool used to prepare the upload. See {@link TagMintingAgent} for details.
* @param opts.agentVersion - an optional version of the `mintingAgent`. See {@link TagMintingAgentVersion} for details.
* @param opts.solanaCluster - the Solana cluster that the uploaded NFTs are to be minted on. defaults to 'devnet' if not provided.
* @param opts.endpoint - the URL of the NFT.Storage API. defaults to 'https://api.nft.storage' if not provided.
* @returns
*/
static withSecretKey(key, opts) {
const { solanaCluster, mintingAgent, agentVersion, endpoint } = opts;
const auth = MetaplexAuthWithSecretKey(key, {
solanaCluster,
mintingAgent,
agentVersion,
});
return new NFTStorageMetaplexor({ auth, endpoint });
}
/**
* Creates a new instance of NFTStorageMetaplexor using the given `Signer`, which is a function that accepts a
* `Uint8Array` to be signed and returns a `Promise<Uint8Array>` containing the signature. The `Signer` type is
* compatible with the `signMessage` method of
* [Solana wallet adapters](https://github.com/solana-labs/wallet-adapter) that support signing arbitrary
* messages.
*
* @param signMessage - a function that asynchronously returns a signature of an input message
* @param publicKey - the public key that can validate signatures produced by the signer
* @param opts
* @param opts.mintingAgent - the "user agent" or tool used to prepare the upload. See {@link TagMintingAgent} for details.
* @param opts.agentVersion - an optional version of the `mintingAgent`. See {@link TagMintingAgentVersion} for details.
* @param opts.solanaCluster - the Solana cluster that the uploaded NFTs are to be minted on. defaults to 'devnet' if not provided.
* @param opts.endpoint - the URL of the NFT.Storage API. defaults to 'https://api.nft.storage' if not provided.
* @returns
*/
static withSigner(signMessage, publicKey, opts) {
const { solanaCluster, mintingAgent, agentVersion, endpoint } = opts;
const auth = MetaplexAuthWithSigner(signMessage, publicKey, {
solanaCluster,
mintingAgent,
agentVersion,
});
return new NFTStorageMetaplexor({ auth, endpoint });
}
/**
* Stores a single Blob (or File) with NFT.Storage, without wrapping in a directory listing.
* If a File is provided, any filenames will be ignored and will not be preserved on IPFS.
*
* @param context information required to authenticate uploads
* @param blob a Blob or File object to store
* @returns CID string for the stored content
*/
static async storeBlob(context, public_key, blob) {
this.init();
const { cid, car } = await NFTStorage.encodeBlob(blob);
return this.storeCar(context, public_key, cid, car);
}
/**
* Stores one or more files with NFT.Storage, bundling them into an IPFS directory.
*
* If the `files` contain directory paths in their `name`s, they MUST all share the same
* parent directory. E.g. 'foo/hello.txt' and 'foo/thing.json' is fine,
* but 'foo/hello.txt' and 'bar/thing.json' will fail.
*
* @param context information required to authenticate uploads
* @param files an iterable of File objects to be uploaded
* @returns CID string of the IPFS directory containing all uploaded files.
*/
static async storeDirectory(context, public_key, files) {
this.init();
const { cid, car } = await NFTStorage.encodeDirectory(files);
return this.storeCar(context, public_key, cid, car);
}
/**
* Stores a Content Archive (CAR) containing content addressed data.
*
* @param context information required to authenticate uploads
* @param cid the root CID of the CAR.
* @param car a CarReader that supplies CAR data. Must have a single root CID that matches the `cid` param.
* @param opts options to pass through to NFTStorage.storeCar
* @returns a Promise that resolves to the uploaded CID, as a CIDv1 string.
*/
static async storeCar(context, public_key, cid, car, opts) {
this.init();
const { auth } = context;
const baseEndpoint = context.endpoint || DEFAULT_ENDPOINT;
// NFTStorage.storeCar adds `/upload` to the base endpoint url.
// We want our request to go to `/metaplex/upload`, so we add the
// `/metaplex/` prefix here.
const endpoint = new URL('/metaplex/', baseEndpoint);
const token = await makeMetaplexUploadToken(auth, public_key, cid.toString());
return NFTStorage.storeCar({ endpoint, token }, car, opts);
}
/**
* Stores a {@link PackagedNFT} object with NFT.Storage.
*
* Uploads the CARs contained in the PackagedNFT object and returns an
* object containing the root CID of each CAR and URLs to the uploaded
* NFT metadata.
*
* See {@link prepareMetaplexNFT} for creating PackagedNFT instances from
* File objects, or {@link loadNFTFromFilesystem} for loading from disk (node.js only).
*
* @param context information required to authenticate uploads
* @param nft a {@link PackagedNFT} object containing NFT assets and metadata
* @param opts options to pass through to NFTStorage.storeCar
* @returns a {@link StoreNFTResult} object containing the CIDs and URLs for the stored NFT
*/
static async storePreparedNFT(context, public_key, nft, opts) {
this.init();
const metadataRootCID = await this.storeCar(context, public_key, nft.encodedMetadata.cid, nft.encodedMetadata.car, opts);
const assetRootCID = await this.storeCar(context, public_key, nft.encodedAssets.cid, nft.encodedAssets.car, opts);
const { metadataGatewayURL, metadataURI } = nft;
return {
metadataRootCID,
assetRootCID,
metadataGatewayURL,
metadataURI,
metadata: nft.metadata,
};
}
/**
* Loads an NFT from disk and stores it with NFT.Storage. Node.js only!
*
* Uses {@link loadNFTFromFilesystem} to load NFT data and stores with
* {@link storePreparedNFT}.
*
* @param context information required to authenticate uploads
* @param metadataFilePath path to metadata.json file
* @param imageFilePath optional path to image file. If not provided, the image will be located using the heuristics described in {@link loadNFTFromFilesystem}.
* @param opts
* @param opts.validateSchema if true, validate the metadata against a JSON schema before processing. off by default
* @param opts.gatewayHost the hostname of an IPFS HTTP gateway to use in metadata links. Defaults to "nftstorage.link" if not set.
* @param opts.storeCarOptions options to pass through to NFTStorage.storeCar
* @returns a {@link StoreNFTResult} object containing the CIDs and URLs for the stored NFT
*/
static async storeNFTFromFilesystem(context, public_key, metadataFilePath, imageFilePath, opts = {}) {
if (isBrowser) {
throw new Error(`storeNFTFromFilesystem is only available on node.js`);
}
const nft = await loadNFTFromFilesystem(metadataFilePath, imageFilePath, opts);
return this.storePreparedNFT(context, public_key, nft, opts.storeCarOptions);
}
// -- instance methods are just "sugar" around the static methods, using `this` as the ServiceContext parameter
/**
* Stores a single Blob (or File) with NFT.Storage, without wrapping in a directory listing.
* If a File is provided, any filenames will be ignored and will not be preserved on IPFS.
*
* @param blob a Blob or File object to store
* @returns CID string for the stored content
*/
async storeBlob(blob) {
const { cid, car } = await NFTStorage.encodeBlob(blob);
return NFTStorageMetaplexor.storeCar(this, this.auth.publicKey, cid, car);
}
/**
* Stores a Content Archive (CAR) containing content addressed data.
*
* @param cid the root CID of the CAR.
* @param car a CarReader that supplies CAR data. Must have a single root CID that matches the `cid` param.
* @param opts options to pass through to NFTStorage.storeCar
* @returns a Promise that resolves to the uploaded CID, as a CIDv1 string.
*/
async storeCar(cid, car, opts) {
return NFTStorageMetaplexor.storeCar(this, this.auth.publicKey, cid, car, opts);
}
/**
* Stores one or more files with NFT.Storage, bundling them into an IPFS directory.
*
* If the `files` contain directory paths in their `name`s, they MUST all share the same
* parent directory. E.g. 'foo/hello.txt' and 'foo/thing.json' is fine,
* but 'foo/hello.txt' and 'bar/thing.json' will fail.
*
* @param files an iterable of File objects to be uploaded
* @returns CID string of the IPFS directory containing all uploaded files.
*/
async storeDirectory(files) {
return NFTStorageMetaplexor.storeDirectory(this, this.auth.publicKey, files);
}
/**
* Stores a {@link PackagedNFT} object with NFT.Storage.
*
* Uploads the CARs contained in the PackagedNFT object and returns an
* object containing the root CID of each CAR and URLs to the uploaded
* NFT metadata.
*
* See {@link prepareMetaplexNFT} for creating PackagedNFT instances from
* File objects, or {@link loadNFTFromFilesystem} for loading from disk (node.js only).
*
* @param nft a {@link PackagedNFT} object containing NFT assets and metadata
* @param opts options to pass through to NFTStorage.storeCar
* @returns a {@link StoreNFTResult} object containing the CIDs and URLs for the stored NFT
*/
async storePreparedNFT(public_key, nft, opts) {
return NFTStorageMetaplexor.storePreparedNFT(this, public_key, nft, opts);
}
/**
* Loads an NFT from disk and stores it with NFT.Storage. Node.js only!
*
* Uses {@link loadNFTFromFilesystem} to load NFT data and stores with
* {@link storePreparedNFT}.
*
* @param metadataFilePath path to metadata.json file
* @param imageFilePath optional path to image file. If not provided, the image will be located using the heuristics described in {@link loadNFTFromFilesystem}.
* @param opts
* @param opts.validateSchema if true, validate the metadata against a JSON schema before processing. off by default
* @param opts.gatewayHost the hostname of an IPFS HTTP gateway to use in metadata links. Defaults to "nftstorage.link" if not set.
* @param opts.storeCarOptions options to pass through to NFTStorage.storeCar
* @returns a {@link StoreNFTResult} object containing the CIDs and URLs for the stored NFT
*/
async storeNFTFromFilesystem(public_key, metadataFilePath, imageFilePath, opts = {}) {
return NFTStorageMetaplexor.storeNFTFromFilesystem(this, public_key, metadataFilePath, imageFilePath, opts);
}
}