Skip to content

Commit c774d97

Browse files
committed
Add NFT URL button
1 parent 9df22c0 commit c774d97

File tree

13 files changed

+350
-40
lines changed

13 files changed

+350
-40
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ tauri-specta = "=2.0.0-rc.18"
7272
# Chia
7373
chia = "0.15.0"
7474
clvmr = "0.9.0"
75-
chia-wallet-sdk = { version = "0.17.0", features = ["rustls"] }
75+
chia-wallet-sdk = { version = "0.18.0", features = ["rustls"] }
7676
bip39 = "2.0.0"
7777
bech32 = "0.9.1"
7878

crates/sage-api/src/types.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
mod amount;
2+
mod nft_uri_kind;
23
mod unit;
34

45
pub use amount::*;
6+
pub use nft_uri_kind::*;
57
pub use unit::*;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
use serde::{Deserialize, Serialize};
2+
use specta::Type;
3+
4+
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Type)]
5+
#[serde(rename_all = "snake_case")]
6+
pub enum NftUriKind {
7+
Data,
8+
Metadata,
9+
License,
10+
}

crates/sage-wallet/src/sync_manager.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,10 @@ impl SyncManager {
171171
self.pending_coin_subscriptions.extend(coin_ids);
172172
}
173173
SyncCommand::ConnectionClosed(ip) => {
174-
self.state.lock().await.remove_peer(ip);
174+
self.state
175+
.lock()
176+
.await
177+
.ban(ip, Duration::from_secs(300), "peer disconnected");
175178
debug!("Peer {ip} disconnected");
176179
}
177180
SyncCommand::SetDiscoverPeers(discover_peers) => {

crates/sage-wallet/src/sync_manager/peer_discovery.rs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use chia::{
99
protocol::{Message, NewPeakWallet, ProtocolMessageTypes},
1010
traits::Streamable,
1111
};
12-
use chia_wallet_sdk::{connect_peer, Peer};
12+
use chia_wallet_sdk::{connect_peer, Peer, PeerOptions};
1313
use futures_lite::StreamExt;
1414
use futures_util::stream::FuturesUnordered;
1515
use tokio::{sync::mpsc, time::timeout};
@@ -153,8 +153,11 @@ impl SyncManager {
153153
let duration = self.options.connection_timeout;
154154

155155
futures.push(async move {
156-
let result =
157-
timeout(duration, connect_peer(network_id, connector, socket_addr)).await;
156+
let result = timeout(
157+
duration,
158+
connect_peer(network_id, connector, socket_addr, PeerOptions::default()),
159+
)
160+
.await;
158161
(socket_addr, result)
159162
});
160163
}
@@ -229,7 +232,25 @@ impl SyncManager {
229232
let ip = peer.socket_addr().ip();
230233
let sender = self.command_sender.clone();
231234

232-
self.state.lock().await.add_peer(PeerInfo {
235+
let mut state = self.state.lock().await;
236+
237+
for (peer, height) in state.peers_with_heights() {
238+
if message.height < height.saturating_sub(3) {
239+
debug!(
240+
"Peer {} is behind by more than 3 blocks, disconnecting",
241+
peer.socket_addr()
242+
);
243+
return false;
244+
} else if message.height > height.saturating_add(3) {
245+
state.ban(
246+
peer.socket_addr().ip(),
247+
Duration::from_secs(900),
248+
"peer is behind",
249+
);
250+
}
251+
}
252+
253+
state.add_peer(PeerInfo {
233254
peer: WalletPeer::new(peer),
234255
claimed_peak: message.height,
235256
header_hash: message.header_hash,
@@ -242,7 +263,7 @@ impl SyncManager {
242263
.await
243264
.is_err()
244265
{
245-
return;
266+
break;
246267
}
247268
}
248269

crates/sage-wallet/src/wallet/nfts.rs

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ use chia::{
33
puzzles::nft::{NftMetadata, NFT_METADATA_UPDATER_PUZZLE_HASH},
44
};
55
use chia_wallet_sdk::{
6-
Conditions, Did, DidOwner, HashedPtr, Launcher, Nft, NftMint, SpendContext, StandardLayer,
6+
Conditions, Did, DidOwner, HashedPtr, Launcher, MetadataUpdate, Nft, NftMint, SpendContext,
7+
StandardLayer,
78
};
89

910
use crate::WalletError;
@@ -136,4 +137,60 @@ impl Wallet {
136137

137138
Ok((ctx.take(), new_nft))
138139
}
140+
141+
pub async fn add_nft_uri(
142+
&self,
143+
nft_id: Bytes32,
144+
fee: u64,
145+
uri: MetadataUpdate,
146+
hardened: bool,
147+
reuse: bool,
148+
) -> Result<(Vec<CoinSpend>, Nft<Program>), WalletError> {
149+
let Some(nft) = self.db.spendable_nft(nft_id).await? else {
150+
return Err(WalletError::MissingNft(nft_id));
151+
};
152+
153+
let total_amount = fee as u128 + 1;
154+
let coins = self.select_p2_coins(total_amount).await?;
155+
let selected: u128 = coins.iter().map(|coin| coin.amount as u128).sum();
156+
157+
let change: u64 = (selected - total_amount)
158+
.try_into()
159+
.expect("change amount overflow");
160+
161+
let p2_puzzle_hash = self.p2_puzzle_hash(hardened, reuse).await?;
162+
163+
let mut ctx = SpendContext::new();
164+
165+
let nft_metadata_ptr = ctx.alloc(&nft.info.metadata)?;
166+
let nft = nft.with_metadata(HashedPtr::from_ptr(&ctx.allocator, nft_metadata_ptr));
167+
168+
let synthetic_key = self.db.synthetic_key(nft.info.p2_puzzle_hash).await?;
169+
let p2 = StandardLayer::new(synthetic_key);
170+
171+
let update_spend = uri.spend(&mut ctx)?;
172+
let new_nft: Nft<HashedPtr> = nft.transfer_with_metadata(
173+
&mut ctx,
174+
&p2,
175+
nft.info.p2_puzzle_hash,
176+
update_spend,
177+
Conditions::new(),
178+
)?;
179+
180+
let mut conditions = Conditions::new().assert_concurrent_spend(nft.coin.coin_id());
181+
182+
if fee > 0 {
183+
conditions = conditions.reserve_fee(fee);
184+
}
185+
186+
if change > 0 {
187+
conditions = conditions.create_coin(p2_puzzle_hash, change, Vec::new());
188+
}
189+
190+
self.spend_p2_coins(&mut ctx, coins, conditions).await?;
191+
192+
let new_nft = new_nft.with_metadata(ctx.serialize(&new_nft.info.metadata)?);
193+
194+
Ok((ctx.take(), new_nft))
195+
}
139196
}

src-tauri/src/commands/transactions.rs

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ use chia::{
77
puzzles::nft::NftMetadata,
88
};
99
use chia_wallet_sdk::{
10-
decode_address, encode_address, AggSigConstants, MAINNET_CONSTANTS, TESTNET11_CONSTANTS,
10+
decode_address, encode_address, AggSigConstants, MetadataUpdate, MAINNET_CONSTANTS,
11+
TESTNET11_CONSTANTS,
1112
};
1213
use hex_literal::hex;
1314
use sage_api::{
14-
Amount, BulkMintNfts, BulkMintNftsResponse, CoinJson, CoinSpendJson, Input, InputKind, Output,
15-
SpendBundleJson, TransactionSummary,
15+
Amount, BulkMintNfts, BulkMintNftsResponse, CoinJson, CoinSpendJson, Input, InputKind,
16+
NftUriKind, Output, SpendBundleJson, TransactionSummary,
1617
};
1718
use sage_database::{CatRow, Database};
1819
use sage_wallet::{
@@ -508,6 +509,45 @@ pub async fn transfer_nft(
508509
summarize(&state, &wallet, coin_spends, ConfirmationInfo::default()).await
509510
}
510511

512+
#[command]
513+
#[specta]
514+
pub async fn add_nft_uri(
515+
state: State<'_, AppState>,
516+
nft_id: String,
517+
uri: String,
518+
kind: NftUriKind,
519+
fee: Amount,
520+
) -> Result<TransactionSummary> {
521+
let state = state.lock().await;
522+
let wallet = state.wallet()?;
523+
524+
if !state.keychain.has_secret_key(wallet.fingerprint) {
525+
return Err(Error::no_secret_key());
526+
}
527+
528+
let (launcher_id, prefix) = decode_address(&nft_id)?;
529+
530+
if prefix != "nft" {
531+
return Err(Error::invalid_prefix(&prefix));
532+
}
533+
534+
let Some(fee) = fee.to_mojos(state.unit.decimals) else {
535+
return Err(Error::invalid_amount(&fee));
536+
};
537+
538+
let uri = match kind {
539+
NftUriKind::Data => MetadataUpdate::NewDataUri(uri),
540+
NftUriKind::Metadata => MetadataUpdate::NewMetadataUri(uri),
541+
NftUriKind::License => MetadataUpdate::NewLicenseUri(uri),
542+
};
543+
544+
let (coin_spends, _new_nft) = wallet
545+
.add_nft_uri(launcher_id.into(), fee, uri, false, true)
546+
.await?;
547+
548+
summarize(&state, &wallet, coin_spends, ConfirmationInfo::default()).await
549+
}
550+
511551
#[command]
512552
#[specta]
513553
pub async fn transfer_did(

src-tauri/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ pub fn run() {
7575
commands::bulk_mint_nfts,
7676
commands::transfer_nft,
7777
commands::transfer_did,
78+
commands::add_nft_uri,
7879
commands::sign_transaction,
7980
commands::submit_transaction,
8081
// Peers

0 commit comments

Comments
 (0)