Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NFT Events Standard #652

Closed
wants to merge 11 commits into from
1 change: 1 addition & 0 deletions near-contract-standards/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ NEAR smart contracts standard library.

[dependencies]
near-sdk = { path = "../near-sdk", version = "=4.0.0-pre.4" }
serde_json = "1.0"
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use super::resolver::NonFungibleTokenResolver;
use crate::non_fungible_token::core::NonFungibleTokenCore;
use crate::non_fungible_token::metadata::TokenMetadata;
use crate::non_fungible_token::events::NftMintLog;
use crate::non_fungible_token::events::{EventLog, EventLogVariant, NftTransferLog};
use crate::non_fungible_token::metadata::{TokenMetadata, NFT_METADATA_SPEC, NFT_STANDARD_NAME};
use crate::non_fungible_token::token::{Token, TokenId};
use crate::non_fungible_token::utils::{
hash_account_id, refund_approved_account_ids, refund_deposit,
Expand Down Expand Up @@ -272,11 +274,42 @@ impl NonFungibleToken {
self.internal_transfer_unguarded(token_id, &owner_id, receiver_id);

log!("Transfer {} from {} to {}", token_id, sender_id, receiver_id);
if let Some(memo) = memo {
if let Some(memo) = memo.as_ref() {
log!("Memo: {}", memo);
}

// return previous owner & approvals
// Default the authorized ID to be None for the logs.
let mut authorized_id = None;
//if the approval ID was provided, set the authorized ID equal to the sender
if approval_id.is_some() {
authorized_id = Some(sender_id.to_string());
}

// Construct the mint log as per the events standard.
let nft_transfer_log: EventLog = EventLog {
// Standard name ("nep171").
standard: NFT_STANDARD_NAME.to_string(),
// Version of the standard ("nft-1.0.0").
version: NFT_METADATA_SPEC.to_string(),
// The data related with the event stored in a vector.
event: EventLogVariant::NftTransfer(vec![NftTransferLog {
// The optional authorized account ID to transfer the token on behalf of the old owner.
authorized_id,
// The old owner's account ID.
old_owner_id: owner_id.to_string(),
// The account ID of the new owner of the token.
new_owner_id: receiver_id.to_string(),
// A vector containing the token IDs as strings.
token_ids: vec![token_id.to_string()],
// An optional memo to include.
memo,
}]),
};

// Log the serialized json.
env::log_str(&nft_transfer_log.to_string());

// Return previous owner & approvals
(owner_id, approved_account_ids)
}

Expand Down Expand Up @@ -346,7 +379,26 @@ impl NonFungibleToken {
let approved_account_ids =
if self.approvals_by_id.is_some() { Some(HashMap::new()) } else { None };

// Return any extra attached deposit not used for storage
// Construct the mint log as per the events standard.
let nft_mint_log: EventLog = EventLog {
// Standard name ("nep171").
standard: NFT_STANDARD_NAME.to_string(),
// Version of the standard ("nft-1.0.0").
version: NFT_METADATA_SPEC.to_string(),
// The data related with the event stored in a vector.
event: EventLogVariant::NftMint(vec![NftMintLog {
// Owner of the token.
owner_id: owner_id.to_string(),
// Vector of token IDs that were minted.
token_ids: vec![token_id.to_string()],
// An optional memo to include.
memo: None,
}]),
};

// Log the serialized json.
env::log_str(&nft_mint_log.to_string());
// Return any extra attached deposit not used for storage.
refund_deposit(env::storage_usage() - initial_storage_usage);

Token { token_id, owner_id, metadata: token_metadata, approved_account_ids }
Expand Down
138 changes: 138 additions & 0 deletions near-contract-standards/src/non_fungible_token/events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use std::fmt;

use near_sdk::serde::{Deserialize, Serialize};

/// Enum that represents the data type of the EventLog.
/// The enum can either be an NftMint or an NftTransfer.
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "event", content = "data")]
#[serde(rename_all = "snake_case")]
#[serde(crate = "near_sdk::serde")]
#[non_exhaustive]
pub enum EventLogVariant {
NftMint(Vec<NftMintLog>),
NftTransfer(Vec<NftTransferLog>),
}

/// Interface to capture data about an event
///
/// Arguments:
/// * `standard`: name of standard e.g. nep171
/// * `version`: e.g. 1.0.0
/// * `event`: associate event data
#[derive(Serialize, Deserialize, Debug)]
#[serde(crate = "near_sdk::serde")]
pub struct EventLog {
pub standard: String,
pub version: String,

// `flatten` to not have "event": {<EventLogVariant>} in the JSON, just have the contents of {<EventLogVariant>}.
#[serde(flatten)]
pub event: EventLogVariant,
}

impl fmt::Display for EventLog {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_fmt(format_args!(
"EVENT_JSON:{}",
&serde_json::to_string(self).map_err(|_| fmt::Error)?
))
}
}

