diff --git a/das_api/src/api/api_impl.rs b/das_api/src/api/api_impl.rs
index 5b98c1340..e7ffa95bb 100644
--- a/das_api/src/api/api_impl.rs
+++ b/das_api/src/api/api_impl.rs
@@ -4,19 +4,23 @@ use digital_asset_types::{
         sea_orm_active_enums::{
             OwnerType, RoyaltyTargetType, SpecificationAssetClass, SpecificationVersions,
         },
-        SearchAssetsQuery,
+        Cursor, PageOptions, SearchAssetsQuery,
     },
     dapi::{
-        get_asset, get_assets_by_authority, get_assets_by_creator, get_assets_by_group,
-        get_assets_by_owner, get_proof_for_asset, search_assets,
+        get_asset, get_asset_batch, get_asset_proof_batch, get_assets_by_authority,
+        get_assets_by_creator, get_assets_by_group, get_assets_by_owner, get_proof_for_asset,
+        search_assets,
+    },
+    rpc::{
+        filter::{AssetSortBy, SearchConditionType},
+        response::GetGroupingResponse,
     },
-    rpc::{filter::SearchConditionType, response::GetGroupingResponse},
     rpc::{OwnershipModel, RoyaltyModel},
 };
 use open_rpc_derive::document_rpc;
 use sea_orm::{sea_query::ConditionType, ConnectionTrait, DbBackend, Statement};
 
-use crate::validation::validate_opt_pubkey;
+use crate::validation::{validate_opt_pubkey, validate_search_with_name};
 use open_rpc_schema::document::OpenrpcDocument;
 use {
     crate::api::*,
@@ -46,21 +50,37 @@ impl DasApi {
         })
     }
 
+    fn get_cursor(&self, cursor: &Option<String>) -> Result<Cursor, DasApiError> {
+        match cursor {
+            Some(cursor_b64) => {
+                let cursor_vec = bs58::decode(cursor_b64)
+                    .into_vec()
+                    .map_err(|_| DasApiError::CursorValidationError(cursor_b64.clone()))?;
+                let cursor_struct = Cursor {
+                    id: Some(cursor_vec),
+                };
+                Ok(cursor_struct)
+            }
+            None => Ok(Cursor::default()),
+        }
+    }
+
     fn validate_pagination(
         &self,
         limit: &Option<u32>,
         page: &Option<u32>,
         before: &Option<String>,
         after: &Option<String>,
-    ) -> Result<(), DasApiError> {
-        if page.is_none() && before.is_none() && after.is_none() {
-            return Err(DasApiError::PaginationEmptyError);
-        }
+        cursor: &Option<String>,
+        sorting: &Option<&AssetSorting>,
+    ) -> Result<PageOptions, DasApiError> {
+        let mut is_cursor_enabled = true;
+        let mut page_opt = PageOptions::default();
 
         if let Some(limit) = limit {
             // make config item
             if *limit > 1000 {
-                return Err(DasApiError::PaginationError);
+                return Err(DasApiError::PaginationExceededError);
             }
         }
 
@@ -70,20 +90,57 @@ impl DasApi {
             }
 
             // make config item
-            if before.is_some() || after.is_some() {
+            if before.is_some() || after.is_some() || cursor.is_some() {
                 return Err(DasApiError::PaginationError);
             }
+
+            is_cursor_enabled = false;
         }
 
         if let Some(before) = before {
+            if cursor.is_some() {
+                return Err(DasApiError::PaginationError);
+            }
+            if let Some(sort) = &sorting {
+                if sort.sort_by != AssetSortBy::Id {
+                    return Err(DasApiError::PaginationSortingValidationError);
+                }
+            }
             validate_pubkey(before.clone())?;
+            is_cursor_enabled = false;
         }
 
         if let Some(after) = after {
+            if cursor.is_some() {
+                return Err(DasApiError::PaginationError);
+            }
+            if let Some(sort) = &sorting {
+                if sort.sort_by != AssetSortBy::Id {
+                    return Err(DasApiError::PaginationSortingValidationError);
+                }
+            }
             validate_pubkey(after.clone())?;
+            is_cursor_enabled = false;
         }
 
-        Ok(())
+        page_opt.limit = limit.map(|x| x as u64).unwrap_or(1000);
+        if is_cursor_enabled {
+            if let Some(sort) = &sorting {
+                if sort.sort_by != AssetSortBy::Id {
+                    return Err(DasApiError::PaginationSortingValidationError);
+                }
+                page_opt.cursor = Some(self.get_cursor(&cursor)?);
+            }
+        } else {
+            page_opt.page = page.map(|x| x as u64);
+            page_opt.before = before
+                .clone()
+                .map(|x| bs58::decode(x).into_vec().unwrap_or_default());
+            page_opt.after = after
+                .clone()
+                .map(|x| bs58::decode(x).into_vec().unwrap_or_default());
+        }
+        Ok(page_opt)
     }
 }
 
@@ -121,14 +178,76 @@ impl ApiContract for DasApi {
             .map_err(Into::into)
     }
 
+    async fn get_asset_proof_batch(
+        self: &DasApi,
+        payload: GetAssetProofBatch,
+    ) -> Result<HashMap<String, Option<AssetProof>>, DasApiError> {
+        let GetAssetProofBatch { ids } = payload;
+
+        let batch_size = ids.len();
+        if batch_size > 1000 {
+            return Err(DasApiError::BatchSizeExceededError);
+        }
+
+        let id_bytes = ids
+            .iter()
+            .map(|id| validate_pubkey(id.clone()).map(|id| id.to_bytes().to_vec()))
+            .collect::<Result<Vec<Vec<u8>>, _>>()?;
+
+        let proofs = get_asset_proof_batch(&self.db_connection, id_bytes).await?;
+
+        let result: HashMap<String, Option<AssetProof>> = ids
+            .iter()
+            .map(|id| (id.clone(), proofs.get(id).cloned()))
+            .collect();
+        Ok(result)
+    }
+
     async fn get_asset(self: &DasApi, payload: GetAsset) -> Result<Asset, DasApiError> {
-        let id = validate_pubkey(payload.id.clone())?;
-        let id_bytes = id.to_bytes().to_vec();
-        get_asset(&self.db_connection, id_bytes)
+        let GetAsset {
+            id,
+            display_options,
+        } = payload;
+        let id_bytes = validate_pubkey(id.clone())?.to_bytes().to_vec();
+        let display_options = display_options.unwrap_or_default();
+        get_asset(&self.db_connection, id_bytes, &display_options.into())
             .await
             .map_err(Into::into)
     }
 
