Skip to content

Commit e688c94

Browse files
committed
Add nep245 metadata ext
1 parent 307ff60 commit e688c94

File tree

4 files changed

+214
-1
lines changed

4 files changed

+214
-1
lines changed

Cargo.lock

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

nep245/Cargo.toml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,16 @@ rust-version.workspace = true
66
repository.workspace = true
77

88
[dependencies]
9-
derive_more.workspace = true
9+
derive_more = { workspace = true, features = ["from"] }
1010
near-sdk.workspace = true
11+
borsh = { version = "1.5.7", features = ["unstable__schema"] }
12+
chrono = { workspace = true, default-features = false, features = ["serde"] }
13+
serde_with = { workspace = true, features = ["chrono_0_4", "schemars_0_8"] }
14+
defuse-borsh-utils = { workspace = true, features = ["chrono"] }
15+
schemars.workspace = true
16+
17+
[dev-dependencies]
18+
hex = "0.4.3"
1119

1220
[lints]
1321
workspace = true

nep245/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
mod core;
22
pub mod enumeration;
33
mod events;
4+
pub mod metadata;
45
pub mod receiver;
56
pub mod resolver;
67
mod token;

nep245/src/metadata.rs

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
//! This module presents traits according to [multi-token metadata extension](https://github.com/near/NEPs/blob/master/specs/Standards/Tokens/MultiToken/Metadata.md)
2+
3+
use crate::TokenId;
4+
use crate::enumeration::MultiTokenEnumeration;
5+
use crate::metadata::adapters::As;
6+
use borsh::schema::{Declaration, Definition};
7+
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
8+
use chrono::{DateTime, Utc};
9+
use defuse_borsh_utils::adapters;
10+
use near_sdk::near;
11+
use near_sdk::serde::{Deserialize, Serialize};
12+
use schemars::JsonSchema;
13+
use schemars::r#gen::SchemaGenerator;
14+
use schemars::schema::Schema;
15+
use serde_with::serde_as;
16+
use serde_with::skip_serializing_none;
17+
use std::collections::BTreeMap;
18+
19+
pub type MetadataId = String;
20+
21+
#[derive(Debug, Clone)]
22+
#[near(serializers = [json, borsh])]
23+
pub struct MTContractMetadata {
24+
pub spec: String, // "a string that MUST be formatted mt-1.0.0" or whatever the spec version is used.
25+
pub name: String,
26+
}
27+
28+
#[derive(Debug, Clone)]
29+
#[skip_serializing_none]
30+
#[near(serializers = [json, borsh])]
31+
pub struct MTBaseTokenMetadata {
32+
/// Human‐readable name of the base (e.g., "Silver Swords" or "Metaverse 3")
33+
pub name: String,
34+
35+
/// Unique identifier for this metadata entry
36+
pub id: MetadataId,
37+
38+
/// Abbreviated symbol for the token (e.g., "MOCHI"), or `None` if unset
39+
pub symbol: Option<String>,
40+
41+
/// Data URL for a small icon image, or `None`
42+
pub icon: Option<String>,
43+
44+
/// Number of decimals (useful if this base represents an FT‐style token), or `None`
45+
pub decimals: Option<u8>,
46+
47+
/// Centralized gateway URL for reliably accessing decentralized storage assets referenced by `reference` or `media`, or `None`
48+
pub base_uri: Option<String>,
49+
50+
/// URL pointing to a JSON file with additional info, or `None`
51+
pub reference: Option<String>,
52+
53+
/// Number of copies of this set of metadata that existed when the token was minted, or `None`
54+
pub copies: Option<u64>,
55+
56+
/// Base64‐encoded SHA-256 hash of the JSON from `reference`; required if `reference` is set, or `None`
57+
pub reference_hash: Option<String>,
58+
}
59+
60+
#[derive(Debug, Clone)]
61+
#[skip_serializing_none]
62+
#[near(serializers = [json, borsh])]
63+
pub struct MTTokenMetadata {
64+
/// Title of this specific token (e.g., "Arch Nemesis: Mail Carrier" or "Parcel #5055"), or `None`
65+
pub title: Option<String>,
66+
67+
/// Free-form description of this token, or `None`
68+
pub description: Option<String>,
69+
70+
/// URL to associated media (ideally decentralized, content-addressed storage), or `None`
71+
pub media: Option<String>,
72+
73+
/// Base64‐encoded SHA-256 hash of the content referenced by `media`; required if `media` is set, or `None`
74+
pub media_hash: Option<String>,
75+
76+
/// Unix epoch in milliseconds or RFC3339 when this token was issued or minted, or `None`
77+
pub issued_at: Option<DatetimeUtcWrapper>,
78+
79+
/// Unix epoch in milliseconds or RFC3339 when this token expires, or `None`
80+
pub expires_at: Option<DatetimeUtcWrapper>,
81+
82+
/// Unix epoch in milliseconds or RFC3339 when this token starts being valid, or `None`
83+
pub starts_at: Option<DatetimeUtcWrapper>,
84+
85+
/// Unix epoch in milliseconds or RFC3339 when this token metadata was last updated, or `None`
86+
pub updated_at: Option<DatetimeUtcWrapper>,
87+
88+
/// Anything extra the MT wants to store on-chain (can be stringified JSON), or `None`
89+
pub extra: Option<String>,
90+
91+
/// URL to an off-chain JSON file with more info, or `None`
92+
pub reference: Option<String>,
93+
94+
/// Base64‐encoded SHA-256 hash of the JSON from `reference`; required if `reference` is set, or `None`
95+
pub reference_hash: Option<String>,
96+
}
97+
98+
#[derive(Debug, Clone)]
99+
#[near(serializers = [json, borsh])]
100+
pub struct MTTokenMetadataAll {
101+
pub base: MTBaseTokenMetadata,
102+
pub token: MTTokenMetadata,
103+
}
104+
105+
pub trait MultiTokenMetadata {
106+
/// Returns the contract‐level metadata (spec + name).
107+
fn mt_metadata_contract(&self) -> MTContractMetadata;
108+
109+
/// For a list of `token_ids`, returns a vector of combined `(base, token)` metadata.
110+
fn mt_metadata_token_all(&self, token_ids: Vec<TokenId>) -> Vec<Option<MTTokenMetadataAll>>;
111+
112+
/// Given `token_ids`, returns each token’s `MTTokenMetadata` or `None` if absent.
113+
fn mt_metadata_token_by_token_id(
114+
&self,
115+
token_ids: Vec<TokenId>,
116+
) -> Vec<Option<MTTokenMetadata>>;
117+
118+
/// Given `token_ids`, returns each token’s `MTBaseTokenMetadata` or `None` if absent.
119+
fn mt_metadata_base_by_token_id(
120+
&self,
121+
token_ids: Vec<TokenId>,
122+
) -> Vec<Option<MTBaseTokenMetadata>>;
123+
124+
/// Given a list of `base_metadata_ids`, returns each `MTBaseTokenMetadata` or `None` if absent.
125+
fn mt_metadata_base_by_metadata_id(
126+
&self,
127+
base_metadata_ids: Vec<MetadataId>,
128+
) -> Vec<Option<MTBaseTokenMetadata>>;
129+
}
130+
131+
/// The contract must implement the following view method if using [multi-token enumeration standard](https://nomicon.io/Standards/Tokens/MultiToken/Enumeration#interface).
132+
pub trait MultiTokenMetadataEnumeration: MultiTokenMetadata + MultiTokenEnumeration {
133+
/// Get list of all base metadata for the contract, with pagination.
134+
///
135+
/// # Arguments
136+
/// * `from_index`: an optional string representing an unsigned 128-bit integer,
137+
/// indicating the starting index
138+
/// * `limit`: an optional u64 indicating the maximum number of entries to return
139+
///
140+
/// # Returns
141+
/// A vector of `MTBaseTokenMetadata` objects, or an empty vector if none.
142+
fn mt_tokens_base_metadata_all(
143+
&self,
144+
from_index: Option<String>,
145+
limit: Option<u64>,
146+
) -> Vec<MTBaseTokenMetadata>;
147+
}
148+
149+
/// A wrapper that implements Borsh de-/serialization for `Datetime<Utc>`
150+
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
151+
#[serde(crate = "::near_sdk::serde")]
152+
#[serde_as]
153+
pub struct DatetimeUtcWrapper(
154+
#[serde_as(as = "PickFirst<(_, serde_with::TimestampMilliSeconds)>")]
155+
#[borsh(
156+
deserialize_with = "As::<adapters::TimestampMilliSeconds>::deserialize",
157+
serialize_with = "As::<adapters::TimestampMilliSeconds>::serialize"
158+
)]
159+
pub DateTime<Utc>,
160+
);
161+
162+
impl JsonSchema for DatetimeUtcWrapper {
163+
fn schema_name() -> String {
164+
"DatetimeUtcWrapper".to_owned()
165+
}
166+
167+
fn json_schema(generator: &mut SchemaGenerator) -> Schema {
168+
generator.subschema_for::<u64>()
169+
}
170+
}
171+
172+
impl BorshSchema for DatetimeUtcWrapper {
173+
fn add_definitions_recursively(definitions: &mut BTreeMap<Declaration, Definition>) {
174+
<u64 as BorshSchema>::add_definitions_recursively(definitions);
175+
}
176+
177+
fn declaration() -> Declaration {
178+
<u64 as BorshSchema>::declaration()
179+
}
180+
}
181+
182+
#[cfg(test)]
183+
mod tests {
184+
use crate::metadata::DatetimeUtcWrapper;
185+
use chrono::DateTime;
186+
use hex::FromHex;
187+
use near_sdk::borsh;
188+
189+
#[test]
190+
fn test_datetime_utc_wrapper_borsh() {
191+
let timestamp = DateTime::from_timestamp(1747772412, 0).unwrap();
192+
let wrapped = DatetimeUtcWrapper(timestamp);
193+
let encoded = borsh::to_vec(&wrapped).unwrap();
194+
assert_eq!(encoded, Vec::from_hex("60905aef96010000").unwrap());
195+
let actual_wrapped: DatetimeUtcWrapper = borsh::from_slice(encoded.as_slice()).unwrap();
196+
assert_eq!(actual_wrapped.0, wrapped.0);
197+
}
198+
}

0 commit comments

Comments
 (0)