Skip to content

Commit 61067f0

Browse files
authored
feat: add metadata and metadata key to update Nfts and Fungible tokens
- 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
2 parents de49a73 + bcb2010 commit 61067f0

35 files changed

+2914
-1495
lines changed

.github/workflows/rust-ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ jobs:
5757
repo-token: ${{ secrets.GITHUB_TOKEN }}
5858

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

6262
- name: "Create env file"
6363
run: |

examples/token_update_nfts.rs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/*
2+
* ‌
3+
* Hedera Rust SDK
4+
* ​
5+
* Copyright (C) 2022 - 2023 Hedera Hashgraph, LLC
6+
* ​
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* ‍
19+
*/
20+
21+
use std::iter::repeat;
22+
23+
use clap::Parser;
24+
use futures_util::{stream, StreamExt, TryStreamExt};
25+
use hedera::{
26+
AccountId, Client, NftId, PrivateKey, TokenCreateTransaction, TokenId, TokenMintTransaction, TokenNftInfoQuery, TokenType, TokenUpdateNftsTransaction
27+
};
28+
use time::{Duration, OffsetDateTime};
29+
30+
#[derive(Parser, Debug)]
31+
struct Args {
32+
#[clap(long, env)]
33+
operator_account_id: AccountId,
34+
35+
#[clap(long, env)]
36+
operator_key: PrivateKey,
37+
38+
#[clap(long, env, default_value = "localnode")]
39+
hedera_network: String,
40+
}
41+
42+
#[tokio::main]
43+
async fn main() -> anyhow::Result<()> {
44+
let _ = dotenvy::dotenv();
45+
46+
let args = Args::parse();
47+
48+
let client = Client::for_name(&args.hedera_network)?;
49+
50+
client.set_operator(args.operator_account_id, args.operator_key);
51+
52+
let metadata_key = PrivateKey::generate_ed25519();
53+
let nft_count = 4;
54+
let initial_metadata_list: Vec<Vec<u8>> = repeat(vec![9, 1, 6]).take(nft_count).collect();
55+
let updated_metadata: Vec<u8> = vec![3, 4];
56+
57+
let token_id = TokenCreateTransaction::new()
58+
.name("ffff")
59+
.symbol("F")
60+
.token_type(TokenType::NonFungibleUnique)
61+
.treasury_account_id(client.get_operator_account_id().unwrap())
62+
.admin_key(client.get_operator_public_key().unwrap())
63+
.supply_key(client.get_operator_public_key().unwrap())
64+
.metadata_key(metadata_key.public_key())
65+
.expiration_time(OffsetDateTime::now_utc() + Duration::minutes(5))
66+
.execute(&client)
67+
.await?
68+
.get_receipt(&client)
69+
.await?
70+
.token_id
71+
.unwrap();
72+
73+
// Mint the token
74+
let serials = TokenMintTransaction::new()
75+
.metadata(initial_metadata_list.clone())
76+
.token_id(token_id)
77+
.execute(&client)
78+
.await?
79+
.get_receipt(&client)
80+
.await?
81+
.serials;
82+
83+
println!(
84+
"Metadata after mint= {:?}",
85+
get_metadata_list(&client, &token_id, &serials).await?
86+
);
87+
88+
let serials = TokenUpdateNftsTransaction::new()
89+
.token_id(token_id)
90+
.serials(serials.into_iter().take(2).collect())
91+
.metadata(updated_metadata)
92+
.sign(metadata_key)
93+
.execute(&client)
94+
.await?
95+
.get_receipt(&client)
96+
.await?
97+
.serials;
98+
99+
// Check if metadata has updated correctly
100+
println!(
101+
"Metadata after mint= {:?}",
102+
get_metadata_list(&client, &token_id, &serials).await?
103+
);
104+
105+
Ok(())
106+
}
107+
108+
async fn get_metadata_list(
109+
client: &Client,
110+
token_id: &TokenId,
111+
serials: &Vec<i64>,
112+
) -> anyhow::Result<Vec<Vec<u8>>> {
113+
let list = stream::iter(serials.into_iter().map(|it| NftId {
114+
token_id: token_id.to_owned(),
115+
serial: *it as u64,
116+
}))
117+
.then(|nft_id| {
118+
let client_clone = client;
119+
async move {
120+
match TokenNftInfoQuery::new()
121+
.nft_id(nft_id)
122+
.execute(&client_clone)
123+
.await
124+
{
125+
Ok(info) => Ok(info.metadata),
126+
Err(err) => anyhow::bail!("error calling TokenNftInfoQuery: {err}"), // CHANGE ERROR MESSAGE
127+
}
128+
}
129+
})
130+
.try_collect::<Vec<_>>()
131+
.await?;
132+
133+
Ok(list)
134+
}