+    async fn get_asset_batch(
+        self: &DasApi,
+        payload: GetAssetBatch,
+    ) -> Result<Vec<Option<Asset>>, DasApiError> {
+        let GetAssetBatch {
+            ids,
+            display_options,
+        } = payload;
+
+        let batch_size = ids.len();
+        if batch_size > 1000 {
+            return Err(DasApiError::BatchSizeExceededError);
+        }
+
+        let id_bytes = ids
+            .iter()
+            .map(|id| validate_pubkey(id.clone()).map(|id| id.to_bytes().to_vec()))
+            .collect::<Result<Vec<Vec<u8>>, _>>()?;
+
+        let display_options = display_options.unwrap_or_default();
+
+        let assets = get_asset_batch(
+            &self.db_connection,
+            id_bytes,
+            batch_size as u64,
+            &display_options.into(),
+        )
+        .await?;
+
+        let result: Vec<Option<Asset>> = ids.iter().map(|id| assets.get(id).cloned()).collect();
+        Ok(result)
+    }
+
     async fn get_assets_by_owner(
         self: &DasApi,
         payload: GetAssetsByOwner,
@@ -140,21 +259,23 @@ impl ApiContract for DasApi {
             page,
             before,
             after,
+            display_options,
+            cursor,
         } = payload;
         let before: Option<String> = before.filter(|before| !before.is_empty());
         let after: Option<String> = after.filter(|after| !after.is_empty());
         let owner_address = validate_pubkey(owner_address.clone())?;
         let owner_address_bytes = owner_address.to_bytes().to_vec();
         let sort_by = sort_by.unwrap_or_default();
-        self.validate_pagination(&limit, &page, &before, &after)?;
+        let display_options = display_options.unwrap_or_default();
+        let page_options =
+            self.validate_pagination(&limit, &page, &before, &after, &cursor, &Some(&sort_by))?;
         get_assets_by_owner(
             &self.db_connection,
             owner_address_bytes,
             sort_by,
-            limit.map(|x| x as u64).unwrap_or(1000),
-            page.map(|x| x as u64),
-            before.map(|x| bs58::decode(x).into_vec().unwrap_or_default()),
-            after.map(|x| bs58::decode(x).into_vec().unwrap_or_default()),
+            &page_options,
+            &display_options,
         )
         .await
         .map_err(Into::into)
@@ -172,20 +293,22 @@ impl ApiContract for DasApi {
             page,
             before,
             after,
+            display_options,
+            cursor,
         } = payload;
         let before: Option<String> = before.filter(|before| !before.is_empty());
         let after: Option<String> = after.filter(|after| !after.is_empty());
         let sort_by = sort_by.unwrap_or_default();
-        self.validate_pagination(&limit, &page, &before, &after)?;
+        let display_options = display_options.unwrap_or_default();
+        let page_options =
+            self.validate_pagination(&limit, &page, &before, &after, &cursor, &Some(&sort_by))?;
         get_assets_by_group(
             &self.db_connection,
             group_key,
             group_value,
             sort_by,
-            limit.map(|x| x as u64).unwrap_or(1000),
-            page.map(|x| x as u64),
-            before.map(|x| bs58::decode(x).into_vec().unwrap_or_default()),
-            after.map(|x| bs58::decode(x).into_vec().unwrap_or_default()),
+            &page_options,
+            &display_options,
         )
         .await
         .map_err(Into::into)
@@ -203,22 +326,24 @@ impl ApiContract for DasApi {
             page,
             before,
             after,
+            display_options,
+            cursor,
         } = payload;
         let creator_address = validate_pubkey(creator_address.clone())?;
         let creator_address_bytes = creator_address.to_bytes().to_vec();
 
-        self.validate_pagination(&limit, &page, &before, &after)?;
         let sort_by = sort_by.unwrap_or_default();
+        let page_options =
+            self.validate_pagination(&limit, &page, &before, &after, &cursor, &Some(&sort_by))?;
         let only_verified = only_verified.unwrap_or_default();
+        let display_options = display_options.unwrap_or_default();
         get_assets_by_creator(
             &self.db_connection,
             creator_address_bytes,
             only_verified,
             sort_by,
-            limit.map(|x| x as u64).unwrap_or(1000),
-            page.map(|x| x as u64),
-            before.map(|x| bs58::decode(x).into_vec().unwrap_or_default()),
-            after.map(|x| bs58::decode(x).into_vec().unwrap_or_default()),
+            &page_options,
+            &display_options,
         )
         .await
         .map_err(Into::into)
@@ -235,20 +360,22 @@ impl ApiContract for DasApi {
             page,
             before,
             after,
+            display_options,
+            cursor,
         } = payload;
         let sort_by = sort_by.unwrap_or_default();
         let authority_address = validate_pubkey(authority_address.clone())?;
         let authority_address_bytes = authority_address.to_bytes().to_vec();
+        let display_options = display_options.unwrap_or_default();
 
-        self.validate_pagination(&limit, &page, &before, &after)?;
+        let page_options =
+            self.validate_pagination(&limit, &page, &before, &after, &cursor, &Some(&sort_by))?;
         get_assets_by_authority(
             &self.db_connection,
             authority_address_bytes,
             sort_by,
-            limit.map(|x| x as u64).unwrap_or(1000),
-            page.map(|x| x as u64),
-            before.map(|x| bs58::decode(x).into_vec().unwrap_or_default()),
-            after.map(|x| bs58::decode(x).into_vec().unwrap_or_default()),
+            &page_options,
+            &display_options,
         )
         .await
         .map_err(Into::into)
@@ -282,9 +409,13 @@ impl ApiContract for DasApi {
             before,
             after,
             json_uri,
+            display_options,
+            cursor,
+            name,
         } = payload;
+
+
         // Deserialize search assets query
-        self.validate_pagination(&limit, &page, &before, &after)?;
         let spec: Option<(SpecificationVersions, SpecificationAssetClass)> =
             interface.map(|x| x.into());
         let specification_version = spec.clone().map(|x| x.0);
@@ -294,6 +425,7 @@ impl ApiContract for DasApi {
             SearchConditionType::All => ConditionType::All,
         });
         let owner_address = validate_opt_pubkey(&owner_address)?;
+        let name = validate_search_with_name(&name, &owner_address)?;
         let creator_address = validate_opt_pubkey(&creator_address)?;
         let delegate = validate_opt_pubkey(&delegate)?;
 
@@ -332,17 +464,19 @@ impl ApiContract for DasApi {
             royalty_amount,
             burnt,
             json_uri,
+            name,
         };
+        let display_options = display_options.unwrap_or_default();
         let sort_by = sort_by.unwrap_or_default();
+        let page_options =
+            self.validate_pagination(&limit, &page, &before, &after, &cursor, &Some(&sort_by))?;
         // Execute query
         search_assets(
             &self.db_connection,
             saq,
             sort_by,
-            limit.map(|x| x as u64).unwrap_or(1000),
-            page.map(|x| x as u64),
-            before.map(|x| bs58::decode(x).into_vec().unwrap_or_default()),
-            after.map(|x| bs58::decode(x).into_vec().unwrap_or_default()),
+            &page_options,
+            &display_options,
         )
         .await
         .map_err(Into::into)
diff --git a/das_api/src/api/mod.rs b/das_api/src/api/mod.rs
index 788c2adf2..3c8ad5f82 100644
--- a/das_api/src/api/mod.rs
+++ b/das_api/src/api/mod.rs
@@ -1,5 +1,6 @@
 use crate::DasApiError;
 use async_trait::async_trait;
+use digital_asset_types::rpc::display_options::DisplayOptions;
 use digital_asset_types::rpc::filter::SearchConditionType;
 use digital_asset_types::rpc::response::AssetList;
 use digital_asset_types::rpc::{filter::AssetSorting, response::GetGroupingResponse};
