wallet.hoon 71 KB

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