diff --git a/.changeset/violet-houses-applaud.md b/.changeset/violet-houses-applaud.md new file mode 100644 index 00000000000..5aec5c86753 --- /dev/null +++ b/.changeset/violet-houses-applaud.md @@ -0,0 +1,21 @@ +--- +"thirdweb": patch +--- + +Add support for custom singlePhase drops + +If you are using a custom drop contract, you can now set claim conditions and claim by passing the `singlePhaseDrop` option to the `setClaimConditions` and `claimTo` functions. + +```ts +setClaimConditions({ + contract, + phases: [ + { + startTime: new Date(0), + maxClaimableSupply: 10n, + }, + ], + tokenId: 0n, + singlePhaseDrop: true, // <--- for custom drop contracts +}); +``` diff --git a/packages/thirdweb/scripts/generate/abis/erc1155/IDropSinglePhase1155.json b/packages/thirdweb/scripts/generate/abis/erc1155/IDropSinglePhase1155.json new file mode 100644 index 00000000000..e270d08a278 --- /dev/null +++ b/packages/thirdweb/scripts/generate/abis/erc1155/IDropSinglePhase1155.json @@ -0,0 +1,7 @@ +[ + "function claim(address receiver, uint256 tokenId, uint256 quantity, address currency, uint256 pricePerToken, (bytes32[] proof, uint256 quantityLimitPerWallet, uint256 pricePerToken, address currency) allowlistProof, bytes data) payable", + "function setClaimConditions(uint256 tokenId, (uint256 startTimestamp, uint256 maxClaimableSupply, uint256 supplyClaimed, uint256 quantityLimitPerWallet, bytes32 merkleRoot, uint256 pricePerToken, address currency, string metadata) phase, bool resetClaimEligibility)", + "function claimCondition(uint256 tokenId) view returns ((uint256 startTimestamp, uint256 maxClaimableSupply, uint256 supplyClaimed, uint256 quantityLimitPerWallet, bytes32 merkleRoot, uint256 pricePerToken, address currency, string metadata) condition)", + "event ClaimConditionUpdated(uint256 indexed tokenId, (uint256 startTimestamp, uint256 maxClaimableSupply, uint256 supplyClaimed, uint256 quantityLimitPerWallet, bytes32 merkleRoot, uint256 pricePerToken, address currency, string metadata) condition, bool resetEligibility)", + "event TokensClaimed(address indexed claimer, address indexed receiver, uint256 indexed tokenId, uint256 quantityClaimed)" +] \ No newline at end of file diff --git a/packages/thirdweb/scripts/generate/abis/erc721/IDropSinglePhase.json b/packages/thirdweb/scripts/generate/abis/erc721/IDropSinglePhase.json new file mode 100644 index 00000000000..94fcd72ef58 --- /dev/null +++ b/packages/thirdweb/scripts/generate/abis/erc721/IDropSinglePhase.json @@ -0,0 +1,7 @@ +[ + "function claim(address receiver, uint256 quantity, address currency, uint256 pricePerToken, (bytes32[] proof, uint256 quantityLimitPerWallet, uint256 pricePerToken, address currency) allowlistProof, bytes data) payable", + "function setClaimConditions((uint256 startTimestamp, uint256 maxClaimableSupply, uint256 supplyClaimed, uint256 quantityLimitPerWallet, bytes32 merkleRoot, uint256 pricePerToken, address currency, string metadata) phase, bool resetClaimEligibility)", + "function claimCondition() view returns ((uint256 startTimestamp, uint256 maxClaimableSupply, uint256 supplyClaimed, uint256 quantityLimitPerWallet, bytes32 merkleRoot, uint256 pricePerToken, address currency, string metadata) condition)", + "event ClaimConditionUpdated((uint256 startTimestamp, uint256 maxClaimableSupply, uint256 supplyClaimed, uint256 quantityLimitPerWallet, bytes32 merkleRoot, uint256 pricePerToken, address currency, string metadata) condition, bool resetEligibility)", + "event TokensClaimed(address indexed claimer, address indexed receiver, uint256 indexed startTokenId, uint256 quantityClaimed)" +] \ No newline at end of file diff --git a/packages/thirdweb/src/extensions/erc1155/__generated__/IDropSinglePhase1155/events/ClaimConditionUpdated.ts b/packages/thirdweb/src/extensions/erc1155/__generated__/IDropSinglePhase1155/events/ClaimConditionUpdated.ts new file mode 100644 index 00000000000..21040cdf986 --- /dev/null +++ b/packages/thirdweb/src/extensions/erc1155/__generated__/IDropSinglePhase1155/events/ClaimConditionUpdated.ts @@ -0,0 +1,43 @@ +import { prepareEvent } from "../../../../../event/prepare-event.js"; +import type { AbiParameterToPrimitiveType } from "abitype"; + +/** + * Represents the filters for the "ClaimConditionUpdated" event. + */ +export type ClaimConditionUpdatedEventFilters = Partial<{ + tokenId: AbiParameterToPrimitiveType<{ + type: "uint256"; + name: "tokenId"; + indexed: true; + }>; +}>; + +/** + * Creates an event object for the ClaimConditionUpdated event. + * @param filters - Optional filters to apply to the event. + * @returns The prepared event object. + * @extension ERC1155 + * @example + * ```ts + * import { getContractEvents } from "thirdweb"; + * import { claimConditionUpdatedEvent } from "thirdweb/extensions/erc1155"; + * + * const events = await getContractEvents({ + * contract, + * events: [ + * claimConditionUpdatedEvent({ + * tokenId: ..., + * }) + * ], + * }); + * ``` + */ +export function claimConditionUpdatedEvent( + filters: ClaimConditionUpdatedEventFilters = {}, +) { + return prepareEvent({ + signature: + "event ClaimConditionUpdated(uint256 indexed tokenId, (uint256 startTimestamp, uint256 maxClaimableSupply, uint256 supplyClaimed, uint256 quantityLimitPerWallet, bytes32 merkleRoot, uint256 pricePerToken, address currency, string metadata) condition, bool resetEligibility)", + filters, + }); +} diff --git a/packages/thirdweb/src/extensions/erc1155/__generated__/IDropSinglePhase1155/events/TokensClaimed.ts b/packages/thirdweb/src/extensions/erc1155/__generated__/IDropSinglePhase1155/events/TokensClaimed.ts new file mode 100644 index 00000000000..fa7b986927c --- /dev/null +++ b/packages/thirdweb/src/extensions/erc1155/__generated__/IDropSinglePhase1155/events/TokensClaimed.ts @@ -0,0 +1,53 @@ +import { prepareEvent } from "../../../../../event/prepare-event.js"; +import type { AbiParameterToPrimitiveType } from "abitype"; + +/** + * Represents the filters for the "TokensClaimed" event. + */ +export type TokensClaimedEventFilters = Partial<{ + claimer: AbiParameterToPrimitiveType<{ + type: "address"; + name: "claimer"; + indexed: true; + }>; + receiver: AbiParameterToPrimitiveType<{ + type: "address"; + name: "receiver"; + indexed: true; + }>; + tokenId: AbiParameterToPrimitiveType<{ + type: "uint256"; + name: "tokenId"; + indexed: true; + }>; +}>; + +/** + * Creates an event object for the TokensClaimed event. + * @param filters - Optional filters to apply to the event. + * @returns The prepared event object. + * @extension ERC1155 + * @example + * ```ts + * import { getContractEvents } from "thirdweb"; + * import { tokensClaimedEvent } from "thirdweb/extensions/erc1155"; + * + * const events = await getContractEvents({ + * contract, + * events: [ + * tokensClaimedEvent({ + * claimer: ..., + * receiver: ..., + * tokenId: ..., + * }) + * ], + * }); + * ``` + */ +export function tokensClaimedEvent(filters: TokensClaimedEventFilters = {}) { + return prepareEvent({ + signature: + "event TokensClaimed(address indexed claimer, address indexed receiver, uint256 indexed tokenId, uint256 quantityClaimed)", + filters, + }); +} diff --git a/packages/thirdweb/src/extensions/erc1155/__generated__/IDropSinglePhase1155/read/claimCondition.ts b/packages/thirdweb/src/extensions/erc1155/__generated__/IDropSinglePhase1155/read/claimCondition.ts new file mode 100644 index 00000000000..13826906051 --- /dev/null +++ b/packages/thirdweb/src/extensions/erc1155/__generated__/IDropSinglePhase1155/read/claimCondition.ts @@ -0,0 +1,160 @@ +import type { AbiParameterToPrimitiveType } from "abitype"; +import { readContract } from "../../../../../transaction/read-contract.js"; +import type { BaseTransactionOptions } from "../../../../../transaction/types.js"; +import { encodeAbiParameters } from "../../../../../utils/abi/encodeAbiParameters.js"; +import { decodeAbiParameters } from "viem"; +import type { Hex } from "../../../../../utils/encoding/hex.js"; +import { detectMethod } from "../../../../../utils/bytecode/detectExtension.js"; + +/** + * Represents the parameters for the "claimCondition" function. + */ +export type ClaimConditionParams = { + tokenId: AbiParameterToPrimitiveType<{ type: "uint256"; name: "tokenId" }>; +}; + +export const FN_SELECTOR = "0xe9703d25" as const; +const FN_INPUTS = [ + { + type: "uint256", + name: "tokenId", + }, +] as const; +const FN_OUTPUTS = [ + { + type: "tuple", + name: "condition", + components: [ + { + type: "uint256", + name: "startTimestamp", + }, + { + type: "uint256", + name: "maxClaimableSupply", + }, + { + type: "uint256", + name: "supplyClaimed", + }, + { + type: "uint256", + name: "quantityLimitPerWallet", + }, + { + type: "bytes32", + name: "merkleRoot", + }, + { + type: "uint256", + name: "pricePerToken", + }, + { + type: "address", + name: "currency", + }, + { + type: "string", + name: "metadata", + }, + ], + }, +] as const; + +/** + * Checks if the `claimCondition` method is supported by the given contract. + * @param availableSelectors An array of 4byte function selectors of the contract. You can get this in various ways, such as using "whatsabi" or if you have the ABI of the contract available you can use it to generate the selectors. + * @returns A boolean indicating if the `claimCondition` method is supported. + * @extension ERC1155 + * @example + * ```ts + * import { isClaimConditionSupported } from "thirdweb/extensions/erc1155"; + * const supported = isClaimConditionSupported(["0x..."]); + * ``` + */ +export function isClaimConditionSupported(availableSelectors: string[]) { + return detectMethod({ + availableSelectors, + method: [FN_SELECTOR, FN_INPUTS, FN_OUTPUTS] as const, + }); +} + +/** + * Encodes the parameters for the "claimCondition" function. + * @param options - The options for the claimCondition function. + * @returns The encoded ABI parameters. + * @extension ERC1155 + * @example + * ```ts + * import { encodeClaimConditionParams } from "thirdweb/extensions/erc1155"; + * const result = encodeClaimConditionParams({ + * tokenId: ..., + * }); + * ``` + */ +export function encodeClaimConditionParams(options: ClaimConditionParams) { + return encodeAbiParameters(FN_INPUTS, [options.tokenId]); +} + +/** + * Encodes the "claimCondition" function into a Hex string with its parameters. + * @param options - The options for the claimCondition function. + * @returns The encoded hexadecimal string. + * @extension ERC1155 + * @example + * ```ts + * import { encodeClaimCondition } from "thirdweb/extensions/erc1155"; + * const result = encodeClaimCondition({ + * tokenId: ..., + * }); + * ``` + */ +export function encodeClaimCondition(options: ClaimConditionParams) { + // we do a "manual" concat here to avoid the overhead of the "concatHex" function + // we can do this because we know the specific formats of the values + return (FN_SELECTOR + + encodeClaimConditionParams(options).slice( + 2, + )) as `${typeof FN_SELECTOR}${string}`; +} + +/** + * Decodes the result of the claimCondition function call. + * @param result - The hexadecimal result to decode. + * @returns The decoded result as per the FN_OUTPUTS definition. + * @extension ERC1155 + * @example + * ```ts + * import { decodeClaimConditionResult } from "thirdweb/extensions/erc1155"; + * const result = decodeClaimConditionResultResult("..."); + * ``` + */ +export function decodeClaimConditionResult(result: Hex) { + return decodeAbiParameters(FN_OUTPUTS, result)[0]; +} + +/** + * Calls the "claimCondition" function on the contract. + * @param options - The options for the claimCondition function. + * @returns The parsed result of the function call. + * @extension ERC1155 + * @example + * ```ts + * import { claimCondition } from "thirdweb/extensions/erc1155"; + * + * const result = await claimCondition({ + * contract, + * tokenId: ..., + * }); + * + * ``` + */ +export async function claimCondition( + options: BaseTransactionOptions, +) { + return readContract({ + contract: options.contract, + method: [FN_SELECTOR, FN_INPUTS, FN_OUTPUTS] as const, + params: [options.tokenId], + }); +} diff --git a/packages/thirdweb/src/extensions/erc1155/__generated__/IDropSinglePhase1155/write/claim.ts b/packages/thirdweb/src/extensions/erc1155/__generated__/IDropSinglePhase1155/write/claim.ts new file mode 100644 index 00000000000..41f861296c1 --- /dev/null +++ b/packages/thirdweb/src/extensions/erc1155/__generated__/IDropSinglePhase1155/write/claim.ts @@ -0,0 +1,229 @@ +import type { AbiParameterToPrimitiveType } from "abitype"; +import type { + BaseTransactionOptions, + WithOverrides, +} from "../../../../../transaction/types.js"; +import { prepareContractCall } from "../../../../../transaction/prepare-contract-call.js"; +import { encodeAbiParameters } from "../../../../../utils/abi/encodeAbiParameters.js"; +import { once } from "../../../../../utils/promise/once.js"; +import { detectMethod } from "../../../../../utils/bytecode/detectExtension.js"; + +/** + * Represents the parameters for the "claim" function. + */ +export type ClaimParams = WithOverrides<{ + receiver: AbiParameterToPrimitiveType<{ type: "address"; name: "receiver" }>; + tokenId: AbiParameterToPrimitiveType<{ type: "uint256"; name: "tokenId" }>; + quantity: AbiParameterToPrimitiveType<{ type: "uint256"; name: "quantity" }>; + currency: AbiParameterToPrimitiveType<{ type: "address"; name: "currency" }>; + pricePerToken: AbiParameterToPrimitiveType<{ + type: "uint256"; + name: "pricePerToken"; + }>; + allowlistProof: AbiParameterToPrimitiveType<{ + type: "tuple"; + name: "allowlistProof"; + components: [ + { type: "bytes32[]"; name: "proof" }, + { type: "uint256"; name: "quantityLimitPerWallet" }, + { type: "uint256"; name: "pricePerToken" }, + { type: "address"; name: "currency" }, + ]; + }>; + data: AbiParameterToPrimitiveType<{ type: "bytes"; name: "data" }>; +}>; + +export const FN_SELECTOR = "0x57bc3d78" as const; +const FN_INPUTS = [ + { + type: "address", + name: "receiver", + }, + { + type: "uint256", + name: "tokenId", + }, + { + type: "uint256", + name: "quantity", + }, + { + type: "address", + name: "currency", + }, + { + type: "uint256", + name: "pricePerToken", + }, + { + type: "tuple", + name: "allowlistProof", + components: [ + { + type: "bytes32[]", + name: "proof", + }, + { + type: "uint256", + name: "quantityLimitPerWallet", + }, + { + type: "uint256", + name: "pricePerToken", + }, + { + type: "address", + name: "currency", + }, + ], + }, + { + type: "bytes", + name: "data", + }, +] as const; +const FN_OUTPUTS = [] as const; + +/** + * Checks if the `claim` method is supported by the given contract. + * @param availableSelectors An array of 4byte function selectors of the contract. You can get this in various ways, such as using "whatsabi" or if you have the ABI of the contract available you can use it to generate the selectors. + * @returns A boolean indicating if the `claim` method is supported. + * @extension ERC1155 + * @example + * ```ts + * import { isClaimSupported } from "thirdweb/extensions/erc1155"; + * + * const supported = isClaimSupported(["0x..."]); + * ``` + */ +export function isClaimSupported(availableSelectors: string[]) { + return detectMethod({ + availableSelectors, + method: [FN_SELECTOR, FN_INPUTS, FN_OUTPUTS] as const, + }); +} + +/** + * Encodes the parameters for the "claim" function. + * @param options - The options for the claim function. + * @returns The encoded ABI parameters. + * @extension ERC1155 + * @example + * ```ts + * import { encodeClaimParams } from "thirdweb/extensions/erc1155"; + * const result = encodeClaimParams({ + * receiver: ..., + * tokenId: ..., + * quantity: ..., + * currency: ..., + * pricePerToken: ..., + * allowlistProof: ..., + * data: ..., + * }); + * ``` + */ +export function encodeClaimParams(options: ClaimParams) { + return encodeAbiParameters(FN_INPUTS, [ + options.receiver, + options.tokenId, + options.quantity, + options.currency, + options.pricePerToken, + options.allowlistProof, + options.data, + ]); +} + +/** + * Encodes the "claim" function into a Hex string with its parameters. + * @param options - The options for the claim function. + * @returns The encoded hexadecimal string. + * @extension ERC1155 + * @example + * ```ts + * import { encodeClaim } from "thirdweb/extensions/erc1155"; + * const result = encodeClaim({ + * receiver: ..., + * tokenId: ..., + * quantity: ..., + * currency: ..., + * pricePerToken: ..., + * allowlistProof: ..., + * data: ..., + * }); + * ``` + */ +export function encodeClaim(options: ClaimParams) { + // we do a "manual" concat here to avoid the overhead of the "concatHex" function + // we can do this because we know the specific formats of the values + return (FN_SELECTOR + + encodeClaimParams(options).slice(2)) as `${typeof FN_SELECTOR}${string}`; +} + +/** + * Prepares a transaction to call the "claim" function on the contract. + * @param options - The options for the "claim" function. + * @returns A prepared transaction object. + * @extension ERC1155 + * @example + * ```ts + * import { sendTransaction } from "thirdweb"; + * import { claim } from "thirdweb/extensions/erc1155"; + * + * const transaction = claim({ + * contract, + * receiver: ..., + * tokenId: ..., + * quantity: ..., + * currency: ..., + * pricePerToken: ..., + * allowlistProof: ..., + * data: ..., + * overrides: { + * ... + * } + * }); + * + * // Send the transaction + * await sendTransaction({ transaction, account }); + * ``` + */ +export function claim( + options: BaseTransactionOptions< + | ClaimParams + | { + asyncParams: () => Promise; + } + >, +) { + const asyncOptions = once(async () => { + return "asyncParams" in options ? await options.asyncParams() : options; + }); + + return prepareContractCall({ + contract: options.contract, + method: [FN_SELECTOR, FN_INPUTS, FN_OUTPUTS] as const, + params: async () => { + const resolvedOptions = await asyncOptions(); + return [ + resolvedOptions.receiver, + resolvedOptions.tokenId, + resolvedOptions.quantity, + resolvedOptions.currency, + resolvedOptions.pricePerToken, + resolvedOptions.allowlistProof, + resolvedOptions.data, + ] as const; + }, + value: async () => (await asyncOptions()).overrides?.value, + accessList: async () => (await asyncOptions()).overrides?.accessList, + gas: async () => (await asyncOptions()).overrides?.gas, + gasPrice: async () => (await asyncOptions()).overrides?.gasPrice, + maxFeePerGas: async () => (await asyncOptions()).overrides?.maxFeePerGas, + maxPriorityFeePerGas: async () => + (await asyncOptions()).overrides?.maxPriorityFeePerGas, + nonce: async () => (await asyncOptions()).overrides?.nonce, + extraGas: async () => (await asyncOptions()).overrides?.extraGas, + erc20Value: async () => (await asyncOptions()).overrides?.erc20Value, + }); +} diff --git a/packages/thirdweb/src/extensions/erc1155/__generated__/IDropSinglePhase1155/write/setClaimConditions.ts b/packages/thirdweb/src/extensions/erc1155/__generated__/IDropSinglePhase1155/write/setClaimConditions.ts new file mode 100644 index 00000000000..8dabd4da425 --- /dev/null +++ b/packages/thirdweb/src/extensions/erc1155/__generated__/IDropSinglePhase1155/write/setClaimConditions.ts @@ -0,0 +1,213 @@ +import type { AbiParameterToPrimitiveType } from "abitype"; +import type { + BaseTransactionOptions, + WithOverrides, +} from "../../../../../transaction/types.js"; +import { prepareContractCall } from "../../../../../transaction/prepare-contract-call.js"; +import { encodeAbiParameters } from "../../../../../utils/abi/encodeAbiParameters.js"; +import { once } from "../../../../../utils/promise/once.js"; +import { detectMethod } from "../../../../../utils/bytecode/detectExtension.js"; + +/** + * Represents the parameters for the "setClaimConditions" function. + */ +export type SetClaimConditionsParams = WithOverrides<{ + tokenId: AbiParameterToPrimitiveType<{ type: "uint256"; name: "tokenId" }>; + phase: AbiParameterToPrimitiveType<{ + type: "tuple"; + name: "phase"; + components: [ + { type: "uint256"; name: "startTimestamp" }, + { type: "uint256"; name: "maxClaimableSupply" }, + { type: "uint256"; name: "supplyClaimed" }, + { type: "uint256"; name: "quantityLimitPerWallet" }, + { type: "bytes32"; name: "merkleRoot" }, + { type: "uint256"; name: "pricePerToken" }, + { type: "address"; name: "currency" }, + { type: "string"; name: "metadata" }, + ]; + }>; + resetClaimEligibility: AbiParameterToPrimitiveType<{ + type: "bool"; + name: "resetClaimEligibility"; + }>; +}>; + +export const FN_SELECTOR = "0x8affb89f" as const; +const FN_INPUTS = [ + { + type: "uint256", + name: "tokenId", + }, + { + type: "tuple", + name: "phase", + components: [ + { + type: "uint256", + name: "startTimestamp", + }, + { + type: "uint256", + name: "maxClaimableSupply", + }, + { + type: "uint256", + name: "supplyClaimed", + }, + { + type: "uint256", + name: "quantityLimitPerWallet", + }, + { + type: "bytes32", + name: "merkleRoot", + }, + { + type: "uint256", + name: "pricePerToken", + }, + { + type: "address", + name: "currency", + }, + { + type: "string", + name: "metadata", + }, + ], + }, + { + type: "bool", + name: "resetClaimEligibility", + }, +] as const; +const FN_OUTPUTS = [] as const; + +/** + * Checks if the `setClaimConditions` method is supported by the given contract. + * @param availableSelectors An array of 4byte function selectors of the contract. You can get this in various ways, such as using "whatsabi" or if you have the ABI of the contract available you can use it to generate the selectors. + * @returns A boolean indicating if the `setClaimConditions` method is supported. + * @extension ERC1155 + * @example + * ```ts + * import { isSetClaimConditionsSupported } from "thirdweb/extensions/erc1155"; + * + * const supported = isSetClaimConditionsSupported(["0x..."]); + * ``` + */ +export function isSetClaimConditionsSupported(availableSelectors: string[]) { + return detectMethod({ + availableSelectors, + method: [FN_SELECTOR, FN_INPUTS, FN_OUTPUTS] as const, + }); +} + +/** + * Encodes the parameters for the "setClaimConditions" function. + * @param options - The options for the setClaimConditions function. + * @returns The encoded ABI parameters. + * @extension ERC1155 + * @example + * ```ts + * import { encodeSetClaimConditionsParams } from "thirdweb/extensions/erc1155"; + * const result = encodeSetClaimConditionsParams({ + * tokenId: ..., + * phase: ..., + * resetClaimEligibility: ..., + * }); + * ``` + */ +export function encodeSetClaimConditionsParams( + options: SetClaimConditionsParams, +) { + return encodeAbiParameters(FN_INPUTS, [ + options.tokenId, + options.phase, + options.resetClaimEligibility, + ]); +} + +/** + * Encodes the "setClaimConditions" function into a Hex string with its parameters. + * @param options - The options for the setClaimConditions function. + * @returns The encoded hexadecimal string. + * @extension ERC1155 + * @example + * ```ts + * import { encodeSetClaimConditions } from "thirdweb/extensions/erc1155"; + * const result = encodeSetClaimConditions({ + * tokenId: ..., + * phase: ..., + * resetClaimEligibility: ..., + * }); + * ``` + */ +export function encodeSetClaimConditions(options: SetClaimConditionsParams) { + // we do a "manual" concat here to avoid the overhead of the "concatHex" function + // we can do this because we know the specific formats of the values + return (FN_SELECTOR + + encodeSetClaimConditionsParams(options).slice( + 2, + )) as `${typeof FN_SELECTOR}${string}`; +} + +/** + * Prepares a transaction to call the "setClaimConditions" function on the contract. + * @param options - The options for the "setClaimConditions" function. + * @returns A prepared transaction object. + * @extension ERC1155 + * @example + * ```ts + * import { sendTransaction } from "thirdweb"; + * import { setClaimConditions } from "thirdweb/extensions/erc1155"; + * + * const transaction = setClaimConditions({ + * contract, + * tokenId: ..., + * phase: ..., + * resetClaimEligibility: ..., + * overrides: { + * ... + * } + * }); + * + * // Send the transaction + * await sendTransaction({ transaction, account }); + * ``` + */ +export function setClaimConditions( + options: BaseTransactionOptions< + | SetClaimConditionsParams + | { + asyncParams: () => Promise; + } + >, +) { + const asyncOptions = once(async () => { + return "asyncParams" in options ? await options.asyncParams() : options; + }); + + return prepareContractCall({ + contract: options.contract, + method: [FN_SELECTOR, FN_INPUTS, FN_OUTPUTS] as const, + params: async () => { + const resolvedOptions = await asyncOptions(); + return [ + resolvedOptions.tokenId, + resolvedOptions.phase, + resolvedOptions.resetClaimEligibility, + ] as const; + }, + value: async () => (await asyncOptions()).overrides?.value, + accessList: async () => (await asyncOptions()).overrides?.accessList, + gas: async () => (await asyncOptions()).overrides?.gas, + gasPrice: async () => (await asyncOptions()).overrides?.gasPrice, + maxFeePerGas: async () => (await asyncOptions()).overrides?.maxFeePerGas, + maxPriorityFeePerGas: async () => + (await asyncOptions()).overrides?.maxPriorityFeePerGas, + nonce: async () => (await asyncOptions()).overrides?.nonce, + extraGas: async () => (await asyncOptions()).overrides?.extraGas, + erc20Value: async () => (await asyncOptions()).overrides?.erc20Value, + }); +} diff --git a/packages/thirdweb/src/extensions/erc1155/customDrop1155.test.ts b/packages/thirdweb/src/extensions/erc1155/customDrop1155.test.ts new file mode 100644 index 00000000000..295b492eaeb --- /dev/null +++ b/packages/thirdweb/src/extensions/erc1155/customDrop1155.test.ts @@ -0,0 +1,181 @@ +import { fetchDeployMetadata } from "../../utils/any-evm/deploy-metadata.js"; + +import { beforeAll, describe, expect, it } from "vitest"; +import { VITALIK_WALLET } from "../../../test/src/addresses.js"; +import { ANVIL_CHAIN } from "../../../test/src/chains.js"; +import { TEST_CLIENT } from "../../../test/src/test-clients.js"; +import { + TEST_ACCOUNT_B, + TEST_ACCOUNT_C, +} from "../../../test/src/test-wallets.js"; +import { type ThirdwebContract, getContract } from "../../contract/contract.js"; +import { sendAndConfirmTransaction } from "../../transaction/actions/send-and-confirm-transaction.js"; +import { deployContractfromDeployMetadata } from "../prebuilts/deploy-published.js"; +import { balanceOf } from "./__generated__/IERC1155/read/balanceOf.js"; +import { nextTokenIdToMint } from "./__generated__/IERC1155Enumerable/read/nextTokenIdToMint.js"; +import { claimTo } from "./drops/write/claimTo.js"; +import { setClaimConditions } from "./drops/write/setClaimConditions.js"; +import { getNFT } from "./read/getNFT.js"; +import { lazyMint } from "./write/lazyMint.js"; + +describe.runIf(process.env.TW_SECRET_KEY)( + "CustomDropERC1155", + { + retry: 0, + }, + () => { + let contract: ThirdwebContract; + + beforeAll(async () => { + const customDropDeployMetadata = await fetchDeployMetadata({ + client: TEST_CLIENT, + uri: "ipfs://QmaqFExXhU8kWkgAZCqxo8F3GZd8D2NJzCJWerfmFjujo8", + }); + const contractAddress = await deployContractfromDeployMetadata({ + chain: ANVIL_CHAIN, + client: TEST_CLIENT, + account: TEST_ACCOUNT_C, + deployMetadata: customDropDeployMetadata, + initializeParams: { + defaultAdmin: TEST_ACCOUNT_C.address, + name: "TestCustomDropERC1155", + symbol: "TT", + }, + }); + + contract = getContract({ + address: contractAddress, + chain: ANVIL_CHAIN, + client: TEST_CLIENT, + }); + // this deploys a contract, it may take some time + }, 60_000); + + it("should allow for lazy minting tokens", async () => { + const mintTx = lazyMint({ + contract, + nfts: [ + { name: "Test NFT" }, + { name: "Test NFT 2" }, + { name: "Test NFT 3" }, + { name: "Test NFT 4" }, + { name: "Test NFT 5" }, + { name: "Test NFT 6" }, + ], + }); + await sendAndConfirmTransaction({ + transaction: mintTx, + account: TEST_ACCOUNT_C, + }); + + await expect(nextTokenIdToMint({ contract })).resolves.toBe(6n); + await expect( + getNFT({ contract, tokenId: 0n }), + ).resolves.toMatchInlineSnapshot(` + { + "id": 0n, + "metadata": { + "name": "Test NFT", + }, + "owner": null, + "supply": 0n, + "tokenURI": "ipfs://QmTo68Dm1ntSp2BHLmE9gesS6ELuXosRz5mAgFCK6tfsRk/0", + "type": "ERC1155", + } + `); + }); + + it("should allow to claim tokens", async () => { + await expect( + balanceOf({ contract, owner: TEST_ACCOUNT_C.address, tokenId: 0n }), + ).resolves.toBe(0n); + await sendAndConfirmTransaction({ + transaction: setClaimConditions({ + contract, + phases: [ + { + startTime: new Date(0), + maxClaimableSupply: 10n, + }, + ], + tokenId: 0n, + singlePhaseDrop: true, + }), + account: TEST_ACCOUNT_C, + }); + const claimTx = claimTo({ + contract, + to: TEST_ACCOUNT_C.address, + tokenId: 0n, + quantity: 1n, + singlePhaseDrop: true, + }); + await sendAndConfirmTransaction({ + transaction: claimTx, + account: TEST_ACCOUNT_C, + }); + await expect( + balanceOf({ contract, owner: TEST_ACCOUNT_C.address, tokenId: 0n }), + ).resolves.toBe(1n); + }); + + it("should allow to claim tokens with an allowlist", async () => { + const tokenId = 1n; + await sendAndConfirmTransaction({ + transaction: setClaimConditions({ + contract, + phases: [ + { + overrideList: [ + { address: TEST_ACCOUNT_C.address, maxClaimable: "100" }, + { address: VITALIK_WALLET, maxClaimable: "100" }, + ], + maxClaimablePerWallet: 0n, + }, + ], + tokenId, + singlePhaseDrop: true, + }), + account: TEST_ACCOUNT_C, + }); + + await expect( + balanceOf({ contract, owner: TEST_ACCOUNT_B.address, tokenId }), + ).resolves.toBe(0n); + + await sendAndConfirmTransaction({ + account: TEST_ACCOUNT_C, + transaction: claimTo({ + contract, + from: TEST_ACCOUNT_C.address, + to: TEST_ACCOUNT_B.address, + tokenId, + quantity: 1n, + singlePhaseDrop: true, + }), + }); + + await expect( + balanceOf({ contract, owner: TEST_ACCOUNT_B.address, tokenId }), + ).resolves.toBe(1n); + + await expect( + sendAndConfirmTransaction({ + account: TEST_ACCOUNT_B, + transaction: claimTo({ + contract, + to: TEST_ACCOUNT_B.address, + tokenId, + quantity: 1n, + singlePhaseDrop: true, + }), + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [TransactionError: Error - !Qty + + contract: ${contract.address} + chainId: ${contract.chain.id}] + `); + }); + }, +); diff --git a/packages/thirdweb/src/extensions/erc1155/drops/write/claimTo.ts b/packages/thirdweb/src/extensions/erc1155/drops/write/claimTo.ts index 15b114a59f4..8ff2cf846e6 100644 --- a/packages/thirdweb/src/extensions/erc1155/drops/write/claimTo.ts +++ b/packages/thirdweb/src/extensions/erc1155/drops/write/claimTo.ts @@ -2,6 +2,7 @@ import type { BaseTransactionOptions } from "../../../../transaction/types.js"; import { getClaimParams } from "../../../../utils/extensions/drops/get-claim-params.js"; import { isGetContractMetadataSupported } from "../../../common/read/getContractMetadata.js"; import * as GeneratedClaim from "../../__generated__/IDrop1155/write/claim.js"; +import { isClaimConditionSupported } from "../../__generated__/IDropSinglePhase1155/read/claimCondition.js"; import { isGetActiveClaimConditionSupported } from "../read/getActiveClaimCondition.js"; /** @@ -12,6 +13,7 @@ export type ClaimToParams = { tokenId: bigint; quantity: bigint; from?: string; + singlePhaseDrop?: boolean; }; /** @@ -59,6 +61,7 @@ export function claimTo(options: BaseTransactionOptions) { quantity: options.quantity, from: options.from, tokenId: options.tokenId, + singlePhaseDrop: options.singlePhaseDrop, }); return { @@ -85,9 +88,10 @@ export function isClaimToSupported(availableSelectors: string[]) { return [ // has to support the claim method GeneratedClaim.isClaimSupported(availableSelectors), - // has to support the getActiveClaimCondition method - isGetActiveClaimConditionSupported(availableSelectors), // requires contractMetadata for claimer proofs isGetContractMetadataSupported(availableSelectors), + // required to check if the contract supports the getActiveClaimCondition method + isGetActiveClaimConditionSupported(availableSelectors) || + isClaimConditionSupported(availableSelectors), ].every(Boolean); } diff --git a/packages/thirdweb/src/extensions/erc1155/drops/write/setClaimConditions.ts b/packages/thirdweb/src/extensions/erc1155/drops/write/setClaimConditions.ts index d86364e4930..0732fb801af 100644 --- a/packages/thirdweb/src/extensions/erc1155/drops/write/setClaimConditions.ts +++ b/packages/thirdweb/src/extensions/erc1155/drops/write/setClaimConditions.ts @@ -8,6 +8,7 @@ import { } from "../../../common/__generated__/IMulticall/write/multicall.js"; import { isGetContractMetadataSupported } from "../../../common/read/getContractMetadata.js"; import { isSetClaimConditionsSupported as isSetClaimConditionsSupportedGenerated } from "../../__generated__/IDrop1155/write/setClaimConditions.js"; +import { isSetClaimConditionsSupported as isSetClaimConditionsSupportedGeneratedSinglePhase } from "../../__generated__/IDropSinglePhase1155/write/setClaimConditions.js"; /** * @extension ERC1155 @@ -16,6 +17,7 @@ export type SetClaimConditionsParams = { tokenId: bigint; phases: ClaimConditionsInput[]; resetClaimEligibility?: boolean; + singlePhaseDrop?: boolean; }; /** @@ -58,6 +60,7 @@ export function setClaimConditions( resetClaimEligibility: options.resetClaimEligibility, tokenId: options.tokenId, tokenDecimals: 0, + singlePhase: options.singlePhaseDrop, }), }; }, @@ -83,6 +86,7 @@ export function isSetClaimConditionsSupported(availableSelectors: string[]) { isGetContractMetadataSupported(availableSelectors) && isSetContractURISupported(availableSelectors) && // needs to actually be able to set the claim Conditions - isSetClaimConditionsSupportedGenerated(availableSelectors) + (isSetClaimConditionsSupportedGenerated(availableSelectors) || + isSetClaimConditionsSupportedGeneratedSinglePhase(availableSelectors)) ); } diff --git a/packages/thirdweb/src/extensions/erc20/customDrop20.test.ts b/packages/thirdweb/src/extensions/erc20/customDrop20.test.ts new file mode 100644 index 00000000000..023274748b6 --- /dev/null +++ b/packages/thirdweb/src/extensions/erc20/customDrop20.test.ts @@ -0,0 +1,80 @@ +import { fetchDeployMetadata } from "../../utils/any-evm/deploy-metadata.js"; + +import { beforeAll, describe, expect, it } from "vitest"; +import { ANVIL_CHAIN } from "../../../test/src/chains.js"; +import { TEST_CLIENT } from "../../../test/src/test-clients.js"; +import { TEST_ACCOUNT_C } from "../../../test/src/test-wallets.js"; +import { type ThirdwebContract, getContract } from "../../contract/contract.js"; +import { sendAndConfirmTransaction } from "../../transaction/actions/send-and-confirm-transaction.js"; +import { toWei } from "../../utils/units.js"; +import { deployContractfromDeployMetadata } from "../prebuilts/deploy-published.js"; +import { balanceOf } from "./__generated__/IERC20/read/balanceOf.js"; +import { claimTo } from "./drops/write/claimTo.js"; +import { setClaimConditions } from "./drops/write/setClaimConditions.js"; + +describe.runIf(process.env.TW_SECRET_KEY)( + "CustomDropERC20", + { + retry: 0, + }, + () => { + let contract: ThirdwebContract; + + beforeAll(async () => { + const customDropDeployMetadata = await fetchDeployMetadata({ + client: TEST_CLIENT, + uri: "ipfs://QmT6h6gZztc2G83gWkJBkNFeq1b9gKrFKnYwgagXMBHV1d", + }); + const contractAddress = await deployContractfromDeployMetadata({ + chain: ANVIL_CHAIN, + client: TEST_CLIENT, + account: TEST_ACCOUNT_C, + deployMetadata: customDropDeployMetadata, + initializeParams: { + defaultAdmin: TEST_ACCOUNT_C.address, + name: "TestCustomDropERC20", + symbol: "TT", + }, + }); + + contract = getContract({ + address: contractAddress, + chain: ANVIL_CHAIN, + client: TEST_CLIENT, + }); + // this deploys a contract, it may take some time + }, 60_000); + + it("should allow to claim tokens", async () => { + await expect( + balanceOf({ contract, address: TEST_ACCOUNT_C.address }), + ).resolves.toBe(0n); + await sendAndConfirmTransaction({ + transaction: setClaimConditions({ + contract, + phases: [ + { + startTime: new Date(0), + maxClaimableSupply: toWei("10"), + }, + ], + singlePhaseDrop: true, + }), + account: TEST_ACCOUNT_C, + }); + const claimTx = claimTo({ + contract, + to: TEST_ACCOUNT_C.address, + quantity: "1", + singlePhaseDrop: true, + }); + await sendAndConfirmTransaction({ + transaction: claimTx, + account: TEST_ACCOUNT_C, + }); + await expect( + balanceOf({ contract, address: TEST_ACCOUNT_C.address }), + ).resolves.toBe(toWei("1")); + }); + }, +); diff --git a/packages/thirdweb/src/extensions/erc20/drops/write/claimTo.ts b/packages/thirdweb/src/extensions/erc20/drops/write/claimTo.ts index dddc5fcf6e0..72bd0c67e6f 100644 --- a/packages/thirdweb/src/extensions/erc20/drops/write/claimTo.ts +++ b/packages/thirdweb/src/extensions/erc20/drops/write/claimTo.ts @@ -13,6 +13,7 @@ import { isGetActiveClaimConditionSupported } from "../read/getActiveClaimCondit export type ClaimToParams = { to: Address; from?: Address; + singlePhaseDrop?: boolean; } & ({ quantityInWei: bigint } | { quantity: string }); /** @@ -71,6 +72,7 @@ export function claimTo(options: BaseTransactionOptions) { quantity, from: options.from, tokenDecimals: await decimals({ contract: options.contract }), + singlePhaseDrop: options.singlePhaseDrop, }); }, }); diff --git a/packages/thirdweb/src/extensions/erc20/drops/write/setClaimConditions.ts b/packages/thirdweb/src/extensions/erc20/drops/write/setClaimConditions.ts index efbefb60bf0..4c34947d6a3 100644 --- a/packages/thirdweb/src/extensions/erc20/drops/write/setClaimConditions.ts +++ b/packages/thirdweb/src/extensions/erc20/drops/write/setClaimConditions.ts @@ -16,6 +16,7 @@ import { decimals, isDecimalsSupported } from "../../read/decimals.js"; export type SetClaimConditionsParams = { phases: ClaimConditionsInput[]; resetClaimEligibility?: boolean; + singlePhaseDrop?: boolean; }; /** @@ -56,6 +57,7 @@ export function setClaimConditions( phases: options.phases, resetClaimEligibility: options.resetClaimEligibility, tokenDecimals: await decimals({ contract: options.contract }), + singlePhase: options.singlePhaseDrop, }), }; }, diff --git a/packages/thirdweb/src/extensions/erc721/__generated__/IDropSinglePhase/events/ClaimConditionUpdated.ts b/packages/thirdweb/src/extensions/erc721/__generated__/IDropSinglePhase/events/ClaimConditionUpdated.ts new file mode 100644 index 00000000000..91675ce5076 --- /dev/null +++ b/packages/thirdweb/src/extensions/erc721/__generated__/IDropSinglePhase/events/ClaimConditionUpdated.ts @@ -0,0 +1,25 @@ +import { prepareEvent } from "../../../../../event/prepare-event.js"; + +/** + * Creates an event object for the ClaimConditionUpdated event. + * @returns The prepared event object. + * @extension ERC721 + * @example + * ```ts + * import { getContractEvents } from "thirdweb"; + * import { claimConditionUpdatedEvent } from "thirdweb/extensions/erc721"; + * + * const events = await getContractEvents({ + * contract, + * events: [ + * claimConditionUpdatedEvent() + * ], + * }); + * ``` + */ +export function claimConditionUpdatedEvent() { + return prepareEvent({ + signature: + "event ClaimConditionUpdated((uint256 startTimestamp, uint256 maxClaimableSupply, uint256 supplyClaimed, uint256 quantityLimitPerWallet, bytes32 merkleRoot, uint256 pricePerToken, address currency, string metadata) condition, bool resetEligibility)", + }); +} diff --git a/packages/thirdweb/src/extensions/erc721/__generated__/IDropSinglePhase/events/TokensClaimed.ts b/packages/thirdweb/src/extensions/erc721/__generated__/IDropSinglePhase/events/TokensClaimed.ts new file mode 100644 index 00000000000..96d8aeb4491 --- /dev/null +++ b/packages/thirdweb/src/extensions/erc721/__generated__/IDropSinglePhase/events/TokensClaimed.ts @@ -0,0 +1,53 @@ +import { prepareEvent } from "../../../../../event/prepare-event.js"; +import type { AbiParameterToPrimitiveType } from "abitype"; + +/** + * Represents the filters for the "TokensClaimed" event. + */ +export type TokensClaimedEventFilters = Partial<{ + claimer: AbiParameterToPrimitiveType<{ + type: "address"; + name: "claimer"; + indexed: true; + }>; + receiver: AbiParameterToPrimitiveType<{ + type: "address"; + name: "receiver"; + indexed: true; + }>; + startTokenId: AbiParameterToPrimitiveType<{ + type: "uint256"; + name: "startTokenId"; + indexed: true; + }>; +}>; + +/** + * Creates an event object for the TokensClaimed event. + * @param filters - Optional filters to apply to the event. + * @returns The prepared event object. + * @extension ERC721 + * @example + * ```ts + * import { getContractEvents } from "thirdweb"; + * import { tokensClaimedEvent } from "thirdweb/extensions/erc721"; + * + * const events = await getContractEvents({ + * contract, + * events: [ + * tokensClaimedEvent({ + * claimer: ..., + * receiver: ..., + * startTokenId: ..., + * }) + * ], + * }); + * ``` + */ +export function tokensClaimedEvent(filters: TokensClaimedEventFilters = {}) { + return prepareEvent({ + signature: + "event TokensClaimed(address indexed claimer, address indexed receiver, uint256 indexed startTokenId, uint256 quantityClaimed)", + filters, + }); +} diff --git a/packages/thirdweb/src/extensions/erc721/__generated__/IDropSinglePhase/read/claimCondition.ts b/packages/thirdweb/src/extensions/erc721/__generated__/IDropSinglePhase/read/claimCondition.ts new file mode 100644 index 00000000000..60f4078f4d2 --- /dev/null +++ b/packages/thirdweb/src/extensions/erc721/__generated__/IDropSinglePhase/read/claimCondition.ts @@ -0,0 +1,105 @@ +import { readContract } from "../../../../../transaction/read-contract.js"; +import type { BaseTransactionOptions } from "../../../../../transaction/types.js"; + +import { decodeAbiParameters } from "viem"; +import type { Hex } from "../../../../../utils/encoding/hex.js"; +import { detectMethod } from "../../../../../utils/bytecode/detectExtension.js"; + +export const FN_SELECTOR = "0xd637ed59" as const; +const FN_INPUTS = [] as const; +const FN_OUTPUTS = [ + { + type: "tuple", + name: "condition", + components: [ + { + type: "uint256", + name: "startTimestamp", + }, + { + type: "uint256", + name: "maxClaimableSupply", + }, + { + type: "uint256", + name: "supplyClaimed", + }, + { + type: "uint256", + name: "quantityLimitPerWallet", + }, + { + type: "bytes32", + name: "merkleRoot", + }, + { + type: "uint256", + name: "pricePerToken", + }, + { + type: "address", + name: "currency", + }, + { + type: "string", + name: "metadata", + }, + ], + }, +] as const; + +/** + * Checks if the `claimCondition` method is supported by the given contract. + * @param availableSelectors An array of 4byte function selectors of the contract. You can get this in various ways, such as using "whatsabi" or if you have the ABI of the contract available you can use it to generate the selectors. + * @returns A boolean indicating if the `claimCondition` method is supported. + * @extension ERC721 + * @example + * ```ts + * import { isClaimConditionSupported } from "thirdweb/extensions/erc721"; + * const supported = isClaimConditionSupported(["0x..."]); + * ``` + */ +export function isClaimConditionSupported(availableSelectors: string[]) { + return detectMethod({ + availableSelectors, + method: [FN_SELECTOR, FN_INPUTS, FN_OUTPUTS] as const, + }); +} + +/** + * Decodes the result of the claimCondition function call. + * @param result - The hexadecimal result to decode. + * @returns The decoded result as per the FN_OUTPUTS definition. + * @extension ERC721 + * @example + * ```ts + * import { decodeClaimConditionResult } from "thirdweb/extensions/erc721"; + * const result = decodeClaimConditionResultResult("..."); + * ``` + */ +export function decodeClaimConditionResult(result: Hex) { + return decodeAbiParameters(FN_OUTPUTS, result)[0]; +} + +/** + * Calls the "claimCondition" function on the contract. + * @param options - The options for the claimCondition function. + * @returns The parsed result of the function call. + * @extension ERC721 + * @example + * ```ts + * import { claimCondition } from "thirdweb/extensions/erc721"; + * + * const result = await claimCondition({ + * contract, + * }); + * + * ``` + */ +export async function claimCondition(options: BaseTransactionOptions) { + return readContract({ + contract: options.contract, + method: [FN_SELECTOR, FN_INPUTS, FN_OUTPUTS] as const, + params: [], + }); +} diff --git a/packages/thirdweb/src/extensions/erc721/__generated__/IDropSinglePhase/write/claim.ts b/packages/thirdweb/src/extensions/erc721/__generated__/IDropSinglePhase/write/claim.ts new file mode 100644 index 00000000000..a29e5f5bec9 --- /dev/null +++ b/packages/thirdweb/src/extensions/erc721/__generated__/IDropSinglePhase/write/claim.ts @@ -0,0 +1,219 @@ +import type { AbiParameterToPrimitiveType } from "abitype"; +import type { + BaseTransactionOptions, + WithOverrides, +} from "../../../../../transaction/types.js"; +import { prepareContractCall } from "../../../../../transaction/prepare-contract-call.js"; +import { encodeAbiParameters } from "../../../../../utils/abi/encodeAbiParameters.js"; +import { once } from "../../../../../utils/promise/once.js"; +import { detectMethod } from "../../../../../utils/bytecode/detectExtension.js"; + +/** + * Represents the parameters for the "claim" function. + */ +export type ClaimParams = WithOverrides<{ + receiver: AbiParameterToPrimitiveType<{ type: "address"; name: "receiver" }>; + quantity: AbiParameterToPrimitiveType<{ type: "uint256"; name: "quantity" }>; + currency: AbiParameterToPrimitiveType<{ type: "address"; name: "currency" }>; + pricePerToken: AbiParameterToPrimitiveType<{ + type: "uint256"; + name: "pricePerToken"; + }>; + allowlistProof: AbiParameterToPrimitiveType<{ + type: "tuple"; + name: "allowlistProof"; + components: [ + { type: "bytes32[]"; name: "proof" }, + { type: "uint256"; name: "quantityLimitPerWallet" }, + { type: "uint256"; name: "pricePerToken" }, + { type: "address"; name: "currency" }, + ]; + }>; + data: AbiParameterToPrimitiveType<{ type: "bytes"; name: "data" }>; +}>; + +export const FN_SELECTOR = "0x84bb1e42" as const; +const FN_INPUTS = [ + { + type: "address", + name: "receiver", + }, + { + type: "uint256", + name: "quantity", + }, + { + type: "address", + name: "currency", + }, + { + type: "uint256", + name: "pricePerToken", + }, + { + type: "tuple", + name: "allowlistProof", + components: [ + { + type: "bytes32[]", + name: "proof", + }, + { + type: "uint256", + name: "quantityLimitPerWallet", + }, + { + type: "uint256", + name: "pricePerToken", + }, + { + type: "address", + name: "currency", + }, + ], + }, + { + type: "bytes", + name: "data", + }, +] as const; +const FN_OUTPUTS = [] as const; + +/** + * Checks if the `claim` method is supported by the given contract. + * @param availableSelectors An array of 4byte function selectors of the contract. You can get this in various ways, such as using "whatsabi" or if you have the ABI of the contract available you can use it to generate the selectors. + * @returns A boolean indicating if the `claim` method is supported. + * @extension ERC721 + * @example + * ```ts + * import { isClaimSupported } from "thirdweb/extensions/erc721"; + * + * const supported = isClaimSupported(["0x..."]); + * ``` + */ +export function isClaimSupported(availableSelectors: string[]) { + return detectMethod({ + availableSelectors, + method: [FN_SELECTOR, FN_INPUTS, FN_OUTPUTS] as const, + }); +} + +/** + * Encodes the parameters for the "claim" function. + * @param options - The options for the claim function. + * @returns The encoded ABI parameters. + * @extension ERC721 + * @example + * ```ts + * import { encodeClaimParams } from "thirdweb/extensions/erc721"; + * const result = encodeClaimParams({ + * receiver: ..., + * quantity: ..., + * currency: ..., + * pricePerToken: ..., + * allowlistProof: ..., + * data: ..., + * }); + * ``` + */ +export function encodeClaimParams(options: ClaimParams) { + return encodeAbiParameters(FN_INPUTS, [ + options.receiver, + options.quantity, + options.currency, + options.pricePerToken, + options.allowlistProof, + options.data, + ]); +} + +/** + * Encodes the "claim" function into a Hex string with its parameters. + * @param options - The options for the claim function. + * @returns The encoded hexadecimal string. + * @extension ERC721 + * @example + * ```ts + * import { encodeClaim } from "thirdweb/extensions/erc721"; + * const result = encodeClaim({ + * receiver: ..., + * quantity: ..., + * currency: ..., + * pricePerToken: ..., + * allowlistProof: ..., + * data: ..., + * }); + * ``` + */ +export function encodeClaim(options: ClaimParams) { + // we do a "manual" concat here to avoid the overhead of the "concatHex" function + // we can do this because we know the specific formats of the values + return (FN_SELECTOR + + encodeClaimParams(options).slice(2)) as `${typeof FN_SELECTOR}${string}`; +} + +/** + * Prepares a transaction to call the "claim" function on the contract. + * @param options - The options for the "claim" function. + * @returns A prepared transaction object. + * @extension ERC721 + * @example + * ```ts + * import { sendTransaction } from "thirdweb"; + * import { claim } from "thirdweb/extensions/erc721"; + * + * const transaction = claim({ + * contract, + * receiver: ..., + * quantity: ..., + * currency: ..., + * pricePerToken: ..., + * allowlistProof: ..., + * data: ..., + * overrides: { + * ... + * } + * }); + * + * // Send the transaction + * await sendTransaction({ transaction, account }); + * ``` + */ +export function claim( + options: BaseTransactionOptions< + | ClaimParams + | { + asyncParams: () => Promise; + } + >, +) { + const asyncOptions = once(async () => { + return "asyncParams" in options ? await options.asyncParams() : options; + }); + + return prepareContractCall({ + contract: options.contract, + method: [FN_SELECTOR, FN_INPUTS, FN_OUTPUTS] as const, + params: async () => { + const resolvedOptions = await asyncOptions(); + return [ + resolvedOptions.receiver, + resolvedOptions.quantity, + resolvedOptions.currency, + resolvedOptions.pricePerToken, + resolvedOptions.allowlistProof, + resolvedOptions.data, + ] as const; + }, + value: async () => (await asyncOptions()).overrides?.value, + accessList: async () => (await asyncOptions()).overrides?.accessList, + gas: async () => (await asyncOptions()).overrides?.gas, + gasPrice: async () => (await asyncOptions()).overrides?.gasPrice, + maxFeePerGas: async () => (await asyncOptions()).overrides?.maxFeePerGas, + maxPriorityFeePerGas: async () => + (await asyncOptions()).overrides?.maxPriorityFeePerGas, + nonce: async () => (await asyncOptions()).overrides?.nonce, + extraGas: async () => (await asyncOptions()).overrides?.extraGas, + erc20Value: async () => (await asyncOptions()).overrides?.erc20Value, + }); +} diff --git a/packages/thirdweb/src/extensions/erc721/__generated__/IDropSinglePhase/write/setClaimConditions.ts b/packages/thirdweb/src/extensions/erc721/__generated__/IDropSinglePhase/write/setClaimConditions.ts new file mode 100644 index 00000000000..8f6a98c74b6 --- /dev/null +++ b/packages/thirdweb/src/extensions/erc721/__generated__/IDropSinglePhase/write/setClaimConditions.ts @@ -0,0 +1,203 @@ +import type { AbiParameterToPrimitiveType } from "abitype"; +import type { + BaseTransactionOptions, + WithOverrides, +} from "../../../../../transaction/types.js"; +import { prepareContractCall } from "../../../../../transaction/prepare-contract-call.js"; +import { encodeAbiParameters } from "../../../../../utils/abi/encodeAbiParameters.js"; +import { once } from "../../../../../utils/promise/once.js"; +import { detectMethod } from "../../../../../utils/bytecode/detectExtension.js"; + +/** + * Represents the parameters for the "setClaimConditions" function. + */ +export type SetClaimConditionsParams = WithOverrides<{ + phase: AbiParameterToPrimitiveType<{ + type: "tuple"; + name: "phase"; + components: [ + { type: "uint256"; name: "startTimestamp" }, + { type: "uint256"; name: "maxClaimableSupply" }, + { type: "uint256"; name: "supplyClaimed" }, + { type: "uint256"; name: "quantityLimitPerWallet" }, + { type: "bytes32"; name: "merkleRoot" }, + { type: "uint256"; name: "pricePerToken" }, + { type: "address"; name: "currency" }, + { type: "string"; name: "metadata" }, + ]; + }>; + resetClaimEligibility: AbiParameterToPrimitiveType<{ + type: "bool"; + name: "resetClaimEligibility"; + }>; +}>; + +export const FN_SELECTOR = "0x426cfaf3" as const; +const FN_INPUTS = [ + { + type: "tuple", + name: "phase", + components: [ + { + type: "uint256", + name: "startTimestamp", + }, + { + type: "uint256", + name: "maxClaimableSupply", + }, + { + type: "uint256", + name: "supplyClaimed", + }, + { + type: "uint256", + name: "quantityLimitPerWallet", + }, + { + type: "bytes32", + name: "merkleRoot", + }, + { + type: "uint256", + name: "pricePerToken", + }, + { + type: "address", + name: "currency", + }, + { + type: "string", + name: "metadata", + }, + ], + }, + { + type: "bool", + name: "resetClaimEligibility", + }, +] as const; +const FN_OUTPUTS = [] as const; + +/** + * Checks if the `setClaimConditions` method is supported by the given contract. + * @param availableSelectors An array of 4byte function selectors of the contract. You can get this in various ways, such as using "whatsabi" or if you have the ABI of the contract available you can use it to generate the selectors. + * @returns A boolean indicating if the `setClaimConditions` method is supported. + * @extension ERC721 + * @example + * ```ts + * import { isSetClaimConditionsSupported } from "thirdweb/extensions/erc721"; + * + * const supported = isSetClaimConditionsSupported(["0x..."]); + * ``` + */ +export function isSetClaimConditionsSupported(availableSelectors: string[]) { + return detectMethod({ + availableSelectors, + method: [FN_SELECTOR, FN_INPUTS, FN_OUTPUTS] as const, + }); +} + +/** + * Encodes the parameters for the "setClaimConditions" function. + * @param options - The options for the setClaimConditions function. + * @returns The encoded ABI parameters. + * @extension ERC721 + * @example + * ```ts + * import { encodeSetClaimConditionsParams } from "thirdweb/extensions/erc721"; + * const result = encodeSetClaimConditionsParams({ + * phase: ..., + * resetClaimEligibility: ..., + * }); + * ``` + */ +export function encodeSetClaimConditionsParams( + options: SetClaimConditionsParams, +) { + return encodeAbiParameters(FN_INPUTS, [ + options.phase, + options.resetClaimEligibility, + ]); +} + +/** + * Encodes the "setClaimConditions" function into a Hex string with its parameters. + * @param options - The options for the setClaimConditions function. + * @returns The encoded hexadecimal string. + * @extension ERC721 + * @example + * ```ts + * import { encodeSetClaimConditions } from "thirdweb/extensions/erc721"; + * const result = encodeSetClaimConditions({ + * phase: ..., + * resetClaimEligibility: ..., + * }); + * ``` + */ +export function encodeSetClaimConditions(options: SetClaimConditionsParams) { + // we do a "manual" concat here to avoid the overhead of the "concatHex" function + // we can do this because we know the specific formats of the values + return (FN_SELECTOR + + encodeSetClaimConditionsParams(options).slice( + 2, + )) as `${typeof FN_SELECTOR}${string}`; +} + +/** + * Prepares a transaction to call the "setClaimConditions" function on the contract. + * @param options - The options for the "setClaimConditions" function. + * @returns A prepared transaction object. + * @extension ERC721 + * @example + * ```ts + * import { sendTransaction } from "thirdweb"; + * import { setClaimConditions } from "thirdweb/extensions/erc721"; + * + * const transaction = setClaimConditions({ + * contract, + * phase: ..., + * resetClaimEligibility: ..., + * overrides: { + * ... + * } + * }); + * + * // Send the transaction + * await sendTransaction({ transaction, account }); + * ``` + */ +export function setClaimConditions( + options: BaseTransactionOptions< + | SetClaimConditionsParams + | { + asyncParams: () => Promise; + } + >, +) { + const asyncOptions = once(async () => { + return "asyncParams" in options ? await options.asyncParams() : options; + }); + + return prepareContractCall({ + contract: options.contract, + method: [FN_SELECTOR, FN_INPUTS, FN_OUTPUTS] as const, + params: async () => { + const resolvedOptions = await asyncOptions(); + return [ + resolvedOptions.phase, + resolvedOptions.resetClaimEligibility, + ] as const; + }, + value: async () => (await asyncOptions()).overrides?.value, + accessList: async () => (await asyncOptions()).overrides?.accessList, + gas: async () => (await asyncOptions()).overrides?.gas, + gasPrice: async () => (await asyncOptions()).overrides?.gasPrice, + maxFeePerGas: async () => (await asyncOptions()).overrides?.maxFeePerGas, + maxPriorityFeePerGas: async () => + (await asyncOptions()).overrides?.maxPriorityFeePerGas, + nonce: async () => (await asyncOptions()).overrides?.nonce, + extraGas: async () => (await asyncOptions()).overrides?.extraGas, + erc20Value: async () => (await asyncOptions()).overrides?.erc20Value, + }); +} diff --git a/packages/thirdweb/src/extensions/erc721/customDrop721.test.ts b/packages/thirdweb/src/extensions/erc721/customDrop721.test.ts new file mode 100644 index 00000000000..215c830e172 --- /dev/null +++ b/packages/thirdweb/src/extensions/erc721/customDrop721.test.ts @@ -0,0 +1,115 @@ +import { fetchDeployMetadata } from "../../utils/any-evm/deploy-metadata.js"; + +import { beforeAll, describe, expect, it } from "vitest"; +import { ANVIL_CHAIN } from "../../../test/src/chains.js"; +import { TEST_CLIENT } from "../../../test/src/test-clients.js"; +import { TEST_ACCOUNT_C } from "../../../test/src/test-wallets.js"; +import { type ThirdwebContract, getContract } from "../../contract/contract.js"; +import { sendAndConfirmTransaction } from "../../transaction/actions/send-and-confirm-transaction.js"; +import { deployContractfromDeployMetadata } from "../prebuilts/deploy-published.js"; +import { balanceOf } from "./__generated__/IERC721A/read/balanceOf.js"; +import { nextTokenIdToMint } from "./__generated__/IERC721Enumerable/read/nextTokenIdToMint.js"; +import { claimTo } from "./drops/write/claimTo.js"; +import { setClaimConditions } from "./drops/write/setClaimConditions.js"; +import { getNFT } from "./read/getNFT.js"; +import { lazyMint } from "./write/lazyMint.js"; + +describe.runIf(process.env.TW_SECRET_KEY)( + "CustomDropERC721", + { + retry: 0, + }, + () => { + let contract: ThirdwebContract; + + beforeAll(async () => { + const customDropDeployMetadata = await fetchDeployMetadata({ + client: TEST_CLIENT, + uri: "ipfs://QmY6CHFuhDpQzuv3y3NyQBPdAa7cYjZGJgFeFcw9BfSMQx", + }); + const contractAddress = await deployContractfromDeployMetadata({ + chain: ANVIL_CHAIN, + client: TEST_CLIENT, + account: TEST_ACCOUNT_C, + deployMetadata: customDropDeployMetadata, + initializeParams: { + defaultAdmin: TEST_ACCOUNT_C.address, + name: "TestCustomDropERC721", + symbol: "TT", + }, + }); + + contract = getContract({ + address: contractAddress, + chain: ANVIL_CHAIN, + client: TEST_CLIENT, + }); + // this deploys a contract, it may take some time + }, 60_000); + + it("should allow for lazy minting tokens", async () => { + const mintTx = lazyMint({ + contract, + nfts: [ + { name: "Test NFT" }, + { name: "Test NFT 2" }, + { name: "Test NFT 3" }, + { name: "Test NFT 4" }, + { name: "Test NFT 5" }, + { name: "Test NFT 6" }, + ], + }); + await sendAndConfirmTransaction({ + transaction: mintTx, + account: TEST_ACCOUNT_C, + }); + + await expect(nextTokenIdToMint({ contract })).resolves.toBe(6n); + await expect( + getNFT({ contract, tokenId: 0n }), + ).resolves.toMatchInlineSnapshot(` + { + "id": 0n, + "metadata": { + "name": "Test NFT", + }, + "owner": null, + "tokenURI": "ipfs://QmTo68Dm1ntSp2BHLmE9gesS6ELuXosRz5mAgFCK6tfsRk/0", + "type": "ERC721", + } + `); + }); + + it("should allow to claim tokens", async () => { + await expect( + balanceOf({ contract, owner: TEST_ACCOUNT_C.address }), + ).resolves.toBe(0n); + await sendAndConfirmTransaction({ + transaction: setClaimConditions({ + contract, + phases: [ + { + startTime: new Date(0), + maxClaimableSupply: 10n, + }, + ], + singlePhaseDrop: true, + }), + account: TEST_ACCOUNT_C, + }); + const claimTx = claimTo({ + contract, + to: TEST_ACCOUNT_C.address, + quantity: 1n, + singlePhaseDrop: true, + }); + await sendAndConfirmTransaction({ + transaction: claimTx, + account: TEST_ACCOUNT_C, + }); + await expect( + balanceOf({ contract, owner: TEST_ACCOUNT_C.address }), + ).resolves.toBe(1n); + }); + }, +); diff --git a/packages/thirdweb/src/extensions/erc721/drops/write/claimTo.ts b/packages/thirdweb/src/extensions/erc721/drops/write/claimTo.ts index bde7fa7c129..0d9c2e996ce 100644 --- a/packages/thirdweb/src/extensions/erc721/drops/write/claimTo.ts +++ b/packages/thirdweb/src/extensions/erc721/drops/write/claimTo.ts @@ -6,6 +6,7 @@ import { claim, isClaimSupported, } from "../../__generated__/IDrop/write/claim.js"; +import { isClaimConditionSupported } from "../../__generated__/IDropSinglePhase/read/claimCondition.js"; import { isGetActiveClaimConditionSupported } from "../read/getActiveClaimCondition.js"; /** @@ -16,6 +17,7 @@ export type ClaimToParams = { to: Address; quantity: bigint; from?: Address; + singlePhaseDrop?: boolean; }; /** @@ -61,6 +63,7 @@ export function claimTo(options: BaseTransactionOptions) { to: options.to, quantity: options.quantity, from: options.from, + singlePhaseDrop: options.singlePhaseDrop, }), }); } @@ -80,9 +83,10 @@ export function claimTo(options: BaseTransactionOptions) { export function isClaimToSupported(availableSelectors: string[]) { return ( isClaimSupported(availableSelectors) && - // required to check if the contract supports the getActiveClaimCondition method - isGetActiveClaimConditionSupported(availableSelectors) && // requires contractMetadata for claimer proofs - isGetContractMetadataSupported(availableSelectors) + isGetContractMetadataSupported(availableSelectors) && + // required to check if the contract supports the getActiveClaimCondition method + (isGetActiveClaimConditionSupported(availableSelectors) || + isClaimConditionSupported(availableSelectors)) ); } diff --git a/packages/thirdweb/src/extensions/erc721/drops/write/setClaimConditions.ts b/packages/thirdweb/src/extensions/erc721/drops/write/setClaimConditions.ts index a070744c001..5a0ccd5f1c0 100644 --- a/packages/thirdweb/src/extensions/erc721/drops/write/setClaimConditions.ts +++ b/packages/thirdweb/src/extensions/erc721/drops/write/setClaimConditions.ts @@ -8,6 +8,7 @@ import { } from "../../../common/__generated__/IMulticall/write/multicall.js"; import { isGetContractMetadataSupported } from "../../../common/read/getContractMetadata.js"; import { isSetClaimConditionsSupported as isSetClaimConditionsSupportedGenerated } from "../../__generated__/IDrop/write/setClaimConditions.js"; +import { isSetClaimConditionsSupported as isSetClaimConditionsSupportedGeneratedSinglePhase } from "../../__generated__/IDropSinglePhase/write/setClaimConditions.js"; /** * @extension ERC721 @@ -15,6 +16,7 @@ import { isSetClaimConditionsSupported as isSetClaimConditionsSupportedGenerated export type SetClaimConditionsParams = { phases: ClaimConditionsInput[]; resetClaimEligibility?: boolean; + singlePhaseDrop?: boolean; }; /** @@ -55,6 +57,7 @@ export function setClaimConditions( phases: options.phases, resetClaimEligibility: options.resetClaimEligibility, tokenDecimals: 0, + singlePhase: options.singlePhaseDrop, }), }; }, @@ -80,6 +83,7 @@ export function isSetClaimConditionsSupported(availableSelectors: string[]) { isGetContractMetadataSupported(availableSelectors) && isSetContractURISupported(availableSelectors) && // needs to actually be able to set the claim Conditions - isSetClaimConditionsSupportedGenerated(availableSelectors) + (isSetClaimConditionsSupportedGenerated(availableSelectors) || + isSetClaimConditionsSupportedGeneratedSinglePhase(availableSelectors)) ); } diff --git a/packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts b/packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts index 49d10e71dc7..baae4e66802 100644 --- a/packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts +++ b/packages/thirdweb/src/utils/extensions/drops/get-claim-params.ts @@ -6,13 +6,14 @@ import { import type { ThirdwebContract } from "../../../contract/contract.js"; import { getContractMetadata } from "../../../extensions/common/read/getContractMetadata.js"; import type { Hex } from "../../encoding/hex.js"; -import type { OverrideProof } from "./types.js"; +import type { ClaimCondition, OverrideProof } from "./types.js"; export type GetClaimParamsOptions = { contract: ThirdwebContract; to: string; quantity: bigint; from?: string; + singlePhaseDrop?: boolean; } & ( | { type: "erc721"; @@ -47,9 +48,18 @@ export type GetClaimParamsOptions = { * @extension COMMON */ export async function getClaimParams(options: GetClaimParamsOptions) { - const cc = await (async () => { + const cc: ClaimCondition = await (async () => { if (options.type === "erc1155") { // lazy-load the getActiveClaimCondition function + if (options.singlePhaseDrop) { + const { claimCondition } = await import( + "../../../extensions/erc1155/__generated__/IDropSinglePhase1155/read/claimCondition.js" + ); + return await claimCondition({ + contract: options.contract, + tokenId: options.tokenId, + }); + } const { getActiveClaimCondition } = await import( "../../../extensions/erc1155/drops/read/getActiveClaimCondition.js" ); @@ -60,6 +70,14 @@ export async function getClaimParams(options: GetClaimParamsOptions) { } if (options.type === "erc721") { // lazy-load the getActiveClaimCondition function + if (options.singlePhaseDrop) { + const { claimCondition } = await import( + "../../../extensions/erc721/__generated__/IDropSinglePhase/read/claimCondition.js" + ); + return await claimCondition({ + contract: options.contract, + }); + } const { getActiveClaimCondition } = await import( "../../../extensions/erc721/drops/read/getActiveClaimCondition.js" ); @@ -71,6 +89,15 @@ export async function getClaimParams(options: GetClaimParamsOptions) { // otherwise erc20 case! // lazy-load the getActiveClaimCondition function + if (options.singlePhaseDrop) { + // same ABI as erc721 + const { claimCondition } = await import( + "../../../extensions/erc721/__generated__/IDropSinglePhase/read/claimCondition.js" + ); + return await claimCondition({ + contract: options.contract, + }); + } const { getActiveClaimCondition } = await import( "../../../extensions/erc20/drops/read/getActiveClaimCondition.js" ); diff --git a/packages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts b/packages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts index f3c2f422b31..1bef0e19013 100644 --- a/packages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts +++ b/packages/thirdweb/src/utils/extensions/drops/get-multicall-set-claim-claim-conditon-transactions.ts @@ -15,6 +15,7 @@ export async function getMulticallSetClaimConditionTransactions(options: { tokenDecimals: number; tokenId?: bigint; resetClaimEligibility?: boolean; + singlePhase?: boolean; }): Promise { const merkleInfos: Record = {}; const phases = await Promise.all( @@ -94,23 +95,52 @@ export async function getMulticallSetClaimConditionTransactions(options: { let encodedSetClaimConditions: Hex; if (options.tokenId !== undefined) { // 1155 - const { encodeSetClaimConditions } = await import( - "../../../extensions/erc1155/__generated__/IDrop1155/write/setClaimConditions.js" - ); - encodedSetClaimConditions = encodeSetClaimConditions({ - tokenId: options.tokenId, - phases: sortedPhases, - resetClaimEligibility: options.resetClaimEligibility || false, - }); + if (options.singlePhase) { + const { encodeSetClaimConditions } = await import( + "../../../extensions/erc1155/__generated__/IDropSinglePhase1155/write/setClaimConditions.js" + ); + const phase = sortedPhases[0]; + if (!phase) { + throw new Error("No phase provided"); + } + encodedSetClaimConditions = encodeSetClaimConditions({ + tokenId: options.tokenId, + phase, + resetClaimEligibility: options.resetClaimEligibility || false, + }); + } else { + const { encodeSetClaimConditions } = await import( + "../../../extensions/erc1155/__generated__/IDrop1155/write/setClaimConditions.js" + ); + encodedSetClaimConditions = encodeSetClaimConditions({ + tokenId: options.tokenId, + phases: sortedPhases, + resetClaimEligibility: options.resetClaimEligibility || false, + }); + } } else { - // 721 - const { encodeSetClaimConditions } = await import( - "../../../extensions/erc721/__generated__/IDrop/write/setClaimConditions.js" - ); - encodedSetClaimConditions = encodeSetClaimConditions({ - phases: sortedPhases, - resetClaimEligibility: options.resetClaimEligibility || false, - }); + // erc721 or erc20 + if (options.singlePhase) { + const { encodeSetClaimConditions } = await import( + "../../../extensions/erc721/__generated__/IDropSinglePhase/write/setClaimConditions.js" + ); + const phase = sortedPhases[0]; + if (!phase) { + throw new Error("No phase provided"); + } + encodedSetClaimConditions = encodeSetClaimConditions({ + phase, + resetClaimEligibility: options.resetClaimEligibility || false, + }); + } else { + const { encodeSetClaimConditions } = await import( + "../../../extensions/erc721/__generated__/IDrop/write/setClaimConditions.js" + ); + encodedSetClaimConditions = encodeSetClaimConditions({ + phases: sortedPhases, + resetClaimEligibility: options.resetClaimEligibility || false, + }); + } } encodedTransactions.push(encodedSetClaimConditions); return encodedTransactions;