@@ -7,6 +8,7 @@ use digital_asset_types::rpc::{Asset, AssetProof, Interface, OwnershipModel, Roy
 use open_rpc_derive::{document_rpc, rpc};
 use open_rpc_schema::schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
 
 mod api_impl;
 pub use api_impl::*;
@@ -21,6 +23,10 @@ pub struct GetAssetsByGroup {
     pub page: Option<u32>,
     pub before: Option<String>,
     pub after: Option<String>,
+    #[serde(default)]
+    pub display_options: Option<DisplayOptions>,
+    #[serde(default)]
+    pub cursor: Option<String>,
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
@@ -32,12 +38,26 @@ pub struct GetAssetsByOwner {
     pub page: Option<u32>,
     pub before: Option<String>,
     pub after: Option<String>,
+    #[serde(default)]
+    pub display_options: Option<DisplayOptions>,
+    #[serde(default)]
+    pub cursor: Option<String>,
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
 #[serde(deny_unknown_fields, rename_all = "camelCase")]
 pub struct GetAsset {
     pub id: String,
+    #[serde(default)]
+    pub display_options: Option<DisplayOptions>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
+#[serde(deny_unknown_fields, rename_all = "camelCase")]
+pub struct GetAssetBatch {
+    pub ids: Vec<String>,
+    #[serde(default)]
+    pub display_options: Option<DisplayOptions>,
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
@@ -46,6 +66,12 @@ pub struct GetAssetProof {
     pub id: String,
 }
 
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
+#[serde(deny_unknown_fields, rename_all = "camelCase")]
+pub struct GetAssetProofBatch {
+    pub ids: Vec<String>,
+}
+
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
 #[serde(deny_unknown_fields, rename_all = "camelCase")]
 pub struct GetAssetsByCreator {
@@ -56,6 +82,10 @@ pub struct GetAssetsByCreator {
     pub page: Option<u32>,
     pub before: Option<String>,
     pub after: Option<String>,
+    #[serde(default)]
+    pub display_options: Option<DisplayOptions>,
+    #[serde(default)]
+    pub cursor: Option<String>,
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
@@ -87,6 +117,12 @@ pub struct SearchAssets {
     pub after: Option<String>,
     #[serde(default)]
     pub json_uri: Option<String>,
+    #[serde(default)]
+    pub display_options: Option<DisplayOptions>,
+    #[serde(default)]
+    pub cursor: Option<String>,
+    #[serde(default)]
+    pub name: Option<String>,
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
@@ -98,6 +134,10 @@ pub struct GetAssetsByAuthority {
     pub page: Option<u32>,
     pub before: Option<String>,
     pub after: Option<String>,
+    #[serde(default)]
+    pub display_options: Option<DisplayOptions>,
+    #[serde(default)]
+    pub cursor: Option<String>,
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
@@ -117,12 +157,30 @@ pub trait ApiContract: Send + Sync + 'static {
         summary = "Get a merkle proof for a compressed asset by its ID"
     )]
     async fn get_asset_proof(&self, payload: GetAssetProof) -> Result<AssetProof, DasApiError>;
+    #[rpc(
+        name = "getAssetProofBatch",
+        params = "named",
+        summary = "Get merkle proofs for compressed assets by their IDs"
+    )]
+    async fn get_asset_proof_batch(
+        &self,
+        payload: GetAssetProofBatch,
+    ) -> Result<HashMap<String, Option<AssetProof>>, DasApiError>;
     #[rpc(
         name = "getAsset",
         params = "named",
         summary = "Get an asset by its ID"
     )]
     async fn get_asset(&self, payload: GetAsset) -> Result<Asset, DasApiError>;
+    #[rpc(
+        name = "getAssetBatch",
+        params = "named",
+        summary = "Get assets by their IDs"
+    )]
+    async fn get_asset_batch(
+        &self,
+        payload: GetAssetBatch,
+    ) -> Result<Vec<Option<Asset>>, DasApiError>;
     #[rpc(
         name = "getAssetsByOwner",
         params = "named",
diff --git a/das_api/src/builder.rs b/das_api/src/builder.rs
index 342d2fba3..e5ff9bb48 100644
--- a/das_api/src/builder.rs
+++ b/das_api/src/builder.rs
@@ -22,12 +22,33 @@ impl RpcApiBuilder {
         })?;
         module.register_alias("getAssetProof", "get_asset_proof")?;
 
+        module.register_async_method(
+            "get_asset_proof_batch",
+            |rpc_params, rpc_context| async move {
+                let payload = rpc_params.parse::<GetAssetProofBatch>()?;
+                rpc_context
+                    .get_asset_proof_batch(payload)
+                    .await
+                    .map_err(Into::into)
+            },
+        )?;
+        module.register_alias("getAssetProofBatch", "get_asset_proof_batch")?;
+
         module.register_async_method("get_asset", |rpc_params, rpc_context| async move {
             let payload = rpc_params.parse::<GetAsset>()?;
             rpc_context.get_asset(payload).await.map_err(Into::into)
         })?;
         module.register_alias("getAsset", "get_asset")?;
 
