Skip to content

Commit

Permalink
Initial TokenMintConfiguration implementation (#412)
Browse files Browse the repository at this point in the history
* wip: initial TokenMintConfiguration implementation

* mike's initial changes

* mint to lock features (wip)

* Support pre-processing with burn-to-mint, rework post-mint lock

* fix imports and exports for mint configurations

* add preBurn configs to TokenMintConfiguration dto and save

* add copyright to userExemptFromFees.ts

* fixes for fee code base pre mint burn implementation

* move burnToMint percent to TokenMintConfiguration

This will support different percentages for different tokens.
The prior implementation relied on Fee Gates and FeeCodeDefinition
objects, which would have forced the same percentage across
all MintToken actions configured for a pre or post mint burn.
Moving this percentage to a token-specific chain object
provides flexibility for different burn percentages for
different tokens.

e2e tests are currently with other still-unmigrated e2e tests
in the internal assets-chaincode repository.

* combine mint related payment failures into single error response

---------

Co-authored-by: Jeff Eganhouse <jeganhouse@gala.games>
Co-authored-by: Sandpiper <befitsandpiper@users.noreply.github.com>
  • Loading branch information
3 people authored Oct 30, 2024
1 parent 6d33786 commit 34fd553
Show file tree
Hide file tree
Showing 12 changed files with 1,072 additions and 20 deletions.
219 changes: 219 additions & 0 deletions chain-api/src/types/TokenMintConfiguration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
/*
* Copyright (c) Gala Games Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Exclude, Type } from "class-transformer";
import {
IsBoolean,
IsDefined,
IsInt,
IsNotEmpty,
IsNumber,
IsOptional,
Max,
Min,
ValidateNested
} from "class-validator";

import { ChainKey, ValidationFailedError } from "../utils";
import { IsUserAlias } from "../validators";
import { ChainObject } from "./ChainObject";

/**
* @description
*
* Configure properties that may be used in conjunction with
* a `TokenMintConfiguration` to lock some percentage of
* tokens minted as a post-mint action.
*
*/
export class PostMintLockConfiguration extends ChainObject {
/**
* @description
*
* This property will be used to create the `name` property
* of the `TokenHold` created on the user's balance.
*
*/
@IsNotEmpty()
lockName: string;

/**
* @description
*
* This property will be used to create the `lockAuthority`
* property of the `TokenHold` created on the user's balance.
*
*/
@IsNotEmpty()
@IsUserAlias()
lockAuthority: string;

/**
* @description
*
* This value will be used to set the `expires` property of
* the `TokenHold` property created on the user's balance.
*
* It will be added to the GalaChainContext.txUnixTime value
* which, at the time of this writing (Oct 2024), is
* represented in milliseconds.
*
* @example
*
* 2592000000 = 30 days (1000 * 60 * 60 * 24 * 30)
*/
@IsNumber()
@IsInt()
expirationModifier: number;

/**
* @description
*
* Set the percentage of tokens that should be locked,
* post-mint.
*
* @example
*
* 0.25 = 25%
*/
@IsNumber()
@Min(0)
@Max(1)
lockPercentage: number;
}

/**
* @description
*
* Configure properties that may be used in conjunction with
* a `TokenMintConfiguration` to specify a pre-mint or post-mint
* burn of the token quantity being minted as part of the
* mint action.
*/
export class BurnToMintConfiguration extends ChainObject {
/**
* @description
*
* Percentage of tokens to be burned in conjunction with each
* mint action, expressed as a value between 0 and 1.
*
* @example
*
* 0.25 = 25%
*/
@IsNumber()
@Min(0)
@Max(1)
burnPercentage: number;
}

/**
* @description
*
* Configure mint configurations for specific token classes.
* The chain key properties are expected to match a token
* class.
*
* On mint actions, the `@GalaTransaction` decorator's
* `before` and/or `after`
* property can potentially be configured with custom functions
* that will look for these configuration options.
*
* If present, they can execute myriad additional actions
* atomically with the mint, such as post-mint fees,
* nft crafting (e.g. burn three common parts to assemble one rare) etc.
*
*/
export class TokenMintConfiguration extends ChainObject {
public static INDEX_KEY = "GCTMC"; // GalaChain Token Mint Configuration

@ChainKey({ position: 0 })
@IsNotEmpty()
public collection: string;

@ChainKey({ position: 1 })
@IsNotEmpty()
public category: string;

@ChainKey({ position: 2 })
@IsNotEmpty()
public type: string;

@ChainKey({ position: 3 })
@IsDefined()
public additionalKey: string;

/**
* @description
*
* (optional) specify a `BurnToMintConfiguration` to configure a specific
* token class to potentially burn some amount of
* the quantity to-be-minted prior to executing
* the mint.
*
*/
@IsOptional()
@ValidateNested()
@Type(() => BurnToMintConfiguration)
public preMintBurn?: BurnToMintConfiguration;

/**
* @description
*
* (optional) specify a `BurnToMintConfiguration` to configure a specific
* token class to potentially burn some amount of
* minted quantity post-mint.
*
*/
@IsOptional()
@ValidateNested()
@Type(() => BurnToMintConfiguration)
public postMintBurn?: BurnToMintConfiguration;

/**
* @description
*
* (optional) set a quantity to configure a specific
* token class to lock some amount of
* minted quantity post-mint.
*
* @remarks
*
* Use in conjucntion with `FeeCodeDefintion` chain objects
* and Fee Exit Gates to set specific amounts and/or percentages
* to be burned.
*
*/
@IsOptional()
@ValidateNested()
@Type(() => PostMintLockConfiguration)
public postMintLock?: PostMintLockConfiguration;

@Exclude()
public validatePostProcessingTotals() {
const burnPercentage: number | undefined = this.postMintBurn?.burnPercentage;
const lockPercentage: number | undefined = this.postMintLock?.lockPercentage;

if (burnPercentage !== undefined) {
const remainderPostBurn = 1 - burnPercentage;

if (lockPercentage !== undefined && lockPercentage > remainderPostBurn) {
throw new ValidationFailedError(
`TokenMintConfiguration specified a combined post-processing total ` +
`greater than 1 (100%): lockPercentage: ${lockPercentage}, burnPercentage: ${burnPercentage}`
);
}
}
}
}
1 change: 1 addition & 0 deletions chain-api/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export * from "./OraclePriceCrossRateAssertion";
export * from "./swap";
export * from "./TokenMintAllowance";
export * from "./TokenMintAllowanceRequest";
export * from "./TokenMintConfiguration";
export * from "./TokenMintFulfillment";
export * from "./TokenMintRequest";
export * from "./TokenBurnCounter";
139 changes: 138 additions & 1 deletion chain-api/src/types/mint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,27 @@
*/
import BigNumber from "bignumber.js";
import { Type } from "class-transformer";
import { ArrayMaxSize, ArrayNotEmpty, IsNotEmpty, IsOptional, ValidateNested } from "class-validator";
import {
ArrayMaxSize,
ArrayNotEmpty,
IsBoolean,
IsDefined,
IsNotEmpty,
IsNumber,
IsOptional,
IsString,
ValidateIf,
ValidateNested
} from "class-validator";
import { JSONSchema } from "class-validator-jsonschema";

import { ArrayUniqueObjects, BigNumberIsNotNegative, BigNumberProperty, IsUserAlias } from "../validators";
import { TokenClassKey } from "./TokenClass";
import {
BurnToMintConfiguration,
PostMintLockConfiguration,
TokenMintConfiguration
} from "./TokenMintConfiguration";
import { AllowanceKey, MintRequestDto } from "./common";
import { ChainCallDTO } from "./dtos";

Expand Down Expand Up @@ -336,3 +352,124 @@ export class PatchMintRequestDto extends ChainCallDTO {
@BigNumberProperty()
totalKnownMintsCount: BigNumber;
}

@JSONSchema({
description: "DTO that describes a TokenMintConfiguration chain object."
})
export class TokenMintConfigurationDto extends ChainCallDTO {
@JSONSchema({
description: "Token collection."
})
@IsNotEmpty()
collection: string;

@JSONSchema({
description: "Token category."
})
@IsNotEmpty()
category: string;

@JSONSchema({
description: "Token type."
})
@IsNotEmpty()
type: string;

@JSONSchema({
description: "Token additionalKey."
})
@IsDefined()
additionalKey: string;

@JSONSchema({
description:
"(optional) Specify a `BurnToMintConfiguration` to require a burn equal to a " +
"percentage of the quantity to-be-minted prior to executing the mint action."
})
@IsOptional()
@ValidateNested()
@Type(() => BurnToMintConfiguration)
preMintBurn?: BurnToMintConfiguration;

@JSONSchema({
description:
"(optional) Specify a `BurnToMintConfiguration` to enable burning a " +
"percentage of each quantity minted"
})
@IsOptional()
@ValidateNested()
@Type(() => BurnToMintConfiguration)
postMintBurn?: BurnToMintConfiguration;

@JSONSchema({
description:
"(optional) Specify a `PostMintLockConfiguration` to enable " +
"locking a percentage of each quantity minted"
})
@IsOptional()
@ValidateNested()
@Type(() => PostMintLockConfiguration)
postMintLock?: PostMintLockConfiguration;
}

@JSONSchema({
description: "Query parameters for fetching a paginated results set of TokenMintConfiguration entries"
})
export class FetchTokenMintConfigurationsDto extends ChainCallDTO {
public static DEFAULT_LIMIT = 100;
public static MAX_LIMIT = 10000;

@JSONSchema({
description: "Token Class collection."
})
@IsOptional()
@IsNotEmpty()
collection: string;

@JSONSchema({
description: "Token Class category. Optional, but required if collection is provided."
})
@ValidateIf((c) => !!c.collection)
@IsNotEmpty()
category: string;

@JSONSchema({
description: "Token Class type. Optional, but required if category is provided."
})
@ValidateIf((c) => !!c.category)
@IsNotEmpty()
type: string;

@JSONSchema({
description: "Token Class additionalKey. Optional, but required if type is provided. "
})
@ValidateIf((c) => !!c.type)
@IsNotEmpty()
additionalKey: string;

@JSONSchema({
description: "Bookmark for paginated queries. Provide the empty string for the first page of results."
})
@IsString()
bookmark: string;

@JSONSchema({
description: "Page size used to limit the results returned. Default: 100. Max: 10000."
})
@IsOptional()
@IsNumber()
limit?: number;
}

@JSONSchema({
description: "DTO that includes a paginated results set of TokenMintConfiguration objects"
})
export class FetchTokenMintConfigurationsResponse extends ChainCallDTO {
@JSONSchema({
description: "Results set of TokenMintConfiguration entries."
})
results: TokenMintConfiguration[];

@IsString()
bookmark: string;
}
Loading

0 comments on commit 34fd553

Please sign in to comment.