Skip to content

Commit

Permalink
Merge pull request #255 from holaplex/abdul/batch-mint
Browse files Browse the repository at this point in the history
mutation for batched random queued mints
  • Loading branch information
kespinola authored Oct 17, 2023
2 parents 256ce1f + 6c05730 commit 75d9090
Show file tree
Hide file tree
Showing 3 changed files with 210 additions and 7 deletions.
8 changes: 4 additions & 4 deletions api/proto.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ sha512 = "d75800df0d4744c6b0f4d9a9952d3bfd0bb6b24a8babd19104cc11b54a525f85551b3c

[[schemas]]
subject = "nfts"
version = 30
sha512 = "bee70bd6f0f18a8049f93bceb9c4b500b49352f9d19d55d5a411da92cbd786c88bec47f73e1ef6946ceefc7de8e558f704bf8187be9d9f4e49bd102baec29327"
version = 31
sha512 = "449574f8551ab8c17824af9e08b1658ad1b26ac80340230ddf02e7a1e0979d8a47025913a6598799cf83dd1a9cda87697ee87a13f404ebb52c95ea0084205767"

[[schemas]]
subject = "organization"
Expand All @@ -20,8 +20,8 @@ sha512 = "c5ddf43d2958ec690ee2261d0ff9808b67ce810d2fc4b6077f96f561929a920f03509f

[[schemas]]
subject = "solana_nfts"
version = 11
sha512 = "967fefde938a0f6ce05194e4fca15673e681caac54d8aeec114c5d38418632b9696dbaf5362345a15114e5abb49de55d0af8b9edcc0f2c91f9ef1ccc4ff55d68"
version = 12
sha512 = "4f85496c50a82cb40faa097cf6d0cb23275b3b90cb561d01388f3e5a71282a8b8e1eea617b7d712b0e415d65af483209fac2db1591456fa814a1f41a1c457433"

[[schemas]]
subject = "timestamp"
Expand Down
4 changes: 2 additions & 2 deletions api/proto.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ endpoint = "https://schemas.holaplex.tools"

[schemas]
organization = 5
nfts = 30
nfts = 31
customer = 2
treasury = 23
solana_nfts = 11
solana_nfts = 12
polygon_nfts = 6
timestamp = 1
205 changes: 204 additions & 1 deletion api/src/mutations/mint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ use crate::{
objects::{CollectionMint, Creator, MetadataJsonInput},
proto::{
self, nft_events::Event as NftEvent, CreationStatus as NftCreationStatus, MetaplexMetadata,
MintCollectionCreation, MintCreation, NftEventKey, NftEvents, RetryUpdateSolanaMintPayload,
MintCollectionCreation, MintCreation, MintOpenDropTransaction, NftEventKey, NftEvents,
RetryUpdateSolanaMintPayload, SolanaMintOpenDropBatchedPayload,
},
Actions, AppContext, OrganizationId, UserID,
};
Expand Down Expand Up @@ -1291,6 +1292,194 @@ impl Mutation {
collection_mint: mint.into(),
})
}

