This commit is contained in:
2026-07-23 16:37:12 +02:00
parent 99c345f2f2
commit 0da75c1311
2159 changed files with 230833 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
# file: kb_materializer_metadata/Cargo.toml
# version: 5
[package]
name = "kb_materializer_metadata"
version.workspace = true
edition.workspace = true
license.workspace = true
publish.workspace = true
[dependencies]
kb_core = { path = "../kb_core" }
kb_decoder_spl_token_2022 = { path = "../kb_decoder_spl_token_2022" }
kb_decoder_metadata_metaplex_token_metadata = { path = "../kb_decoder_metadata_metaplex_token_metadata" }
kb_materializer_api = { path = "../kb_materializer_api" }
kb_model = { path = "../kb_model" }
kb_program_ids = { path = "../kb_program_ids" }
serde_json.workspace = true
[lints]
workspace = true

View File

@@ -0,0 +1,45 @@
<!-- file: kb_materializer_metadata/README.md -->
<!-- version: 2 -->
# kb_materializer_metadata
Ce crate matérialise les états et observations de metadata dans une projection métier canonique.
## Frontière
Le matérialisateur ne lit ni RPC, ni wallet, ni Tauri. Il reçoit des snapshots déjà décodés, bornés et accompagnés de leur provenance.
Une même réalité métier possède une seule projection canonique. Lorsquun snapshot de compte owned est disponible, il constitue la source autoritative de létat final. Les instructions restent des observations de mutation et ne produisent pas une seconde copie concurrente du même état.
## Metaplex Token Metadata
`materialize_metaplex_metadata_snapshot` matérialise un `CanonicalMetadataAccountSnapshot` de type `MetadataV1` lorsque sa provenance est commitée.
La projection conserve notamment :
- lidentité stable programme/compte ;
- mint et update authority ;
- name, symbol et URI ;
- royalties en basis points ;
- primary sale et mutabilité ;
- creators, collection, uses, token standard et programmable config depuis le payload borné ;
- layout, lifecycle et version canonique ;
- source, slot, write version, signature et indices dinstruction lorsquils sont disponibles.
La fonction ne télécharge jamais lURI externe. Sa clé de sortie stable est basée sur ladresse du compte Metadata, afin quun replay ou backfill mette à jour la même identité logique au lieu de créer un doublon par instruction.
Une provenance non commitée ou un autre layout est refusé.
## Token-2022 incorporé
`materialize_token_2022_metadata_snapshot` projette uniquement les extensions `TokenMetadata`, `TokenGroup` et `TokenGroupMember` dun état Mint déjà parsé et borné. La provenance reste `token_2022_embedded_tlv`; aucun URI externe nest téléchargé et aucune donnée Metaplex nest fusionnée implicitement.
Les conflits éventuels entre Metaplex, Token-2022 embedded metadata et futures sources externes doivent être résolus par une politique de provenance explicite, jamais par écrasement silencieux.
## Metaplex Token Metadata — `0.4.7-pre.035`
Les comptes Edition, Master Edition, Edition Marker, Token Record, Delegate Record, Authority Record, Token Owned Escrow et Reservation List sont matérialisés depuis leurs snapshots owned commités.
Chaque compte conserve une clé stable, son lifecycle actif/expérimental/historique, sa provenance et un propriétaire unique du fait. Les comptes historiques restent matérialisables pendant les backfills et replays, mais leur politique dexécution demeure `never_historical`.
Les instructions décodées décrivent les mutations ; elles ne produisent pas une seconde copie concurrente de létat final du compte.

View File

