Created
January 5, 2025 12:06
-
-
Save ligulfzhou/fd12b5abf9e6d3d5d9817a8b8046d9e5 to your computer and use it in GitHub Desktop.
swap on bluemove
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use anyhow::bail; | |
| use fastcrypto::hash::HashFunction; | |
| use move_core_types::identifier::Identifier; | |
| use move_core_types::language_storage::TypeTag; | |
| use shared_crypto::intent::{Intent, IntentMessage}; | |
| use std::str::FromStr; | |
| use sui_sdk::types::base_types::{ObjectID, SequenceNumber}; | |
| use sui_sdk::types::transaction::{Argument, ProgrammableTransaction}; | |
| use sui_sdk::{ | |
| rpc_types::{Coin, SuiTransactionBlockResponseOptions}, | |
| types::{ | |
| base_types::SuiAddress, | |
| crypto::{get_key_pair_from_rng, DefaultHash, Signer, SuiKeyPair}, | |
| programmable_transaction_builder::ProgrammableTransactionBuilder, | |
| quorum_driver_types::ExecuteTransactionRequestType, | |
| transaction::{Command, ObjectArg, Transaction, TransactionData}, | |
| }, | |
| SuiClient, SuiClientBuilder, SUI_COIN_TYPE, | |
| }; | |
| const RPC: &str = "https://fullnode.mainnet.sui.io:443"; | |
| pub const BLUEMOVE_PACKAGE: &str = | |
| "0x08cd33481587d4c4612865b164796d937df13747d8c763b8a178c87e3244498f"; | |
| pub const BLUEMOVE_VAULT_PACKAGE: &str = | |
| "0x7ea6e27ad7af6f3b8671d59df1aaebd7c03dddab893e52a714227b2f4fe91519"; | |
| pub const BLUEMOVE_VAULT_OBJECT_ID: &str = | |
| "0x39a3c55742c0e011b6f65548e73cf589e1ae5e82dbfab449ca57f24c3bcd9514"; | |
| pub const BLUEMOVE_VAULT_OBJECT_INIT_VERSION: u64 = 335738127; | |
| pub const BLUEMOVE_CONFIG_OBJECT_ID: &str = | |
| "0x0f8fc23dbcc9362b72c7a4c5aa53fcefa02ebfbb83a812c8c262ccd2c076d9ee"; | |
| pub const BLUEMOVE_CONFIG_OBJECT_INIT_VERSION: u64 = 335738127; | |
| pub const BLUEMOVE_SWAP_DEX_INFO_OBJECT_ID: &str = | |
| "0x3f2d9f724f4a1ce5e71676448dc452be9a6243dac9c5b975a588c8c867066e92"; | |
| pub const BLUEMOVE_SWAP_DEX_INFO_OBJECT_VERSION: u64 = 1587827; | |
| pub const ONE_PACKAGE: &str = "0x0000000000000000000000000000000000000000000000000000000000000001"; | |
| pub const TWO_PACKAGE: &str = "0x0000000000000000000000000000000000000000000000000000000000000002"; | |
| // 获取用户的token,可能会有非常多的碎片 | |
| async fn get_all_coins( | |
| client: &SuiClient, | |
| address: SuiAddress, | |
| coin_type: &str, | |
| ) -> anyhow::Result<Vec<Coin>> { | |
| let mut cursor = None; | |
| let mut coins = vec![]; | |
| loop { | |
| let coins_res = client | |
| .coin_read_api() | |
| .get_coins(address, Some(coin_type.to_string()), cursor, None) // default limit is 50 | |
| .await?; | |
| coins.extend(coins_res.data); | |
| if coins_res.has_next_page { | |
| cursor = coins_res.next_cursor; | |
| continue; | |
| } | |
| return Ok(coins); | |
| } | |
| } | |
| async fn send_tx( | |
| tx: ProgrammableTransaction, | |
| keypair: &SuiKeyPair, | |
| gas_coin: &[Coin], | |
| client: &SuiClient, | |
| gas_budget: u64, | |
| ) -> anyhow::Result<String> { | |
| let gas_price = client.read_api().get_reference_gas_price().await?; | |
| // create the transaction data that will be sent to the network | |
| let tx_data = TransactionData::new_programmable( | |
| SuiAddress::from(keypair.public()), | |
| gas_coin | |
| .iter() | |
| .map(|coin| coin.object_ref()) | |
| .collect::<Vec<_>>(), | |
| tx, | |
| gas_budget, | |
| gas_price, | |
| ); | |
| // 4) sign transaction | |
| let intent_msg = IntentMessage::new(Intent::sui_transaction(), tx_data); | |
| let raw_tx = bcs::to_bytes(&intent_msg).expect("bcs should not fail"); | |
| let mut hasher = DefaultHash::default(); | |
| hasher.update(raw_tx.clone()); | |
| let digest = hasher.finalize().digest; | |
| let sig = keypair.sign(&digest); | |
| // 5) execute the transaction | |
| println!("Executing the transaction..."); | |
| let transaction_response = client | |
| .quorum_driver_api() | |
| .execute_transaction_block( | |
| Transaction::from_data(tx_data, vec![sig]), | |
| SuiTransactionBlockResponseOptions::full_content(), | |
| Some(ExecuteTransactionRequestType::WaitForLocalExecution), | |
| ) | |
| .await?; | |
| let hash = transaction_response.digest.to_string(); | |
| println!("transaction hash: {:?}", hash); | |
| Ok(hash) | |
| } | |
| // 买token | |
| async fn buy_token( | |
| keypair: &SuiKeyPair, | |
| client: &SuiClient, | |
| coin_type: &str, | |
| buy_amount: u64, | |
| ) -> anyhow::Result<()> { | |
| // no need to get all gas_coins to pay the gas | |
| let gas_coins = | |
| get_all_coins(&client, SuiAddress::from(keypair.public()), SUI_COIN_TYPE).await?; | |
| let mut ptb = ProgrammableTransactionBuilder::new(); | |
| // 1: 切出要花的sui | |
| let split_cmd = Command::SplitCoins(Argument::GasCoin, vec![ptb.pure(buy_amount).unwrap()]); | |
| ptb.command(split_cmd); | |
| let value_cmd = Command::move_call( | |
| ObjectID::from_hex_literal(TWO_PACKAGE)?, | |
| Identifier::new("coin")?, | |
| Identifier::new("value")?, | |
| vec![TypeTag::from_str(SUI_COIN_TYPE)?], | |
| vec![Argument::NestedResult(0, 0)], | |
| ); | |
| ptb.command(value_cmd); | |
| // 3: swap_exact_input_ | |
| let swap_cmd = Command::move_call( | |
| ObjectID::from_hex_literal(BLUEMOVE_PACKAGE)?, | |
| Identifier::new("router")?, | |
| Identifier::new("swap_exact_input_")?, | |
| vec![ | |
| TypeTag::from_str(SUI_COIN_TYPE)?, | |
| TypeTag::from_str(coin_type)?, | |
| ], | |
| vec![ | |
| Argument::NestedResult(1, 0), | |
| Argument::NestedResult(0, 0), | |
| ptb.pure(0u64)?, | |
| ptb.obj(ObjectArg::SharedObject { | |
| id: ObjectID::from_str(BLUEMOVE_SWAP_DEX_INFO_OBJECT_ID)?, | |
| initial_shared_version: SequenceNumber::from_u64( | |
| BLUEMOVE_SWAP_DEX_INFO_OBJECT_VERSION, | |
| ), | |
| mutable: true, | |
| })?, | |
| ], | |
| ); | |
| ptb.command(swap_cmd); | |
| // 4: some | |
| let swap_cmd = Command::move_call( | |
| ObjectID::from_hex_literal(ONE_PACKAGE)?, | |
| Identifier::new("option")?, | |
| Identifier::new("some")?, | |
| vec![TypeTag::Address], | |
| vec![ptb.pure(keypair.public())?], | |
| ); | |
| ptb.command(swap_cmd); | |
| // 5: settle | |
| let settle_cmd = Command::move_call( | |
| ObjectID::from_hex_literal(BLUEMOVE_VAULT_PACKAGE)?, | |
| Identifier::new("settle")?, | |
| Identifier::new("settle")?, | |
| vec![ | |
| TypeTag::from_str(SUI_COIN_TYPE)?, | |
| TypeTag::from_str(coin_type)?, | |
| ], | |
| vec![ | |
| ptb.obj(ObjectArg::SharedObject { | |
| id: ObjectID::from_str(BLUEMOVE_CONFIG_OBJECT_ID)?, | |
| initial_shared_version: SequenceNumber::from_u64( | |
| BLUEMOVE_CONFIG_OBJECT_INIT_VERSION, | |
| ), | |
| mutable: false, | |
| }) | |
| .unwrap(), | |
| ptb.obj(ObjectArg::SharedObject { | |
| id: ObjectID::from_str(BLUEMOVE_VAULT_OBJECT_ID)?, | |
| initial_shared_version: SequenceNumber::from_u64( | |
| BLUEMOVE_VAULT_OBJECT_INIT_VERSION, | |
| ), | |
| mutable: true, | |
| })?, | |
| ptb.pure(buy_amount)?, | |
| Argument::NestedResult(2, 0), | |
| ptb.pure(0u64)?, | |
| ptb.pure(u64::MAX)?, | |
| Argument::NestedResult(3, 0), | |
| ptb.pure(0u64)?, | |
| ], | |
| ); | |
| ptb.command(settle_cmd); | |
| let transfer_cmd = Command::TransferObjects( | |
| vec![Argument::NestedResult(2, 0)], | |
| ptb.pure(keypair.public())?, | |
| ); | |
| ptb.command(transfer_cmd); | |
| let pt = ptb.finish(); | |
| let hash = send_tx(pt, keypair, &gas_coins, &client, 400_0000).await?; | |
| println!("Transaction hash: {:?}", hash); | |
| Ok(()) | |
| } | |
| // 卖token | |
| async fn sell_token( | |
| keypair: &SuiKeyPair, | |
| client: &SuiClient, | |
| coin_type: &str, | |
| sell_amount: u64, | |
| ) -> anyhow::Result<()> { | |
| // no need to get all gas_coins to pay the gas | |
| let gas_coins = | |
| get_all_coins(&client, SuiAddress::from(keypair.public()), SUI_COIN_TYPE).await?; | |
| let coins = get_all_coins(&client, SuiAddress::from(keypair.public()), coin_type).await?; | |
| if coins.is_empty() { | |
| bail!("No coins to sell"); | |
| } | |
| let mut ptb = ProgrammableTransactionBuilder::new(); | |
| // 1: 如果有多个碎片,可以先merge一下 | |
| let add_idx = if coins.len() > 1 { | |
| let merge_coin_cmd = Command::MergeCoins( | |
| ptb.obj(ObjectArg::ImmOrOwnedObject(coins[0].object_ref()))?, | |
| coins[1..] | |
| .iter() | |
| .map(|coin| { | |
| ptb.obj(ObjectArg::ImmOrOwnedObject(coin.object_ref())) | |
| .unwrap() | |
| }) | |
| .collect::<Vec<_>>(), | |
| ); | |
| ptb.command(merge_coin_cmd); | |
| 1 | |
| } else { | |
| 0 | |
| }; | |
| // 2: 切出要卖掉的coin | |
| let split_cmd = Command::SplitCoins( | |
| ptb.obj(ObjectArg::ImmOrOwnedObject(coins[0].object_ref()))?, | |
| vec![ptb.pure(sell_amount).unwrap()], | |
| ); | |
| ptb.command(split_cmd); | |
| // 3: value | |
| let value_cmd = Command::move_call( | |
| ObjectID::from_hex_literal(TWO_PACKAGE)?, | |
| Identifier::new("coin")?, | |
| Identifier::new("value")?, | |
| vec![TypeTag::from_str(coin_type).unwrap()], | |
| vec![Argument::NestedResult(add_idx, 0)], | |
| ); | |
| ptb.command(value_cmd); | |
| // 3: swap_exact_input_ | |
| let swap_cmd = Command::move_call( | |
| ObjectID::from_hex_literal(fk_sui::consts::BLUEMOVE_PACKAGE)?, | |
| Identifier::new("router")?, | |
| Identifier::new("swap_exact_input_")?, | |
| vec![ | |
| TypeTag::from_str(coin_type)?, | |
| TypeTag::from_str(SUI_COIN_TYPE)?, | |
| ], | |
| vec![ | |
| Argument::NestedResult(1 + add_idx, 0), | |
| Argument::NestedResult(add_idx, 0), | |
| ptb.pure(0u64)?, | |
| ptb.obj(ObjectArg::SharedObject { | |
| id: ObjectID::from_str(BLUEMOVE_SWAP_DEX_INFO_OBJECT_ID)?, | |
| initial_shared_version: SequenceNumber::from_u64( | |
| BLUEMOVE_SWAP_DEX_INFO_OBJECT_VERSION, | |
| ), | |
| mutable: true, | |
| })?, | |
| ], | |
| ); | |
| ptb.command(swap_cmd); | |
| // 4: some | |
| let swap_cmd = Command::move_call( | |
| ObjectID::from_hex_literal(ONE_PACKAGE)?, | |
| Identifier::new("option")?, | |
| Identifier::new("some")?, | |
| vec![TypeTag::Address], | |
| vec![ptb.pure(SuiAddress::from(keypair.public()))?], | |
| ); | |
| ptb.command(swap_cmd); | |
| // 5: settle | |
| let settle_cmd = Command::move_call( | |
| ObjectID::from_hex_literal(BLUEMOVE_VAULT_PACKAGE)?, | |
| Identifier::new("settle")?, | |
| Identifier::new("settle")?, | |
| vec![ | |
| TypeTag::from_str(coin_type)?, | |
| TypeTag::from_str(SUI_COIN_TYPE)?, | |
| ], | |
| vec![ | |
| ptb.obj(ObjectArg::SharedObject { | |
| id: ObjectID::from_str(BLUEMOVE_CONFIG_OBJECT_ID)?, | |
| initial_shared_version: SequenceNumber::from_u64( | |
| BLUEMOVE_CONFIG_OBJECT_INIT_VERSION, | |
| ), | |
| mutable: false, | |
| })?, | |
| ptb.obj(ObjectArg::SharedObject { | |
| id: ObjectID::from_str(BLUEMOVE_VAULT_OBJECT_ID)?, | |
| initial_shared_version: SequenceNumber::from_u64( | |
| BLUEMOVE_VAULT_OBJECT_INIT_VERSION, | |
| ), | |
| mutable: true, | |
| })?, | |
| ptb.pure(sell_amount).unwrap(), | |
| Argument::NestedResult(2 + add_idx, 0), | |
| ptb.pure(0u64).unwrap(), | |
| ptb.pure(u64::MAX).unwrap(), | |
| Argument::NestedResult(3 + add_idx, 0), | |
| ptb.pure(0u64).unwrap(), | |
| ], | |
| ); | |
| ptb.command(settle_cmd); | |
| let transfer_cmd = Command::TransferObjects( | |
| vec![Argument::NestedResult(2 + add_idx, 0)], | |
| ptb.pure(SuiAddress::from(keypair.public())).unwrap(), | |
| ); | |
| ptb.command(transfer_cmd); | |
| let pt = ptb.finish(); | |
| let hash = send_tx(pt, keypair, &gas_coins, &client, 400_0000).await?; | |
| println!("Transaction hash: {:?}", hash); | |
| Ok(()) | |
| } | |
| #[tokio::main] | |
| async fn main() -> anyhow::Result<()> { | |
| let keypair = SuiKeyPair::Ed25519(get_key_pair_from_rng(&mut rand::rngs::OsRng).1); | |
| let client = SuiClientBuilder::default().build(RPC).await?; | |
| let coin_to_trade: &str = | |
| "0xa8b69040684d576828475115b30cc4ce7c7743eab9c7d669535ee31caccef4f5::suiman::SUIMAN"; | |
| // 花 1_0000_0000(0.1sui) 买 某coin | |
| buy_token(&keypair, &client, coin_to_trade, 1_0000_0000).await?; | |
| // 出售 100 个某coin | |
| sell_token(&keypair, &client, coin_to_trade, 100).await?; | |
| Ok(()) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment