Skip to content

Commit 1b2bfc8

Browse files
feat: add ERC20 and ERC1155 signature mint extensions (#2811)
Co-authored-by: Jonas Daniels <jonas.daniels@outlook.com>
1 parent 576bb57 commit 1b2bfc8

File tree

26 files changed

+810
-88
lines changed

26 files changed

+810
-88
lines changed

.changeset/slimy-coats-crash.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": minor
3+
---
4+
5+
Added ERC1155 and ERC20 signature mint extensions
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[
22
"event TokensMintedWithSignature(address indexed signer, address indexed mintedTo, uint256 indexed tokenIdMinted, (address to, address royaltyRecipient, uint256 royaltyBps, address primarySaleRecipient, uint256 tokenId, string uri, uint256 quantity, uint256 pricePerToken, address currency, uint128 validityStartTimestamp, uint128 validityEndTimestamp, bytes32 uid) mintRequest)",
3-
"function mintWithSignature((address to, address royaltyRecipient, uint256 royaltyBps, address primarySaleRecipient, uint256 tokenId, string uri, uint256 quantity, uint256 pricePerToken, address currency, uint128 validityStartTimestamp, uint128 validityEndTimestamp, bytes32 uid) req, bytes signature) payable returns (address signer)",
3+
"function mintWithSignature((address to, address royaltyRecipient, uint256 royaltyBps, address primarySaleRecipient, uint256 tokenId, string uri, uint256 quantity, uint256 pricePerToken, address currency, uint128 validityStartTimestamp, uint128 validityEndTimestamp, bytes32 uid) payload, bytes signature) payable returns (address signer)",
44
"function verify((address to, address royaltyRecipient, uint256 royaltyBps, address primarySaleRecipient, uint256 tokenId, string uri, uint256 quantity, uint256 pricePerToken, address currency, uint128 validityStartTimestamp, uint128 validityEndTimestamp, bytes32 uid) req, bytes signature) view returns (bool success, address signer)"
55
]
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[
22
"event TokensMintedWithSignature(address indexed signer, address indexed mintedTo, (address to, address primarySaleRecipient, uint256 quantity, uint256 price, address currency, uint128 validityStartTimestamp, uint128 validityEndTimestamp, bytes32 uid) mintRequest)",
3-
"function mintWithSignature((address to, address primarySaleRecipient, uint256 quantity, uint256 price, address currency, uint128 validityStartTimestamp, uint128 validityEndTimestamp, bytes32 uid) req, bytes signature) payable returns (address signer)",
3+
"function mintWithSignature((address to, address primarySaleRecipient, uint256 quantity, uint256 price, address currency, uint128 validityStartTimestamp, uint128 validityEndTimestamp, bytes32 uid) payload, bytes signature) payable returns (address signer)",
44
"function verify((address to, address primarySaleRecipient, uint256 quantity, uint256 price, address currency, uint128 validityStartTimestamp, uint128 validityEndTimestamp, bytes32 uid) req, bytes signature) view returns (bool success, address signer)"
55
]

packages/thirdweb/src/exports/extensions/erc1155.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,15 @@ export {
103103
type SetClaimConditionsParams,
104104
} from "../../extensions/erc1155/drops/write/setClaimConditions.js";
105105

106+
/**
107+
* SIGNATURE extension for ERC1155
108+
*/
109+
export {
110+
mintWithSignature,
111+
type GenerateMintSignatureOptions,
112+
generateMintSignature,
113+
} from "../../extensions/erc1155/write/sigMint.js";
114+
106115
// EVENTS
107116
export { tokensLazyMintedEvent } from "../../extensions/erc1155/__generated__/ILazyMint/events/TokensLazyMinted.js";
108117
export { tokensClaimedEvent } from "../../extensions/erc1155/__generated__/IDrop1155/events/TokensClaimed.js";

packages/thirdweb/src/exports/extensions/erc20.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,15 @@ export {
7878
type SetClaimConditionsParams,
7979
} from "../../extensions/erc20/drops/write/setClaimConditions.js";
8080

81+
/**
82+
* SIGNATURE extension for ERC20
83+
*/
84+
export {
85+
mintWithSignature,
86+
type GenerateMintSignatureOptions,
87+
generateMintSignature,
88+
} from "../../extensions/erc20/write/sigMint.js";
89+
8190
// ----------------------------
8291
// WETH
8392
// ----------------------------

packages/thirdweb/src/extensions/erc1155/__generated__/ISignatureMintERC1155/write/mintWithSignature.ts

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/thirdweb/src/extensions/erc1155/drop1155.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
TEST_ACCOUNT_B,
88
} from "../../../test/src/test-wallets.js";
99
import { type ThirdwebContract, getContract } from "../../contract/contract.js";
10-
import { sendAndConfirmTransaction } from "../../exports/transaction.js";
10+
import { sendAndConfirmTransaction } from "../../transaction/actions/send-and-confirm-transaction.js";
1111
import { getContractMetadata } from "../common/read/getContractMetadata.js";
1212
import { deployERC1155Contract } from "../prebuilts/deploy-erc1155.js";
1313
import { balanceOf } from "./__generated__/IERC1155/read/balanceOf.js";