@@ -0,0 +1,18 @@
// file: kb_materializer_metadata/src/lib.rs
// version: 5
//! Materializer crate for `metadata`.
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
mod materializer;
/// Exposes the materializer type implemented by this crate.
pub use crate::materializer::MetadataMaterializer;
/// Materialize one canonical non-Metadata Metaplex account snapshot.
pub use crate::materializer::materialize_metaplex_account_snapshot;
/// Materialize one canonical Metaplex Metadata account snapshot without instruction-state duplication.
pub use crate::materializer::materialize_metaplex_metadata_snapshot;
/// Materialize Token-2022 embedded metadata and group state from one parsed mint snapshot.
pub use crate::materializer::materialize_token_2022_metadata_snapshot;

View File

@@ -0,0 +1,585 @@
// file: kb_materializer_metadata/src/materializer.rs
// version: 7
//! Materializer scaffold for the `kb_materializer_metadata` crate.
/// Initial materializer implementation placeholder.
#[derive(Clone, Debug, Default)]
pub struct MetadataMaterializer;
impl kb_materializer_api::Materializer for crate::MetadataMaterializer {
fn materializer_name(&self) -> &'static str {
return "kb_materializer_metadata";
}
fn materializer_version(&self) -> &'static str {
return env!("CARGO_PKG_VERSION");
}
fn accepts_event(&self, _event: &kb_model::DecodedProtocolEvent) -> bool {
return false;
}
fn materialize_event(
&self,
event: &kb_model::DecodedProtocolEvent,
) -> kb_core::Result<std::vec::Vec<kb_model::MaterializedEvent>> {
let materialized = kb_model::MaterializedEvent {
signature: event.signature.clone(),
slot: event.slot,
decoded_family: event.event_family,
materialized_family: kb_model::MaterializedEventFamily::Metadata,
};
return std::result::Result::Ok(std::vec![materialized]);
}
}
/// Materialize embedded Token-2022 metadata, group, and group-member extensions.
pub fn materialize_token_2022_metadata_snapshot(
account_key: &str,
slot: u64,
state: &kb_decoder_spl_token_2022::state::Token2022State,
) -> std::result::Result<std::vec::Vec<kb_materializer_api::MaterializedOutput>, String> {
if account_key.is_empty() {
return std::result::Result::Err(
"Token-2022 metadata snapshot requires a non-empty account key".to_string(),
);
}
if state.kind != kb_decoder_spl_token_2022::state::Token2022StateKind::Mint {
return std::result::Result::Err(
"Token-2022 metadata snapshot requires a parsed mint state".to_string(),
);
}
let mut outputs = std::vec::Vec::new();
for entry in &state.extensions {
let projection_kind = match entry.extension_name {
"token_metadata" => std::option::Option::Some("token_metadata"),
"token_group" => std::option::Option::Some("token_group"),
"token_group_member" => std::option::Option::Some("token_group_member"),
_ => std::option::Option::None,
};
let kind = match projection_kind {
std::option::Option::Some(value) => value,
std::option::Option::None => continue,
};
outputs.push(kb_materializer_api::MaterializedOutput {
output_key: format!("token_2022_metadata_snapshot:{account_key}:{slot}:{kind}"),
family: kb_model::MaterializedEventFamily::Metadata,
payload_json: serde_json::json!({
"projectionVersion": 1,
"domain": "spl_token_2022_embedded_metadata",
"projectionSemantics": "authoritative_bounded_mint_extension_snapshot",
"idempotenceKey": format!("token_2022_metadata:{account_key}:{slot}:{kind}"),
"accountKey": account_key,
"slot": slot,
"programId": kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
"metadataSource": "token_2022_embedded_tlv",
"projectionKind": kind,
"extensionType": entry.extension_type,
"extensionName": entry.extension_name,
"valueHex": entry.value_hex,
"valueFields": entry.value_fields,
"externalUriFetched": false,
"identityAuthority": "mint_address",
"finalAccountStateCaptured": true,
"provenance": {
"processorName": "kb_materializer_metadata",
"processorVersion": env!("CARGO_PKG_VERSION"),
"source": "bounded_token_2022_state_parser"
}
}),
});
}
return std::result::Result::Ok(outputs);
}
/// Materialize one canonical Metaplex `MetadataV1` account snapshot.
///
/// Account snapshots are authoritative for current state. Instruction observations may describe
/// mutations, but must not emit a second competing metadata-state projection.
pub fn materialize_metaplex_metadata_snapshot(
snapshot: &kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountSnapshot,
) -> std::result::Result<kb_materializer_api::MaterializedOutput, String> {
if !snapshot.provenance.committed {
return std::result::Result::Err(
"Metaplex metadata state materialization requires committed provenance".to_string(),
);
}
if snapshot.identity.materialization
!= kb_decoder_metadata_metaplex_token_metadata::CanonicalMaterializationPolicy::ActiveState
{
return std::result::Result::Err(
"Metaplex MetadataV1 materialization requires active-state policy".to_string(),
);
}
let metadata = match &snapshot.state {
kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountState::Metadata(
value,
) => value,
_ => {
return std::result::Result::Err(
"Metaplex metadata materializer received another account layout".to_string(),
);
},
};
let slot = snapshot.provenance.slot.map(|value| return value.to_string());
let write_version = snapshot.provenance.write_version.map(|value| return value.to_string());
let payload = serde_json::json!({
"projectionVersion": 1,
"domain": "metaplex_token_metadata",
"projectionSemantics": "authoritative_committed_account_snapshot",
"stableId": snapshot.identity.stable_id,
"account": metadata.account,
"mint": metadata.mint,
"updateAuthority": metadata.update_authority,
"name": metadata.name,
"symbol": metadata.symbol,
"uri": metadata.uri,
"sellerFeeBasisPoints": metadata.seller_fee_basis_points,
"primarySaleHappened": metadata.primary_sale_happened,
"isMutable": metadata.is_mutable,
"layout": snapshot.identity.layout,
"layoutDiscriminant": snapshot.identity.discriminant,
"canonicalVersion": snapshot.identity.canonical_version,
"lifecycle": "active",
"materializationPolicy": "active_state",
"metadataSource": "metaplex_owned_account",
"accountState": metadata.payload_json,
"externalUriFetched": false,
"stateAuthority": "owned_account_snapshot",
"instructionProjectionRole": "mutation_observation_only_no_duplicate_state",
"provenance": {
"source": format!("{:?}", snapshot.provenance.source),
"slot": slot,
"writeVersion": write_version,
"signature": snapshot.provenance.signature,
"instructionIndex": snapshot.provenance.instruction_index,
"innerInstructionIndex": snapshot.provenance.inner_instruction_index,
"committed": snapshot.provenance.committed,
"processorName": "kb_materializer_metadata",
"processorVersion": env!("CARGO_PKG_VERSION")
}
});
return std::result::Result::Ok(kb_materializer_api::MaterializedOutput {
output_key: format!("metaplex_metadata_state:{}", metadata.account),
family: kb_model::MaterializedEventFamily::Metadata,
payload_json: payload,
});
}
#[cfg(test)]
mod tests_metaplex_metadata_state {
pub(super) fn snapshot(
committed: bool,
) -> kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountSnapshot {
let metadata = kb_decoder_metadata_metaplex_token_metadata::MetadataAccountSnapshot {
account: "metadata111".to_string(),
bump: 1,
mint: "mint111".to_string(),
update_authority: "authority111".to_string(),
name: "Asset".to_string(),
symbol: "AST".to_string(),
uri: "https://example.invalid/asset.json".to_string(),
seller_fee_basis_points: 500,
primary_sale_happened: true,
is_mutable: false,
payload_json: serde_json::json!({
"creators": [{"address":"creator111","verified":true,"share":100}],
"collection": {"verified":true,"key":"collection111"},
"uses": {"useMethod":"Single","remaining":"0","total":"1"},
"tokenStandard": "ProgrammableNonFungible",
"programmableConfig": {"ruleSet":"rules111"}
}),
};
return kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountSnapshot::from_metadata(
metadata,
kb_decoder_metadata_metaplex_token_metadata::CanonicalAccountProvenance {
source: kb_decoder_metadata_metaplex_token_metadata::CanonicalObservationSource::Backfill,
slot: std::option::Option::Some(42),
write_version: std::option::Option::Some(7),
signature: std::option::Option::Some("signature111".to_string()),
instruction_index: std::option::Option::Some(3),
inner_instruction_index: std::option::Option::None,
committed,
},
);
}
#[test]
fn account_snapshot_is_one_stable_authoritative_metadata_projection() {
let first = crate::materialize_metaplex_metadata_snapshot(&snapshot(true));
let second = crate::materialize_metaplex_metadata_snapshot(&snapshot(true));
assert_eq!(first, second);
assert_eq!(
first.as_ref().map(|value| return value.output_key.as_str()),
std::result::Result::Ok("metaplex_metadata_state:metadata111")
);
assert_eq!(
first.as_ref().map(|value| return value.payload_json["metadataSource"].clone()),
std::result::Result::Ok(serde_json::json!("metaplex_owned_account"))
);
assert_eq!(
first
.as_ref()
.map(|value| return value.payload_json["instructionProjectionRole"].clone()),
std::result::Result::Ok(serde_json::json!(
"mutation_observation_only_no_duplicate_state"
))
);
assert_eq!(
first
.as_ref()
.map(|value| return value.payload_json["accountState"]["creators"][0]["share"]
.clone()),
std::result::Result::Ok(serde_json::json!(100))
);
}
#[test]
fn uncommitted_or_non_metadata_state_is_rejected() {
assert!(crate::materialize_metaplex_metadata_snapshot(&snapshot(false)).is_err());
let mut wrong = snapshot(true);
wrong.state = kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountState::EditionMarker(
kb_decoder_metadata_metaplex_token_metadata::EditionMarkerAccountSnapshot {
account: "marker111".to_string(),
bump: 1,
mint: "mint111".to_string(),
kind: kb_decoder_metadata_metaplex_token_metadata::EditionMarkerAccountKind::EditionMarkerV1,
marker_group: std::option::Option::Some(0),
edition: 1,
byte_index: 0,
bit_mask: 64,
edition_taken: true,
ledger: std::vec![64],
payload_json: serde_json::json!({}),
},
);
assert!(crate::materialize_metaplex_metadata_snapshot(&wrong).is_err());
}
}
/// Materialize any canonical Metaplex account state other than `MetadataV1`.
///
/// Every account address maps to one stable state key. Historical layouts remain materializable
/// with an explicit historical lifecycle, while execution policy remains independently disabled.
pub fn materialize_metaplex_account_snapshot(
snapshot: &kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountSnapshot,
) -> std::result::Result<kb_materializer_api::MaterializedOutput, String> {
if !snapshot.provenance.committed {
return std::result::Result::Err(
"Metaplex account-state materialization requires committed provenance".to_string(),
);
}
let (projection_kind, fact_owner, state_json) = match &snapshot.state {
kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountState::Metadata(_) => {
return std::result::Result::Err(
"MetadataV1 must use the dedicated metadata-state materializer".to_string(),
);
},
kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountState::Edition(value) => (
"edition_state",
"kb_materializer_metadata",
value.payload_json.clone(),
),
kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountState::EditionMarker(value) => (
"edition_marker_state",
"kb_materializer_metadata",
value.payload_json.clone(),
),
kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountState::TokenRecord(value) => (
"programmable_token_state",
"kb_materializer_lifecycle",
value.payload_json.clone(),
),
kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountState::DelegateRecord(value) => (
"delegate_admin_state",
"kb_materializer_admin",
value.payload_json.clone(),
),
kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountState::AuthorityRecord(value) => (
"authority_admin_state",
"kb_materializer_admin",
value.payload_json.clone(),
),
kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountState::TokenOwnedEscrow(value) => (
"token_owned_escrow_state",
"kb_materializer_lifecycle",
value.payload_json.clone(),
),
kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountState::ReservationList(value) => (
"historical_reservation_state",
"kb_materializer_metadata",
value.payload_json.clone(),
),
};
let lifecycle = match snapshot.identity.lifecycle {
kb_decoder_metadata_metaplex_token_metadata::CanonicalAccountLifecycle::Active => "active",
kb_decoder_metadata_metaplex_token_metadata::CanonicalAccountLifecycle::ActiveExperimental => {
"active_experimental"
},
kb_decoder_metadata_metaplex_token_metadata::CanonicalAccountLifecycle::Historical => {
"historical"
},
};
let materialization_policy = match snapshot.identity.materialization {
kb_decoder_metadata_metaplex_token_metadata::CanonicalMaterializationPolicy::ActiveState => {
"active_state"
},
kb_decoder_metadata_metaplex_token_metadata::CanonicalMaterializationPolicy::HistoricalState => {
"historical_state"
},
};
let execution_policy = match snapshot.identity.execution {
kb_decoder_metadata_metaplex_token_metadata::CanonicalExecutionPolicy::CurrentCandidate => {
"current_candidate"
},
kb_decoder_metadata_metaplex_token_metadata::CanonicalExecutionPolicy::ExperimentalCandidate => {
"experimental_candidate"
},
kb_decoder_metadata_metaplex_token_metadata::CanonicalExecutionPolicy::NeverHistorical => {
"never_historical"
},
};
return std::result::Result::Ok(kb_materializer_api::MaterializedOutput {
output_key: format!("metaplex_account_state:{}", snapshot.identity.account),
family: kb_model::MaterializedEventFamily::Metadata,
payload_json: serde_json::json!({
"projectionVersion": 1,
"domain": "metaplex_token_metadata",
"projectionKind": projection_kind,
"projectionSemantics": "authoritative_committed_owned_account_snapshot",
"stableId": snapshot.identity.stable_id,
"account": snapshot.identity.account,
"programId": snapshot.identity.program_id,
"layout": snapshot.identity.layout,
"layoutDiscriminant": snapshot.identity.discriminant,
"canonicalVersion": snapshot.identity.canonical_version,
"lifecycle": lifecycle,
"materializationPolicy": materialization_policy,
"executionPolicy": execution_policy,
"factOwner": fact_owner,
"metadataSource": "metaplex_owned_account",
"accountState": state_json,
"instructionProjectionRole": "mutation_observation_only_no_duplicate_state",
"provenance": {
"source": format!("{:?}", snapshot.provenance.source),
"slot": snapshot.provenance.slot.map(|value: u64| return value.to_string()),
"writeVersion": snapshot.provenance.write_version.map(|value: u64| return value.to_string()),
"signature": snapshot.provenance.signature,
"instructionIndex": snapshot.provenance.instruction_index,
"innerInstructionIndex": snapshot.provenance.inner_instruction_index,
"committed": snapshot.provenance.committed,
"processorName": "kb_materializer_metadata",
"processorVersion": env!("CARGO_PKG_VERSION")
}
}),
});
}
#[cfg(test)]
mod tests_metaplex_account_state {
fn provenance() -> kb_decoder_metadata_metaplex_token_metadata::CanonicalAccountProvenance {
return kb_decoder_metadata_metaplex_token_metadata::CanonicalAccountProvenance {
source:
kb_decoder_metadata_metaplex_token_metadata::CanonicalObservationSource::Backfill,
slot: std::option::Option::Some(90),
write_version: std::option::Option::Some(2),
signature: std::option::Option::None,
instruction_index: std::option::Option::None,
inner_instruction_index: std::option::Option::None,
committed: true,
};
}
#[test]
fn historical_edition_and_reservation_are_stable_materializable_state() {
let edition = kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountSnapshot::from_edition(
kb_decoder_metadata_metaplex_token_metadata::EditionAccountSnapshot {
account: "master-v1".to_string(),
bump: 1,
mint: "mint-v1".to_string(),
kind: kb_decoder_metadata_metaplex_token_metadata::EditionAccountKind::MasterEditionV1,
supply: std::option::Option::Some(3),
max_supply: std::option::Option::Some(10),
parent: std::option::Option::None,
edition: std::option::Option::None,
printing_mint: std::option::Option::Some("printing".to_string()),
one_time_printing_authorization_mint: std::option::Option::Some("authorization".to_string()),
payload_json: serde_json::json!({"supply":"3","maxSupply":"10"}),
},
provenance(),
);
let reservation = kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountSnapshot::from_reservation_list(
kb_decoder_metadata_metaplex_token_metadata::ReservationListAccountSnapshot {
account: "reservation-v2".to_string(),
bump: 2,
kind: kb_decoder_metadata_metaplex_token_metadata::ReservationListAccountKind::V2,
master_edition: "master-v1".to_string(),
resource: "resource".to_string(),
supply_snapshot: std::option::Option::Some(3),
reservations: std::vec::Vec::new(),
total_reservation_spots: 0,
current_reservation_spots: 0,
payload_json: serde_json::json!({"reservations":[]}),
},
provenance(),
);
let first = crate::materialize_metaplex_account_snapshot(&edition);
let second = crate::materialize_metaplex_account_snapshot(&reservation);
assert_eq!(
first.as_ref().map(|value| return value.payload_json["lifecycle"].clone()),
std::result::Result::Ok(serde_json::json!("historical"))
);
assert_eq!(
first.as_ref().map(|value| return value.payload_json["executionPolicy"].clone()),
std::result::Result::Ok(serde_json::json!("never_historical"))
);
assert_eq!(
second.as_ref().map(|value| return value.output_key.as_str()),
std::result::Result::Ok("metaplex_account_state:reservation-v2")
);
}
#[test]
fn programmable_delegate_authority_and_escrow_keep_unique_fact_owners() {
let token = kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountSnapshot::from_token_record(
kb_decoder_metadata_metaplex_token_metadata::TokenRecordAccountSnapshot {
account: "token-record".to_string(),
bump: 1,
mint: "mint".to_string(),
token: "token".to_string(),
stored_bump: 1,
state: "Locked".to_string(),
rule_set_revision: std::option::Option::Some(4),
delegate: std::option::Option::Some("delegate".to_string()),
delegate_role: std::option::Option::Some("Transfer".to_string()),
locked_transfer: std::option::Option::Some("destination".to_string()),
locked: true,
payload_json: serde_json::json!({"state":"Locked"}),
},
provenance(),
);
let output = crate::materialize_metaplex_account_snapshot(&token);
assert_eq!(
output.as_ref().map(|value| return value.payload_json["factOwner"].clone()),
std::result::Result::Ok(serde_json::json!("kb_materializer_lifecycle"))
);
assert_eq!(
output.as_ref().map(|value| return value.payload_json["projectionKind"].clone()),
std::result::Result::Ok(serde_json::json!("programmable_token_state"))
);
}
#[test]
fn uncommitted_and_metadata_snapshots_use_explicit_boundaries() {
let mut marker = kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountSnapshot::from_edition_marker(
kb_decoder_metadata_metaplex_token_metadata::EditionMarkerAccountSnapshot {
account: "marker".to_string(),
bump: 1,
mint: "mint".to_string(),
kind: kb_decoder_metadata_metaplex_token_metadata::EditionMarkerAccountKind::EditionMarkerV2,
marker_group: std::option::Option::None,
edition: 8,
byte_index: 1,
bit_mask: 128,
edition_taken: true,
ledger: std::vec![0, 128],
payload_json: serde_json::json!({"editionTaken":true}),
},
provenance(),
);
marker.provenance.committed = false;
assert!(crate::materialize_metaplex_account_snapshot(&marker).is_err());
assert!(
crate::materialize_metaplex_account_snapshot(
&super::tests_metaplex_metadata_state::snapshot(true)
)
.is_err()
);
}
}
#[cfg(test)]
mod tests_token_2022_state {
#[test]
fn embedded_metadata_group_and_member_have_distinct_stable_outputs() {
let state = kb_decoder_spl_token_2022::state::Token2022State {
kind: kb_decoder_spl_token_2022::state::Token2022StateKind::Mint,
base_fields: serde_json::json!({"supply":"1","decimals":0}),
base_hex: "00".repeat(82),
account_type: std::option::Option::Some(1),
extensions: std::vec![
kb_decoder_spl_token_2022::state::Token2022TlvEntry {
extension_type: 19,
extension_name: "token_metadata",
value_hex: "01".to_string(),
value_fields: serde_json::json!({"name":"Token","symbol":"TOK","uri":"https://example.invalid/token.json"}),
},
kb_decoder_spl_token_2022::state::Token2022TlvEntry {
extension_type: 20,
extension_name: "token_group",
value_hex: "02".to_string(),
value_fields: serde_json::json!({"mint":"mint111","size":"1","maxSize":"10"}),
},
kb_decoder_spl_token_2022::state::Token2022TlvEntry {
extension_type: 21,
extension_name: "token_group_member",
value_hex: "03".to_string(),
value_fields: serde_json::json!({"mint":"mint111","group":"group111","memberNumber":"1"}),
},
],
};
let outputs = crate::materialize_token_2022_metadata_snapshot("mint111", 9, &state);
assert_eq!(outputs.as_ref().map(|values| return values.len()), std::result::Result::Ok(3));
assert_eq!(
outputs
.as_ref()
.map(|values| return values[0].payload_json["metadataSource"].clone()),
std::result::Result::Ok(serde_json::json!("token_2022_embedded_tlv"))
);
assert_eq!(
outputs
.as_ref()
.map(|values| return values[0].payload_json["externalUriFetched"].clone()),
std::result::Result::Ok(serde_json::json!(false))
);
assert_eq!(
outputs
.as_ref()
.map(|values| return values[1].payload_json["projectionKind"].clone()),
std::result::Result::Ok(serde_json::json!("token_group"))
);
}
#[test]
fn snapshot_is_idempotent_filters_unrelated_extensions_and_requires_mint_identity() {
let state = kb_decoder_spl_token_2022::state::Token2022State {
kind: kb_decoder_spl_token_2022::state::Token2022StateKind::Mint,
base_fields: serde_json::json!({}),
base_hex: "00".repeat(82),
account_type: std::option::Option::Some(1),
extensions: std::vec![kb_decoder_spl_token_2022::state::Token2022TlvEntry {
extension_type: 1,
extension_name: "transfer_fee_config",
value_hex: "00".to_string(),
value_fields: serde_json::json!({}),
}],
};
let first = crate::materialize_token_2022_metadata_snapshot("mint111", 9, &state);
let second = crate::materialize_token_2022_metadata_snapshot("mint111", 9, &state);
assert_eq!(first, second);
assert_eq!(first.as_ref().map(|values| return values.len()), std::result::Result::Ok(0));
assert!(crate::materialize_token_2022_metadata_snapshot("", 9, &state).is_err());
let account_state = kb_decoder_spl_token_2022::state::Token2022State {
kind: kb_decoder_spl_token_2022::state::Token2022StateKind::Account,
base_fields: serde_json::json!({}),
base_hex: "00".repeat(165),
account_type: std::option::Option::None,
extensions: std::vec::Vec::new(),
};
assert!(
crate::materialize_token_2022_metadata_snapshot("account111", 9, &account_state)
.is_err()
);
}
}