Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip #9

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class () extends Migration {
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('beams', function (Blueprint $table) {
$table->json('probabilities')->nullable()->after('flags_mask');
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('beams', function (Blueprint $table) {
$table->dropColumn('probabilities');
});
}
};
4 changes: 4 additions & 0 deletions lang/en/input_type.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@
'claim_token.field.tokenIdDataUpload' => 'You can use this to upload a txt file that contains a list of token ID ranges, one per line.',
'claim_token.field.claimQuantity' => 'The total amount of times each token ID can be claimed. This is mainly relevant for fungible tokens, where you can specify that there are a certain amount of claims for a token ID, e.g. 10 individual claims to receive 1 token with ID 123 per claim.',
'claim_token.field.tokenQuantityPerClaim' => 'The quantity of token that can be received per claim.',
'claim_probability.description' => 'The probabilities of the claim data.',
'claim_probability.field.nft' => 'The NFT data probability.',
'ft_probability.description' => 'The FT data probability.',
'ft_probability.field.chance' => 'The probability percentage.',
];
1 change: 1 addition & 0 deletions lang/en/validation.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@
'tokens_doesnt_exist_in_beam' => 'The :attribute already exist in beam.',
'tokens_exist_in_beam' => 'The :attribute doesn\'t exist in beam.',
'not_owner' => 'The :attribute should not be the owner of the collection.',
'token_id_exists_in_params' => 'The :attribute doesn\'t exist in the `tokens` parameter.',
];
1 change: 1 addition & 0 deletions src/BeamServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public function configurePackage(Package $package): void
->hasMigration('create_beam_scans_table')
->hasMigration('update_beams_table')
->hasMigration('add_collection_chain_id_to_beam_batches_table')
->hasMigration('add_probabilities_to_beam_table')
->hasRoute('enjin-platform-beam')
->hasTranslations();
}
Expand Down
14 changes: 14 additions & 0 deletions src/GraphQL/Mutations/CreateBeamMutation.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Enjin\Platform\Beam\GraphQL\Traits\HasBeamCommonFields;
use Enjin\Platform\Beam\Rules\MaxTokenCount;
use Enjin\Platform\Beam\Rules\MaxTokenSupply;
use Enjin\Platform\Beam\Rules\TokenIdExistsInParams;
use Enjin\Platform\Beam\Rules\TokensDoNotExistInCollection;
use Enjin\Platform\Beam\Rules\TokensExistInCollection;
use Enjin\Platform\Beam\Rules\TokenUploadExistInCollection;
Expand Down Expand Up @@ -65,6 +66,10 @@ public function args(): array
'type' => GraphQL::type('[ClaimToken!]!'),
'description' => __('enjin-platform-beam::input_type.claim_token.description'),
],
'probabilities' => [
'type' => GraphQL::type('ClaimProbability!'),
'description' => __('enjin-platform-beam::input_type.claim_probability.description'),
],
];
}

Expand Down Expand Up @@ -148,6 +153,15 @@ protected function rules(array $args = []): array
new MaxTokenCount($args['collectionId']),
],
'flags.*.flag' => ['required', 'distinct'],
'probabilities.ft.*.tokenId' => [
'required_with:probabilities.ft.*.chance',
new TokenIdExistsInParams(),
],
'probabilities.ft.*.chance' => [
'required_with:probabilities.ft.*.tokenId',
'decimal',
'min:0',
],
];
}
}
41 changes: 41 additions & 0 deletions src/GraphQL/Types/Input/ClaimProbabilityInputType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace Enjin\Platform\Beam\GraphQL\Types\Input;

use Enjin\Platform\Beam\Rules\MaxBigIntIntegerRange;
use Enjin\Platform\Beam\Rules\MinBigIntIntegerRange;
use Enjin\Platform\Support\Hex;
use Rebing\GraphQL\Support\Facades\GraphQL;

