main.rs 78 KB

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