main.rs 84 KB

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