/// An event log to capture token minting
///
/// Arguments
/// * `owner_id`: "account.near"
/// * `token_ids`: ["1", "abc"]
/// * `memo`: optional message
#[derive(Serialize, Deserialize, Debug)]
#[serde(crate = "near_sdk::serde")]
pub struct NftMintLog {
pub owner_id: String,
pub token_ids: Vec<String>,

#[serde(skip_serializing_if = "Option::is_none")]
pub memo: Option<String>,
}

/// An event log to capture token transfer
///
/// Arguments
/// * `authorized_id`: approved account to transfer
/// * `old_owner_id`: "owner.near"
/// * `new_owner_id`: "receiver.near"
/// * `token_ids`: ["1", "12345abc"]
/// * `memo`: optional message
#[derive(Serialize, Deserialize, Debug)]
#[serde(crate = "near_sdk::serde")]
pub struct NftTransferLog {
#[serde(skip_serializing_if = "Option::is_none")]
pub authorized_id: Option<String>,

pub old_owner_id: String,
pub new_owner_id: String,
pub token_ids: Vec<String>,

#[serde(skip_serializing_if = "Option::is_none")]
pub memo: Option<String>,
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn nep_format_vector() {
let expected = r#"EVENT_JSON:{"standard":"nep171","version":"1.0.0","event":"nft_mint","data":[{"owner_id":"foundation.near","token_ids":["aurora","proximitylabs"]},{"owner_id":"user1.near","token_ids":["meme"]}]}"#;
let log = EventLog {
standard: "nep171".to_string(),
version: "1.0.0".to_string(),
event: EventLogVariant::NftMint(vec![
NftMintLog {
owner_id: "foundation.near".to_owned(),
token_ids: vec!["aurora".to_string(), "proximitylabs".to_string()],
memo: None,
},
NftMintLog {
owner_id: "user1.near".to_owned(),
token_ids: vec!["meme".to_string()],
memo: None,
},
]),
};
assert_eq!(expected, log.to_string());
}

#[test]
fn nep_format_mint() {
let expected = r#"EVENT_JSON:{"standard":"nep171","version":"1.0.0","event":"nft_mint","data":[{"owner_id":"foundation.near","token_ids":["aurora","proximitylabs"]}]}"#;
let log = EventLog {
standard: "nep171".to_string(),
version: "1.0.0".to_string(),
event: EventLogVariant::NftMint(vec![NftMintLog {
owner_id: "foundation.near".to_owned(),
token_ids: vec!["aurora".to_string(), "proximitylabs".to_string()],
memo: None,
}]),
};
assert_eq!(expected, log.to_string());
}

#[test]
fn nep_format_transfer_all_fields() {
let expected = r#"EVENT_JSON:{"standard":"nep171","version":"1.0.0","event":"nft_transfer","data":[{"authorized_id":"market.near","old_owner_id":"user1.near","new_owner_id":"user2.near","token_ids":["token"],"memo":"Go Team!"}]}"#;
let log = EventLog {
standard: "nep171".to_string(),
version: "1.0.0".to_string(),
event: EventLogVariant::NftTransfer(vec![NftTransferLog {
authorized_id: Some("market.near".to_string()),
old_owner_id: "user1.near".to_string(),
new_owner_id: "user2.near".to_string(),
token_ids: vec!["token".to_string()],
memo: Some("Go Team!".to_owned()),
}]),
};
assert_eq!(expected, log.to_string());
}
}
2 changes: 2 additions & 0 deletions near-contract-standards/src/non_fungible_token/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use near_sdk::serde::{Deserialize, Serialize};

/// This spec can be treated like a version of the standard.
pub const NFT_METADATA_SPEC: &str = "nft-1.0.0";
/// This is the name of the NFT standard we're using
pub const NFT_STANDARD_NAME: &str = "nep171";

/// Metadata for the NFT contract itself.
#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Clone, Debug, PartialEq)]
Expand Down
3 changes: 3 additions & 0 deletions near-contract-standards/src/non_fungible_token/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ pub mod core;
/// Trait for the [NFT enumeration standard](https://nomicon.io/Standards/NonFungibleToken/Enumeration.html).
/// This provides useful view-only methods returning token supply, tokens by owner, etc.
pub mod enumeration;
/// Events traits according to the [Events standard](https://nomicon.io/Standards/NonFungibleToken/Event.html)
/// this covers the EventLogJson and the data structures for mint and tranfer logs
pub mod events;
/// Macros typically used by a contract wanting to take advantage of the non-fungible
/// token NEAR contract standard approach.
mod macros;
Expand Down