Skip to content
This repository was archived by the owner on Aug 30, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/sdk.erc721.lazy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@thirdweb-dev/sdk](./sdk.md) &gt; [Erc721](./sdk.erc721.md) &gt; [lazy](./sdk.erc721.lazy.md)

## Erc721.lazy property

<b>Signature:</b>

```typescript
lazy: Erc721LazyMintable | undefined;
```
1 change: 1 addition & 0 deletions docs/sdk.erc721.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ await contract.nft.transfer(walletAddress, tokenId);
| --- | --- | --- | --- |
| [contractWrapper](./sdk.erc721.contractwrapper.md) | | ContractWrapper&lt;T&gt; | |
| [featureName](./sdk.erc721.featurename.md) | | "ERC721" | |
| [lazy](./sdk.erc721.lazy.md) | | Erc721LazyMintable \| undefined | |
| [mint](./sdk.erc721.mint.md) | | [Erc721Mintable](./sdk.erc721mintable.md) \| undefined | |
| [options](./sdk.erc721.options.md) | | [SDKOptions](./sdk.sdkoptions.md) | |
| [query](./sdk.erc721.query.md) | | [Erc721Supply](./sdk.erc721supply.md) \| undefined | |
Expand Down
2 changes: 1 addition & 1 deletion docs/sdk.nftdrop.createbatch.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ createBatch(metadatas: NFTMetadataInput[], options?: {
| Parameter | Type | Description |
| --- | --- | --- |
| metadatas | [NFTMetadataInput](./sdk.nftmetadatainput.md)<!-- -->\[\] | The metadata to include in the batch. |
| options | { onProgress: (event: [UploadProgressEvent](./sdk.uploadprogressevent.md)<!-- -->) =&gt; void; } | <i>(Optional)</i> |
| options | { onProgress: (event: [UploadProgressEvent](./sdk.uploadprogressevent.md)<!-- -->) =&gt; void; } | <i>(Optional)</i> optional upload progress callback |

<b>Returns:</b>

Expand Down
6 changes: 5 additions & 1 deletion etc/sdk.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1698,8 +1698,12 @@ export class Erc721<T extends Multiwrap_2 | SignatureDrop_2 | DropERC721 | Token
// (undocumented)
getAddress(): string;
// @internal (undocumented)
protected getTokenMetadata(tokenId: BigNumberish): Promise<NFTMetadata>;
getTokenMetadata(tokenId: BigNumberish): Promise<NFTMetadata>;
isApproved(address: string, operator: string): Promise<boolean>;
// Warning: (ae-forgotten-export) The symbol "Erc721LazyMintable" needs to be exported by the entry point index.d.ts
//
// (undocumented)
lazy: Erc721LazyMintable | undefined;
// (undocumented)
mint: Erc721Mintable | undefined;
// @internal (undocumented)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
"ethers": "^5"
},
"dependencies": {
"@thirdweb-dev/contracts": "2.3.9",
"@thirdweb-dev/contracts": "2.3.12",
"@web-std/file": "^3.0.0",
"cbor": "^8.1.0",
"cross-fetch": "^3.1.5",
Expand Down
3 changes: 3 additions & 0 deletions src/common/claim-conditions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,9 @@ export async function getClaimerProofs(
/**
* Create and uploads snapshots + converts claim conditions to contract format
* @param claimConditionInputs
* @param tokenDecimals
* @param provider
* @param storage
* @internal
*/
export async function processClaimConditionInputs(
Expand Down
1 change: 1 addition & 0 deletions src/common/snapshots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { BigNumber, BigNumberish, utils } from "ethers";
/**
* Create a snapshot (merkle tree) from a list of addresses and uploads it to IPFS
* @param snapshotInput - the list of addresses to hash
* @param tokenDecimals - the token decimals
* @param storage - the storage to upload to
* @returns the generated snapshot and URI
* @internal
Expand Down
2 changes: 2 additions & 0 deletions src/constants/contract-features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
FEATURE_NFT,
FEATURE_NFT_BATCH_MINTABLE,
FEATURE_NFT_ENUMERABLE,
FEATURE_NFT_LAZY_MINTABLE,
FEATURE_NFT_MINTABLE,
FEATURE_NFT_SUPPLY,
} from "./erc721-features";
Expand Down Expand Up @@ -36,6 +37,7 @@ export type Feature =
| typeof FEATURE_NFT_ENUMERABLE
| typeof FEATURE_NFT_MINTABLE
| typeof FEATURE_NFT_BATCH_MINTABLE
| typeof FEATURE_NFT_LAZY_MINTABLE
| typeof FEATURE_EDITION
| typeof FEATURE_EDITION_ENUMERABLE
| typeof FEATURE_EDITION_MINTABLE
Expand Down
13 changes: 13 additions & 0 deletions src/constants/erc721-features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@ import Erc721EnumerableAbi from "../../abis/IERC721Enumerable.json";
import Erc721SupplyAbi from "../../abis/IERC721Supply.json";
import IMintableERC721Abi from "../../abis/IMintableERC721.json";
import MulticallAbi from "../../abis/IMulticall.json";
import LazyMintERC721Abi from "../../abis/LazyMintERC721.json";

export const FEATURE_NFT_LAZY_MINTABLE = {
name: "ERC721LazyMintable",
namespace: "nft.lazyMint",
docLinks: {
sdk: "sdk.erc721lazymintable",
contracts: "LazyMintERC721",
},
abis: [Erc721Abi, LazyMintERC721Abi],
features: {},
} as const;

export const FEATURE_NFT_BATCH_MINTABLE = {
name: "ERC721BatchMintable",
Expand Down Expand Up @@ -63,5 +75,6 @@ export const FEATURE_NFT = {
features: {
[FEATURE_NFT_SUPPLY.name]: FEATURE_NFT_SUPPLY,
[FEATURE_NFT_MINTABLE.name]: FEATURE_NFT_MINTABLE,
[FEATURE_NFT_LAZY_MINTABLE.name]: FEATURE_NFT_LAZY_MINTABLE,
},
} as const;
1 change: 1 addition & 0 deletions src/contracts/nft-drop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ export class NFTDrop extends Erc721<DropERC721> {
* ```
*
* @param metadatas - The metadata to include in the batch.
* @param options - optional upload progress callback
*/
public async createBatch(
metadatas: NFTMetadataInput[],
Expand Down
6 changes: 1 addition & 5 deletions src/core/classes/drop-claim-conditions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,11 +362,7 @@ export class DropClaimConditions<
this.isSignatureDrop(this.contractWrapper.readContract, contractType)
? this.contractWrapper.readContract.interface.encodeFunctionData(
"setClaimConditions",
[
sortedConditions,
resetClaimEligibilityForAll,
ethers.utils.toUtf8Bytes(""),
],
[sortedConditions, resetClaimEligibilityForAll],
)
: this.contractWrapper.readContract.interface.encodeFunctionData(
"setClaimConditions",
Expand Down
105 changes: 105 additions & 0 deletions src/core/classes/erc-721-lazy-mintable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { ContractWrapper } from "./contract-wrapper";
import { LazyMintERC721 } from "contracts";
import { CommonNFTInput, NFTMetadata, NFTMetadataInput } from "../../schema";
import { TransactionResultWithId } from "../types";
import { IStorage } from "../interfaces";
import { Erc721 } from "./erc-721";
import { Erc721BatchMintable } from "./erc-721-batch-mintable";
import { FEATURE_NFT_LAZY_MINTABLE } from "../../constants/erc721-features";
import { DetectableFeature } from "../interfaces/DetectableFeature";
import { UploadProgressEvent } from "../../types";
import { ethers } from "ethers";
import { TokensLazyMintedEvent } from "contracts/LazyMintERC721";

/**
* LazyMint ERC721 NFTs
* @remarks NFT lazy minting functionality that handles IPFS batch uploads for you.
* @example
* ```javascript
* const contract = await sdk.getContract("{{contract_address}}");
* await contract.nft.lazy.mint(walletAddress, nftMetadata);
* ```
* @public
*/
export class Erc721LazyMintable implements DetectableFeature {
featureName = FEATURE_NFT_LAZY_MINTABLE.name;
private contractWrapper: ContractWrapper<LazyMintERC721>;
private storage: IStorage;
private erc721: Erc721;

public batch: Erc721BatchMintable | undefined;

constructor(
erc721: Erc721,
contractWrapper: ContractWrapper<LazyMintERC721>,
storage: IStorage,
) {
this.erc721 = erc721;
this.contractWrapper = contractWrapper;
this.storage = storage;
}

/**
* Create a batch of unique NFTs to be claimed in the future
*
* @remarks Create batch allows you to create a batch of many unique NFTs in one transaction.
*
* @example
* ```javascript
* // Custom metadata of the NFTs to create
* const metadatas = [{
* name: "Cool NFT",
* description: "This is a cool NFT",
* image: fs.readFileSync("path/to/image.png"), // This can be an image url or file
* }, {
* name: "Cool NFT",
* description: "This is a cool NFT",
* image: fs.readFileSync("path/to/image.png"),
* }];
*
* const results = await contract.nft.lazy.mint(metadatas); // uploads and creates the NFTs on chain
* const firstTokenId = results[0].id; // token id of the first created NFT
* const firstNFT = await results[0].data(); // (optional) fetch details of the first created NFT
* ```
*
* @param metadatas - The metadata to include in the batch.
* @param options - optional upload progress callback
*/
public async mint(
metadatas: NFTMetadataInput[],
options?: {
onProgress: (event: UploadProgressEvent) => void;
},
): Promise<TransactionResultWithId<NFTMetadata>[]> {
const startFileNumber =
await this.contractWrapper.readContract.nextTokenIdToMint();
const batch = await this.storage.uploadMetadataBatch(
metadatas.map((m) => CommonNFTInput.parse(m)),
startFileNumber.toNumber(),
this.contractWrapper.readContract.address,
await this.contractWrapper.getSigner()?.getAddress(),
options,
);
const baseUri = batch.baseUri;
const receipt = await this.contractWrapper.sendTransaction("lazyMint", [
batch.uris.length,
baseUri.endsWith("/") ? baseUri : `${baseUri}/`,
ethers.utils.toUtf8Bytes(""),
]);
const event = this.contractWrapper.parseLogs<TokensLazyMintedEvent>(
"TokensLazyMinted",
receipt?.logs,
);
const startingIndex = event[0].args.startTokenId;
const endingIndex = event[0].args.endTokenId;
const results = [];
for (let id = startingIndex; id.lte(endingIndex); id = id.add(1)) {
results.push({
id,
receipt,
data: () => this.erc721.getTokenMetadata(id),
});
}
return results;
}
}
20 changes: 17 additions & 3 deletions src/core/classes/erc-721.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
DropERC721,
IERC721Supply,
IMintableERC721,
LazyMintERC721,
Multiwrap,
SignatureDrop,
TokenERC721,
Expand All @@ -24,6 +25,7 @@ import { Erc721Mintable } from "./erc-721-mintable";
import { BaseERC721 } from "../../types/eips";
import { FEATURE_NFT } from "../../constants/erc721-features";
import { DetectableFeature } from "../interfaces/DetectableFeature";
import { Erc721LazyMintable } from "./erc-721-lazy-mintable";

/**
* Standard ERC721 NFT functions
Expand Down Expand Up @@ -51,6 +53,7 @@ export class Erc721<

public query: Erc721Supply | undefined;
public mint: Erc721Mintable | undefined;
public lazy: Erc721LazyMintable | undefined;

constructor(
contractWrapper: ContractWrapper<T>,
Expand All @@ -70,6 +73,7 @@ export class Erc721<
}
this.query = this.detectErc721Enumerable();
this.mint = this.detectErc721Mintable();
this.lazy = this.detectErc721LazyMintable();
}

/**
Expand Down Expand Up @@ -225,9 +229,7 @@ export class Erc721<
/**
* @internal
*/
protected async getTokenMetadata(
tokenId: BigNumberish,
): Promise<NFTMetadata> {
async getTokenMetadata(tokenId: BigNumberish): Promise<NFTMetadata> {
const tokenUri = await this.contractWrapper.readContract.tokenURI(tokenId);
if (!tokenUri) {
throw new NotFoundError();
Expand Down Expand Up @@ -259,4 +261,16 @@ export class Erc721<
}
return undefined;
}

private detectErc721LazyMintable(): Erc721LazyMintable | undefined {
if (
detectContractFeature<LazyMintERC721>(
this.contractWrapper,
"ERC721LazyMintable",
)
) {
return new Erc721LazyMintable(this, this.contractWrapper, this.storage);
}
return undefined;
}
}
3 changes: 2 additions & 1 deletion src/core/classes/ipfs-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,9 @@ export class IpfsStorage implements IStorage {
*
* @internal
*
* @param metadata - The metadata to recursively process
* @returns - The processed metadata with properties pointing at ipfs in place of `File | Buffer`
* @param metadatas
* @param options
*/
private async batchUploadProperties(
metadatas: JsonObject[],
Expand Down
29 changes: 29 additions & 0 deletions test/custom.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { signers } from "./before-setup";
import { expect } from "chai";
import invariant from "tiny-invariant";
import {
DropERC721__factory,
SignatureDrop__factory,
TokenERC1155__factory,
TokenERC20__factory,
TokenERC721__factory,
Expand All @@ -18,6 +20,7 @@ global.fetch = require("cross-fetch");
describe("Custom Contracts", async () => {
let customContractAddress: string;
let nftContractAddress: string;
let sigDropContractAddress: string;
let tokenContractAddress: string;
let editionContractAddress: string;
let adminWallet: SignerWithAddress,
Expand Down Expand Up @@ -71,6 +74,10 @@ describe("Custom Contracts", async () => {
platform_fee_basis_points: 10,
platform_fee_recipient: adminWallet.address,
});
sigDropContractAddress = await sdk.deployer.deploySignatureDrop({
name: "sigdrop",
primary_sale_recipient: adminWallet.address,
});
});

it("should call raw ABI functions and read deployer address", async () => {
Expand Down Expand Up @@ -224,6 +231,28 @@ describe("Custom Contracts", async () => {
expect(nfts[0].metadata.name).to.eq("Custom NFT");
});

it("should detect feature: erc721 lazy mint", async () => {
const c = await sdk.getContractFromAbi(
sigDropContractAddress,
SignatureDrop__factory.abi,
);
invariant(c, "Contract undefined");
invariant(c.nft, "ERC721 undefined");
invariant(c.nft.query, "ERC721 query undefined");
invariant(c.nft.lazy, "ERC721 lazy undefined");
await c.nft.lazy.mint([
{
name: "Custom NFT",
},
{
name: "Another one",
},
]);
const nfts = await c.nft.query.all();
expect(nfts.length).to.eq(2);
expect(nfts[0].metadata.name).to.eq("Custom NFT");
});

it("should detect feature: erc1155", async () => {
const c = await sdk.getContractFromAbi(
editionContractAddress,
Expand Down
22 changes: 22 additions & 0 deletions test/publisher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,4 +220,26 @@ describe("Publishing", async () => {
});
expect(tx).to.not.eq(undefined);
});

it("Custom drop contract lazy mint", async () => {
const realSDK = new ThirdwebSDK(adminWallet);
const pub = await realSDK.getPublisher();
const ipfsUri = "ipfs://QmfKR3MMsE8AtXnoDZPHj7Z9SdNkyDTVhHEd1D9cDHDn1o/0";
const addr = await pub.deployContract(ipfsUri, []);
const c = await sdk.getContract(addr);
invariant(c.nft, "no nft detected");
invariant(c.nft.query, "no query detected");
invariant(c.nft.lazy, "no lazy detected");
const tx = await c.nft.lazy.mint([
{
name: "cool nft",
},
{
name: "cool nft2",
},
]);
expect(tx).to.not.eq(undefined);
const all = await c.nft.query.all();
expect(all).length(2);
});
});
Loading