class ClaimProbabilityInputType extends InputType
{
/**
* Get the input type's attributes.
*/
public function attributes(): array
{
return [
'name' => 'ClaimProbability',
'description' => __('enjin-platform-beam::input_type.claim_probability.description'),
];
}

/**
* Get the input type's fields.
*/
public function fields(): array
{
return [
'ft' => [
'type' => GraphQL::type('[FTProbability!]'),
'description' => __('enjin-platform-beam::input_type.ft_probability.description'),
'rules' => [new MinBigIntIntegerRange(), new MaxBigIntIntegerRange(Hex::MAX_UINT128)],
],
'nft' => [
'type' => GraphQL::type('Int!'),
'description' => __('enjin-platform-beam::input_type.claim_probability.field.nft'),
'defaultValue' => 0,
],
];
}
}
41 changes: 41 additions & 0 deletions src/GraphQL/Types/Input/FTProbabilityInputType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace Enjin\Platform\Beam\GraphQL\Types\Input;

use Enjin\Platform\Rules\MaxBigInt;
use Enjin\Platform\Rules\MinBigInt;
use Enjin\Platform\Support\Hex;
use Rebing\GraphQL\Support\Facades\GraphQL;

class FTProbabilityInputType extends InputType
{
/**
* Get the input type's attributes.
*/
public function attributes(): array
{
return [
'name' => 'FTProbability',
'description' => __('enjin-platform-beam::input_type.ft_probability.description'),
];
}

/**
* Get the input type's fields.
*/
public function fields(): array
{
return [
'tokenId' => [
'type' => GraphQL::type('BigInt!'),
'description' => __('enjin-platform-beam::input_type.claim_token.field.tokenId'),
'rules' => [new MinBigInt(), new MaxBigInt(Hex::MAX_UINT128)],
],
'chance' => [
'type' => GraphQL::type('Float!'),
'description' => __('enjin-platform-beam::input_type.ft_probability.field.chance'),
'defaultValue' => 0,
],
];
}
}
49 changes: 44 additions & 5 deletions src/Jobs/ClaimBeam.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Enjin\Platform\Beam\Jobs;

use Enjin\Platform\Beam\Enums\BeamType;
use Enjin\Platform\Beam\Models\Beam;
use Enjin\Platform\Beam\Models\BeamClaim;
use Enjin\Platform\Beam\Models\BeamScan;
use Enjin\Platform\Beam\Services\BatchService;
Expand Down Expand Up @@ -40,11 +41,18 @@ public function handle(BatchService $batch, WalletService $wallet): void
{
if ($data = $this->data) {
try {
$claim = BeamClaim::where('beam_id', $data['beam']['id'])
->claimable()
->when($data['code'], fn ($query) => $query->withSingleUseCode($data['code']))
->unless($data['code'], fn ($query) => $query->inRandomOrder())
->first();
$beam = Beam::find($data['beam']['id']);
$claim = null;
if ($beam->probabilities && !$data['code']) {
$claim = $this->computeClaim($beam);
}
if (!$claim) {
$claim = BeamClaim::where('beam_id', $data['beam']['id'])
->claimable()
->when($data['code'], fn ($query) => $query->withSingleUseCode($data['code']))
->unless($data['code'], fn ($query) => $query->inRandomOrder())
->first();
}

if ($claim) {
DB::beginTransaction();
Expand Down Expand Up @@ -76,6 +84,37 @@ public function failed(Throwable $exception): void
}
}

/**
* Compute the claim base from its probabilities.
*/
protected function computeClaim(Model $beam): ?Model
{
$tryLimit = BeamClaim::where('beam_id', $beam->id)->claimable()->count() + count($beam->chances);
$tries = 0;
$claim = null;
do {
$rand = random_int(1, 100);
foreach ($beam->chances as $tokenId => $chance) {
if ($rand <= $chance) {
if ($tokenId === 'nft') {
$claim = BeamClaim::where('beam_id', $beam->id)
->claimable()
->nft($beam->collection_chain_id)
->first();
} else {
$claim = BeamClaim::where('beam_id', $beam->id)
->claimable()
->where('token_chain_id', $tokenId)
->first();
}
}
}
$tries++;
} while (is_null($claim) && $tries <= $tryLimit);

return $claim;
}

/**
* Build the claim attributes.
*/
Expand Down
28 changes: 28 additions & 0 deletions src/Models/Laravel/Beam.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
use Staudenmeir\EloquentEagerLimit\HasEagerLimit;

Expand Down Expand Up @@ -46,6 +47,7 @@ class Beam extends BaseModel
'end',
'collection_chain_id',
'flags_mask',
'probabilities',
];

/**
Expand All @@ -64,6 +66,13 @@ class Beam extends BaseModel
'deleted_at',
];

/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = ['probabilities' => 'array'];

/**
* The beam claim's relationship.
*/
Expand Down Expand Up @@ -96,6 +105,25 @@ public function hasFlag(BeamFlag $flag): bool
return BitMask::getBit($flag->value, $this->flags_mask ?? 0);
}

