Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions ark-marketplace-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ actix-web = "4.4.0"
actix-cors = "0.7.0"
actix-rt = "2.9.0"
actix-web-httpauth = "0.8.1"
ark-sqlx.workspace = true
bigdecimal = { version = "0.3", features = ["serde"] }
env_logger = "0.11.3"
num-bigint = "0.4.4"
Expand Down
99 changes: 17 additions & 82 deletions ark-marketplace-api/src/db/db_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -570,25 +570,13 @@ impl DatabaseAccess for PgPool {
event_type_list(&[TokenEventType::Fulfill, TokenEventType::Executed])
);

let from_select_part = format!(
"
CASE
WHEN te.event_type in ({}) THEN token_offer.from_address
ELSE te.from_address
END AS from
",
event_type_list(&[TokenEventType::Fulfill, TokenEventType::Executed])
);
let from_select_part = "
te.from_address AS from
";

let to_select_part = format!(
"
CASE
WHEN te.event_type in ({}) THEN token_offer.to_address
ELSE te.to_address
END AS to
",
event_type_list(&[TokenEventType::Fulfill, TokenEventType::Executed])
);
let to_select_part = "
te.to_address AS to
";

let activity_sql_query = format!(
"
Expand Down Expand Up @@ -1120,74 +1108,35 @@ impl DatabaseAccess for PgPool {
) -> Result<(Vec<TokenActivityData>, bool, i64), Error> {
let offset = (page - 1) * items_per_page;

let token_event_with_previous_cte_part = format!(
"
WITH temporary_event_with_previous AS (
(
SELECT
*,
-- LAG() function is a window function that provides access to a row at a specified physical offset which comes before the current row.
-- Here we want to retrieve the previous event type for given order_hash
LAG(event_type) OVER (PARTITION BY order_hash ORDER BY block_timestamp) AS previous_event_type
FROM token_event WHERE contract_address = $1 AND chain_id = $2 AND token_id = $3
)
),
token_event_with_previous AS (
SELECT
*,
-- Create new event type if needed
CASE
WHEN event_type = '{executed_type}' THEN '{sale_type}'
WHEN event_type = '{cancelled_type}' AND previous_event_type = '{listing_type}' THEN '{listing_cancelled_type}'
WHEN event_type = '{cancelled_type}' AND previous_event_type = '{auction_type}' THEN '{auction_cancelled_type}'
WHEN event_type = '{cancelled_type}' AND previous_event_type = '{offer_type}' THEN '{offer_cancelled_type}'
ELSE event_type
END AS new_event_type
FROM temporary_event_with_previous
WHERE contract_address = $1 AND chain_id = $2 AND token_id = $3
)
",
executed_type = TokenEventType::Executed.to_db_string(),
sale_type = TokenEventType::Sale.to_db_string(),
cancelled_type = TokenEventType::Cancelled.to_db_string(),
listing_type = TokenEventType::Listing.to_db_string(),
auction_type = TokenEventType::Auction.to_db_string(),
offer_type = TokenEventType::Offer.to_db_string(),
listing_cancelled_type = TokenEventType::ListingCancelled.to_db_string(),
auction_cancelled_type = TokenEventType::AuctionCancelled.to_db_string(),
offer_cancelled_type = TokenEventType::OfferCancelled.to_db_string(),
);

let types_filter = match types {
None => String::from(""),
Some(values) => {
format!("AND te.new_event_type IN ({})", event_type_list(values))
format!("AND te.event_type IN ({})", event_type_list(values))
}
};

let common_where_part = format!(
"
FROM token_event_with_previous as te
FROM token_event as te
LEFT JOIN token_offer ON te.order_hash = token_offer.order_hash
LEFT JOIN token ON te.token_id = token.token_id and te.contract_address = token.contract_address and te.chain_id = token.chain_id
LEFT JOIN contract ON te.contract_address = contract.contract_address and te.chain_id = contract.chain_id
WHERE te.contract_address = $1
AND te.chain_id = $2
AND te.token_id = $3
{}
AND te.new_event_type NOT IN ({})
AND te.event_type NOT IN ({})
",
types_filter,
event_type_list(&[TokenEventType::Fulfill])
);

let count_sql_query = format!(
"
{}
SELECT COUNT(*) AS total
{}
",
token_event_with_previous_cte_part, common_where_part,
common_where_part,
);

let total_count: Count = sqlx::query_as(&count_sql_query)
Expand All @@ -1208,25 +1157,13 @@ impl DatabaseAccess for PgPool {
event_type_list(&[TokenEventType::Fulfill, TokenEventType::Executed])
);

let from_select_part = format!(
"
CASE
WHEN te.event_type in ({}) THEN token_offer.from_address
ELSE te.from_address
END AS from
",
event_type_list(&[TokenEventType::Fulfill, TokenEventType::Executed])
);
let from_select_part = "
te.from_address AS from
";

let to_select_part = format!(
"
CASE
WHEN te.event_type in ({}) THEN token_offer.to_address
ELSE te.to_address
END AS to
",
event_type_list(&[TokenEventType::Fulfill, TokenEventType::Executed])
);
let to_select_part = "
te.to_address AS to
";

let result_ordering = format!(
"
Expand All @@ -1240,11 +1177,10 @@ impl DatabaseAccess for PgPool {

let activity_sql_query = format!(
"
{token_event_with_previous_cte}
SELECT
te.block_timestamp AS time_stamp,
te.transaction_hash,
te.new_event_type AS activity_type,
te.event_type AS activity_type,
token.metadata,
contract.contract_name as collection_name,
contract.is_verified as collection_is_verified,
Expand All @@ -1255,7 +1191,6 @@ impl DatabaseAccess for PgPool {
{common_where}
{result_ordering}
",
token_event_with_previous_cte = token_event_with_previous_cte_part,
common_where = common_where_part,
price_select = price_select_part,
from_select = from_select_part,
Expand Down
26 changes: 7 additions & 19 deletions ark-marketplace-api/src/db/portfolio_db_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,25 +81,13 @@ impl DatabaseAccess for PgPool {
event_type_list(&[TokenEventType::Fulfill, TokenEventType::Executed])
);

let from_select_part = format!(
"
CASE
WHEN te.event_type in ({}) THEN token_offer.from_address
ELSE te.from_address
END AS from
",
event_type_list(&[TokenEventType::Fulfill, TokenEventType::Executed])
);
let from_select_part = "
te.from_address AS from
";

let to_select_part = format!(
"
CASE
WHEN te.event_type in ({}) THEN token_offer.to_address
ELSE te.to_address
END AS to
",
event_type_list(&[TokenEventType::Fulfill, TokenEventType::Executed])
);
let to_select_part = "
te.to_address AS to
";

let from_sql_query = format!(
"
Expand Down Expand Up @@ -132,7 +120,7 @@ impl DatabaseAccess for PgPool {
{},
{}
{}
ORDER BY te.block_timestamp {}
ORDER BY te.block_timestamp {}, te.token_event_id
LIMIT {} OFFSET {}
",
price_select_part,
Expand Down
2 changes: 1 addition & 1 deletion ark-marketplace-api/src/handlers/token_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ pub async fn get_tokens<D: DatabaseAccess + Sync>(
let mut redis_con_ref = redis_con.get_ref().lock().await;
let mut token_ids = None;
if let Some(filters_param) = &query_parameters.filters {
let decoded_filters = decode(&filters_param).expect("Failed to decode filters");
let decoded_filters = decode(filters_param).expect("Failed to decode filters");
let filters_map: HashMap<String, serde_json::Value> =
serde_json::from_str(&decoded_filters).expect("Failed to parse JSON");

Expand Down
104 changes: 45 additions & 59 deletions ark-marketplace-api/src/utils/db_utils.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,41 @@
use crate::models::token::TokenEventType;
use ark_sqlx::providers::marketplace::types::TokenEventType as TokenEventTypeDB;

pub fn event_type_list(values: &[TokenEventType]) -> String {
let mut types = values
.iter()
.map(|v| format!("'{}'", v.to_db_string()))
.collect::<Vec<_>>();

if types.contains(&format!("'{}'", TOKEN_EVENT_SALE_STR)) {
types.push(format!("'{}'", TOKEN_EVENT_EXECUTED_STR));
if types.contains(&format!("'{}'", TokenEventType::Sale.to_db_string())) {
types.push(format!("'{}'", TokenEventType::Executed.to_db_string()));
}

types.join(", ")
}

/// DB conversion for TokenEventType
const TOKEN_EVENT_LISTING_STR: &str = "Listing";
const TOKEN_EVENT_COLLECTION_OFFER_STR: &str = "CollectionOffer";
const TOKEN_EVENT_OFFER_STR: &str = "Offer";
const TOKEN_EVENT_AUCTION_STR: &str = "Auction";
const TOKEN_EVENT_FULFILL_STR: &str = "Fulfill";
const TOKEN_EVENT_CANCELLED_STR: &str = "Cancelled";
const TOKEN_EVENT_EXECUTED_STR: &str = "Executed";
const TOKEN_EVENT_SALE_STR: &str = "Sale";
const TOKEN_EVENT_MINT_STR: &str = "Mint";
const TOKEN_EVENT_BURN_STR: &str = "Burn";
const TOKEN_EVENT_TRANSFER_STR: &str = "Transfer";
// Cancel event
const TOKEN_EVENT_LISTING_CANCELLED_STR: &str = "ListingCancelled";
const TOKEN_EVENT_AUCTION_CANCELLED_STR: &str = "AuctionCancelled";
const TOKEN_EVENT_OFFER_CANCELLED_STR: &str = "OfferCancelled";
/// Convert TokenEventType to matching keys in DB
impl TokenEventType {
pub fn to_db_string(&self) -> String {
match self {
Self::Listing => TokenEventTypeDB::Listing.to_db_string(),
Self::CollectionOffer => TokenEventTypeDB::CollectionOffer.to_db_string(),
Self::Offer => TokenEventTypeDB::Offer.to_string(),
Self::Auction => TokenEventTypeDB::Auction.to_string(),
Self::Fulfill => TokenEventTypeDB::Fulfill.to_string(),
Self::Cancelled => TokenEventTypeDB::Cancelled.to_string(),
Self::Executed => TokenEventTypeDB::Executed.to_string(),
Self::Sale => TokenEventTypeDB::Sale.to_string(),
Self::Mint => TokenEventTypeDB::Mint.to_string(),
Self::Burn => TokenEventTypeDB::Burn.to_string(),
Self::Transfer => TokenEventType::Transfer.to_string(),
// Cancel event
Self::ListingCancelled => TokenEventTypeDB::ListingCancelled.to_string(),
Self::AuctionCancelled => TokenEventTypeDB::AuctionCancelled.to_string(),
Self::OfferCancelled => TokenEventTypeDB::OfferCancelled.to_string(),
}
}
}

impl<DB> sqlx::Type<DB> for TokenEventType
where
Expand All @@ -48,47 +55,26 @@ where
fn decode(
value: <DB as sqlx::database::HasValueRef<'r>>::ValueRef,
) -> Result<Self, sqlx::error::BoxDynError> {
let s = <&str as sqlx::Decode<DB>>::decode(value)?;
match s {
TOKEN_EVENT_LISTING_STR => Ok(TokenEventType::Listing),
TOKEN_EVENT_COLLECTION_OFFER_STR => Ok(TokenEventType::CollectionOffer),
TOKEN_EVENT_OFFER_STR => Ok(TokenEventType::Offer),
TOKEN_EVENT_AUCTION_STR => Ok(TokenEventType::Auction),
TOKEN_EVENT_FULFILL_STR => Ok(TokenEventType::Fulfill),
TOKEN_EVENT_CANCELLED_STR => Ok(TokenEventType::Cancelled),
TOKEN_EVENT_EXECUTED_STR => Ok(TokenEventType::Executed),
TOKEN_EVENT_SALE_STR => Ok(TokenEventType::Sale),
TOKEN_EVENT_MINT_STR => Ok(TokenEventType::Mint),
TOKEN_EVENT_BURN_STR => Ok(TokenEventType::Burn),
TOKEN_EVENT_TRANSFER_STR => Ok(TokenEventType::Transfer),
// Cancel event
TOKEN_EVENT_LISTING_CANCELLED_STR => Ok(TokenEventType::ListingCancelled),
TOKEN_EVENT_AUCTION_CANCELLED_STR => Ok(TokenEventType::AuctionCancelled),
TOKEN_EVENT_OFFER_CANCELLED_STR => Ok(TokenEventType::OfferCancelled),
_ => Err("Invalid event type".into()),
}
}
}

/// Convert TokenEventType to matching keys in DB
impl TokenEventType {
pub fn to_db_string(&self) -> String {
match self {
Self::Listing => TOKEN_EVENT_LISTING_STR.to_string(),
Self::CollectionOffer => TOKEN_EVENT_COLLECTION_OFFER_STR.to_string(),
Self::Offer => TOKEN_EVENT_OFFER_STR.to_string(),
Self::Auction => TOKEN_EVENT_AUCTION_STR.to_string(),
Self::Fulfill => TOKEN_EVENT_FULFILL_STR.to_string(),
Self::Cancelled => TOKEN_EVENT_CANCELLED_STR.to_string(),
Self::Executed => TOKEN_EVENT_EXECUTED_STR.to_string(),
Self::Sale => TOKEN_EVENT_SALE_STR.to_string(),
Self::Mint => TOKEN_EVENT_MINT_STR.to_string(),
Self::Burn => TOKEN_EVENT_BURN_STR.to_string(),
Self::Transfer => TOKEN_EVENT_TRANSFER_STR.to_string(),
// Cancel event
Self::ListingCancelled => TOKEN_EVENT_LISTING_CANCELLED_STR.to_string(),
Self::AuctionCancelled => TOKEN_EVENT_AUCTION_CANCELLED_STR.to_string(),
Self::OfferCancelled => TOKEN_EVENT_OFFER_CANCELLED_STR.to_string(),
if let Ok(e) = TokenEventTypeDB::decode(value) {
match e {
TokenEventTypeDB::Listing => Ok(TokenEventType::Listing),
TokenEventTypeDB::Auction => Ok(TokenEventType::Auction),
TokenEventTypeDB::Offer => Ok(TokenEventType::Offer),
TokenEventTypeDB::CollectionOffer => Ok(TokenEventType::CollectionOffer),
TokenEventTypeDB::Fulfill => Ok(TokenEventType::Offer),
TokenEventTypeDB::Executed => Ok(TokenEventType::Executed),
TokenEventTypeDB::Cancelled => Ok(TokenEventType::Cancelled),
TokenEventTypeDB::Sale => Ok(TokenEventType::Sale),
TokenEventTypeDB::Mint => Ok(TokenEventType::Mint),
TokenEventTypeDB::Burn => Ok(TokenEventType::Burn),
TokenEventTypeDB::Transfer => Ok(TokenEventType::Transfer),
TokenEventTypeDB::ListingCancelled => Ok(TokenEventType::ListingCancelled),
TokenEventTypeDB::AuctionCancelled => Ok(TokenEventType::AuctionCancelled),
TokenEventTypeDB::OfferCancelled => Ok(TokenEventType::OfferCancelled),
TokenEventTypeDB::Rollback => Err("Unsupported rollback event".into()), // _ => Ok(TokenEventType::Burn),
}
} else {
Err("Invalid event type".into())
}
}
}
5 changes: 4 additions & 1 deletion ark-sqlx/migrations/marketplace/0_marketplace.sql
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ CREATE TABLE token_event (
order_hash TEXT,
token_id TEXT NOT NULL,
token_id_hex TEXT NOT NULL,
event_type TEXT CHECK (event_type IN ('Listing', 'CollectionOffer', 'Offer', 'Auction', 'Fulfill', 'Cancelled', 'Executed', 'Sale', 'Mint', 'Burn', 'Transfer')),
event_type TEXT CHECK (event_type IN
('Listing', 'CollectionOffer', 'Offer', 'Auction',
'Fulfill', 'Cancelled', 'Executed', 'Sale', 'Mint',
'Burn', 'Transfer', 'ListingCancelled', 'AuctionCancelled', 'OfferCancelled')),
block_timestamp BIGINT NOT NULL,
transaction_hash TEXT NULL,
to_address TEXT, -- NULL if not transfer
Expand Down
1 change: 1 addition & 0 deletions ark-sqlx/src/providers/marketplace/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod order;
pub use order::OrderProvider;
pub mod types;
Loading