Skip to main content

merge_coins/
merge_coins.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{str::FromStr, time::Duration};
6
7use iota_config::{IOTA_CLIENT_CONFIG, iota_config_dir};
8use iota_faucet::FaucetError;
9use iota_keys::keystore::AccountKeystore;
10use iota_sdk::wallet_context::WalletContext;
11use iota_sdk_transaction_builder::WaitForTransaction;
12use iota_sdk_types::ObjectId;
13use iota_types::gas_coin::GasCoin;
14use tracing::info;
15
16#[tokio::main]
17async fn main() -> Result<(), anyhow::Error> {
18    let wallet = create_wallet_context(60)?;
19    let active_address = wallet
20        .active_address()
21        .map_err(|err| FaucetError::Wallet(err.to_string()))?;
22    println!("SimpleFaucet::new with active address: {active_address}");
23
24    // Example scripts
25    // merge_coins(
26    //     "0x0215b800acc47d80a50741f0eecfa507fc2c21f5a9aa6140a219686ad20d7f4c",
27    //     wallet,
28    // )
29    // .await?;
30
31    // split_coins_equally(
32    //     "0xd42a75242975780037e170486540f28ab3c9be07dbb1f6f2a9430ad268e3b1d1",
33    //     wallet,
34    //     1000,
35    // )
36    // .await?;
37
38    Ok(())
39}
40
41async fn _split_coins_equally(
42    gas_coin: &str,
43    wallet: WalletContext,
44    count: u64,
45) -> Result<(), anyhow::Error> {
46    let active_address = wallet
47        .active_address()
48        .map_err(|err| FaucetError::Wallet(err.to_string()))?;
49    let client = wallet.get_grpc_client().await?;
50    let coin_object_id = ObjectId::from_str(gas_coin).unwrap();
51
52    let mut builder = client.transaction_builder(active_address);
53    builder.divide_coin(coin_object_id, count);
54    builder.gas_budget(50000000000);
55
56    let signer = wallet.config().keystore().get_key(&active_address)?;
57    let effects = builder
58        .execute(signer.as_keypair()?, WaitForTransaction::Finalized)
59        .await?;
60    println!("{effects:?}");
61    Ok(())
62}
63
64async fn _merge_coins(gas_coin: &str, wallet: WalletContext) -> Result<(), anyhow::Error> {
65    let active_address = wallet
66        .active_address()
67        .map_err(|err| FaucetError::Wallet(err.to_string()))?;
68    let client = wallet.get_grpc_client().await?;
69    // Pick a gas coin here that isn't in use by the faucet otherwise there will be
70    // some contention.
71    let small_coins = wallet
72        .gas_objects(active_address)
73        .await
74        .map_err(|e| FaucetError::Wallet(e.to_string()))?
75        .iter()
76        // Ok to unwrap() since `get_gas_objects` guarantees gas
77        .map(|q| GasCoin::try_from(&q.1).unwrap())
78        // Everything less than 1 iota
79        .filter(|coin| coin.0.balance.value() <= 10000000000)
80        .collect::<Vec<GasCoin>>();
81
82    let signer = wallet.config().keystore().get_key(&active_address)?;
83
84    // Smash coins togethers 254 objects at a time
85    for chunk in small_coins.chunks(254) {
86        let total_balance: u64 = chunk.iter().map(|coin| coin.0.balance.value()).sum();
87
88        let mut coin_vector = chunk
89            .iter()
90            .map(|coin| *coin.id())
91            .collect::<Vec<ObjectId>>();
92
93        // prepend big gas coin instance to vector
94        coin_vector.insert(0, ObjectId::from_str(gas_coin).unwrap());
95
96        // The coins pay for the transaction, so that gas smashing is what merges
97        // them, and the whole balance is split back to the sender.
98        let mut builder = client.transaction_builder(active_address);
99        builder
100            .pay_iota([(active_address, total_balance)])
101            .gas(coin_vector);
102        builder.gas_budget(1000000);
103
104        builder
105            .execute(signer.as_keypair()?, WaitForTransaction::Finalized)
106            .await?;
107    }
108    Ok(())
109}
110
111pub fn create_wallet_context(timeout_secs: u64) -> Result<WalletContext, anyhow::Error> {
112    let wallet_conf = iota_config_dir()?.join(IOTA_CLIENT_CONFIG);
113    info!("Initialize wallet from config path: {wallet_conf:?}");
114    Ok(WalletContext::new(&wallet_conf)?.with_request_timeout(Duration::from_secs(timeout_secs)))
115}