Skip to content

Commit

Permalink
feat: add metadata and metadata key to update Nfts and Fungible tokens
Browse files Browse the repository at this point in the history
- Add HIP-646: Fungible Token Metadata Field #614
- Add HIP-765: Non-Fungible Token Metadata Field #763
- Add HIP-657 Mutable metadata fields for dynamic NFTs #612
- Solve backward compatibility issues with hedera-services v0.48.0 #766
  • Loading branch information
RickyLB authored Apr 11, 2024
2 parents de49a73 + bcb2010 commit 61067f0
Show file tree
Hide file tree
Showing 35 changed files with 2,914 additions and 1,495 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/rust-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ jobs:
repo-token: ${{ secrets.GITHUB_TOKEN }}

- name: Start the local node
run: npx @hashgraph/hedera-local start -d --network local
run: npx @hashgraph/hedera-local start -d --network local --network-tag=0.48.0-alpha.12

- name: "Create env file"
run: |
Expand Down
134 changes: 134 additions & 0 deletions examples/token_update_nfts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* ‌
* Hedera Rust SDK
* ​
* Copyright (C) 2022 - 2023 Hedera Hashgraph, LLC
* ​
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ‍
*/

use std::iter::repeat;

use clap::Parser;
use futures_util::{stream, StreamExt, TryStreamExt};
use hedera::{
AccountId, Client, NftId, PrivateKey, TokenCreateTransaction, TokenId, TokenMintTransaction, TokenNftInfoQuery, TokenType, TokenUpdateNftsTransaction
};
use time::{Duration, OffsetDateTime};

#[derive(Parser, Debug)]
struct Args {
#[clap(long, env)]
operator_account_id: AccountId,

#[clap(long, env)]
operator_key: PrivateKey,

#[clap(long, env, default_value = "localnode")]
hedera_network: String,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
let _ = dotenvy::dotenv();

let args = Args::parse();

let client = Client::for_name(&args.hedera_network)?;

client.set_operator(args.operator_account_id, args.operator_key);

let metadata_key = PrivateKey::generate_ed25519();
let nft_count = 4;
let initial_metadata_list: Vec<Vec<u8>> = repeat(vec![9, 1, 6]).take(nft_count).collect();
let updated_metadata: Vec<u8> = vec![3, 4];

let token_id = TokenCreateTransaction::new()
.name("ffff")
.symbol("F")
.token_type(TokenType::NonFungibleUnique)
.treasury_account_id(client.get_operator_account_id().unwrap())
.admin_key(client.get_operator_public_key().unwrap())
.supply_key(client.get_operator_public_key().unwrap())
.metadata_key(metadata_key.public_key())
.expiration_time(OffsetDateTime::now_utc() + Duration::minutes(5))
.execute(&client)
.await?
.get_receipt(&client)
.await?
.token_id
.unwrap();

// Mint the token
let serials = TokenMintTransaction::new()
.metadata(initial_metadata_list.clone())
.token_id(token_id)
.execute(&client)
.await?
.get_receipt(&client)
.await?
.serials;

println!(
"Metadata after mint= {:?}",
get_metadata_list(&client, &token_id, &serials).await?
);

let serials = TokenUpdateNftsTransaction::new()
.token_id(token_id)
.serials(serials.into_iter().take(2).collect())
.metadata(updated_metadata)
.sign(metadata_key)
.execute(&client)
.await?
.get_receipt(&client)
.await?
.serials;

// Check if metadata has updated correctly
println!(
"Metadata after mint= {:?}",
get_metadata_list(&client, &token_id, &serials).await?
);

Ok(())
}