async fn mint_random_queued_to_drop_batched(
&self,
ctx: &Context<'_>,
input: MintRandomQueuedBatchedInput,
) -> Result<MintRandomQueuedBatchedPayload> {
let AppContext {
db,
user_id,
organization_id,
balance,
..
} = ctx.data::<AppContext>()?;
let credits = ctx.data::<CreditsClient<Actions>>()?;
let conn = db.get();
let nfts_producer = ctx.data::<Producer<NftEvents>>()?;

let UserID(id) = user_id;
let OrganizationId(org) = organization_id;

let user_id = id.ok_or(Error::new("X-USER-ID header not found"))?;
let org_id = org.ok_or(Error::new("X-ORGANIZATION-ID header not found"))?;
let balance = balance
.0
.ok_or(Error::new("X-CREDIT-BALANCE header not found"))?;

let batch_size = input.recipients.len();

if batch_size == 0 {
return Err(Error::new("No recipients provided"));
}

if batch_size > 250 {
return Err(Error::new("Batch size cannot be greater than 250"));
}

let drop = drops::Entity::find_by_id(input.drop)
.one(conn)
.await?
.ok_or(Error::new("drop not found"))?;

let result = CollectionMints::find()
.select_also(metadata_jsons::Entity)
.join(
JoinType::InnerJoin,
metadata_jsons::Entity::belongs_to(CollectionMints)
.from(metadata_jsons::Column::Id)
.to(collection_mints::Column::Id)
.into(),
)
.filter(collection_mints::Column::CollectionId.eq(drop.collection_id))
.filter(collection_mints::Column::CreationStatus.eq(CreationStatus::Queued))
.order_by(SimpleExpr::FunctionCall(Func::random()), Order::Asc)
.limit(Some(batch_size.try_into()?))
.all(conn)
.await?;

let (mints, _): (Vec<_>, Vec<_>) = result.iter().cloned().unzip();

let creators = mints.load_many(mint_creators::Entity, conn).await?;

if mints.len() != batch_size {
return Err(Error::new("Not enough mints found for the drop"));
}

let collection = collections::Entity::find_by_id(drop.collection_id)
.one(conn)
.await?
.ok_or(Error::new("collection not found"))?;

let project_id = collection.project_id;
let blockchain = collection.blockchain;

if blockchain != BlockchainEnum::Solana {
return Err(Error::new("Only Solana is supported at this time"));
}

let owner_address = fetch_owner(conn, project_id, blockchain).await?;

let action = if input.compressed {
Actions::MintCompressed
} else {
Actions::Mint
};

let event_key = NftEventKey {
id: collection.id.to_string(),
user_id: user_id.to_string(),
project_id: project_id.to_string(),
};

let mut transactions = Vec::new();

for (((mint, metadata_json), creators), recipient) in result
.into_iter()
.zip(creators.into_iter())
.zip(input.recipients.into_iter())
{
let metadata_json = metadata_json.ok_or(Error::new("No metadata json found"))?;
let metadata_uri = metadata_json
.uri
.ok_or(Error::new("No metadata json uri found"))?;

let TransactionId(deduction_id) = credits
.submit_pending_deduction(
org_id,
user_id,
action,
collection.blockchain.into(),
balance,
)
.await?;

let tx = conn.begin().await?;

let mut mint_am: collection_mints::ActiveModel = mint.into();

mint_am.creation_status = Set(CreationStatus::Pending);
mint_am.credits_deduction_id = Set(Some(deduction_id));
mint_am.compressed = Set(Some(input.compressed));
mint_am.owner = Set(Some(recipient.clone()));
mint_am.seller_fee_basis_points = Set(collection.seller_fee_basis_points);

let mint = mint_am.update(&tx).await?;

let mint_history_am = mint_histories::ActiveModel {
mint_id: Set(mint.id),
wallet: Set(recipient.clone()),
collection_id: Set(collection.id),
tx_signature: Set(None),
status: Set(CreationStatus::Pending),
created_at: Set(Utc::now().into()),
..Default::default()
};

mint_history_am.insert(&tx).await?;

tx.commit().await?;

nfts_producer
.send(
Some(&NftEvents {
event: Some(NftEvent::DropMinted(MintCreation {
drop_id: drop.id.to_string(),
status: NftCreationStatus::InProgress as i32,
})),
}),
Some(&NftEventKey {
id: mint.id.to_string(),
project_id: drop.project_id.to_string(),
user_id: user_id.to_string(),
}),
)
.await?;

transactions.push(MintOpenDropTransaction {
recipient_address: recipient,
metadata: Some(MetaplexMetadata {
owner_address: owner_address.clone(),
name: metadata_json.name,
symbol: metadata_json.symbol,
metadata_uri,
seller_fee_basis_points: mint.seller_fee_basis_points.into(),
creators: creators.into_iter().map(Into::into).collect(),
}),
mint_id: mint.id.to_string(),
});
}

nfts_producer
.send(
Some(&NftEvents {
event: Some(NftEvent::SolanaMintOpenDropBatched(
SolanaMintOpenDropBatchedPayload {
collection_id: collection.id.to_string(),
compressed: input.compressed,
mint_open_drop_transactions: transactions,
},
)),
}),
Some(&event_key),
)
.await?;

Ok(MintRandomQueuedBatchedPayload {
collection_mints: mints.into_iter().map(Into::into).collect(),
})
}
}

fn validate_compress(blockchain: BlockchainEnum, compressed: bool) -> Result<(), Error> {
Expand Down Expand Up @@ -1475,3 +1664,17 @@ pub struct MintRandomQueuedInput {
recipient: String,
compressed: bool,
}

/// Represents input data for `mint_random_queued_batched` mutation
#[derive(Debug, Clone, InputObject)]
pub struct MintRandomQueuedBatchedInput {
drop: Uuid,
recipients: Vec<String>,
compressed: bool,
}

/// Represents payload data for `mint_random_queued_batched` mutation
#[derive(Debug, Clone, SimpleObject)]
pub struct MintRandomQueuedBatchedPayload {
collection_mints: Vec<CollectionMint>,
}

0 comments on commit 75d9090

Please sign in to comment.