main.rs 74 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2024 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use std::{
  19. io::{stdin, Read},
  20. process::exit,
  21. str::FromStr,
  22. sync::Arc,
  23. };
  24. use prettytable::{format, row, Table};
  25. use rand::rngs::OsRng;
  26. use smol::{fs::read_to_string, stream::StreamExt};
  27. use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml};
  28. use url::Url;
  29. use darkfi::{
  30. async_daemonize, cli_desc,
  31. util::{
  32. encoding::base64,
  33. parse::{decode_base10, encode_base10},
  34. path::{expand_path, get_config_path},
  35. },
  36. zk::halo2::Field,
  37. Error, Result,
  38. };
  39. use darkfi_dao_contract::{blockwindow, model::DaoProposalBulla, DaoFunction};
  40. use darkfi_money_contract::model::{Coin, CoinAttributes, TokenId};
  41. use darkfi_sdk::{
  42. crypto::{
  43. note::AeadEncryptedNote, BaseBlind, FuncId, FuncRef, PublicKey, SecretKey, DAO_CONTRACT_ID,
  44. },
  45. pasta::{group::ff::PrimeField, pallas},
  46. tx::TransactionHash,
  47. };
  48. use darkfi_serial::{deserialize_async, serialize_async};
  49. use drk::{
  50. cli_util::{
  51. generate_completions, kaching, parse_token_pair, parse_tx_from_stdin, parse_value_pair,
  52. },
  53. dao::{DaoParams, ProposalRecord},
  54. money::BALANCE_BASE10_DECIMALS,
  55. swap::PartialSwapData,
  56. Drk,
  57. };
  58. const CONFIG_FILE: &str = "drk_config.toml";
  59. const CONFIG_FILE_CONTENTS: &str = include_str!("../drk_config.toml");
  60. // Dev Note: when adding/modifying args here,
  61. // don't forget to update cli_util::generate_completions()
  62. #[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)]
  63. #[serde(default)]
  64. #[structopt(name = "drk", about = cli_desc!())]
  65. struct Args {
  66. #[structopt(short, long)]
  67. /// Configuration file to use
  68. config: Option<String>,
  69. #[structopt(short, long, default_value = "testnet")]
  70. /// Blockchain network to use
  71. network: String,
  72. #[structopt(subcommand)]
  73. /// Sub command to execute
  74. command: Subcmd,
  75. #[structopt(short, long)]
  76. /// Set log file to ouput into
  77. log: Option<String>,
  78. #[structopt(short, parse(from_occurrences))]
  79. /// Increase verbosity (-vvv supported)
  80. verbose: u8,
  81. }
  82. // Dev Note: when adding/modifying commands here,
  83. // don't forget to update cli_util::generate_completions()
  84. #[derive(Clone, Debug, Deserialize, StructOpt)]
  85. enum Subcmd {
  86. /// Fun
  87. Kaching,
  88. /// Send a ping request to the darkfid RPC endpoint
  89. Ping,
  90. /// Generate a SHELL completion script and print to stdout
  91. Completions {
  92. /// The Shell you want to generate script for
  93. shell: String,
  94. },
  95. /// Wallet operations
  96. Wallet {
  97. #[structopt(long)]
  98. /// Initialize wallet database
  99. initialize: bool,
  100. #[structopt(long)]
  101. /// Generate a new keypair in the wallet
  102. keygen: bool,
  103. #[structopt(long)]
  104. /// Query the wallet for known balances
  105. balance: bool,
  106. #[structopt(long)]
  107. /// Get the default address in the wallet
  108. address: bool,
  109. #[structopt(long)]
  110. /// Print all the addresses in the wallet
  111. addresses: bool,
  112. #[structopt(long)]
  113. /// Set the default address in the wallet
  114. default_address: Option<usize>,
  115. #[structopt(long)]
  116. /// Print all the secret keys from the wallet
  117. secrets: bool,
  118. #[structopt(long)]
  119. /// Import secret keys from stdin into the wallet, separated by newlines
  120. import_secrets: bool,
  121. #[structopt(long)]
  122. /// Print the Merkle tree in the wallet
  123. tree: bool,
  124. #[structopt(long)]
  125. /// Print all the coins in the wallet
  126. coins: bool,
  127. },
  128. /// Read a transaction from stdin and mark its input coins as spent
  129. Spend,
  130. /// Unspend a coin
  131. Unspend {
  132. /// base58-encoded coin to mark as unspent
  133. coin: String,
  134. },
  135. /// Create a payment transaction
  136. Transfer {
  137. /// Amount to send
  138. amount: String,
  139. /// Token ID to send
  140. token: String,
  141. /// Recipient address
  142. recipient: String,
  143. /// Optional contract spend hook to use
  144. spend_hook: Option<String>,
  145. /// Optional user data to use
  146. user_data: Option<String>,
  147. #[structopt(long)]
  148. /// Split the output coin into two equal halves
  149. half_split: bool,
  150. },
  151. /// OTC atomic swap
  152. Otc {
  153. #[structopt(subcommand)]
  154. /// Sub command to execute
  155. command: OtcSubcmd,
  156. },
  157. /// Attach the fee call to a transaction given from stdin
  158. AttachFee,
  159. /// Inspect a transaction from stdin
  160. Inspect,
  161. /// Read a transaction from stdin and broadcast it
  162. Broadcast,
  163. /// This subscription will listen for incoming blocks from darkfid and look
  164. /// through their transactions to see if there's any that interest us.
  165. /// With `drk` we look at transactions calling the money contract so we can
  166. /// find coins sent to us and fill our wallet with the necessary metadata.
  167. Subscribe,
  168. /// DAO functionalities
  169. Dao {
  170. #[structopt(subcommand)]
  171. /// Sub command to execute
  172. command: DaoSubcmd,
  173. },
  174. /// Scan the blockchain and parse relevant transactions
  175. Scan {
  176. #[structopt(long)]
  177. /// Reset Merkle tree and start scanning from first block
  178. reset: bool,
  179. },
  180. /// Explorer related subcommands
  181. Explorer {
  182. #[structopt(subcommand)]
  183. /// Sub command to execute
  184. command: ExplorerSubcmd,
  185. },
  186. /// Manage Token aliases
  187. Alias {
  188. #[structopt(subcommand)]
  189. /// Sub command to execute
  190. command: AliasSubcmd,
  191. },
  192. /// Token functionalities
  193. Token {
  194. #[structopt(subcommand)]
  195. /// Sub command to execute
  196. command: TokenSubcmd,
  197. },
  198. /// Contract functionalities
  199. Contract {
  200. #[structopt(subcommand)]
  201. /// Sub command to execute
  202. command: ContractSubcmd,
  203. },
  204. }
  205. #[derive(Clone, Debug, Deserialize, StructOpt)]
  206. enum OtcSubcmd {
  207. /// Initialize the first half of the atomic swap
  208. Init {
  209. /// Value pair to send:recv (11.55:99.42)
  210. #[structopt(short, long)]
  211. value_pair: String,
  212. /// Token pair to send:recv (f00:b4r)
  213. #[structopt(short, long)]
  214. token_pair: String,
  215. },
  216. /// Build entire swap tx given the first half from stdin
  217. Join,
  218. /// Inspect a swap half or the full swap tx from stdin
  219. Inspect,
  220. /// Sign a swap transaction given from stdin
  221. Sign,
  222. }
  223. #[derive(Clone, Debug, Deserialize, StructOpt)]
  224. enum DaoSubcmd {
  225. /// Create DAO parameters
  226. Create {
  227. /// The minimum amount of governance tokens needed to open a proposal for this DAO
  228. proposer_limit: String,
  229. /// Minimal threshold of participating total tokens needed for a proposal to pass
  230. quorum: String,
  231. /// The ratio of winning votes/total votes needed for a proposal to pass (2 decimals)
  232. approval_ratio: f64,
  233. /// DAO's governance token ID
  234. gov_token_id: String,
  235. },
  236. /// View DAO data from stdin
  237. View,
  238. /// Import DAO data from stdin
  239. Import {
  240. /// Name identifier for the DAO
  241. name: String,
  242. },
  243. /// List imported DAOs (or info about a specific one)
  244. List {
  245. /// Name identifier for the DAO (optional)
  246. name: Option<String>,
  247. },
  248. /// Show the balance of a DAO
  249. Balance {
  250. /// Name identifier for the DAO
  251. name: String,
  252. },
  253. /// Mint an imported DAO on-chain
  254. Mint {
  255. /// Name identifier for the DAO
  256. name: String,
  257. },
  258. /// Create a transfer proposal for a DAO
  259. ProposeTransfer {
  260. /// Name identifier for the DAO
  261. name: String,
  262. /// Duration of the proposal, in block windows
  263. duration: u64,
  264. /// Amount to send
  265. amount: String,
  266. /// Token ID to send
  267. token: String,
  268. /// Recipient address
  269. recipient: String,
  270. /// Optional contract spend hook to use
  271. spend_hook: Option<String>,
  272. /// Optional user data to use
  273. user_data: Option<String>,
  274. },
  275. /// List DAO proposals
  276. Proposals {
  277. /// Name identifier for the DAO
  278. name: String,
  279. },
  280. /// View a DAO proposal data
  281. Proposal {
  282. /// Bulla identifier for the proposal
  283. bulla: String,
  284. #[structopt(long)]
  285. /// Encrypt the proposal and encode it to base64
  286. export: bool,
  287. #[structopt(long)]
  288. /// Create the proposal transaction
  289. mint_proposal: bool,
  290. },
  291. /// Import a base64 encoded and encrypted proposal from stdin
  292. ProposalImport,
  293. /// Vote on a given proposal
  294. Vote {
  295. /// Bulla identifier for the proposal
  296. bulla: String,
  297. /// Vote (0 for NO, 1 for YES)
  298. vote: u8,
  299. /// Optional vote weight (amount of governance tokens)
  300. vote_weight: Option<String>,
  301. },
  302. /// Execute a DAO proposal
  303. Exec {
  304. /// Bulla identifier for the proposal
  305. bulla: String,
  306. },
  307. /// Print the DAO contract base58-encoded spend hook
  308. SpendHook,
  309. }
  310. #[derive(Clone, Debug, Deserialize, StructOpt)]
  311. enum ExplorerSubcmd {
  312. /// Fetch a blockchain transaction by hash
  313. FetchTx {
  314. /// Transaction hash
  315. tx_hash: String,
  316. #[structopt(long)]
  317. /// Print the full transaction information
  318. full: bool,
  319. #[structopt(long)]
  320. /// Encode transaction to base58
  321. encode: bool,
  322. },
  323. /// Read a transaction from stdin and simulate it
  324. SimulateTx,
  325. /// Fetch broadcasted transactions history
  326. TxsHistory {
  327. /// Fetch specific history record (optional)
  328. tx_hash: Option<String>,
  329. #[structopt(long)]
  330. /// Encode specific history record transaction to base58
  331. encode: bool,
  332. },
  333. }
  334. #[derive(Clone, Debug, Deserialize, StructOpt)]
  335. enum AliasSubcmd {
  336. /// Create a Token alias
  337. Add {
  338. /// Token alias
  339. alias: String,
  340. /// Token to create alias for
  341. token: String,
  342. },
  343. /// Print alias info of optional arguments.
  344. /// If no argument is provided, list all the aliases in the wallet.
  345. Show {
  346. /// Token alias to search for
  347. #[structopt(short, long)]
  348. alias: Option<String>,
  349. /// Token to search alias for
  350. #[structopt(short, long)]
  351. token: Option<String>,
  352. },
  353. /// Remove a Token alias
  354. Remove {
  355. /// Token alias to remove
  356. alias: String,
  357. },
  358. }
  359. #[derive(Clone, Debug, Deserialize, StructOpt)]
  360. enum TokenSubcmd {
  361. /// Import a mint authority
  362. Import {
  363. /// Mint authority secret key
  364. secret_key: String,
  365. /// Mint authority token blind
  366. token_blind: String,
  367. },
  368. /// Generate a new mint authority
  369. GenerateMint,
  370. /// List token IDs with available mint authorities
  371. List,
  372. /// Mint tokens
  373. Mint {
  374. /// Token ID to mint
  375. token: String,
  376. /// Amount to mint
  377. amount: String,
  378. /// Recipient of the minted tokens
  379. recipient: String,
  380. /// Optional contract spend hook to use
  381. spend_hook: Option<String>,
  382. /// Optional user data to use
  383. user_data: Option<String>,
  384. },
  385. /// Freeze a token mint
  386. Freeze {
  387. /// Token ID to freeze
  388. token: String,
  389. },
  390. }
  391. #[derive(Clone, Debug, Deserialize, StructOpt)]
  392. enum ContractSubcmd {
  393. /// Generate a new deploy authority
  394. GenerateDeploy,
  395. /// List deploy authorities in the wallet
  396. List,
  397. /// Deploy a smart contract
  398. Deploy {
  399. /// Contract ID (deploy authority)
  400. deploy_auth: u64,
  401. /// Path to contract wasm bincode
  402. wasm_path: String,
  403. /// Path to serialized deploy instruction
  404. deploy_ix: String,
  405. },
  406. /// Lock a smart contract
  407. Lock {
  408. /// Contract ID (deploy authority)
  409. deploy_auth: u64,
  410. },
  411. }
  412. /// Defines a blockchain network configuration.
  413. /// Default values correspond to a local network.
  414. #[derive(Clone, Debug, serde::Deserialize, structopt::StructOpt, structopt_toml::StructOptToml)]
  415. #[structopt()]
  416. struct BlockchainNetwork {
  417. #[structopt(long, default_value = "~/.local/darkfi/drk/localnet/wallet.db")]
  418. /// Path to wallet database
  419. wallet_path: String,
  420. #[structopt(long, default_value = "changeme")]
  421. /// Password for the wallet database
  422. wallet_pass: String,
  423. #[structopt(short, long, default_value = "tcp://127.0.0.1:8240")]
  424. /// darkfid JSON-RPC endpoint
  425. endpoint: Url,
  426. }
  427. /// Auxiliary function to parse darkfid configuration file and extract requested
  428. /// blockchain network config.
  429. async fn parse_blockchain_config(
  430. config: Option<String>,
  431. network: &str,
  432. ) -> Result<BlockchainNetwork> {
  433. // Grab config path
  434. let config_path = get_config_path(config, CONFIG_FILE)?;
  435. // Parse TOML file contents
  436. let contents = read_to_string(&config_path).await?;
  437. let contents: toml::Value = match toml::from_str(&contents) {
  438. Ok(v) => v,
  439. Err(e) => {
  440. eprintln!("Failed parsing TOML config: {e}");
  441. return Err(Error::ParseFailed("Failed parsing TOML config"))
  442. }
  443. };
  444. // Grab requested network config
  445. let Some(table) = contents.as_table() else { return Err(Error::ParseFailed("TOML not a map")) };
  446. let Some(network_configs) = table.get("network_config") else {
  447. return Err(Error::ParseFailed("TOML does not contain network configurations"))
  448. };
  449. let Some(network_configs) = network_configs.as_table() else {
  450. return Err(Error::ParseFailed("`network_config` not a map"))
  451. };
  452. let Some(network_config) = network_configs.get(network) else {
  453. return Err(Error::ParseFailed("TOML does not contain requested network configuration"))
  454. };
  455. let network_config = toml::to_string(&network_config).unwrap();
  456. let network_config =
  457. match BlockchainNetwork::from_iter_with_toml::<Vec<String>>(&network_config, vec![]) {
  458. Ok(v) => v,
  459. Err(e) => {
  460. eprintln!("Failed parsing requested network configuration: {e}");
  461. return Err(Error::ParseFailed("Failed parsing requested network configuration"))
  462. }
  463. };
  464. Ok(network_config)
  465. }
  466. async_daemonize!(realmain);
  467. async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
  468. // Grab blockchain network configuration
  469. let blockchain_config = match args.network.as_str() {
  470. "localnet" => parse_blockchain_config(args.config, "localnet").await?,
  471. "testnet" => parse_blockchain_config(args.config, "testnet").await?,
  472. "mainnet" => parse_blockchain_config(args.config, "mainnet").await?,
  473. _ => {
  474. eprintln!("Unsupported chain `{}`", args.network);
  475. return Err(Error::UnsupportedChain)
  476. }
  477. };
  478. match args.command {
  479. Subcmd::Kaching => {
  480. kaching().await;
  481. Ok(())
  482. }
  483. Subcmd::Ping => {
  484. let drk = Drk::new(
  485. blockchain_config.wallet_path,
  486. blockchain_config.wallet_pass,
  487. Some(blockchain_config.endpoint),
  488. ex,
  489. )
  490. .await?;
  491. drk.ping().await?;
  492. drk.stop_rpc_client().await
  493. }
  494. Subcmd::Completions { shell } => generate_completions(&shell),
  495. Subcmd::Wallet {
  496. initialize,
  497. keygen,
  498. balance,
  499. address,
  500. addresses,
  501. default_address,
  502. secrets,
  503. import_secrets,
  504. tree,
  505. coins,
  506. } => {
  507. if !initialize &&
  508. !keygen &&
  509. !balance &&
  510. !address &&
  511. !addresses &&
  512. default_address.is_none() &&
  513. !secrets &&
  514. !tree &&
  515. !coins &&
  516. !import_secrets
  517. {
  518. eprintln!("Error: You must use at least one flag for this subcommand");
  519. eprintln!("Run with \"wallet -h\" to see the subcommand usage.");
  520. exit(2);
  521. }
  522. let drk =
  523. Drk::new(blockchain_config.wallet_path, blockchain_config.wallet_pass, None, ex)
  524. .await?;
  525. if initialize {
  526. drk.initialize_wallet()?;
  527. if let Err(e) = drk.initialize_money().await {
  528. eprintln!("Failed to initialize Money: {e:?}");
  529. exit(2);
  530. }
  531. if let Err(e) = drk.initialize_dao().await {
  532. eprintln!("Failed to initialize DAO: {e:?}");
  533. exit(2);
  534. }
  535. if let Err(e) = drk.initialize_deployooor() {
  536. eprintln!("Failed to initialize Deployooor: {e:?}");
  537. exit(2);
  538. }
  539. return Ok(())
  540. }
  541. if keygen {
  542. if let Err(e) = drk.money_keygen().await {
  543. eprintln!("Failed to generate keypair: {e:?}");
  544. exit(2);
  545. }
  546. return Ok(())
  547. }
  548. if balance {
  549. let balmap = drk.money_balance().await?;
  550. let aliases_map = drk.get_aliases_mapped_by_token().await?;
  551. // Create a prettytable with the new data:
  552. let mut table = Table::new();
  553. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  554. table.set_titles(row!["Token ID", "Aliases", "Balance"]);
  555. for (token_id, balance) in balmap.iter() {
  556. let aliases = match aliases_map.get(token_id) {
  557. Some(a) => a,
  558. None => "-",
  559. };
  560. table.add_row(row![
  561. token_id,
  562. aliases,
  563. encode_base10(*balance, BALANCE_BASE10_DECIMALS)
  564. ]);
  565. }
  566. if table.is_empty() {
  567. println!("No unspent balances found");
  568. } else {
  569. println!("{table}");
  570. }
  571. return Ok(())
  572. }
  573. if address {
  574. let address = match drk.default_address().await {
  575. Ok(a) => a,
  576. Err(e) => {
  577. eprintln!("Failed to fetch default address: {e:?}");
  578. exit(2);
  579. }
  580. };
  581. println!("{address}");
  582. return Ok(())
  583. }
  584. if addresses {
  585. let addresses = drk.addresses().await?;
  586. // Create a prettytable with the new data:
  587. let mut table = Table::new();
  588. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  589. table.set_titles(row!["Key ID", "Public Key", "Secret Key", "Is Default"]);
  590. for (key_id, public_key, secret_key, is_default) in addresses {
  591. let is_default = match is_default {
  592. 1 => "*",
  593. _ => "",
  594. };
  595. table.add_row(row![key_id, public_key, secret_key, is_default]);
  596. }
  597. if table.is_empty() {
  598. println!("No addresses found");
  599. } else {
  600. println!("{table}");
  601. }
  602. return Ok(())
  603. }
  604. if let Some(idx) = default_address {
  605. if let Err(e) = drk.set_default_address(idx) {
  606. eprintln!("Failed to set default address: {e:?}");
  607. exit(2);
  608. }
  609. return Ok(())
  610. }
  611. if secrets {
  612. let v = drk.get_money_secrets().await?;
  613. for i in v {
  614. println!("{i}");
  615. }
  616. return Ok(())
  617. }
  618. if import_secrets {
  619. let mut secrets = vec![];
  620. let lines = stdin().lines();
  621. for (i, line) in lines.enumerate() {
  622. if let Ok(line) = line {
  623. let bytes = bs58::decode(&line.trim()).into_vec()?;
  624. let Ok(secret) = deserialize_async(&bytes).await else {
  625. println!("Warning: Failed to deserialize secret on line {i}");
  626. continue
  627. };
  628. secrets.push(secret);
  629. }
  630. }
  631. let pubkeys = match drk.import_money_secrets(secrets).await {
  632. Ok(p) => p,
  633. Err(e) => {
  634. eprintln!("Failed to import secret keys into wallet: {e:?}");
  635. exit(2);
  636. }
  637. };
  638. for key in pubkeys {
  639. println!("{key}");
  640. }
  641. return Ok(())
  642. }
  643. if tree {
  644. let tree = drk.get_money_tree().await?;
  645. println!("{tree:#?}");
  646. return Ok(())
  647. }
  648. if coins {
  649. let coins = drk.get_coins(true).await?;
  650. let aliases_map = drk.get_aliases_mapped_by_token().await?;
  651. if coins.is_empty() {
  652. return Ok(())
  653. }
  654. let mut table = Table::new();
  655. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  656. table.set_titles(row![
  657. "Coin",
  658. "Spent",
  659. "Token ID",
  660. "Aliases",
  661. "Value",
  662. "Spend Hook",
  663. "User Data",
  664. "Spent TX"
  665. ]);
  666. for coin in coins {
  667. let aliases = match aliases_map.get(&coin.0.note.token_id.to_string()) {
  668. Some(a) => a,
  669. None => "-",
  670. };
  671. let spend_hook = if coin.0.note.spend_hook != FuncId::none() {
  672. format!("{}", coin.0.note.spend_hook)
  673. } else {
  674. String::from("-")
  675. };
  676. let user_data = if coin.0.note.user_data != pallas::Base::ZERO {
  677. bs58::encode(&serialize_async(&coin.0.note.user_data).await)
  678. .into_string()
  679. .to_string()
  680. } else {
  681. String::from("-")
  682. };
  683. table.add_row(row![
  684. bs58::encode(&serialize_async(&coin.0.coin.inner()).await)
  685. .into_string()
  686. .to_string(),
  687. coin.1,
  688. coin.0.note.token_id,
  689. aliases,
  690. format!(
  691. "{} ({})",
  692. coin.0.note.value,
  693. encode_base10(coin.0.note.value, BALANCE_BASE10_DECIMALS)
  694. ),
  695. spend_hook,
  696. user_data,
  697. coin.2
  698. ]);
  699. }
  700. println!("{table}");
  701. return Ok(())
  702. }
  703. unreachable!()
  704. }
  705. Subcmd::Spend => {
  706. let tx = parse_tx_from_stdin().await?;
  707. let drk =
  708. Drk::new(blockchain_config.wallet_path, blockchain_config.wallet_pass, None, ex)
  709. .await?;
  710. if let Err(e) = drk.mark_tx_spend(&tx).await {
  711. eprintln!("Failed to mark transaction coins as spent: {e:?}");
  712. exit(2);
  713. };
  714. Ok(())
  715. }
  716. Subcmd::Unspend { coin } => {
  717. let bytes: [u8; 32] = match bs58::decode(&coin).into_vec()?.try_into() {
  718. Ok(b) => b,
  719. Err(e) => {
  720. eprintln!("Invalid coin: {e:?}");
  721. exit(2);
  722. }
  723. };
  724. let elem: pallas::Base = match pallas::Base::from_repr(bytes).into() {
  725. Some(v) => v,
  726. None => {
  727. eprintln!("Invalid coin");
  728. exit(2);
  729. }
  730. };
  731. let coin = Coin::from(elem);
  732. let drk =
  733. Drk::new(blockchain_config.wallet_path, blockchain_config.wallet_pass, None, ex)
  734. .await?;
  735. if let Err(e) = drk.unspend_coin(&coin).await {
  736. eprintln!("Failed to mark coin as unspent: {e:?}");
  737. exit(2);
  738. }
  739. Ok(())
  740. }
  741. Subcmd::Transfer { amount, token, recipient, spend_hook, user_data, half_split } => {
  742. let drk = Drk::new(
  743. blockchain_config.wallet_path,
  744. blockchain_config.wallet_pass,
  745. Some(blockchain_config.endpoint),
  746. ex,
  747. )
  748. .await?;
  749. if let Err(e) = f64::from_str(&amount) {
  750. eprintln!("Invalid amount: {e:?}");
  751. exit(2);
  752. }
  753. let rcpt = match PublicKey::from_str(&recipient) {
  754. Ok(r) => r,
  755. Err(e) => {
  756. eprintln!("Invalid recipient: {e:?}");
  757. exit(2);
  758. }
  759. };
  760. let token_id = match drk.get_token(token).await {
  761. Ok(t) => t,
  762. Err(e) => {
  763. eprintln!("Invalid token alias: {e:?}");
  764. exit(2);
  765. }
  766. };
  767. let spend_hook = match spend_hook {
  768. Some(s) => match FuncId::from_str(&s) {
  769. Ok(s) => Some(s),
  770. Err(e) => {
  771. eprintln!("Invalid spend hook: {e:?}");
  772. exit(2);
  773. }
  774. },
  775. None => None,
  776. };
  777. let user_data = match user_data {
  778. Some(u) => {
  779. let bytes: [u8; 32] = match bs58::decode(&u).into_vec()?.try_into() {
  780. Ok(b) => b,
  781. Err(e) => {
  782. eprintln!("Invalid user data: {e:?}");
  783. exit(2);
  784. }
  785. };
  786. match pallas::Base::from_repr(bytes).into() {
  787. Some(v) => Some(v),
  788. None => {
  789. eprintln!("Invalid user data");
  790. exit(2);
  791. }
  792. }
  793. }
  794. None => None,
  795. };
  796. let tx = match drk
  797. .transfer(&amount, token_id, rcpt, spend_hook, user_data, half_split)
  798. .await
  799. {
  800. Ok(t) => t,
  801. Err(e) => {
  802. eprintln!("Failed to create payment transaction: {e:?}");
  803. exit(2);
  804. }
  805. };
  806. println!("{}", base64::encode(&serialize_async(&tx).await));
  807. drk.stop_rpc_client().await
  808. }
  809. Subcmd::Otc { command } => match command {
  810. OtcSubcmd::Init { value_pair, token_pair } => {
  811. let drk = Drk::new(
  812. blockchain_config.wallet_path,
  813. blockchain_config.wallet_pass,
  814. Some(blockchain_config.endpoint),
  815. ex,
  816. )
  817. .await?;
  818. let value_pair = parse_value_pair(&value_pair)?;
  819. let token_pair = parse_token_pair(&drk, &token_pair).await?;
  820. let half = match drk.init_swap(value_pair, token_pair, None, None, None).await {
  821. Ok(h) => h,
  822. Err(e) => {
  823. eprintln!("Failed to create swap transaction half: {e:?}");
  824. exit(2);
  825. }
  826. };
  827. println!("{}", base64::encode(&serialize_async(&half).await));
  828. drk.stop_rpc_client().await
  829. }
  830. OtcSubcmd::Join => {
  831. let mut buf = String::new();
  832. stdin().read_to_string(&mut buf)?;
  833. let Some(bytes) = base64::decode(buf.trim()) else {
  834. eprintln!("Failed to decode partial swap data");
  835. exit(2);
  836. };
  837. let partial: PartialSwapData = deserialize_async(&bytes).await?;
  838. let drk = Drk::new(
  839. blockchain_config.wallet_path,
  840. blockchain_config.wallet_pass,
  841. Some(blockchain_config.endpoint),
  842. ex,
  843. )
  844. .await?;
  845. let tx = match drk.join_swap(partial, None, None, None).await {
  846. Ok(tx) => tx,
  847. Err(e) => {
  848. eprintln!("Failed to create a join swap transaction: {e:?}");
  849. exit(2);
  850. }
  851. };
  852. println!("{}", base64::encode(&serialize_async(&tx).await));
  853. drk.stop_rpc_client().await
  854. }
  855. OtcSubcmd::Inspect => {
  856. let mut buf = String::new();
  857. stdin().read_to_string(&mut buf)?;
  858. let Some(bytes) = base64::decode(buf.trim()) else {
  859. eprintln!("Failed to decode swap transaction");
  860. exit(2);
  861. };
  862. let drk = Drk::new(
  863. blockchain_config.wallet_path,
  864. blockchain_config.wallet_pass,
  865. None,
  866. ex,
  867. )
  868. .await?;
  869. if let Err(e) = drk.inspect_swap(bytes).await {
  870. eprintln!("Failed to inspect swap: {e:?}");
  871. exit(2);
  872. };
  873. Ok(())
  874. }
  875. OtcSubcmd::Sign => {
  876. let mut tx = parse_tx_from_stdin().await?;
  877. let drk = Drk::new(
  878. blockchain_config.wallet_path,
  879. blockchain_config.wallet_pass,
  880. None,
  881. ex,
  882. )
  883. .await?;
  884. if let Err(e) = drk.sign_swap(&mut tx).await {
  885. eprintln!("Failed to sign joined swap transaction: {e:?}");
  886. exit(2);
  887. };
  888. println!("{}", base64::encode(&serialize_async(&tx).await));
  889. Ok(())
  890. }
  891. },
  892. Subcmd::Dao { command } => match command {
  893. DaoSubcmd::Create { proposer_limit, quorum, approval_ratio, gov_token_id } => {
  894. if let Err(e) = f64::from_str(&proposer_limit) {
  895. eprintln!("Invalid proposer limit: {e:?}");
  896. exit(2);
  897. }
  898. if let Err(e) = f64::from_str(&quorum) {
  899. eprintln!("Invalid quorum: {e:?}");
  900. exit(2);
  901. }
  902. let proposer_limit = decode_base10(&proposer_limit, BALANCE_BASE10_DECIMALS, true)?;
  903. let quorum = decode_base10(&quorum, BALANCE_BASE10_DECIMALS, true)?;
  904. if approval_ratio > 1.0 {
  905. eprintln!("Error: Approval ratio cannot be >1.0");
  906. exit(2);
  907. }
  908. let approval_ratio_base = 100_u64;
  909. let approval_ratio_quot = (approval_ratio * approval_ratio_base as f64) as u64;
  910. let drk = Drk::new(
  911. blockchain_config.wallet_path,
  912. blockchain_config.wallet_pass,
  913. None,
  914. ex,
  915. )
  916. .await?;
  917. let gov_token_id = match drk.get_token(gov_token_id).await {
  918. Ok(g) => g,
  919. Err(e) => {
  920. eprintln!("Invalid Token ID: {e:?}");
  921. exit(2);
  922. }
  923. };
  924. let secret_key = SecretKey::random(&mut OsRng);
  925. let bulla_blind = BaseBlind::random(&mut OsRng);
  926. let params = DaoParams::new(
  927. proposer_limit,
  928. quorum,
  929. approval_ratio_base,
  930. approval_ratio_quot,
  931. gov_token_id,
  932. secret_key,
  933. bulla_blind,
  934. );
  935. let encoded = bs58::encode(&serialize_async(&params).await).into_string();
  936. println!("{encoded}");
  937. Ok(())
  938. }
  939. DaoSubcmd::View => {
  940. let mut buf = String::new();
  941. stdin().read_to_string(&mut buf)?;
  942. let bytes = bs58::decode(&buf.trim()).into_vec()?;
  943. let dao_params: DaoParams = deserialize_async(&bytes).await?;
  944. println!("{dao_params}");
  945. Ok(())
  946. }
  947. DaoSubcmd::Import { name } => {
  948. let mut buf = String::new();
  949. stdin().read_to_string(&mut buf)?;
  950. let bytes = bs58::decode(&buf.trim()).into_vec()?;
  951. let params: DaoParams = deserialize_async(&bytes).await?;
  952. let drk = Drk::new(
  953. blockchain_config.wallet_path,
  954. blockchain_config.wallet_pass,
  955. None,
  956. ex,
  957. )
  958. .await?;
  959. if let Err(e) = drk.import_dao(&name, params).await {
  960. eprintln!("Failed to import DAO: {e:?}");
  961. exit(2);
  962. }
  963. Ok(())
  964. }
  965. DaoSubcmd::List { name } => {
  966. let drk = Drk::new(
  967. blockchain_config.wallet_path,
  968. blockchain_config.wallet_pass,
  969. None,
  970. ex,
  971. )
  972. .await?;
  973. if let Err(e) = drk.dao_list(&name).await {
  974. eprintln!("Failed to list DAO: {e:?}");
  975. exit(2);
  976. }
  977. Ok(())
  978. }
  979. DaoSubcmd::Balance { name } => {
  980. let drk = Drk::new(
  981. blockchain_config.wallet_path,
  982. blockchain_config.wallet_pass,
  983. None,
  984. ex,
  985. )
  986. .await?;
  987. let balmap = match drk.dao_balance(&name).await {
  988. Ok(b) => b,
  989. Err(e) => {
  990. eprintln!("Failed to fetch DAO balance: {e:?}");
  991. exit(2);
  992. }
  993. };
  994. let aliases_map = match drk.get_aliases_mapped_by_token().await {
  995. Ok(a) => a,
  996. Err(e) => {
  997. eprintln!("Failed to fetch wallet aliases: {e:?}");
  998. exit(2);
  999. }
  1000. };
  1001. // Create a prettytable with the new data:
  1002. let mut table = Table::new();
  1003. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  1004. table.set_titles(row!["Token ID", "Aliases", "Balance"]);
  1005. for (token_id, balance) in balmap.iter() {
  1006. let aliases = match aliases_map.get(token_id) {
  1007. Some(a) => a,
  1008. None => "-",
  1009. };
  1010. table.add_row(row![
  1011. token_id,
  1012. aliases,
  1013. encode_base10(*balance, BALANCE_BASE10_DECIMALS)
  1014. ]);
  1015. }
  1016. if table.is_empty() {
  1017. println!("No unspent balances found");
  1018. } else {
  1019. println!("{table}");
  1020. }
  1021. Ok(())
  1022. }
  1023. DaoSubcmd::Mint { name } => {
  1024. let drk = Drk::new(
  1025. blockchain_config.wallet_path,
  1026. blockchain_config.wallet_pass,
  1027. Some(blockchain_config.endpoint),
  1028. ex,
  1029. )
  1030. .await?;
  1031. let tx = match drk.dao_mint(&name).await {
  1032. Ok(tx) => tx,
  1033. Err(e) => {
  1034. eprintln!("Failed to mint DAO: {e:?}");
  1035. exit(2);
  1036. }
  1037. };
  1038. println!("{}", base64::encode(&serialize_async(&tx).await));
  1039. drk.stop_rpc_client().await
  1040. }
  1041. DaoSubcmd::ProposeTransfer {
  1042. name,
  1043. duration,
  1044. amount,
  1045. token,
  1046. recipient,
  1047. spend_hook,
  1048. user_data,
  1049. } => {
  1050. let drk = Drk::new(
  1051. blockchain_config.wallet_path,
  1052. blockchain_config.wallet_pass,
  1053. Some(blockchain_config.endpoint),
  1054. ex,
  1055. )
  1056. .await?;
  1057. if let Err(e) = f64::from_str(&amount) {
  1058. eprintln!("Invalid amount: {e:?}");
  1059. exit(2);
  1060. }
  1061. let rcpt = match PublicKey::from_str(&recipient) {
  1062. Ok(r) => r,
  1063. Err(e) => {
  1064. eprintln!("Invalid recipient: {e:?}");
  1065. exit(2);
  1066. }
  1067. };
  1068. let token_id = match drk.get_token(token).await {
  1069. Ok(t) => t,
  1070. Err(e) => {
  1071. eprintln!("Invalid token alias: {e:?}");
  1072. exit(2);
  1073. }
  1074. };
  1075. let spend_hook = match spend_hook {
  1076. Some(s) => match FuncId::from_str(&s) {
  1077. Ok(s) => Some(s),
  1078. Err(e) => {
  1079. eprintln!("Invalid spend hook: {e:?}");
  1080. exit(2);
  1081. }
  1082. },
  1083. None => None,
  1084. };
  1085. let user_data = match user_data {
  1086. Some(u) => {
  1087. let bytes: [u8; 32] = match bs58::decode(&u).into_vec()?.try_into() {
  1088. Ok(b) => b,
  1089. Err(e) => {
  1090. eprintln!("Invalid user data: {e:?}");
  1091. exit(2);
  1092. }
  1093. };
  1094. match pallas::Base::from_repr(bytes).into() {
  1095. Some(v) => Some(v),
  1096. None => {
  1097. eprintln!("Invalid user data");
  1098. exit(2);
  1099. }
  1100. }
  1101. }
  1102. None => None,
  1103. };
  1104. let proposal = match drk
  1105. .dao_propose_transfer(
  1106. &name, duration, &amount, token_id, rcpt, spend_hook, user_data,
  1107. )
  1108. .await
  1109. {
  1110. Ok(p) => p,
  1111. Err(e) => {
  1112. eprintln!("Failed to create DAO transfer proposal: {e:?}");
  1113. exit(2);
  1114. }
  1115. };
  1116. println!("Generated proposal: {}", proposal.bulla());
  1117. drk.stop_rpc_client().await
  1118. }
  1119. DaoSubcmd::Proposals { name } => {
  1120. let drk = Drk::new(
  1121. blockchain_config.wallet_path,
  1122. blockchain_config.wallet_pass,
  1123. None,
  1124. ex,
  1125. )
  1126. .await?;
  1127. let proposals = drk.get_dao_proposals(&name).await?;
  1128. for (i, proposal) in proposals.iter().enumerate() {
  1129. println!("{i}. {}", proposal.bulla());
  1130. }
  1131. Ok(())
  1132. }
  1133. DaoSubcmd::Proposal { bulla, export, mint_proposal } => {
  1134. let bulla = match DaoProposalBulla::from_str(&bulla) {
  1135. Ok(b) => b,
  1136. Err(e) => {
  1137. eprintln!("Invalid proposal bulla: {e:?}");
  1138. exit(2);
  1139. }
  1140. };
  1141. let drk = Drk::new(
  1142. blockchain_config.wallet_path,
  1143. blockchain_config.wallet_pass,
  1144. Some(blockchain_config.endpoint),
  1145. ex,
  1146. )
  1147. .await?;
  1148. let proposal = drk.get_dao_proposal_by_bulla(&bulla).await?;
  1149. if export {
  1150. // Retrieve the DAO
  1151. let dao = drk.get_dao_by_bulla(&proposal.proposal.dao_bulla).await?;
  1152. // Encypt the proposal
  1153. let enc_note = AeadEncryptedNote::encrypt(
  1154. &proposal,
  1155. &dao.params.dao.public_key,
  1156. &mut OsRng,
  1157. )
  1158. .unwrap();
  1159. // Export it to base64
  1160. println!("{}", base64::encode(&serialize_async(&enc_note).await));
  1161. return drk.stop_rpc_client().await
  1162. }
  1163. if mint_proposal {
  1164. for call in &proposal.proposal.auth_calls {
  1165. if call.function_code == DaoFunction::AuthMoneyTransfer as u8 {
  1166. let tx = match drk.dao_transfer_proposal_tx(&proposal).await {
  1167. Ok(tx) => tx,
  1168. Err(e) => {
  1169. eprintln!("Failed to create DAO transfer proposal: {e:?}");
  1170. exit(2);
  1171. }
  1172. };
  1173. println!("{}", base64::encode(&serialize_async(&tx).await));
  1174. return drk.stop_rpc_client().await
  1175. }
  1176. }
  1177. eprintln!("Unsuported DAO proposal");
  1178. exit(2);
  1179. }
  1180. println!("{proposal}");
  1181. let mut contract_calls = "\nInvoked contracts:\n".to_string();
  1182. for call in proposal.proposal.auth_calls {
  1183. contract_calls.push_str(&format!(
  1184. "\tContract: {}\n\tFunction: {}\n\tData: ",
  1185. call.contract_id, call.function_code
  1186. ));
  1187. if call.auth_data.is_empty() {
  1188. contract_calls.push_str("-\n");
  1189. continue;
  1190. }
  1191. if call.function_code == DaoFunction::AuthMoneyTransfer as u8 {
  1192. // We know that the plaintext data live in the data plaintext vec
  1193. if proposal.data.is_none() {
  1194. contract_calls.push_str("-\n");
  1195. continue;
  1196. }
  1197. let coin: CoinAttributes =
  1198. deserialize_async(proposal.data.as_ref().unwrap()).await?;
  1199. let spend_hook = if coin.spend_hook == FuncId::none() {
  1200. "-".to_string()
  1201. } else {
  1202. format!("{}", coin.spend_hook)
  1203. };
  1204. let user_data = if coin.user_data == pallas::Base::ZERO {
  1205. "-".to_string()
  1206. } else {
  1207. format!("{:?}", coin.user_data)
  1208. };
  1209. contract_calls.push_str(&format!("\n\t\t{}: {}\n\t\t{}: {} ({})\n\t\t{}: {}\n\t\t{}: {}\n\t\t{}: {}\n\t\t{}: {}\n\n",
  1210. "Recipient",
  1211. coin.public_key,
  1212. "Amount",
  1213. coin.value,
  1214. encode_base10(coin.value, BALANCE_BASE10_DECIMALS),
  1215. "Token",
  1216. coin.token_id,
  1217. "Spend hook",
  1218. spend_hook,
  1219. "User data",
  1220. user_data,
  1221. "Blind",
  1222. coin.blind));
  1223. }
  1224. }
  1225. println!("{contract_calls}");
  1226. let votes = drk.get_dao_proposal_votes(&bulla).await?;
  1227. let mut total_yes_vote_value = 0;
  1228. let mut total_no_vote_value = 0;
  1229. let mut total_all_vote_value = 0;
  1230. let mut table = Table::new();
  1231. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  1232. table.set_titles(row!["Transaction", "Tokens", "Vote"]);
  1233. for vote in votes {
  1234. let vote_option = if vote.vote_option {
  1235. total_yes_vote_value += vote.all_vote_value;
  1236. "Yes"
  1237. } else {
  1238. total_no_vote_value += vote.all_vote_value;
  1239. "No"
  1240. };
  1241. total_all_vote_value += vote.all_vote_value;
  1242. table.add_row(row![
  1243. vote.tx_hash,
  1244. encode_base10(vote.all_vote_value, BALANCE_BASE10_DECIMALS),
  1245. vote_option
  1246. ]);
  1247. }
  1248. let outcome = if table.is_empty() {
  1249. println!("Votes: No votes found");
  1250. "Rejected"
  1251. } else {
  1252. println!("Votes:");
  1253. println!("{table}");
  1254. println!(
  1255. "Total tokens votes: {}",
  1256. encode_base10(total_all_vote_value, BALANCE_BASE10_DECIMALS)
  1257. );
  1258. let approval_ratio =
  1259. (total_yes_vote_value as f64 * 100.0) / total_all_vote_value as f64;
  1260. println!(
  1261. "Total tokens Yes votes: {} ({approval_ratio:.2}%)",
  1262. encode_base10(total_yes_vote_value, BALANCE_BASE10_DECIMALS)
  1263. );
  1264. println!(
  1265. "Total tokens No votes: {} ({:.2}%)",
  1266. encode_base10(total_no_vote_value, BALANCE_BASE10_DECIMALS),
  1267. (total_no_vote_value as f64 * 100.0) / total_all_vote_value as f64
  1268. );
  1269. let dao = drk.get_dao_by_bulla(&proposal.proposal.dao_bulla).await?;
  1270. if total_all_vote_value >= dao.params.dao.quorum &&
  1271. approval_ratio >=
  1272. (dao.params.dao.approval_ratio_quot /
  1273. dao.params.dao.approval_ratio_base)
  1274. as f64
  1275. {
  1276. "Approved"
  1277. } else {
  1278. "Rejected"
  1279. }
  1280. };
  1281. if let Some(exec_tx_hash) = proposal.exec_tx_hash {
  1282. println!("Proposal was executed on transaction: {exec_tx_hash}");
  1283. return drk.stop_rpc_client().await
  1284. }
  1285. // Retrieve next block height and current block time target,
  1286. // to compute their window.
  1287. let next_block_height = drk.get_next_block_height().await?;
  1288. let block_target = drk.get_block_target().await?;
  1289. let current_window = blockwindow(next_block_height, block_target);
  1290. let end_time = proposal.proposal.creation_blockwindow +
  1291. proposal.proposal.duration_blockwindows;
  1292. let (voting_status, proposal_status_message) = if current_window < end_time {
  1293. ("Ongoing", format!("Current proposal outcome: {outcome}"))
  1294. } else {
  1295. ("Concluded", format!("Proposal outcome: {outcome}"))
  1296. };
  1297. println!("Voting status: {voting_status}");
  1298. println!("{proposal_status_message}");
  1299. drk.stop_rpc_client().await
  1300. }
  1301. DaoSubcmd::ProposalImport {} => {
  1302. let mut buf = String::new();
  1303. stdin().read_to_string(&mut buf)?;
  1304. let Some(bytes) = base64::decode(buf.trim()) else {
  1305. eprintln!("Failed to decode encrypted proposal data");
  1306. exit(2);
  1307. };
  1308. let encrypted_proposal: AeadEncryptedNote = deserialize_async(&bytes).await?;
  1309. let drk = Drk::new(
  1310. blockchain_config.wallet_path,
  1311. blockchain_config.wallet_pass,
  1312. None,
  1313. ex,
  1314. )
  1315. .await?;
  1316. // Retrieve all DAOs to try to decrypt the proposal
  1317. let daos = drk.get_daos().await?;
  1318. for dao in &daos {
  1319. if let Ok(proposal) =
  1320. encrypted_proposal.decrypt::<ProposalRecord>(&dao.params.secret_key)
  1321. {
  1322. let proposal = match drk.get_dao_proposal_by_bulla(&proposal.bulla()).await
  1323. {
  1324. Ok(p) => {
  1325. let mut our_proposal = p;
  1326. our_proposal.data = proposal.data;
  1327. our_proposal
  1328. }
  1329. Err(_) => proposal,
  1330. };
  1331. return drk.put_dao_proposal(&proposal).await
  1332. }
  1333. }
  1334. eprintln!("Couldn't decrypt the proposal with out DAO keys");
  1335. exit(2);
  1336. }
  1337. DaoSubcmd::Vote { bulla, vote, vote_weight } => {
  1338. let bulla = match DaoProposalBulla::from_str(&bulla) {
  1339. Ok(b) => b,
  1340. Err(e) => {
  1341. eprintln!("Invalid proposal bulla: {e:?}");
  1342. exit(2);
  1343. }
  1344. };
  1345. if vote > 1 {
  1346. eprintln!("Vote can be either 0 (NO) or 1 (YES)");
  1347. exit(2);
  1348. }
  1349. let vote = vote != 0;
  1350. let weight = match vote_weight {
  1351. Some(w) => {
  1352. if let Err(e) = f64::from_str(&w) {
  1353. eprintln!("Invalid vote weight: {e:?}");
  1354. exit(2);
  1355. }
  1356. Some(decode_base10(&w, BALANCE_BASE10_DECIMALS, true)?)
  1357. }
  1358. None => None,
  1359. };
  1360. let drk = Drk::new(
  1361. blockchain_config.wallet_path,
  1362. blockchain_config.wallet_pass,
  1363. Some(blockchain_config.endpoint),
  1364. ex,
  1365. )
  1366. .await?;
  1367. let tx = match drk.dao_vote(&bulla, vote, weight).await {
  1368. Ok(tx) => tx,
  1369. Err(e) => {
  1370. eprintln!("Failed to create DAO Vote transaction: {e:?}");
  1371. exit(2);
  1372. }
  1373. };
  1374. println!("{}", base64::encode(&serialize_async(&tx).await));
  1375. drk.stop_rpc_client().await
  1376. }
  1377. DaoSubcmd::Exec { bulla } => {
  1378. let bulla = match DaoProposalBulla::from_str(&bulla) {
  1379. Ok(b) => b,
  1380. Err(e) => {
  1381. eprintln!("Invalid proposal bulla: {e:?}");
  1382. exit(2);
  1383. }
  1384. };
  1385. let drk = Drk::new(
  1386. blockchain_config.wallet_path,
  1387. blockchain_config.wallet_pass,
  1388. Some(blockchain_config.endpoint),
  1389. ex,
  1390. )
  1391. .await?;
  1392. let proposal = drk.get_dao_proposal_by_bulla(&bulla).await?;
  1393. for call in &proposal.proposal.auth_calls {
  1394. if call.function_code == DaoFunction::AuthMoneyTransfer as u8 {
  1395. let tx = match drk.dao_exec_transfer(&proposal).await {
  1396. Ok(tx) => tx,
  1397. Err(e) => {
  1398. eprintln!("Failed to execute DAO transfer proposal: {e:?}");
  1399. exit(2);
  1400. }
  1401. };
  1402. println!("{}", base64::encode(&serialize_async(&tx).await));
  1403. return drk.stop_rpc_client().await
  1404. }
  1405. }
  1406. eprintln!("Unsuported DAO proposal");
  1407. exit(2);
  1408. }
  1409. DaoSubcmd::SpendHook => {
  1410. let spend_hook =
  1411. FuncRef { contract_id: *DAO_CONTRACT_ID, func_code: DaoFunction::Exec as u8 }
  1412. .to_func_id();
  1413. println!("{spend_hook}");
  1414. Ok(())
  1415. }
  1416. },
  1417. Subcmd::AttachFee => {
  1418. let mut tx = parse_tx_from_stdin().await?;
  1419. let drk = Drk::new(
  1420. blockchain_config.wallet_path,
  1421. blockchain_config.wallet_pass,
  1422. Some(blockchain_config.endpoint),
  1423. ex,
  1424. )
  1425. .await?;
  1426. if let Err(e) = drk.attach_fee(&mut tx).await {
  1427. eprintln!("Failed to attach the fee call to the transaction: {e:?}");
  1428. exit(2);
  1429. };
  1430. println!("{}", base64::encode(&serialize_async(&tx).await));
  1431. drk.stop_rpc_client().await
  1432. }
  1433. Subcmd::Inspect => {
  1434. let tx = parse_tx_from_stdin().await?;
  1435. println!("{tx:#?}");
  1436. Ok(())
  1437. }
  1438. Subcmd::Broadcast => {
  1439. let tx = parse_tx_from_stdin().await?;
  1440. let drk = Drk::new(
  1441. blockchain_config.wallet_path,
  1442. blockchain_config.wallet_pass,
  1443. Some(blockchain_config.endpoint),
  1444. ex,
  1445. )
  1446. .await?;
  1447. if let Err(e) = drk.simulate_tx(&tx).await {
  1448. eprintln!("Failed to simulate tx: {e:?}");
  1449. exit(2);
  1450. };
  1451. if let Err(e) = drk.mark_tx_spend(&tx).await {
  1452. eprintln!("Failed to mark transaction coins as spent: {e:?}");
  1453. exit(2);
  1454. };
  1455. let txid = match drk.broadcast_tx(&tx).await {
  1456. Ok(t) => t,
  1457. Err(e) => {
  1458. eprintln!("Failed to broadcast transaction: {e:?}");
  1459. exit(2);
  1460. }
  1461. };
  1462. println!("Transaction ID: {txid}");
  1463. drk.stop_rpc_client().await
  1464. }
  1465. Subcmd::Subscribe => {
  1466. let drk = Drk::new(
  1467. blockchain_config.wallet_path,
  1468. blockchain_config.wallet_pass,
  1469. Some(blockchain_config.endpoint.clone()),
  1470. ex.clone(),
  1471. )
  1472. .await?;
  1473. if let Err(e) = drk.subscribe_blocks(blockchain_config.endpoint, ex).await {
  1474. eprintln!("Block subscription failed: {e:?}");
  1475. exit(2);
  1476. }
  1477. drk.stop_rpc_client().await
  1478. }
  1479. Subcmd::Scan { reset } => {
  1480. let drk = Drk::new(
  1481. blockchain_config.wallet_path,
  1482. blockchain_config.wallet_pass,
  1483. Some(blockchain_config.endpoint),
  1484. ex,
  1485. )
  1486. .await?;
  1487. if reset {
  1488. println!("Reset requested.");
  1489. if let Err(e) = drk.scan_blocks(true).await {
  1490. eprintln!("Failed during scanning: {e:?}");
  1491. exit(2);
  1492. }
  1493. println!("Finished scanning blockchain");
  1494. return drk.stop_rpc_client().await
  1495. }
  1496. if let Err(e) = drk.scan_blocks(false).await {
  1497. eprintln!("Failed during scanning: {e:?}");
  1498. exit(2);
  1499. }
  1500. println!("Finished scanning blockchain");
  1501. drk.stop_rpc_client().await
  1502. }
  1503. Subcmd::Explorer { command } => match command {
  1504. ExplorerSubcmd::FetchTx { tx_hash, full, encode } => {
  1505. let tx_hash = TransactionHash(*blake3::Hash::from_hex(&tx_hash)?.as_bytes());
  1506. let drk = Drk::new(
  1507. blockchain_config.wallet_path,
  1508. blockchain_config.wallet_pass,
  1509. Some(blockchain_config.endpoint),
  1510. ex,
  1511. )
  1512. .await?;
  1513. let tx = match drk.get_tx(&tx_hash).await {
  1514. Ok(tx) => tx,
  1515. Err(e) => {
  1516. eprintln!("Failed to fetch transaction: {e:?}");
  1517. exit(2);
  1518. }
  1519. };
  1520. let Some(tx) = tx else {
  1521. println!("Transaction was not found");
  1522. exit(1);
  1523. };
  1524. // Make sure the tx is correct
  1525. assert_eq!(tx.hash(), tx_hash);
  1526. if encode {
  1527. println!("{}", base64::encode(&serialize_async(&tx).await));
  1528. exit(1)
  1529. }
  1530. println!("Transaction ID: {tx_hash}");
  1531. if full {
  1532. println!("{tx:?}");
  1533. }
  1534. drk.stop_rpc_client().await
  1535. }
  1536. ExplorerSubcmd::SimulateTx => {
  1537. let tx = parse_tx_from_stdin().await?;
  1538. let drk = Drk::new(
  1539. blockchain_config.wallet_path,
  1540. blockchain_config.wallet_pass,
  1541. Some(blockchain_config.endpoint),
  1542. ex,
  1543. )
  1544. .await?;
  1545. let is_valid = match drk.simulate_tx(&tx).await {
  1546. Ok(b) => b,
  1547. Err(e) => {
  1548. eprintln!("Failed to simulate tx: {e:?}");
  1549. exit(2);
  1550. }
  1551. };
  1552. println!("Transaction ID: {}", tx.hash());
  1553. println!("State: {}", if is_valid { "valid" } else { "invalid" });
  1554. drk.stop_rpc_client().await
  1555. }
  1556. ExplorerSubcmd::TxsHistory { tx_hash, encode } => {
  1557. let drk = Drk::new(
  1558. blockchain_config.wallet_path,
  1559. blockchain_config.wallet_pass,
  1560. None,
  1561. ex,
  1562. )
  1563. .await?;
  1564. if let Some(c) = tx_hash {
  1565. let (tx_hash, status, tx) = drk.get_tx_history_record(&c).await?;
  1566. if encode {
  1567. println!("{}", base64::encode(&serialize_async(&tx).await));
  1568. exit(1)
  1569. }
  1570. println!("Transaction ID: {tx_hash}");
  1571. println!("Status: {status}");
  1572. println!("{tx:?}");
  1573. return Ok(())
  1574. }
  1575. let map = match drk.get_txs_history() {
  1576. Ok(m) => m,
  1577. Err(e) => {
  1578. eprintln!("Failed to retrieve transactions history records: {e:?}");
  1579. exit(2);
  1580. }
  1581. };
  1582. // Create a prettytable with the new data:
  1583. let mut table = Table::new();
  1584. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  1585. table.set_titles(row!["Transaction Hash", "Status"]);
  1586. for (txs_hash, status) in map.iter() {
  1587. table.add_row(row![txs_hash, status]);
  1588. }
  1589. if table.is_empty() {
  1590. println!("No transactions found");
  1591. } else {
  1592. println!("{table}");
  1593. }
  1594. Ok(())
  1595. }
  1596. },
  1597. Subcmd::Alias { command } => match command {
  1598. AliasSubcmd::Add { alias, token } => {
  1599. if alias.chars().count() > 5 {
  1600. eprintln!("Error: Alias exceeds 5 characters");
  1601. exit(2);
  1602. }
  1603. let token_id = match TokenId::from_str(token.as_str()) {
  1604. Ok(t) => t,
  1605. Err(e) => {
  1606. eprintln!("Invalid Token ID: {e:?}");
  1607. exit(2);
  1608. }
  1609. };
  1610. let drk = Drk::new(
  1611. blockchain_config.wallet_path,
  1612. blockchain_config.wallet_pass,
  1613. None,
  1614. ex,
  1615. )
  1616. .await?;
  1617. if let Err(e) = drk.add_alias(alias, token_id).await {
  1618. eprintln!("Failed to add alias: {e:?}");
  1619. exit(2);
  1620. }
  1621. Ok(())
  1622. }
  1623. AliasSubcmd::Show { alias, token } => {
  1624. let token_id = match token {
  1625. Some(t) => match TokenId::from_str(t.as_str()) {
  1626. Ok(t) => Some(t),
  1627. Err(e) => {
  1628. eprintln!("Invalid Token ID: {e:?}");
  1629. exit(2);
  1630. }
  1631. },
  1632. None => None,
  1633. };
  1634. let drk = Drk::new(
  1635. blockchain_config.wallet_path,
  1636. blockchain_config.wallet_pass,
  1637. None,
  1638. ex,
  1639. )
  1640. .await?;
  1641. let map = drk.get_aliases(alias, token_id).await?;
  1642. // Create a prettytable with the new data:
  1643. let mut table = Table::new();
  1644. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  1645. table.set_titles(row!["Alias", "Token ID"]);
  1646. for (alias, token_id) in map.iter() {
  1647. table.add_row(row![alias, token_id]);
  1648. }
  1649. if table.is_empty() {
  1650. println!("No aliases found");
  1651. } else {
  1652. println!("{table}");
  1653. }
  1654. Ok(())
  1655. }
  1656. AliasSubcmd::Remove { alias } => {
  1657. let drk = Drk::new(
  1658. blockchain_config.wallet_path,
  1659. blockchain_config.wallet_pass,
  1660. None,
  1661. ex,
  1662. )
  1663. .await?;
  1664. if let Err(e) = drk.remove_alias(alias).await {
  1665. eprintln!("Failed to remove alias: {e:?}");
  1666. exit(2);
  1667. }
  1668. Ok(())
  1669. }
  1670. },
  1671. Subcmd::Token { command } => match command {
  1672. TokenSubcmd::Import { secret_key, token_blind } => {
  1673. let mint_authority = match SecretKey::from_str(&secret_key) {
  1674. Ok(ma) => ma,
  1675. Err(e) => {
  1676. eprintln!("Invalid mint authority: {e:?}");
  1677. exit(2);
  1678. }
  1679. };
  1680. let token_blind = match BaseBlind::from_str(&token_blind) {
  1681. Ok(tb) => tb,
  1682. Err(e) => {
  1683. eprintln!("Invalid token blind: {e:?}");
  1684. exit(2);
  1685. }
  1686. };
  1687. let drk = Drk::new(
  1688. blockchain_config.wallet_path,
  1689. blockchain_config.wallet_pass,
  1690. None,
  1691. ex,
  1692. )
  1693. .await?;
  1694. let token_id = drk.import_mint_authority(mint_authority, token_blind).await?;
  1695. println!("Successfully imported mint authority for token ID: {token_id}");
  1696. Ok(())
  1697. }
  1698. TokenSubcmd::GenerateMint => {
  1699. let drk = Drk::new(
  1700. blockchain_config.wallet_path,
  1701. blockchain_config.wallet_pass,
  1702. None,
  1703. ex,
  1704. )
  1705. .await?;
  1706. let mint_authority = SecretKey::random(&mut OsRng);
  1707. let token_blind = BaseBlind::random(&mut OsRng);
  1708. let token_id = drk.import_mint_authority(mint_authority, token_blind).await?;
  1709. println!("Successfully imported mint authority for token ID: {token_id}");
  1710. Ok(())
  1711. }
  1712. TokenSubcmd::List => {
  1713. let drk = Drk::new(
  1714. blockchain_config.wallet_path,
  1715. blockchain_config.wallet_pass,
  1716. None,
  1717. ex,
  1718. )
  1719. .await?;
  1720. let tokens = drk.get_mint_authorities().await?;
  1721. let aliases_map = match drk.get_aliases_mapped_by_token().await {
  1722. Ok(map) => map,
  1723. Err(e) => {
  1724. eprintln!("Failed to fetch wallet aliases: {e:?}");
  1725. exit(2);
  1726. }
  1727. };
  1728. let mut table = Table::new();
  1729. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  1730. table.set_titles(row![
  1731. "Token ID",
  1732. "Aliases",
  1733. "Mint Authority",
  1734. "Token Blind",
  1735. "Frozen"
  1736. ]);
  1737. for (token_id, authority, blind, frozen) in tokens {
  1738. let aliases = match aliases_map.get(&token_id.to_string()) {
  1739. Some(a) => a,
  1740. None => "-",
  1741. };
  1742. table.add_row(row![token_id, aliases, authority, blind, frozen]);
  1743. }
  1744. if table.is_empty() {
  1745. println!("No tokens found");
  1746. } else {
  1747. println!("{table}");
  1748. }
  1749. Ok(())
  1750. }
  1751. TokenSubcmd::Mint { token, amount, recipient, spend_hook, user_data } => {
  1752. let drk = Drk::new(
  1753. blockchain_config.wallet_path,
  1754. blockchain_config.wallet_pass,
  1755. Some(blockchain_config.endpoint),
  1756. ex,
  1757. )
  1758. .await?;
  1759. if let Err(e) = f64::from_str(&amount) {
  1760. eprintln!("Invalid amount: {e:?}");
  1761. exit(2);
  1762. }
  1763. let rcpt = match PublicKey::from_str(&recipient) {
  1764. Ok(r) => r,
  1765. Err(e) => {
  1766. eprintln!("Invalid recipient: {e:?}");
  1767. exit(2);
  1768. }
  1769. };
  1770. let token_id = match drk.get_token(token).await {
  1771. Ok(t) => t,
  1772. Err(e) => {
  1773. eprintln!("Invalid Token ID: {e:?}");
  1774. exit(2);
  1775. }
  1776. };
  1777. let spend_hook = match spend_hook {
  1778. Some(s) => match FuncId::from_str(&s) {
  1779. Ok(s) => Some(s),
  1780. Err(e) => {
  1781. eprintln!("Invalid spend hook: {e:?}");
  1782. exit(2);
  1783. }
  1784. },
  1785. None => None,
  1786. };
  1787. let user_data = match user_data {
  1788. Some(u) => {
  1789. let bytes: [u8; 32] = match bs58::decode(&u).into_vec()?.try_into() {
  1790. Ok(b) => b,
  1791. Err(e) => {
  1792. eprintln!("Invalid user data: {e:?}");
  1793. exit(2);
  1794. }
  1795. };
  1796. match pallas::Base::from_repr(bytes).into() {
  1797. Some(v) => Some(v),
  1798. None => {
  1799. eprintln!("Invalid user data");
  1800. exit(2);
  1801. }
  1802. }
  1803. }
  1804. None => None,
  1805. };
  1806. let tx = match drk.mint_token(&amount, rcpt, token_id, spend_hook, user_data).await
  1807. {
  1808. Ok(tx) => tx,
  1809. Err(e) => {
  1810. eprintln!("Failed to create token mint transaction: {e:?}");
  1811. exit(2);
  1812. }
  1813. };
  1814. println!("{}", base64::encode(&serialize_async(&tx).await));
  1815. drk.stop_rpc_client().await
  1816. }
  1817. TokenSubcmd::Freeze { token } => {
  1818. let drk = Drk::new(
  1819. blockchain_config.wallet_path,
  1820. blockchain_config.wallet_pass,
  1821. Some(blockchain_config.endpoint),
  1822. ex,
  1823. )
  1824. .await?;
  1825. let token_id = match drk.get_token(token).await {
  1826. Ok(t) => t,
  1827. Err(e) => {
  1828. eprintln!("Invalid Token ID: {e:?}");
  1829. exit(2);
  1830. }
  1831. };
  1832. let tx = match drk.freeze_token(token_id).await {
  1833. Ok(tx) => tx,
  1834. Err(e) => {
  1835. eprintln!("Failed to create token freeze transaction: {e:?}");
  1836. exit(2);
  1837. }
  1838. };
  1839. println!("{}", base64::encode(&serialize_async(&tx).await));
  1840. drk.stop_rpc_client().await
  1841. }
  1842. },
  1843. Subcmd::Contract { command } => match command {
  1844. ContractSubcmd::GenerateDeploy => {
  1845. let drk = Drk::new(
  1846. blockchain_config.wallet_path,
  1847. blockchain_config.wallet_pass,
  1848. None,
  1849. ex,
  1850. )
  1851. .await?;
  1852. if let Err(e) = drk.deploy_auth_keygen().await {
  1853. eprintln!("Error creating deploy auth keypair: {:?}", e);
  1854. exit(2);
  1855. }
  1856. Ok(())
  1857. }
  1858. ContractSubcmd::List => {
  1859. let drk = Drk::new(
  1860. blockchain_config.wallet_path,
  1861. blockchain_config.wallet_pass,
  1862. None,
  1863. ex,
  1864. )
  1865. .await?;
  1866. let auths = drk.list_deploy_auth().await?;
  1867. let mut table = Table::new();
  1868. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  1869. table.set_titles(row!["Index", "Contract ID", "Frozen"]);
  1870. for (idx, contract_id, frozen) in auths {
  1871. table.add_row(row![idx, contract_id, frozen]);
  1872. }
  1873. if table.is_empty() {
  1874. println!("No deploy authorities found");
  1875. } else {
  1876. println!("{table}");
  1877. }
  1878. Ok(())
  1879. }
  1880. ContractSubcmd::Deploy { deploy_auth, wasm_path, deploy_ix } => {
  1881. // Read the wasm bincode and deploy instruction
  1882. let wasm_bin = smol::fs::read(expand_path(&wasm_path)?).await?;
  1883. let deploy_ix = smol::fs::read(expand_path(&deploy_ix)?).await?;
  1884. let drk = Drk::new(
  1885. blockchain_config.wallet_path,
  1886. blockchain_config.wallet_pass,
  1887. Some(blockchain_config.endpoint),
  1888. ex,
  1889. )
  1890. .await?;
  1891. let mut tx = match drk.deploy_contract(deploy_auth, wasm_bin, deploy_ix).await {
  1892. Ok(v) => v,
  1893. Err(e) => {
  1894. eprintln!("Error creating contract deployment tx: {}", e);
  1895. exit(2);
  1896. }
  1897. };
  1898. if let Err(e) = drk.attach_fee(&mut tx).await {
  1899. eprintln!("Failed to attach the fee call to the transaction: {e:?}");
  1900. exit(2);
  1901. };
  1902. println!("{}", base64::encode(&serialize_async(&tx).await));
  1903. drk.stop_rpc_client().await
  1904. }
  1905. ContractSubcmd::Lock { deploy_auth } => {
  1906. let drk = Drk::new(
  1907. blockchain_config.wallet_path,
  1908. blockchain_config.wallet_pass,
  1909. Some(blockchain_config.endpoint),
  1910. ex,
  1911. )
  1912. .await?;
  1913. let mut tx = match drk.lock_contract(deploy_auth).await {
  1914. Ok(v) => v,
  1915. Err(e) => {
  1916. eprintln!("Error creating contract lock tx: {}", e);
  1917. exit(2);
  1918. }
  1919. };
  1920. if let Err(e) = drk.attach_fee(&mut tx).await {
  1921. eprintln!("Failed to attach the fee call to the transaction: {e:?}");
  1922. exit(2);
  1923. };
  1924. println!("{}", base64::encode(&serialize_async(&tx).await));
  1925. drk.stop_rpc_client().await
  1926. }
  1927. },
  1928. }
  1929. }