packages/thirdweb/src/extensions/erc1155/token1155.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
TEST_ACCOUNT_B,
77
} from "../../../test/src/test-wallets.js";
88
import { type ThirdwebContract, getContract } from "../../contract/contract.js";
9-
import { sendAndConfirmTransaction } from "../../exports/transaction.js";
9+
import { sendAndConfirmTransaction } from "../../transaction/actions/send-and-confirm-transaction.js";
1010
import { getContractMetadata } from "../common/read/getContractMetadata.js";
1111
import { deployERC1155Contract } from "../prebuilts/deploy-erc1155.js";
1212
import { balanceOf } from "./__generated__/IERC1155/read/balanceOf.js";
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
import type { AbiParameterToPrimitiveType, Address } from "abitype";
2+
import { maxUint256 } from "viem";
3+
import { NATIVE_TOKEN_ADDRESS } from "../../../constants/addresses.js";
4+
import type { ThirdwebContract } from "../../../contract/contract.js";
5+
import { toBigInt } from "../../../utils/bigint.js";
6+
import { dateToSeconds, tenYearsFromNow } from "../../../utils/date.js";
7+
import type { Hex } from "../../../utils/encoding/hex.js";
8+
import type { NFTInput } from "../../../utils/nft/parseNft.js";
9+
import { randomBytes32 } from "../../../utils/uuid.js";
10+
import type { Account } from "../../../wallets/interfaces/wallet.js";
11+
import { mintWithSignature as generatedMintWithSignature } from "../__generated__/ISignatureMintERC1155/write/mintWithSignature.js";
12+
13+
/**
14+
* Mints a new ERC1155 token with the given minter signature
15+
* @param options - The transaction options.
16+
* @example
17+
* ```ts
18+
* import { mintWithSignature, generateMintSignature } from "thirdweb/extensions/erc1155";
19+
*
20+
* const { payload, signature } = await generateMintSignature(...)
21+
*
22+
* const transaction = mintWithSignature({
23+
* contract,
24+
* payload,
25+
* signature,
26+
* });
27+
* await sendTransaction({ transaction, account });
28+
* ```
29+
* @extension ERC1155
30+
* @returns A promise that resolves to the transaction result.
31+
*/
32+
export const mintWithSignature = generatedMintWithSignature;
33+
34+
export type GenerateMintSignatureOptions = {
35+
account: Account;
36+
contract: ThirdwebContract;
37+
mintRequest: GeneratePayloadInput;
38+
};
39+
40+
/**
41+
* Generates the payload and signature for minting an ERC1155 token.
42+
* @param options - The options for the minting process.
43+
* @example
44+
* ```ts
45+
* import { mintWithSignature, generateMintSignature } from "thirdweb/extensions/erc1155";
46+
*
47+
* const { payload, signature } = await generateMintSignature({
48+
* account,
49+
* contract,
50+
* mintRequest: {
51+
* to: "0x...",
52+
* quantity: 10n,
53+
* metadata: {
54+
* name: "My NFT",
55+
* description: "This is my NFT",
56+
* image: "https://example.com/image.png",
57+
* },
58+
* },
59+
* });
60+
*
61+
* const transaction = mintWithSignature({
62+
* contract,
63+
* payload,
64+
* signature,
65+
* });
66+
* await sendTransaction({ transaction, account });
67+
* ```
68+
* @extension ERC1155
69+
* @returns A promise that resolves to the payload and signature.
70+
*/
71+
export async function generateMintSignature(
72+
options: GenerateMintSignatureOptions,
73+
) {
74+
const { mintRequest, account, contract } = options;
75+
const currency = mintRequest.currency || NATIVE_TOKEN_ADDRESS;
76+
const [pricePerToken, uri, uid] = await Promise.all([
77+
// price per token in wei
78+
(async () => {
79+
// if priceInWei is provided, use it
80+
if ("pricePerTokenWei" in mintRequest && mintRequest.pricePerTokenWei) {
81+
return mintRequest.pricePerTokenWei;
82+
}
83+
// if price is provided, convert it to wei
84+
if ("pricePerToken" in mintRequest && mintRequest.pricePerToken) {
85+
const { convertErc20Amount } = await import(
86+
"../../../utils/extensions/convert-erc20-amount.js"
87+
);
88+
return await convertErc20Amount({
89+
amount: mintRequest.pricePerToken,
90+
client: contract.client,
91+
chain: contract.chain,
92+
erc20Address: currency,
93+
});
94+
}
95+
// if neither price nor priceInWei is provided, default to 0
96+
return 0n;
97+
})(),
98+
// uri
99+
(async () => {
100+
if ("metadata" in mintRequest) {
101+
if (typeof mintRequest.metadata === "object") {
102+
// async import the upload function because it is not always required
103+
const { upload } = await import("../../../storage/upload.js");
104+
return await upload({
105+
client: options.contract.client,
106+
files: [mintRequest.metadata],
107+
});
108+
}
109+
return mintRequest.metadata;
110+
}
111+
return "";
112+
})(),
113+
// uid computation
114+
mintRequest.uid || (await randomBytes32()),
115+
]);
116+
117+
const startTime = mintRequest.validityStartTimestamp || new Date(0);
118+
const endTime = mintRequest.validityEndTimestamp || tenYearsFromNow();
119+
120+
const payload: PayloadType = {
121+
uri,
122+
currency,
123+
uid,
124+
pricePerToken,
125+
tokenId:
126+
"tokenId" in mintRequest ? mintRequest.tokenId || maxUint256 : maxUint256,
127+
quantity: mintRequest.quantity,
128+
to: mintRequest.to,
129+
royaltyRecipient: mintRequest.royaltyRecipient || account.address,
130+
royaltyBps: toBigInt(mintRequest.royaltyBps || 0),
131+
primarySaleRecipient: mintRequest.primarySaleRecipient || account.address,
132+
validityStartTimestamp: dateToSeconds(startTime),
133+
validityEndTimestamp: dateToSeconds(endTime),
134+
};
135+
136+
const signature = await account.signTypedData({
137+
domain: {
138+
name: "TokenERC1155",
139+
version: "1",
140+
chainId: contract.chain.id,
141+
verifyingContract: contract.address as Hex,
142+
},
143+
types: { MintRequest: MintRequest1155 },
144+
primaryType: "MintRequest",
145+
message: payload,
146+
});
147+
return { payload, signature };
148+
}
149+
150+
type PayloadType = AbiParameterToPrimitiveType<{
151+
type: "tuple";
152+
name: "payload";
153+
components: typeof MintRequest1155;
154+
}>;
155+
156+
type GeneratePayloadInput = {
157+
to: string;
158+
quantity: bigint;
159+
royaltyRecipient?: Address;
160+
royaltyBps?: number;
161+
primarySaleRecipient?: Address;
162+
pricePerToken?: string;
163+
pricePerTokenWei?: bigint;
164+
currency?: Address;
165+
validityStartTimestamp?: Date;
166+
validityEndTimestamp?: Date;
167+
uid?: Hex;
168+
} & (
169+
| {
170+
metadata: NFTInput | string;
171+
}
172+
| { tokenId: bigint }
173+
);
174+
175+
const MintRequest1155 = [
176+
{ name: "to", type: "address" },
177+
{ name: "royaltyRecipient", type: "address" },
178+
{ name: "royaltyBps", type: "uint256" },
179+
{ name: "primarySaleRecipient", type: "address" },
180+
{ name: "tokenId", type: "uint256" },
181+
{ name: "uri", type: "string" },
182+
{ name: "quantity", type: "uint256" },
183+
{ name: "pricePerToken", type: "uint256" },
184+
{ name: "currency", type: "address" },
185+
{ name: "validityStartTimestamp", type: "uint128" },
186+
{ name: "validityEndTimestamp", type: "uint128" },
187+
{ name: "uid", type: "bytes32" },
188+
] as const;

0 commit comments

Comments
 (0)