From 41f04bb1d3b38a3f2c48a742ee9148afe70f63ea Mon Sep 17 00:00:00 2001 From: Blockiosaurus Date: Thu, 8 Aug 2024 12:05:13 -0400 Subject: [PATCH 1/3] Adding a guard for vanity mint addresses. --- clients/js/src/defaultGuards/default.ts | 5 + clients/js/src/defaultGuards/index.ts | 1 + clients/js/src/defaultGuards/vanityMint.ts | 34 +++ clients/js/src/errors.ts | 11 + .../src/generated/errors/mplCoreCandyGuard.ts | 43 ++++ clients/js/src/generated/types/guardType.ts | 1 + clients/js/src/generated/types/index.ts | 1 + clients/js/src/generated/types/vanityMint.ts | 27 +++ clients/js/src/plugin.ts | 4 +- .../js/test/defaultGuards/vanityMint.test.ts | 221 ++++++++++++++++++ idls/candy_guard.json | 44 ++++ programs/candy-guard/Cargo.lock | 23 +- programs/candy-guard/program/Cargo.toml | 27 ++- programs/candy-guard/program/src/errors.rs | 9 + .../candy-guard/program/src/guards/mod.rs | 2 + .../program/src/guards/vanity_mint.rs | 61 +++++ .../program/src/state/candy_guard.rs | 3 + 17 files changed, 497 insertions(+), 20 deletions(-) create mode 100644 clients/js/src/defaultGuards/vanityMint.ts create mode 100644 clients/js/src/generated/types/vanityMint.ts create mode 100644 clients/js/test/defaultGuards/vanityMint.test.ts create mode 100644 programs/candy-guard/program/src/guards/vanity_mint.rs diff --git a/clients/js/src/defaultGuards/default.ts b/clients/js/src/defaultGuards/default.ts index ce97c13..1fd40fe 100644 --- a/clients/js/src/defaultGuards/default.ts +++ b/clients/js/src/defaultGuards/default.ts @@ -60,6 +60,8 @@ import { TokenGateArgs, TokenPayment, TokenPaymentArgs, + VanityMint, + VanityMintArgs, } from '../generated'; import { GuardSet, @@ -131,6 +133,7 @@ export type DefaultGuardSetArgs = GuardSetArgs & { assetBurnMulti: OptionOrNullable; assetPaymentMulti: OptionOrNullable; assetGate: OptionOrNullable; + vanityMint: OptionOrNullable; }; /** @@ -167,6 +170,7 @@ export type DefaultGuardSet = GuardSet & { assetBurnMulti: Option; assetPaymentMulti: Option; assetGate: Option; + vanityMint: Option; }; /** @@ -264,6 +268,7 @@ export const defaultCandyGuardNames: string[] = [ 'assetBurnMulti', 'assetPaymentMulti', 'assetGate', + 'vanityMint', ]; /** @internal */ diff --git a/clients/js/src/defaultGuards/index.ts b/clients/js/src/defaultGuards/index.ts index 12bd9f0..a6f5fee 100644 --- a/clients/js/src/defaultGuards/index.ts +++ b/clients/js/src/defaultGuards/index.ts @@ -29,3 +29,4 @@ export * from './assetMintLimit'; export * from './assetBurnMulti'; export * from './assetPaymentMulti'; export * from './assetGate'; +export * from './vanityMint'; diff --git a/clients/js/src/defaultGuards/vanityMint.ts b/clients/js/src/defaultGuards/vanityMint.ts new file mode 100644 index 0000000..a4edf26 --- /dev/null +++ b/clients/js/src/defaultGuards/vanityMint.ts @@ -0,0 +1,34 @@ +import { + fixSerializer, + mapSerializer, +} from '@metaplex-foundation/umi/serializers'; +import { ExceededRegexLengthError } from '../errors'; +import { + getVanityMintSerializer, + VanityMint, + VanityMintArgs, +} from '../generated'; +import { GuardManifest, noopParser } from '../guards'; + +/** + * The vanityMint guard determines the start date of the mint. + * Before this date, minting is not allowed. + */ +export const vanityMintGuardManifest: GuardManifest< + VanityMintArgs, + VanityMint +> = { + name: 'vanityMint', + serializer: () => + mapSerializer( + fixSerializer(getVanityMintSerializer(), 4 + 100), + (value) => { + if (value.regex.length > 100) { + throw new ExceededRegexLengthError(); + } + return value; + } + ), + mintParser: noopParser, + routeParser: noopParser, +}; diff --git a/clients/js/src/errors.ts b/clients/js/src/errors.ts index 76f9bdb..beeb970 100644 --- a/clients/js/src/errors.ts +++ b/clients/js/src/errors.ts @@ -161,3 +161,14 @@ export class MaximumOfFiveAdditionalProgramsError extends CandyMachineError { super(message); } } + +export class ExceededRegexLengthError extends CandyMachineError { + readonly name: string = 'ExceededRegexLengthError'; + + constructor() { + const message = + `The maximum length of a regex that can be used is 100 characters. ` + + 'Please reduce the length of the regex to <= 100.'; + super(message); + } +} diff --git a/clients/js/src/generated/errors/mplCoreCandyGuard.ts b/clients/js/src/generated/errors/mplCoreCandyGuard.ts index eb6e127..58268aa 100644 --- a/clients/js/src/generated/errors/mplCoreCandyGuard.ts +++ b/clients/js/src/generated/errors/mplCoreCandyGuard.ts @@ -731,6 +731,49 @@ export class CgInvalidAccountVersionError extends ProgramError { codeToErrorMap.set(0x17a3, CgInvalidAccountVersionError); nameToErrorMap.set('InvalidAccountVersion', CgInvalidAccountVersionError); +/** ExceededRegexLength: Exceeded the maximum length of a regex that can be used */ +export class CgExceededRegexLengthError extends ProgramError { + readonly name: string = 'ExceededRegexLength'; + + readonly code: number = 0x17a4; // 6052 + + constructor(program: Program, cause?: Error) { + super( + 'Exceeded the maximum length of a regex that can be used', + program, + cause + ); + } +} +codeToErrorMap.set(0x17a4, CgExceededRegexLengthError); +nameToErrorMap.set('ExceededRegexLength', CgExceededRegexLengthError); + +/** InvalidVanityAddress: Invalid vanity address */ +export class CgInvalidVanityAddressError extends ProgramError { + readonly name: string = 'InvalidVanityAddress'; + + readonly code: number = 0x17a5; // 6053 + + constructor(program: Program, cause?: Error) { + super('Invalid vanity address', program, cause); + } +} +codeToErrorMap.set(0x17a5, CgInvalidVanityAddressError); +nameToErrorMap.set('InvalidVanityAddress', CgInvalidVanityAddressError); + +/** InvalidRegex: Invalid regex */ +export class CgInvalidRegexError extends ProgramError { + readonly name: string = 'InvalidRegex'; + + readonly code: number = 0x17a6; // 6054 + + constructor(program: Program, cause?: Error) { + super('Invalid regex', program, cause); + } +} +codeToErrorMap.set(0x17a6, CgInvalidRegexError); +nameToErrorMap.set('InvalidRegex', CgInvalidRegexError); + /** * Attempts to resolve a custom program error from the provided error code. * @category Errors diff --git a/clients/js/src/generated/types/guardType.ts b/clients/js/src/generated/types/guardType.ts index 9db9ba1..3db7d15 100644 --- a/clients/js/src/generated/types/guardType.ts +++ b/clients/js/src/generated/types/guardType.ts @@ -40,6 +40,7 @@ export enum GuardType { AssetBurnMulti, AssetPaymentMulti, AssetGate, + VanityMint, } export type GuardTypeArgs = GuardType; diff --git a/clients/js/src/generated/types/index.ts b/clients/js/src/generated/types/index.ts index 3e94d6c..5bd9232 100644 --- a/clients/js/src/generated/types/index.ts +++ b/clients/js/src/generated/types/index.ts @@ -42,3 +42,4 @@ export * from './token2022Payment'; export * from './tokenBurn'; export * from './tokenGate'; export * from './tokenPayment'; +export * from './vanityMint'; diff --git a/clients/js/src/generated/types/vanityMint.ts b/clients/js/src/generated/types/vanityMint.ts new file mode 100644 index 0000000..441beae --- /dev/null +++ b/clients/js/src/generated/types/vanityMint.ts @@ -0,0 +1,27 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + Serializer, + string, + struct, +} from '@metaplex-foundation/umi/serializers'; + +/** Guard that sets a specific start date for the mint. */ +export type VanityMint = { regex: string }; + +export type VanityMintArgs = VanityMint; + +export function getVanityMintSerializer(): Serializer< + VanityMintArgs, + VanityMint +> { + return struct([['regex', string()]], { + description: 'VanityMint', + }) as Serializer; +} diff --git a/clients/js/src/plugin.ts b/clients/js/src/plugin.ts index a381731..783a335 100644 --- a/clients/js/src/plugin.ts +++ b/clients/js/src/plugin.ts @@ -32,6 +32,7 @@ import { assetBurnMultiGuardManifest, assetPaymentMultiGuardManifest, assetGateGuardManifest, + vanityMintGuardManifest, } from './defaultGuards'; import { createMplCoreCandyGuardProgram, @@ -95,7 +96,8 @@ export const mplCandyMachine = (): UmiPlugin => ({ assetMintLimitGuardManifest, assetBurnMultiGuardManifest, assetPaymentMultiGuardManifest, - assetGateGuardManifest + assetGateGuardManifest, + vanityMintGuardManifest ); }, }); diff --git a/clients/js/test/defaultGuards/vanityMint.test.ts b/clients/js/test/defaultGuards/vanityMint.test.ts new file mode 100644 index 0000000..e726640 --- /dev/null +++ b/clients/js/test/defaultGuards/vanityMint.test.ts @@ -0,0 +1,221 @@ +import { setComputeUnitLimit } from '@metaplex-foundation/mpl-toolbox'; +import { + generateSigner, + sol, + some, + transactionBuilder, +} from '@metaplex-foundation/umi'; +import test from 'ava'; +import { mintV1 } from '../../src'; +import { + assertBotTax, + assertSuccessfulMint, + createCollection, + createUmi, + createV2, +} from '../_setup'; + +test('it can mint an asset that starts with a specific pattern', async (t) => { + // Given a candy machine with a start date in the past. + const umi = await createUmi(); + const mint = generateSigner(umi); + const collection = (await createCollection(umi)).publicKey; + const { publicKey: candyMachine } = await createV2(umi, { + collection, + configLines: [{ name: 'Degen #1', uri: 'https://example.com/degen/1' }], + guards: { + vanityMint: some({ regex: `^${mint.publicKey.toString().slice(0, 5)}` }), + }, + }); + + // When we mint from it. + await transactionBuilder() + .add(setComputeUnitLimit(umi, { units: 600_000 })) + .add( + mintV1(umi, { + candyMachine, + asset: mint, + collection, + }) + ) + .sendAndConfirm(umi); + + // Then the mint was successful. + await assertSuccessfulMint(t, umi, { mint, owner: umi.identity }); +}); + +test('it cannot mint an asset that does not start with a specific pattern', async (t) => { + // Given a candy machine with a start date in the past. + const umi = await createUmi(); + const mint = generateSigner(umi); + const collection = (await createCollection(umi)).publicKey; + const { publicKey: candyMachine } = await createV2(umi, { + collection, + configLines: [{ name: 'Degen #1', uri: 'https://example.com/degen/1' }], + guards: { + vanityMint: some({ regex: `^OOOO` }), + }, + }); + + // When we mint from it. + const promise = transactionBuilder() + .add(setComputeUnitLimit(umi, { units: 600_000 })) + .add( + mintV1(umi, { + candyMachine, + asset: mint, + collection, + }) + ) + .sendAndConfirm(umi); + + // Then the mint was unsuccessful. + await t.throwsAsync(promise, { message: /InvalidVanityAddress/ }); +}); + +test('it can mint an asset that ends with a specific pattern', async (t) => { + // Given a candy machine with a start date in the past. + const umi = await createUmi(); + const mint = generateSigner(umi); + const collection = (await createCollection(umi)).publicKey; + const { publicKey: candyMachine } = await createV2(umi, { + collection, + configLines: [{ name: 'Degen #1', uri: 'https://example.com/degen/1' }], + guards: { + vanityMint: some({ regex: `${mint.publicKey.toString().slice(-4)}$` }), + }, + }); + + // When we mint from it. + await transactionBuilder() + .add(setComputeUnitLimit(umi, { units: 600_000 })) + .add( + mintV1(umi, { + candyMachine, + asset: mint, + collection, + }) + ) + .sendAndConfirm(umi); + + // Then the mint was successful. + await assertSuccessfulMint(t, umi, { mint, owner: umi.identity }); +}); + +test('it cannot mint an asset that does not end with a specific pattern', async (t) => { + // Given a candy machine with a start date in the past. + const umi = await createUmi(); + const mint = generateSigner(umi); + const collection = (await createCollection(umi)).publicKey; + const { publicKey: candyMachine } = await createV2(umi, { + collection, + configLines: [{ name: 'Degen #1', uri: 'https://example.com/degen/1' }], + guards: { + vanityMint: some({ regex: `OOOO$` }), + }, + }); + + // When we mint from it. + const promise = transactionBuilder() + .add(setComputeUnitLimit(umi, { units: 600_000 })) + .add( + mintV1(umi, { + candyMachine, + asset: mint, + collection, + }) + ) + .sendAndConfirm(umi); + + // Then the mint was unsuccessful. + await t.throwsAsync(promise, { message: /InvalidVanityAddress/ }); +}); + +test('it can mint an asset that exactly matches a specific pattern', async (t) => { + // Given a candy machine with a start date in the past. + const umi = await createUmi(); + const mint = generateSigner(umi); + const collection = (await createCollection(umi)).publicKey; + const { publicKey: candyMachine } = await createV2(umi, { + collection, + configLines: [{ name: 'Degen #1', uri: 'https://example.com/degen/1' }], + guards: { + vanityMint: some({ regex: `^${mint.publicKey.toString()}$` }), + }, + }); + + // When we mint from it. + await transactionBuilder() + .add(setComputeUnitLimit(umi, { units: 600_000 })) + .add( + mintV1(umi, { + candyMachine, + asset: mint, + collection, + }) + ) + .sendAndConfirm(umi); + + // Then the mint was successful. + await assertSuccessfulMint(t, umi, { mint, owner: umi.identity }); +}); + +test('it cannot mint an asset that does not exactly match a specific pattern', async (t) => { + // Given a candy machine with a start date in the past. + const umi = await createUmi(); + const mint = generateSigner(umi); + const collection = (await createCollection(umi)).publicKey; + const { publicKey: candyMachine } = await createV2(umi, { + collection, + configLines: [{ name: 'Degen #1', uri: 'https://example.com/degen/1' }], + guards: { + vanityMint: some({ regex: `^${mint.publicKey.toString().slice(1)}$` }), + }, + }); + + // When we mint from it. + const promise = transactionBuilder() + .add(setComputeUnitLimit(umi, { units: 600_000 })) + .add( + mintV1(umi, { + candyMachine, + asset: mint, + collection, + }) + ) + .sendAndConfirm(umi); + + // Then the mint was unsuccessful. + await t.throwsAsync(promise, { message: /InvalidVanityAddress/ }); +}); + +test('it charges a bot tax when trying to mint an asset that starts with a specific pattern', async (t) => { + // Given a candy machine with a bot tax and start date in the future. + const umi = await createUmi(); + const collection = (await createCollection(umi)).publicKey; + const { publicKey: candyMachine } = await createV2(umi, { + collection, + configLines: [{ name: 'Degen #1', uri: 'https://example.com/degen/1' }], + guards: { + botTax: some({ lamports: sol(0.01), lastInstruction: true }), + // O is not valid in bs58, so this will always fail. + vanityMint: some({ regex: `^OOOO` }), + }, + }); + + // When we mint from it. + const mint = generateSigner(umi); + const { signature } = await transactionBuilder() + .add(setComputeUnitLimit(umi, { units: 600_000 })) + .add( + mintV1(umi, { + candyMachine, + asset: mint, + collection, + }) + ) + .sendAndConfirm(umi); + + // Then we expect a silent bot tax error. + await assertBotTax(t, umi, mint, signature, /InvalidVanityAddress/); +}); diff --git a/idls/candy_guard.json b/idls/candy_guard.json index 6920b2c..fab6b37 100644 --- a/idls/candy_guard.json +++ b/idls/candy_guard.json @@ -1280,6 +1280,21 @@ ] } }, + { + "name": "VanityMint", + "docs": [ + "Guard that sets a specific start date for the mint." + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "regex", + "type": "string" + } + ] + } + }, { "name": "RouteArgs", "docs": [ @@ -1690,6 +1705,17 @@ "defined": "AssetGate" } } + }, + { + "name": "vanityMint", + "docs": [ + "Vanity Mint (the address of the new asset must match a pattern)." + ], + "type": { + "option": { + "defined": "VanityMint" + } + } } ] } @@ -1808,6 +1834,9 @@ }, { "name": "AssetGate" + }, + { + "name": "VanityMint" } ] } @@ -2073,6 +2102,21 @@ "code": 6051, "name": "InvalidAccountVersion", "msg": "Invalid account version" + }, + { + "code": 6052, + "name": "ExceededRegexLength", + "msg": "Exceeded the maximum length of a regex that can be used" + }, + { + "code": 6053, + "name": "InvalidVanityAddress", + "msg": "Invalid vanity address" + }, + { + "code": 6054, + "name": "InvalidRegex", + "msg": "Invalid regex" } ], "metadata": { diff --git a/programs/candy-guard/Cargo.lock b/programs/candy-guard/Cargo.lock index 06dc934..bf578cf 100644 --- a/programs/candy-guard/Cargo.lock +++ b/programs/candy-guard/Cargo.lock @@ -1197,9 +1197,9 @@ checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memmap2" @@ -1255,6 +1255,7 @@ dependencies = [ "mpl-core-candy-guard-derive", "mpl-core-candy-machine-core", "mpl-token-metadata", + "regex-lite", "solana-gateway", "solana-program", "spl-associated-token-account", @@ -1631,9 +1632,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.3" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", @@ -1643,20 +1644,26 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + [[package]] name = "regex-syntax" -version = "0.7.4" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "rustc-hash" diff --git a/programs/candy-guard/program/Cargo.toml b/programs/candy-guard/program/Cargo.toml index a628d05..a960256 100644 --- a/programs/candy-guard/program/Cargo.toml +++ b/programs/candy-guard/program/Cargo.toml @@ -1,31 +1,36 @@ [package] -name = "mpl-core-candy-guard" -version = "0.2.1" -description = "Metaplex Candy Guard: programmatic access control for Candy Machine." authors = ["Metaplex Developers "] -repository = "https://github.com/metaplex-foundation/mpl-candy-machine-asset" -license-file = "../../../LICENSE" +description = "Metaplex Candy Guard: programmatic access control for Candy Machine." edition = "2021" +license-file = "../../../LICENSE" +name = "mpl-core-candy-guard" readme = "../README.md" +repository = "https://github.com/metaplex-foundation/mpl-candy-machine-asset" +version = "0.2.1" [lib] crate-type = ["cdylib", "lib"] [features] -no-entrypoint = [] -test-bpf = [] cpi = ["no-entrypoint"] default = [] +no-entrypoint = [] +test-bpf = [] [dependencies] anchor-lang = "0.28.0" arrayref = "0.3.6" +mpl-core = { version = "0.6.1" } mpl-core-candy-guard-derive = { path = "../macro", version = "0.2.1" } -mpl-core-candy-machine-core = { path = "../../candy-machine-core/program", version = "0.2.0", features = ["cpi"] } +mpl-core-candy-machine-core = { path = "../../candy-machine-core/program", version = "0.2.0", features = [ + "cpi", +] } mpl-token-metadata = "3.2.1" -mpl-core = { version = "0.6.1" } +regex-lite = "0.1.6" +solana-gateway = { version = "0.4.0", features = ["no-entrypoint"] } solana-program = "~1.16.5" -spl-associated-token-account = { version = ">= 1.1.3, < 3.0", features = ["no-entrypoint"] } +spl-associated-token-account = { version = ">= 1.1.3, < 3.0", features = [ + "no-entrypoint", +] } spl-token = { version = ">= 3.5.0, < 5.0", features = ["no-entrypoint"] } spl-token-2022 = { version = ">= 0.6", features = ["no-entrypoint"] } -solana-gateway = { version = "0.4.0", features = ["no-entrypoint"] } diff --git a/programs/candy-guard/program/src/errors.rs b/programs/candy-guard/program/src/errors.rs index 8311452..b80b539 100644 --- a/programs/candy-guard/program/src/errors.rs +++ b/programs/candy-guard/program/src/errors.rs @@ -157,4 +157,13 @@ pub enum CandyGuardError { #[msg("Invalid account version")] InvalidAccountVersion, + + #[msg("Exceeded the maximum length of a regex that can be used")] + ExceededRegexLength, + + #[msg("Invalid vanity address")] + InvalidVanityAddress, + + #[msg("Invalid regex")] + InvalidRegex, } diff --git a/programs/candy-guard/program/src/guards/mod.rs b/programs/candy-guard/program/src/guards/mod.rs index 2986da3..1113fe3 100644 --- a/programs/candy-guard/program/src/guards/mod.rs +++ b/programs/candy-guard/program/src/guards/mod.rs @@ -43,6 +43,7 @@ pub use token2022_payment::Token2022Payment; pub use token_burn::TokenBurn; pub use token_gate::TokenGate; pub use token_payment::TokenPayment; +pub use vanity_mint::VanityMint; mod address_gate; mod allocation; @@ -74,6 +75,7 @@ mod token2022_payment; mod token_burn; mod token_gate; mod token_payment; +mod vanity_mint; pub trait Condition { /// Validate the condition of the guard. When the guard condition is diff --git a/programs/candy-guard/program/src/guards/vanity_mint.rs b/programs/candy-guard/program/src/guards/vanity_mint.rs new file mode 100644 index 0000000..c816626 --- /dev/null +++ b/programs/candy-guard/program/src/guards/vanity_mint.rs @@ -0,0 +1,61 @@ +use regex_lite::Regex; + +use crate::state::GuardType; + +use super::*; + +const MAXIMUM_LENGTH: usize = 100; + +/// Guard that sets a specific start date for the mint. +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)] +pub struct VanityMint { + pub regex: String, +} + +impl Guard for VanityMint { + fn size() -> usize { + 4 + // String prefix + 100 // MAXIMUM_LENGTH + } + + fn mask() -> u64 { + GuardType::as_mask(GuardType::VanityMint) + } + + fn verify(data: &CandyGuardData) -> Result<()> { + if let Some(vanity_mint) = &data.default.vanity_mint { + if vanity_mint.regex.len() > MAXIMUM_LENGTH { + return err!(CandyGuardError::ExceededRegexLength); + } + } + + if let Some(groups) = &data.groups { + for group in groups { + if let Some(vanity_mint) = &group.guards.vanity_mint { + if vanity_mint.regex.len() > MAXIMUM_LENGTH { + return err!(CandyGuardError::ExceededRegexLength); + } + } + } + } + + Ok(()) + } +} + +impl Condition for VanityMint { + fn validate<'info>( + &self, + ctx: &mut EvaluationContext, + _guard_set: &GuardSet, + _mint_args: &[u8], + ) -> Result<()> { + let mint_address = ctx.accounts.asset.key().to_string(); + let regex = Regex::new(&self.regex).map_err(|_| CandyGuardError::InvalidRegex)?; + if !regex.is_match(&mint_address) { + return err!(CandyGuardError::InvalidVanityAddress); + } + + Ok(()) + } +} diff --git a/programs/candy-guard/program/src/state/candy_guard.rs b/programs/candy-guard/program/src/state/candy_guard.rs index 38fe5d5..f7c650d 100644 --- a/programs/candy-guard/program/src/state/candy_guard.rs +++ b/programs/candy-guard/program/src/state/candy_guard.rs @@ -142,6 +142,8 @@ pub struct GuardSet { pub asset_payment_multi: Option, /// Asset Gate (restrict access to holders of a specific asset). pub asset_gate: Option, + /// Vanity Mint (the address of the new asset must match a pattern). + pub vanity_mint: Option, } /// Available guard types. @@ -177,6 +179,7 @@ pub enum GuardType { AssetBurnMulti, AssetPaymentMulti, AssetGate, + VanityMint, } impl GuardType { From 8c70aac090d90331300634b4f5c09c151aae9872 Mon Sep 17 00:00:00 2001 From: Blockiosaurus Date: Thu, 8 Aug 2024 12:15:45 -0400 Subject: [PATCH 2/3] Fixing comment. --- clients/js/src/defaultGuards/vanityMint.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/js/src/defaultGuards/vanityMint.ts b/clients/js/src/defaultGuards/vanityMint.ts index a4edf26..8be9df7 100644 --- a/clients/js/src/defaultGuards/vanityMint.ts +++ b/clients/js/src/defaultGuards/vanityMint.ts @@ -11,8 +11,8 @@ import { import { GuardManifest, noopParser } from '../guards'; /** - * The vanityMint guard determines the start date of the mint. - * Before this date, minting is not allowed. + * The vanityMint guard verifies that the new asset + * address matches the provided regex. */ export const vanityMintGuardManifest: GuardManifest< VanityMintArgs, From f4963ca565399b3cb2a27639138b1c17daf4cb68 Mon Sep 17 00:00:00 2001 From: Blockiosaurus Date: Thu, 8 Aug 2024 13:53:08 -0400 Subject: [PATCH 3/3] Bump solana version. --- .github/.env | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/.env b/.github/.env index c02d90b..ddafd7c 100644 --- a/.github/.env +++ b/.github/.env @@ -2,8 +2,8 @@ CARGO_TERM_COLOR=always NODE_VERSION=16.x PROGRAMS=["candy-machine-core", "candy-guard"] RUST_VERSION=1.75.0 -SOLANA_VERSION=1.17.20 +SOLANA_VERSION=1.18.21 ANCHOR_VERSION=0.27.0 COMMIT_USER_NAME="github-actions[bot]" COMMIT_USER_EMAIL="41898282+github-actions[bot]@users.noreply.github.com" -DEPLOY_SOLANA_VERSION=1.18.14 +DEPLOY_SOLANA_VERSION=1.18.21