async fn get_metadata_list(
client: &Client,
token_id: &TokenId,
serials: &Vec<i64>,
) -> anyhow::Result<Vec<Vec<u8>>> {
let list = stream::iter(serials.into_iter().map(|it| NftId {
token_id: token_id.to_owned(),
serial: *it as u64,
}))
.then(|nft_id| {
let client_clone = client;
async move {
match TokenNftInfoQuery::new()
.nft_id(nft_id)
.execute(&client_clone)
.await
{
Ok(info) => Ok(info.metadata),
Err(err) => anyhow::bail!("error calling TokenNftInfoQuery: {err}"), // CHANGE ERROR MESSAGE
}
}
})
.try_collect::<Vec<_>>()
.await?;

Ok(list)
}
1 change: 1 addition & 0 deletions protobufs/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ fn main() -> anyhow::Result<()> {
.services_same("TokenUnfreezeAccountTransactionBody")
.services_same("TokenUnpauseTransactionBody")
.services_same("TokenUpdateTransactionBody")
.services_same("TokenUpdateNftsTransactionBody")
.services_same("TokenWipeAccountTransactionBody")
.services_same("Transaction")
.services_same("TransactionBody")
Expand Down
2 changes: 1 addition & 1 deletion protobufs/protobufs
Submodule protobufs updated 38 files
+8 −0 README.md
+62 −0 platform/event/event_payload.proto
+48 −0 platform/event/event_payloads.proto
+70 −0 platform/event/state_signature_payload.proto
+41 −0 services/basic_types.proto
+1 −1 services/contract_call_local.proto
+0 −1 services/crypto_get_account_balance.proto
+0 −1 services/crypto_get_info.proto
+0 −1 services/freeze.proto
+19 −0 services/response_code.proto
+0 −1 services/response_header.proto
+6 −0 services/schedulable_transaction_body.proto
+67 −0 services/state/blockrecords/block_info.proto
+50 −0 services/state/blockrecords/running_hashes.proto
+43 −0 services/state/common.proto
+41 −0 services/state/congestion/congestion_level_starts.proto
+6 −6 services/state/consensus/topic.proto
+37 −0 services/state/contract/bytecode.proto
+70 −0 services/state/contract/storage_slot.proto
+67 −0 services/state/file/file.proto
+65 −0 services/state/primitives.proto
+55 −0 services/state/recordcache/recordcache.proto
+148 −0 services/state/schedule/schedule.proto
+52 −0 services/state/throttles/throttle_usage_snapshots.proto
+236 −0 services/state/token/account.proto
+62 −0 services/state/token/network_staking_rewards.proto
+77 −0 services/state/token/nft.proto
+102 −0 services/state/token/staking_node_info.proto
+175 −0 services/state/token/token.proto
+69 −0 services/state/token/token_relation.proto
+11 −0 services/token_create.proto
+11 −0 services/token_get_info.proto
+0 −15 services/token_get_nft_info.proto
+5 −0 services/token_service.proto
+18 −0 services/token_update.proto
+61 −0 services/token_update_nfts.proto
+1 −3 services/transaction.proto
+6 −0 services/transaction_body.proto
2 changes: 1 addition & 1 deletion src/contract/contract_function_result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ pub struct ContractFunctionResult {
pub contract_nonces: Vec<ContractNonceInfo>,

/// If not null this field specifies what the value of the signer account nonce is post transaction execution.
/// For transactions that don't update the signer nonce (like HAPI ContractCall and ContractCreate transactions) this field should be null.
/// For transactions that don't update the signer nonce, this field should be null.
pub signer_nonce: Option<u64>,
}

Expand Down
10 changes: 10 additions & 0 deletions src/fee_schedules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,12 @@ pub enum RequestType {

/// Execute a PRNG transaction.
UtilPrng,

/// Get a record for a transaction.
TransactionGetFastRecord,

/// Update the metadata of one or more NFT's of a specific token type.
TokenUpdateNfts,
}

impl FromProtobuf<services::HederaFunctionality> for RequestType {
Expand Down Expand Up @@ -482,6 +488,8 @@ impl FromProtobuf<services::HederaFunctionality> for RequestType {
HederaFunctionality::EthereumTransaction => Self::EthereumTransaction,
HederaFunctionality::NodeStakeUpdate => Self::NodeStakeUpdate,
HederaFunctionality::UtilPrng => Self::UtilPrng,
HederaFunctionality::TransactionGetFastRecord => Self::TransactionGetFastRecord,
HederaFunctionality::TokenUpdateNfts => Self::TokenUpdateNfts,
};

Ok(value)
Expand Down Expand Up @@ -567,6 +575,8 @@ impl ToProtobuf for RequestType {
Self::EthereumTransaction => HederaFunctionality::EthereumTransaction,
Self::NodeStakeUpdate => HederaFunctionality::NodeStakeUpdate,
Self::UtilPrng => HederaFunctionality::UtilPrng,
Self::TransactionGetFastRecord => HederaFunctionality::TransactionGetFastRecord,
Self::TokenUpdateNfts => HederaFunctionality::TokenUpdateNfts,
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ pub use token::{
TokenType,
TokenUnfreezeTransaction,
TokenUnpauseTransaction,
TokenUpdateNftsTransaction,
TokenUpdateTransaction,
TokenWipeTransaction,
};
Expand Down
11 changes: 11 additions & 0 deletions src/schedule/schedulable_transaction_body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ mod data {
TokenRevokeKycTransactionData as TokenRevokeKyc,
TokenUnfreezeTransactionData as TokenUnfreeze,
TokenUnpauseTransactionData as TokenUnpause,
TokenUpdateNftsTransactionData as TokenUpdateNfts,
TokenUpdateTransactionData as TokenUpdate,
TokenWipeTransactionData as TokenWipe,
};
Expand Down Expand Up @@ -136,6 +137,7 @@ pub(super) enum AnySchedulableTransactionData {
SystemUndelete(data::SystemUndelete),
Freeze(data::Freeze),
ScheduleDelete(data::ScheduleDelete),
TokenUpdateNfts(data::TokenUpdateNfts),
}

impl AnySchedulableTransactionData {
Expand Down Expand Up @@ -176,6 +178,7 @@ impl AnySchedulableTransactionData {
AnySchedulableTransactionData::TokenFreeze(it) => it.default_max_transaction_fee(),
AnySchedulableTransactionData::TokenGrantKyc(it) => it.default_max_transaction_fee(),
AnySchedulableTransactionData::TokenMint(it) => it.default_max_transaction_fee(),
AnySchedulableTransactionData::TokenUpdateNfts(it) => it.default_max_transaction_fee(),
AnySchedulableTransactionData::TokenPause(it) => it.default_max_transaction_fee(),
AnySchedulableTransactionData::TokenRevokeKyc(it) => it.default_max_transaction_fee(),
AnySchedulableTransactionData::TokenUnfreeze(it) => it.default_max_transaction_fee(),
Expand Down Expand Up @@ -281,6 +284,9 @@ impl FromProtobuf<services::schedulable_transaction_body::Data> for AnySchedulab
Ok(Self::ScheduleDelete(data::ScheduleDelete::from_protobuf(it)?))
}
Data::UtilPrng(it) => Ok(Self::Prng(data::Prng::from_protobuf(it)?)),
Data::TokenUpdateNfts(it) => {
Ok(Self::TokenUpdateNfts(data::TokenUpdateNfts::from_protobuf(it)?))
}
}
}
}
Expand Down Expand Up @@ -405,6 +411,9 @@ impl ToSchedulableTransactionDataProtobuf for AnySchedulableTransactionData {
AnySchedulableTransactionData::Prng(it) => {
it.to_schedulable_transaction_data_protobuf()
}
AnySchedulableTransactionData::TokenUpdateNfts(it) => {
it.to_schedulable_transaction_data_protobuf()
}
}
}
}
Expand Down Expand Up @@ -454,6 +463,7 @@ impl TryFrom<AnyTransactionData> for AnySchedulableTransactionData {
AnyTransactionData::Freeze(it) => Ok(Self::Freeze(it)),
AnyTransactionData::ScheduleDelete(it) => Ok(Self::ScheduleDelete(it)),
AnyTransactionData::Prng(it) => Ok(Self::Prng(it)),
AnyTransactionData::TokenUpdateNfts(it) => Ok(Self::TokenUpdateNfts(it)),

// fixme: basic-parse isn't suitable for this.
AnyTransactionData::ScheduleCreate(_) => {
Expand Down Expand Up @@ -516,6 +526,7 @@ impl From<AnySchedulableTransactionData> for AnyTransactionData {
AnySchedulableTransactionData::Freeze(it) => Self::Freeze(it),
AnySchedulableTransactionData::ScheduleDelete(it) => Self::ScheduleDelete(it),
AnySchedulableTransactionData::Prng(it) => Self::Prng(it),
AnySchedulableTransactionData::TokenUpdateNfts(it) => Self::TokenUpdateNfts(it),
}
}
}
Loading

0 comments on commit 61067f0

Please sign in to comment.