/**
* The beam chances attribute.
*/
public function getChancesAttribute(): array
{
$chances = [];
foreach (Arr::get($this->probabilities, 'ft', []) as $value) {
if ($value['chance'] > 0) {
$chances[$value['tokenId']] = $value['chance'];
}
}
if ($value = Arr::get($this->probabilities, 'nft')) {
$chances['nft'] = $value;
}
asort($chances);

return $chances;
}

/**
* Interact with the beam's start attribute.
*/
Expand Down
27 changes: 27 additions & 0 deletions src/Models/Laravel/BeamClaim.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Enjin\Platform\Beam\Enums\BeamFlag;
use Enjin\Platform\Beam\Enums\BeamRoute;
use Enjin\Platform\Beam\Services\BeamService;
use Enjin\Platform\Enums\Substrate\TokenMintCapType;
use Enjin\Platform\Models\BaseModel;
use Enjin\Platform\Models\Laravel\Collection;
use Enjin\Platform\Models\Laravel\Token;
Expand Down Expand Up @@ -193,6 +194,32 @@ public function prunable()
return static::where('id', 0);
}

/**
* Scope for NFTs.
*/
public function scopeNft(Builder $query, int $collectionId): Builder
{
return $query->leftJoin(
'tokens',
fn ($join) => $join->on('tokens.token_chain_id', '=', 'beam_claims.token_chain_id')
->whereColumn('tokens.collection_id', 'beam_claims.collection_id')
)
->join(
'collections',
fn ($join) => $join->on('collections.id', '=', 'beam_claims.collection_id')
->where('collections.collection_chain_id', $collectionId)
)
->whereRaw("
(tokens.is_currency is false OR tokens.is_currency is NULL)
AND (
collections.max_token_supply = '1'
OR (collections.force_single_mint is true AND tokens.supply = '1')
OR (tokens.cap='" . TokenMintCapType::SUPPLY->name . "' AND tokens.cap_supply = '1')
OR (tokens.cap='" . TokenMintCapType::SINGLE_MINT->name . "' AND tokens.supply = '1')
)
");
}

/**
* This model's factory.
*/
Expand Down
28 changes: 28 additions & 0 deletions src/Rules/TokenIdExistsInParams.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace Enjin\Platform\Beam\Rules;

use Closure;
use Enjin\Platform\Beam\Rules\Traits\HasDataAwareRule;
use Enjin\Platform\Beam\Rules\Traits\IntegerRange;
use Illuminate\Contracts\Validation\DataAwareRule;
use Illuminate\Contracts\Validation\ValidationRule;

class TokenIdExistsInParams implements DataAwareRule, ValidationRule
{
use IntegerRange;
use HasDataAwareRule;

public function validate(string $attribute, mixed $value, Closure $fail): void
{
$tokens = collect($this->data['tokens'])
->pluck('tokenIds')
->filter()
->flatten()
->sortBy(fn ($tokenId) => false === $this->integerRange($tokenId))
->all();
if ($tokens && !$this->tokenIdExists($tokens, $value)) {
$fail(__('enjin-platform-beam::validation.token_id_exists_in_params'));
}
}
}
Loading
Loading