From c851178add70ff2b78bf014c4a8e4d7f091b0182 Mon Sep 17 00:00:00 2001 From: imabdulbasit Date: Mon, 11 Sep 2023 19:13:18 +0500 Subject: [PATCH 1/8] Open Drop API --- api/proto.lock | 18 +- api/proto.toml | 14 +- api/src/blockchains/mod.rs | 18 +- api/src/blockchains/polygon.rs | 87 +++++--- api/src/blockchains/solana.rs | 172 +++++++++++++--- api/src/dataloaders/collection_mints.rs | 11 +- api/src/entities/collection_mints.rs | 4 +- api/src/entities/drops.rs | 3 +- api/src/entities/sea_orm_active_enums.rs | 11 + api/src/events.rs | 26 +-- api/src/mutations/drop.rs | 193 +++++++++++------- api/src/mutations/transfer.rs | 33 +-- api/src/objects/collection_mint.rs | 2 +- api/src/objects/drop.rs | 3 +- api/src/objects/holder.rs | 2 +- migration/src/lib.rs | 6 + .../src/m20230905_100852_add_type_to_drop.rs | 66 ++++++ ...4731_add_queued_variant_to_mints_status.rs | 24 +++ ...42_make_owner_address_optional_for_mint.rs | 35 ++++ 19 files changed, 529 insertions(+), 199 deletions(-) create mode 100644 migration/src/m20230905_100852_add_type_to_drop.rs create mode 100644 migration/src/m20230910_204731_add_queued_variant_to_mints_status.rs create mode 100644 migration/src/m20230910_212742_make_owner_address_optional_for_mint.rs diff --git a/api/proto.lock b/api/proto.lock index ecab697..0c75d46 100644 --- a/api/proto.lock +++ b/api/proto.lock @@ -1,27 +1,27 @@ [[schemas]] subject = "customer" -version = 2 +version = 1 sha512 = "d75800df0d4744c6b0f4d9a9952d3bfd0bb6b24a8babd19104cc11b54a525f85551b3c7375d69aeabbcf629cd826aa0bc6b0c0467add20716c504f5e856ce1c5" [[schemas]] subject = "nfts" -version = 28 -sha512 = "25960e99e2eb1edeb70b7619918926d877b834640cf44fcc8104da5be6e213c05be18f8f49dba35e649d751bc62e16d9ca428cbafdb432b7c0b7f40a6cef7483" +version = 2 +sha512 = "db8fa9f4b2874ab79305997c3b255a9b9c1b04d291c66f1cb97f332b32758055c01e6ba0e7a4089255c664324c51fc4fe25a9d138698b7a999b1f4d2b5503a48" [[schemas]] subject = "organization" -version = 5 +version = 1 sha512 = "9fb28ac73d9712292297394a5fa53a7dae9deba6847353582987ba749859301c23c05fd49d2ce84a1640f8864c5c04d59fa38907700b280000e5c4afc96654bf" [[schemas]] subject = "polygon_nfts" -version = 6 +version = 1 sha512 = "c5ddf43d2958ec690ee2261d0ff9808b67ce810d2fc4b6077f96f561929a920f03509fc8bd7adbda219250eb019f5f7be8a3f51c554f665ea1881f7a973ef2a6" [[schemas]] subject = "solana_nfts" -version = 10 -sha512 = "1bcb166ab5dfdf4841d60caa07a4098dcec03d7e3b0e63adb090ed2f5fe990c1e13826867e8df7521ec631027d5a931a08865fd2cf2daa905807c4b7dca40213" +version = 1 +sha512 = "37fd0e45d12271323d8abfe7343c08e475ca4737014d6181dcdcbcd683ebc33ab7cea1f4da00673e7ad2f0e1ae5ae917f16175b0650df357f649545928685a50" [[schemas]] subject = "timestamp" @@ -30,5 +30,5 @@ sha512 = "d167e0a143c813073eef8597f0b237e5a8eaf32abbf709724e8071b2dd73ce0438b82f [[schemas]] subject = "treasury" -version = 22 -sha512 = "bde788b07f818aa52e684dcbd91e1f1e3db82f242f616ec2a42ab6d412df33a1461677c229f2f9bae345938c2f325e6332a95caef2c7e01a47531af53e39bf03" +version = 1 +sha512 = "03580129f0561b3d153f83b9f2729bd18e27c7d29815c9c3b7493e57e3cad3ea56f6e803e0ec5f19e8c17669de5ef8c0eb1bd59cfbae5f400741d0a73a464ba5" diff --git a/api/proto.toml b/api/proto.toml index 3d92c89..030a40b 100644 --- a/api/proto.toml +++ b/api/proto.toml @@ -1,11 +1,11 @@ [registry] -endpoint = "https://schemas.holaplex.tools" +endpoint = "http://localhost:8081" [schemas] -organization = 5 -nfts = 28 -customer = 2 -treasury = 22 -solana_nfts = 10 -polygon_nfts = 6 +organization = 1 +nfts = 2 +customer = 1 +treasury = 1 +solana_nfts = 1 +polygon_nfts = 1 timestamp = 1 \ No newline at end of file diff --git a/api/src/blockchains/mod.rs b/api/src/blockchains/mod.rs index 6aab281..954e6ec 100644 --- a/api/src/blockchains/mod.rs +++ b/api/src/blockchains/mod.rs @@ -3,8 +3,11 @@ pub mod solana; use hub_core::anyhow::Result; -use crate::proto::{ - NftEventKey, RetryUpdateSolanaMintPayload, SwitchCollectionPayload, UpdateSolanaMintPayload, +use crate::{ + entities::sea_orm_active_enums::DropType, + proto::{ + NftEventKey, RetryUpdateSolanaMintPayload, SwitchCollectionPayload, UpdateSolanaMintPayload, + }, }; /// Represents a response from a transaction on the blockchain. This struct @@ -19,9 +22,14 @@ pub struct TransactionResponse { #[async_trait::async_trait] pub trait DropEvent { - async fn create_drop(&self, key: NftEventKey, payload: A) -> Result<()>; - async fn retry_create_drop(&self, key: NftEventKey, payload: A) -> Result<()>; - async fn update_drop(&self, key: NftEventKey, payload: C) -> Result<()>; + async fn create_drop(&self, drop_type: DropType, key: NftEventKey, payload: A) -> Result<()>; + async fn retry_create_drop( + &self, + drop_type: DropType, + key: NftEventKey, + payload: A, + ) -> Result<()>; + async fn update_drop(&self, drop_type: DropType, key: NftEventKey, payload: C) -> Result<()>; async fn mint_drop(&self, key: NftEventKey, payload: B) -> Result<()>; async fn retry_mint_drop(&self, key: NftEventKey, payload: B) -> Result<()>; } diff --git a/api/src/blockchains/polygon.rs b/api/src/blockchains/polygon.rs index b2853ff..d1c9146 100644 --- a/api/src/blockchains/polygon.rs +++ b/api/src/blockchains/polygon.rs @@ -1,13 +1,16 @@ -use hub_core::{anyhow::Result, producer::Producer}; +use hub_core::{anyhow::Result, prelude::bail, producer::Producer}; use super::{DropEvent, TransferEvent}; -use crate::proto::{ - nft_events::Event::{ - PolygonCreateDrop, PolygonMintDrop, PolygonRetryDrop, PolygonRetryMintDrop, - PolygonTransferAsset, PolygonUpdateDrop, +use crate::{ + entities::sea_orm_active_enums::DropType, + proto::{ + nft_events::Event::{ + PolygonCreateDrop, PolygonMintDrop, PolygonRetryDrop, PolygonRetryMintDrop, + PolygonTransferAsset, PolygonUpdateDrop, + }, + CreateEditionTransaction, MintEditionTransaction, NftEventKey, NftEvents, + TransferPolygonAsset, UpdateEdtionTransaction, }, - CreateEditionTransaction, MintEditionTransaction, NftEventKey, NftEvents, TransferPolygonAsset, - UpdateEdtionTransaction, }; #[derive(Clone)] @@ -25,7 +28,7 @@ impl Polygon { pub fn event( &self, ) -> impl DropEvent - + TransferEvent { + + TransferEvent { self.clone() } } @@ -34,46 +37,69 @@ impl Polygon { impl DropEvent for Polygon { - async fn create_drop(&self, key: NftEventKey, payload: CreateEditionTransaction) -> Result<()> { - let event = NftEvents { - event: Some(PolygonCreateDrop(payload)), + async fn create_drop( + &self, + drop_type: DropType, + key: NftEventKey, + payload: CreateEditionTransaction, + ) -> Result<()> { + let event = match drop_type { + DropType::Edition => Some(PolygonCreateDrop(payload)), + DropType::Open => bail!("Open drops are not supported on Polygon"), }; - self.producer.send(Some(&event), Some(&key)).await?; + self.producer + .send(Some(&NftEvents { event }), Some(&key)) + .await?; Ok(()) } async fn retry_create_drop( &self, + drop_type: DropType, key: NftEventKey, payload: CreateEditionTransaction, ) -> Result<()> { - let event = NftEvents { - event: Some(PolygonRetryDrop(payload)), + let event = match drop_type { + DropType::Edition => Some(PolygonRetryDrop(payload)), + DropType::Open => bail!("Open drops are not supported on Polygon"), }; - self.producer.send(Some(&event), Some(&key)).await?; + self.producer + .send(Some(&NftEvents { event }), Some(&key)) + .await?; Ok(()) } - async fn update_drop(&self, key: NftEventKey, payload: UpdateEdtionTransaction) -> Result<()> { - let event = NftEvents { - event: Some(PolygonUpdateDrop(payload)), + async fn update_drop( + &self, + drop_type: DropType, + key: NftEventKey, + payload: UpdateEdtionTransaction, + ) -> Result<()> { + let event = match drop_type { + DropType::Edition => Some(PolygonUpdateDrop(payload)), + DropType::Open => bail!("Open drops are not supported on Polygon"), }; - self.producer.send(Some(&event), Some(&key)).await?; + self.producer + .send(Some(&NftEvents { event }), Some(&key)) + .await?; Ok(()) } async fn mint_drop(&self, key: NftEventKey, payload: MintEditionTransaction) -> Result<()> { - let event = NftEvents { - event: Some(PolygonMintDrop(payload)), - }; - - self.producer.send(Some(&event), Some(&key)).await?; + self.producer + .send( + Some(&NftEvents { + event: Some(PolygonMintDrop(payload)), + }), + Some(&key), + ) + .await?; Ok(()) } @@ -83,11 +109,14 @@ impl DropEvent Result<()> { - let event = NftEvents { - event: Some(PolygonRetryMintDrop(payload)), - }; - - self.producer.send(Some(&event), Some(&key)).await?; + self.producer + .send( + Some(&NftEvents { + event: Some(PolygonRetryMintDrop(payload)), + }), + Some(&key), + ) + .await?; Ok(()) } diff --git a/api/src/blockchains/solana.rs b/api/src/blockchains/solana.rs index ac999a6..3fd24ac 100644 --- a/api/src/blockchains/solana.rs +++ b/api/src/blockchains/solana.rs @@ -1,23 +1,34 @@ use hub_core::{anyhow::Result, producer::Producer}; use super::{CollectionEvent, DropEvent, TransferEvent}; -use crate::proto::{ - nft_events::Event::{ - SolanaCreateCollection, SolanaCreateDrop, SolanaMintDrop, SolanaMintToCollection, - SolanaRetryCreateCollection, SolanaRetryDrop, SolanaRetryMintDrop, - SolanaRetryMintToCollection, SolanaRetryUpdatedCollectionMint, - SolanaSwitchMintCollectionRequested, SolanaTransferAsset, SolanaUpdateCollection, - SolanaUpdateDrop, SolanaUpdatedCollectionMint, +use crate::{ + entities::sea_orm_active_enums::DropType, + proto::{ + nft_events::Event::{ + SolanaCreateCollection, SolanaCreateEditionDrop, SolanaCreateOpenDrop, + SolanaMintEditionDrop, SolanaMintOpenDrop, SolanaMintToCollection, + SolanaRetryCreateCollection, SolanaRetryEditionDrop, SolanaRetryMintEditionDrop, + SolanaRetryMintOpenDrop, SolanaRetryMintToCollection, SolanaRetryOpenDrop, + SolanaRetryUpdatedCollectionMint, SolanaSwitchMintCollectionRequested, + SolanaTransferAsset, SolanaUpdateCollection, SolanaUpdateEditionDrop, + SolanaUpdateOpenDrop, SolanaUpdatedCollectionMint, + }, + MetaplexMasterEditionTransaction, MintMetaplexEditionTransaction, + MintMetaplexMetadataTransaction, NftEventKey, NftEvents, RetryUpdateSolanaMintPayload, + SwitchCollectionPayload, TransferMetaplexAssetTransaction, UpdateSolanaMintPayload, }, - MetaplexMasterEditionTransaction, MintMetaplexEditionTransaction, - MintMetaplexMetadataTransaction, NftEventKey, NftEvents, RetryUpdateSolanaMintPayload, - SwitchCollectionPayload, TransferMetaplexAssetTransaction, UpdateSolanaMintPayload, }; + #[derive(Clone)] pub struct Solana { producer: Producer, } +pub enum MintDropTransaction { + Edition(MintMetaplexEditionTransaction), + Open(MintMetaplexMetadataTransaction), +} + impl Solana { #[must_use] pub fn new(producer: Producer) -> Self { @@ -29,7 +40,7 @@ impl Solana { &self, ) -> impl DropEvent< MetaplexMasterEditionTransaction, - MintMetaplexEditionTransaction, + MintDropTransaction, MetaplexMasterEditionTransaction, > + TransferEvent + CollectionEvent< @@ -42,51 +53,59 @@ impl Solana { } #[async_trait::async_trait] -impl - DropEvent< - MetaplexMasterEditionTransaction, - MintMetaplexEditionTransaction, - MetaplexMasterEditionTransaction, - > for Solana +impl DropEvent + for Solana { async fn create_drop( &self, + drop_type: DropType, key: NftEventKey, payload: MetaplexMasterEditionTransaction, ) -> Result<()> { - let event = NftEvents { - event: Some(SolanaCreateDrop(payload)), + let event = match drop_type { + DropType::Edition => Some(SolanaCreateEditionDrop(payload)), + DropType::Open => Some(SolanaCreateOpenDrop(payload)), }; - self.producer.send(Some(&event), Some(&key)).await?; + self.producer + .send(Some(&NftEvents { event }), Some(&key)) + .await?; Ok(()) } async fn retry_create_drop( &self, + drop_type: DropType, key: NftEventKey, payload: MetaplexMasterEditionTransaction, ) -> Result<()> { - let event = NftEvents { - event: Some(SolanaRetryDrop(payload)), + let event = match drop_type { + DropType::Edition => Some(SolanaRetryEditionDrop(payload)), + DropType::Open => Some(SolanaRetryOpenDrop(payload)), }; - self.producer.send(Some(&event), Some(&key)).await?; + self.producer + .send(Some(&NftEvents { event }), Some(&key)) + .await?; Ok(()) } async fn update_drop( &self, + drop_type: DropType, key: NftEventKey, payload: MetaplexMasterEditionTransaction, ) -> Result<()> { - let event = NftEvents { - event: Some(SolanaUpdateDrop(payload)), + let event = match drop_type { + DropType::Edition => Some(SolanaUpdateEditionDrop(payload)), + DropType::Open => Some(SolanaUpdateOpenDrop(payload)), }; - self.producer.send(Some(&event), Some(&key)).await?; + self.producer + .send(Some(&NftEvents { event }), Some(&key)) + .await?; Ok(()) } @@ -94,13 +113,16 @@ impl async fn mint_drop( &self, key: NftEventKey, - payload: MintMetaplexEditionTransaction, + payload: MintDropTransaction, ) -> Result<()> { - let event = NftEvents { - event: Some(SolanaMintDrop(payload)), + let event = match payload { + MintDropTransaction::Edition(p) => Some(SolanaMintEditionDrop(p)), + MintDropTransaction::Open(p) => Some(SolanaMintOpenDrop(p)), }; - self.producer.send(Some(&event), Some(&key)).await?; + self.producer + .send(Some(&NftEvents { event }), Some(&key)) + .await?; Ok(()) } @@ -108,13 +130,16 @@ impl async fn retry_mint_drop( &self, key: NftEventKey, - payload: MintMetaplexEditionTransaction, + payload: MintDropTransaction, ) -> Result<()> { - let event = NftEvents { - event: Some(SolanaRetryMintDrop(payload)), + let event = match payload { + MintDropTransaction::Edition(tx) => Some(SolanaRetryMintEditionDrop(tx)), + MintDropTransaction::Open(tx) => Some(SolanaRetryMintOpenDrop(tx)), }; - self.producer.send(Some(&event), Some(&key)).await?; + self.producer + .send(Some(&NftEvents { event }), Some(&key)) + .await?; Ok(()) } @@ -255,3 +280,82 @@ impl Ok(()) } } + +// #[async_trait::async_trait] +// impl +// OpenDropEvent< +// MetaplexMasterEditionTransaction, +// MintMetaplexMetadataTransaction, +// MetaplexMasterEditionTransaction, +// > for Solana +// { +// async fn create_open_drop( +// &self, +// key: NftEventKey, +// payload: MetaplexMasterEditionTransaction, +// ) -> Result<()> { +// let event = NftEvents { +// event: Some(SolanaCreateOpenDrop(payload)), +// }; + +// self.producer.send(Some(&event), Some(&key)).await?; + +// Ok(()) +// } + +// async fn retry_create_open_drop( +// &self, +// key: NftEventKey, +// payload: MetaplexMasterEditionTransaction, +// ) -> Result<()> { +// let event = NftEvents { +// event: Some(SolanaRetryOpenDrop(payload)), +// }; + +// self.producer.send(Some(&event), Some(&key)).await?; + +// Ok(()) +// } + +// async fn update_open_drop( +// &self, +// key: NftEventKey, +// payload: MetaplexMasterEditionTransaction, +// ) -> Result<()> { +// let event = NftEvents { +// event: Some(SolanaUpdateOpenDrop(payload)), +// }; + +// self.producer.send(Some(&event), Some(&key)).await?; + +// Ok(()) +// } + +// async fn mint_open_drop( +// &self, +// key: NftEventKey, +// payload: MintMetaplexMetadataTransaction, +// ) -> Result<()> { +// let event = NftEvents { +// event: Some(SolanaMintOpenDrop(payload)), +// }; + +// self.producer.send(Some(&event), Some(&key)).await?; + +// Ok(()) +// } + +// async fn retry_mint_open_drop( +// &self, +// key: NftEventKey, +// payload: MintMetaplexMetadataTransaction, +// ) -> Result<()> { +// let event = NftEvents { +// event: Some(SolanaRetryMintOpenDrop(payload)), +// }; + +// self.producer.send(Some(&event), Some(&key)).await?; + +// Ok(()) +// } +// } diff --git a/api/src/dataloaders/collection_mints.rs b/api/src/dataloaders/collection_mints.rs index 1e37b6f..1993d3e 100644 --- a/api/src/dataloaders/collection_mints.rs +++ b/api/src/dataloaders/collection_mints.rs @@ -74,12 +74,11 @@ impl DataLoader for OwnerLoader { Ok(collection_mints .into_iter() .fold(HashMap::new(), |mut acc, collection_mint| { - acc.entry(collection_mint.owner.clone()) - .or_insert_with(Vec::new); - - acc.entry(collection_mint.owner.clone()) - .and_modify(|collection_mints| collection_mints.push(collection_mint.into())); - + if let Some(owner) = collection_mint.owner.clone() { + acc.entry(owner.clone()) + .or_insert_with(Vec::new) + .push(collection_mint.into()); + } acc })) } diff --git a/api/src/entities/collection_mints.rs b/api/src/entities/collection_mints.rs index 940ab90..730f98d 100644 --- a/api/src/entities/collection_mints.rs +++ b/api/src/entities/collection_mints.rs @@ -13,8 +13,8 @@ pub struct Model { pub collection_id: Uuid, #[sea_orm(column_type = "Text", nullable)] pub address: Option, - #[sea_orm(column_type = "Text")] - pub owner: String, + #[sea_orm(column_type = "Text", nullable)] + pub owner: Option, pub creation_status: CreationStatus, pub created_by: Uuid, pub created_at: DateTimeWithTimeZone, diff --git a/api/src/entities/drops.rs b/api/src/entities/drops.rs index b19c1b2..ed5a48e 100644 --- a/api/src/entities/drops.rs +++ b/api/src/entities/drops.rs @@ -2,7 +2,7 @@ use sea_orm::entity::prelude::*; -use super::sea_orm_active_enums::CreationStatus; +use super::sea_orm_active_enums::{CreationStatus, DropType}; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "drops")] @@ -20,6 +20,7 @@ pub struct Model { pub paused_at: Option, pub shutdown_at: Option, pub credits_deduction_id: Option, + pub drop_type: DropType, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/api/src/entities/sea_orm_active_enums.rs b/api/src/entities/sea_orm_active_enums.rs index db967e9..824c485 100644 --- a/api/src/entities/sea_orm_active_enums.rs +++ b/api/src/entities/sea_orm_active_enums.rs @@ -43,4 +43,15 @@ pub enum CreationStatus { Pending, #[sea_orm(string_value = "rejected")] Rejected, + #[sea_orm(string_value = "queued")] + Queued, +} + +#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Enum, Copy)] +#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "drop_type")] +pub enum DropType { + #[sea_orm(string_value = "edition")] + Edition, + #[sea_orm(string_value = "open")] + Open, } diff --git a/api/src/events.rs b/api/src/events.rs index 8a2dd9a..40e5eac 100644 --- a/api/src/events.rs +++ b/api/src/events.rs @@ -71,6 +71,8 @@ pub enum ProcessorErrorKind { #[error("Database record contains no deduction ID")] RecordMissingDeductionId, + #[error("Database record contains no owner address")] + RecordMissingOwner, #[error("Invalid basis point value for seller fee")] #[permanent] @@ -188,8 +190,8 @@ impl Processor { e, ) => match e.event { Some( - SolanaNftsEvent::CreateDropSubmitted(payload) - | SolanaNftsEvent::RetryCreateDropSubmitted(payload), + SolanaNftsEvent::CreateEditionDropSubmitted(payload) + | SolanaNftsEvent::RetryCreateEditionDropSubmitted(payload), ) => { self.drop_created(id, MintResult::Success(payload.into())) .await @@ -202,8 +204,8 @@ impl Processor { .await }, Some( - SolanaNftsEvent::MintDropSubmitted(payload) - | SolanaNftsEvent::RetryMintDropSubmitted(payload), + SolanaNftsEvent::MintEditionDropSubmitted(payload) + | SolanaNftsEvent::RetryMintEditionDropSubmitted(payload), ) => { self.drop_minted(id, MintResult::Success(payload.into())) .await @@ -229,14 +231,14 @@ impl Processor { .await }, Some( - SolanaNftsEvent::CreateDropFailed(_) - | SolanaNftsEvent::RetryCreateDropFailed(_), + SolanaNftsEvent::CreateEditionDropFailed(_) + | SolanaNftsEvent::RetryCreateEditionDropFailed(_), ) => self.drop_created(id, MintResult::Failure).await, Some( SolanaNftsEvent::CreateCollectionFailed(_) | SolanaNftsEvent::RetryCreateCollectionFailed(_), ) => self.collection_created(id, MintResult::Failure).await, - Some(SolanaNftsEvent::MintDropFailed(_)) => { + Some(SolanaNftsEvent::MintEditionDropFailed(_)) => { self.drop_minted(id, MintResult::Failure).await }, Some( @@ -246,7 +248,7 @@ impl Processor { Some(SolanaNftsEvent::TransferAssetFailed(_)) => { self.mint_transferred(id, TransferResult::Failure).await }, - Some(SolanaNftsEvent::RetryMintDropFailed(_)) => { + Some(SolanaNftsEvent::RetryMintEditionDropFailed(_)) => { self.drop_minted(id, MintResult::Failure).await }, Some( @@ -390,7 +392,7 @@ impl Processor { id: Set(id.parse()?), collection_id: Set(collection_id.parse()?), address: Set(Some(mint_address)), - owner: Set(owner), + owner: Set(Some(owner)), creation_status: Set(CreationStatus::Created), created_by: Set(created_by.parse()?), created_at: Set(Utc::now().into()), @@ -463,7 +465,7 @@ impl Processor { .ok_or(ProcessorErrorKind::DbMissingCollectionMint)?; let mut mint_am: collection_mints::ActiveModel = mint.into(); - mint_am.owner = Set(payload.recipient.clone()); + mint_am.owner = Set(Some(payload.recipient.clone())); mint_am.update(self.db.get()).await?; @@ -514,13 +516,13 @@ impl Processor { for mint in mints { let mut mint_am: collection_mints::ActiveModel = mint.clone().into(); - mint_am.owner = Set(new_owner.clone()); + mint_am.owner = Set(Some(new_owner.clone())); mint_am.update(&txn).await?; let nft_transfers = nft_transfers::ActiveModel { tx_signature: Set(Some(transaction_hash.clone())), collection_mint_id: Set(mint.id), - sender: Set(mint.owner), + sender: Set(mint.owner.ok_or(ProcessorErrorKind::RecordMissingOwner)?), recipient: Set(new_owner.clone()), created_at: Set(created_at), ..Default::default() diff --git a/api/src/mutations/drop.rs b/api/src/mutations/drop.rs index 71e0a64..670bd02 100644 --- a/api/src/mutations/drop.rs +++ b/api/src/mutations/drop.rs @@ -15,7 +15,7 @@ use crate::{ collection_creators, collections, drops, metadata_jsons, prelude::{CollectionCreators, Collections, Drops, MetadataJsons}, project_wallets, - sea_orm_active_enums::{Blockchain as BlockchainEnum, CreationStatus}, + sea_orm_active_enums::{Blockchain as BlockchainEnum, CreationStatus, DropType}, }, metadata_json::MetadataJson, objects::{Creator, Drop, MetadataJsonInput}, @@ -98,7 +98,14 @@ impl Mutation { .save(db) .await?; - let metadata_json = MetadataJson::new(input.metadata_json) + let metadata_jsons::Model { + name, + symbol, + uri, + description, + image, + .. + } = MetadataJson::new(input.metadata_json) .upload(nft_storage) .await? .save(collection.id, db) @@ -114,6 +121,7 @@ impl Mutation { created_by: Set(user_id), created_at: Set(Utc::now().into()), credits_deduction_id: Set(Some(credits_deduction_id)), + drop_type: Set(input.drop_type), ..Default::default() }; @@ -124,47 +132,56 @@ impl Mutation { project_id: input.project.to_string(), }; + + + let payload = proto::MetaplexMasterEditionTransaction { + master_edition: Some(proto::MasterEdition { + owner_address: owner_address.clone(), + supply: input.supply.map(TryInto::try_into).transpose()?, + name: name.clone(), + symbol, + metadata_uri: uri.clone(), + seller_fee_basis_points: seller_fee_basis_points.into(), + creators: input + .creators + .clone() + .into_iter() + .map(TryFrom::try_from) + .collect::>()?, + }), + }; + match input.blockchain { BlockchainEnum::Solana => { solana .event() - .create_drop(event_key, proto::MetaplexMasterEditionTransaction { - master_edition: Some(proto::MasterEdition { - owner_address, - supply: input.supply.map(TryInto::try_into).transpose()?, - name: metadata_json.name, - symbol: metadata_json.symbol, - metadata_uri: metadata_json.uri, - seller_fee_basis_points: seller_fee_basis_points.into(), - creators: input - .creators - .into_iter() - .map(TryFrom::try_from) - .collect::>()?, - }), - }) + .create_drop(input.drop_type, event_key, payload) .await?; }, BlockchainEnum::Polygon => { let amount = input.supply.ok_or(Error::new("supply is required"))?; polygon - .create_drop(event_key, proto::CreateEditionTransaction { - amount: amount.try_into()?, - edition_info: Some(proto::EditionInfo { - creator: input - .creators - .get(0) - .ok_or(Error::new("creator is required"))? - .clone() - .address, - collection: metadata_json.name, - uri: metadata_json.uri, - description: metadata_json.description, - image_uri: metadata_json.image, - }), - fee_receiver: owner_address.clone(), - fee_numerator: seller_fee_basis_points.into(), - }) + .create_drop( + input.drop_type, + event_key, + proto::CreateEditionTransaction { + amount: amount.try_into()?, + edition_info: Some(proto::EditionInfo { + creator: input + .creators + .get(0) + .ok_or(Error::new("creator is required"))? + .clone() + .address, + collection: name, + uri, + description, + image_uri: image, + }), + fee_receiver: owner_address, + fee_numerator: seller_fee_basis_points.into(), + }, + ) .await?; }, BlockchainEnum::Ethereum => { @@ -269,24 +286,28 @@ impl Mutation { BlockchainEnum::Solana => { solana .event() - .retry_create_drop(event_key, proto::MetaplexMasterEditionTransaction { - master_edition: Some(proto::MasterEdition { - owner_address, - supply: collection.supply.map(TryInto::try_into).transpose()?, - name: metadata_json.name, - symbol: metadata_json.symbol, - metadata_uri: metadata_json.uri, - seller_fee_basis_points: collection.seller_fee_basis_points.into(), - creators: creators - .into_iter() - .map(|c| proto::Creator { - address: c.address, - verified: c.verified, - share: c.share, - }) - .collect(), - }), - }) + .retry_create_drop( + drop.drop_type, + event_key, + proto::MetaplexMasterEditionTransaction { + master_edition: Some(proto::MasterEdition { + owner_address, + supply: collection.supply.map(TryInto::try_into).transpose()?, + name: metadata_json.name, + symbol: metadata_json.symbol, + metadata_uri: metadata_json.uri, + seller_fee_basis_points: collection.seller_fee_basis_points.into(), + creators: creators + .into_iter() + .map(|c| proto::Creator { + address: c.address, + verified: c.verified, + share: c.share, + }) + .collect(), + }), + }, + ) .await?; }, BlockchainEnum::Polygon => { @@ -296,12 +317,16 @@ impl Mutation { polygon .event() - .retry_create_drop(event_key, proto::CreateEditionTransaction { - edition_info: None, - amount, - fee_receiver: owner_address, - fee_numerator: collection.seller_fee_basis_points.into(), - }) + .retry_create_drop( + drop.drop_type, + event_key, + proto::CreateEditionTransaction { + edition_info: None, + amount, + fee_receiver: owner_address, + fee_numerator: collection.seller_fee_basis_points.into(), + }, + ) .await?; }, BlockchainEnum::Ethereum => { @@ -569,17 +594,21 @@ impl Mutation { solana .event() - .update_drop(event_key, proto::MetaplexMasterEditionTransaction { - master_edition: Some(proto::MasterEdition { - owner_address, - supply: collection.supply.map(TryInto::try_into).transpose()?, - name: metadata_json_model.name, - symbol: metadata_json_model.symbol, - metadata_uri: metadata_json_model.uri, - seller_fee_basis_points: collection.seller_fee_basis_points.into(), - creators, - }), - }) + .update_drop( + drop_model.drop_type, + event_key, + proto::MetaplexMasterEditionTransaction { + master_edition: Some(proto::MasterEdition { + owner_address, + supply: collection.supply.map(TryInto::try_into).transpose()?, + name: metadata_json_model.name, + symbol: metadata_json_model.symbol, + metadata_uri: metadata_json_model.uri, + seller_fee_basis_points: collection.seller_fee_basis_points.into(), + creators, + }), + }, + ) .await?; }, BlockchainEnum::Polygon => { @@ -595,15 +624,19 @@ impl Mutation { polygon .event() - .update_drop(event_key, proto::UpdateEdtionTransaction { - edition_info: Some(EditionInfo { - description: metadata_json_model.description, - image_uri: metadata_json_model.image, - collection: metadata_json_model.name, - uri: metadata_json_model.uri, - creator, - }), - }) + .update_drop( + drop_model.drop_type, + event_key, + proto::UpdateEdtionTransaction { + edition_info: Some(EditionInfo { + description: metadata_json_model.description, + image_uri: metadata_json_model.image, + collection: metadata_json_model.name, + uri: metadata_json_model.uri, + creator, + }), + }, + ) .await?; }, BlockchainEnum::Ethereum => { @@ -644,7 +677,7 @@ pub struct CreateDropPayload { drop: Drop, } -#[derive(Debug, Clone, Serialize, Deserialize, InputObject)] +#[derive(Debug, Clone, InputObject)] pub struct CreateDropInput { pub project: Uuid, pub price: Option, @@ -655,6 +688,8 @@ pub struct CreateDropInput { pub blockchain: BlockchainEnum, pub creators: Vec, pub metadata_json: MetadataJsonInput, + #[graphql(name = "type")] + pub drop_type: DropType, } impl CreateDropInput { diff --git a/api/src/mutations/transfer.rs b/api/src/mutations/transfer.rs index 98ea103..4d27341 100644 --- a/api/src/mutations/transfer.rs +++ b/api/src/mutations/transfer.rs @@ -64,7 +64,10 @@ impl Mutation { let collection = collection.ok_or(Error::new("collection not found"))?; input.validate_recipient_address(collection.blockchain)?; - let owner_address = collection_mint_model.owner.clone(); + let owner_address = collection_mint_model + .owner + .clone() + .ok_or(Error::new("NFT is not owned by any wallet"))?; CustomerWallets::find_by_address(owner_address.clone()) .one(conn) @@ -102,23 +105,29 @@ impl Mutation { solana .event() - .transfer_asset(event_key, proto::TransferMetaplexAssetTransaction { - recipient_address, - owner_address, - collection_mint_id, - }) + .transfer_asset( + event_key, + proto::TransferMetaplexAssetTransaction { + recipient_address, + owner_address, + collection_mint_id, + }, + ) .await?; }, Blockchain::Polygon => { let polygon = ctx.data::()?; polygon .event() - .transfer_asset(event_key, TransferPolygonAsset { - collection_mint_id, - owner_address, - recipient_address, - amount: 1, - }) + .transfer_asset( + event_key, + TransferPolygonAsset { + collection_mint_id, + owner_address, + recipient_address, + amount: 1, + }, + ) .await?; }, Blockchain::Ethereum => { diff --git a/api/src/objects/collection_mint.rs b/api/src/objects/collection_mint.rs index a7c6b21..a4b79a8 100644 --- a/api/src/objects/collection_mint.rs +++ b/api/src/objects/collection_mint.rs @@ -25,7 +25,7 @@ pub struct CollectionMint { /// On EVM chains it is the concatenation of the contract address and the token id `{contractAddress}:{tokenId}`. pub address: Option, /// The wallet address of the owner of the NFT. - pub owner: String, + pub owner: Option, /// The status of the NFT creation. pub creation_status: CreationStatus, /// The unique ID of the creator of the NFT. diff --git a/api/src/objects/drop.rs b/api/src/objects/drop.rs index 2231872..580c535 100644 --- a/api/src/objects/drop.rs +++ b/api/src/objects/drop.rs @@ -1,4 +1,4 @@ -use async_graphql::{Context, Enum, Object, Result}; +use async_graphql::{Context, Enum, Object, Result, Error}; use hub_core::chrono::Utc; use sea_orm::entity::prelude::*; @@ -121,6 +121,7 @@ impl Drop { (_, _, Some(false), ..) | (_, _, None, _, _, CreationStatus::Created) => { Ok(DropStatus::Minting) }, + (_, _, _, _, _, CreationStatus::Queued) => Err(Error::new("Invalid Drop Status")), } } diff --git a/api/src/objects/holder.rs b/api/src/objects/holder.rs index e33aed4..c417a7d 100644 --- a/api/src/objects/holder.rs +++ b/api/src/objects/holder.rs @@ -31,7 +31,7 @@ impl Holder { .unwrap_or_default(); Ok(mints.into_iter().fold(Vec::new(), |mut acc, mint| { - if mint.owner == self.address { + if mint.owner == Some(self.address.clone()) { acc.push(mint.id); } diff --git a/migration/src/lib.rs b/migration/src/lib.rs index 5ec6e56..48935ec 100644 --- a/migration/src/lib.rs +++ b/migration/src/lib.rs @@ -53,6 +53,9 @@ mod m20230725_144506_drop_solana_collections_table; mod m20230807_090847_create_histories_table; mod m20230818_163948_downcase_polygon_addresses; mod m20230821_131630_create_switch_collection_histories_table; +mod m20230905_100852_add_type_to_drop; +mod m20230910_204731_add_queued_variant_to_mints_status; +mod m20230910_212742_make_owner_address_optional_for_mint; pub struct Migrator; @@ -113,6 +116,9 @@ impl MigratorTrait for Migrator { Box::new(m20230807_090847_create_histories_table::Migration), Box::new(m20230818_163948_downcase_polygon_addresses::Migration), Box::new(m20230821_131630_create_switch_collection_histories_table::Migration), + Box::new(m20230905_100852_add_type_to_drop::Migration), + Box::new(m20230910_204731_add_queued_variant_to_mints_status::Migration), + Box::new(m20230910_212742_make_owner_address_optional_for_mint::Migration), ] } } diff --git a/migration/src/m20230905_100852_add_type_to_drop.rs b/migration/src/m20230905_100852_add_type_to_drop.rs new file mode 100644 index 0000000..7db9fdb --- /dev/null +++ b/migration/src/m20230905_100852_add_type_to_drop.rs @@ -0,0 +1,66 @@ +use sea_orm_migration::{prelude::*, sea_query::extension::postgres::Type}; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_type( + Type::create() + .as_enum(DropType::Type) + .values([DropType::Edition, DropType::Open]) + .to_owned(), + ) + .await?; + + manager + .alter_table( + Table::alter() + .table(Drops::Table) + .add_column_if_not_exists( + ColumnDef::new(Drops::DropType) + .custom(DropType::Type) + .default("edition") + .not_null(), + ) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .alter_table( + Table::alter() + .table(Drops::Table) + .drop_column(Drops::DropType) + .to_owned(), + ) + .await + } +} + +#[derive(Iden)] +enum Drops { + Table, + DropType, +} + +pub enum DropType { + Type, + Edition, + Open, +} + +impl Iden for DropType { + fn unquoted(&self, s: &mut dyn std::fmt::Write) { + write!(s, "{}", match self { + Self::Type => "drop_type", + Self::Edition => "edition", + Self::Open => "open", + }) + .unwrap(); + } +} diff --git a/migration/src/m20230910_204731_add_queued_variant_to_mints_status.rs b/migration/src/m20230910_204731_add_queued_variant_to_mints_status.rs new file mode 100644 index 0000000..e2b4095 --- /dev/null +++ b/migration/src/m20230910_204731_add_queued_variant_to_mints_status.rs @@ -0,0 +1,24 @@ +use sea_orm_migration::{prelude::*, sea_query::extension::postgres::Type}; + +use crate::m20230214_212301_create_collections_table::CreationStatus; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .alter_type( + Type::alter() + .name(CreationStatus::Type) + .add_value(Alias::new("queued")) + .to_owned(), + ) + .await + } + + async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> { + Ok(()) + } +} diff --git a/migration/src/m20230910_212742_make_owner_address_optional_for_mint.rs b/migration/src/m20230910_212742_make_owner_address_optional_for_mint.rs new file mode 100644 index 0000000..093455c --- /dev/null +++ b/migration/src/m20230910_212742_make_owner_address_optional_for_mint.rs @@ -0,0 +1,35 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .alter_table( + Table::alter() + .table(CollectionMints::Table) + .modify_column(ColumnDef::new(CollectionMints::OwnerAddress).text().null()) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .alter_table( + Table::alter() + .table(CollectionMints::Table) + .modify_column(ColumnDef::new(CollectionMints::OwnerAddress).text().not_null()) + .to_owned(), + ) + .await + } +} + +#[derive(Iden)] +enum CollectionMints { + Table, + OwnerAddress, +} From dd4eecc3f5e01673c308f84ae9b3764c0abf8ae9 Mon Sep 17 00:00:00 2001 From: imabdulbasit Date: Mon, 11 Sep 2023 19:13:39 +0500 Subject: [PATCH 2/8] Mint open drop --- api/src/mutations/mint.rs | 456 +++++++++++++++++++++++++++++++++----- 1 file changed, 395 insertions(+), 61 deletions(-) diff --git a/api/src/mutations/mint.rs b/api/src/mutations/mint.rs index 263bd3a..c5b7383 100644 --- a/api/src/mutations/mint.rs +++ b/api/src/mutations/mint.rs @@ -6,16 +6,20 @@ use hub_core::{ credits::{CreditsClient, TransactionId}, producer::Producer, }; -use sea_orm::{prelude::*, JoinType, QuerySelect, Set, TransactionTrait}; +use sea_orm::{prelude::*, JoinType, Order, QueryOrder, QuerySelect, Set, TransactionTrait}; use super::collection::{ fetch_owner, validate_creators, validate_json, validate_solana_creator_verification, }; use crate::{ - blockchains::{polygon::Polygon, solana::Solana, CollectionEvent, DropEvent}, + blockchains::{ + polygon::Polygon, + solana::{MintDropTransaction, Solana}, + CollectionEvent, DropEvent, + }, entities::{ - collection_mints, collections, drops, mint_creators, mint_histories, - prelude::{CollectionMints, Collections, Drops}, + collection_creators, collection_mints, collections, drops, mint_creators, mint_histories, + prelude::{CollectionCreators, CollectionMints, Collections, Drops}, project_wallets, sea_orm_active_enums::{Blockchain as BlockchainEnum, CreationStatus}, update_histories, @@ -114,7 +118,7 @@ impl Mutation { // insert a collection mint record into database let collection_mint_active_model = collection_mints::ActiveModel { collection_id: Set(collection.id), - owner: Set(input.recipient.clone()), + owner: Set(Some(input.recipient.clone())), creation_status: Set(CreationStatus::Pending), seller_fee_basis_points: Set(collection.seller_fee_basis_points), created_by: Set(user_id), @@ -139,22 +143,28 @@ impl Mutation { solana .event() - .mint_drop(event_key, proto::MintMetaplexEditionTransaction { - recipient_address: input.recipient.to_string(), - owner_address: owner_address.to_string(), - edition, - collection_id: collection.id.to_string(), - }) + .mint_drop( + event_key, + MintDropTransaction::Edition(proto::MintMetaplexEditionTransaction { + recipient_address: input.recipient.to_string(), + owner_address: owner_address.to_string(), + edition, + collection_id: collection.id.to_string(), + }), + ) .await?; }, BlockchainEnum::Polygon => { polygon .event() - .mint_drop(event_key, proto::MintEditionTransaction { - receiver: input.recipient.to_string(), - amount: 1, - collection_id: collection.id.to_string(), - }) + .mint_drop( + event_key, + proto::MintEditionTransaction { + receiver: input.recipient.to_string(), + amount: 1, + collection_id: collection.id.to_string(), + }, + ) .await?; }, BlockchainEnum::Ethereum => { @@ -253,7 +263,11 @@ impl Mutation { let drop_model = drop.ok_or(Error::new("drop not found"))?; - let recipient = collection_mint_model.owner.clone(); + let recipient = collection_mint_model + .owner + .clone() + .ok_or(Error::new("collection mint does not have an owner"))?; + let edition = collection_mint_model.edition; let project_id = drop_model.project_id; @@ -294,22 +308,28 @@ impl Mutation { BlockchainEnum::Solana => { solana .event() - .retry_mint_drop(event_key, proto::MintMetaplexEditionTransaction { - recipient_address: recipient.to_string(), - owner_address: owner_address.to_string(), - edition, - collection_id: collection.id.to_string(), - }) + .retry_mint_drop( + event_key, + MintDropTransaction::Edition(proto::MintMetaplexEditionTransaction { + recipient_address: recipient.to_string(), + owner_address: owner_address.to_string(), + edition, + collection_id: collection.id.to_string(), + }), + ) .await?; }, BlockchainEnum::Polygon => { polygon .event() - .retry_mint_drop(event_key, proto::MintEditionTransaction { - receiver: recipient.to_string(), - amount: 1, - collection_id: collection.id.to_string(), - }) + .retry_mint_drop( + event_key, + proto::MintEditionTransaction { + receiver: recipient.to_string(), + amount: 1, + collection_id: collection.id.to_string(), + }, + ) .await?; }, BlockchainEnum::Ethereum => { @@ -398,7 +418,7 @@ impl Mutation { // insert a collection mint record into database let collection_mint_active_model = collection_mints::ActiveModel { collection_id: Set(collection.id), - owner: Set(input.recipient.clone()), + owner: Set(Some(input.recipient.clone())), creation_status: Set(CreationStatus::Pending), seller_fee_basis_points: Set(collection.seller_fee_basis_points), created_by: Set(user_id), @@ -436,22 +456,25 @@ impl Mutation { BlockchainEnum::Solana => { solana .event() - .mint_to_collection(event_key, proto::MintMetaplexMetadataTransaction { - metadata: Some(MetaplexMetadata { - owner_address, - name: metadata_json.name, - symbol: metadata_json.symbol, - metadata_uri: metadata_json.uri, - seller_fee_basis_points: seller_fee_basis_points.into(), - creators: creators - .into_iter() - .map(TryFrom::try_from) - .collect::>()?, - }), - recipient_address: input.recipient.to_string(), - compressed, - collection_id: collection.id.to_string(), - }) + .mint_to_collection( + event_key, + proto::MintMetaplexMetadataTransaction { + metadata: Some(MetaplexMetadata { + owner_address, + name: metadata_json.name, + symbol: metadata_json.symbol, + metadata_uri: metadata_json.uri, + seller_fee_basis_points: seller_fee_basis_points.into(), + creators: creators + .into_iter() + .map(TryFrom::try_from) + .collect::>()?, + }), + recipient_address: input.recipient.to_string(), + compressed, + collection_id: collection.id.to_string(), + }, + ) .await?; }, BlockchainEnum::Ethereum | BlockchainEnum::Polygon => { @@ -764,7 +787,11 @@ impl Mutation { let collection = collection.ok_or(Error::new("collection not found"))?; - let recipient = collection_mint_model.owner.clone(); + let recipient = collection_mint_model + .owner + .clone() + .ok_or(Error::new("collection mint does not have an owner"))?; + let project_id = collection.project_id; let blockchain = collection.blockchain; @@ -798,21 +825,24 @@ impl Mutation { BlockchainEnum::Solana => { solana .event() - .retry_mint_to_collection(event_key, proto::MintMetaplexMetadataTransaction { - metadata: Some(MetaplexMetadata { - owner_address, - name: metadata_json.name, - symbol: metadata_json.symbol, - metadata_uri: uri.ok_or(Error::new("metadata uri not found"))?, - seller_fee_basis_points: collection_mint_model - .seller_fee_basis_points - .into(), - creators: creators.into_iter().map(Into::into).collect(), - }), - recipient_address: recipient.to_string(), - compressed: collection_mint_model.compressed, - collection_id: collection_mint_model.collection_id.to_string(), - }) + .retry_mint_to_collection( + event_key, + proto::MintMetaplexMetadataTransaction { + metadata: Some(MetaplexMetadata { + owner_address, + name: metadata_json.name, + symbol: metadata_json.symbol, + metadata_uri: uri.ok_or(Error::new("metadata uri not found"))?, + seller_fee_basis_points: collection_mint_model + .seller_fee_basis_points + .into(), + creators: creators.into_iter().map(Into::into).collect(), + }), + recipient_address: recipient.to_string(), + compressed: collection_mint_model.compressed, + collection_id: collection_mint_model.collection_id.to_string(), + }, + ) .await?; }, BlockchainEnum::Ethereum | BlockchainEnum::Polygon => { @@ -824,6 +854,280 @@ impl Mutation { collection_mint: collection_mint_model.into(), }) } + + async fn queue_mint_to_drop( + &self, + ctx: &Context<'_>, + input: QueueMintToDropInput, + ) -> Result { + let AppContext { db, user_id, .. } = ctx.data::()?; + + let conn = db.get(); + let nft_storage = ctx.data::()?; + + let UserID(id) = user_id; + let user_id = id.ok_or(Error::new("X-USER-ID header not found"))?; + + let (drop, collection) = drops::Entity::find_by_id(input.drop) + .find_also_related(Collections) + .one(conn) + .await? + .ok_or(Error::new("drop not found"))?; + + let collection_model = collection.ok_or(Error::new("collection not found"))?; + + let mint = collection_mints::ActiveModel { + collection_id: Set(drop.collection_id), + owner: Set(None), + creation_status: Set(CreationStatus::Queued), + seller_fee_basis_points: Set(collection_model.seller_fee_basis_points), + created_by: Set(user_id), + compressed: Set(false), + ..Default::default() + }; + + let mint_model = mint.insert(conn).await?; + + MetadataJson::new(input.metadata_json) + .upload(nft_storage) + .await? + .save(mint_model.id, db) + .await?; + + let creators = CollectionCreators::find() + .filter(collection_creators::Column::CollectionId.eq(collection_model.id)) + .all(conn) + .await?; + + let mint_creators: Vec<_> = creators + .iter() + .map(|creator| mint_creators::ActiveModel { + collection_mint_id: Set(mint_model.id), + address: Set(creator.address.clone()), + verified: Set(creator.verified), + share: Set(creator.share), + }) + .collect(); + + mint_creators::Entity::insert_many(mint_creators) + .exec(conn) + .await?; + + Ok(QueueMintToDropPayload { + collection_mint: mint_model.into(), + }) + } + + async fn mint_queued( + &self, + ctx: &Context<'_>, + input: MintQueuedInput, + ) -> Result { + let AppContext { + db, + user_id, + organization_id, + balance, + .. + } = ctx.data::()?; + let credits = ctx.data::>()?; + let conn = db.get(); + let solana = ctx.data::()?; + + 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 (mint, collection) = collection_mints::Entity::find_by_id_with_collection(input.mint) + .one(conn) + .await? + .ok_or(Error::new("collection mint not found"))?; + + if mint.creation_status != CreationStatus::Queued { + return Err(Error::new("mint is not queued")); + } + + let collection = collection.ok_or(Error::new("collection not found"))?; + + let project_id = collection.project_id; + let blockchain = collection.blockchain; + + let owner_address = fetch_owner(conn, project_id, blockchain).await?; + + let MetadataJson { + metadata_json, uri, .. + } = MetadataJson::fetch(mint.id, db).await?; + + let event_key = NftEventKey { + id: mint.id.to_string(), + user_id: user_id.to_string(), + project_id: project_id.to_string(), + }; + + let creators = mint_creators::Entity::find_by_collection_mint_id(mint.id) + .all(conn) + .await?; + + let TransactionId(_) = credits + .submit_pending_deduction( + org_id, + user_id, + Actions::Mint, + collection.blockchain.into(), + balance, + ) + .await?; + + match collection.blockchain { + BlockchainEnum::Solana => { + solana + .event() + .mint_drop( + event_key, + MintDropTransaction::Open(proto::MintMetaplexMetadataTransaction { + metadata: Some(MetaplexMetadata { + owner_address, + name: metadata_json.name, + symbol: metadata_json.symbol, + metadata_uri: uri.unwrap(), + seller_fee_basis_points: mint.seller_fee_basis_points.into(), + creators: creators.into_iter().map(Into::into).collect(), + }), + recipient_address: input.recipient.to_string(), + compressed: input.compressed, + collection_id: collection.id.to_string(), + }), + ) + .await?; + }, + BlockchainEnum::Ethereum | BlockchainEnum::Polygon => { + return Err(Error::new("blockchain not supported as this time")); + }, + }; + + let mut mint_am: collection_mints::ActiveModel = mint.into(); + mint_am.creation_status = Set(CreationStatus::Pending); + let mint = mint_am.update(conn).await?; + + Ok(MintQueuedPayload { + collection_mint: mint.into(), + }) + } + + async fn mint_random_queued( + &self, + ctx: &Context<'_>, + input: MintRandomQueuedInput, + ) -> Result { + let AppContext { + db, + user_id, + organization_id, + balance, + .. + } = ctx.data::()?; + let credits = ctx.data::>()?; + let conn = db.get(); + let solana = ctx.data::()?; + + 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 drop = drops::Entity::find_by_id(input.drop) + .one(conn) + .await? + .ok_or(Error::new("drop not found"))?; + + let (mint, collection) = CollectionMints::find() + .join( + JoinType::InnerJoin, + collection_mints::Relation::Collections.def(), + ) + .select_also(collections::Entity) + .filter(collection_mints::Column::CollectionId.eq(drop.collection_id)) + .filter(collection_mints::Column::CreationStatus.eq(CreationStatus::Queued)) + .order_by(sea_orm::sea_query::Func::random(), Order::Asc) + .one(conn) + .await? + .ok_or(Error::new("No Queued mint found for the drop"))?; + + let collection = collection.ok_or(Error::new("collection not found"))?; + + let project_id = collection.project_id; + let blockchain = collection.blockchain; + + let owner_address = fetch_owner(conn, project_id, blockchain).await?; + + let MetadataJson { + metadata_json, uri, .. + } = MetadataJson::fetch(mint.id, db).await?; + + let event_key = NftEventKey { + id: mint.id.to_string(), + user_id: user_id.to_string(), + project_id: project_id.to_string(), + }; + + let creators = mint_creators::Entity::find_by_collection_mint_id(mint.id) + .all(conn) + .await?; + + let TransactionId(_) = credits + .submit_pending_deduction( + org_id, + user_id, + Actions::Mint, + collection.blockchain.into(), + balance, + ) + .await?; + + match collection.blockchain { + BlockchainEnum::Solana => { + solana + .event() + .mint_drop( + event_key, + MintDropTransaction::Open(proto::MintMetaplexMetadataTransaction { + metadata: Some(MetaplexMetadata { + owner_address, + name: metadata_json.name, + symbol: metadata_json.symbol, + metadata_uri: uri.unwrap(), + seller_fee_basis_points: mint.seller_fee_basis_points.into(), + creators: creators.into_iter().map(Into::into).collect(), + }), + recipient_address: input.recipient.to_string(), + compressed: input.compressed, + collection_id: collection.id.to_string(), + }), + ) + .await?; + }, + BlockchainEnum::Ethereum | BlockchainEnum::Polygon => { + return Err(Error::new("blockchain not supported as this time")); + }, + }; + + let mut mint_am: collection_mints::ActiveModel = mint.into(); + mint_am.creation_status = Set(CreationStatus::Pending); + let mint = mint_am.update(conn).await?; + + Ok(MintQueuedPayload { + collection_mint: mint.into(), + }) + } } fn validate_compress(blockchain: BlockchainEnum, compressed: bool) -> Result<(), Error> { @@ -973,3 +1277,33 @@ pub struct RetryUpdateMintInput { pub struct RetryUpdateMintPayload { status: CreationStatus, } + +#[derive(Debug, Clone, InputObject)] +pub struct QueueMintToDropInput { + drop: Uuid, + metadata_json: MetadataJsonInput, +} + +#[derive(Debug, Clone, SimpleObject)] +pub struct QueueMintToDropPayload { + collection_mint: CollectionMint, +} + +#[derive(Debug, Clone, InputObject)] +pub struct MintQueuedInput { + mint: Uuid, + recipient: String, + compressed: bool, +} + +#[derive(Debug, Clone, SimpleObject)] +pub struct MintQueuedPayload { + collection_mint: CollectionMint, +} + +#[derive(Debug, Clone, InputObject)] +pub struct MintRandomQueuedInput { + drop: Uuid, + recipient: String, + compressed: bool, +} From adfd25e3abe976e474f969a809d2d19ab8801f06 Mon Sep 17 00:00:00 2001 From: imabdulbasit Date: Mon, 11 Sep 2023 19:28:39 +0500 Subject: [PATCH 3/8] fmt --- api/proto.lock | 4 +- api/proto.toml | 2 +- api/src/blockchains/polygon.rs | 2 +- api/src/blockchains/solana.rs | 20 ++-- api/src/mutations/drop.rs | 18 ++-- api/src/mutations/mint.rs | 94 ++++++++----------- api/src/mutations/transfer.rs | 28 +++--- api/src/objects/drop.rs | 2 +- ...42_make_owner_address_optional_for_mint.rs | 8 +- 9 files changed, 77 insertions(+), 101 deletions(-) diff --git a/api/proto.lock b/api/proto.lock index 0c75d46..4c30c53 100644 --- a/api/proto.lock +++ b/api/proto.lock @@ -5,8 +5,8 @@ sha512 = "d75800df0d4744c6b0f4d9a9952d3bfd0bb6b24a8babd19104cc11b54a525f85551b3c [[schemas]] subject = "nfts" -version = 2 -sha512 = "db8fa9f4b2874ab79305997c3b255a9b9c1b04d291c66f1cb97f332b32758055c01e6ba0e7a4089255c664324c51fc4fe25a9d138698b7a999b1f4d2b5503a48" +version = 3 +sha512 = "b3b2136bd6c7a136d317da84395661de5fc056e8270510575a3281d78884d99a0d89f444754ed02cb18ad26dcc7cd65300c1df73b9d74d2edc6bcc8d552465d0" [[schemas]] subject = "organization" diff --git a/api/proto.toml b/api/proto.toml index 030a40b..77c8551 100644 --- a/api/proto.toml +++ b/api/proto.toml @@ -3,7 +3,7 @@ endpoint = "http://localhost:8081" [schemas] organization = 1 -nfts = 2 +nfts = 3 customer = 1 treasury = 1 solana_nfts = 1 diff --git a/api/src/blockchains/polygon.rs b/api/src/blockchains/polygon.rs index d1c9146..8696a33 100644 --- a/api/src/blockchains/polygon.rs +++ b/api/src/blockchains/polygon.rs @@ -28,7 +28,7 @@ impl Polygon { pub fn event( &self, ) -> impl DropEvent - + TransferEvent { + + TransferEvent { self.clone() } } diff --git a/api/src/blockchains/solana.rs b/api/src/blockchains/solana.rs index 3fd24ac..f99ad76 100644 --- a/api/src/blockchains/solana.rs +++ b/api/src/blockchains/solana.rs @@ -53,8 +53,12 @@ impl Solana { } #[async_trait::async_trait] -impl DropEvent - for Solana +impl + DropEvent< + MetaplexMasterEditionTransaction, + MintDropTransaction, + MetaplexMasterEditionTransaction, + > for Solana { async fn create_drop( &self, @@ -110,11 +114,7 @@ impl DropEvent Result<()> { + async fn mint_drop(&self, key: NftEventKey, payload: MintDropTransaction) -> Result<()> { let event = match payload { MintDropTransaction::Edition(p) => Some(SolanaMintEditionDrop(p)), MintDropTransaction::Open(p) => Some(SolanaMintOpenDrop(p)), @@ -127,11 +127,7 @@ impl DropEvent Result<()> { + async fn retry_mint_drop(&self, key: NftEventKey, payload: MintDropTransaction) -> Result<()> { let event = match payload { MintDropTransaction::Edition(tx) => Some(SolanaRetryMintEditionDrop(tx)), MintDropTransaction::Open(tx) => Some(SolanaRetryMintOpenDrop(tx)), diff --git a/api/src/mutations/drop.rs b/api/src/mutations/drop.rs index 670bd02..dc42084 100644 --- a/api/src/mutations/drop.rs +++ b/api/src/mutations/drop.rs @@ -132,8 +132,6 @@ impl Mutation { project_id: input.project.to_string(), }; - - let payload = proto::MetaplexMasterEditionTransaction { master_edition: Some(proto::MasterEdition { owner_address: owner_address.clone(), @@ -317,16 +315,12 @@ impl Mutation { polygon .event() - .retry_create_drop( - drop.drop_type, - event_key, - proto::CreateEditionTransaction { - edition_info: None, - amount, - fee_receiver: owner_address, - fee_numerator: collection.seller_fee_basis_points.into(), - }, - ) + .retry_create_drop(drop.drop_type, event_key, proto::CreateEditionTransaction { + edition_info: None, + amount, + fee_receiver: owner_address, + fee_numerator: collection.seller_fee_basis_points.into(), + }) .await?; }, BlockchainEnum::Ethereum => { diff --git a/api/src/mutations/mint.rs b/api/src/mutations/mint.rs index c5b7383..b1adbc0 100644 --- a/api/src/mutations/mint.rs +++ b/api/src/mutations/mint.rs @@ -157,14 +157,11 @@ impl Mutation { BlockchainEnum::Polygon => { polygon .event() - .mint_drop( - event_key, - proto::MintEditionTransaction { - receiver: input.recipient.to_string(), - amount: 1, - collection_id: collection.id.to_string(), - }, - ) + .mint_drop(event_key, proto::MintEditionTransaction { + receiver: input.recipient.to_string(), + amount: 1, + collection_id: collection.id.to_string(), + }) .await?; }, BlockchainEnum::Ethereum => { @@ -322,14 +319,11 @@ impl Mutation { BlockchainEnum::Polygon => { polygon .event() - .retry_mint_drop( - event_key, - proto::MintEditionTransaction { - receiver: recipient.to_string(), - amount: 1, - collection_id: collection.id.to_string(), - }, - ) + .retry_mint_drop(event_key, proto::MintEditionTransaction { + receiver: recipient.to_string(), + amount: 1, + collection_id: collection.id.to_string(), + }) .await?; }, BlockchainEnum::Ethereum => { @@ -456,25 +450,22 @@ impl Mutation { BlockchainEnum::Solana => { solana .event() - .mint_to_collection( - event_key, - proto::MintMetaplexMetadataTransaction { - metadata: Some(MetaplexMetadata { - owner_address, - name: metadata_json.name, - symbol: metadata_json.symbol, - metadata_uri: metadata_json.uri, - seller_fee_basis_points: seller_fee_basis_points.into(), - creators: creators - .into_iter() - .map(TryFrom::try_from) - .collect::>()?, - }), - recipient_address: input.recipient.to_string(), - compressed, - collection_id: collection.id.to_string(), - }, - ) + .mint_to_collection(event_key, proto::MintMetaplexMetadataTransaction { + metadata: Some(MetaplexMetadata { + owner_address, + name: metadata_json.name, + symbol: metadata_json.symbol, + metadata_uri: metadata_json.uri, + seller_fee_basis_points: seller_fee_basis_points.into(), + creators: creators + .into_iter() + .map(TryFrom::try_from) + .collect::>()?, + }), + recipient_address: input.recipient.to_string(), + compressed, + collection_id: collection.id.to_string(), + }) .await?; }, BlockchainEnum::Ethereum | BlockchainEnum::Polygon => { @@ -825,24 +816,21 @@ impl Mutation { BlockchainEnum::Solana => { solana .event() - .retry_mint_to_collection( - event_key, - proto::MintMetaplexMetadataTransaction { - metadata: Some(MetaplexMetadata { - owner_address, - name: metadata_json.name, - symbol: metadata_json.symbol, - metadata_uri: uri.ok_or(Error::new("metadata uri not found"))?, - seller_fee_basis_points: collection_mint_model - .seller_fee_basis_points - .into(), - creators: creators.into_iter().map(Into::into).collect(), - }), - recipient_address: recipient.to_string(), - compressed: collection_mint_model.compressed, - collection_id: collection_mint_model.collection_id.to_string(), - }, - ) + .retry_mint_to_collection(event_key, proto::MintMetaplexMetadataTransaction { + metadata: Some(MetaplexMetadata { + owner_address, + name: metadata_json.name, + symbol: metadata_json.symbol, + metadata_uri: uri.ok_or(Error::new("metadata uri not found"))?, + seller_fee_basis_points: collection_mint_model + .seller_fee_basis_points + .into(), + creators: creators.into_iter().map(Into::into).collect(), + }), + recipient_address: recipient.to_string(), + compressed: collection_mint_model.compressed, + collection_id: collection_mint_model.collection_id.to_string(), + }) .await?; }, BlockchainEnum::Ethereum | BlockchainEnum::Polygon => { diff --git a/api/src/mutations/transfer.rs b/api/src/mutations/transfer.rs index 4d27341..5da8778 100644 --- a/api/src/mutations/transfer.rs +++ b/api/src/mutations/transfer.rs @@ -105,29 +105,23 @@ impl Mutation { solana .event() - .transfer_asset( - event_key, - proto::TransferMetaplexAssetTransaction { - recipient_address, - owner_address, - collection_mint_id, - }, - ) + .transfer_asset(event_key, proto::TransferMetaplexAssetTransaction { + recipient_address, + owner_address, + collection_mint_id, + }) .await?; }, Blockchain::Polygon => { let polygon = ctx.data::()?; polygon .event() - .transfer_asset( - event_key, - TransferPolygonAsset { - collection_mint_id, - owner_address, - recipient_address, - amount: 1, - }, - ) + .transfer_asset(event_key, TransferPolygonAsset { + collection_mint_id, + owner_address, + recipient_address, + amount: 1, + }) .await?; }, Blockchain::Ethereum => { diff --git a/api/src/objects/drop.rs b/api/src/objects/drop.rs index 580c535..3286ef1 100644 --- a/api/src/objects/drop.rs +++ b/api/src/objects/drop.rs @@ -1,4 +1,4 @@ -use async_graphql::{Context, Enum, Object, Result, Error}; +use async_graphql::{Context, Enum, Error, Object, Result}; use hub_core::chrono::Utc; use sea_orm::entity::prelude::*; diff --git a/migration/src/m20230910_212742_make_owner_address_optional_for_mint.rs b/migration/src/m20230910_212742_make_owner_address_optional_for_mint.rs index 093455c..5c91d27 100644 --- a/migration/src/m20230910_212742_make_owner_address_optional_for_mint.rs +++ b/migration/src/m20230910_212742_make_owner_address_optional_for_mint.rs @@ -21,13 +21,17 @@ impl MigrationTrait for Migration { .alter_table( Table::alter() .table(CollectionMints::Table) - .modify_column(ColumnDef::new(CollectionMints::OwnerAddress).text().not_null()) + .modify_column( + ColumnDef::new(CollectionMints::OwnerAddress) + .text() + .not_null(), + ) .to_owned(), ) .await } } - + #[derive(Iden)] enum CollectionMints { Table, From ae1a301a52ae939a554cab698781328f8a6cd81a Mon Sep 17 00:00:00 2001 From: imabdulbasit Date: Mon, 11 Sep 2023 19:54:36 +0500 Subject: [PATCH 4/8] make mints compressed column nullable --- api/src/entities/collection_mints.rs | 3 +- api/src/events.rs | 2 +- api/src/mutations/collection.rs | 2 +- api/src/mutations/mint.rs | 11 ++++-- api/src/objects/collection_mint.rs | 2 +- migration/src/lib.rs | 2 + ..._144938_make_compressed_column_optional.rs | 39 +++++++++++++++++++ 7 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 migration/src/m20230911_144938_make_compressed_column_optional.rs diff --git a/api/src/entities/collection_mints.rs b/api/src/entities/collection_mints.rs index 730f98d..27dd451 100644 --- a/api/src/entities/collection_mints.rs +++ b/api/src/entities/collection_mints.rs @@ -23,7 +23,8 @@ pub struct Model { pub edition: i64, pub seller_fee_basis_points: i16, pub credits_deduction_id: Option, - pub compressed: bool, + #[sea_orm(nullable)] + pub compressed: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/api/src/events.rs b/api/src/events.rs index 40e5eac..9132466 100644 --- a/api/src/events.rs +++ b/api/src/events.rs @@ -402,7 +402,7 @@ impl Processor { .try_into() .map_err(ProcessorErrorKind::InvalidSellerFee)?), credits_deduction_id: Set(None), - compressed: Set(compressed), + compressed: Set(Some(compressed)), }; let mint_model = mint_am.insert(self.db.get()).await?; diff --git a/api/src/mutations/collection.rs b/api/src/mutations/collection.rs index f7ad8cb..e1ce85d 100644 --- a/api/src/mutations/collection.rs +++ b/api/src/mutations/collection.rs @@ -521,7 +521,7 @@ impl Mutation { return Err(Error::new("New collection must be Metaplex Certified")); } - if mint.compressed == true { + if let Some(true) = mint.compressed { return Err(Error::new( "Switching collection is only supported for uncompressed mint", )); diff --git a/api/src/mutations/mint.rs b/api/src/mutations/mint.rs index b1adbc0..5186e6e 100644 --- a/api/src/mutations/mint.rs +++ b/api/src/mutations/mint.rs @@ -416,7 +416,7 @@ impl Mutation { creation_status: Set(CreationStatus::Pending), seller_fee_basis_points: Set(collection.seller_fee_basis_points), created_by: Set(user_id), - compressed: Set(compressed), + compressed: Set(Some(compressed)), credits_deduction_id: Set(Some(credits_deduction_id)), ..Default::default() }; @@ -556,7 +556,7 @@ impl Mutation { return Err(Error::new("Mint is an edition and cannot be updated")); } - if mint.compressed { + if let Some(true) = mint.compressed { return Err(Error::new("Mint is compressed and cannot be updated")); } @@ -785,6 +785,9 @@ impl Mutation { let project_id = collection.project_id; let blockchain = collection.blockchain; + let compressed = collection_mint_model.compressed.ok_or(Error::new( + "collection mint does not have a compressed value", + ))?; let owner_address = fetch_owner(conn, project_id, blockchain).await?; @@ -828,7 +831,7 @@ impl Mutation { creators: creators.into_iter().map(Into::into).collect(), }), recipient_address: recipient.to_string(), - compressed: collection_mint_model.compressed, + compressed, collection_id: collection_mint_model.collection_id.to_string(), }) .await?; @@ -870,7 +873,7 @@ impl Mutation { creation_status: Set(CreationStatus::Queued), seller_fee_basis_points: Set(collection_model.seller_fee_basis_points), created_by: Set(user_id), - compressed: Set(false), + compressed: Set(None), ..Default::default() }; diff --git a/api/src/objects/collection_mint.rs b/api/src/objects/collection_mint.rs index a4b79a8..8893cd6 100644 --- a/api/src/objects/collection_mint.rs +++ b/api/src/objects/collection_mint.rs @@ -41,7 +41,7 @@ pub struct CollectionMint { /// credits deduction id pub credits_deduction_id: Option, /// Indicates if the NFT is compressed. Compression is only supported on Solana. - pub compressed: bool, + pub compressed: Option, } #[ComplexObject] diff --git a/migration/src/lib.rs b/migration/src/lib.rs index 48935ec..f227465 100644 --- a/migration/src/lib.rs +++ b/migration/src/lib.rs @@ -56,6 +56,7 @@ mod m20230821_131630_create_switch_collection_histories_table; mod m20230905_100852_add_type_to_drop; mod m20230910_204731_add_queued_variant_to_mints_status; mod m20230910_212742_make_owner_address_optional_for_mint; +mod m20230911_144938_make_compressed_column_optional; pub struct Migrator; @@ -119,6 +120,7 @@ impl MigratorTrait for Migrator { Box::new(m20230905_100852_add_type_to_drop::Migration), Box::new(m20230910_204731_add_queued_variant_to_mints_status::Migration), Box::new(m20230910_212742_make_owner_address_optional_for_mint::Migration), + Box::new(m20230911_144938_make_compressed_column_optional::Migration), ] } } diff --git a/migration/src/m20230911_144938_make_compressed_column_optional.rs b/migration/src/m20230911_144938_make_compressed_column_optional.rs new file mode 100644 index 0000000..536e90b --- /dev/null +++ b/migration/src/m20230911_144938_make_compressed_column_optional.rs @@ -0,0 +1,39 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .alter_table( + Table::alter() + .table(CollectionMints::Table) + .modify_column(ColumnDef::new(CollectionMints::Compressed).text().null()) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .alter_table( + Table::alter() + .table(CollectionMints::Table) + .modify_column( + ColumnDef::new(CollectionMints::Compressed) + .text() + .not_null(), + ) + .to_owned(), + ) + .await + } +} + +#[derive(Iden)] +enum CollectionMints { + Table, + Compressed, +} From f98fd9d94287dcac7bf51159d2823b8eddb729b3 Mon Sep 17 00:00:00 2001 From: imabdulbasit Date: Tue, 12 Sep 2023 17:48:01 +0500 Subject: [PATCH 5/8] Process successful transaction events to update drop status --- api/proto.lock | 4 +- api/proto.toml | 2 +- api/src/blockchains/solana.rs | 79 ------------------- api/src/events.rs | 21 +++++ api/src/mutations/collection.rs | 2 +- api/src/mutations/drop.rs | 10 ++- api/src/mutations/mint.rs | 22 +++++- ...42_make_owner_address_optional_for_mint.rs | 10 +-- ..._144938_make_compressed_column_optional.rs | 4 +- 9 files changed, 57 insertions(+), 97 deletions(-) diff --git a/api/proto.lock b/api/proto.lock index 4c30c53..50ab138 100644 --- a/api/proto.lock +++ b/api/proto.lock @@ -20,8 +20,8 @@ sha512 = "c5ddf43d2958ec690ee2261d0ff9808b67ce810d2fc4b6077f96f561929a920f03509f [[schemas]] subject = "solana_nfts" -version = 1 -sha512 = "37fd0e45d12271323d8abfe7343c08e475ca4737014d6181dcdcbcd683ebc33ab7cea1f4da00673e7ad2f0e1ae5ae917f16175b0650df357f649545928685a50" +version = 4 +sha512 = "967fefde938a0f6ce05194e4fca15673e681caac54d8aeec114c5d38418632b9696dbaf5362345a15114e5abb49de55d0af8b9edcc0f2c91f9ef1ccc4ff55d68" [[schemas]] subject = "timestamp" diff --git a/api/proto.toml b/api/proto.toml index 77c8551..9326838 100644 --- a/api/proto.toml +++ b/api/proto.toml @@ -6,6 +6,6 @@ organization = 1 nfts = 3 customer = 1 treasury = 1 -solana_nfts = 1 +solana_nfts = 4 polygon_nfts = 1 timestamp = 1 \ No newline at end of file diff --git a/api/src/blockchains/solana.rs b/api/src/blockchains/solana.rs index f99ad76..838775a 100644 --- a/api/src/blockchains/solana.rs +++ b/api/src/blockchains/solana.rs @@ -276,82 +276,3 @@ impl Ok(()) } } - -// #[async_trait::async_trait] -// impl -// OpenDropEvent< -// MetaplexMasterEditionTransaction, -// MintMetaplexMetadataTransaction, -// MetaplexMasterEditionTransaction, -// > for Solana -// { -// async fn create_open_drop( -// &self, -// key: NftEventKey, -// payload: MetaplexMasterEditionTransaction, -// ) -> Result<()> { -// let event = NftEvents { -// event: Some(SolanaCreateOpenDrop(payload)), -// }; - -// self.producer.send(Some(&event), Some(&key)).await?; - -// Ok(()) -// } - -// async fn retry_create_open_drop( -// &self, -// key: NftEventKey, -// payload: MetaplexMasterEditionTransaction, -// ) -> Result<()> { -// let event = NftEvents { -// event: Some(SolanaRetryOpenDrop(payload)), -// }; - -// self.producer.send(Some(&event), Some(&key)).await?; - -// Ok(()) -// } - -// async fn update_open_drop( -// &self, -// key: NftEventKey, -// payload: MetaplexMasterEditionTransaction, -// ) -> Result<()> { -// let event = NftEvents { -// event: Some(SolanaUpdateOpenDrop(payload)), -// }; - -// self.producer.send(Some(&event), Some(&key)).await?; - -// Ok(()) -// } - -// async fn mint_open_drop( -// &self, -// key: NftEventKey, -// payload: MintMetaplexMetadataTransaction, -// ) -> Result<()> { -// let event = NftEvents { -// event: Some(SolanaMintOpenDrop(payload)), -// }; - -// self.producer.send(Some(&event), Some(&key)).await?; - -// Ok(()) -// } - -// async fn retry_mint_open_drop( -// &self, -// key: NftEventKey, -// payload: MintMetaplexMetadataTransaction, -// ) -> Result<()> { -// let event = NftEvents { -// event: Some(SolanaRetryMintOpenDrop(payload)), -// }; - -// self.producer.send(Some(&event), Some(&key)).await?; - -// Ok(()) -// } -// } diff --git a/api/src/events.rs b/api/src/events.rs index 9132466..8d5969f 100644 --- a/api/src/events.rs +++ b/api/src/events.rs @@ -273,6 +273,27 @@ impl Processor { ) .await }, + Some( + SolanaNftsEvent::CreateOpenDropSubmitted(payload) + | SolanaNftsEvent::RetryCreateOpenDropSubmitted(payload), + ) => { + self.drop_created(id, MintResult::Success(payload.into())) + .await + }, + Some( + SolanaNftsEvent::MintOpenDropSubmitted(payload) + | SolanaNftsEvent::RetryMintOpenDropSubmitted(payload), + ) => { + self.drop_minted(id, MintResult::Success(payload.into())) + .await + }, + Some( + SolanaNftsEvent::CreateOpenDropFailed(_) + | SolanaNftsEvent::RetryCreateOpenDropFailed(_), + ) => self.drop_created(id, MintResult::Failure).await, + Some(SolanaNftsEvent::MintOpenDropFailed(_)) => { + self.drop_minted(id, MintResult::Failure).await + }, None | Some(_) => Ok(()), }, Services::Polygon(_, e) => match e.event { diff --git a/api/src/mutations/collection.rs b/api/src/mutations/collection.rs index e1ce85d..02238c8 100644 --- a/api/src/mutations/collection.rs +++ b/api/src/mutations/collection.rs @@ -521,7 +521,7 @@ impl Mutation { return Err(Error::new("New collection must be Metaplex Certified")); } - if let Some(true) = mint.compressed { + if Some(true) == mint.compressed { return Err(Error::new( "Switching collection is only supported for uncompressed mint", )); diff --git a/api/src/mutations/drop.rs b/api/src/mutations/drop.rs index dc42084..6a8c86d 100644 --- a/api/src/mutations/drop.rs +++ b/api/src/mutations/drop.rs @@ -63,7 +63,11 @@ impl Mutation { let nfts_producer = ctx.data::>()?; let owner_address = fetch_owner(conn, input.project, input.blockchain).await?; - + let supply = if input.drop_type == DropType::Open { + Some(0) + } else { + input.supply.map(TryInto::try_into).transpose()? + }; input.validate()?; if input.blockchain == BlockchainEnum::Solana { @@ -84,7 +88,7 @@ impl Mutation { let collection_am = collections::ActiveModel { blockchain: Set(input.blockchain), - supply: Set(input.supply.map(TryFrom::try_from).transpose()?), + supply: Set(supply), creation_status: Set(CreationStatus::Pending), seller_fee_basis_points: Set(seller_fee_basis_points.try_into()?), created_by: Set(user_id), @@ -135,7 +139,7 @@ impl Mutation { let payload = proto::MetaplexMasterEditionTransaction { master_edition: Some(proto::MasterEdition { owner_address: owner_address.clone(), - supply: input.supply.map(TryInto::try_into).transpose()?, + supply, name: name.clone(), symbol, metadata_uri: uri.clone(), diff --git a/api/src/mutations/mint.rs b/api/src/mutations/mint.rs index 5186e6e..4c9c087 100644 --- a/api/src/mutations/mint.rs +++ b/api/src/mutations/mint.rs @@ -556,7 +556,7 @@ impl Mutation { return Err(Error::new("Mint is an edition and cannot be updated")); } - if let Some(true) = mint.compressed { + if Some(true) == mint.compressed { return Err(Error::new("Mint is compressed and cannot be updated")); } @@ -964,11 +964,17 @@ impl Mutation { .all(conn) .await?; + let action = if input.compressed { + Actions::MintCompressed + } else { + Actions::Mint + }; + let TransactionId(_) = credits .submit_pending_deduction( org_id, user_id, - Actions::Mint, + action, collection.blockchain.into(), balance, ) @@ -1005,6 +1011,18 @@ impl Mutation { mint_am.creation_status = Set(CreationStatus::Pending); let mint = mint_am.update(conn).await?; + let mint_history_am = mint_histories::ActiveModel { + mint_id: Set(mint.id), + wallet: Set(input.recipient), + 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(conn).await?; + Ok(MintQueuedPayload { collection_mint: mint.into(), }) diff --git a/migration/src/m20230910_212742_make_owner_address_optional_for_mint.rs b/migration/src/m20230910_212742_make_owner_address_optional_for_mint.rs index 5c91d27..93dc88f 100644 --- a/migration/src/m20230910_212742_make_owner_address_optional_for_mint.rs +++ b/migration/src/m20230910_212742_make_owner_address_optional_for_mint.rs @@ -10,7 +10,7 @@ impl MigrationTrait for Migration { .alter_table( Table::alter() .table(CollectionMints::Table) - .modify_column(ColumnDef::new(CollectionMints::OwnerAddress).text().null()) + .modify_column(ColumnDef::new(CollectionMints::Owner).text().null()) .to_owned(), ) .await @@ -21,11 +21,7 @@ impl MigrationTrait for Migration { .alter_table( Table::alter() .table(CollectionMints::Table) - .modify_column( - ColumnDef::new(CollectionMints::OwnerAddress) - .text() - .not_null(), - ) + .modify_column(ColumnDef::new(CollectionMints::Owner).text().not_null()) .to_owned(), ) .await @@ -35,5 +31,5 @@ impl MigrationTrait for Migration { #[derive(Iden)] enum CollectionMints { Table, - OwnerAddress, + Owner, } diff --git a/migration/src/m20230911_144938_make_compressed_column_optional.rs b/migration/src/m20230911_144938_make_compressed_column_optional.rs index 536e90b..a4305cf 100644 --- a/migration/src/m20230911_144938_make_compressed_column_optional.rs +++ b/migration/src/m20230911_144938_make_compressed_column_optional.rs @@ -10,7 +10,7 @@ impl MigrationTrait for Migration { .alter_table( Table::alter() .table(CollectionMints::Table) - .modify_column(ColumnDef::new(CollectionMints::Compressed).text().null()) + .modify_column(ColumnDef::new(CollectionMints::Compressed).boolean().null()) .to_owned(), ) .await @@ -23,7 +23,7 @@ impl MigrationTrait for Migration { .table(CollectionMints::Table) .modify_column( ColumnDef::new(CollectionMints::Compressed) - .text() + .boolean() .not_null(), ) .to_owned(), From a13290585bf30d45bff23613173dd35ed2ab647c Mon Sep 17 00:00:00 2001 From: imabdulbasit Date: Tue, 12 Sep 2023 18:18:45 +0500 Subject: [PATCH 6/8] update protobuf schemas version --- api/proto.lock | 14 +++++++------- api/proto.toml | 14 +++++++------- .../dataloaders/switch_collection_histories.rs | 6 +++--- api/src/events.rs | 7 +++++++ api/src/mutations/collection.rs | 15 +++++++++++++++ api/src/mutations/mint.rs | 5 +++++ 6 files changed, 44 insertions(+), 17 deletions(-) diff --git a/api/proto.lock b/api/proto.lock index 50ab138..d5d8369 100644 --- a/api/proto.lock +++ b/api/proto.lock @@ -1,26 +1,26 @@ [[schemas]] subject = "customer" -version = 1 +version = 2 sha512 = "d75800df0d4744c6b0f4d9a9952d3bfd0bb6b24a8babd19104cc11b54a525f85551b3c7375d69aeabbcf629cd826aa0bc6b0c0467add20716c504f5e856ce1c5" [[schemas]] subject = "nfts" -version = 3 +version = 29 sha512 = "b3b2136bd6c7a136d317da84395661de5fc056e8270510575a3281d78884d99a0d89f444754ed02cb18ad26dcc7cd65300c1df73b9d74d2edc6bcc8d552465d0" [[schemas]] subject = "organization" -version = 1 +version = 5 sha512 = "9fb28ac73d9712292297394a5fa53a7dae9deba6847353582987ba749859301c23c05fd49d2ce84a1640f8864c5c04d59fa38907700b280000e5c4afc96654bf" [[schemas]] subject = "polygon_nfts" -version = 1 +version = 6 sha512 = "c5ddf43d2958ec690ee2261d0ff9808b67ce810d2fc4b6077f96f561929a920f03509fc8bd7adbda219250eb019f5f7be8a3f51c554f665ea1881f7a973ef2a6" [[schemas]] subject = "solana_nfts" -version = 4 +version = 11 sha512 = "967fefde938a0f6ce05194e4fca15673e681caac54d8aeec114c5d38418632b9696dbaf5362345a15114e5abb49de55d0af8b9edcc0f2c91f9ef1ccc4ff55d68" [[schemas]] @@ -30,5 +30,5 @@ sha512 = "d167e0a143c813073eef8597f0b237e5a8eaf32abbf709724e8071b2dd73ce0438b82f [[schemas]] subject = "treasury" -version = 1 -sha512 = "03580129f0561b3d153f83b9f2729bd18e27c7d29815c9c3b7493e57e3cad3ea56f6e803e0ec5f19e8c17669de5ef8c0eb1bd59cfbae5f400741d0a73a464ba5" +version = 23 +sha512 = "0e4d77999767d5971122e720c1cee7a57c3e47ce69f58a582f1762d8e65e031ea3bd9024cfc21bd7da5db6e38a71657151c58cdfa21d9ff643fb2fc657105cf5" diff --git a/api/proto.toml b/api/proto.toml index 9326838..e66a353 100644 --- a/api/proto.toml +++ b/api/proto.toml @@ -1,11 +1,11 @@ [registry] -endpoint = "http://localhost:8081" +endpoint = "https://schemas.holaplex.tools" [schemas] -organization = 1 -nfts = 3 -customer = 1 -treasury = 1 -solana_nfts = 4 -polygon_nfts = 1 +organization = 5 +nfts = 29 +customer = 2 +treasury = 23 +solana_nfts = 11 +polygon_nfts = 6 timestamp = 1 \ No newline at end of file diff --git a/api/src/dataloaders/switch_collection_histories.rs b/api/src/dataloaders/switch_collection_histories.rs index c1653bf..ceade5f 100644 --- a/api/src/dataloaders/switch_collection_histories.rs +++ b/api/src/dataloaders/switch_collection_histories.rs @@ -37,11 +37,11 @@ impl DataLoader for SwitchCollectionHistoryLoader { Ok(switch_histories.into_iter().fold( HashMap::new(), |mut acc: HashMap>, switch_history| { - acc.entry(switch_history.collection_mint_id.clone()) + acc.entry(switch_history.collection_mint_id) .or_insert_with(Vec::new); - acc.entry(switch_history.collection_mint_id.clone()) - .and_modify(|switch_histories| switch_histories.push(switch_history.into())); + acc.entry(switch_history.collection_mint_id) + .and_modify(|switch_histories| switch_histories.push(switch_history)); acc }, diff --git a/api/src/events.rs b/api/src/events.rs index 8d5969f..62d4c20 100644 --- a/api/src/events.rs +++ b/api/src/events.rs @@ -159,6 +159,13 @@ impl Processor { } } + #[allow(clippy::too_many_lines)] + + /// Processes incoming messages related to different services like Treasury and Solana. + /// Routes each message to the corresponding handler based on the type of service and the specific event. + + /// # Errors + /// - Returns an error wrapped in `ProcessorError` if any of the operations inside the function fail. pub async fn process(&self, msg: Services) -> Result<()> { match msg { Services::Treasury(TreasuryEventKey { id, .. }, e) => match e.event { diff --git a/api/src/mutations/collection.rs b/api/src/mutations/collection.rs index 02238c8..b8bafbc 100644 --- a/api/src/mutations/collection.rs +++ b/api/src/mutations/collection.rs @@ -577,6 +577,11 @@ impl Mutation { } } +/// Fetches the owner's wallet address for a given project and blockchain. +/// # Returns +/// - Returns a `Result` containing the wallet address of the owner if the operation is successful. +/// # Errors +/// - Returns an error if no project wallet is found for the specified blockchain. pub async fn fetch_owner( conn: &DatabaseConnection, project: Uuid, @@ -634,6 +639,10 @@ impl CreateCollectionInput { } } +/// Validates the Solana creator verification based on project treasury wallet address and the list of creators. +/// # Errors +/// - Returns an error if any of the creators are verified but their address does not match +/// the project treasury wallet address. pub fn validate_solana_creator_verification( project_treasury_wallet_address: &str, creators: &Vec, @@ -695,6 +704,9 @@ pub fn validate_creators(blockchain: BlockchainEnum, creators: &Vec) -> Ok(()) } +/// Validates a Solana address +/// # Errors +/// - Returns an error if the provided address is not a valid Solana address. pub fn validate_solana_address(address: &str) -> Result<()> { if Pubkey::from_str(address).is_err() { return Err(Error::new(format!( @@ -705,6 +717,9 @@ pub fn validate_solana_address(address: &str) -> Result<()> { Ok(()) } +/// Validates an EVM (Ethereum Virtual Machine) address format. +/// # Errors +/// - Returns an error if the provided address does not match the required EVM address format. pub fn validate_evm_address(address: &str) -> Result<()> { let err = Err(Error::new(format!("{address} is not a valid EVM address"))); diff --git a/api/src/mutations/mint.rs b/api/src/mutations/mint.rs index 4c9c087..d85e873 100644 --- a/api/src/mutations/mint.rs +++ b/api/src/mutations/mint.rs @@ -1287,17 +1287,20 @@ pub struct RetryUpdateMintPayload { status: CreationStatus, } +/// Represents input data for `queue_mint_to_drop` mutation #[derive(Debug, Clone, InputObject)] pub struct QueueMintToDropInput { drop: Uuid, metadata_json: MetadataJsonInput, } +/// Represents payload data for `queue_mint_to_drop` mutation #[derive(Debug, Clone, SimpleObject)] pub struct QueueMintToDropPayload { collection_mint: CollectionMint, } +/// Represents input data for `mint_queued` mutation #[derive(Debug, Clone, InputObject)] pub struct MintQueuedInput { mint: Uuid, @@ -1305,11 +1308,13 @@ pub struct MintQueuedInput { compressed: bool, } +/// Represents payload data for `mint_queued` mutation #[derive(Debug, Clone, SimpleObject)] pub struct MintQueuedPayload { collection_mint: CollectionMint, } +/// Represents input data for `mint_random_queued` mutation #[derive(Debug, Clone, InputObject)] pub struct MintRandomQueuedInput { drop: Uuid, From 1c43201f5d8cc9e275fdffc4d930a99d68640b52 Mon Sep 17 00:00:00 2001 From: imabdulbasit Date: Thu, 14 Sep 2023 15:54:28 +0500 Subject: [PATCH 7/8] make Edition as default drop type and emit events for hub-permissions --- api/src/entities/sea_orm_active_enums.rs | 6 +++ api/src/mutations/drop.rs | 2 +- api/src/mutations/mint.rs | 59 +++++++++++++++++++++++- 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/api/src/entities/sea_orm_active_enums.rs b/api/src/entities/sea_orm_active_enums.rs index 824c485..a453bc0 100644 --- a/api/src/entities/sea_orm_active_enums.rs +++ b/api/src/entities/sea_orm_active_enums.rs @@ -55,3 +55,9 @@ pub enum DropType { #[sea_orm(string_value = "open")] Open, } + +impl Default for DropType { + fn default() -> Self { + DropType::Edition + } +} diff --git a/api/src/mutations/drop.rs b/api/src/mutations/drop.rs index 6a8c86d..9e900df 100644 --- a/api/src/mutations/drop.rs +++ b/api/src/mutations/drop.rs @@ -686,7 +686,7 @@ pub struct CreateDropInput { pub blockchain: BlockchainEnum, pub creators: Vec, pub metadata_json: MetadataJsonInput, - #[graphql(name = "type")] + #[graphql(name = "type", default)] pub drop_type: DropType, } diff --git a/api/src/mutations/mint.rs b/api/src/mutations/mint.rs index d85e873..2b33ed3 100644 --- a/api/src/mutations/mint.rs +++ b/api/src/mutations/mint.rs @@ -846,6 +846,9 @@ impl Mutation { }) } + // Add a mint to the queue for a drop. + // The queued mint can be minted by calling `mint_queued` mutation for specific mint + // or `mint_random_queued_to_drop` for random mint. async fn queue_mint_to_drop( &self, ctx: &Context<'_>, @@ -909,6 +912,7 @@ impl Mutation { }) } + /// This mutation mints a specific queued drop mint. async fn mint_queued( &self, ctx: &Context<'_>, @@ -924,6 +928,7 @@ impl Mutation { let credits = ctx.data::>()?; let conn = db.get(); let solana = ctx.data::()?; + let nfts_producer = ctx.data::>()?; let UserID(id) = user_id; let OrganizationId(org) = organization_id; @@ -1011,6 +1016,28 @@ impl Mutation { mint_am.creation_status = Set(CreationStatus::Pending); let mint = mint_am.update(conn).await?; + let drop = drops::Entity::find() + .filter(drops::Column::CollectionId.eq(collection.id)) + .one(conn) + .await? + .ok_or(Error::new("drop not found"))?; + + 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?; + let mint_history_am = mint_histories::ActiveModel { mint_id: Set(mint.id), wallet: Set(input.recipient), @@ -1028,7 +1055,8 @@ impl Mutation { }) } - async fn mint_random_queued( + /// This mutation mints a random queued drop mint. + async fn mint_random_queued_to_drop( &self, ctx: &Context<'_>, input: MintRandomQueuedInput, @@ -1043,6 +1071,7 @@ impl Mutation { let credits = ctx.data::>()?; let conn = db.get(); let solana = ctx.data::()?; + let nfts_producer = ctx.data::>()?; let UserID(id) = user_id; let OrganizationId(org) = organization_id; @@ -1133,6 +1162,34 @@ impl Mutation { mint_am.creation_status = Set(CreationStatus::Pending); let mint = mint_am.update(conn).await?; + let mint_history_am = mint_histories::ActiveModel { + mint_id: Set(mint.id), + wallet: Set(input.recipient), + 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(conn).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?; + Ok(MintQueuedPayload { collection_mint: mint.into(), }) From 3ab49bfb331693ca80af44dd88d6c818affe4821 Mon Sep 17 00:00:00 2001 From: Kyle Espinola Date: Thu, 14 Sep 2023 08:38:50 -0400 Subject: [PATCH 8/8] Update collection_mints.rs refactor:no need to clone owner --- api/src/dataloaders/collection_mints.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/dataloaders/collection_mints.rs b/api/src/dataloaders/collection_mints.rs index 1993d3e..5924358 100644 --- a/api/src/dataloaders/collection_mints.rs +++ b/api/src/dataloaders/collection_mints.rs @@ -75,7 +75,7 @@ impl DataLoader for OwnerLoader { .into_iter() .fold(HashMap::new(), |mut acc, collection_mint| { if let Some(owner) = collection_mint.owner.clone() { - acc.entry(owner.clone()) + acc.entry(owner) .or_insert_with(Vec::new) .push(collection_mint.into()); }