Skip to content

Commit

Permalink
Merge pull request #32 from holaplex/espi/compression-transfer
Browse files Browse the repository at this point in the history
Compressed Transfer
  • Loading branch information
kespinola authored Aug 1, 2023
2 parents 48d9168 + 185035a commit 1d56989
Show file tree
Hide file tree
Showing 10 changed files with 214 additions and 71 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion consumer/src/asset_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ pub struct Asset {
pub struct AssetSupply {
pub print_max_supply: u32,
pub print_current_supply: u32,
pub edition_nonce: u64,
pub edition_nonce: Option<u64>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
Expand Down
13 changes: 9 additions & 4 deletions consumer/src/backend.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use holaplex_hub_nfts_solana_core::proto::{
MetaplexMasterEditionTransaction, SolanaPendingTransaction, TransferMetaplexAssetTransaction,
};
use holaplex_hub_nfts_solana_entity::{collection_mints, collections};
use holaplex_hub_nfts_solana_entity::collections;
use hub_core::prelude::*;
use solana_program::pubkey::Pubkey;

Expand Down Expand Up @@ -43,6 +43,11 @@ pub struct MintCompressedMintV1Addresses {
pub leaf_owner: Pubkey,
}

pub struct TransferCompressedMintV1Addresses {
pub owner: Pubkey,
pub recipient: Pubkey,
}

#[derive(Clone)]
pub struct UpdateMasterEditionAddresses {
pub metadata: Pubkey,
Expand Down Expand Up @@ -103,10 +108,10 @@ pub trait MintBackend<T, R> {
}

#[async_trait]
pub trait TransferBackend {
pub trait TransferBackend<M, R> {
async fn transfer(
&self,
collection_mint: &collection_mints::Model,
collection_mint: &M,
txn: TransferMetaplexAssetTransaction,
) -> Result<TransactionResponse<TransferAssetAddresses>>;
) -> Result<TransactionResponse<R>>;
}
24 changes: 19 additions & 5 deletions consumer/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ impl Processor {
self.process_nft(
EventKind::TransferAsset,
&key,
self.transfer_asset(&UncompressedRef(self.solana()), &key, payload),
self.transfer_asset(&key, payload),
)
.await
},
Expand Down Expand Up @@ -730,19 +730,33 @@ impl Processor {
Ok(tx.into())
}

async fn transfer_asset<B: TransferBackend>(
async fn transfer_asset(
&self,
backend: &B,
_key: &SolanaNftEventKey,
payload: TransferMetaplexAssetTransaction,
) -> ProcessResult<SolanaPendingTransaction> {
let collection_mint_id = Uuid::parse_str(&payload.collection_mint_id.clone())?;
let collection_mint = CollectionMint::find_by_id(&self.db, collection_mint_id)
let collection_mint = CollectionMint::find_by_id(&self.db, collection_mint_id).await?;

if let Some(collection_mint) = collection_mint {
let backend = &UncompressedRef(self.solana());

let tx = backend
.transfer(&collection_mint, payload)
.await
.map_err(ProcessorErrorKind::Solana)?;

return Ok(tx.into());
}

let compression_leaf = CompressionLeaf::find_by_id(&self.db, collection_mint_id)
.await?
.ok_or(ProcessorErrorKind::RecordNotFound)?;

let backend = &CompressedRef(self.solana());

let tx = backend
.transfer(&collection_mint, payload)
.transfer(&compression_leaf, payload)
.await
.map_err(ProcessorErrorKind::Solana)?;

Expand Down
2 changes: 1 addition & 1 deletion consumer/src/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ impl Processor {
_ => Ok(None),
}
},
_ => Ok(None),
Services::Treasury(..) => Ok(None),
}
}

Expand Down
110 changes: 69 additions & 41 deletions consumer/src/solana.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use holaplex_hub_nfts_solana_core::proto::{
MetaplexMetadata, MintMetaplexEditionTransaction, MintMetaplexMetadataTransaction,
TransferMetaplexAssetTransaction,
};
use holaplex_hub_nfts_solana_entity::{collection_mints, collections};
use holaplex_hub_nfts_solana_entity::{collection_mints, collections, compression_leafs};
use hub_core::{anyhow::Result, clap, prelude::*, thiserror, uuid::Uuid};
use mpl_bubblegum::state::metaplex_adapter::{
Collection, Creator as BubblegumCreator, TokenProgramVersion,
Expand All @@ -13,7 +13,7 @@ use mpl_token_metadata::{
instruction::{mint_new_edition_from_master_edition_via_token, update_metadata_accounts_v2},
state::{Creator, DataV2, EDITION, PREFIX},
};
use solana_client::rpc_client::RpcClient;
use solana_client::rpc_client::RpcClient as SolanaRpcClient;
use solana_program::{
instruction::Instruction, program_pack::Pack, pubkey::Pubkey,
system_instruction::create_account, system_program,
Expand All @@ -37,11 +37,11 @@ use spl_token::{
};

use crate::{
asset_api::RpcClient as _,
asset_api::RpcClient,
backend::{
CollectionBackend, MasterEditionAddresses, MintBackend, MintCompressedMintV1Addresses,
MintEditionAddresses, MintMetaplexAddresses, TransactionResponse, TransferAssetAddresses,
TransferBackend, UpdateMasterEditionAddresses,
TransferBackend, TransferCompressedMintV1Addresses, UpdateMasterEditionAddresses,
},
};

Expand Down Expand Up @@ -114,11 +114,13 @@ pub enum SolanaAssetIdError {
Base58(#[from] bs58::decode::Error),
#[error("Borsh deserialization error")]
BorshDeserialize(#[from] std::io::Error),
#[error("Asset id not found")]
NotFound,
}

#[derive(Clone)]
pub struct Solana {
rpc_client: Arc<RpcClient>,
rpc_client: Arc<SolanaRpcClient>,
treasury_wallet_address: Pubkey,
bubblegum_tree_authority: Pubkey,
bubblegum_merkle_tree: Pubkey,
Expand All @@ -135,7 +137,7 @@ impl Solana {
tree_authority,
merkle_tree,
} = args;
let rpc_client = Arc::new(RpcClient::new(solana_endpoint));
let rpc_client = Arc::new(SolanaRpcClient::new(solana_endpoint));

let (bubblegum_cpi_address, _) = Pubkey::find_program_address(
&[mpl_bubblegum::state::COLLECTION_CPI_PREFIX.as_bytes()],
Expand All @@ -160,7 +162,7 @@ impl Solana {
}

#[must_use]
pub fn rpc(&self) -> Arc<RpcClient> {
pub fn rpc(&self) -> Arc<SolanaRpcClient> {
self.rpc_client.clone()
}

Expand Down Expand Up @@ -581,7 +583,7 @@ impl<'a> MintBackend<MintMetaplexEditionTransaction, MintEditionAddresses> for E
}

#[async_trait]
impl<'a> TransferBackend for UncompressedRef<'a> {
impl<'a> TransferBackend<collection_mints::Model, TransferAssetAddresses> for UncompressedRef<'a> {
async fn transfer(
&self,
collection_mint: &collection_mints::Model,
Expand Down Expand Up @@ -645,50 +647,81 @@ impl<'a> TransferBackend for UncompressedRef<'a> {
}

#[async_trait]
impl<'a> TransferBackend for CompressedRef<'a> {
impl<'a> TransferBackend<compression_leafs::Model, TransferCompressedMintV1Addresses>
for CompressedRef<'a>
{
async fn transfer(
&self,
collection_mint: &collection_mints::Model,
compression_leaf: &compression_leafs::Model,
txn: TransferMetaplexAssetTransaction,
) -> hub_core::prelude::Result<TransactionResponse<TransferAssetAddresses>> {
) -> hub_core::prelude::Result<TransactionResponse<TransferCompressedMintV1Addresses>> {
let TransferMetaplexAssetTransaction {
recipient_address,
owner_address,
collection_mint_id,
..
} = txn;
let payer = self.0.treasury_wallet_address;
let recipient = recipient_address.parse()?;
let owner = owner_address.parse()?;

let asset_id = todo!("wait where's the asset address");
let asset = self
.0
.asset_rpc_client
.get_asset(asset_id)
let asset_api = &self.0.asset_rpc();

let tree_authority_address = Pubkey::from_str(&compression_leaf.tree_authority)?;
let merkle_tree_address = Pubkey::from_str(&compression_leaf.merkle_tree)?;

let asset_id = compression_leaf
.asset_id
.clone()
.ok_or(SolanaAssetIdError::NotFound)?;
let asset = asset_api
.get_asset(&asset_id)
.await
.context("Error getting asset data")?;
.context("fetching asset from DAA")?;
let asset_proof = asset_api
.get_asset_proof(&asset_id)
.await
.context("fetching asset proof from DAA")?;

let root: Vec<u8> = asset_proof.root.into();
let data_hash: Vec<u8> = asset.compression.data_hash.context("no data hash")?.into();
let creator_hash: Vec<u8> = asset
.compression
.creator_hash
.context("no creator hash")?
.into();
let leaf_id = asset.compression.leaf_id;
let proofs = asset_proof
.proof
.into_iter()
.map(|proof| Ok(AccountMeta::new_readonly(proof.try_into()?, false)))
.collect::<Result<Vec<AccountMeta>>>()?;

let mut accounts = vec![
AccountMeta::new(tree_authority_address, false),
AccountMeta::new_readonly(owner, true),
AccountMeta::new_readonly(owner, false),
AccountMeta::new_readonly(recipient, false),
AccountMeta::new(merkle_tree_address, false),
AccountMeta::new_readonly(spl_noop::ID, false),
AccountMeta::new_readonly(spl_account_compression::ID, false),
AccountMeta::new_readonly(system_program::ID, false),
];

accounts.extend(proofs);

let instructions = [Instruction {
program_id: mpl_bubblegum::ID,
accounts: [
AccountMeta::new(self.0.bubblegum_tree_authority, false),
AccountMeta::new_readonly(owner, true),
AccountMeta::new_readonly(owner, false),
AccountMeta::new_readonly(recipient, false),
AccountMeta::new(self.0.bubblegum_merkle_tree, false),
AccountMeta::new_readonly(spl_noop::ID, false),
AccountMeta::new_readonly(spl_account_compression::ID, false),
AccountMeta::new_readonly(system_program::ID, false),
]
.into_iter()
.collect(),
accounts,
data: mpl_bubblegum::instruction::Transfer {
root: todo!("how does DAA work"),
data_hash: todo!("how does DAA work"),
creator_hash: todo!("how does DAA work"),
nonce: todo!("how does DAA work"),
index: todo!("how does DAA work"),
root: root.try_into().map_err(|_| anyhow!("Invalid root hash"))?,
data_hash: data_hash
.try_into()
.map_err(|_| anyhow!("Invalid data hash"))?,
creator_hash: creator_hash
.try_into()
.map_err(|_| anyhow!("Invalid creator hash"))?,
nonce: leaf_id.into(),
index: leaf_id,
}
.data(),
}];
Expand All @@ -703,12 +736,7 @@ impl<'a> TransferBackend for CompressedRef<'a> {
Ok(TransactionResponse {
serialized_message,
signatures_or_signers_public_keys: vec![payer.to_string(), owner.to_string()],
addresses: TransferAssetAddresses {
owner,
recipient,
recipient_associated_token_account: todo!("what"),
owner_associated_token_account: todo!("what"),
},
addresses: TransferCompressedMintV1Addresses { owner, recipient },
})
}
}
Expand Down
12 changes: 12 additions & 0 deletions core/src/compression_leafs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ impl CompressionLeaf {
Entity::find().filter(Column::Id.eq(id)).one(conn).await
}

pub async fn find_by_asset_id(
db: &Connection,
address: String,
) -> Result<Option<Model>, DbErr> {
let conn = db.get();

Entity::find()
.filter(Column::AssetId.eq(address))
.one(conn)
.await
}

pub async fn update(db: &Connection, model: ActiveModel) -> Result<Model, DbErr> {
let conn = db.get();

Expand Down
2 changes: 2 additions & 0 deletions indexer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ bs58 = "0.5.0"
futures = "0.3.24"
hex = "0.4.3"
solana-sdk = "1.14"
mpl-bubblegum = "0.7.0"
solana-program = "1.14"
anchor-lang = "0.26.0"
yellowstone-grpc-client = { git = "https://github.com/rpcpool/yellowstone-grpc", tag = "v1.0.0+solana.1.16.1" }
yellowstone-grpc-proto = { git = "https://github.com/rpcpool/yellowstone-grpc", tag = "v1.0.0+solana.1.16.1" }
dashmap = "5.4.0"
Expand Down
2 changes: 1 addition & 1 deletion indexer/src/connector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ impl GeyserGrpcConnector {
vote: Some(false),
failed: Some(false),
signature: None,
account_include: vec![spl_token::ID.to_string()],
account_include: vec![spl_token::ID.to_string(), mpl_bubblegum::ID.to_string()],
account_exclude: Vec::new(),
account_required: Vec::new(),
});
Expand Down
Loading

0 comments on commit 1d56989

Please sign in to comment.