protobufs/build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ fn main() -> anyhow::Result<()> {
211211
.services_same("TokenUnfreezeAccountTransactionBody")
212212
.services_same("TokenUnpauseTransactionBody")
213213
.services_same("TokenUpdateTransactionBody")
214+
.services_same("TokenUpdateNftsTransactionBody")
214215
.services_same("TokenWipeAccountTransactionBody")
215216
.services_same("Transaction")
216217
.services_same("TransactionBody")

protobufs/protobufs

src/contract/contract_function_result.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ pub struct ContractFunctionResult {
7777
pub contract_nonces: Vec<ContractNonceInfo>,
7878

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

src/fee_schedules.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,12 @@ pub enum RequestType {
403403

404404
/// Execute a PRNG transaction.
405405
UtilPrng,
406+
407+
/// Get a record for a transaction.
408+
TransactionGetFastRecord,
409+
410+
/// Update the metadata of one or more NFT's of a specific token type.
411+
TokenUpdateNfts,
406412
}
407413

408414
impl FromProtobuf<services::HederaFunctionality> for RequestType {
@@ -482,6 +488,8 @@ impl FromProtobuf<services::HederaFunctionality> for RequestType {
482488
HederaFunctionality::EthereumTransaction => Self::EthereumTransaction,
483489
HederaFunctionality::NodeStakeUpdate => Self::NodeStakeUpdate,
484490
HederaFunctionality::UtilPrng => Self::UtilPrng,
491+
HederaFunctionality::TransactionGetFastRecord => Self::TransactionGetFastRecord,
492+
HederaFunctionality::TokenUpdateNfts => Self::TokenUpdateNfts,
485493
};
486494

487495
Ok(value)
@@ -567,6 +575,8 @@ impl ToProtobuf for RequestType {
567575
Self::EthereumTransaction => HederaFunctionality::EthereumTransaction,
568576
Self::NodeStakeUpdate => HederaFunctionality::NodeStakeUpdate,
569577
Self::UtilPrng => HederaFunctionality::UtilPrng,
578+
Self::TransactionGetFastRecord => HederaFunctionality::TransactionGetFastRecord,
579+
Self::TokenUpdateNfts => HederaFunctionality::TokenUpdateNfts,
570580
}
571581
}
572582
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ pub use token::{
330330
TokenType,
331331
TokenUnfreezeTransaction,
332332
TokenUnpauseTransaction,
333+
TokenUpdateNftsTransaction,
333334
TokenUpdateTransaction,
334335
TokenWipeTransaction,
335336
};

src/schedule/schedulable_transaction_body.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ mod data {
4949
TokenRevokeKycTransactionData as TokenRevokeKyc,
5050
TokenUnfreezeTransactionData as TokenUnfreeze,
5151
TokenUnpauseTransactionData as TokenUnpause,
52+
TokenUpdateNftsTransactionData as TokenUpdateNfts,
5253
TokenUpdateTransactionData as TokenUpdate,
5354
TokenWipeTransactionData as TokenWipe,
5455
};
@@ -136,6 +137,7 @@ pub(super) enum AnySchedulableTransactionData {
136137
SystemUndelete(data::SystemUndelete),
137138
Freeze(data::Freeze),
138139
ScheduleDelete(data::ScheduleDelete),
140+
TokenUpdateNfts(data::TokenUpdateNfts),
139141
}
140142

141143
impl AnySchedulableTransactionData {
@@ -176,6 +178,7 @@ impl AnySchedulableTransactionData {
176178
AnySchedulableTransactionData::TokenFreeze(it) => it.default_max_transaction_fee(),
177179
AnySchedulableTransactionData::TokenGrantKyc(it) => it.default_max_transaction_fee(),
178180
AnySchedulableTransactionData::TokenMint(it) => it.default_max_transaction_fee(),
181+
AnySchedulableTransactionData::TokenUpdateNfts(it) => it.default_max_transaction_fee(),
179182
AnySchedulableTransactionData::TokenPause(it) => it.default_max_transaction_fee(),
180183
AnySchedulableTransactionData::TokenRevokeKyc(it) => it.default_max_transaction_fee(),
181184
AnySchedulableTransactionData::TokenUnfreeze(it) => it.default_max_transaction_fee(),
@@ -281,6 +284,9 @@ impl FromProtobuf<services::schedulable_transaction_body::Data> for AnySchedulab
281284
Ok(Self::ScheduleDelete(data::ScheduleDelete::from_protobuf(it)?))
282285
}
283286
Data::UtilPrng(it) => Ok(Self::Prng(data::Prng::from_protobuf(it)?)),
287+
Data::TokenUpdateNfts(it) => {
288+
Ok(Self::TokenUpdateNfts(data::TokenUpdateNfts::from_protobuf(it)?))
289+
}
284290
}
285291
}
286292
}
@@ -405,6 +411,9 @@ impl ToSchedulableTransactionDataProtobuf for AnySchedulableTransactionData {
405411
AnySchedulableTransactionData::Prng(it) => {
406412
it.to_schedulable_transaction_data_protobuf()
407413
}
414+
AnySchedulableTransactionData::TokenUpdateNfts(it) => {
415+
it.to_schedulable_transaction_data_protobuf()
416+
}
408417
}
409418
}
410419
}
@@ -454,6 +463,7 @@ impl TryFrom<AnyTransactionData> for AnySchedulableTransactionData {
454463
AnyTransactionData::Freeze(it) => Ok(Self::Freeze(it)),
455464
AnyTransactionData::ScheduleDelete(it) => Ok(Self::ScheduleDelete(it)),
456465
AnyTransactionData::Prng(it) => Ok(Self::Prng(it)),
466+
AnyTransactionData::TokenUpdateNfts(it) => Ok(Self::TokenUpdateNfts(it)),
457467

458468
// fixme: basic-parse isn't suitable for this.
459469
AnyTransactionData::ScheduleCreate(_) => {
@@ -516,6 +526,7 @@ impl From<AnySchedulableTransactionData> for AnyTransactionData {
516526
AnySchedulableTransactionData::Freeze(it) => Self::Freeze(it),
517527
AnySchedulableTransactionData::ScheduleDelete(it) => Self::ScheduleDelete(it),
518528
AnySchedulableTransactionData::Prng(it) => Self::Prng(it),
529+
AnySchedulableTransactionData::TokenUpdateNfts(it) => Self::TokenUpdateNfts(it),
519530
}
520531
}
521532
}

0 commit comments

Comments
 (0)