main.rs 92 KB

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