main.rs 93 KB

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