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_json_rpc_types::IotaTransactionBlockResponseOptions;
10use iota_keys::keystore::AccountKeystore;
11use iota_sdk::wallet_context::WalletContext;
12use iota_types::{
13    base_types::ObjectID, gas_coin::GasCoin, quorum_driver_types::ExecuteTransactionRequestType,
14    transaction::Transaction,
15};
16use shared_crypto::intent::Intent;
17use tracing::info;
18
19#[tokio::main]
20async fn main() -> Result<(), anyhow::Error> {
21    let wallet = create_wallet_context(60)?;
22    let active_address = wallet
23        .active_address()
24        .map_err(|err| FaucetError::Wallet(err.to_string()))?;
25    println!("SimpleFaucet::new with active address: {active_address}");
26
27    // Example scripts
28    // merge_coins(
29    //     "0x0215b800acc47d80a50741f0eecfa507fc2c21f5a9aa6140a219686ad20d7f4c",
30    //     wallet,
31    // )
32    // .await?;
33
34    // split_coins_equally(
35    //     "0xd42a75242975780037e170486540f28ab3c9be07dbb1f6f2a9430ad268e3b1d1",
36    //     wallet,
37    //     1000,
38    // )
39    // .await?;
40
41    Ok(())
42}
43
44async fn _split_coins_equally(
45    gas_coin: &str,
46    wallet: WalletContext,
47    count: u64,
48) -> Result<(), anyhow::Error> {
49    let active_address = wallet
50        .active_address()
51        .map_err(|err| FaucetError::Wallet(err.to_string()))?;
52    let client = wallet.get_client().await?;
53    let coin_object_id = ObjectID::from_str(gas_coin).unwrap();
54    let tx_data = client
55        .transaction_builder()
56        .split_coin_equal(active_address, coin_object_id, count, None, 50000000000)
57        .await?;
58
59    let signature = wallet
60        .config()
61        .keystore()
62        .sign_secure(&active_address, &tx_data, Intent::iota_transaction())
63        .unwrap();
64    let tx = Transaction::from_data(tx_data, vec![signature]);
65    let resp = client
66        .quorum_driver_api()
67        .execute_transaction_block(
68            tx.clone(),
69            IotaTransactionBlockResponseOptions::new().with_effects(),
70            Some(ExecuteTransactionRequestType::WaitForLocalExecution),
71        )
72        .await?;
73
74    println!("{:?}", resp);
75    Ok(())
76}
77
78async fn _merge_coins(gas_coin: &str, wallet: WalletContext) -> Result<(), anyhow::Error> {
79    let active_address = wallet
80        .active_address()
81        .map_err(|err| FaucetError::Wallet(err.to_string()))?;
82    let client = wallet.get_client().await?;
83    // Pick a gas coin here that isn't in use by the faucet otherwise there will be
84    // some contention.
85    let small_coins = wallet
86        .gas_objects(active_address)
87        .await
88        .map_err(|e| FaucetError::Wallet(e.to_string()))?
89        .iter()
90        // Ok to unwrap() since `get_gas_objects` guarantees gas
91        .map(|q| GasCoin::try_from(&q.1).unwrap())
92        // Everything less than 1 iota
93        .filter(|coin| coin.0.balance.value() <= 10000000000)
94        .collect::<Vec<GasCoin>>();
95
96    // Smash coins togethers 254 objects at a time
97    for chunk in small_coins.chunks(254) {
98        let total_balance: u64 = chunk.iter().map(|coin| coin.0.balance.value()).sum();
99
100        let mut coin_vector = chunk
101            .iter()
102            .map(|coin| *coin.id())
103            .collect::<Vec<ObjectID>>();
104
105        // prepend big gas coin instance to vector
106        coin_vector.insert(0, ObjectID::from_str(gas_coin).unwrap());
107        let target = vec![active_address];
108        let target_amount = vec![total_balance];
109
110        let tx_data = client
111            .transaction_builder()
112            .pay_iota(active_address, coin_vector, target, target_amount, 1000000)
113            .await?;
114        let signature = wallet
115            .config()
116            .keystore()
117            .sign_secure(&active_address, &tx_data, Intent::iota_transaction())
118            .unwrap();
119        let tx = Transaction::from_data(tx_data, vec![signature]);
120        client
121            .quorum_driver_api()
122            .execute_transaction_block(
123                tx.clone(),
124                IotaTransactionBlockResponseOptions::new().with_effects(),
125                Some(ExecuteTransactionRequestType::WaitForLocalExecution),
126            )
127            .await?;
128    }
129    Ok(())
130}
131
132pub fn create_wallet_context(timeout_secs: u64) -> Result<WalletContext, anyhow::Error> {
133    let wallet_conf = iota_config_dir()?.join(IOTA_CLIENT_CONFIG);
134    info!("Initialize wallet from config path: {:?}", wallet_conf);
135    WalletContext::new(&wallet_conf, Some(Duration::from_secs(timeout_secs)), None)
136}