wallet.hoon 72 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454
  1. :: /ker/wallet/wallet: nockchain wallet
  2. /= bip39 /common/bip39
  3. /= slip10 /common/slip10
  4. /= m /common/markdown/types
  5. /= md /common/markdown/markdown
  6. /= transact /common/tx-engine
  7. /= z /common/zeke
  8. /= zo /common/zoon
  9. /= dumb /apps/dumbnet/lib/types
  10. /= * /common/zose
  11. /= * /common/wrapper
  12. ::
  13. =>
  14. =| bug=_&
  15. |%
  16. :: $key: public or private key
  17. ::
  18. :: both private and public keys are in serialized cheetah point form
  19. :: they MUST be converted to base58 for export.
  20. ::
  21. +$ key
  22. $~ [%pub p=*@ux]
  23. $% [%pub p=@ux]
  24. [%prv p=@ux]
  25. ==
  26. :: $coil: key and chaincode
  27. ::
  28. :: a wallet consists of a collection of +coil (address and entropy pair). the
  29. :: $cc (called a chain code elsewhere) allows for the deterministic
  30. :: generation of child keys from a parent key without compromising other
  31. :: branches of the hierarchy.
  32. ::
  33. :: .key: public or private key
  34. :: .cc: associated entropy (chain code)
  35. ::
  36. +$ coil [%coil =key =cc]
  37. ::
  38. :: $meta: stored metadata for a key
  39. +$ meta
  40. $% coil
  41. [%label @t]
  42. [%seed @t]
  43. ==
  44. ::
  45. :: $keys: path indexed map for keys
  46. ::
  47. :: path format for keys state:
  48. ::
  49. :: /keys :: root path (holds nothing in its fil)
  50. :: /keys/[t/master]/[key-type]/m/[coil/key] :: master key path
  51. :: /keys/[t/master]/[key-type]/[ud/index]/[coil/key] :: derived key path
  52. :: /keys/[t/master]/[key-type]/[ud/index]/[coil/key] :: specific key path
  53. :: /keys/[t/master]/[key-type]/[ud/index]/label/[label/label] :: key label path for derived key
  54. :: /keys/[t/master]/[key-type]/m/label/[label/label] :: key label path for master key
  55. :: /keys/[t/master]/seed/[seed/seed-phrase] :: seed-phrase path
  56. ::
  57. :: Note the terminal entry of the path holds that value, this value is the
  58. :: non-unit `fil` in the $axal definition
  59. ::
  60. :: where:
  61. :: - [t/master] is the base58 encoded master public key as @t
  62. :: - m denotes the master key
  63. :: - [ud/index] is the derivation index as @ud
  64. :: - [key-type] is either %pub or %prv
  65. :: - [coil/key] is the key and chaincode pair. key is in serialized
  66. :: format as a @ux, NOT base58.
  67. :: - [seed/seed-phrase] is the seed phrase as a tape
  68. :: - [label/label] is a label value
  69. ::
  70. :: master key is stored under 'm'.
  71. :: derived keys use incrementing indices starting from 0 under their master-key and key-type
  72. :: labels are stored as children of their associated keys.
  73. :: seed is a seed phrase and is only stored as a child of [t/master]
  74. ::
  75. +$ keys $+(keys-axal (axal meta))
  76. ::
  77. :: $draft-tree: structured tree of draft, input, and seed data
  78. ::
  79. :: we use the axal structure to track the relationship between drafts,
  80. :: inputs, and seeds. this allows us to navigate the tree and maintain
  81. :: all the relationships without duplicating data.
  82. ::
  83. :: paths in the draft-tree follow these conventions:
  84. ::
  85. :: /draft/[draft-name] :: draft node
  86. :: /draft/[draft-name]/input/[input-name] :: input in a draft
  87. :: /input/[input-name] :: input node
  88. :: /input/[input-name]/seed/[seed-name] :: seed in an input
  89. :: /seed/[seed-name] :: seed node
  90. ::
  91. +$ draft-tree
  92. $+ wallet-draft-tree
  93. (axal draft-entity)
  94. :: $draft-entity: entities stored in the draft tree
  95. ::
  96. +$ draft-entity
  97. $% [%draft =draft-name =draft]
  98. [%input =input-name =preinput]
  99. [%seed =seed-name =preseed]
  100. ==
  101. ::
  102. :: +master: master key pair
  103. ++ master
  104. =< form
  105. |%
  106. +$ form (unit coil)
  107. ++ public
  108. |= =form
  109. ?: ?=(^ form)
  110. u.form
  111. ~|("master public key not found" !!)
  112. ::
  113. ++ to-b58
  114. |= =form
  115. ^- @t
  116. (crip (en:base58:wrap p.key:(public form)))
  117. --
  118. :: $cc: chaincode
  119. ::
  120. +$ cc @ux
  121. :: $balance: wallet balance
  122. +$ balance
  123. $+ wallet-balance
  124. (z-map:zo nname:transact nnote:transact)
  125. :: $state: wallet state
  126. ::
  127. +$ state
  128. $: %0
  129. =balance
  130. hash-to-name=(z-map:zo hash:transact nname:transact) :: hash of note -> name of note
  131. name-to-hash=(z-map:zo nname:transact hash:transact) :: name of note -> hash of note
  132. receive-address=lock:transact
  133. =master
  134. =keys
  135. transactions=$+(transactions (map * transaction))
  136. last-block=(unit block-id:transact)
  137. peek-requests=$+(peek-requests (map @ud ?(%balance %block)))
  138. active-draft=(unit draft-name)
  139. active-input=(unit input-name)
  140. active-seed=(unit seed-name) :: currently selected seed
  141. draft-tree=draft-tree :: structured tree of drafts, inputs, and seeds
  142. pending-commands=(z-map:zo @ud [phase=?(%block %balance %ready) wrapped=cause]) :: commands waiting for sync
  143. ==
  144. +$ seed-name $~('default-seed' @t)
  145. ::
  146. +$ draft-name $~('default-draft' @t)
  147. ::
  148. +$ input-name $~('default-input' @t)
  149. ::
  150. :: $transaction: TODO
  151. ::
  152. +$ transaction
  153. $: recipient=@ux
  154. amount=@ud
  155. status=?(%unsigned %signed %sent)
  156. ==
  157. ::
  158. +$ cause
  159. $% [%keygen entropy=byts salt=byts]
  160. [%derive-child key-type=?(%pub %prv) i=@ label=(unit @t)]
  161. [%import-keys keys=(list (pair trek meta))]
  162. [%export-keys ~]
  163. [%export-master-pubkey ~]
  164. [%import-master-pubkey =coil] :: base58-encoded pubkey + chain code
  165. [%make-tx dat=draft]
  166. [%list-notes-by-pubkey pubkey=@t] :: base58-encoded pubkey
  167. $: %simple-spend
  168. names=(list [first=@t last=@t]) :: base58-encoded name hashes
  169. recipients=(list [m=@ pks=(list @t)]) :: base58-encoded locks
  170. gifts=(list coins:transact) :: number of coins to spend
  171. fee=coins:transact :: fee
  172. ==
  173. [%sign-tx dat=draft index=(unit @ud) entropy=@]
  174. [%list-pubkeys ~]
  175. [%list-notes ~]
  176. [%show-seedphrase ~]
  177. [%show-master-pubkey ~]
  178. [%show-master-privkey ~]
  179. [%show =path]
  180. [%gen-master-privkey seedphrase=@t]
  181. [%gen-master-pubkey master-privkey=keyc:slip10]
  182. [%update-balance ~]
  183. [%update-block ~]
  184. [%sync-run wrapped=cause] :: run command after sync completes
  185. $: %scan
  186. master-pubkey=@t :: base58 encoded master public key to scan for
  187. search-depth=$~(100 @ud) :: how many addresses to scan (default 100)
  188. include-timelocks=$~(%.n ?) :: include timelocked notes (default false)
  189. include-multisig=$~(%.n ?) :: include notes with multisigs (default false)
  190. ==
  191. [%advanced-spend advanced-spend]
  192. [%file %write path=@t contents=@t success=?]
  193. npc-cause
  194. ==
  195. ::
  196. +$ advanced-spend
  197. $% [%seed advanced-spend-seed]
  198. [%input advanced-spend-input]
  199. [%draft advanced-spend-draft]
  200. ==
  201. ::
  202. +$ advanced-spend-seed
  203. $% [%new name=@t] :: new empty seed in draft
  204. $: %set-name
  205. seed-name=@t
  206. new-name=@t
  207. ==
  208. $: %set-source :: set .output-source
  209. seed-name=@t
  210. source=(unit [hash=@t is-coinbase=?])
  211. ==
  212. $: %set-recipient :: set .recipient
  213. seed-name=@t
  214. recipient=[m=@ pks=(list @t)]
  215. ==
  216. $: %set-timelock :: set .timelock-intent
  217. seed-name=@t
  218. absolute=timelock-range:transact
  219. relative=timelock-range:transact
  220. ==
  221. $: %set-gift
  222. seed-name=@t
  223. gift=coins:transact
  224. ==
  225. $: %set-parent-hash
  226. seed-name=@t
  227. parent-hash=@t
  228. ==
  229. $: %set-parent-hash-from-name
  230. seed-name=@t
  231. name=[@t @t]
  232. ==
  233. $: %print-status :: do the needful
  234. seed-name=@t
  235. ==
  236. ==
  237. :: $seed-mask: tracks which fields of a $seed:transact have been set
  238. ::
  239. :: this might have been better as a "unitized seed" but would have been
  240. :: much more annoying to read the code
  241. +$ seed-mask
  242. $~ [%.n %.n %.n %.n %.n]
  243. $: output-source=?
  244. recipient=?
  245. timelock-intent=?
  246. gift=?
  247. parent-hash=?
  248. ==
  249. :: $preseed: a $seed:transact in process of being built
  250. +$ preseed [name=@t (pair seed:transact seed-mask)]
  251. ::
  252. :: $spend-mask: tracks which field of a $spend:transact have been set
  253. +$ spend-mask
  254. $~ [%.n %.n %.n]
  255. $: signature=?
  256. seeds=?
  257. fee=?
  258. ==
  259. ::
  260. +$ advanced-spend-input
  261. :: there is only one right way to create an $input from a $spend, so we don't need
  262. :: the mask or other commands.
  263. $% [%new name=@t] :: new empty input
  264. $: %set-name
  265. input-name=@t
  266. new-name=@t
  267. ==
  268. $: %add-seed
  269. input-name=@t
  270. seed-name=@t
  271. ==
  272. $: %set-fee
  273. input-name=@t
  274. fee=coins:transact
  275. ==
  276. $: %set-note-from-name :: set .note using .name
  277. input-name=@t
  278. name=[@t @t]
  279. ==
  280. $: %set-note-from-hash :: set .note using hash
  281. input-name=@t
  282. hash=@t
  283. ==
  284. $: %derive-note-from-seeds :: derive note from seeds
  285. input-name=@t
  286. ==
  287. $: %remove-seed
  288. input-name=@t
  289. seed-name=@t
  290. ==
  291. $: %remove-seed-by-hash
  292. input-name=@t
  293. hash=@t
  294. ==
  295. $: %print-status
  296. input-name=@t
  297. ==
  298. ==
  299. ::
  300. +$ input-mask
  301. $~ [%.n *spend-mask]
  302. $: note=?
  303. spend=spend-mask
  304. ==
  305. ::
  306. +$ preinput [name=@t (pair input:transact input-mask)]
  307. ::
  308. +$ draft [name=@t p=inputs:transact]
  309. ::
  310. +$ advanced-spend-draft
  311. $% [%new name=@t] :: new input draft
  312. $: %set-name
  313. draft-name=@t
  314. new-name=@t
  315. ==
  316. $: %add-input
  317. draft-name=@t
  318. input-name=@t
  319. ==
  320. $: %remove-input
  321. draft-name=@t
  322. input-name=@t
  323. ==
  324. $: %remove-input-by-name
  325. draft-name=@t
  326. name=[first=@t last=@t]
  327. ==
  328. [%print-status =draft-name] :: print draft status
  329. ==
  330. ::
  331. +$ npc-cause
  332. $% [%npc-bind pid=@ result=*]
  333. ==
  334. ::
  335. +$ effect
  336. $~ [%npc 0 %poke %fact *fact:dumb]
  337. $% file-effect
  338. [%markdown @t]
  339. [%raw *]
  340. [%npc pid=@ npc-effect]
  341. [%exit code=@]
  342. ==
  343. ::
  344. +$ file-effect
  345. $%
  346. [%file %read path=@t]
  347. [%file %write path=@t contents=@]
  348. ==
  349. ::
  350. +$ npc-effect
  351. $% [%poke $>(%fact cause:dumb)]
  352. [%peek path]
  353. ==
  354. ::
  355. ::TODO this probably shouldnt live in here
  356. ::
  357. ++ print
  358. |= nodes=markdown:m
  359. ^- (list effect)
  360. ~[(make-markdown-effect nodes)]
  361. ::
  362. ++ warn
  363. |* meg=tape
  364. |* *
  365. ?. bug +<
  366. ~> %slog.[1 (cat 3 'wallet: warning: ' (crip meg))]
  367. +<
  368. ::
  369. ++ debug
  370. |* meg=tape
  371. |* *
  372. ?. bug +<
  373. ~> %slog.[2 (cat 3 'wallet: debug: ' (crip meg))]
  374. +<
  375. ::
  376. ++ moat (keep state)
  377. ::
  378. ::
  379. :: +edit: modify inputs
  380. ++ edit
  381. |_ =state
  382. ::
  383. +* inp
  384. ^- preinput
  385. ?~ active-input.state
  386. %- (debug "no active input set!")
  387. *preinput
  388. =/ input-result (~(get-input plan draft-tree.state) u.active-input.state)
  389. ?~ input-result
  390. ~|("active input not found in draft-tree" !!)
  391. u.input-result
  392. :: +add-seed: add a seed to the input
  393. ::
  394. ++ add-seed
  395. |= =seed:transact
  396. ^- [(list effect) ^state]
  397. ?: (~(has z-in:zo seeds.spend.p.inp) seed)
  398. :_ state
  399. %- print
  400. %- need
  401. %- de:md
  402. %- crip
  403. """
  404. ## add-seed
  405. **seed already exists in .spend**
  406. """
  407. =/ pre=preinput inp
  408. =/ =preinput
  409. %= pre
  410. seeds.spend.p
  411. %. seed
  412. ~(put z-in:zo seeds.spend.p.pre)
  413. ::
  414. seeds.spend.q %.y
  415. ==
  416. =. active-input.state (some name.pre)
  417. ::
  418. =/ input-name=input-name (need active-input.state)
  419. =. draft-tree.state
  420. (~(add-input plan draft-tree.state) input-name preinput)
  421. :: if active-seed is set, link it to this input
  422. =. draft-tree.state
  423. ?: ?=(^ active-seed.state)
  424. (~(link-seed-to-input plan draft-tree.state) input-name u.active-seed.state)
  425. draft-tree.state
  426. `state
  427. ::
  428. ++ remove-seed
  429. |= =seed:transact
  430. ^- [(list effect) ^state]
  431. ?. (~(has z-in:zo seeds.spend.p.inp) seed)
  432. :_ state
  433. %- print
  434. %- need
  435. %- de:md
  436. %- crip
  437. """
  438. ## remove-seed
  439. **seed not found in .spend**
  440. """
  441. =/ pre=preinput inp
  442. =. seeds.spend.p.pre
  443. %. seed
  444. ~(del z-in:zo seeds.spend.p.pre)
  445. =. draft-tree.state
  446. =/ input-name=input-name (need active-input.state)
  447. (~(add-input plan draft-tree.state) input-name pre)
  448. `state
  449. --
  450. ::
  451. :: +draw: modify drafts
  452. ++ draw
  453. |_ =state
  454. +* df
  455. ^- draft
  456. ?> ?=(^ active-draft.state)
  457. =/ draft-result (~(get-draft plan draft-tree.state) u.active-draft.state)
  458. ?~ draft-result
  459. *draft
  460. u.draft-result
  461. :: +add-input: add an input to the draft
  462. ::
  463. ++ add-input
  464. |= =input:transact
  465. ^- [(list effect) ^state]
  466. =/ =draft df
  467. =/ =input-name
  468. =+ (to-b58:nname:transact name.note.input)
  469. %- crip
  470. "{<first>}-{<last>}"
  471. ?: (~(has z-by:zo p.df) name.note.input)
  472. :_ state
  473. %- print
  474. %- need
  475. %- de:md
  476. %- crip
  477. """
  478. ## add-input
  479. **input already exists in .draft**
  480. draft already has input with note name: {<input-name>}
  481. """
  482. =/ active-draft=draft-name (need active-draft.state)
  483. =. p.draft
  484. %- ~(put z-by:zo p.draft)
  485. :- name.note.input
  486. input
  487. =. draft-tree.state
  488. %. [active-draft draft]
  489. ~(add-draft plan draft-tree.state)
  490. =. draft-tree.state
  491. %. [active-draft input-name]
  492. ~(link-input-to-draft plan draft-tree.state)
  493. write-draft
  494. ::
  495. ++ write-draft
  496. ^- [(list effect) ^state]
  497. =? active-draft.state ?=(~ active-draft.state) (some *draft-name)
  498. ?> ?=(^ active-draft.state)
  499. =/ =draft df
  500. =. draft-tree.state (~(add-draft plan draft-tree.state) u.active-draft.state draft)
  501. =/ dat-jam (jam draft)
  502. =/ path=@t (crip "drafts/{(trip u.active-draft.state)}.draft")
  503. =/ effect [%file %write path dat-jam]
  504. :_ state
  505. ~[effect [%exit 0]]
  506. --
  507. ::
  508. :: Convenience wrapper door for slip10 library
  509. :: ** Never use slip10 directly in the wallet **
  510. ++ s10
  511. |_ bas=base:slip10
  512. ++ gen-master-key
  513. |= [entropy=byts salt=byts]
  514. =/ argon-byts=byts
  515. :- 32
  516. %+ argon2-nockchain:argon2:crypto
  517. entropy
  518. salt
  519. =/ memo=tape (from-entropy:bip39 argon-byts)
  520. %- (debug "memo: {memo}")
  521. :- (crip memo)
  522. (from-seed:slip10 [64 (to-seed:bip39 memo "")])
  523. ::
  524. ++ from-seed
  525. |= =byts
  526. (from-seed:slip10 byts)
  527. ::
  528. ++ from-private
  529. |= =keyc:slip10
  530. (from-private:slip10 keyc)
  531. ::
  532. ++ from-public
  533. |= =keyc:slip10
  534. (from-public:slip10 keyc)
  535. ::
  536. :: Derives public key from parent public key
  537. :: index i is expected to be a bip32 style index
  538. :: meaning that for the n-th child key, i=n.
  539. ::
  540. ++ derive-public
  541. |= [parent=coil i=@u]
  542. ?> &(?=(%pub -.key.parent) (lte i (dec (bex 31))))
  543. => [cor=(from-public [p.key cc]:parent) i=i]
  544. (derive-public:cor i)
  545. ::
  546. :: Derives private key from parent private key
  547. :: index i is expected to be a bip32 style index
  548. :: meaning that for n-th child key: i = (n + 2^31)
  549. ::
  550. ++ derive-private
  551. |= [parent=coil i=@u]
  552. ?> &(?=(%prv -.key.parent) (gte i (bex 31)))
  553. => [cor=(from-private [p.key cc]:parent) i=i]
  554. (derive-private:cor i)
  555. --
  556. ::
  557. ++ vault
  558. |_ =state
  559. ++ base-path ^- trek
  560. ?~ master.state
  561. ~|("base path not accessible because master not set" !!)
  562. /keys/[t/(to-b58:master master.state)]
  563. ::
  564. ++ seed-path ^- trek
  565. (welp base-path /seed)
  566. ::
  567. ++ get
  568. |_ key-type=?(%pub %prv)
  569. ::
  570. ++ key-path ^- trek
  571. (welp base-path ~[key-type])
  572. ::
  573. ++ seed-path ^- trek
  574. (welp base-path /seed)
  575. ::
  576. ++ master
  577. ^- coil
  578. =/ =trek (welp key-path /m)
  579. =/ =meta (~(got of keys.state) trek)
  580. ?> ?=(%coil -.meta)
  581. meta
  582. ::
  583. ++ by-index
  584. |= index=@ud
  585. ^- coil
  586. =/ =trek (welp key-path /[ud/index])
  587. =/ =meta (~(got of keys.state) trek)
  588. ?> ?=(%coil -.meta)
  589. meta
  590. ::
  591. ++ seed
  592. ^- meta
  593. (~(got of keys.state) seed-path)
  594. ::
  595. ++ by-label
  596. |= label=@t
  597. %+ murn keys
  598. |= [t=trek =meta]
  599. ?:(&(?=(%label -.meta) =(label +.meta)) `t ~)
  600. ::
  601. ++ keys
  602. ^- (list [trek meta])
  603. =/ subtree
  604. %- ~(kids of keys.state)
  605. key-path
  606. ~(tap by kid.subtree)
  607. ::
  608. ++ coils
  609. ^- (list coil)
  610. %+ murn keys
  611. |= [t=trek =meta]
  612. ^- (unit coil)
  613. ;; (unit coil)
  614. ?:(=(%coil -.meta) `meta ~)
  615. --
  616. ::
  617. ++ put
  618. |%
  619. ::
  620. ++ seed
  621. |= seed-phrase=@t
  622. ^- (axal meta)
  623. %- ~(put of keys.state)
  624. [seed-path [%seed seed-phrase]]
  625. ::
  626. ++ key
  627. |= [=coil index=(unit @) label=(unit @t)]
  628. ^- (axal meta)
  629. =/ key-type=@tas -.key.coil
  630. =/ suffix=trek
  631. ?@ index
  632. /[key-type]/m
  633. /[key-type]/[ud/u.index]
  634. =/ key-path=trek (welp base-path suffix)
  635. %- (debug "adding key at {(en-tape:trek key-path)}")
  636. =. keys.state (~(put of keys.state) key-path coil)
  637. ?~ label
  638. keys.state
  639. %+ ~(put of keys.state)
  640. (welp key-path /label)
  641. label/u.label
  642. --
  643. ::
  644. :: +derive-child: derives the i-th hardened/unhardened child
  645. ::
  646. :: derives the i-th hardened or unhardened child from the master key.
  647. :: this arm will convert i to fit the slip10/bip32 indexing schemes.
  648. :: the i-th hardened corresponds to index i + 2^31 while the ith
  649. :: unhardened child corresponds to index i.
  650. ::
  651. ++ derive-child
  652. |= [parent=coil i=@u]
  653. ^- coil
  654. ?: (gte i (bex 31))
  655. ~|("Child index {<i>} out of range. Child indices are capped to values between [0, 2^31)" !!)
  656. ?~ master.state
  657. ~|("No master keys available for derivation" !!)
  658. ?: ?=(%prv -.key.parent)
  659. ::
  660. :: If the parent key is %prv, then the child is hardened
  661. :: and we add 2^31 to the index
  662. => (derive-private:s10 parent (add i (bex 31)))
  663. [%coil [%prv private-key] chain-code]
  664. => (derive-public:s10 parent i)
  665. [%coil [%pub public-key] chain-code]
  666. ::
  667. ++ get-note
  668. |= name=nname:transact
  669. ^- nnote:transact
  670. ?: (~(has z-by:zo balance.state) name)
  671. (~(got z-by:zo balance.state) name)
  672. ~|("note not found: {<name>}" !!)
  673. ::
  674. ++ get-note-from-hash
  675. |= has=hash:transact
  676. ^- nnote:transact
  677. =/ name=nname:transact
  678. (~(got z-by:zo hash-to-name.state) has)
  679. (get-note name)
  680. ::
  681. ++ generate-pid
  682. |= peek-type=?(%balance %block)
  683. ^- (unit @ud)
  684. =/ has-active-peek=?
  685. %- ~(any by peek-requests.state)
  686. |=(t=?(%balance %block) =(t peek-type))
  687. ?: has-active-peek ~
  688. =/ used-pids=(list @ud)
  689. ~(tap in ~(key by peek-requests.state))
  690. =/ max-pid=@ud
  691. (roll used-pids max)
  692. =/ next-pid=@ud +(max-pid)
  693. ?: =(next-pid 0) `1 :: handle wraparound
  694. `next-pid
  695. --
  696. :: +plan: core for managing draft relationships
  697. ::
  698. :: provides methods for adding, removing, and navigating the draft tree.
  699. :: uses the axal structure to maintain relationships between drafts, inputs,
  700. :: and seeds.
  701. ::
  702. ++ plan
  703. |_ tree=draft-tree
  704. ::
  705. :: +get-draft: retrieve a draft by name
  706. ::
  707. ++ get-draft
  708. |= name=draft-name
  709. ^- (unit draft)
  710. =/ res (~(get of tree) /draft/[name])
  711. ?~ res ~
  712. ?. ?=(%draft -.u.res) ~
  713. `draft.u.res
  714. :: +get-input: retrieve an input by name
  715. ::
  716. ++ get-input
  717. |= name=input-name
  718. ^- (unit preinput)
  719. =/ res (~(get of tree) /input/[name])
  720. ?~ res ~
  721. ?. ?=(%input -.u.res) ~
  722. `preinput.u.res
  723. :: +get-seed: retrieve a seed by name
  724. ::
  725. ++ get-seed
  726. |= name=seed-name
  727. ^- (unit preseed)
  728. =/ res (~(get of tree) /seed/[name])
  729. ?~ res ~
  730. ?. ?=(%seed -.u.res) ~
  731. `preseed.u.res
  732. :: +add-draft: add a new draft
  733. ::
  734. ++ add-draft
  735. |= [name=draft-name =draft]
  736. ^- draft-tree
  737. =/ entity [%draft name draft]
  738. (~(put of tree) /draft/[name] entity)
  739. :: +add-input: add a new input
  740. ::
  741. ++ add-input
  742. |= [name=input-name =preinput]
  743. ^- draft-tree
  744. =/ entity [%input name preinput]
  745. (~(put of tree) /input/[name] entity)
  746. :: +add-seed: add a new seed
  747. ::
  748. ++ add-seed
  749. |= [name=seed-name =preseed]
  750. ^- draft-tree
  751. =/ entity [%seed name preseed]
  752. (~(put of tree) /seed/[name] entity)
  753. :: +link-input-to-draft: link an input to a draft
  754. ::
  755. ++ link-input-to-draft
  756. |= [draft-name=draft-name input-name=input-name]
  757. ^- draft-tree
  758. =/ input-entity (~(get of tree) /input/[input-name])
  759. ?~ input-entity tree
  760. ?. ?=(%input -.u.input-entity) tree
  761. (~(put of tree) /draft/[draft-name]/input/[input-name] u.input-entity)
  762. :: +link-seed-to-input: link a seed to an input
  763. ::
  764. ++ link-seed-to-input
  765. |= [input-name=input-name seed-name=seed-name]
  766. ^- draft-tree
  767. =/ seed-entity (~(get of tree) /seed/[seed-name])
  768. ?~ seed-entity tree
  769. ?. ?=(%seed -.u.seed-entity) tree
  770. (~(put of tree) /input/[input-name]/seed/[seed-name] u.seed-entity)
  771. :: +unlink-input-from-draft: remove an input from a draft
  772. ::
  773. ++ unlink-input-from-draft
  774. |= [draft-name=draft-name input-name=input-name]
  775. ^- draft-tree
  776. (~(del of tree) /draft/[draft-name]/input/[input-name])
  777. :: +unlink-seed-from-input: remove a seed from an input
  778. ::
  779. ++ unlink-seed-from-input
  780. |= [input-name=input-name seed-name=seed-name]
  781. ^- draft-tree
  782. (~(del of tree) /input/[input-name]/seed/[seed-name])
  783. :: +list-draft-inputs: list all inputs in a draft
  784. ::
  785. ++ list-draft-inputs
  786. |= name=draft-name
  787. ^- (list input-name)
  788. =/ kids (~(kid of tree) /draft/[name])
  789. %+ murn ~(tap in ~(key by kids))
  790. |= pax=pith
  791. ^- (unit input-name)
  792. =/ pax=path (pout pax)
  793. ?> ?=([%input *] pax)
  794. ?> ?=(^ t.pax)
  795. `i.t.pax
  796. :: +list-input-seeds: list all seeds in an input
  797. ::
  798. ++ list-input-seeds
  799. |= name=input-name
  800. ^- (list seed-name)
  801. =/ kids (~(kid of tree) /input/[name])
  802. %+ murn ~(tap in ~(key by kids))
  803. |= pax=pith
  804. ^- (unit seed-name)
  805. =/ pax=path (pout pax)
  806. ?: &(?=([%seed *] pax) ?=(^ t.pax))
  807. `i.t.pax
  808. ~
  809. :: +list-all-drafts: list all draft names
  810. ::
  811. ++ list-all-drafts
  812. ^- (list draft-name)
  813. =/ kids (~(kid of tree) /draft)
  814. %+ murn ~(tap in ~(key by kids))
  815. |= pax=pith
  816. ^- (unit draft-name)
  817. =/ pax=path (pout pax)
  818. ?: ?=(^ pax)
  819. `i.pax
  820. ~
  821. :: +list-all-inputs: list all input names
  822. ::
  823. ++ list-all-inputs
  824. ^- (list input-name)
  825. =/ kids (~(kid of tree) /input)
  826. %+ murn ~(tap in ~(key by kids))
  827. |= pax=pith
  828. ^- (unit input-name)
  829. =/ pax=path (pout pax)
  830. ?: ?=(^ pax)
  831. `i.pax
  832. ~
  833. :: +list-all-seeds: list all seed names
  834. ::
  835. ++ list-all-seeds
  836. ^- (list seed-name)
  837. =/ kids (~(kid of tree) /seed)
  838. %+ murn ~(tap in ~(key by kids))
  839. |= pax=pith
  840. ^- (unit seed-name)
  841. =/ pax=path (pout pax)
  842. ?: &(?=([%seed *] pax) ?=(^ t.pax))
  843. `i.pax
  844. ~
  845. :: +remove-draft: completely remove a draft and its associations
  846. ::
  847. ++ remove-draft
  848. |= name=draft-name
  849. ^- draft-tree
  850. (~(lop of tree) /draft/[name])
  851. :: +remove-input: completely remove an input and its associations
  852. ::
  853. ++ remove-input
  854. |= name=input-name
  855. ^- draft-tree
  856. (~(lop of tree) /input/[name])
  857. :: +remove-seed: completely remove a seed and its associations
  858. ::
  859. ++ remove-seed
  860. |= name=seed-name
  861. ^- draft-tree
  862. (~(lop of tree) /seed/[name])
  863. --
  864. ::
  865. ++ make-markdown-effect
  866. |= nodes=markdown:m
  867. [%markdown (crip (en:md nodes))]
  868. ::
  869. ++ display-poke
  870. |= =cause
  871. ^- effect
  872. =/ nodes=markdown:m
  873. %- need
  874. %- de:md
  875. %- crip
  876. """
  877. ## poke
  878. {<cause>}
  879. """
  880. (make-markdown-effect nodes)
  881. ::
  882. ++ display-note
  883. |= note=nnote:transact
  884. ^- markdown:m
  885. %- need
  886. %- de:md
  887. %- crip
  888. """
  889. ## details
  890. - name: {<(to-b58:nname:transact name.note)>}
  891. - assets: {<assets.note>}
  892. - source: {<source.note>}
  893. ## lock
  894. - m: {<m.lock.note>}
  895. - signers: {<(to-b58:signers:lock:transact lock.note)>}
  896. """
  897. ::
  898. ++ show
  899. |= [=state =path]
  900. ^- [(list effect) ^state]
  901. |^
  902. ?+ path !!
  903. [%balance ~]
  904. :- ~[(display-balance balance.state)]
  905. state
  906. ::
  907. [%receive-address ~]
  908. :- (display-receive-address receive-address.state)
  909. state
  910. ::
  911. [%state ~]
  912. :- display-state
  913. state
  914. ==
  915. ++ display-balance
  916. |= =balance
  917. ^- effect
  918. =/ nodes=markdown:m
  919. %- need
  920. %- de:md
  921. %- crip
  922. """
  923. ## balance
  924. {<balance>}
  925. """
  926. (make-markdown-effect nodes)
  927. ::
  928. ++ display-receive-address
  929. |= address=lock:transact
  930. ^- (list effect)
  931. =/ nodes=markdown:m
  932. %- need
  933. %- de:md
  934. %- crip
  935. """
  936. ## receive address
  937. {<address>}
  938. """
  939. ~[(make-markdown-effect nodes)]
  940. ::
  941. ++ display-state
  942. ^- (list effect)
  943. =/ nodes=markdown:m
  944. %- need
  945. %- de:md
  946. %- crip
  947. """
  948. ## state
  949. - last block: {<last-block.state>}
  950. """
  951. ~[(make-markdown-effect nodes)]
  952. --
  953. --
  954. ::
  955. %- (moat &)
  956. ^- fort:moat
  957. |_ =state
  958. +* v ~(. vault state)
  959. d ~(. draw state)
  960. e ~(. edit state)
  961. p ~(. plan draft-tree.state)
  962. ::
  963. ++ load
  964. |= arg=^state
  965. ^- ^state
  966. arg
  967. ::
  968. ++ peek
  969. |= =path
  970. ^- (unit (unit *))
  971. %- (debug "peek: {<state>}")
  972. ?+ path ~
  973. ::
  974. [%balance ~]
  975. ``balance.state
  976. ::
  977. [%receive-address ~]
  978. ``receive-address.state
  979. ::
  980. [%state ~]
  981. ``state
  982. ==
  983. ::
  984. ++ poke
  985. |= =ovum:moat
  986. |^
  987. ^- [(list effect) ^state]
  988. %- (warn "debug printing may expose sensitive information")
  989. =/ =wire wire.ovum
  990. =+ [@ src=@ta ver=@ rest=*]=wire.ovum
  991. %- (debug "wire: {<[src ver `path`rest]>}")
  992. =/ cause=(unit cause)
  993. %- (soft cause)
  994. cause.input.ovum
  995. =/ success [%exit 0]
  996. =/ failure [%exit 1]
  997. ?~ cause
  998. %- (debug "input does not have a proper cause: {<cause.input.ovum>}")
  999. [~[failure] state]
  1000. =/ cause u.cause
  1001. %- (debug "cause: {<-.cause>}")
  1002. =; process
  1003. =^ effs state process
  1004. :: check for pending balance commands and execute them
  1005. =^ pending-effs state handle-pending-commands
  1006. [(weld effs pending-effs) state]
  1007. ?- -.cause
  1008. %npc-bind (handle-npc cause)
  1009. %show (show state path.cause)
  1010. %keygen (do-keygen cause)
  1011. %derive-child (do-derive-child cause)
  1012. %sign-tx (do-sign-tx cause)
  1013. %scan (do-scan cause)
  1014. %list-notes (do-list-notes cause)
  1015. %list-notes-by-pubkey (do-list-notes-by-pubkey cause)
  1016. %simple-spend (do-simple-spend cause)
  1017. %update-balance (do-update-balance cause)
  1018. %update-block (do-update-block cause)
  1019. %import-keys (do-import-keys cause)
  1020. %export-keys (do-export-keys cause)
  1021. %export-master-pubkey (do-export-master-pubkey cause)
  1022. %import-master-pubkey (do-import-master-pubkey cause)
  1023. %gen-master-privkey (do-gen-master-privkey cause)
  1024. %gen-master-pubkey (do-gen-master-pubkey cause)
  1025. %make-tx (do-make-tx cause)
  1026. %list-pubkeys (do-list-pubkeys cause)
  1027. %sync-run (do-sync-run cause)
  1028. %show-seedphrase (do-show-seedphrase cause)
  1029. %show-master-pubkey (do-show-master-pubkey cause)
  1030. %show-master-privkey (do-show-master-privkey cause)
  1031. ::
  1032. %advanced-spend
  1033. ?- +<.cause
  1034. %seed (do-advanced-spend-seed +>.cause)
  1035. %input (do-advanced-spend-input +>.cause)
  1036. %draft (do-advanced-spend-draft +>.cause)
  1037. ==
  1038. ::
  1039. %file
  1040. ?> ?=(%write +<.cause)
  1041. ~& > "%file %write: {<cause>}"
  1042. [[%exit 0]~ state]
  1043. ==
  1044. ::
  1045. ++ handle-npc
  1046. |= =npc-cause
  1047. ^- [(list effect) ^state]
  1048. %- (debug "handle-npc: {<-.npc-cause>}")
  1049. ?- -.npc-cause
  1050. %npc-bind
  1051. =/ pid pid.npc-cause
  1052. =/ peek-type
  1053. ?. (~(has by peek-requests.state) pid)
  1054. ~|("no peek request found for pid: {<pid>}" !!)
  1055. (~(got by peek-requests.state) pid)
  1056. =/ result result.npc-cause
  1057. ?- peek-type
  1058. %balance
  1059. =/ softed=(unit (unit (unit (z-map:zo nname:transact nnote:transact))))
  1060. %- (soft (unit (unit (z-map:zo nname:transact nnote:transact))))
  1061. result
  1062. =. peek-requests.state
  1063. (~(del by peek-requests.state) pid)
  1064. ?~ softed
  1065. %- (debug "handle-npc: %balance: could not soft result")
  1066. [[%exit 0]~ state]
  1067. =/ balance-result=(unit (unit _balance.state)) u.softed
  1068. ?~ balance-result
  1069. %- (warn "%update-balance did not return a result: bad path")
  1070. [[%exit 0]~ state]
  1071. ?~ u.balance-result
  1072. %- (warn "%update-balance did not return a result: nothing")
  1073. [[%exit 0]~ state]
  1074. ?~ u.u.balance-result
  1075. %- (warn "%update-balance did not return a result: empty result")
  1076. [[%exit 0]~ state]
  1077. =. balance.state u.u.balance-result
  1078. %- (debug "balance state updated!")
  1079. :: count the number of notes in the balance
  1080. =/ note-count=@ud
  1081. (lent ~(tap z-by:zo balance.state))
  1082. %- (debug "note count: {<note-count>}")
  1083. =. name-to-hash.state
  1084. %- ~(run z-by:zo balance.state)
  1085. |= not=nnote:transact
  1086. ^- hash:transact
  1087. (hash:nnote:transact not)
  1088. =. hash-to-name.state
  1089. %- ~(gas z-by:zo *(z-map:zo hash:transact nname:transact))
  1090. %+ turn ~(tap z-by:zo name-to-hash.state)
  1091. |= [name=nname:transact =hash:transact]
  1092. [hash name]
  1093. :: move each command from balance phase to ready phase
  1094. =/ balance-commands=(list [pid=@ud [phase=?(%block %balance %ready) wrapped=cause]])
  1095. %+ skim ~(tap z-by:zo pending-commands.state)
  1096. |= [pid=@ud [phase=?(%block %balance %ready) wrapped=cause]]
  1097. =(phase %balance)
  1098. =. pending-commands.state
  1099. %- ~(gas z-by:zo pending-commands.state)
  1100. %+ turn balance-commands
  1101. |= [pid=@ud [phase=?(%block %balance %ready) wrapped=cause]]
  1102. [pid [%ready wrapped]]
  1103. ::
  1104. :: the top-level poke arm should check for pending commands
  1105. :: and execute them as appropriate
  1106. %- (debug "handle-npc: %balance: balance updated, pending commands ready for execution")
  1107. [~ state]
  1108. ::
  1109. %block
  1110. %- (debug "handle-npc: %block")
  1111. =/ softed=(unit (unit (unit (unit block-id:transact))))
  1112. %- (soft (unit (unit (unit block-id:transact))))
  1113. result
  1114. =. peek-requests.state
  1115. (~(del by peek-requests.state) pid)
  1116. ?~ softed
  1117. %- (warn "handle-npc: %block: could not soft result")
  1118. [[%exit 0]~ state]
  1119. =/ block-result u.softed
  1120. %- (debug "handle-npc: %block: {<block-result>}")
  1121. %- (debug "handle-npc: %block: {<peek-requests.state>}")
  1122. ?~ block-result
  1123. %- (warn "handle-npc: %block: bad path")
  1124. [[%exit 0]~ state]
  1125. ?~ u.block-result
  1126. %- (warn "handle-npc: %block: nothing")
  1127. [[%exit 0]~ state]
  1128. ?~ u.u.block-result
  1129. %- (warn "handle-npc: %block: empty result")
  1130. [[%exit 0]~ state]
  1131. %- (debug "handle-npc: %block: found block")
  1132. %- (debug "handle-npc: {<(to-b58:block-id:transact (need u.u.block-result))>}")
  1133. %- (debug "handle-npc: hash: {<(to-b58:hash:transact (need u.u.block-result))>}")
  1134. =. last-block.state u.u.block-result
  1135. :: move each command from block phase to balance phase
  1136. =/ block-commands=(list [pid=@ud [phase=?(%block %balance %ready) wrapped=cause]])
  1137. %+ skim ~(tap z-by:zo pending-commands.state)
  1138. |= [pid=@ud [phase=?(%block %balance %ready) wrapped=cause]]
  1139. =(phase %block)
  1140. ::
  1141. %- (debug "handle-npc: %block: preparing {<(lent block-commands)>} commands for balance update")
  1142. :: move each command to balance update phase
  1143. =. pending-commands.state
  1144. %- ~(gas z-by:zo pending-commands.state)
  1145. %+ turn block-commands
  1146. |= [pid=@ud [phase=?(%block %balance %ready) wrapped=cause]]
  1147. [pid [%balance wrapped]]
  1148. :: check if we need to update balance (if there are commands waiting for it)
  1149. =/ have-balance-cmds=?
  1150. %- ~(any z-by:zo pending-commands.state)
  1151. |= [phase=?(%block %balance %ready) wrapped=*]
  1152. =(phase %balance)
  1153. ::
  1154. ?: have-balance-cmds
  1155. %- (debug "handle-npc: %block: initiating balance update for pending commands")
  1156. =^ balance-update-effs state
  1157. (do-update-balance [%update-balance ~])
  1158. [balance-update-effs state]
  1159. `state
  1160. ::
  1161. ==
  1162. ==
  1163. ::
  1164. ++ handle-pending-commands
  1165. ^- [(list effect) ^state]
  1166. =/ ready-commands=(list [pid=@ud =cause])
  1167. %+ turn
  1168. %+ skim ~(tap z-by:zo pending-commands.state)
  1169. |= [pid=@ud [phase=?(%block %balance %ready) wrapped=cause]]
  1170. =(phase %ready)
  1171. |= [pid=@ud [phase=?(%block %balance %ready) wrapped=cause]]
  1172. [pid wrapped]
  1173. ?~ ready-commands
  1174. %- (debug "no pending commands to execute")
  1175. `state
  1176. %- (debug "executing {<(lent ready-commands)>} pending commands")
  1177. :: process each command
  1178. =/ effs=(list effect) ~
  1179. =/ cmds=(list [pid=@ud =cause]) ready-commands
  1180. |-
  1181. ?~ cmds
  1182. [effs state]
  1183. =/ pid=@ud pid.i.cmds
  1184. =/ =cause +.i.cmds
  1185. =/ ov=^ovum
  1186. %* . ovum
  1187. cause.input cause
  1188. ==
  1189. =. pending-commands.state
  1190. (~(del z-by:zo pending-commands.state) pid)
  1191. ::
  1192. =^ cmd-effs state
  1193. (poke ov)
  1194. $(cmds t.cmds, effs (weld effs cmd-effs))
  1195. ::
  1196. ++ do-sync-run
  1197. |= =cause
  1198. ?> ?=(%sync-run -.cause)
  1199. %- (debug "sync-run: wrapped command {<-.wrapped.cause>}")
  1200. :: get a new pid for the command
  1201. =/ pid=(unit @ud) (generate-pid:v %block)
  1202. ?~ pid
  1203. :: if we can't get a pid, run the command directly
  1204. %- (debug "sync-run: no pid available, running command directly")
  1205. =/ ov=^ovum
  1206. %* . ovum
  1207. cause.input wrapped.cause
  1208. ==
  1209. (poke ov)
  1210. :: store the command in pending-commands
  1211. =. pending-commands.state
  1212. %+ ~(put z-by:zo pending-commands.state)
  1213. u.pid
  1214. [%block wrapped.cause]
  1215. :: initiate block update
  1216. =^ block-update-effs state
  1217. (do-update-block [%update-block ~])
  1218. :: return block update effects
  1219. [block-update-effs state]
  1220. ::
  1221. ++ do-update-balance
  1222. |= =cause
  1223. ?> ?=(%update-balance -.cause)
  1224. %- (debug "update-balance")
  1225. %- (debug "last balance size: {<(lent ~(tap z-by:zo balance.state))>}")
  1226. =/ pid=(unit @ud) (generate-pid:v %balance)
  1227. ?~ pid `state
  1228. =/ bid=block-id:transact
  1229. ?: ?=(~ last-block.state)
  1230. ~|("no last block found, not updating balance" !!)
  1231. (need last-block.state)
  1232. =/ =path (snoc /balance (to-b58:block-id:transact bid))
  1233. =/ =effect [%npc u.pid %peek path]
  1234. :- ~[effect]
  1235. state(peek-requests (~(put by peek-requests.state) u.pid %balance))
  1236. ::
  1237. ++ do-update-block
  1238. |= =cause
  1239. ?> ?=(%update-block -.cause)
  1240. %- (debug "update-block")
  1241. %- (debug "last block: {<last-block.state>}")
  1242. =/ pid=(unit @ud) (generate-pid:v %block)
  1243. ?~ pid `state
  1244. =/ =path /heavy
  1245. =/ =effect [%npc u.pid %peek path]
  1246. :- ~[effect]
  1247. state(peek-requests (~(put by peek-requests.state) u.pid %block))
  1248. ::
  1249. ++ do-import-keys
  1250. |= =cause
  1251. ?> ?=(%import-keys -.cause)
  1252. =/ new-keys=_keys.state
  1253. %+ roll keys.cause
  1254. |= [[=trek =meta] acc=_keys.state]
  1255. (~(put of acc) trek meta)
  1256. =/ master-key=coil
  1257. %- head
  1258. %+ murn ~(tap of new-keys)
  1259. |= [t=trek m=meta]
  1260. ^- (unit coil)
  1261. ?: ?&
  1262. ?=(%coil -.m)
  1263. =((slag 2 t) /pub/m)
  1264. ==
  1265. `m
  1266. ~
  1267. =/ key-list=(list tape)
  1268. %+ murn ~(tap of new-keys)
  1269. |= [t=trek m=meta]
  1270. ^- (unit tape)
  1271. ?: ?=(%coil -.m)
  1272. `(en:base58:wrap p.key.m)
  1273. ~
  1274. =. master.state `master-key
  1275. :_ state(keys new-keys)
  1276. :~ :- %markdown
  1277. %- crip
  1278. """
  1279. ## imported keys
  1280. {<key-list>}
  1281. """
  1282. [%exit 0]
  1283. ==
  1284. ::
  1285. ++ do-export-keys
  1286. |= =cause
  1287. ?> ?=(%export-keys -.cause)
  1288. =/ keys-list=(list [trek meta])
  1289. ~(tap of keys.state)
  1290. =/ dat-jam (jam keys-list)
  1291. =/ path=@t 'keys.export'
  1292. =/ =effect [%file %write path dat-jam]
  1293. :_ state
  1294. :~ effect
  1295. :- %markdown
  1296. %- crip
  1297. """
  1298. ## exported keys
  1299. - path: {<path>}
  1300. """
  1301. [%exit 0]
  1302. ==
  1303. ::
  1304. ++ do-export-master-pubkey
  1305. |= =cause
  1306. ?> ?=(%export-master-pubkey -.cause)
  1307. %- (debug "import-master-pubkey")
  1308. ?~ master.state
  1309. ~& "wallet warning: no master keys available for export"
  1310. [[%exit 0]~ state]
  1311. =/ master-coil=coil ~(master get:v %pub)
  1312. ?. ?=(%pub -.key.master-coil)
  1313. ~& "wallet fatal: master pubkey malformed"
  1314. [[%exit 0]~ state]
  1315. =/ dat-jam=@ (jam master-coil)
  1316. =/ key-b58=tape (en:base58:wrap p.key.master-coil)
  1317. =/ cc-b58=tape (en:base58:wrap cc.master-coil)
  1318. :_ state
  1319. :~ :- %markdown
  1320. %- crip
  1321. """
  1322. ## exported master public key:
  1323. - pubkey: {key-b58}
  1324. - chaincode: {cc-b58}
  1325. """
  1326. [%exit 0]
  1327. [%file %write 'master-pubkey.export' dat-jam]
  1328. ==
  1329. ::
  1330. ++ do-import-master-pubkey
  1331. |= =cause
  1332. ?> ?=(%import-master-pubkey -.cause)
  1333. %- (debug "import-master-pubkey: {<coil.cause>}")
  1334. =/ master-pubkey-coil=coil coil.cause
  1335. =. master.state (some master-pubkey-coil)
  1336. =/ label `(crip "master-public-{<(end [3 4] p.key.master-pubkey-coil)>}")
  1337. =. keys.state (key:put:v master-pubkey-coil ~ label)
  1338. =/ key-b58=tape (en:base58:wrap p.key.master-pubkey-coil)
  1339. =/ cc-b58=tape (en:base58:wrap cc.master-pubkey-coil)
  1340. :_ state
  1341. :~ :- %markdown
  1342. %- crip
  1343. """
  1344. ## imported master public key:
  1345. - pubkey: {key-b58}
  1346. - chaincode: {cc-b58}
  1347. """
  1348. [%exit 0]
  1349. ==
  1350. ::
  1351. ++ do-gen-master-privkey
  1352. |= =cause
  1353. ?> ?=(%gen-master-privkey -.cause)
  1354. :: We do not need to reverse the endian-ness of the seedphrase
  1355. :: because the bip39 code expects a tape.
  1356. =/ seed=byts [64 (to-seed:bip39 (trip seedphrase.cause) "")]
  1357. =/ cor (from-seed:s10 seed)
  1358. =/ master-pubkey-coil=coil [%coil [%pub public-key] chain-code]:cor
  1359. =/ master-privkey-coil=coil [%coil [%prv private-key] chain-code]:cor
  1360. =. master.state (some master-pubkey-coil)
  1361. =/ public-label `(crip "master-public-{<(end [3 4] public-key:cor)>}")
  1362. =/ private-label `(crip "master-private-{<(end [3 4] public-key:cor)>}")
  1363. =. keys.state (key:put:v master-privkey-coil ~ private-label)
  1364. =. keys.state (key:put:v master-pubkey-coil ~ public-label)
  1365. =. keys.state (seed:put:v seedphrase.cause)
  1366. %- (debug "master.state: {<master.state>}")
  1367. [[%exit 0]~ state]
  1368. ::
  1369. ++ do-gen-master-pubkey
  1370. |= =cause
  1371. ?> ?=(%gen-master-pubkey -.cause)
  1372. =/ cor (from-private:s10 master-privkey.cause)
  1373. =/ master-pubkey-coil=coil [%coil [%pub public-key] chain-code]:cor
  1374. =/ master-privkey-coil=coil [%coil [%prv private-key] chain-code]:cor
  1375. %- (debug "Generated master public key: {<public-key:cor>}")
  1376. =/ public-label `(crip "master-public-{<(end [3 4] public-key:cor)>}")
  1377. =/ private-label `(crip "master-private-{<(end [3 4] public-key:cor)>}")
  1378. =. master.state (some master-pubkey-coil)
  1379. =. keys.state (key:put:v master-privkey-coil ~ private-label)
  1380. =. keys.state (key:put:v master-pubkey-coil ~ public-label)
  1381. %- (debug "master.state: {<master.state>}")
  1382. :_ state
  1383. :~ :- %markdown
  1384. %- crip
  1385. """
  1386. ## master public key
  1387. {<public-key:cor>}
  1388. """
  1389. [%exit 0]
  1390. ==
  1391. ::
  1392. ++ do-make-tx
  1393. |= =cause
  1394. ?> ?=(%make-tx -.cause)
  1395. %- (debug "make-tx: creating raw-tx")
  1396. :: note that new:raw-tx calls +validate already
  1397. =/ raw=raw-tx:transact (new:raw-tx:transact p.dat.cause)
  1398. =/ tx-id id.raw
  1399. =/ nock-cause=$>(%fact cause:dumb)
  1400. [%fact %0 %heard-tx raw]
  1401. %- (debug "make-tx: made raw-tx, poking over npc")
  1402. :_ state
  1403. :~
  1404. [%npc 0 %poke nock-cause]
  1405. [%exit 0]
  1406. ==
  1407. ::
  1408. ++ do-list-pubkeys
  1409. |= =cause
  1410. ?> ?=(%list-pubkeys -.cause)
  1411. =/ pubkeys ~(coils get:v %pub)
  1412. =/ base58-keys=(list tape)
  1413. %+ turn pubkeys
  1414. |= =coil
  1415. =/ pubkey=schnorr-pubkey:transact pub:(from-public:s10 [p.key cc]:coil)
  1416. %- trip
  1417. (to-b58:schnorr-pubkey:transact pubkey)
  1418. :_ state
  1419. :~ :- %markdown
  1420. %- crip
  1421. """
  1422. ## pubkeys
  1423. {<base58-keys>}
  1424. """
  1425. [%exit 0]
  1426. ==
  1427. ::
  1428. ++ do-show-seedphrase
  1429. |= =cause
  1430. ?> ?=(%show-seedphrase -.cause)
  1431. %- (debug "show-seedphrase")
  1432. =/ =meta seed:get:v
  1433. =/ seedphrase=@t
  1434. ?: ?=(%seed -.meta)
  1435. +.meta
  1436. %- crip
  1437. "no seedphrase found"
  1438. :_ state
  1439. :~ :- %markdown
  1440. %- crip
  1441. """
  1442. ## seedphrase
  1443. {<seedphrase>}
  1444. """
  1445. [%exit 0]
  1446. ==
  1447. ::
  1448. ++ do-show-master-pubkey
  1449. |= =cause
  1450. ?> ?=(%show-master-pubkey -.cause)
  1451. %- (debug "show-master-pubkey")
  1452. =/ =meta ~(master get:v %pub)
  1453. ?> ?=(%coil -.meta)
  1454. :_ state
  1455. :~ :- %markdown
  1456. %- crip
  1457. """
  1458. ## master public key
  1459. {(en:base58:wrap p.key.meta)}
  1460. """
  1461. [%exit 0]
  1462. ==
  1463. ::
  1464. ++ do-show-master-privkey
  1465. |= =cause
  1466. ?> ?=(%show-master-privkey -.cause)
  1467. %- (debug "show-master-privkey")
  1468. =/ =meta ~(master get:v %prv)
  1469. ?> ?=(%coil -.meta)
  1470. :_ state
  1471. :~ :- %markdown
  1472. %- crip
  1473. """
  1474. ## master private key
  1475. {(en:base58:wrap p.key.meta)}
  1476. """
  1477. [%exit 0]
  1478. ==
  1479. ++ do-scan
  1480. |= =cause
  1481. ?> ?=(%scan -.cause)
  1482. %- (debug "scan: scanning {<search-depth.cause>} addresses")
  1483. ?> ?=(^ master.state)
  1484. :: get all public keys up to search depth
  1485. =/ index=@ud search-depth.cause
  1486. =/ coils=(list coil)
  1487. =/ keys=(list coil) [~(master get:v %pub)]~
  1488. =| done=_|
  1489. |- ^- (list coil)
  1490. ?: done keys
  1491. =? done =(0 index) &
  1492. =/ base=trek /keys/pub/[ux/p.key:(public:master master.state)]/[ud/index]
  1493. =/ key=(unit coil)
  1494. ;; (unit coil)
  1495. (~(get of keys.state) base)
  1496. %= $
  1497. index ?:(=(0 index) 0 (dec index))
  1498. keys ?^(key (snoc keys u.key) keys)
  1499. ==
  1500. :: fail when no coils
  1501. ?: ?=(~ coils)
  1502. ~|("no coils for master key" !!)
  1503. :: generate first names of notes owned by each pubkey
  1504. =/ first-names=(list [hash:transact schnorr-pubkey:transact])
  1505. %+ turn coils
  1506. |= =coil
  1507. :: create lock from public key
  1508. =/ pubkey=schnorr-pubkey:transact pub:(from-public:s10 [p.key cc]:coil)
  1509. =/ =lock:transact (new:lock:transact pubkey)
  1510. :: generate name and take first name
  1511. =/ match-name=nname:transact
  1512. %- new:nname:transact
  1513. :* lock
  1514. [*hash:transact %.n] :: empty source, not a coinbase
  1515. *timelock:transact :: no timelock
  1516. ==
  1517. [-.match-name pubkey]
  1518. :: find notes with matching first names in balance
  1519. =/ notes=(list nnote:transact)
  1520. %+ murn
  1521. ~(tap z-by:zo balance.state)
  1522. |= [name=nname:transact note=nnote:transact]
  1523. ^- (unit nnote:transact)
  1524. :: check if first name matches any in our list
  1525. =/ matches
  1526. %+ lien first-names
  1527. |= [first-name=hash:transact pubkey=schnorr-pubkey:transact]
  1528. =/ =lock:transact (new:lock:transact pubkey)
  1529. :: update lock if include-multisig is true and pubkey is in
  1530. :: the multisig set in the note's lock
  1531. =? lock
  1532. ?& include-multisig.cause
  1533. (~(has z-in:zo pubkeys.lock.note) pubkey)
  1534. ==
  1535. lock.note
  1536. :: update match-name if include-timelocks is set
  1537. =? first-name include-timelocks.cause
  1538. =< -
  1539. %- new:nname:transact
  1540. :* lock
  1541. [*hash:transact %.n] :: empty source, not a coinbase
  1542. timelock.note :: include timelock
  1543. ==
  1544. =(-.name first-name)
  1545. ?:(matches `note ~)
  1546. %- (debug "found matches: {<notes>}")
  1547. =/ nodes=markdown:m
  1548. :~ :- %leaf
  1549. :- %heading
  1550. :* %atx 1
  1551. :~ [%text 'Scan Result']
  1552. ==
  1553. ==
  1554. :- %container
  1555. :- %ul
  1556. :* 0 '*'
  1557. (turn notes display-note)
  1558. ==
  1559. ==
  1560. :_ state
  1561. ~[(make-markdown-effect nodes) [%exit 0]]
  1562. ::
  1563. ++ do-list-notes
  1564. |= =cause
  1565. ?> ?=(%list-notes -.cause)
  1566. %- (debug "list-notes")
  1567. =/ notes=(list [name=nname:transact note=nnote:transact])
  1568. %+ sort ~(tap z-by:zo balance.state)
  1569. |= $: a=[name=nname:transact note=nnote:transact]
  1570. b=[name=nname:transact note=nnote:transact]
  1571. ==
  1572. (gth assets.note.a assets.note.b)
  1573. =/ nodes=markdown:m
  1574. %+ welp
  1575. %- need
  1576. %- de:md
  1577. %- crip
  1578. """
  1579. ## wallet notes
  1580. """
  1581. %- zing
  1582. %+ turn notes
  1583. |= [* =nnote:transact]
  1584. (display-note nnote)
  1585. :_ state
  1586. ~[(make-markdown-effect nodes) [%exit 0]]
  1587. ::
  1588. ++ do-list-notes-by-pubkey
  1589. |= =cause
  1590. ~& "list-notes-by-pubkey"
  1591. ?> ?=(%list-notes-by-pubkey -.cause)
  1592. ~& "list-notes-by-pubkey: {<pubkey.cause>}"
  1593. =/ target-pubkey=schnorr-pubkey:transact
  1594. (from-b58:schnorr-pubkey:transact pubkey.cause)
  1595. =/ matching-notes=(list [name=nname:transact note=nnote:transact])
  1596. %+ skim ~(tap z-by:zo balance.state)
  1597. |= [name=nname:transact note=nnote:transact]
  1598. (~(has z-in:zo pubkeys.lock.note) target-pubkey)
  1599. =/ sorted-notes=(list [name=nname:transact note=nnote:transact])
  1600. %+ sort matching-notes
  1601. |= $: a=[name=nname:transact note=nnote:transact]
  1602. b=[name=nname:transact note=nnote:transact]
  1603. ==
  1604. (gth assets.note.a assets.note.b)
  1605. =/ nodes=markdown:m
  1606. %+ welp
  1607. %- need
  1608. %- de:md
  1609. %- crip
  1610. """
  1611. ## wallet notes for pubkey {<(to-b58:schnorr-pubkey:transact target-pubkey)>}
  1612. """
  1613. %- zing
  1614. %+ turn sorted-notes
  1615. |= [* =nnote:transact]
  1616. (display-note nnote)
  1617. :_ state
  1618. ~[(make-markdown-effect nodes) [%raw sorted-notes] [%exit 0]]
  1619. ::
  1620. ++ do-simple-spend
  1621. |= =cause
  1622. ?> ?=(%simple-spend -.cause)
  1623. %- (debug "simple-spend: {<names.cause>}")
  1624. :: for now, each input corresponds to a single name and recipient. all
  1625. :: assets associated with the name are transferred to the recipient.
  1626. ::
  1627. :: thus there is one recipient per name, and one seed per recipient.
  1628. ::
  1629. =/ names=(list nname:transact)
  1630. %+ turn names.cause
  1631. |= [first=@t last=@t]
  1632. (from-b58:nname:transact [first last])
  1633. =/ recipients=(list lock:transact)
  1634. %+ turn recipients.cause
  1635. |= [m=@ pks=(list @t)]
  1636. %+ m-of-n:new:lock:transact m
  1637. %- ~(gas z-in:zo *(z-set:zo schnorr-pubkey:transact))
  1638. %+ turn pks
  1639. |= pk=@t
  1640. (from-b58:schnorr-pubkey:transact pk)
  1641. ::
  1642. =/ gifts=(list coins:transact) gifts.cause
  1643. ::
  1644. ?. ?& =((lent names) (lent recipients))
  1645. =((lent names) (lent gifts))
  1646. ==
  1647. ~|("different number of names/recipients/gifts" !!)
  1648. =| ledger=(list [name=nname:transact recipient=lock:transact gifts=coins:transact])
  1649. =. ledger
  1650. |-
  1651. ?~ names ledger
  1652. :: since we assert they are equal in length above, this is just to get
  1653. :: the i face
  1654. ?~ gifts ledger
  1655. ?~ recipients ledger
  1656. %= $
  1657. ledger [[i.names i.recipients i.gifts] ledger]
  1658. names t.names
  1659. gifts t.gifts
  1660. recipients t.recipients
  1661. ==
  1662. ::
  1663. :: the fee is subtracted from the first note that permits doing so without overspending
  1664. =/ fee=coins:transact fee.cause
  1665. :: use the first private key to construct the subsequent inputs
  1666. =/ sender=coil
  1667. =/ private-keys=(list coil) ~(coils get:v %prv)
  1668. ?~ private-keys
  1669. ~|("No private keys available for signing" !!)
  1670. (head private-keys)
  1671. =/ sender-key=schnorr-seckey:transact
  1672. (from-atom:schnorr-seckey:transact p.key.sender)
  1673. :: for each name, create an input from the corresponding note in sender's
  1674. :: balance at the current block. the fee will be subtracted entirely from
  1675. :: the first note that has sufficient assets for both the fee and the gift.
  1676. :: the refund is sent to receive-address.state
  1677. =/ [ins=(list input:transact) spent-fee=?]
  1678. %^ spin ledger `?`%.n
  1679. |= $: $: name=nname:transact
  1680. recipient=lock:transact
  1681. gift=coins:transact
  1682. ==
  1683. spent-fee=?
  1684. ==
  1685. =/ note=nnote:transact (get-note:v name)
  1686. ?: (gth gift assets.note)
  1687. ~| "gift {<gift>} larger than assets {<assets.note>} for recipient {<recipient>}"
  1688. !!
  1689. ?: ?& !spent-fee
  1690. (lte (add gift fee) assets.note)
  1691. ==
  1692. :: we can subtract the fee from this note
  1693. :_ %.y
  1694. %- with-choice:with-refund:simple-from-note:new:input:transact
  1695. [recipient gift fee note sender-key receive-address.state]
  1696. :: :: we cannot subtract the fee from this note, or we already have from a previous one
  1697. :_ %.n
  1698. %- with-choice:with-refund:simple-from-note:new:input:transact
  1699. [recipient gift 0 note sender-key receive-address.state]
  1700. ::
  1701. ?. spent-fee
  1702. ~|("no note suitable to subtract fee from, aborting operation" !!)
  1703. =/ ins-draft=inputs:transact (multi:new:inputs:transact ins)
  1704. ?: ?=(~ last-block.state)
  1705. ~|("last-block unknown!" !!)
  1706. :: name is the b58-encoded name of the first input
  1707. =/ draft-name=@t
  1708. %- head
  1709. %+ turn ~(tap z-by:zo (names:inputs:transact ins-draft))
  1710. |= =nname:transact
  1711. =< last
  1712. (to-b58:nname:transact nname)
  1713. :: jam inputs and save as draft
  1714. =/ =draft
  1715. %* . *draft
  1716. p ins-draft
  1717. name draft-name
  1718. ==
  1719. =/ draft-jam (jam draft)
  1720. =/ path=@t
  1721. %- crip
  1722. "./drafts/{(trip name.draft)}.draft"
  1723. %- (debug "saving draft to {<path>}")
  1724. =/ =effect [%file %write path draft-jam]
  1725. :- ~[effect [%exit 0]]
  1726. state
  1727. ::
  1728. ++ do-keygen
  1729. |= =cause
  1730. ?> ?=(%keygen -.cause)
  1731. =+ [seed-phrase=@t cor]=(gen-master-key:s10 entropy.cause salt.cause)
  1732. =/ master-public-coil [%coil [%pub public-key] chain-code]:cor
  1733. =/ master-private-coil [%coil [%prv private-key] chain-code]:cor
  1734. =. master.state (some master-public-coil)
  1735. %- (debug "keygen: public key: {<(en:base58:wrap public-key:cor)>}")
  1736. %- (debug "keygen: private key: {<(en:base58:wrap private-key:cor)>}")
  1737. =/ pub-label `(crip "master-public-{<(end [3 4] public-key:cor)>}")
  1738. =/ prv-label `(crip "master-public-{<(end [3 4] public-key:cor)>}")
  1739. =. keys.state (key:put:v master-public-coil ~ pub-label)
  1740. =. keys.state (key:put:v master-private-coil ~ prv-label)
  1741. =. keys.state (seed:put:v seed-phrase)
  1742. :_ state
  1743. :~ :- %markdown
  1744. %- crip
  1745. """
  1746. ## Keygen
  1747. ### New Public Key
  1748. {<(en:base58:wrap public-key:cor)>}
  1749. ### New Private Key
  1750. {<(en:base58:wrap private-key:cor)>}
  1751. ### Chain Code
  1752. {<(en:base58:wrap chain-code:cor)>}
  1753. ### Seed Phrase
  1754. {<seed-phrase>}
  1755. """
  1756. [%exit 0]
  1757. ==
  1758. ::
  1759. :: derives child %pub or %prv key of current master key
  1760. :: at index `i`. this will overwrite existing paths.
  1761. ++ do-derive-child
  1762. |= =cause
  1763. ?> ?=(%derive-child -.cause)
  1764. =/ par=coil ~(master get:v key-type.cause)
  1765. =/ child=coil (derive-child:v par i.cause)
  1766. =. keys.state
  1767. (key:put:v child `i.cause label.cause)
  1768. :- [%exit 0]~
  1769. state
  1770. ::
  1771. ++ do-sign-tx
  1772. |= =cause
  1773. ?> ?=(%sign-tx -.cause)
  1774. %- (debug "sign-tx: {<dat.cause>}")
  1775. :: get private key at specified index, or first derived key if no index
  1776. =/ private-keys=(list coil) ~(coils get:v %prv)
  1777. ?~ private-keys
  1778. ~|("No private keys available for signing" !!)
  1779. =/ sender=coil
  1780. ?~ index.cause i.private-keys
  1781. =/ key-at-index=meta (~(by-index get:v %prv) u.index.cause)
  1782. ?> ?=(%coil -.key-at-index)
  1783. key-at-index
  1784. =/ sender-key=schnorr-seckey:transact
  1785. (from-atom:schnorr-seckey:transact p.key.sender)
  1786. =/ signed-inputs=inputs:transact
  1787. %- ~(run z-by:zo p.dat.cause)
  1788. |= =input:transact
  1789. %- (debug "signing input: {<input>}")
  1790. =. spend.input
  1791. %+ sign:spend:transact
  1792. spend.input
  1793. sender-key
  1794. input
  1795. =/ signed-draft=draft
  1796. %= dat.cause
  1797. p signed-inputs
  1798. ==
  1799. =/ draft-jam (jam signed-draft)
  1800. =/ path=@t
  1801. %- crip
  1802. "./drafts/{(trip name.signed-draft)}.draft"
  1803. %- (debug "saving input draft to {<path>}")
  1804. =/ =effect [%file %write path draft-jam]
  1805. :- ~[effect [%exit 0]]
  1806. state
  1807. ::
  1808. ++ do-advanced-spend-seed
  1809. |= cause=advanced-spend-seed
  1810. ^- [(list effect) ^state]
  1811. |^
  1812. =? active-draft.state ?=(~ active-draft.state) `*draft-name
  1813. =? active-seed.state ?=(~ active-seed.state) `*seed-name
  1814. ?- -.cause
  1815. %new do-new
  1816. %set-name do-set-name
  1817. %set-source do-set-source
  1818. %set-recipient do-set-recipient
  1819. %set-timelock do-set-timelock
  1820. %set-gift do-set-gift
  1821. %set-parent-hash do-set-parent-hash
  1822. %set-parent-hash-from-name do-set-parent-hash-from-name
  1823. %print-status do-print-status
  1824. ==
  1825. ::
  1826. ++ do-new
  1827. ?> ?=([%new *] cause)
  1828. =/ sed=preseed
  1829. %* . *preseed
  1830. name name.cause
  1831. ==
  1832. (write-seed sed)
  1833. ::
  1834. ++ do-set-name
  1835. ?> ?=([%set-name *] cause)
  1836. =/ pre=(unit preseed)
  1837. (get-seed:p seed-name.cause)
  1838. ?> ?=(^ pre)
  1839. =. name.u.pre new-name.cause
  1840. (write-seed u.pre)
  1841. ::
  1842. ++ do-set-source
  1843. ?> ?=([%set-source *] cause)
  1844. =/ pre=(unit preseed)
  1845. (get-seed:p seed-name.cause)
  1846. ?> ?=(^ pre)
  1847. =/ sed=preseed
  1848. ?~ source.cause
  1849. %= u.pre
  1850. output-source.p ~
  1851. output-source.q %.y
  1852. ==
  1853. %= u.pre
  1854. output-source.p (some (from-b58:source:transact u.source.cause))
  1855. output-source.q %.y
  1856. ==
  1857. (write-seed sed)
  1858. ::
  1859. ++ do-set-recipient
  1860. ?> ?=([%set-recipient *] cause)
  1861. =/ pre=(unit preseed)
  1862. (get-seed:p seed-name.cause)
  1863. ?> ?=(^ pre)
  1864. =/ recipient=lock:transact
  1865. %+ m-of-n:new:lock:transact m.recipient.cause
  1866. %- ~(gas z-in:zo *(z-set:zo schnorr-pubkey:transact))
  1867. (turn pks.recipient.cause from-b58:schnorr-pubkey:transact)
  1868. =/ sed=preseed
  1869. %= u.pre
  1870. recipient.p recipient
  1871. recipient.q %.y
  1872. ==
  1873. (write-seed sed)
  1874. ::
  1875. ++ do-set-timelock
  1876. ?> ?=([%set-timelock *] cause)
  1877. ::TODO
  1878. !!
  1879. ::
  1880. ++ do-set-gift
  1881. ?> ?=([%set-gift *] cause)
  1882. =/ pre=(unit preseed)
  1883. (get-seed:p seed-name.cause)
  1884. ?> ?=(^ pre)
  1885. =/ sed=preseed
  1886. %= u.pre
  1887. gift.p gift.cause
  1888. gift.q %.y
  1889. ==
  1890. (write-seed sed)
  1891. ::
  1892. ++ do-set-parent-hash
  1893. ?> ?=([%set-parent-hash *] cause)
  1894. =/ pre=(unit preseed)
  1895. (get-seed:p seed-name.cause)
  1896. ?> ?=(^ pre)
  1897. =/ sed=preseed
  1898. %= u.pre
  1899. parent-hash.q %.y
  1900. parent-hash.p (from-b58:hash:transact parent-hash.cause)
  1901. ==
  1902. (write-seed sed)
  1903. ::
  1904. ++ do-set-parent-hash-from-name
  1905. ?> ?=([%set-parent-hash-from-name *] cause)
  1906. =/ pre=(unit preseed)
  1907. (get-seed:p seed-name.cause)
  1908. ?> ?=(^ pre)
  1909. =/ name=nname:transact (from-b58:nname:transact name.cause)
  1910. =/ not=nnote:transact (get-note:v name)
  1911. =/ sed=preseed
  1912. %= u.pre
  1913. parent-hash.p (hash:nnote:transact not)
  1914. parent-hash.q %.y
  1915. ==
  1916. (write-seed sed)
  1917. ::
  1918. ++ do-print-status
  1919. ?> ?=([%print-status *] cause)
  1920. =/ pre=(unit preseed)
  1921. (get-seed:p seed-name.cause)
  1922. ?> ?=(^ pre)
  1923. =/ output-source-text=tape
  1924. ?: !output-source.q.u.pre
  1925. "Unset (any output source is OK)"
  1926. <output-source.p.u.pre>
  1927. =/ recipient-text=tape
  1928. ?: !recipient.q.u.pre
  1929. "Unset"
  1930. <recipient.p.u.pre>
  1931. =/ timelock-text=tape
  1932. ?: !timelock-intent.q.u.pre
  1933. "Unset (no intent)"
  1934. <timelock-intent.p.u.pre>
  1935. =/ gift-text=tape
  1936. ?: !gift.q.u.pre
  1937. "Gift: unset (gift must be nonzero)"
  1938. ?: =(0 gift.p.u.pre)
  1939. """
  1940. Gift: 0 (must be nonzero)
  1941. """
  1942. """
  1943. Gift: {<gift.p.u.pre>}
  1944. """
  1945. =/ status-text=tape
  1946. """
  1947. ## Seed Status
  1948. ### Output Source
  1949. {output-source-text}
  1950. ### Recipient
  1951. {recipient-text}
  1952. ### Timelock Intent
  1953. {timelock-text}
  1954. ### Gift
  1955. {gift-text}
  1956. """
  1957. :_ state
  1958. (print (need (de:md (crip status-text))))
  1959. ::
  1960. ++ write-seed
  1961. |= sed=preseed
  1962. ^- [(list effect) ^state]
  1963. =. active-seed.state (some name.sed)
  1964. =. draft-tree.state (~(add-seed plan draft-tree.state) name.sed sed)
  1965. =^ writes state write-draft:d
  1966. [writes state]
  1967. -- ::+do-advanced-spend-seed
  1968. ::
  1969. ++ do-advanced-spend-input
  1970. |= cause=advanced-spend-input
  1971. ^- [(list effect) ^state]
  1972. |^
  1973. ?- -.cause
  1974. %new do-new
  1975. %set-name do-set-name
  1976. %add-seed do-add-seed
  1977. %set-fee do-set-fee
  1978. %set-note-from-name do-set-note-from-name
  1979. %set-note-from-hash do-set-note-from-hash
  1980. %derive-note-from-seeds do-derive-note-from-seeds
  1981. %remove-seed do-remove-seed
  1982. %remove-seed-by-hash do-remove-seed-by-hash
  1983. %print-status do-print-status
  1984. ==
  1985. ::
  1986. ++ do-new
  1987. ?> ?=([%new *] cause)
  1988. =/ inp=preinput
  1989. %* . *preinput
  1990. name name.cause
  1991. ==
  1992. =. active-input.state (some name.cause)
  1993. (write-input inp)
  1994. ::
  1995. ++ do-set-name
  1996. ?> ?=([%set-name *] cause)
  1997. =/ pre=(unit preinput)
  1998. (get-input:p input-name.cause)
  1999. ?> ?=(^ pre)
  2000. =. name.u.pre new-name.cause
  2001. =. active-input.state (some new-name.cause)
  2002. (write-input u.pre)
  2003. ::
  2004. ++ do-add-seed
  2005. ?> ?=([%add-seed *] cause)
  2006. ::
  2007. =/ pre=(unit preinput)
  2008. (get-input:p input-name.cause)
  2009. ?> ?=(^ pre)
  2010. =/ sed=(unit preseed)
  2011. (get-seed:p seed-name.cause)
  2012. ?> ?=(^ sed)
  2013. ?: (~(has z-in:zo seeds.spend.p.u.pre) p.u.sed)
  2014. :_ state
  2015. =/ nodes=markdown:m
  2016. :~ :- %leaf
  2017. :- %paragraph
  2018. :~ [%text (crip "seed already exists in .spend, doing nothing.")]
  2019. ==
  2020. ==
  2021. (print nodes)
  2022. =/ inp=preinput
  2023. %= u.pre
  2024. seeds.spend.p (~(put z-in:zo seeds.spend.p.u.pre) p.u.sed)
  2025. seeds.spend.q %.y
  2026. ==
  2027. (write-input inp)
  2028. ::
  2029. ++ do-set-fee
  2030. ?> ?=([%set-fee *] cause)
  2031. =/ pre=(unit preinput)
  2032. (get-input:p input-name.cause)
  2033. ?> ?=(^ pre)
  2034. =. fee.spend.p.u.pre fee.cause
  2035. =. fee.spend.q.u.pre %.y
  2036. (write-input u.pre)
  2037. ::
  2038. ++ do-set-note-from-name
  2039. ?> ?=([%set-note-from-name *] cause)
  2040. ::
  2041. =/ pre=(unit preinput)
  2042. (get-input:p input-name.cause)
  2043. ?> ?=(^ pre)
  2044. =/ name=nname:transact (from-b58:nname:transact name.cause)
  2045. =/ not=nnote:transact (get-note:v name)
  2046. =/ inp=preinput
  2047. %= u.pre
  2048. note.p not
  2049. note.q %.y
  2050. ==
  2051. (write-input inp)
  2052. ::
  2053. ++ do-set-note-from-hash
  2054. ?> ?=([%set-note-from-hash *] cause)
  2055. ::
  2056. =/ pre=(unit preinput)
  2057. (get-input:p input-name.cause)
  2058. ?> ?=(^ pre)
  2059. =/ =hash:transact (from-b58:hash:transact hash.cause)
  2060. =/ note=nnote:transact (get-note-from-hash:v hash)
  2061. =/ inp=preinput
  2062. %= u.pre
  2063. note.p note
  2064. note.q %.y
  2065. ==
  2066. (write-input inp)
  2067. ::
  2068. ++ do-derive-note-from-seeds
  2069. ?> ?=([%derive-note-from-seeds *] cause)
  2070. ::
  2071. =/ pre=(unit preinput)
  2072. (get-input:p input-name.cause)
  2073. ?> ?=(^ pre)
  2074. =/ seeds-list=(list seed:transact)
  2075. ~(tap z-in:zo seeds.spend.p.u.pre)
  2076. ?~ seeds-list
  2077. :_ state
  2078. =/ nodes=markdown:m
  2079. :~ :- %leaf
  2080. :- %paragraph
  2081. :~ [%text (crip "no seeds exist in .spend, so note cannot be set.")]
  2082. ==
  2083. ==
  2084. (print nodes)
  2085. =/ =hash:transact parent-hash.i.seeds-list
  2086. =/ note=nnote:transact (get-note-from-hash:v hash)
  2087. =/ inp=preinput
  2088. %= u.pre
  2089. note.p note
  2090. note.q %.y
  2091. ==
  2092. (write-input inp)
  2093. ::
  2094. ++ do-remove-seed
  2095. ?> ?=([%remove-seed *] cause)
  2096. ::
  2097. =/ pre=(unit preinput)
  2098. (get-input:p input-name.cause)
  2099. ?> ?=(^ pre)
  2100. =. active-input.state (some input-name.cause)
  2101. =/ sed=(unit preseed)
  2102. (get-seed:p seed-name.cause)
  2103. ?> ?=(^ sed)
  2104. ?: !(~(has z-in:zo seeds.spend.p.u.pre) p.u.sed)
  2105. :_ state
  2106. =/ nodes=markdown:m
  2107. :~ :- %leaf
  2108. :- %paragraph
  2109. :~ [%text (crip "seed does not exist in .spend, doing nothing")]
  2110. ==
  2111. ==
  2112. (print nodes)
  2113. =/ inp=preinput
  2114. %= u.pre
  2115. seeds.spend.p (~(del z-in:zo seeds.spend.p.u.pre) p.u.sed)
  2116. seeds.spend.q !=(*seeds:transact seeds.spend.p.u.pre)
  2117. ==
  2118. (write-input inp)
  2119. ::
  2120. ++ do-remove-seed-by-hash
  2121. ?> ?=([%remove-seed-by-hash *] cause)
  2122. :: find seed with hash
  2123. =/ pre=(unit preinput)
  2124. (get-input:p input-name.cause)
  2125. ?> ?=(^ pre)
  2126. =. active-input.state (some input-name.cause)
  2127. =/ seed-hashes=(z-map:zo hash:transact seed:transact)
  2128. %- ~(gas z-by:zo *(z-map:zo hash:transact seed:transact))
  2129. %+ turn ~(tap z-in:zo seeds.spend.p.u.pre)
  2130. |= sed=seed:transact
  2131. [(hash:seed:transact sed) sed]
  2132. =/ has=hash:transact (from-b58:hash:transact hash.cause)
  2133. ?. (~(has z-by:zo seed-hashes) has)
  2134. :_ state
  2135. =/ nodes=markdown:m
  2136. :~ :- %leaf
  2137. :- %paragraph
  2138. :~ [%text (crip "seed does not exist in .spend, doing nothing")]
  2139. ==
  2140. ==
  2141. (print nodes)
  2142. =/ remove-seed=seed:transact
  2143. (~(got z-by:zo seed-hashes) has)
  2144. ::
  2145. =/ inp=preinput
  2146. %= u.pre
  2147. seeds.spend.p (~(del z-in:zo seeds.spend.p.u.pre) remove-seed)
  2148. seeds.spend.q !=(*seeds:transact seeds.spend.p.u.pre)
  2149. ==
  2150. (write-input inp)
  2151. ::
  2152. ++ do-print-status
  2153. ?> ?=([%print-status *] cause)
  2154. =/ pre=(unit preinput)
  2155. (get-input:p input-name.cause)
  2156. ?> ?=(^ pre)
  2157. =| status-nodes=markdown:m
  2158. =. status-nodes
  2159. %+ snoc status-nodes
  2160. :- %leaf
  2161. :- %paragraph
  2162. ?: !signature.spend.q.u.pre
  2163. :: TODO we removed the ability to sign in this control flow
  2164. [%text (crip ".signature: unset")]~
  2165. :: check the signature
  2166. ::
  2167. :: get a .parent-hash of a seed. they have to all be the same, so which
  2168. :: one doesn't matter; if they're not all the same validation will fail.
  2169. =/ seeds-list=(list seed:transact)
  2170. ~(tap z-in:zo seeds.spend.p.u.pre)
  2171. ?~ seeds-list
  2172. [%text (crip "no seeds exist, so signature cannot be checked")]~
  2173. =/ parent-note-hash=hash:transact parent-hash.i.seeds-list
  2174. =/ parent-note-hash-b58=tape
  2175. (trip (to-b58:hash:transact parent-note-hash))
  2176. =/ parent-note-name=(unit nname:transact)
  2177. (~(get z-by:zo hash-to-name.state) parent-note-hash)
  2178. ?~ parent-note-name
  2179. :~
  2180. :- %text
  2181. %- crip
  2182. """
  2183. note with hash {parent-note-hash-b58} present in .spend but
  2184. has no matching .name in wallet
  2185. """
  2186. :- %text
  2187. :: TODO better, more succint error message.
  2188. '''
  2189. this implies that it is not in the balance unless there is a hash collision.
  2190. please report this as a bug if you are sure you have the $note, as this
  2191. situation is very unlkely. the spend ought to still be valid in that case
  2192. and you can broadcast it anyway.
  2193. '''
  2194. ==
  2195. =/ parent-note-name-b58=tape
  2196. =; [first=@t last=@t]
  2197. "<(trip first)> <(trip last)>"
  2198. (to-b58:nname:transact u.parent-note-name)
  2199. =/ parent-note=(unit nnote:transact)
  2200. (~(get z-by:zo balance.state) u.parent-note-name)
  2201. ?~ parent-note
  2202. :~
  2203. :- %text
  2204. %- crip
  2205. """
  2206. note with name {parent-note-name-b58} and hash {parent-note-hash-b58}
  2207. present in .spend but not in balance
  2208. """
  2209. ==
  2210. ?: (verify:spend:transact spend.p.u.pre u.parent-note)
  2211. [%text (crip "signature(s) on spend are valid.")]~
  2212. :: missing or invalid sigs
  2213. =/ have-sigs
  2214. %+ turn
  2215. %~ tap z-in:zo
  2216. ^- (z-set:zo schnorr-pubkey:transact)
  2217. %~ key z-by:zo (need signature.spend.p.u.pre)
  2218. |= pk=schnorr-pubkey:transact
  2219. [%text (to-b58:schnorr-pubkey:transact pk)]
  2220. ?~ have-sigs
  2221. [%text 'no signatures found!']~
  2222. =/ lock-b58=[m=@ pks=(list @t)]
  2223. (to-b58:lock:transact recipient.i.seeds-list)
  2224. =/ need-sigs
  2225. (turn pks.lock-b58 (lead %text))
  2226. ?~ need-sigs
  2227. [%text 'no recipients found!']~
  2228. ;: welp
  2229. :~ [%text (crip "signature on spend did not validate.")]
  2230. [%text (crip "signatures on spend:")]
  2231. ==
  2232. ::TODO check if any particular signature did not validate
  2233. have-sigs
  2234. :~ [%text (crip ".lock on parent note:")]
  2235. [%text (crip "number of sigs required: {(scow %ud m.lock-b58)}")]
  2236. [%text (crip "pubkeys of possible signers:")]
  2237. ==
  2238. need-sigs
  2239. ==
  2240. ::TODO check individual seeds? this would require some refactoring and
  2241. ::the happy path does not involve adding unfinished seeds to an input.
  2242. :_ state
  2243. (print status-nodes)
  2244. ::
  2245. ++ write-input
  2246. |= inp=preinput
  2247. ^- [(list effect) ^state]
  2248. =. active-input.state (some name.inp)
  2249. =. draft-tree.state (~(add-input plan draft-tree.state) name.inp inp)
  2250. =^ writes state write-draft:d
  2251. [writes state]
  2252. -- ::+do-advanced-spend-input
  2253. ::
  2254. ++ do-advanced-spend-draft
  2255. |= cause=advanced-spend-draft
  2256. ^- [(list effect) ^state]
  2257. |^
  2258. =? active-draft.state ?=(~ active-draft.state) `*draft-name
  2259. ?- -.cause
  2260. %new do-new
  2261. %set-name do-set-name
  2262. %add-input do-add-input
  2263. %remove-input do-remove-input
  2264. %remove-input-by-name do-remove-input-by-name
  2265. %print-status do-print-status
  2266. ==
  2267. ::
  2268. ++ do-new
  2269. ?> ?=([%new *] cause)
  2270. =. active-draft.state (some name.cause)
  2271. =/ dat=draft
  2272. %* . *draft
  2273. name name.cause
  2274. ==
  2275. (write-draft dat)
  2276. ::
  2277. ++ do-set-name
  2278. ?> ?=([%set-name *] cause)
  2279. =/ pre=(unit draft)
  2280. (get-draft:p draft-name.cause)
  2281. ?> ?=(^ pre)
  2282. =. active-draft.state (some new-name.cause)
  2283. =. name.u.pre new-name.cause
  2284. (write-draft u.pre)
  2285. ::
  2286. ++ do-add-input
  2287. ?> ?=([%add-input *] cause)
  2288. =/ pre=(unit draft)
  2289. (get-draft:p draft-name.cause)
  2290. ?> ?=(^ pre)
  2291. =. active-draft.state (some draft-name.cause)
  2292. =/ inp=(unit preinput)
  2293. (get-input:p input-name.cause)
  2294. ?> ?=(^ inp)
  2295. ?: (~(has z-by:zo p.u.pre) name.note.p.u.inp)
  2296. :_ state
  2297. %- print
  2298. ^- markdown:m
  2299. :_ ~ :- %leaf
  2300. :- %paragraph
  2301. :_ ~ :- %text
  2302. %- crip
  2303. """
  2304. draft already has input with note name
  2305. {<(to-b58:nname:transact name.note.p.u.inp)>}, doing nothing.
  2306. """
  2307. =. p.u.pre
  2308. (~(put z-by:zo p.u.pre) [name.note.p.u.inp p.u.inp])
  2309. (write-draft u.pre)
  2310. ::
  2311. ++ do-remove-input
  2312. ?> ?=([%remove-input *] cause)
  2313. =/ pre=(unit draft)
  2314. (get-draft:p draft-name.cause)
  2315. ?> ?=(^ pre)
  2316. =. active-draft.state (some draft-name.cause)
  2317. =/ inp=(unit preinput)
  2318. (get-input:p input-name.cause)
  2319. ?> ?=(^ inp)
  2320. ?. (~(has z-by:zo p.u.pre) name.note.p.u.inp)
  2321. :_ state
  2322. %- print
  2323. :_ ~ :- %leaf
  2324. :- %paragraph
  2325. :_ ~ :- %text
  2326. %- crip
  2327. """
  2328. draft does not have input with note name
  2329. {<(to-b58:nname:transact name.note.p.u.inp)>}, doing nothing.
  2330. """
  2331. ?. =(u.inp (~(got z-by:zo p.u.pre) name.note.p.u.inp))
  2332. :_ state
  2333. %- print
  2334. :_ ~ :- %leaf
  2335. :- %paragraph
  2336. :_ ~ :- %text
  2337. %- crip
  2338. """
  2339. draft has input with note name
  2340. {<(to-b58:nname:transact name.note.p.u.inp)>}, but it is
  2341. a different input. to remove this input, use %remove-input-by-name
  2342. instead.
  2343. """
  2344. =. p.u.pre
  2345. (~(del z-by:zo p.u.pre) name.note.p.u.inp)
  2346. (write-draft u.pre)
  2347. ::
  2348. ++ do-remove-input-by-name
  2349. ?> ?=([%remove-input-by-name *] cause)
  2350. =/ pre=(unit draft)
  2351. (get-draft:p draft-name.cause)
  2352. =. active-draft.state (some draft-name.cause)
  2353. ?> ?=(^ pre)
  2354. =/ name=nname:transact (from-b58:nname:transact name.cause)
  2355. ?. (~(has z-by:zo p.u.pre) name)
  2356. :_ state
  2357. %- print
  2358. :_ ~ :- %leaf
  2359. :- %paragraph
  2360. :_ ~ :- %text
  2361. %- crip
  2362. """
  2363. draft does not have input with note name {(trip first.name.cause)}
  2364. {(trip last.name.cause)}, doing nothing.
  2365. """
  2366. =. p.u.pre (~(del z-by:zo p.u.pre) name)
  2367. (write-draft u.pre)
  2368. ::
  2369. ++ do-print-status
  2370. ?> ?=([%print-status *] cause)
  2371. =/ pre=(unit draft)
  2372. (get-draft:p draft-name.cause)
  2373. =. active-draft.state (some draft-name.cause)
  2374. ?> ?=(^ pre)
  2375. =/ inputs=(list [name=nname:transact =input:transact])
  2376. ~(tap z-by:zo p.u.pre)
  2377. =/ input-texts=(list tape)
  2378. %+ turn inputs
  2379. |= [name=nname:transact =input:transact]
  2380. =/ signature-text=tape ?~(signature.spend.input "unset" "set")
  2381. =/ name-text=tape <(to-b58:nname:transact name)>
  2382. =/ note-text=tape <(to-b58:hash:transact (hash:nnote:transact note.input))>
  2383. =/ seeds-text=tape
  2384. %- zing
  2385. %+ turn ~(tap z-in:zo seeds.spend.input)
  2386. |= =seed:transact
  2387. """
  2388. - recipient: {<(to-b58:lock:transact recipient.seed)>}
  2389. - gift: {<gift.seed>}
  2390. - parent hash: {<(to-b58:hash:transact parent-hash.seed)>}
  2391. """
  2392. """
  2393. #### Input {name-text}:
  2394. - Note hash: {note-text}
  2395. - Fee: {<fee.spend.input>}
  2396. - Signature: {signature-text}
  2397. ##### Seeds
  2398. {seeds-text}
  2399. """
  2400. =/ status-text=tape
  2401. """
  2402. ## Draft Status
  2403. Name: {(trip name.u.pre)}
  2404. Number of inputs: {<(lent inputs)>}
  2405. ### Inputs
  2406. {(zing input-texts)}
  2407. """
  2408. :_ state
  2409. (print (need (de:md (crip status-text))))
  2410. ::
  2411. ++ write-draft
  2412. |= dat=draft
  2413. ^- [(list effect) ^state]
  2414. =. active-draft.state (some name.dat)
  2415. write-draft:d
  2416. -- ::+do-advanced-spend-draft
  2417. -- ::+poke
  2418. --