+        module.register_async_method("get_asset_batch", |rpc_params, rpc_context| async move {
+            let payload = rpc_params.parse::<GetAssetBatch>()?;
+            rpc_context
+                .get_asset_batch(payload)
+                .await
+                .map_err(Into::into)
+        })?;
+        module.register_alias("getAssetBatch", "get_asset_batch")?;
+
         module.register_async_method(
             "get_assets_by_owner",
             |rpc_params, rpc_context| async move {
diff --git a/das_api/src/error.rs b/das_api/src/error.rs
index 3640da765..4b009ceac 100644
--- a/das_api/src/error.rs
+++ b/das_api/src/error.rs
@@ -22,6 +22,14 @@ pub enum DasApiError {
     PaginationEmptyError,
     #[error("Deserialization error: {0}")]
     DeserializationError(#[from] serde_json::Error),
+    #[error("Batch Size Error. Batch size should not be greater than 1000.")]
+    BatchSizeExceededError,
+    #[error("Pagination Error. Limit should not be greater than 1000.")]
+    PaginationExceededError,
+    #[error("Cursor Validation Err: {0} is invalid")]
+    CursorValidationError(String),
+    #[error("Pagination Sorting Error. Only sorting based on id is support for this pagination")]
+    PaginationSortingValidationError,
 }
 
 impl Into<RpcError> for DasApiError {
diff --git a/das_api/src/validation.rs b/das_api/src/validation.rs
index b3377a7b4..394463208 100644
--- a/das_api/src/validation.rs
+++ b/das_api/src/validation.rs
@@ -6,6 +6,23 @@ pub fn validate_pubkey(str_pubkey: String) -> Result<Pubkey, DasApiError> {
     Pubkey::from_str(&str_pubkey).map_err(|_| DasApiError::PubkeyValidationError(str_pubkey))
 }
 
+pub fn validate_search_with_name(
+    name: &Option<String>,
+    owner: &Option<Vec<u8>>,
+) -> Result<Option<Vec<u8>>, DasApiError> {
+    let opt_name = if let Some(n) = name {
+        if owner.is_none() {
+            return Err(DasApiError::ValidationError(
+                "Owner address must be provided in order to search assets by name".to_owned(),
+            ));
+        }
+        Some(n.clone().into_bytes())
+    } else {
+        None
+    };
+    Ok(opt_name)
+}
+
 pub fn validate_opt_pubkey(pubkey: &Option<String>) -> Result<Option<Vec<u8>>, DasApiError> {
     let opt_bytes = if let Some(pubkey) = pubkey {
         let pubkey = Pubkey::from_str(pubkey)
diff --git a/digital_asset_types/src/dao/mod.rs b/digital_asset_types/src/dao/mod.rs
index e30c92755..2622c3659 100644
--- a/digital_asset_types/src/dao/mod.rs
+++ b/digital_asset_types/src/dao/mod.rs
@@ -2,23 +2,37 @@
 mod full_asset;
 mod generated;
 pub mod scopes;
-pub use full_asset::*;
-pub use generated::*;
-
 use self::sea_orm_active_enums::{
     OwnerType, RoyaltyTargetType, SpecificationAssetClass, SpecificationVersions,
 };
+pub use full_asset::*;
+pub use generated::*;
 use sea_orm::{
     entity::*,
     sea_query::Expr,
-    sea_query::{ConditionType, IntoCondition},
+    sea_query::{ConditionType, IntoCondition, SimpleExpr},
     Condition, DbErr, RelationDef,
 };
+use serde::{Deserialize, Serialize};
 
 pub struct GroupingSize {
     pub size: u64,
 }
 
+#[derive(Serialize, Deserialize, Debug, Default)]
+pub struct PageOptions {
+    pub limit: u64,
+    pub page: Option<u64>,
+    pub before: Option<Vec<u8>>,
+    pub after: Option<Vec<u8>>,
+    pub cursor: Option<Cursor>,
+}
+
+#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
+pub struct Cursor {
+    pub id: Option<Vec<u8>>,
+}
+
 pub enum Pagination {
     Keyset {
         before: Option<Vec<u8>>,
@@ -27,6 +41,7 @@ pub enum Pagination {
     Page {
         page: u64,
     },
+    Cursor(Cursor),
 }
 
 #[derive(Debug, Clone, PartialEq)]
@@ -54,6 +69,7 @@ pub struct SearchAssetsQuery {
     pub royalty_amount: Option<u32>,
     pub burnt: Option<bool>,
     pub json_uri: Option<String>,
+    pub name: Option<Vec<u8>>,
 }
 
 impl SearchAssetsQuery {
@@ -115,6 +131,9 @@ impl SearchAssetsQuery {
         if self.json_uri.is_some() {
             num_conditions += 1;
         }
+        if self.name.is_some() {
+            num_conditions += 1;
+        }
 
         num_conditions
     }
@@ -245,6 +264,28 @@ impl SearchAssetsQuery {
             joins.push(rel);
         }
 
+        if let Some(n) = self.name.to_owned() {
+            let name_as_str = std::str::from_utf8(&n).map_err(|_| {
+                DbErr::Custom(
+                    "Could not convert raw name bytes into string for comparison".to_owned(),
+                )
+            })?;
+
+            let name_expr =
+                SimpleExpr::Custom(format!("chain_data->>'name' LIKE '%{}%'", name_as_str).into());
+
+            conditions = conditions.add(name_expr);
+            let rel = asset_data::Relation::Asset
+                .def()
+                .rev()
+                .on_condition(|left, right| {
+                    Expr::tbl(right, asset_data::Column::Id)
+                        .eq(Expr::tbl(left, asset::Column::AssetData))
+                        .into_condition()
+                });
+            joins.push(rel);
+        }
+
         Ok((
             match self.negate {
                 None | Some(false) => conditions,
diff --git a/digital_asset_types/src/dao/scopes/asset.rs b/digital_asset_types/src/dao/scopes/asset.rs
index cd9db02ba..8be2ff072 100644
--- a/digital_asset_types/src/dao/scopes/asset.rs
+++ b/digital_asset_types/src/dao/scopes/asset.rs
@@ -1,29 +1,32 @@
-use crate::{
-    dao::{
-        asset::{self, Entity},
-        asset_authority, asset_creators, asset_data, asset_grouping, FullAsset,
-        GroupingSize, Pagination,
-    },
-    dapi::common::safe_select,
-    rpc::{response::AssetList},
+use crate::dao::{
+    asset::{self},
+    asset_authority, asset_creators, asset_data, asset_grouping, Cursor, FullAsset, GroupingSize,
+    Pagination,
 };
 
 use indexmap::IndexMap;
 use sea_orm::{entity::*, query::*, ConnectionTrait, DbErr, Order};
-use std::collections::{HashMap, HashSet};
+use std::collections::HashMap;
 
-pub fn paginate<'db, T>(pagination: &Pagination, limit: u64, stmt: T) -> T
+pub fn paginate<'db, T, C>(
+    pagination: &Pagination,
+    limit: u64,
+    stmt: T,
+    sort_direction: Order,
+    column: C,
+) -> T
 where
     T: QueryFilter + QuerySelect,
+    C: ColumnTrait,
 {
     let mut stmt = stmt;
     match pagination {
         Pagination::Keyset { before, after } => {
             if let Some(b) = before {
-                stmt = stmt.filter(asset::Column::Id.lt(b.clone()));
+                stmt = stmt.filter(column.lt(b.clone()));
             }
             if let Some(a) = after {
-                stmt = stmt.filter(asset::Column::Id.gt(a.clone()));
+                stmt = stmt.filter(column.gt(a.clone()));
             }
         }
         Pagination::Page { page } => {
@@ -31,6 +34,15 @@ where
                 stmt = stmt.offset((page - 1) * limit)
             }
         }
+        Pagination::Cursor(cursor) => {
+            if *cursor != Cursor::default() {
+                if sort_direction == sea_orm::Order::Asc {
+                    stmt = stmt.filter(column.gt(cursor.id.clone()));
+                } else {
+                    stmt = stmt.filter(column.lt(cursor.id.clone()));
+                }
+            }
+        }
     }
     stmt.limit(limit)
 }
@@ -43,6 +55,7 @@ pub async fn get_by_creator(
     sort_direction: Order,
     pagination: &Pagination,
     limit: u64,
+    show_unverified_collections: bool,
 ) -> Result<Vec<FullAsset>, DbErr> {
     let mut condition = Condition::all()
         .add(asset_creators::Column::Creator.eq(creator))
@@ -58,6 +71,7 @@ pub async fn get_by_creator(
         sort_direction,
         pagination,
         limit,
+        show_unverified_collections,
     )
     .await
 }
@@ -91,15 +105,20 @@ pub async fn get_by_grouping(
     sort_direction: Order,
     pagination: &Pagination,
     limit: u64,
+    show_unverified_collections: bool,
 ) -> Result<Vec<FullAsset>, DbErr> {
-    let condition = asset_grouping::Column::GroupKey
+    let mut condition = asset_grouping::Column::GroupKey
         .eq(group_key)
-        .and(asset_grouping::Column::GroupValue.eq(group_value))
-        .and(
+        .and(asset_grouping::Column::GroupValue.eq(group_value));
+
+    if !show_unverified_collections {
+        condition = condition.and(
             asset_grouping::Column::Verified
                 .eq(true)
                 .or(asset_grouping::Column::Verified.is_null()),
         );
+    }
+
     get_by_related_condition(
         conn,
         Condition::all()
@@ -110,6 +129,7 @@ pub async fn get_by_grouping(
         sort_direction,
         pagination,
         limit,
+        show_unverified_collections,
     )
     .await
 }
@@ -121,6 +141,7 @@ pub async fn get_assets_by_owner(
     sort_direction: Order,
     pagination: &Pagination,
     limit: u64,
+    show_unverified_collections: bool,
 ) -> Result<Vec<FullAsset>, DbErr> {
     let cond = Condition::all()
         .add(asset::Column::Owner.eq(owner))
@@ -133,6 +154,30 @@ pub async fn get_assets_by_owner(
         sort_direction,
         pagination,
         limit,
+        show_unverified_collections,
+    )
+    .await
+}
+
+pub async fn get_asset_batch(
+    conn: &impl ConnectionTrait,
+    asset_ids: Vec<Vec<u8>>,
+    pagination: &Pagination,
+    limit: u64,
+) -> Result<Vec<FullAsset>, DbErr> {
+    let cond = Condition::all()
+        .add(asset::Column::Id.is_in(asset_ids))
+        .add(asset::Column::Supply.gt(0));
+    get_assets_by_condition(
+        conn,
+        cond,
+        vec![],
+        // Default values provided. The args below are not used for batch requests
+        None,
+        Order::Asc,
+        pagination,
+        limit,
+        false,
     )
     .await
 }
@@ -144,6 +189,7 @@ pub async fn get_by_authority(
     sort_direction: Order,
     pagination: &Pagination,
     limit: u64,
+    show_unverified_collections: bool,
 ) -> Result<Vec<FullAsset>, DbErr> {
     let cond = Condition::all()
         .add(asset_authority::Column::Authority.eq(authority))
@@ -156,6 +202,7 @@ pub async fn get_by_authority(
         sort_direction,
         pagination,
         limit,
+        show_unverified_collections,
     )
     .await
 }
@@ -168,6 +215,7 @@ async fn get_by_related_condition<E>(
     sort_direction: Order,
     pagination: &Pagination,
     limit: u64,
+    show_unverified_collections: bool,
 ) -> Result<Vec<FullAsset>, DbErr>
 where
     E: RelationTrait,
@@ -179,16 +227,19 @@ where
     if let Some(col) = sort_by {
         stmt = stmt
             .order_by(col, sort_direction.clone())
-            .order_by(asset::Column::Id, sort_direction);
+            .order_by(asset::Column::Id, sort_direction.clone());
     }
 
-    let assets = paginate(pagination, limit, stmt).all(conn).await?;
-    get_related_for_assets(conn, assets).await
+    let assets = paginate(pagination, limit, stmt, sort_direction, asset::Column::Id)
+        .all(conn)
+        .await?;
+    get_related_for_assets(conn, assets, show_unverified_collections).await
 }
 
 pub async fn get_related_for_assets(
     conn: &impl ConnectionTrait,
     assets: Vec<asset::Model>,
+    show_unverified_collections: bool,
 ) -> Result<Vec<FullAsset>, DbErr> {
     let asset_ids = assets.iter().map(|a| a.id.clone()).collect::<Vec<_>>();
 
@@ -244,16 +295,20 @@ pub async fn get_related_for_assets(
         }
     }
 
+    let cond = if show_unverified_collections {
+        Condition::all()
+    } else {
+        Condition::any()
+            .add(asset_grouping::Column::Verified.eq(true))
+            // Older versions of the indexer did not have the verified flag. A group would be present if and only if it was verified.
+            // Therefore if verified is null, we can assume that the group is verified.
+            .add(asset_grouping::Column::Verified.is_null())
+    };
+
     let grouping = asset_grouping::Entity::find()
         .filter(asset_grouping::Column::AssetId.is_in(ids.clone()))
         .filter(asset_grouping::Column::GroupValue.is_not_null())
-        .filter(
-            Condition::any()
-                .add(asset_grouping::Column::Verified.eq(true))
-                // Older versions of the indexer did not have the verified flag. A group would be present if and only if it was verified.
-                // Therefore if verified is null, we can assume that the group is verified.
-                .add(asset_grouping::Column::Verified.is_null()),
-        )
+        .filter(cond)
         .order_by_asc(asset_grouping::Column::AssetId)
         .all(conn)
         .await?;
@@ -274,6 +329,7 @@ pub async fn get_assets_by_condition(
     sort_direction: Order,
     pagination: &Pagination,
     limit: u64,
+    show_unverified_collections: bool,
 ) -> Result<Vec<FullAsset>, DbErr> {
     let mut stmt = asset::Entity::find();
     for def in joins {
@@ -283,11 +339,13 @@ pub async fn get_assets_by_condition(
     if let Some(col) = sort_by {
         stmt = stmt
             .order_by(col, sort_direction.clone())
-            .order_by(asset::Column::Id, sort_direction);
+            .order_by(asset::Column::Id, sort_direction.clone());
     }
 
-    let assets = paginate(pagination, limit, stmt).all(conn).await?;
-    let full_assets = get_related_for_assets(conn, assets).await?;
+    let assets = paginate(pagination, limit, stmt, sort_direction, asset::Column::Id)
+        .all(conn)
+        .await?;
+    let full_assets = get_related_for_assets(conn, assets, show_unverified_collections).await?;
     Ok(full_assets)
 }
 
diff --git a/digital_asset_types/src/dapi/assets_by_authority.rs b/digital_asset_types/src/dapi/assets_by_authority.rs
index 525881146..774b23b3e 100644
--- a/digital_asset_types/src/dapi/assets_by_authority.rs
+++ b/digital_asset_types/src/dapi/assets_by_authority.rs
@@ -1,4 +1,6 @@
 use crate::dao::scopes;
+use crate::dao::PageOptions;
+use crate::rpc::display_options::DisplayOptions;
 use crate::rpc::filter::AssetSorting;
 use crate::rpc::response::AssetList;
 use sea_orm::DatabaseConnection;
@@ -10,12 +12,10 @@ pub async fn get_assets_by_authority(
     db: &DatabaseConnection,
     authority: Vec<u8>,
     sorting: AssetSorting,
-    limit: u64,
-    page: Option<u64>,
-    before: Option<Vec<u8>>,
-    after: Option<Vec<u8>>,
+    page_options: &PageOptions,
+    display_options: &DisplayOptions,
 ) -> Result<AssetList, DbErr> {
-    let pagination = create_pagination(before, after, page)?;
+    let pagination = create_pagination(&page_options)?;
     let (sort_direction, sort_column) = create_sorting(sorting);
     let assets = scopes::asset::get_by_authority(
         db,
@@ -23,8 +23,14 @@ pub async fn get_assets_by_authority(
         sort_column,
         sort_direction,
         &pagination,
-        limit,
+        page_options.limit,
+        display_options.show_unverified_collections,
     )
     .await?;
-    Ok(build_asset_response(assets, limit, &pagination))
+    Ok(build_asset_response(
+        assets,
+        page_options.limit,
+        &pagination,
+        display_options,
+    ))
 }
diff --git a/digital_asset_types/src/dapi/assets_by_creator.rs b/digital_asset_types/src/dapi/assets_by_creator.rs
index e69a12f92..6df93cf26 100644
--- a/digital_asset_types/src/dapi/assets_by_creator.rs
+++ b/digital_asset_types/src/dapi/assets_by_creator.rs
@@ -1,4 +1,6 @@
 use crate::dao::scopes;
+use crate::dao::PageOptions;
+use crate::rpc::display_options::DisplayOptions;
 use crate::rpc::filter::AssetSorting;
 use crate::rpc::response::AssetList;
 use sea_orm::DatabaseConnection;
@@ -11,12 +13,10 @@ pub async fn get_assets_by_creator(
     creator: Vec<u8>,
     only_verified: bool,
     sorting: AssetSorting,
-    limit: u64,
-    page: Option<u64>,
-    before: Option<Vec<u8>>,
-    after: Option<Vec<u8>>,
+    page_options: &PageOptions,
+    display_options: &DisplayOptions,
 ) -> Result<AssetList, DbErr> {
-    let pagination = create_pagination(before, after, page)?;
+    let pagination = create_pagination(&page_options)?;
     let (sort_direction, sort_column) = create_sorting(sorting);
     let assets = scopes::asset::get_by_creator(
         db,
@@ -25,8 +25,14 @@ pub async fn get_assets_by_creator(
         sort_column,
         sort_direction,
         &pagination,
-        limit,
+        page_options.limit,
+        display_options.show_unverified_collections,
     )
     .await?;
-    Ok(build_asset_response(assets, limit, &pagination))
+    Ok(build_asset_response(
+        assets,
+        page_options.limit,
+        &pagination,
+        display_options,
+    ))
 }
diff --git a/digital_asset_types/src/dapi/assets_by_group.rs b/digital_asset_types/src/dapi/assets_by_group.rs
index 1e655b6ab..4a9472799 100644
--- a/digital_asset_types/src/dapi/assets_by_group.rs
+++ b/digital_asset_types/src/dapi/assets_by_group.rs
@@ -1,4 +1,6 @@
 use crate::dao::scopes;
+use crate::dao::PageOptions;
+use crate::rpc::display_options::DisplayOptions;
 use crate::rpc::filter::AssetSorting;
 use crate::rpc::response::AssetList;
 use sea_orm::DatabaseConnection;
@@ -10,12 +12,10 @@ pub async fn get_assets_by_group(
     group_key: String,
     group_value: String,
     sorting: AssetSorting,
-    limit: u64,
-    page: Option<u64>,
-    before: Option<Vec<u8>>,
-    after: Option<Vec<u8>>,
+    page_options: &PageOptions,
+    display_options: &DisplayOptions,
 ) -> Result<AssetList, DbErr> {
-    let pagination = create_pagination(before, after, page)?;
+    let pagination = create_pagination(&page_options)?;
     let (sort_direction, sort_column) = create_sorting(sorting);
     let assets = scopes::asset::get_by_grouping(
         db,
@@ -24,8 +24,14 @@ pub async fn get_assets_by_group(
         sort_column,
         sort_direction,
         &pagination,
-        limit,
+        page_options.limit,
+        display_options.show_unverified_collections,
     )
     .await?;
-    Ok(build_asset_response(assets, limit, &pagination))
+    Ok(build_asset_response(
+        assets,
+        page_options.limit,
+        &pagination,
+        display_options,
+    ))
 }
diff --git a/digital_asset_types/src/dapi/assets_by_owner.rs b/digital_asset_types/src/dapi/assets_by_owner.rs
index c2d9cce0e..9e7b3bc89 100644
--- a/digital_asset_types/src/dapi/assets_by_owner.rs
+++ b/digital_asset_types/src/dapi/assets_by_owner.rs
@@ -1,4 +1,6 @@
 use crate::dao::scopes;
+use crate::dao::PageOptions;
+use crate::rpc::display_options::DisplayOptions;
 use crate::rpc::filter::AssetSorting;
 use crate::rpc::response::AssetList;
 use sea_orm::DatabaseConnection;
@@ -10,12 +12,10 @@ pub async fn get_assets_by_owner(
     db: &DatabaseConnection,
     owner_address: Vec<u8>,
     sort_by: AssetSorting,
-    limit: u64,
-    page: Option<u64>,
-    before: Option<Vec<u8>>,
-    after: Option<Vec<u8>>,
+    page_options: &PageOptions,
+    display_options: &DisplayOptions,
 ) -> Result<AssetList, DbErr> {
-    let pagination = create_pagination(before, after, page)?;
+    let pagination = create_pagination(&page_options)?;
     let (sort_direction, sort_column) = create_sorting(sort_by);
     let assets = scopes::asset::get_assets_by_owner(
         db,
@@ -23,8 +23,14 @@ pub async fn get_assets_by_owner(
         sort_column,
         sort_direction,
         &pagination,
-        limit,
+        page_options.limit,
+        display_options.show_unverified_collections,
     )
     .await?;
-    Ok(build_asset_response(assets, limit, &pagination))
+    Ok(build_asset_response(
+        assets,
+        page_options.limit,
+        &pagination,
+        display_options,
+    ))
 }
diff --git a/digital_asset_types/src/dapi/change_logs.rs b/digital_asset_types/src/dapi/change_logs.rs
index a710c0a5a..11e1aa5ca 100644
--- a/digital_asset_types/src/dapi/change_logs.rs
+++ b/digital_asset_types/src/dapi/change_logs.rs
@@ -1,6 +1,6 @@
-use log::debug;
 use sea_orm::sea_query::Expr;
 use sea_orm::{DatabaseConnection, DbBackend};
+use std::collections::HashMap;
 use {
     crate::dao::asset,
     crate::dao::cl_items,
@@ -15,6 +15,22 @@ struct SimpleChangeLog {
     level: i64,
     node_idx: i64,
     seq: i64,
+    tree: Vec<u8>,
+}
+
+#[derive(FromQueryResult, Debug, Default, Clone, Eq, PartialEq)]
+struct LeafInfo {
+    id: Vec<u8>,
+    tree_id: Vec<u8>,
+    leaf_idx: i64,
+    node_idx: i64,
+    hash: Vec<u8>,
+}
+
+#[derive(Hash, Debug, Default, Clone, Eq, PartialEq)]
+struct Leaf {
+    tree_id: Vec<u8>,
+    leaf_idx: i64,
 }
 
 pub async fn get_proof_for_asset(
@@ -42,9 +58,6 @@ pub async fn get_proof_for_asset(
     }
     let leaf = leaf.unwrap();
     let req_indexes = get_required_nodes_for_proof(leaf.node_idx);
-    let expected_proof_size = req_indexes.len();
-    let mut final_node_list: Vec<SimpleChangeLog> =
-        vec![SimpleChangeLog::default(); expected_proof_size];
     let mut query = cl_items::Entity::find()
         .select_only()
         .column(cl_items::Column::NodeIdx)
@@ -61,48 +74,169 @@ pub async fn get_proof_for_asset(
     query.sql = query
         .sql
         .replace("SELECT", "SELECT DISTINCT ON (cl_items.node_idx)");
+    let required_nodes: Vec<SimpleChangeLog> = db.query_all(query).await.map(|qr| {
+        qr.iter()
+            .map(|q| SimpleChangeLog::from_query_result(q, "").unwrap())
+            .collect()
+    })?;
+    let asset_proof = build_asset_proof(
+        leaf.tree,
+        leaf.node_idx,
+        leaf.hash,
+        &req_indexes,
+        &required_nodes,
+    );
+    Ok(asset_proof)
+}
+
+pub async fn get_asset_proof_batch(
+    db: &DatabaseConnection,
+    asset_ids: Vec<Vec<u8>>,
+) -> Result<HashMap<String, AssetProof>, DbErr> {
+    // get the leaves (JOIN with `asset` table to get the asset ids)
+    let q = asset::Entity::find()
+        .join(
+            JoinType::InnerJoin,
+            asset::Entity::belongs_to(cl_items::Entity)
+                .from(asset::Column::Nonce)
+                .to(cl_items::Column::LeafIdx)
+                .into(),
+        )
+        .select_only()
+        .column(asset::Column::Id)
+        .column(asset::Column::TreeId)
+        .column(cl_items::Column::LeafIdx)
+        .column(cl_items::Column::NodeIdx)
+        .column(cl_items::Column::Hash)
+        .filter(Expr::cust("asset.tree_id = cl_items.tree"))
+        // filter by user provided asset ids
+        .filter(asset::Column::Id.is_in(asset_ids.clone()))
+        .build(DbBackend::Postgres);
+    let leaves: Vec<LeafInfo> = db.query_all(q).await.map(|qr| {
+        qr.iter()
+            .map(|q| LeafInfo::from_query_result(q, "").unwrap())
+            .collect()
+    })?;
+
+    let mut asset_map: HashMap<Leaf, LeafInfo> = HashMap::new();
+    for l in &leaves {
+        let key = Leaf {
+            tree_id: l.tree_id.clone(),
+            leaf_idx: l.leaf_idx,
+        };
+        asset_map.insert(key, l.clone());
+    }
+
+    // map: (tree_id, leaf_idx) -> [...req_indexes]
+    let mut tree_indexes: HashMap<Leaf, Vec<i64>> = HashMap::new();
+    for leaf in &leaves {
+        let key = Leaf {
+            tree_id: leaf.tree_id.clone(),
+            leaf_idx: leaf.leaf_idx,
+        };
+        let req_indexes = get_required_nodes_for_proof(leaf.node_idx);
+        tree_indexes.insert(key, req_indexes);
+    }
+
+    // get the required nodes for all assets
+    // SELECT * FROM cl_items WHERE (tree = ? AND node_idx IN (?)) OR (tree = ? AND node_idx IN (?)) OR ...
+    let mut condition = Condition::any();
+    for (leaf, req_indexes) in &tree_indexes {
+        let cond = Condition::all()
+            .add(cl_items::Column::Tree.eq(leaf.tree_id.clone()))
+            .add(cl_items::Column::NodeIdx.is_in(req_indexes.clone()));
+        condition = condition.add(cond);
+    }
+    let query = cl_items::Entity::find()
+        .select_only()
+        .column(cl_items::Column::Tree)
+        .column(cl_items::Column::NodeIdx)
+        .column(cl_items::Column::Level)
+        .column(cl_items::Column::Seq)
+        .column(cl_items::Column::Hash)
+        .filter(condition)
+        .build(DbBackend::Postgres);
     let nodes: Vec<SimpleChangeLog> = db.query_all(query).await.map(|qr| {
         qr.iter()
             .map(|q| SimpleChangeLog::from_query_result(q, "").unwrap())
             .collect()
     })?;
-    for node in nodes.iter() {
+
+    // map: (tree, node_idx) -> SimpleChangeLog
+    let mut node_map: HashMap<(Vec<u8>, i64), SimpleChangeLog> = HashMap::new();
+    for node in nodes {
+        let key = (node.tree.clone(), node.node_idx);
+        node_map.insert(key, node);
+    }
+
+    // construct the proofs
+    let mut asset_proofs: HashMap<String, AssetProof> = HashMap::new();
+    for (leaf, req_indexes) in &tree_indexes {
+        let required_nodes: Vec<SimpleChangeLog> = req_indexes
+            .iter()
+            .filter_map(|n| {
+                let key = (leaf.tree_id.clone(), *n);
+                node_map.get(&key).cloned()
+            })
+            .collect();
+
+        let leaf_info = asset_map.get(&leaf).unwrap();
+        let asset_proof = build_asset_proof(
+            leaf_info.tree_id.clone(),
+            leaf_info.node_idx,
+            leaf_info.hash.clone(),
+            &req_indexes,
+            &required_nodes,
+        );
+
+        let asset_id = bs58::encode(leaf_info.id.to_owned()).into_string();
+        asset_proofs.insert(asset_id, asset_proof);
+    }
+
+    Ok(asset_proofs)
+}
+
+fn build_asset_proof(
+    tree_id: Vec<u8>,
+    leaf_node_idx: i64,
+    leaf_hash: Vec<u8>,
+    req_indexes: &Vec<i64>,
+    required_nodes: &Vec<SimpleChangeLog>,
+) -> AssetProof {
+    let mut final_node_list = vec![SimpleChangeLog::default(); req_indexes.len()];
+    for node in required_nodes.iter() {
         if node.level < final_node_list.len().try_into().unwrap() {
             final_node_list[node.level as usize] = node.to_owned();
         }
     }
-    for (i, (n, nin)) in final_node_list.iter_mut().zip(req_indexes).enumerate() {
+    for (i, (n, nin)) in final_node_list
+        .iter_mut()
+        .zip(req_indexes.clone())
+        .enumerate()
+    {
         if *n == SimpleChangeLog::default() {
-            *n = make_empty_node(i as i64, nin);
+            *n = make_empty_node(i as i64, nin, tree_id.clone());
         }
     }
-    for n in final_node_list.iter() {
-        debug!(
-            "level {} index {} seq {} hash {}",
-            n.level,
-            n.node_idx,
-            n.seq,
-            bs58::encode(&n.hash).into_string()
-        );
-    }
-    Ok(AssetProof {
+    AssetProof {
         root: bs58::encode(final_node_list.pop().unwrap().hash).into_string(),
-        leaf: bs58::encode(&leaf.hash).into_string(),
+        leaf: bs58::encode(leaf_hash).into_string(),
         proof: final_node_list
             .iter()
             .map(|model| bs58::encode(&model.hash).into_string())
             .collect(),
-        node_index: leaf.node_idx,
-        tree_id: bs58::encode(&leaf.tree).into_string(),
-    })
+        node_index: leaf_node_idx,
+        tree_id: bs58::encode(tree_id).into_string(),
+    }
 }
 
-fn make_empty_node(lvl: i64, node_index: i64) -> SimpleChangeLog {
+fn make_empty_node(lvl: i64, node_index: i64, tree: Vec<u8>) -> SimpleChangeLog {
     SimpleChangeLog {
         node_idx: node_index,
         level: lvl,
         hash: empty_node(lvl as u32).to_vec(),
         seq: 0,
+        tree,
     }
 }
 
diff --git a/digital_asset_types/src/dapi/common/asset.rs b/digital_asset_types/src/dapi/common/asset.rs
index 007e927b6..68be6482a 100644
--- a/digital_asset_types/src/dapi/common/asset.rs
+++ b/digital_asset_types/src/dapi/common/asset.rs
@@ -1,7 +1,9 @@
 use crate::dao::sea_orm_active_enums::SpecificationVersions;
 use crate::dao::FullAsset;
+use crate::dao::PageOptions;
 use crate::dao::Pagination;
 use crate::dao::{asset, asset_authority, asset_creators, asset_data, asset_grouping};
+use crate::rpc::display_options::DisplayOptions;
 use crate::rpc::filter::{AssetSortBy, AssetSortDirection, AssetSorting};
 use crate::rpc::response::{AssetError, AssetList};
 use crate::rpc::{
@@ -48,17 +50,27 @@ pub fn build_asset_response(
     assets: Vec<FullAsset>,
     limit: u64,
     pagination: &Pagination,
+    display_options: &DisplayOptions,
 ) -> AssetList {
     let total = assets.len() as u32;
-    let (page, before, after) = match pagination {
+    let (page, before, after, cursor) = match pagination {
         Pagination::Keyset { before, after } => {
             let bef = before.clone().and_then(|x| String::from_utf8(x).ok());
             let aft = after.clone().and_then(|x| String::from_utf8(x).ok());
-            (None, bef, aft)
+            (None, bef, aft, None)
+        }
+        Pagination::Page { page } => (Some(*page), None, None, None),
+        Pagination::Cursor(_) => {
+            if let Some(last_asset) = assets.last() {
+                let cursor_str = bs58::encode(&last_asset.asset.id.clone()).into_string();
+                (None, None, None, Some(cursor_str))
+            } else {
+                (None, None, None, None)
+            }
         }
-        Pagination::Page { page } => (Some(*page), None, None),
     };
-    let (items, errors) = asset_list_to_rpc(assets);
+
+    let (items, errors) = asset_list_to_rpc(assets, display_options);
     AssetList {
         total,
         limit: limit as u32,
@@ -67,11 +79,13 @@ pub fn build_asset_response(
         after,
         items,
         errors,
+        cursor,
     }
 }
 
 pub fn create_sorting(sorting: AssetSorting) -> (sea_orm::query::Order, Option<asset::Column>) {
     let sort_column = match sorting.sort_by {
+        AssetSortBy::Id => Some(asset::Column::Id),
         AssetSortBy::Created => Some(asset::Column::CreatedAt),
         AssetSortBy::Updated => Some(asset::Column::SlotUpdated),
         AssetSortBy::RecentAction => Some(asset::Column::SlotUpdated),
@@ -84,18 +98,22 @@ pub fn create_sorting(sorting: AssetSorting) -> (sea_orm::query::Order, Option<a
     (sort_direction, sort_column)
 }
 
-pub fn create_pagination(
-    before: Option<Vec<u8>>,
-    after: Option<Vec<u8>>,
-    page: Option<u64>,
-) -> Result<Pagination, DbErr> {
-    match (&before, &after, &page) {
-        (_, _, None) => Ok(Pagination::Keyset {
-            before: before.map(|x| x.into()),
-            after: after.map(|x| x.into()),
-        }),
-        (None, None, Some(p)) => Ok(Pagination::Page { page: *p }),
-        _ => Err(DbErr::Custom("Invalid Pagination".to_string())),
+pub fn create_pagination(page_options: &PageOptions) -> Result<Pagination, DbErr> {
+    if let Some(cursor) = &page_options.cursor {
+        Ok(Pagination::Cursor(cursor.clone()))
+    } else {
+        match (
+            page_options.before.as_ref(),
+            page_options.after.as_ref(),
+            page_options.page,
+        ) {
+            (_, _, None) => Ok(Pagination::Keyset {
+                before: page_options.before.clone(),
+                after: page_options.after.clone(),
+            }),
+            (None, None, Some(p)) => Ok(Pagination::Page { page: p }),
+            _ => Err(DbErr::Custom("Invalid Pagination".to_string())),
+        }
     }
 }
 
@@ -150,6 +168,10 @@ pub fn v1_content_from_json(asset_data: &asset_data::Model) -> Result<Content, D
     if let Some(symbol) = symbol {
         meta.set_item("attributes", symbol.clone());
     }
+    let token_standard = safe_select(chain_data_selector, "$.token_standard");
+    if let Some(token_standard) = token_standard {
+        meta.set_item("token_standard", token_standard.clone());
+    }
     let mut links = HashMap::new();
     let link_fields = vec!["image", "animation_url", "external_url"];
     for f in link_fields {
@@ -221,7 +243,6 @@ pub fn v1_content_from_json(asset_data: &asset_data::Model) -> Result<Content, D
         _ => Ordering::Equal,
     });
 
-
     Ok(Content {
         schema: "https://schema.metaplex.com/nft1.0.json".to_string(),
         json_uri,
@@ -231,10 +252,7 @@ pub fn v1_content_from_json(asset_data: &asset_data::Model) -> Result<Content, D
     })
 }
 
-pub fn get_content(
-    asset: &asset::Model,
-    data: &asset_data::Model,
-) -> Result<Content, DbErr> {
+pub fn get_content(asset: &asset::Model, data: &asset_data::Model) -> Result<Content, DbErr> {
     match asset.specification_version {
         Some(SpecificationVersions::V1) | Some(SpecificationVersions::V0) => {
             v1_content_from_json(data)
@@ -265,20 +283,27 @@ pub fn to_creators(creators: Vec<asset_creators::Model>) -> Vec<Creator> {
         .collect()
 }
 
-pub fn to_grouping(groups: Vec<asset_grouping::Model>) -> Result<Vec<Group>, DbErr> {
-    fn find_group(model: &asset_grouping::Model) -> Result<Group, DbErr> {
-        Ok(Group {
-            group_key: model.group_key.clone(),
-            group_value: Some(
-                model
-                    .group_value
-                    .clone()
-                    .ok_or(DbErr::Custom("Group value not found".to_string()))?,
-            ),
+pub fn to_grouping(
+    groups: Vec<asset_grouping::Model>,
+    display_options: &DisplayOptions,
+) -> Result<Vec<Group>, DbErr> {
+    let result: Vec<Group> = groups
+        .iter()
+        .filter_map(|model| {
+            let verified = match display_options.show_unverified_collections {
+                // Null verified indicates legacy data, meaning it is verified.
+                true => Some(model.verified.unwrap_or(true)),
+                false => None,
+            };
+            // Filter out items where group_value is None.
+            model.group_value.clone().map(|group_value| Group {
+                group_key: model.group_key.clone(),
+                group_value: Some(group_value),
+                verified,
+            })
         })
-    }
-
-    groups.iter().map(find_group).collect()
+        .collect();
+    Ok(result)
 }
 
 pub fn get_interface(asset: &asset::Model) -> Result<Interface, DbErr> {
@@ -297,9 +322,7 @@ pub fn get_interface(asset: &asset::Model) -> Result<Interface, DbErr> {
 }
 
 //TODO -> impl custom error type
-pub fn asset_to_rpc(
-    asset: FullAsset
-) -> Result<RpcAsset, DbErr> {
+pub fn asset_to_rpc(asset: FullAsset, display_options: &DisplayOptions) -> Result<RpcAsset, DbErr> {
     let FullAsset {
         asset,
         data,
@@ -309,7 +332,7 @@ pub fn asset_to_rpc(
     } = asset;
     let rpc_authorities = to_authority(authorities);
     let rpc_creators = to_creators(creators);
-    let rpc_groups = to_grouping(groups)?;
+    let rpc_groups = to_grouping(groups, display_options)?;
     let interface = get_interface(&asset)?;
     let content = get_content(&asset, &data)?;
     let mut chain_data_selector_fn = jsonpath_lib::selector(&data.chain_data);
@@ -390,13 +413,14 @@ pub fn asset_to_rpc(
 }
 
 pub fn asset_list_to_rpc(
-    asset_list: Vec<FullAsset>
+    asset_list: Vec<FullAsset>,
+    display_options: &DisplayOptions,
 ) -> (Vec<RpcAsset>, Vec<AssetError>) {
     asset_list
         .into_iter()
         .fold((vec![], vec![]), |(mut assets, mut errors), asset| {
             let id = bs58::encode(asset.asset.id.clone()).into_string();
-            match asset_to_rpc(asset) {
+            match asset_to_rpc(asset, display_options) {
                 Ok(rpc_asset) => assets.push(rpc_asset),
                 Err(e) => errors.push(AssetError {
                     id,
diff --git a/digital_asset_types/src/dapi/get_asset.rs b/digital_asset_types/src/dapi/get_asset.rs
index 110d9cb62..6056d56eb 100644
--- a/digital_asset_types/src/dapi/get_asset.rs
+++ b/digital_asset_types/src/dapi/get_asset.rs
@@ -1,10 +1,33 @@
+use super::common::{asset_to_rpc, build_asset_response};
+use crate::{
+    dao::{scopes, Pagination},
+    rpc::{display_options::DisplayOptions, Asset},
+};
 use sea_orm::{DatabaseConnection, DbErr};
+use std::collections::HashMap;
 
-use crate::{dao::scopes, rpc::Asset};
-
-use super::common::asset_to_rpc;
-
-pub async fn get_asset(db: &DatabaseConnection, id: Vec<u8>) -> Result<Asset, DbErr> {
+pub async fn get_asset(
+    db: &DatabaseConnection,
+    id: Vec<u8>,
+    display_options: &DisplayOptions,
+) -> Result<Asset, DbErr> {
     let asset = scopes::asset::get_by_id(db, id, false).await?;
-    asset_to_rpc(asset)
+    asset_to_rpc(asset, display_options)
+}
+
+pub async fn get_asset_batch(
+    db: &DatabaseConnection,
+    ids: Vec<Vec<u8>>,
+    limit: u64,
+    display_options: &DisplayOptions,
+) -> Result<HashMap<String, Asset>, DbErr> {
+    let pagination = Pagination::Page { page: 1 };
+    let assets = scopes::asset::get_asset_batch(db, ids, &pagination, limit).await?;
+    let asset_list = build_asset_response(assets, limit, &pagination, display_options);
+    let asset_map = asset_list
+        .items
+        .into_iter()
+        .map(|asset| (asset.id.clone(), asset))
+        .collect();
+    Ok(asset_map)
 }
diff --git a/digital_asset_types/src/dapi/search_assets.rs b/digital_asset_types/src/dapi/search_assets.rs
index d6998ea76..cd467ab8b 100644
--- a/digital_asset_types/src/dapi/search_assets.rs
+++ b/digital_asset_types/src/dapi/search_assets.rs
@@ -1,7 +1,7 @@
 use super::common::{build_asset_response, create_pagination, create_sorting};
 use crate::{
-    dao::{scopes, SearchAssetsQuery},
-    rpc::{filter::AssetSorting, response::AssetList},
+    dao::{scopes, PageOptions, SearchAssetsQuery},
+    rpc::{display_options::DisplayOptions, filter::AssetSorting, response::AssetList},
 };
 use sea_orm::{DatabaseConnection, DbErr};
 
@@ -9,12 +9,10 @@ pub async fn search_assets(
     db: &DatabaseConnection,
     search_assets_query: SearchAssetsQuery,
     sorting: AssetSorting,
-    limit: u64,
-    page: Option<u64>,
-    before: Option<Vec<u8>>,
-    after: Option<Vec<u8>>,
+    page_options: &PageOptions,
+    display_options: &DisplayOptions,
 ) -> Result<AssetList, DbErr> {
-    let pagination = create_pagination(before, after, page)?;
+    let pagination = create_pagination(&page_options)?;
     let (sort_direction, sort_column) = create_sorting(sorting);
     let (condition, joins) = search_assets_query.conditions()?;
     let assets = scopes::asset::get_assets_by_condition(
@@ -24,8 +22,14 @@ pub async fn search_assets(
         sort_column,
         sort_direction,
         &pagination,
-        limit,
+        page_options.limit,
+        display_options.show_unverified_collections,
     )
     .await?;
-    Ok(build_asset_response(assets, limit, &pagination))
+    Ok(build_asset_response(
+        assets,
+        page_options.limit,
+        &pagination,
+        display_options,
+    ))
 }
diff --git a/digital_asset_types/src/rpc/asset.rs b/digital_asset_types/src/rpc/asset.rs
index ea365e4b1..cfbb02d40 100644
--- a/digital_asset_types/src/rpc/asset.rs
+++ b/digital_asset_types/src/rpc/asset.rs
@@ -200,6 +200,8 @@ pub type GroupValue = String;
 pub struct Group {
     pub group_key: String,
     pub group_value: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub verified: Option<bool>,
 }
 
 #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, JsonSchema)]
diff --git a/digital_asset_types/src/rpc/display_options.rs b/digital_asset_types/src/rpc/display_options.rs
new file mode 100644
index 000000000..ac3efc6b6
--- /dev/null
+++ b/digital_asset_types/src/rpc/display_options.rs
@@ -0,0 +1,9 @@
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, JsonSchema, Default)]
+#[serde(deny_unknown_fields, rename_all = "camelCase")]
+pub struct DisplayOptions {
+    #[serde(default)]
+    pub show_unverified_collections: bool,
+}
diff --git a/digital_asset_types/src/rpc/filter.rs b/digital_asset_types/src/rpc/filter.rs
index f6aa010f8..0de5574b7 100644
--- a/digital_asset_types/src/rpc/filter.rs
+++ b/digital_asset_types/src/rpc/filter.rs
@@ -19,8 +19,9 @@ impl Default for AssetSorting {
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
-
 pub enum AssetSortBy {
+    #[serde(rename = "id")]
+    Id,
     #[serde(rename = "created")]
     Created,
     #[serde(rename = "updated")]
diff --git a/digital_asset_types/src/rpc/mod.rs b/digital_asset_types/src/rpc/mod.rs
index 92325d382..a27a904b5 100644
--- a/digital_asset_types/src/rpc/mod.rs
+++ b/digital_asset_types/src/rpc/mod.rs
@@ -1,5 +1,6 @@
 mod asset;
 
+pub mod display_options;
 pub mod filter;
 pub mod response;
 
diff --git a/digital_asset_types/src/rpc/response.rs b/digital_asset_types/src/rpc/response.rs
index 855061438..3945f6955 100644
--- a/digital_asset_types/src/rpc/response.rs
+++ b/digital_asset_types/src/rpc/response.rs
@@ -30,6 +30,8 @@ pub struct AssetList {
     pub before: Option<String>,
     #[serde(skip_serializing_if = "Option::is_none")]
     pub after: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub cursor: Option<String>,
     pub items: Vec<Asset>,
     #[serde(skip_serializing_if = "Vec::is_empty")]
     pub errors: Vec<AssetError>,