summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpolwex <polwex@sortug.com>2025-06-22 06:14:42 +0700
committerpolwex <polwex@sortug.com>2025-06-22 06:14:42 +0700
commit6dccba9bb5100329209ad01732f9d63f4c4fb43b (patch)
tree140b33d2e25084174fce057056de9dea0e2dcbea
metamask login getting there
-rw-r--r--app/zodiac.hoon62
-rw-r--r--desk.bill2
-rw-r--r--desk.ship1
-rw-r--r--lib/ethereum.hoon1006
-rw-r--r--lib/metamask.hoon169
-rw-r--r--lib/naive.hoon926
-rw-r--r--lib/server.hoon179
-rw-r--r--lib/sortug.hoon679
-rw-r--r--lib/tiny.hoon670
-rw-r--r--mar/bill.hoon34
-rw-r--r--mar/hoon.hoon36
-rw-r--r--mar/kelvin.hoon28
-rw-r--r--mar/mime.hoon32
-rw-r--r--mar/noun.hoon22
-rw-r--r--mar/ship.hoon20
-rw-r--r--sur/zodiac.hoon7
-rw-r--r--sys.kelvin1
-rw-r--r--web/index.hoon465
-rw-r--r--web/router.hoon101
19 files changed, 4440 insertions, 0 deletions
diff --git a/app/zodiac.hoon b/app/zodiac.hoon
new file mode 100644
index 0000000..43a5694
--- /dev/null
+++ b/app/zodiac.hoon
@@ -0,0 +1,62 @@
+/- *zodiac
+/= router /web/router
+=| versioned-state
+=* state -
+^- agent:gall
+|_ =bowl:gall
++* this .
+++ on-init
+ ^- (quip card:agent:gall agent:gall)
+ :_ this
+ =/ weblib router(bowl bowl)
+ :~((eyre-binding-card:weblib /zodiac))
+::
+++ on-save
+ ^- vase
+ !>(state)
+::
+++ on-load
+ |~ old-state=vase
+ ^- (quip card:agent:gall agent:gall)
+ `this
+::
+++ on-poke
+ |= [=mark =vase]
+ ^- (quip card:agent:gall agent:gall)
+ ?+ mark `this
+ %handle-http-request
+ =/ order !<([id=@ta req=inbound-request:eyre] vase)
+ =/ weblib ~(. router [bowl id.order req.order state])
+ :- route:weblib this
+ ==
+::
+++ on-watch
+ |~ path
+ ^- (quip card:agent:gall agent:gall)
+ `this
+::
+++ on-leave
+ |~ path
+ ^- (quip card:agent:gall agent:gall)
+ `this
+::
+++ on-peek
+ |~ path
+ ^- (unit (unit cage))
+ ~
+::
+++ on-agent
+ |~ [wire sign:agent:gall]
+ ^- (quip card:agent:gall agent:gall)
+ `this
+::
+++ on-arvo
+ |~ [wire =sign-arvo]
+ ^- (quip card:agent:gall agent:gall)
+ `this
+::
+++ on-fail
+ |~ [term tang]
+ ^- (quip card:agent:gall agent:gall)
+ `this
+--
diff --git a/desk.bill b/desk.bill
new file mode 100644
index 0000000..32ce7b6
--- /dev/null
+++ b/desk.bill
@@ -0,0 +1,2 @@
+:~ %zodiac
+==
diff --git a/desk.ship b/desk.ship
new file mode 100644
index 0000000..7661bd5
--- /dev/null
+++ b/desk.ship
@@ -0,0 +1 @@
+~zod
diff --git a/lib/ethereum.hoon b/lib/ethereum.hoon
new file mode 100644
index 0000000..0c2c635
--- /dev/null
+++ b/lib/ethereum.hoon
@@ -0,0 +1,1006 @@
+:: ethereum: utilities
+::
+=, ethereum-types
+|%
+:: deriving and using ethereum keys
+::
+++ key
+ |%
+ ++ address-from-pub
+ =, keccak:crypto
+ |= pub=@
+ %+ end [3 20]
+ %+ keccak-256 64
+ (rev 3 64 pub)
+ ::
+ ++ address-from-prv
+ (cork pub-from-prv address-from-pub)
+ ::
+ ++ pub-from-prv
+ =, secp256k1:secp:crypto
+ |= prv=@
+ %- serialize-point
+ (priv-to-pub prv)
+ ::
+ ++ sign-typed-transaction
+ |= [tx=typed-transaction:rpc pk=@]
+ ^- @ux
+ =- (cat 3 - -.tx)
+ ?- -.tx
+ %0x0 (sign-transaction +.tx pk)
+ %0x2 (sign-transaction-1559 +.tx pk)
+ ==
+ ::
+ ++ sign-transaction
+ =, crypto
+ |= [tx=transaction:rpc pk=@]
+ |^ ^- @ux
+ :: hash the raw transaction data
+ =/ hash=@
+ %- keccak-256:keccak
+ =+ dat=(encode chain-id.tx 0 0)
+ =+ wid=(met 3 dat)
+ [wid (rev 3 wid dat)]
+ :: sign transaction hash with private key
+ =+ (ecdsa-raw-sign:secp256k1:secp hash pk)
+ :: complete transaction is raw data, with r and s
+ :: taken from the signature, and v as per eip-155
+ (encode :(add (mul chain-id.tx 2) 35 v) r s)
+ ::
+ ++ encode
+ |= [v=@ r=@ s=@]
+ %+ encode:rlp %l
+ tx(to b+20^to.tx, chain-id [v r s ~])
+ --
+ ::
+ ++ sign-transaction-1559
+ =, crypto
+ |= [tx=transaction-1559:rpc pk=@]
+ |^ ^- @ux
+ =; hash=@
+ =+ (ecdsa-raw-sign:secp256k1:secp hash pk)
+ ::NOTE we retrieve y's parity from the v value
+ (encode-1559 ~ (end 0 v) r s)
+ :: hash the raw transaction data including leading 0x2
+ %- keccak-256:keccak
+ =+ dat=(cat 3 (encode-1559 ~) 0x2)
+ =+ wid=(met 3 dat)
+ [wid (rev 3 wid dat)]
+ ::
+ ++ encode-1559
+ |= sig=(unit [y=@ r=@ s=@])
+ %+ encode:rlp %l
+ =, tx
+ :* chain-id
+ nonce
+ max-priority-gas-fee
+ max-gas-fee
+ gas
+ b+20^to
+ value
+ data
+ ::
+ :- %l
+ %+ turn ~(tap by access-list)
+ |= [a=address b=(list @ux)]
+ l+~[b+20^a l+(turn b |=(c=@ux b+32^c))]
+ ::
+ ?~ sig ~
+ ~[y r s]:u.sig
+ ==
+ --
+ --
+::
+:: rlp en/decoding
+::NOTE https://eth.wiki/en/fundamentals/rlp
+::
+++ rlp
+ |%
+ ::NOTE rlp encoding doesn't really care about leading zeroes,
+ :: but because we need to disinguish between no-bytes zero
+ :: and one-byte zero (and also empty list) we end up with
+ :: this awful type...
+ +$ item
+ $@ @
+ $% [%l l=(list item)]
+ [%b b=byts]
+ ==
+ :: +encode-atoms: encode list of atoms as a %l of %b items
+ ::
+ ++ encode-atoms ::NOTE deprecated
+ |= l=(list @)
+ ^- @
+ (encode l+l)
+ ::
+ ++ encode
+ |= in=item
+ |^ ^- @
+ ?- in
+ @
+ $(in [%b (met 3 in) in])
+ ::
+ [%b *]
+ ?: &(=(1 wid.b.in) (lte dat.b.in 0x7f))
+ dat.b.in
+ =- (can 3 ~[b.in [(met 3 -) -]])
+ (encode-length wid.b.in 0x80)
+ ::
+ [%l *]
+ :: we +can because b+1^0x0 encodes to 0x00
+ ::
+ =/ l=(list byts)
+ %+ turn l.in
+ |= ni=item
+ =+ (encode ni)
+ [(max 1 (met 3 -)) -]
+ %+ can 3
+ %- flop
+ =- [(met 3 -)^- l]
+ (encode-length (roll (turn l head) add) 0xc0)
+ ==
+ ::
+ ++ encode-length
+ |= [len=@ off=@]
+ ?: (lth len 56) (add len off)
+ =- (cat 3 len -)
+ :(add (met 3 len) off 55)
+ --
+ :: +decode-atoms: decode expecting a %l of %b items, producing atoms within
+ ::
+ ++ decode-atoms
+ |= dat=@
+ ^- (list @)
+ =/ i=item (decode dat)
+ ~| [%unexpected-data i]
+ ?> ?=(%l -.i)
+ %+ turn l.i
+ |= i=item
+ ~| [%unexpected-list i]
+ ?> ?=(%b -.i)
+ dat.b.i
+ ::
+ ++ decode
+ |= dat=@
+ ^- item
+ =/ bytes=(list @) (flop (rip 3 dat))
+ =? bytes ?=(~ bytes) ~[0]
+ |^ item:decode-head
+ ::
+ ++ decode-head
+ ^- [done=@ud =item]
+ ?~ bytes
+ ~| %rlp-unexpected-end
+ !!
+ =* byt i.bytes
+ :: byte in 0x00-0x79 range encodes itself
+ ::
+ ?: (lte byt 0x79)
+ :- 1
+ [%b 1^byt]
+ :: byte in 0x80-0xb7 range encodes string length
+ ::
+ ?: (lte byt 0xb7)
+ =+ len=(sub byt 0x80)
+ :- +(len)
+ :- %b
+ len^(get-value 1 len)
+ :: byte in 0xb8-0xbf range encodes string length length
+ ::
+ ?: (lte byt 0xbf)
+ =+ led=(sub byt 0xb7)
+ =+ len=(get-value 1 led)
+ :- (add +(led) len)
+ :- %b
+ len^(get-value +(led) len)
+ :: byte in 0xc0-f7 range encodes list length
+ ::
+ ?: (lte byt 0xf7)
+ =+ len=(sub byt 0xc0)
+ :- +(len)
+ :- %l
+ %. len
+ decode-list(bytes (slag 1 `(list @)`bytes))
+ :: byte in 0xf8-ff range encodes list length length
+ ::
+ ?: (lte byt 0xff)
+ =+ led=(sub byt 0xf7)
+ =+ len=(get-value 1 led)
+ :- (add +(led) len)
+ :- %l
+ %. len
+ decode-list(bytes (slag +(led) `(list @)`bytes))
+ ~| [%rip-not-bloq-3 `@ux`byt]
+ !!
+ ::
+ ++ decode-list
+ |= rem=@ud
+ ^- (list item)
+ ?: =(0 rem) ~
+ =+ ^- [don=@ud =item] ::TODO =/
+ decode-head
+ :- item
+ %= $
+ rem (sub rem don)
+ bytes (slag don bytes)
+ ==
+ ::
+ ++ get-value
+ |= [at=@ud to=@ud]
+ ^- @
+ (rep 3 (flop (swag [at to] bytes)))
+ --
+ --
+::
+:: abi en/decoding
+::NOTE https://solidity.readthedocs.io/en/develop/abi-spec.html
+::
+++ abi
+ => |%
+ :: solidity types. integer bitsizes ignored
+ ++ etyp
+ $@ $? :: static
+ %address %bool
+ %int %uint
+ %real %ureal
+ :: dynamic
+ %bytes %string
+ ==
+ $% :: static
+ [%bytes-n n=@ud]
+ :: dynamic
+ [%array-n t=etyp n=@ud]
+ [%array t=etyp]
+ ==
+ ::
+ :: solidity-style typed data. integer bitsizes ignored
+ ++ data
+ $% [%address p=address]
+ [%string p=tape]
+ [%bool p=?]
+ [%int p=@sd]
+ [%uint p=@ud]
+ [%real p=@rs]
+ [%ureal p=@urs]
+ [%array-n p=(list data)]
+ [%array p=(list data)]
+ [%bytes-n p=octs] ::TODO just @, because context knows length?
+ [%bytes p=octs]
+ ==
+ --
+ =, mimes:html
+ |%
+ :: encoding
+ ::
+ ++ encode-args
+ :: encode list of arguments.
+ ::
+ |= das=(list data)
+ ^- tape
+ (encode-data [%array-n das])
+ ::
+ ++ encode-data
+ :: encode typed data into ABI bytestring.
+ ::
+ |= dat=data
+ ^- tape
+ ?+ -.dat
+ ~| [%unsupported-type -.dat]
+ !!
+ ::
+ %array-n
+ :: enc(X) = head(X[0]) ... head(X[k-1]) tail(X[0]) ... tail(X[k-1])
+ :: where head and tail are defined for X[i] being of a static type as
+ :: head(X[i]) = enc(X[i]) and tail(X[i]) = "" (the empty string), or as
+ :: head(X[i]) = enc(len( head(X[0])..head(X[k-1])
+ :: tail(X[0])..tail(X[i-1]) ))
+ :: and tail(X[i]) = enc(X[i]) otherwise.
+ ::
+ :: so: if it's a static type, data goes in the head. if it's a dynamic
+ :: type, a reference goes into the head and data goes into the tail.
+ ::
+ :: in the head, we first put a placeholder where references need to go.
+ =+ hol=(reap 64 'x')
+ =/ hes=(list tape)
+ %+ turn p.dat
+ |= d=data
+ ?. (is-dynamic-type d) ^$(dat d)
+ hol
+ =/ tas=(list tape)
+ %+ turn p.dat
+ |= d=data
+ ?. (is-dynamic-type d) ""
+ ^$(dat d)
+ :: once we know the head and tail, we can fill in the references in head.
+ =- (weld nes `tape`(zing tas))
+ ^- [@ud nes=tape]
+ =+ led=(lent (zing hes))
+ %+ roll hes
+ |= [t=tape i=@ud nes=tape]
+ :- +(i)
+ :: if no reference needed, just put the data.
+ ?. =(t hol) (weld nes t)
+ :: calculate byte offset of data we need to reference.
+ =/ ofs=@ud
+ =- (div - 2) :: two hex digits per byte.
+ %+ add led :: count head, and
+ %- lent %- zing :: count all tail data
+ (scag i tas) :: preceding ours.
+ =+ ref=^$(dat [%uint ofs])
+ :: shouldn't hit this unless we're sending over 2gb of data?
+ ~| [%weird-ref-lent (lent ref)]
+ ?> =((lent ref) (lent hol))
+ (weld nes ref)
+ ::
+ %array :: where X has k elements (k is assumed to be of type uint256):
+ :: enc(X) = enc(k) enc([X[1], ..., X[k]])
+ :: i.e. it is encoded as if it were an array of static size k, prefixed
+ :: with the number of elements.
+ %+ weld $(dat [%uint (lent p.dat)])
+ $(dat [%array-n p.dat])
+ ::
+ %bytes-n
+ :: enc(X) is the sequence of bytes in X padded with zero-bytes to a
+ :: length of 32.
+ :: Note that for any X, len(enc(X)) is a multiple of 32.
+ ~| [%bytes-n-too-long max=32 actual=p.p.dat]
+ ?> (lte p.p.dat 32)
+ (pad-to-multiple (render-hex-bytes p.dat) 64 %right)
+ ::
+ %bytes :: of length k (which is assumed to be of type uint256)
+ :: enc(X) = enc(k) pad_right(X), i.e. the number of bytes is encoded as a
+ :: uint256 followed by the actual value of X as a byte sequence, followed
+ :: by the minimum number of zero-bytes such that len(enc(X)) is a
+ :: multiple of 32.
+ %+ weld $(dat [%uint p.p.dat])
+ (pad-to-multiple (render-hex-bytes p.dat) 64 %right)
+ ::
+ %string
+ :: enc(X) = enc(enc_utf8(X)), i.e. X is utf-8 encoded and this value is
+ :: interpreted as of bytes type and encoded further. Note that the length
+ :: used in this subsequent encoding is the number of bytes of the utf-8
+ :: encoded string, not its number of characters.
+ $(dat [%bytes (lent p.dat) (swp 3 (crip p.dat))])
+ ::
+ %uint
+ :: enc(X) is the big-endian encoding of X, padded on the higher-order
+ :: (left) side with zero-bytes such that the length is a multiple of 32
+ :: bytes.
+ (pad-to-multiple (render-hex-bytes (as-octs p.dat)) 64 %left)
+ ::
+ %bool
+ :: as in the uint8 case, where 1 is used for true and 0 for false
+ $(dat [%uint ?:(p.dat 1 0)])
+ ::
+ %address
+ :: as in the uint160 case
+ $(dat [%uint `@ud`p.dat])
+ ==
+ ::
+ ++ is-dynamic-type
+ |= a=data
+ ?. ?=(%array-n -.a)
+ ?=(?(%string %bytes %array) -.a)
+ &(!=((lent p.a) 0) (lien p.a is-dynamic-type))
+ ::
+ :: decoding
+ ::
+ ++ decode-topics decode-arguments
+ ::
+ ++ decode-results
+ :: rex: string of hex bytes with leading 0x.
+ |* [rex=@t tys=(list etyp)]
+ =- (decode-arguments - tys)
+ %^ rut 9
+ (rsh [3 2] rex)
+ (curr rash hex)
+ ::
+ ++ decode-arguments
+ |* [wos=(list @) tys=(list etyp)]
+ =/ wos=(list @) wos :: get rid of tmi
+ =| win=@ud
+ =< (decode-from 0 tys)
+ |%
+ ++ decode-from
+ |* [win=@ud tys=(list etyp)]
+ ?~ tys !!
+ =- ?~ t.tys dat
+ [dat $(win nin, tys t.tys)]
+ (decode-one win ~[i.tys])
+ ::
+ ++ decode-one
+ ::NOTE we take (list etyp) even though we only operate on
+ :: a single etyp as a workaround for urbit/arvo#673
+ |* [win=@ud tys=(list etyp)]
+ =- [nin dat]=- ::NOTE ^= regular form broken
+ ?~ tys !!
+ =* typ i.tys
+ =+ wor=(snag win wos)
+ ?+ typ
+ ~| [%unsupported-type typ]
+ !!
+ ::
+ ?(%address %bool %uint) :: %int %real %ureal
+ :- +(win)
+ ?- typ
+ %address `@ux`wor
+ %uint `@ud`wor
+ %bool =(1 wor)
+ ==
+ ::
+ %string
+ =+ $(tys ~[%bytes])
+ [nin (trip (swp 3 q.dat))]
+ ::
+ %bytes
+ :- +(win)
+ :: find the word index of the actual data.
+ =/ lic=@ud (div wor 32)
+ :: learn the bytelength of the data.
+ =/ len=@ud (snag lic wos)
+ (decode-bytes-n +(lic) len)
+ ::
+ [%bytes-n *]
+ :- (add win +((div (dec n.typ) 32)))
+ (decode-bytes-n win n.typ)
+ ::
+ [%array *]
+ :- +(win)
+ :: find the word index of the actual data.
+ =. win (div wor 32)
+ :: read the elements from their location.
+ %- tail
+ %^ decode-array-n ~[t.typ] +(win)
+ (snag win wos)
+ ::
+ [%array-n *]
+ (decode-array-n ~[t.typ] win n.typ)
+ ==
+ ::
+ ++ decode-bytes-n
+ |= [fro=@ud bys=@ud]
+ ^- octs
+ :: parse {bys} bytes from {fro}.
+ :- bys
+ %+ rsh
+ :- 3
+ =+ (mod bys 32)
+ ?:(=(0 -) - (sub 32 -))
+ %+ rep 8
+ %- flop
+ =- (swag [fro -] wos)
+ +((div (dec bys) 32))
+ ::
+ ++ decode-array-n
+ ::NOTE we take (list etyp) even though we only operate on
+ :: a single etyp as a workaround for urbit/arvo#673
+ ::NOTE careful! produces lists without type info
+ =| res=(list)
+ |* [tys=(list etyp) fro=@ud len=@ud]
+ ^- [@ud (list)]
+ ?~ tys !!
+ ?: =(len 0) [fro (flop `(list)`res)]
+ =+ (decode-one fro ~[i.tys]) :: [nin=@ud dat=*]
+ $(res ^+(res [dat res]), fro nin, len (dec len))
+ --
+ --
+::
+:: communicating with rpc nodes
+::NOTE https://github.com/ethereum/wiki/wiki/JSON-RPC
+::
+++ rpc
+ :: types
+ ::
+ => =, abi
+ =, format
+ |%
+ :: raw call data
+ ++ call-data
+ $: function=@t
+ arguments=(list data)
+ ==
+ ::
+ :: raw transaction data
+ +$ typed-transaction
+ $% [%0x0 transaction]
+ [%0x2 transaction-1559]
+ ==
+ ::
+ +$ transaction
+ $: nonce=@ud
+ gas-price=@ud
+ gas=@ud
+ to=address
+ value=@ud
+ data=@ux
+ chain-id=@ux
+ ==
+ ::
+ +$ transaction-1559
+ $: chain-id=@ux
+ nonce=@ud
+ max-priority-gas-fee=@ud
+ max-gas-fee=@ud
+ gas=@ud
+ to=address
+ value=@ud
+ data=@ux
+ access-list=(jar address @ux)
+ ==
+ ::
+ :: ethereum json rpc api
+ ::
+ :: supported requests.
+ ++ request
+ $% [%eth-block-number ~]
+ [%eth-call cal=call deb=block]
+ $: %eth-new-filter
+ fro=(unit block)
+ tob=(unit block)
+ adr=(list address)
+ top=(list ?(@ux (list @ux)))
+ ==
+ [%eth-get-block-by-number bon=@ud txs=?]
+ [%eth-get-filter-logs fid=@ud]
+ $: %eth-get-logs
+ fro=(unit block)
+ tob=(unit block)
+ adr=(list address)
+ top=(list ?(@ux (list @ux)))
+ ==
+ $: %eth-get-logs-by-hash
+ has=@
+ adr=(list address)
+ top=(list ?(@ux (list @ux)))
+ ==
+ [%eth-get-filter-changes fid=@ud]
+ [%eth-get-transaction-by-hash txh=@ux]
+ [%eth-get-transaction-count adr=address =block]
+ [%eth-get-balance adr=address =block]
+ [%eth-get-transaction-receipt txh=@ux]
+ [%eth-send-raw-transaction dat=@ux]
+ ==
+ ::
+ ::TODO clean up & actually use
+ ++ response
+ $% ::TODO
+ [%eth-new-filter fid=@ud]
+ [%eth-get-filter-logs los=(list event-log)]
+ [%eth-get-logs los=(list event-log)]
+ [%eth-get-logs-by-hash los=(list event-log)]
+ [%eth-got-filter-changes los=(list event-log)]
+ [%eth-transaction-hash haz=@ux]
+ ==
+ ::
+ ++ transaction-result
+ $: block-hash=(unit @ux)
+ block-number=(unit @ud)
+ transaction-index=(unit @ud)
+ from=@ux
+ to=(unit @ux)
+ input=@t
+ ==
+ ::
+ ++ event-log
+ $: :: null for pending logs
+ $= mined %- unit
+ $: input=(unit @ux)
+ log-index=@ud
+ transaction-index=@ud
+ transaction-hash=@ux
+ block-number=@ud
+ block-hash=@ux
+ removed=?
+ ==
+ ::
+ address=@ux
+ data=@t
+ :: event data
+ ::
+ :: For standard events, the first topic is the event signature
+ :: hash. For anonymous events, the first topic is the first
+ :: indexed argument.
+ :: Note that this does not support the "anonymous event with
+ :: zero topics" case. This has dubious usability, and using
+ :: +lest instead of +list saves a lot of ?~ checks.
+ ::
+ topics=(lest @ux)
+ ==
+ ::
+ :: data for eth_call.
+ ++ call
+ $: from=(unit address)
+ to=address
+ gas=(unit @ud)
+ gas-price=(unit @ud)
+ value=(unit @ud)
+ data=tape
+ ==
+ ::
+ :: minimum data needed to construct a read call
+ ++ proto-read-request
+ $: id=(unit @t)
+ to=address
+ call-data
+ ==
+ ::
+ :: block to operate on.
+ ++ block
+ $% [%number n=@ud]
+ [%label l=?(%earliest %latest %pending)]
+ ==
+ --
+ ::
+ :: logic
+ ::
+ |%
+ ++ encode-call
+ |= call-data
+ ^- tape
+ ::TODO should this check to see if the data matches the function signature?
+ =- :(weld "0x" - (encode-args arguments))
+ %+ scag 8
+ %+ render-hex-bytes 32
+ %- keccak-256:keccak:crypto
+ (as-octs:mimes:html function)
+ ::
+ :: building requests
+ ::
+ ++ json-request
+ =, eyre
+ |= [url=purl jon=json]
+ ^- hiss
+ :^ url %post
+ %- ~(gas in *math)
+ ~['Content-Type'^['application/json']~]
+ (some (as-octs (en:json:html jon)))
+ :: +light-json-request: like json-request, but for %l
+ ::
+ :: TODO: Exorcising +purl from our system is a much longer term effort;
+ :: get the current output types for now.
+ ::
+ ++ light-json-request
+ |= [url=purl:eyre jon=json]
+ ^- request:http
+ ::
+ :* %'POST'
+ (crip (en-purl:html url))
+ ~[['content-type' 'application/json']]
+ (some (as-octs (en:json:html jon)))
+ ==
+ ::
+ ++ batch-read-request
+ |= req=(list proto-read-request)
+ ^- json
+ a+(turn req read-request)
+ ::
+ ++ read-request
+ |= proto-read-request
+ ^- json
+ %+ request-to-json id
+ :+ %eth-call
+ ^- call
+ [~ to ~ ~ ~ `tape`(encode-call function arguments)]
+ [%label %latest]
+ ::
+ ++ request-to-json
+ =, enjs:format
+ |= [riq=(unit @t) req=request]
+ ^- json
+ %- pairs
+ =; r=[met=@t pas=(list json)]
+ ::TODO should use request-to-json:rpc:jstd,
+ :: and probably (fall riq -.req)
+ :* jsonrpc+s+'2.0'
+ method+s+met.r
+ params+a+pas.r
+ ::TODO would just jamming the req noun for id be a bad idea?
+ ?~ riq ~
+ [id+s+u.riq]~
+ ==
+ ?- -.req
+ %eth-block-number
+ ['eth_blockNumber' ~]
+ ::
+ %eth-call
+ :- 'eth_call'
+ :~ (eth-call-to-json cal.req)
+ (block-to-json deb.req)
+ ==
+ ::
+ %eth-new-filter
+ :- 'eth_newFilter'
+ :_ ~
+ :- %o %- ~(gas by *(map @t json))
+ =- (murn - same)
+ ^- (list (unit (pair @t json)))
+ :~ ?~ fro.req ~
+ `['fromBlock' (block-to-json u.fro.req)]
+ ::
+ ?~ tob.req ~
+ `['toBlock' (block-to-json u.tob.req)]
+ ::
+ ::NOTE tmi
+ ?: =(0 (lent adr.req)) ~
+ :+ ~ 'address'
+ ?: =(1 (lent adr.req)) (tape (address-to-hex (snag 0 adr.req)))
+ :- %a
+ (turn adr.req (cork address-to-hex tape))
+ ::
+ ?~ top.req ~
+ :+ ~ 'topics'
+ (topics-to-json top.req)
+ ==
+ ::
+ %eth-get-block-by-number
+ :- 'eth_getBlockByNumber'
+ :~ (tape (num-to-hex-minimal bon.req))
+ b+txs.req
+ ==
+ ::
+ %eth-get-filter-logs
+ ['eth_getFilterLogs' (tape (num-to-hex fid.req)) ~]
+ ::
+ %eth-get-logs
+ :- 'eth_getLogs'
+ :_ ~
+ :- %o %- ~(gas by *(map @t json))
+ =- (murn - same)
+ ^- (list (unit (pair @t json)))
+ :~ ?~ fro.req ~
+ `['fromBlock' (block-to-json u.fro.req)]
+ ::
+ ?~ tob.req ~
+ `['toBlock' (block-to-json u.tob.req)]
+ ::
+ ?: =(0 (lent adr.req)) ~
+ :+ ~ 'address'
+ ?: =(1 (lent adr.req)) (tape (address-to-hex (snag 0 adr.req)))
+ :- %a
+ (turn adr.req (cork address-to-hex tape))
+ ::
+ ?~ top.req ~
+ :+ ~ 'topics'
+ (topics-to-json top.req)
+ ==
+ ::
+ %eth-get-logs-by-hash
+ :- 'eth_getLogs'
+ :_ ~ :- %o
+ %- ~(gas by *(map @t json))
+ =- (murn - same)
+ ^- (list (unit (pair @t json)))
+ :~ `['blockHash' (tape (transaction-to-hex has.req))]
+ ::
+ ?: =(0 (lent adr.req)) ~
+ :+ ~ 'address'
+ ?: =(1 (lent adr.req)) (tape (address-to-hex (snag 0 adr.req)))
+ :- %a
+ (turn adr.req (cork address-to-hex tape))
+ ::
+ ?~ top.req ~
+ :+ ~ 'topics'
+ (topics-to-json top.req)
+ ==
+ ::
+ %eth-get-filter-changes
+ ['eth_getFilterChanges' (tape (num-to-hex fid.req)) ~]
+ ::
+ %eth-get-transaction-count
+ :- 'eth_getTransactionCount'
+ :~ (tape (address-to-hex adr.req))
+ (block-to-json block.req)
+ ==
+ ::
+ %eth-get-balance
+ :- 'eth_getBalance'
+ :~ (tape (address-to-hex adr.req))
+ (block-to-json block.req)
+ ==
+ ::
+ %eth-get-transaction-by-hash
+ ['eth_getTransactionByHash' (tape (transaction-to-hex txh.req)) ~]
+ ::
+ %eth-get-transaction-receipt
+ ['eth_getTransactionReceipt' (tape (transaction-to-hex txh.req)) ~]
+ ::
+ %eth-send-raw-transaction
+ ['eth_sendRawTransaction' (tape (num-to-hex dat.req)) ~]
+ ==
+ ::
+ ++ eth-call-to-json
+ =, enjs:format
+ |= cal=call
+ ^- json
+ :- %o %- ~(gas by *(map @t json))
+ =- (murn - same)
+ ^- (list (unit (pair @t json)))
+ :~ ?~ from.cal ~
+ `['from' (tape (address-to-hex u.from.cal))]
+ ::
+ `['to' (tape (address-to-hex to.cal))]
+ ::
+ ?~ gas.cal ~
+ `['gas' (tape (num-to-hex u.gas.cal))]
+ ::
+ ?~ gas-price.cal ~
+ `['gasPrice' (tape (num-to-hex u.gas-price.cal))]
+ ::
+ ?~ value.cal ~
+ `['value' (tape (num-to-hex u.value.cal))]
+ ::
+ ?~ data.cal ~
+ `['data' (tape data.cal)]
+ ==
+ ::
+ ++ block-to-json
+ |= dob=block
+ ^- json
+ ?- -.dob
+ %number s+(crip '0' 'x' ((x-co:co 1) n.dob))
+ %label s+l.dob
+ ==
+ ::
+ ++ topics-to-json
+ |= tos=(list ?(@ux (list @ux)))
+ ^- json
+ :- %a
+ =/ ttj
+ ;: cork
+ (cury render-hex-bytes 32)
+ prefix-hex
+ tape:enjs:format
+ ==
+ %+ turn tos
+ |= t=?(@ (list @))
+ ?@ t
+ ?: =(0 t) ~
+ (ttj `@`t)
+ a+(turn t ttj)
+ ::
+ :: parsing responses
+ ::
+ ::TODO ++ parse-response |= json ^- response
+ ::
+ ++ parse-hex-result
+ |= j=json
+ ^- @
+ ?> ?=(%s -.j)
+ (hex-to-num p.j)
+ ::
+ ++ parse-eth-new-filter-res parse-hex-result
+ ::
+ ++ parse-eth-block-number parse-hex-result
+ ::
+ ++ parse-transaction-hash parse-hex-result
+ ::
+ ++ parse-eth-get-transaction-count parse-hex-result
+ ::
+ ++ parse-eth-get-balance parse-hex-result
+ ::
+ ++ parse-event-logs
+ (ar:dejs:format parse-event-log)
+ ::
+ ++ parse-event-log
+ =, dejs:format
+ |= log=json
+ ^- event-log
+ =- ((ot -) log)
+ :~ =- ['logIndex'^(cu - (mu so))]
+ |= li=(unit @t)
+ ?~ li ~
+ =- ``((ou -) log) ::TODO not sure if elegant or hacky.
+ :~ 'logIndex'^(un (cu hex-to-num so))
+ 'transactionIndex'^(un (cu hex-to-num so))
+ 'transactionHash'^(un (cu hex-to-num so))
+ 'blockNumber'^(un (cu hex-to-num so))
+ 'blockHash'^(un (cu hex-to-num so))
+ 'removed'^(uf | bo)
+ ==
+ ::
+ address+(cu hex-to-num so)
+ data+so
+ ::
+ =- topics+(cu - (ar so))
+ |= r=(list @t)
+ ^- (lest @ux)
+ ?> ?=([@t *] r)
+ :- (hex-to-num i.r)
+ (turn t.r hex-to-num)
+ ==
+ ::
+ ++ parse-transaction-result
+ =, dejs:format
+ |= jon=json
+ ~| jon=jon
+ ^- transaction-result
+ =- ((ot -) jon)
+ :~ 'blockHash'^_~ :: TODO: fails if maybe-num?
+ 'blockNumber'^maybe-num
+ 'transactionIndex'^maybe-num
+ from+(cu hex-to-num so)
+ to+maybe-num
+ input+so
+ ==
+ ::
+ ++ maybe-num
+ =, dejs:format
+ =- (cu - (mu so))
+ |= r=(unit @t)
+ ?~ r ~
+ `(hex-to-num u.r)
+ --
+::
+:: utilities
+::TODO give them better homes!
+::
+++ num-to-hex
+ |= n=@
+ ^- tape
+ %- prefix-hex
+ ?: =(0 n)
+ "0"
+ %- render-hex-bytes
+ (as-octs:mimes:html n)
+::
+++ num-to-hex-minimal
+ |= n=@
+ ^- tape
+ %- prefix-hex
+ ((x-co:co 1) n)
+::
+++ address-to-hex
+ |= a=address
+ ^- tape
+ %- prefix-hex
+ (render-hex-bytes 20 `@`a)
+::
+++ address-to-checksum
+ |= a=address
+ ^- tape
+ =/ hexed (render-hex-bytes 20 `@`a)
+ =/ hash (keccak-256:keccak:crypto (as-octs:mimes:html (crip hexed)))
+ =| ret=tape
+ =/ pos 63
+ |-
+ ?~ hexed (prefix-hex (flop ret))
+ =/ char i.hexed
+ ?: (lth char 58) $(pos (dec pos), ret [char ret], hexed t.hexed)
+ =/ nib (cut 2 [pos 1] hash)
+ ?: (lth 7 nib) $(pos (dec pos), ret [(sub char 32) ret], hexed t.hexed)
+ $(pos (dec pos), ret [char ret], hexed t.hexed)
+::
+++ transaction-to-hex
+ |= h=@
+ ^- tape
+ %- prefix-hex
+ (render-hex-bytes 32 h)
+::
+++ prefix-hex
+ |= a=tape
+ ^- tape
+ ['0' 'x' a]
+::
+++ render-hex-bytes
+ :: atom to string of hex bytes without 0x prefix and dots.
+ |= a=octs
+ ^- tape
+ ((x-co:co (mul 2 p.a)) q.a)
+::
+++ pad-to-multiple
+ |= [wat=tape mof=@ud wer=?(%left %right)]
+ ^- tape
+ =+ len=(lent wat)
+ ?: =(0 len) (reap mof '0')
+ =+ mad=(mod len mof)
+ ?: =(0 mad) wat
+ =+ tad=(reap (sub mof mad) '0')
+ %- weld
+ ?:(?=(%left wer) [tad wat] [wat tad])
+::
+++ hex-to-num
+ |= a=@t
+ ~| %non-hex-cord
+ ?> =((end [3 2] a) '0x')
+ =< ?<(=(0 p) q) %- need
+ (de:base16:mimes:html (rsh [3 2] a))
+--
diff --git a/lib/metamask.hoon b/lib/metamask.hoon
new file mode 100644
index 0000000..4cb403b
--- /dev/null
+++ b/lib/metamask.hoon
@@ -0,0 +1,169 @@
+/+ naive, ethereum, server
+=>
+|%
++$ challenges (set secret)
++$ secret @uv
++$ authorization
+ $: who=@p
+ =secret
+ adr=tape
+ sig=tape
+ ==
++$ user-sessions (map comet=@p id=@p)
++$ sessions
+ $: =challenges
+ users=user-sessions
+ ==
+--
+|_ [=sessions =bowl:gall]
++$ sess ^sessions
+:: state field to keep track of users logged with metamask
+++ login ^- @p
+ =/ session (~(get by users.sessions) src.bowl)
+ ?~ session src.bowl
+ u.session
+
+ :: this goes on the router
+ ++ serve-metamask-challenge
+ |= eyre-id=@ta
+ :: special-case MetaMask auth handling
+ =/ new-challenge (sham [now eny]:bowl)
+ %+ weld (self-poke [%meta new-challenge])
+ %+ give-simple-payload:app:server
+ eyre-id
+ ^- simple-payload:http
+ :- :- 200
+ ~[['Content-Type' 'application/json']]
+ `(as-octs:mimes:html (en:json:html (enjs-challenge new-challenge)))
+ :: Modified from ~rabsef-bicrym's %mask by ~hanfel-dovned.
+ ++ process-metamask-auth
+ |= [order-id=@t octs=(unit octs)]
+ ^- (list card:agent:gall)
+ =/ challenges challenges.sessions
+ |^
+ ?~ octs ~|(%empty-auth-request !!)
+ :: ?. =('auth' (cut 3 [0 4] q.u.octs))
+ :: *(list card:agent:gall)
+ =/ jon (de:json:html q.u.octs)
+ ?~ jon ~|(%empty-auth-json !!)
+ =/ body=json u.jon
+ =/ axn (dejs-action body)
+ =/ is-valid (validate who.axn secret.axn adr.axn sig.axn)
+ ~& >> signature-valid=[is-valid who.axn secret.axn adr.axn sig.axn]
+ ?. is-valid ~|(%bad-metamask-signature !!)
+ %+ weld
+ (self-poke [%auth who.axn src.bowl secret.axn])
+ %+ give-simple-payload:app:server
+ order-id
+ ^- simple-payload:http
+ :- :- 200
+ ~[['Content-Type' 'application/json']]
+ =/ obj=json %- pairs:enjs:format :~([%login-ok [%b .y]])
+ `(as-octs:mimes:html (en:json:html obj))
+
+ ++ validate
+ |= [who=@p challenge=secret address=tape hancock=tape]
+ ^- ?
+ =/ addy (from-tape address)
+ =/ cock (from-tape hancock)
+ =/ owner (get-owner who) ?~ owner
+ ~& "no owner"
+ %.n
+ ?. =(addy u.owner)
+ ~& "wrong owner"
+ %.n
+ ?. (~(has in challenges) challenge)
+ ~& "bad challenge"
+ %.n
+ =/ note=@uvI
+ =+ octs=(as-octs:mimes:html (scot %uv challenge))
+ %- keccak-256:keccak:crypto
+ %- as-octs:mimes:html
+ ;: (cury cat 3)
+ '\19Ethereum Signed Message:\0a'
+ (crip (a-co:co p.octs))
+ q.octs
+ ==
+ ?. &(=(20 (met 3 addy)) =(65 (met 3 cock)))
+ ~& "addy != cock"
+ %.n
+ =/ r (cut 3 [33 32] cock)
+ =/ s (cut 3 [1 32] cock)
+ =/ v=@
+ =+ v=(cut 3 [0 1] cock)
+ ?+ v 99
+ %0 0
+ %1 1
+ %27 0
+ %28 1
+ ==
+ ?. |(=(0 v) =(1 v))
+ ~& "wrong v"
+ %.n
+ =/ xy
+ (ecdsa-raw-recover:secp256k1:secp:crypto note v r s)
+ =/ pub :((cury cat 3) y.xy x.xy 0x4)
+ =/ add (address-from-pub:key:ethereum pub)
+ =(addy add)
+ ::
+ ++ from-tape
+ |=(h=tape ^-(@ux (scan h ;~(pfix (jest '0x') hex))))
+ ::
+ ++ get-owner
+ |= who=@p
+ ^- (unit @ux)
+ =- ?~ pin=`(unit point:naive)`-
+ ~
+ ?. |(?=(%l1 dominion.u.pin) ?=(%l2 dominion.u.pin))
+ ~
+ `address.owner.own.u.pin
+ .^ (unit point:naive)
+ %gx
+ %+ en-beam
+ [our.bowl %azimuth [%da now.bowl]]
+ /point/(scot %p who)/noun
+ ==
+ ++ dejs-action
+ |= jon=json
+ ^- authorization
+ =, dejs:format
+ %. jon
+ %- ot
+ :~ [%who (se %p)]
+ [%secret (se %uv)]
+ [%address sa]
+ [%signature sa]
+ ==
+ --
+ ++ enjs-challenge
+ =, enjs:format
+ |= chal=@
+ ^- json
+ %- pairs
+ :~ [%challenge [%s (scot %uv chal)]]
+ ==
+ ++ self-poke
+ |= noun=*
+ ^- (list card:agent:gall)
+ :~ [%pass /gib %agent [our.bowl dap.bowl] %poke %noun !>(noun)]
+ ==
+
+ :: these are the poke handlers
+ ++ handle-meta
+ |= new-challenge=@ ^- ^sessions
+ =? users.sessions
+ !(~(has by users.sessions) src.bowl)
+ (~(put by users.sessions) [src.bowl src.bowl])
+ =? challenges.sessions
+ =(src.bowl (~(got by users.sessions) src.bowl))
+ (~(put in challenges.sessions) new-challenge)
+
+ sessions
+ ++ handle-auth
+ |= [who=@p src=@p =secret] ^- ^sessions
+ ~& > "%ustj: Successful authentication of {<src>} as {<who>}."
+ =. users.sessions (~(put by users.sessions) src who)
+ =. challenges.sessions (~(del in challenges.sessions) secret)
+ sessions
+
+--
diff --git a/lib/naive.hoon b/lib/naive.hoon
new file mode 100644
index 0000000..7f46eaf
--- /dev/null
+++ b/lib/naive.hoon
@@ -0,0 +1,926 @@
+/+ tiny
+!.
+=> => tiny
+:: Laconic bit
+::
+=| lac=?
+:: Constants
+::
+|%
+:: Transfers on L1 to this address count as depositing to L2
+::
+++ deposit-address 0x1111.1111.1111.1111.1111.1111.1111.1111.1111.1111
+++ log-names
+ |%
+ :: Generated with (keccak-256:keccak:crypto (as-octs:mimes:html name))
+ ::
+ :: OwnerChanged(uint32,address)
+ ++ owner-changed
+ 0x16d0.f539.d49c.6cad.822b.767a.9445.bfb1.
+ cf7e.a6f2.a6c2.b120.a7ea.4cc7.660d.8fda
+ ::
+ :: Activated(uint32)
+ ++ activated
+ 0xe74c.0380.9d07.69e1.b1f7.06cc.8414.258c.
+ d1f3.b6fe.020c.d15d.0165.c210.ba50.3a0f
+ ::
+ :: Spawned(uint32,uint32)
+ ++ spawned
+ 0xb2d3.a6e7.a339.f5c8.ff96.265e.2f03.a010.
+ a854.1070.f374.4a24.7090.9644.1508.1546
+ ::
+ :: OwnershipTransferred(address,address)
+ ++ ownership-transferred
+ 0x8be0.079c.5316.5914.1344.cd1f.d0a4.f284.
+ 1949.7f97.22a3.daaf.e3b4.186f.6b64.57e0
+ ::
+ :: EscapeRequested(uint32,uint32)
+ ++ escape-requested
+ 0xb4d4.850b.8f21.8218.141c.5665.cba3.79e5.
+ 3e9b.b015.b51e.8d93.4be7.0210.aead.874a
+ ::
+ :: EscapeCanceled(uint32,uint32)
+ ++ escape-canceled
+ 0xd653.bb0e.0bb7.ce83.93e6.24d9.8fbf.17cd.
+ a590.2c83.28ed.0cd0.9988.f368.90d9.932a
+ ::
+ :: EscapeAccepted(uint32,uint32)
+ ++ escape-accepted
+ 0x7e44.7c9b.1bda.4b17.4b07.96e1.00bf.7f34.
+ ebf3.6dbb.7fe6.6549.0b1b.fce6.246a.9da5
+ ::
+ :: LostSponsor(uint32,uint32)
+ ++ lost-sponsor
+ 0xd770.4f9a.2519.3dbd.0b0c.b4a8.09fe.ffff.
+ a7f1.9d1a.ae88.17a7.1346.c194.4482.10d5
+ ::
+ :: ChangedKeys(uint32,bytes32,bytes32,uint32,uint32)
+ ++ changed-keys
+ 0xaa10.e7a0.117d.4323.f1d9.9d63.0ec1.69be.
+ bb3a.988e.8957.70e3.5198.7e01.ff54.23d5
+ ::
+ :: BrokeContinuity(uint32,uint32)
+ ++ broke-continuity
+ 0x2929.4799.f1c2.1a37.ef83.8e15.f79d.d91b.
+ cee2.df99.d63c.d1c1.8ac9.68b1.2951.4e6e
+ ::
+ :: ChangedSpawnProxy(uint32,address)
+ ++ changed-spawn-proxy
+ 0x9027.36af.7b3c.efe1.0d9e.840a.ed0d.687e.
+ 35c8.4095.122b.2505.1a20.ead8.866f.006d
+ ::
+ :: ChangedTransferProxy(uint32,address)
+ ++ changed-transfer-proxy
+ 0xcfe3.69b7.197e.7f0c.f067.93ae.2472.a9b1.
+ 3583.fecb.ed2f.78df.a14d.1f10.796b.847c
+ ::
+ :: ChangedManagementProxy(uint32,address)
+ ++ changed-management-proxy
+ 0xab9c.9327.cffd.2acc.168f.afed.be06.139f.
+ 5f55.cb84.c761.df05.e051.1c25.1e2e.e9bf
+ ::
+ :: ChangedVotingProxy(uint32,address)
+ ++ changed-voting-proxy
+ 0xcbd6.269e.c714.57f2.c7b1.a227.74f2.46f6.
+ c5a2.eae3.795e.d730.0db5.1768.0c61.c805
+ ::
+ :: ChangedDns(string,string,string)
+ ++ changed-dns
+ 0xfafd.04ad.e1da.ae2e.1fdb.0fc1.cc6a.899f.
+ d424.063e.d5c9.2120.e67e.0730.53b9.4898
+ ::
+ :: ApprovalForAll(address,address,bool)
+ ++ approval-for-all
+ 0x1730.7eab.39ab.6107.e889.9845.ad3d.59bd.
+ 9653.f200.f220.9204.89ca.2b59.3769.6c31
+ --
+-- =>
+:: Types
+|%
+:: ethereum address, 20 bytes.
+::
++$ address @ux
++$ nonce @ud
++$ dominion ?(%l1 %l2 %spawn)
++$ keys [=life suite=@ud auth=@ crypt=@]
+++ orm ((on ship point) por)
+++ point
+ $: :: domain
+ ::
+ =dominion
+ ::
+ :: ownership
+ ::
+ $= own
+ $: owner=[=address =nonce]
+ spawn-proxy=[=address =nonce]
+ management-proxy=[=address =nonce]
+ voting-proxy=[=address =nonce]
+ transfer-proxy=[=address =nonce]
+ ==
+ ::
+ :: networking
+ ::
+ $= net
+ $: rift=@ud
+ =keys
+ sponsor=[has=? who=@p]
+ escape=(unit @p)
+ ==
+ ==
+::
+++ diff
+ $% [%nonce =ship =proxy =nonce]
+ [%tx =raw-tx err=(unit @tas)]
+ [%operator owner=address operator=address approved=?]
+ [%dns domains=(list @t)]
+ $: %point =ship
+ $% [%rift =rift]
+ [%keys =keys]
+ [%sponsor sponsor=(unit @p)]
+ [%escape to=(unit @p)]
+ [%owner =address]
+ [%spawn-proxy =address]
+ [%management-proxy =address]
+ [%voting-proxy =address]
+ [%transfer-proxy =address]
+ [%dominion =dominion]
+ == == ==
+::
++$ state
+ $: %0
+ =points
+ =operators
+ dns=(list @t)
+ ==
++$ points (tree [ship point])
++$ operators (jug address address)
++$ effects (list diff)
++$ proxy ?(%own %spawn %manage %vote %transfer)
++$ roll (list raw-tx)
++$ raw-tx [sig=@ raw=octs =tx]
++$ tx [from=[=ship =proxy] skim-tx]
++$ skim-tx
+ $% [%transfer-point =address reset=?]
+ [%spawn =ship =address]
+ [%configure-keys encrypt=@ auth=@ crypto-suite=@ breach=?]
+ [%escape parent=ship]
+ [%cancel-escape parent=ship]
+ [%adopt =ship]
+ [%reject =ship]
+ [%detach =ship]
+ [%set-management-proxy =address]
+ [%set-spawn-proxy =address]
+ [%set-transfer-proxy =address]
+ ==
+::
++$ event-log
+ $: address=@ux
+ data=@ux
+ topics=(lest @ux)
+ ==
++$ input
+ $: block=@ud
+ $% [%bat batch=@]
+ [%log =event-log]
+ == ==
+:: ECDSA verifier.
+::
+:: Must keccak `dat` and recover the ethereum address which signed.
+:: Must not crash. `v` will normally be between 0 and 3; if it is not,
+:: should produce null.
+::
++$ verifier $-([dat=octs v=@ r=@ s=@] (unit address))
+-- =>
+::
+|%
+++ debug
+ |* [meg=@t *]
+ ?: lac
+ +<+
+ ~> %slog.[0 meg]
+ +<+
+::
+++ parse-roll
+ |= batch=@
+ =| =roll
+ =| pos=@ud
+ =/ las (met 0 batch)
+ |- ^+ roll
+ ?: (gte pos las)
+ (flop roll)
+ =/ parse-result (parse-raw-tx pos batch)
+ :: Parsing failed, abort batch
+ ::
+ ?~ parse-result
+ (debug %parse-failed ~)
+ =^ =raw-tx pos u.parse-result
+ $(roll [raw-tx roll])
+::
+++ parse-raw-tx
+ |= [pos=@ud batch=@]
+ ^- (unit [raw-tx pos=@ud])
+ |^
+ =^ sig pos (take 3 65)
+ =/ res=(unit [=tx pos=@ud]) parse-tx
+ ?~ res ~
+ =/ dif (sub pos.u.res pos)
+ =/ len =>((dvr dif 8) ?>(=(0 q) p))
+ :- ~ :_ pos.u.res
+ [sig [len (cut 0 [pos dif] batch)] tx.u.res]
+ ::
+ ++ parse-tx
+ ^- (unit [tx pos=@ud])
+ =^ from-proxy=@ pos (take 0 3)
+ ?. ?=(?(%0 %1 %2 %3 %4) from-proxy) (debug %bad-proxy ~)
+ =/ =proxy
+ ?- from-proxy
+ %0 %own
+ %1 %spawn
+ %2 %manage
+ %3 %vote
+ %4 %transfer
+ ==
+ =^ pad pos (take 0 5)
+ =^ from-ship=ship pos (take 3 4)
+ =- ?~ res
+ ~
+ `[[[from-ship proxy] skim-tx.u.res] pos.u.res]
+ ^- res=(unit [=skim-tx pos=@ud])
+ =^ op pos (take 0 7)
+ ?+ op (debug %strange-opcode ~)
+ %0
+ =^ reset=@ pos (take 0)
+ =^ =address pos (take 3 20)
+ `[[%transfer-point address =(0 reset)] pos]
+ ::
+ %1
+ =^ pad=@ pos (take 0)
+ =^ =ship pos (take 3 4)
+ =^ =address pos (take 3 20)
+ `[[%spawn ship address] pos]
+ ::
+ %2
+ =^ breach=@ pos (take 0)
+ =^ encrypt=@ pos (take 3 32)
+ =^ auth=@ pos (take 3 32)
+ =^ crypto-suite=@ pos (take 3 4)
+ `[[%configure-keys encrypt auth crypto-suite =(0 breach)] pos]
+ ::
+ %3 =^(res pos take-ship `[[%escape res] pos])
+ %4 =^(res pos take-ship `[[%cancel-escape res] pos])
+ %5 =^(res pos take-ship `[[%adopt res] pos])
+ %6 =^(res pos take-ship `[[%reject res] pos])
+ %7 =^(res pos take-ship `[[%detach res] pos])
+ %8 =^(res pos take-address `[[%set-management-proxy res] pos])
+ %9 =^(res pos take-address `[[%set-spawn-proxy res] pos])
+ %10 =^(res pos take-address `[[%set-transfer-proxy res] pos])
+ ==
+ ::
+ :: Take a bite
+ ::
+ ++ take
+ |= =bite
+ ^- [@ @ud]
+ =/ =step
+ ?@ bite (bex bite)
+ (mul step.bite (bex bloq.bite))
+ [(cut 0 [pos step] batch) (add pos step)]
+ :: Encode ship and address
+ ::
+ ++ take-address
+ ^- [address @ud]
+ =^ pad=@ pos (take 0)
+ =^ =address pos (take 3 20)
+ [address pos]
+ :: Encode escape-related txs
+ ::
+ ++ take-ship
+ ^- [ship @ud]
+ =^ pad=@ pos (take 0)
+ =^ other=ship pos (take 3 4)
+ [other pos]
+ --
+::
+++ proxy-from-point
+ |= [=proxy point]
+ ^- [=address =nonce]
+ ?- proxy
+ %own owner.own
+ %spawn spawn-proxy.own
+ %manage management-proxy.own
+ %vote voting-proxy.own
+ %transfer transfer-proxy.own
+ ==
+::
+++ verify-sig-and-nonce
+ |= [=verifier chain-t=@t =state =raw-tx]
+ ^- ?
+ |^
+ =/ point (get-point state ship.from.tx.raw-tx)
+ ?> ?=(^ point) :: we never parse more than four bytes for a ship
+ =/ need=[=address =nonce]
+ (proxy-from-point proxy.from.tx.raw-tx u.point)
+ :: We include a domain separator to avoid letting signatures be
+ :: accidentally reused with other applications. We include the name
+ :: UrbitID, a signature format version number, and the EIP-155 chain
+ :: ID.
+ ::
+ :: We also include a nonce so that a transaction cannot be
+ :: rebroadcast.
+ ::
+ =/ prepared-data=octs
+ %: cad 3
+ 14^'UrbitIDV1Chain'
+ (met 3 chain-t)^chain-t
+ 1^':'
+ 4^nonce.need
+ raw.raw-tx
+ ~
+ ==
+ :: Wallets which support personal_sign include this preamble to avoid
+ :: letting personal_sign be used to sign ethereum transactions
+ ::
+ =/ signed-data=octs
+ =/ len (ud-to-ascii p.prepared-data)
+ %: cad 3
+ 26^'\19Ethereum Signed Message:\0a'
+ (met 3 len)^len
+ prepared-data
+ ~
+ ==
+ =/ dress (verify-sig sig.raw-tx signed-data)
+ ?~ dress
+ |
+ =(address.need u.dress)
+ :: Verify signature and produce signer address
+ ::
+ ++ verify-sig
+ |= [sig=@ txdata=octs]
+ ^- (unit address)
+ |^
+ :: Reversed of the usual r-s-v order because Ethereum integers are
+ :: big-endian
+ ::
+ =^ v sig (take 3)
+ =^ s sig (take 3 32)
+ =^ r sig (take 3 32)
+ :: In Ethereum, v is generally 27 + recid, and verifier expects a
+ :: recid. Old versions of geth used 0 + recid, so most software
+ :: now supports either format. See:
+ ::
+ :: https://github.com/ethereum/go-ethereum/issues/2053
+ ::
+ =? v (gte v 27) (sub v 27)
+ (verifier txdata v r s)
+ ::
+ ++ take
+ |= =bite
+ [(end bite sig) (rsh bite sig)]
+ --
+ --
+:: ASCII-decimal encode
+::
+++ ud-to-ascii
+ |= n=@ud
+ ?~ n '0'
+ =| l=(list @)
+ |- ^- @t
+ ?~ n (rep 3 l)
+ =+ (dvr n 10)
+ $(n p, l [(add '0' q) l])
+::
+++ ship-rank
+ |= =ship
+ ^- ?(%0 %1 %2 %3 %4)
+ ?: (lth ship 0x100) %0
+ ?: (lth ship 0x1.0000) %1
+ ?: (lth ship 0x1.0000.0000) %2
+ ?: (lth ship 0x1.0000.0000.0000.0000) %3
+ %4
+::
+++ sein :: autoboss
+ |= who=ship
+ ^- ship
+ =/ mir (ship-rank who)
+ ?- mir
+ %0 who
+ %1 (end 3 who)
+ %2 (end 4 who)
+ %3 (end 5 who)
+ %4 (end 4 who)
+ ==
+::
+:: Produces null only if ship is not a galaxy, star, or planet
+::
+++ get-point
+ |= [=state =ship]
+ ^- (unit point)
+ =/ existing (get:orm points.state ship)
+ ?^ existing
+ `u.existing
+ =| =point
+ =. who.sponsor.net.point (sein ship)
+ ?+ (ship-rank ship) (debug %strange-point ~)
+ %0 `point(dominion %l1)
+ ?(%1 %2)
+ =/ existing-parent $(ship (sein ship))
+ ?~ existing-parent ~
+ :- ~
+ %= point
+ dominion
+ ?- dominion.u.existing-parent
+ %l1 %l1
+ %l2 %l2
+ %spawn %l2
+ ==
+ ==
+ ==
+-- =>
+|%
+:: Receive log from L1 transaction
+::
+++ receive-log
+ |= [=state log=event-log]
+ ^- [effects ^state]
+ =* log-name i.topics.log
+ ?: =(log-name activated:log-names) `state
+ ?: =(log-name spawned:log-names) `state
+ ?: =(log-name ownership-transferred:log-names) `state
+ ?: =(log-name changed-dns:log-names)
+ ?> ?=(~ t.topics.log)
+ =/ words (rip 8 data.log)
+ :: This is only true if each domain is <= 32 bytes
+ ::
+ ?. ?=([c=@ @ b=@ @ a=@ @ @ @ @ ~] words) `state
+ =* one &5.words
+ =* two &3.words
+ =* tri &1.words
+ =/ domains ~[(swp 3 one) (swp 3 two) (swp 3 tri)]
+ :- [%dns domains]~
+ state(dns domains)
+ ::
+ ?: =(log-name approval-for-all:log-names)
+ ?> ?=([@ @ ~] t.topics.log)
+ =* owner i.t.topics.log
+ =* operator i.t.t.topics.log
+ =/ approved !=(0 data.log)
+ :- [%operator owner operator approved]~
+ =- state(operators -)
+ ?: approved
+ (~(put ju operators.state) owner operator)
+ (~(del ju operators.state) owner operator)
+ ::
+ :: The rest of the logs modify a particular ship, specified in the
+ :: second topic. We fetch it, and insert the modification back into
+ :: our state.
+ ::
+ ?> ?=([@ *] t.topics.log)
+ =* ship=@ i.t.topics.log
+ =/ the-point (get-point state ship)
+ ?> ?=(^ the-point)
+ =* point u.the-point
+ ::
+ :: Important to fully no-op on failure so we don't insert an entry
+ :: into points.state
+ ::
+ =- ?~ res
+ `state
+ [effects.u.res state(points (put:orm points.state ship new-point.u.res))]
+ ^- res=(unit [=effects new-point=^point])
+ ::
+ ?: =(log-name changed-spawn-proxy:log-names)
+ ?. ?=(%l1 -.point) ~
+ ?> ?=([@ ~] t.t.topics.log)
+ =* to i.t.t.topics.log
+ :: Depositing to L2 is represented by a spawn proxy change on L1,
+ :: but it doesn't change the actual spawn proxy.
+ ::
+ ?: =(deposit-address to)
+ :+ ~ [%point ship %dominion %spawn]~
+ point(dominion %spawn)
+ :+ ~ [%point ship %spawn-proxy to]~
+ point(address.spawn-proxy.own to)
+ ::
+ ?: =(log-name escape-accepted:log-names)
+ ?> ?=([@ ~] t.t.topics.log)
+ =* parent=@ i.t.t.topics.log
+ =/ parent-point (get-point state parent)
+ ?> ?=(^ parent-point)
+ ?: ?=(%l2 -.u.parent-point) ~
+ :+ ~ [%point ship %sponsor `parent]~
+ point(escape.net ~, sponsor.net [%& parent])
+ ::
+ ?: =(log-name lost-sponsor:log-names)
+ ?> ?=([@ ~] t.t.topics.log)
+ =* parent=@ i.t.t.topics.log
+ :: If the sponsor we lost was not our actual sponsor, we didn't
+ :: actually lose anything.
+ ::
+ ?. =(parent who.sponsor.net.point) ~
+ ::
+ =/ parent-point (get-point state parent)
+ ?> ?=(^ parent-point)
+ ::
+ :: We can detach even if the child is on L2, as long as the parent
+ :: is on L1.
+ ::
+ ?: ?=(%l2 -.u.parent-point) ~
+ :+ ~ [%point ship %sponsor ~]~
+ point(has.sponsor.net %|)
+ ::
+ :: The rest can be done by any ship on L1, even if their spawn proxy
+ :: is set to L2
+ ::
+ ?: ?=(%l2 -.point) ~
+ ::
+ ?: =(log-name escape-requested:log-names)
+ ?> ?=([@ ~] t.t.topics.log)
+ =* parent=@ i.t.t.topics.log
+ =/ parent-point (get-point state parent)
+ ?> ?=(^ parent-point)
+ :+ ~ [%point ship %escape `parent]~
+ point(escape.net `parent)
+ ::
+ ?: =(log-name escape-canceled:log-names)
+ ?> ?=([@ ~] t.t.topics.log)
+ =* parent=@ i.t.t.topics.log
+ =/ parent-point (get-point state parent)
+ ?> ?=(^ parent-point)
+ :+ ~ [%point ship %escape ~]~
+ point(escape.net ~)
+ ::
+ ?: =(log-name broke-continuity:log-names)
+ ?> ?=(~ t.t.topics.log)
+ =* rift=@ data.log
+ :+ ~ [%point ship %rift rift]~
+ point(rift.net rift)
+ ::
+ ?: =(log-name changed-keys:log-names)
+ ?> ?=(~ t.t.topics.log)
+ =/ =keys
+ :* life=(cut 8 [0 1] data.log)
+ suite=(cut 8 [1 1] data.log)
+ auth=(cut 8 [2 1] data.log)
+ crypt=(cut 8 [3 1] data.log)
+ ==
+ :+ ~ [%point ship %keys keys]~
+ point(keys.net keys)
+ ::
+ ?: =(log-name owner-changed:log-names)
+ ?> ?=([@ ~] t.t.topics.log)
+ =* to i.t.t.topics.log
+ :: Depositing to L2 is represented by an ownership change on L1,
+ :: but it doesn't change who actually owns the ship.
+ ::
+ ?: =(deposit-address to)
+ :+ ~ [%point ship %dominion %l2]~
+ point(dominion %l2)
+ :+ ~ [%point ship %owner to]~
+ point(address.owner.own to)
+ ::
+ ?: =(log-name changed-transfer-proxy:log-names)
+ ?> ?=([@ ~] t.t.topics.log)
+ =* to i.t.t.topics.log
+ :+ ~ [%point ship %transfer-proxy to]~
+ point(address.transfer-proxy.own to)
+ ::
+ ?: =(log-name changed-management-proxy:log-names)
+ ?> ?=([@ ~] t.t.topics.log)
+ =* to i.t.t.topics.log
+ :+ ~ [%point ship %management-proxy to]~
+ point(address.management-proxy.own to)
+ ::
+ ?: =(log-name changed-voting-proxy:log-names)
+ ?> ?=([@ ~] t.t.topics.log)
+ =* to i.t.t.topics.log
+ :+ ~ [%point ship %voting-proxy to]~
+ point(address.voting-proxy.own to)
+ ::
+ (debug %unknown-log ~)
+::
+:: Receive batch of L2 transactions
+::
+++ receive-batch
+ |= [=verifier chain-id=@ud =state batch=@]
+ =/ chain-t (ud-to-ascii chain-id)
+ =/ =roll (parse-roll batch)
+ |- ^- [effects ^state]
+ ?~ roll
+ [~ state]
+ :: Verify signature, else skip tx
+ ::
+ ?. (verify-sig-and-nonce verifier chain-t state i.roll)
+ %+ debug %l2-sig-failed
+ =^ effects state $(roll t.roll)
+ :_ state
+ [[%tx i.roll `%sig-or-nonce-failed] effects]
+ :: Increment nonce, even if it later fails
+ ::
+ =^ effects-1 points.state (increment-nonce state from.tx.i.roll)
+ :: Process tx
+ ::
+ =^ effects-2 state
+ =/ tx-result=(unit [=effects =^state]) (receive-tx state tx.i.roll)
+ ?~ tx-result
+ %+ debug %l2-tx-failed
+ [[%tx i.roll `%tx-failed]~ state]
+ [[[%tx i.roll ~] effects.u.tx-result] state.u.tx-result]
+ =^ effects-3 state $(roll t.roll)
+ [:(welp effects-1 effects-2 effects-3) state]
+::
+++ increment-nonce
+ |= [=state =ship =proxy]
+ =/ point (get-point state ship)
+ ?> ?=(^ point) :: we only parsed 4 bytes
+ =* own own.u.point
+ =^ nonce u.point
+ ?- proxy
+ %own
+ :- nonce.owner.own
+ u.point(nonce.owner.own +(nonce.owner.own))
+ ::
+ %spawn
+ :- nonce.spawn-proxy.own
+ u.point(nonce.spawn-proxy.own +(nonce.spawn-proxy.own))
+ ::
+ %manage
+ :- nonce.management-proxy.own
+ u.point(nonce.management-proxy.own +(nonce.management-proxy.own))
+ ::
+ %vote
+ :- nonce.voting-proxy.own
+ u.point(nonce.voting-proxy.own +(nonce.voting-proxy.own))
+ ::
+ %transfer
+ :- nonce.transfer-proxy.own
+ u.point(nonce.transfer-proxy.own +(nonce.transfer-proxy.own))
+ ==
+ ::
+ :- [%nonce ship proxy nonce]~
+ (put:orm points.state ship u.point)
+::
+:: Receive an individual L2 transaction
+::
+++ receive-tx
+ |= [=state =tx]
+ |^
+ ^- (unit [effects ^state])
+ ?- +<.tx
+ %spawn (process-spawn +>.tx)
+ %transfer-point (w-point process-transfer-point ship.from.tx +>.tx)
+ %configure-keys (w-point process-configure-keys ship.from.tx +>.tx)
+ %escape (w-point-esc process-escape ship.from.tx +>.tx)
+ %cancel-escape (w-point-esc process-cancel-escape ship.from.tx +>.tx)
+ %adopt (w-point-esc process-adopt ship.tx +>.tx)
+ %reject (w-point-esc process-reject ship.tx +>.tx)
+ %detach (w-point-esc process-detach ship.tx +>.tx)
+ %set-spawn-proxy
+ (w-point-spawn process-set-spawn-proxy ship.from.tx +>.tx)
+ ::
+ %set-transfer-proxy
+ (w-point process-set-transfer-proxy ship.from.tx +>.tx)
+ ::
+ %set-management-proxy
+ (w-point process-set-management-proxy ship.from.tx +>.tx)
+ ==
+ ::
+ ++ w-point
+ |* [fun=$-([ship point *] (unit [effects point])) =ship rest=*]
+ ^- (unit [effects ^state])
+ =/ point (get-point state ship)
+ ?~ point (debug %strange-ship ~)
+ ?. ?=(%l2 -.u.point) (debug %ship-not-on-l2 ~)
+ :: Important to fully no-op on failure so we don't insert an entry
+ :: into points.state
+ ::
+ =/ res=(unit [=effects new-point=^point]) (fun u.point rest)
+ ?~ res
+ ~
+ `[effects.u.res state(points (put:orm points.state ship new-point.u.res))]
+ ::
+ ++ w-point-esc
+ |* [fun=$-([ship point *] (unit [effects point])) =ship rest=*]
+ ^- (unit [effects ^state])
+ =/ point (get-point state ship)
+ ?~ point (debug %strange-ship ~)
+ =/ res=(unit [=effects new-point=^point]) (fun u.point rest)
+ ?~ res
+ ~
+ `[effects.u.res state(points (put:orm points.state ship new-point.u.res))]
+ ::
+ ++ w-point-spawn
+ |* [fun=$-([ship point *] (unit [effects point])) =ship rest=*]
+ ^- (unit [effects ^state])
+ =/ point (get-point state ship)
+ ?~ point (debug %strange-ship ~)
+ ?: ?=(%l1 -.u.point) (debug %ship-on-l2 ~)
+ =/ res=(unit [=effects new-point=^point]) (fun u.point rest)
+ ?~ res
+ ~
+ `[effects.u.res state(points (put:orm points.state ship new-point.u.res))]
+ ::
+ ++ process-transfer-point
+ |= [=point to=address reset=?]
+ =* ship ship.from.tx
+ :: Assert from owner or transfer prxoy
+ ::
+ ?. |(=(%own proxy.from.tx) =(%transfer proxy.from.tx))
+ (debug %bad-permission ~)
+ :: Execute transfer
+ ::
+ =/ effects-1
+ ~[[%point ship %owner to] [%point ship %transfer-proxy *address]]
+ =: address.owner.own.point to
+ address.transfer-proxy.own.point *address
+ ==
+ :: Execute reset if requested
+ ::
+ ?. reset
+ `[effects-1 point]
+ ::
+ =^ effects-2 net.point
+ ?: =([0 0 0] +.keys.net.point)
+ `net.point
+ =/ =keys [+(life.keys.net.point) 0 0 0]
+ :- [%point ship %keys keys]~
+ [rift.net.point keys sponsor.net.point escape.net.point]
+ =^ effects-3 rift.net.point
+ ?: =(0 life.keys.net.point)
+ `rift.net.point
+ :- [%point ship %rift +(rift.net.point)]~
+ +(rift.net.point)
+ =/ effects-4
+ :~ [%point ship %spawn-proxy *address]
+ [%point ship %management-proxy *address]
+ [%point ship %voting-proxy *address]
+ [%point ship %transfer-proxy *address]
+ ==
+ =: address.spawn-proxy.own.point *address
+ address.management-proxy.own.point *address
+ address.voting-proxy.own.point *address
+ address.transfer-proxy.own.point *address
+ ==
+ `[:(welp effects-1 effects-2 effects-3 effects-4) point]
+ ::
+ ++ process-spawn
+ |= [=ship to=address]
+ ^- (unit [effects ^state])
+ =/ parent=^ship (sein ship)
+ :: Assert parent is on L2
+ ::
+ =/ parent-point (get-point state parent)
+ ?~ parent-point ~
+ ?. ?=(?(%l2 %spawn) -.u.parent-point) ~
+ :: Assert from owner or spawn proxy
+ ::
+ ?. ?& =(parent ship.from.tx)
+ |(=(%own proxy.from.tx) =(%spawn proxy.from.tx))
+ ==
+ (debug %bad-permission ~)
+ :: Assert child not already spawned
+ ::
+ ?^ (get:orm points.state ship) (debug %spawn-exists ~)
+ :: Assert one-level-down
+ ::
+ ?. =(+((ship-rank parent)) (ship-rank ship)) (debug %bad-rank ~)
+ ::
+ =/ [=effects new-point=point]
+ =/ point=(unit point) (get-point state ship)
+ ?> ?=(^ point) :: only parsed 4 bytes
+ :: If spawning to self, just do it
+ ::
+ ?: ?| ?& =(%own proxy.from.tx)
+ =(to address.owner.own.u.parent-point)
+ ==
+ ?& =(%spawn proxy.from.tx)
+ =(to address.spawn-proxy.own.u.parent-point)
+ ==
+ ==
+ :- ~[[%point ship %dominion %l2] [%point ship %owner to]]
+ u.point(address.owner.own to)
+ :: Else spawn to parent and set transfer proxy
+ ::
+ :- :~ [%point ship %dominion %l2]
+ [%point ship %owner address.owner.own.u.parent-point]
+ [%point ship %transfer-proxy to]
+ ==
+ %= u.point
+ address.owner.own address.owner.own.u.parent-point
+ address.transfer-proxy.own to
+ ==
+ `[effects state(points (put:orm points.state ship new-point))]
+ ::
+ ++ process-configure-keys
+ |= [=point crypt=@ auth=@ suite=@ breach=?]
+ =* ship ship.from.tx
+ ::
+ ?. |(=(%own proxy.from.tx) =(%manage proxy.from.tx))
+ (debug %bad-permission ~)
+ ::
+ =^ rift-effects rift.net.point
+ ?. breach
+ `rift.net.point
+ [[%point ship %rift +(rift.net.point)]~ +(rift.net.point)]
+ ::
+ =^ keys-effects keys.net.point
+ ?: =(+.keys.net.point [suite auth crypt])
+ `keys.net.point
+ =/ =keys
+ [+(life.keys.net.point) suite auth crypt]
+ [[%point ship %keys keys]~ keys]
+ ::
+ `[(welp rift-effects keys-effects) point]
+ ::
+ ++ process-escape
+ |= [=point parent=ship]
+ =* ship ship.from.tx
+ ?. |(=(%own proxy.from.tx) =(%manage proxy.from.tx))
+ (debug %bad-permission ~)
+ ::
+ ?. =(+((ship-rank parent)) (ship-rank ship))
+ (debug %bad-rank ~)
+ ::
+ :+ ~ [%point ship %escape `parent]~
+ point(escape.net `parent)
+ ::
+ ++ process-cancel-escape
+ |= [=point parent=ship]
+ =* ship ship.from.tx
+ ?. |(=(%own proxy.from.tx) =(%manage proxy.from.tx))
+ (debug %bad-permission ~)
+ ::
+ :+ ~ [%point ship %escape ~]~
+ point(escape.net ~)
+ ::
+ ++ process-adopt
+ |= [=point =ship]
+ =* parent ship.from.tx
+ ?. |(=(%own proxy.from.tx) =(%manage proxy.from.tx))
+ (debug %bad-permission ~)
+ ::
+ ?. =(escape.net.point `parent) (debug %no-adopt ~)
+ :+ ~ [%point ship %sponsor `parent]~
+ point(escape.net ~, sponsor.net [%& parent])
+ ::
+ ++ process-reject
+ |= [=point =ship]
+ =* parent ship.from.tx
+ ?. |(=(%own proxy.from.tx) =(%manage proxy.from.tx))
+ (debug %bad-permission ~)
+ ::
+ ?. =(escape.net.point `parent) (debug %no-reject ~)
+ :+ ~ [%point ship %escape ~]~
+ point(escape.net ~)
+ ::
+ ++ process-detach
+ |= [=point =ship]
+ =* parent ship.from.tx
+ ?. |(=(%own proxy.from.tx) =(%manage proxy.from.tx))
+ (debug %bad-permission ~)
+ ::
+ ?. =(who.sponsor.net.point parent) (debug %no-detach ~)
+ :+ ~ [%point ship %sponsor ~]~
+ point(has.sponsor.net %|)
+ ::
+ ++ process-set-management-proxy
+ |= [=point =address]
+ ?. |(=(%own proxy.from.tx) =(%manage proxy.from.tx))
+ (debug %bad-permission ~)
+ ::
+ :+ ~ [%point ship.from.tx %management-proxy address]~
+ point(address.management-proxy.own address)
+ ::
+ ++ process-set-spawn-proxy
+ |= [=point =address]
+ ?. |(=(%own proxy.from.tx) =(%spawn proxy.from.tx))
+ (debug %bad-permission ~)
+ ::
+ ?: (gte (ship-rank ship.from.tx) 2)
+ (debug %spawn-proxy-planet ~)
+ ::
+ :+ ~ [%point ship.from.tx %spawn-proxy address]~
+ point(address.spawn-proxy.own address)
+ ::
+ ++ process-set-transfer-proxy
+ |= [=point =address]
+ ?. |(=(%own proxy.from.tx) =(%transfer proxy.from.tx))
+ (debug %bad-permission ~)
+ ::
+ :+ ~ [%point ship.from.tx %transfer-proxy address]~
+ point(address.transfer-proxy.own address)
+ --
+--
+::
+:: State transition function
+::
+|= [=verifier chain-id=@ud =state =input]
+^- [effects ^state]
+?: ?=(%log +<.input)
+ :: Received log from L1 transaction
+ ::
+ (receive-log state event-log.input)
+:: Received L2 batch
+::
+:: %+ debug %batch
+(receive-batch verifier chain-id state batch.input)
diff --git a/lib/server.hoon b/lib/server.hoon
new file mode 100644
index 0000000..74230b4
--- /dev/null
+++ b/lib/server.hoon
@@ -0,0 +1,179 @@
+=, eyre
+|%
++$ request-line
+ $: [ext=(unit @ta) site=(list @t)]
+ args=(list [key=@t value=@t])
+ ==
+:: +parse-request-line: take a cord and parse out a url
+::
+++ parse-request-line
+ |= url=@t
+ ^- request-line
+ (fall (rush url ;~(plug apat:de-purl:html yque:de-purl:html)) [[~ ~] ~])
+::
+++ manx-to-octs
+ |= man=manx
+ ^- octs
+ (as-octt:mimes:html (en-xml:html man))
+::
+++ json-to-octs
+ |= jon=json
+ ^- octs
+ (as-octs:mimes:html (en:json:html jon))
+::
+++ app
+ |%
+ ::
+ :: +require-authorization:
+ :: redirect to the login page when unauthenticated
+ :: otherwise call handler on inbound request
+ ::
+ ++ require-authorization
+ |= $: =inbound-request:eyre
+ handler=$-(inbound-request:eyre simple-payload:http)
+ ==
+ ^- simple-payload:http
+ ::
+ ?: authenticated.inbound-request
+ ~! this
+ ~! +:*handler
+ (handler inbound-request)
+ ::
+ =- [[307 ['location' -]~] ~]
+ %^ cat 3
+ '/~/login?redirect='
+ url.request.inbound-request
+ ::
+ :: +require-authorization-simple:
+ :: redirect to the login page when unauthenticated
+ :: otherwise pass through simple-paylod
+ ::
+ ++ require-authorization-simple
+ |= [=inbound-request:eyre =simple-payload:http]
+ ^- simple-payload:http
+ ::
+ ?: authenticated.inbound-request
+ ~! this
+ simple-payload
+ ::
+ =- [[307 ['location' -]~] ~]
+ %^ cat 3
+ '/~/login?redirect='
+ url.request.inbound-request
+ ::
+ ++ give-simple-payload
+ |= [eyre-id=@ta =simple-payload:http]
+ ^- (list card:agent:gall)
+ =/ header-cage
+ [%http-response-header !>(response-header.simple-payload)]
+ =/ data-cage
+ [%http-response-data !>(data.simple-payload)]
+ :~ [%give %fact ~[/http-response/[eyre-id]] header-cage]
+ [%give %fact ~[/http-response/[eyre-id]] data-cage]
+ [%give %kick ~[/http-response/[eyre-id]] ~]
+ ==
+ --
+++ core
+ |%
+ ++ gate
+ =| opt=@
+ |= a=@ opt
+ :: ++ try gate(opt 5)
+ ++ try2
+ =/ g gate
+ g(opt 5)
+ --
+++ gen
+ |%
+ ::
+ ++ max-1-da ['cache-control' 'max-age=86400']
+ :: ++ max-1-wk ['cache-control' 'max-age=604800']
+ ++ max-1-wk ['cache-control' 'no-cache, no-store, must-revalidate']
+ ++ no-cache ['cache-control' 'no-cache, no-store, must-revalidate']
+ ::
+ ++ lol
+ =| lmao=?
+ |= a=* %lol
+ ++ html-response
+ =| cache=?
+ |= =octs
+ ^- simple-payload:http
+ :_ `octs
+ [200 [['content-type' 'text/html'] ?:(cache [max-1-wk ~] ~)]]
+ ::
+ ++ css-response
+ =| cache=?
+ |= =octs
+ ^- simple-payload:http
+ :_ `octs
+ [200 [['content-type' 'text/css'] ?:(cache [max-1-wk ~] ~)]]
+ ::
+ ++ js-response
+ =| cache=?
+ |= =octs
+ ^- simple-payload:http
+ :_ `octs
+ [200 [['content-type' 'text/javascript'] ?:(cache [max-1-wk ~] ~)]]
+ ::
+ ++ png-response
+ =| cache=?
+ |= =octs
+ ^- simple-payload:http
+ :_ `octs
+ [200 [['content-type' 'image/png'] ?:(cache [max-1-wk ~] ~)]]
+ ::
+ ++ svg-response
+ =| cache=?
+ |= =octs
+ ^- simple-payload:http
+ :_ `octs
+ [200 [['content-type' 'image/svg+xml'] ?:(cache [max-1-wk ~] ~)]]
+ ::
+ ++ ico-response
+ |= =octs
+ ^- simple-payload:http
+ [[200 [['content-type' 'image/x-icon'] max-1-wk ~]] `octs]
+ ::
+ ++ woff2-response
+ =| cache=?
+ |= =octs
+ ^- simple-payload:http
+ [[200 [['content-type' 'font/woff2'] max-1-wk ~]] `octs]
+ ++ woff-response
+ =| cache=?
+ |= =octs
+ ^- simple-payload:http
+ [[200 [['content-type' 'font/woff'] max-1-wk ~]] `octs]
+ ::
+ ++ json-response
+ =| cache=_|
+ |= =json
+ ^- simple-payload:http
+ :_ `(json-to-octs json)
+ [200 [['content-type' 'application/json'] ?:(cache [max-1-da ~] ~)]]
+ ::
+ ++ manx-response
+ =| cache=_|
+ |= man=manx
+ ^- simple-payload:http
+ :_ `(manx-to-octs man)
+ [200 [['content-type' 'text/html'] ?:(cache [max-1-da ~] ~)]]
+ ::
+ ++ not-found
+ ^- simple-payload:http
+ [[404 ~] ~]
+ ::
+ ++ login-redirect
+ |= =request:http
+ ^- simple-payload:http
+ =- [[307 ['location' -]~] ~]
+ %^ cat 3
+ '/~/login?redirect='
+ url.request
+ ::
+ ++ redirect
+ |= redirect=cord
+ ^- simple-payload:http
+ [[303 ['location' redirect]~] ~]
+ --
+--
diff --git a/lib/sortug.hoon b/lib/sortug.hoon
new file mode 100644
index 0000000..e86e163
--- /dev/null
+++ b/lib/sortug.hoon
@@ -0,0 +1,679 @@
+|%
+:: parsing and printing dates
+++ dates
+ |%
+ ++ pad pad:string
+ ++ to-htmldate |= d=@da ^- tape
+ =+ [[* y=@] m=@ [d=@ h=@ mm=@ s=@ f=*]]=(yore d)
+ =/ ys (scow %ud y)
+ =/ ms (pad:string (scow %ud m) 2)
+ =/ ds (pad:string (scow %ud d) 2)
+ =/ hs (pad:string (scow %ud h) 2)
+ =/ mins (pad:string (scow %ud mm) 2)
+ "{ys}-{ms}-{ds}T{hs}:{mins}"
+ ++ date-to-path
+ |= d=@da ^- path
+ =+ [[a y] m [d h mm s f]]=(yore d)
+ =/ yr (numb:enjs:format y)
+ ?> ?=(%n -.yr)
+ /[p.yr]/(scot %ud m)/(scot %ud d)
+ ++ date-to-tape
+ |= [d=@da delim=tape] ^- tape
+ =+ [[a y] m [d h mm s f]]=(yore d)
+ =/ ys (ud-to-cord:string y)
+ =/ month (pad "{<m.m>}" 2)
+ =/ day (pad "{<d.d>}" 2)
+ "{(trip ys)}{delim}{month}{delim}{day}"
+ ++ datetime-to-tape
+ |= [d=@da delim=tape] ^- tape
+ =+ [[a y] m [d h mm s f]]=(yore d)
+ =/ ys (ud-to-cord:string y)
+ =/ month (pad "{<m.m>}" 2)
+ =/ day (pad "{<d.d>}" 2)
+ =/ hours (pad "{<h.h>}" 2)
+ =/ minutes (pad "{<m.mm>}" 2)
+ =/ seconds (pad "{<s.s>}" 2)
+ "{(trip ys)}{delim}{month}{delim}{day} {hours}:{minutes}:{seconds}"
+ ++ time-to-tape
+ |= d=@da ^- tape
+ =+ [[a y] m [d h mm s f]]=(yore d)
+ =/ hours (pad "{<h.h>}" 2)
+ =/ minutes (pad "{<m.mm>}" 2)
+ "{hours}:{minutes}"
+
+ --
+++ parsing
+ |%
+ :: utils
+ ++ atom-dots
+ |= s=@t ^- @ud
+ =/ no-dots (rush s dem)
+ ?~ no-dots (rash s dem:ag) u.no-dots
+ ++ uri-encode
+ |= t=tape ^- tape
+ =+ (en-urlt:html t) :: double-encoding because iris decodes shit at some point for some reason
+ (en-urlt:html -)
+ :: raw functions used by other functions
+ ++ white (star gah)
+ ++ para
+ |%
+ ++ eof ;~(less next (easy ~))
+ ++ white (mask "\09 ")
+ ++ blank ;~(plug (star white) (just '\0a'))
+ ++ hard-wrap (cold ' ' ;~(plug blank (star white)))
+ ++ one-space (cold ' ' (plus white))
+ ++ empty
+ ;~ pose
+ ;~(plug blank (plus blank))
+ ;~(plug (star white) eof)
+ ;~(plug blank (star white) eof)
+ ==
+ ++ para
+ %+ ifix
+ [(star white) empty]
+ %- plus
+ ;~ less
+ empty
+ next
+ ==
+ --
+ ++ trim para:para :: from whom/lib/docu
+ :: Dates
+ ++ y (bass 10 (stun 3^4 dit))
+ :: ++ m (sear (snug mon:yu) (plus alf))
+ ++ d (bass 10 (stun 1^2 dit))
+ ++ t :: hours:minutes:secs
+ :: %+ cook |=([h=@u @ m=@u @ s=@u] ~[h m s])
+ :: ;~(plug d col d col d)
+ ;~(plug d col d)
+ ++ weekday-3
+ ;~ pose
+ %+ cold 0 (jest 'Sun')
+ %+ cold 1 (jest 'Mon')
+ %+ cold 2 (jest 'Tue')
+ %+ cold 3 (jest 'Wed')
+ %+ cold 4 (jest 'Thu')
+ %+ cold 5 (jest 'Fri')
+ %+ cold 6 (jest 'Sat')
+ ==
+ ++ monthname-3
+ ;~ pose
+ %+ cold 1 (jest 'Jan')
+ %+ cold 2 (jest 'Feb')
+ %+ cold 3 (jest 'Mar')
+ %+ cold 4 (jest 'Apr')
+ %+ cold 5 (jest 'May')
+ %+ cold 6 (jest 'Jun')
+ %+ cold 7 (jest 'Jul')
+ %+ cold 8 (jest 'Aug')
+ %+ cold 9 (jest 'Sep')
+ %+ cold 10 (jest 'Oct')
+ %+ cold 11 (jest 'Nov')
+ %+ cold 12 (jest 'Dec')
+ ==
+ ++ three-section-time :: hours:minutes:secs
+ %+ cook |=([h=@u @ m=@u @ s=@u] [h m s])
+ ;~(plug d col d col d)
+ ++ html-datetime
+ %+ cook |= [y=@ud * m=@ud * d=@ud * h=@ud * min=@ud]
+ =/ dt *date =. dt dt(y y, m m, d.t d, h.t h, m.t min)
+ (year dt)
+ ;~ plug y hep d hep d (just 'T')
+ t
+ ==
+ ++ twatter-date
+ %+ cook |= [w=@ * m=@ * d=@ * [h=@ mn=@ s=@] * y=@]
+ =| dat=date %- year
+ dat(y y, m m, t [d h mn s ~])
+ ;~ plug
+ weekday-3
+ ace
+ monthname-3
+ ace
+ d
+ ace
+ three-section-time
+ ;~ plug
+ ace
+ lus
+ y
+ ace
+ ==
+ y
+ ==
+ :: urls
+ ++ link auri:de-purl:html
+ ++ youtube
+ ;~ pfix
+ ;~ plug
+ (jest 'https://')
+ ;~ pose
+ (jest 'www.youtube.com/watch?v=')
+ (jest 'youtube.com/watch?v=')
+ (jest 'youtu.be/')
+ ==
+ ==
+ ;~ sfix
+ (star aln)
+ (star next)
+ ==
+ ==
+ ++ twatter
+ ;~ pfix
+ ;~ plug
+ (jest 'https://')
+ ;~ pose
+ (jest 'x.com/')
+ (jest 'twitter.com/')
+ ==
+ (star ;~(less fas next))
+ (jest '/status/')
+ ==
+ ;~ sfix
+ (star nud)
+ (star next)
+ ==
+ ==
+ :: Better scow/slaw
+ ++ scow
+ |= [mod=@tas a=@] ^- tape
+ ?+ mod ""
+ %s (signed-scow a)
+ %ud (a-co:co a)
+ %ux ((x-co:co 0) a)
+ %uv ((v-co:co 0) a)
+ %uw ((w-co:co 0) a)
+ ==
+ ++ signed-scow |= a=@s ^- tape
+ =/ old (old:si a)
+ =/ num (scow %ud +.old)
+ =/ sign=tape ?: -.old "" "-"
+ "{sign}{num}"
+ ++ b64 (bass 64 (plus siw:ab))
+ ++ b16 (bass 16 (plus six:ab))
+ ++ slaw
+ |= [mod=@tas txt=@t] ^- (unit @)
+ ?+ mod ~
+ %ud (rush txt dem)
+ %ux (rush txt b16)
+ %uv (rush txt vum:ag)
+ %uw (rush txt b64)
+ ==
+ :: ++ b64
+ :: %+ cook
+ :: |=(a=tape (rap 3 ^-((list @) a)))
+ :: (star ;~(pose nud low cen hig hep dot sig cab))
+ :: Paths
+ +$ michi (list @t)
+ ++ stam
+ %+ sear
+ |= m=michi
+ ^- (unit michi)
+ ?: ?=([~ ~] m) ~
+ ?. =(~ (rear m)) `m
+ ~
+ ;~(pfix fas (most fas b64))
+ ++ smat
+ |= m=michi ^- tape
+ =/ t "/"
+ |-
+ ?~ m t
+ =/ nt "{t}/{(trip i.m)}"
+ $(m t.m, t nt)
+ ++ de-comma
+ %+ sear
+ |= p=path
+ ^- (unit path)
+ ?: ?=([~ ~] p) `~
+ ?. =(~ (rear p)) `p
+ ~
+ (most com urs:ab)
+ ++ de-path
+ :: modified from apat:de-purl:html
+ =/ delim ;~(pose fas dot)
+ %+ cook :: get rid of the last dot and make the extension a part of the path
+ |= p=path ?~ p p
+ =/ flopped=path (flop p)
+ =/ sr=path (rash -.flopped (csplit dot))
+ =/ rest=path +.flopped
+ %- flop %+ welp (flop sr) rest
+ ;~(pfix fas (more fas smeg:de-purl:html))
+
+ :: splitting
+ ++ csplit |* =rule
+ (more rule (cook crip (star ;~(less rule next))))
+ ++ split |* =rule
+ (more rule (star ;~(less rule next)))
+ ++ dinfix |* [pf=rule sf=rule]
+ =/ neither (star ;~(less ;~(pose pf sf) next))
+ ;~(pfix pf ;~(sfix neither sf))
+ :: infixes
+ ++ infix
+ |* =rule
+ (ifix [rule rule] (star ;~(less rule next)))
+ ++ infix2
+ |* [delim=rule inner=rule]
+ |= tub=nail
+ =+ vex=(delim tub)
+ ?~ q.vex
+ (fail tub)
+ =/ but=nail tub
+ =+ outer=(;~(sfix (plus ;~(less delim next)) delim) q.u.q.vex)
+ ?~ q.outer
+ (fail tub)
+ =+ in=(inner [1 1] p.u.q.outer)
+ ?~ q.in
+ (fail tub)
+ outer(p.u.q p.u.q.in)
+
+ :: this fixes parsing with troon flags
+ :: :: nest-failing
+ :: ++ esca :: escaped character
+ :: =/ qux (bass 16 (stun [4 4] hit))
+ :: ;~ pfix bas
+ :: =* loo
+ :: =* lip
+ :: ^- (list (pair @t @))
+ :: [b+8 t+9 n+10 f+12 r+13 ~]
+ :: =* wow `(map @t @)`(malt lip)
+ :: (sear ~(get by wow) low)
+ :: =* tuf ;~(pfix (just 'u') (cook tuft qux))
+ :: ;~(pose doq fas soq bas loo tuf)
+ :: ==
+ :: ++ dejson :: parse JSON
+ :: =/ de de:json:html
+ :: =. esca.de esca
+ :: de
+ --
+ :: string utils
+ ++ string
+ |%
+ ++ replace
+ |= [bit=tape bot=tape =tape]
+ ^- ^tape
+ |-
+ =/ off (find bit tape)
+ ?~ off tape
+ =/ clr (oust [(need off) (lent bit)] tape)
+ $(tape :(weld (scag (need off) clr) bot (slag (need off) clr)))
+ ::
+ ++ split
+ |= [str=tape delim=tape]
+ ^- (list tape)
+ (split-rule str (jest (crip delim)))
+ ++ split-rule
+ |* [str=tape delim=rule]
+ ^- (list tape)
+ %+ fall
+ (rust str (more delim (star ;~(less delim next))))
+ [str ~]
+ ++ contains
+ |= [str=tape nedl=tape]
+ ^- ?
+ ?~ (find nedl str) | &
+ ++ trim
+ |= a=tape
+ |- ^- tape
+ ?: ?=([%' ' *] a)
+ $(a t.a)
+ (flop a)
+ ++ number
+ |= a=@ud ^- tape
+ ?: =(0 a) "0"
+ %- flop
+ |- ^- tape
+ ?:(=(0 a) ~ [(add '0' (mod a 10)) $(a (div a 10))])
+ ++ capitalize
+ |= a=@t ^- tape
+ =/ t=(list @t) (trip a)
+ ?~ t (trip a)
+ t(i (sub i.t 32))
+ ++ enpath
+ |= str=cord ^- path
+ =/ allow ;~(pose low nud)
+ =/ lcase %+ cook
+ |= a=@t (add 32 a) hig
+ =/ rul ;~(pose allow lcase)
+ =/ del ;~(less rul next)
+ =/ frul (more del (cook crip (star rul)))
+ (rash str frul)
+ ++ enkebab |= s=@t (crip (enkebab2 s))
+ ++ enkebab2
+ |= str=cord ^- tape
+ =/ allow ;~(pose low nud hep)
+ =/ kebab (cold '-' next)
+ =/ lcase %+ cook
+ |= a=@t (add 32 a) hig
+ =/ rul ;~(pose allow lcase kebab)
+ (rash str (star rul))
+ ++ enkebab3
+ |= str=cord ^- tape
+ =/ allow ;~(pose low nud hep)
+ =/ kebab (cold '-' next)
+ =/ lcase %+ cook
+ |= a=@t (add 32 a) hig
+ =/ rul ;~(pose allow lcase unic kebab)
+ (rash str (star rul))
+ ++ unic
+ %- cook :_ unicode
+ |= a=@ %- crip (scow:parsing %uw a)
+ ++ unicode (shim 128 100.000.000)
+ :: Split string by parsing rule delimiter.
+ :: ++ enkebab
+ :: |= str=cord
+ :: ^- cord
+ :: ~| str
+ :: =- (fall - str)
+ :: %+ rush str
+ :: =/ name
+ :: %+ cook
+ :: |= part=tape
+ :: ^- tape
+ :: ?~ part part
+ :: :- (add i.part 32)
+ :: t.part
+ :: ;~(plug hig (star low))
+ :: %+ cook
+ :: |=(a=(list tape) (crip (zing (join "-" a))))
+ :: ;~(plug (star low) (star name))
+ ++ cut-cord
+ |= [=cord chars=@ud]
+ %+ end 3^chars cord
+ ::
+ ++ pad
+ |= [t=tape length=@ud] ^- tape
+ ?: .=(length (lent t)) t
+ $(t "0{t}")
+ ::
+ ++ ud-to-cord
+ |= n=@ud ^- @t
+ %- crip
+ %- zing (rush (scot %ud n) (more dot (star nud)))
+ ++ remove
+ |= [s=@t r=@t] ^- @t
+ %- crip
+ %- zing
+ %+ rush s
+ %+ more (jest r)
+ %- star
+ ;~(less (jest r) next)
+ :: bitwise stuff
+ ++ cord-size
+ |= c=@t
+ (met 3 c)
+ ++ concat-cord-list
+ |= c=(list @t)
+ (rap 3 c)
+ ++ cut-cord-2
+ |= [c=@t s=@ud e=@ud]
+ (cut 3 [s e] c)
+ ++ cfind-index
+ |= [nedl=@t hay=@t length=@ud case=?] ^- (unit [snip=@t left-amari=@ud right-amari=@ud])
+ =/ nlen (met 3 nedl)
+ =/ hlen (met 3 hay)
+ ?: (lth hlen nlen) ~
+ =? nedl !case
+ (crass nedl)
+ :: iterate from index 0
+ =/ pos 0
+ =/ lim (sub hlen nlen)
+ |-
+ :: If our position is further than the length of query
+ :: it's obviously not gonna happen anymore so return
+ ?: (gth pos lim) ~
+ :: If needle is equal to the [position needle-length] slice of hay then we're good
+ =/ substring ?: case (cut 3 [pos nlen] hay) (crass (cut 3 [pos nlen] hay))
+ ?. .=(nedl substring) $(pos +(pos))
+ :: we grab a bigger piece of the cord, starting where
+ =/ [start-index=@ud end-index=@ud] [pos (add pos nlen)]
+ :: say it's [150 160]
+ =/ halfway=@ud (div (sub length nlen) 2) :: that's 45
+ =/ start=@ud ?: (gth pos halfway) (sub pos halfway) 0 :: that's 105
+ =/ end=@ud ?: (gte (add halfway end-index) hlen) hlen (add halfway end-index) :: 200
+
+ =/ right-amari (sub halfway (sub end end-index)) :: 5
+ =/ left-amari (sub halfway (sub start-index start)) :: 0
+ =/ snip=@t (cut 3 [start end] hay)
+ %- some :+ snip left-amari right-amari
+
+ ++ cfindi
+ |= [nedl=@t hay=@t case=?] ^- @t
+ =/ nlen (met 3 nedl)
+ =/ hlen (met 3 hay)
+ =| res=@t
+ ?: (lth hlen nlen) res
+ =? nedl !case
+ (crass nedl)
+ :: iterate from index 0
+ =/ pos 0
+ =/ lim (sub hlen nlen)
+ |-
+ :: If our position is further than the length of query
+ :: it's obviously not gonna happen anymore so return
+ ?: (gth pos lim) res
+ :: If needle is equal to the [position needle-length] slice of hay then we're good
+ ?: .= nedl
+ ?: case
+ (cut 3 [pos nlen] hay)
+ (crass (cut 3 [pos nlen] hay))
+ :: we grab a bigger piece of the cord, starting where
+ =/ s ?: (lte pos 50) 0 (sub pos 50)
+ (cut 3 [s 100] hay)
+ $(pos +(pos))
+ ++ cfind
+ |= [nedl=@t hay=@t case=?]
+ ^- ?
+ =/ nlen (met 3 nedl)
+ =/ hlen (met 3 hay)
+ ?: (lth hlen nlen)
+ |
+ =? nedl !case
+ (crass nedl)
+ =/ pos 0
+ =/ lim (sub hlen nlen)
+ |-
+ ?: (gth pos lim)
+ |
+ ?: .= nedl
+ ?: case
+ (cut 3 [pos nlen] hay)
+ (crass (cut 3 [pos nlen] hay))
+ &
+ $(pos +(pos))
+ ++ crass
+ |= text=@t
+ ^- @t
+ %^ run
+ 3
+ text
+ |= dat=@
+ ^- @
+ ?. &((gth dat 64) (lth dat 91))
+ dat
+ (add dat 32)
+
+
+ --
+ :: agentio replacement
+++ io
+ |_ =bowl:gall
+ ++ retrieve
+ |= =path
+ =/ bp /gx/(scot %p our.bowl)/[dap.bowl]/(scot %da now.bowl)
+ .^(* (weld bp (weld path /noun)))
+ ++ scry
+ |* [app=@tas =path =mold]
+ =/ bp /gx/(scot %p our.bowl)/[app]/(scot %da now.bowl)
+ =/ pat (weld bp (weld path /noun))
+ .^(mold pat)
+ ++ scry2
+ |* [app=@tas =path =mold]
+ =/ bp /gx/(scot %p our.bowl)/[app]/(scot %da now.bowl)
+ =/ pat (weld bp (weld path /noun))
+ %- mole |. .^(mold pat)
+ ++ scry-pad
+ |= t=@tas ^- path /(scot %p our.bowl)/[t]/(scot %da now.bowl)
+ --
+ :: esoteric types
+ ++ types
+ |%
+ :: result type
+ ++ resu
+ |* =mold
+ $% [%ok mold]
+ [%err p=@t]
+ ==
+ --
+ ++ web
+ |%
+ ++ images
+ %- silt :~('png' 'jpg' 'jpeg' 'svg' 'webp')
+ ++ is-image
+ |= url=@t ^- ?
+ =/ u=(unit purl:eyre) (de-purl:html url)
+ ?~ u .n
+ =/ ext p.q.u.u
+ ?~ ext .n
+ (~(has in images) u.ext)
+ --
+:: list functions
+++ seq
+ |%
+ ++ slice
+ |* [a=(list) count=@ud index=@ud]
+ =| i=@ud
+ |- ^+ a
+ ?~ a ~
+ ?: .=(count 0) ~
+ ?: (lth i index) $(a t.a, i +(i))
+ :- i.a
+ $(a t.a, i +(i), count (dec count))
+
+ ++ pick
+ |* [a=(list) b=@]
+ =/ top (dec (lent a))
+ =/ ind (mod b top)
+ (snag ind a)
+ ++ flop :: reverse
+ |* a=(list)
+ => .(a (homo a))
+ ^+ a
+ =+ b=`_a`~
+ |-
+ ?~ a b
+ $(a t.a, b [i.a b])
+ :: TODO study these two well
+ ++ reel
+ ~/ %reel
+ |* [a=(list) b=_=>(~ |=([* *] +<+))]
+ |- ^+ ,.+<+.b
+ ?~ a
+ +<+.b
+ (b i.a $(a t.a))
+ ++ roll
+ ~/ %roll
+ |* [a=(list) b=_=>(~ |=([* *] +<+))]
+ |- ^+ ,.+<+.b
+ ?~ a
+ +<+.b
+ $(a t.a, b b(+<+ (b i.a +<+.b)))
+ ++ snip
+ |* a=(list)
+ =/ rev (flop a)
+ ?~ rev a
+ (flop t.rev)
+ ++ fold
+ |* [a=(list) b=* c=_|=(^ +<+)]
+ |- ^+ b
+ ?~ a b
+ =/ nb (c [i.a b])
+ $(a t.a, b nb)
+ ++ foldi
+ |* [a=(list) b=* c=_|=(^ +<+)]
+ =| i=@ud
+ |- ^+ b
+ ?~ a b
+ =/ nb (c i i.a b)
+ $(a t.a, b nb, i +(i))
+ ++ mapi
+ |* [a=(list) b=gate]
+ =| i=@ud
+ => .(a (homo a))
+ ^- (list _?>(?=(^ a) (b i i.a)))
+ |-
+ ?~ a ~
+ :- i=(b i i.a)
+ t=$(a t.a, i +(i))
+ --
+++ js
+ |%
+ ++ de
+ |%
+ ++ maybe
+ |* fst=$-(json *)
+ |= jon=json
+ ?~ jon ~ (fst jon)
+ ++ st
+ |= mol=mold
+ |= jon=json
+ ?> ?=([%s *] jon)
+ %- mol p.jon
+ ++ ur
+ |* wit=$-(json *)
+ |= jon=(unit json)
+ ?~(jon ~ `(wit u.jon))
+ ++ gen
+ |= jon=json
+ ?- -.jon
+ %s p.jon
+ %n p.jon
+ %b p.jon
+ %a (turn p.jon gen)
+ %o ((om:dejs:format gen) jon)
+ ==
+ --
+ ++ en
+ |%
+ ++ bitch %bitch
+ --
+ --
+++ sail
+ |%
+ ++ coki-to-string
+ |= m=(map @t @t) ^- cord
+ %- crip %- ~(rep by m)
+ |= [pair=[key=@t value=@t] acc=tape]
+ "{acc}{(trip key.pair)}={(trip value.pair)}; "
+ ++ handle-html-form
+ |= body=(unit octs) ^- (map @t @t)
+ ?~ body ~
+ =/ text q.u.body
+ =/ clean (remove:string text '%0D')
+ :: TODO html forms use \0d\0a
+ :: for carriage return
+ :: this breaks the markdown parser
+ =/ parser (more tis (star next))
+ =/ res (rush clean yquy:de-purl:html)
+ ?~ res ~ (malt u.res)
+ --
+++ search
+ |%
+ ++ parse-query
+ |= query=@t ^- (map @t [query=@t neg=?])
+ :: =s \'"machine learning" OR "data science" lmao -filter:links -from:elonmusk since:2023-01-01 until:2023-10-12 lang:en filter:verified\'
+ ~
+ ++ apex
+ %+ knee **
+ |. ~+
+ %- star
+ ;~ pose
+ (stag %quot quotes)
+ (stag %white (split:parsing ace))
+ (stag %lol rest)
+ :: next
+ ==
+++ rest (plus ;~(less doq next))
+++ quotes (infix:parsing doq)
+++ else (star next)
+ --
+--
diff --git a/lib/tiny.hoon b/lib/tiny.hoon
new file mode 100644
index 0000000..f4cbf06
--- /dev/null
+++ b/lib/tiny.hoon
@@ -0,0 +1,670 @@
+!.
+=> %a50
+~% %a.50 ~ ~
+|%
+:: Types
+::
++$ ship @p
++$ life @ud
++$ rift @ud
++$ pass @
++$ bloq @
++$ step _`@u`1
++$ bite $@(bloq [=bloq =step])
++$ octs [p=@ud q=@]
++$ mold $~(* $-(* *))
+++ unit |$ [item] $@(~ [~ u=item])
+++ list |$ [item] $@(~ [i=item t=(list item)])
+++ lest |$ [item] [i=item t=(list item)]
+++ tree |$ [node] $@(~ [n=node l=(tree node) r=(tree node)])
+++ pair |$ [head tail] [p=head q=tail]
+++ map
+ |$ [key value]
+ $| (tree (pair key value))
+ |=(a=(tree (pair)) ?:(=(~ a) & ~(apt by a)))
+::
+++ set
+ |$ [item]
+ $| (tree item)
+ |=(a=(tree) ?:(=(~ a) & ~(apt in a)))
+::
+++ jug |$ [key value] (map key (set value))
+::
+:: Bits
+::
+++ dec :: decrement
+ ~/ %dec
+ |= a=@
+ ~_ leaf+"decrement-underflow"
+ ?< =(0 a)
+ =+ b=0
+ |- ^- @
+ ?: =(a +(b)) b
+ $(b +(b))
+::
+++ add :: plus
+ ~/ %add
+ |= [a=@ b=@]
+ ^- @
+ ?: =(0 a) b
+ $(a (dec a), b +(b))
+::
+++ sub :: subtract
+ ~/ %sub
+ |= [a=@ b=@]
+ ~_ leaf+"subtract-underflow"
+ :: difference
+ ^- @
+ ?: =(0 b) a
+ $(a (dec a), b (dec b))
+::
+++ mul :: multiply
+ ~/ %mul
+ |: [a=`@`1 b=`@`1]
+ ^- @
+ =+ c=0
+ |-
+ ?: =(0 a) c
+ $(a (dec a), c (add b c))
+::
+++ div :: divide
+ ~/ %div
+ |: [a=`@`1 b=`@`1]
+ ^- @
+ ~_ leaf+"divide-by-zero"
+ ?< =(0 b)
+ =+ c=0
+ |-
+ ?: (lth a b) c
+ $(a (sub a b), c +(c))
+::
+++ dvr :: divide w/remainder
+ ~/ %dvr
+ |: [a=`@`1 b=`@`1]
+ ^- [p=@ q=@]
+ [(div a b) (mod a b)]
+::
+++ mod :: modulus
+ ~/ %mod
+ |: [a=`@`1 b=`@`1]
+ ^- @
+ ?< =(0 b)
+ (sub a (mul b (div a b)))
+::
+++ bex :: binary exponent
+ ~/ %bex
+ |= a=bloq
+ ^- @
+ ?: =(0 a) 1
+ (mul 2 $(a (dec a)))
+::
+++ lsh :: left-shift
+ ~/ %lsh
+ |= [a=bite b=@]
+ =/ [=bloq =step] ?^(a a [a *step])
+ (mul b (bex (mul (bex bloq) step)))
+::
+++ rsh :: right-shift
+ ~/ %rsh
+ |= [a=bite b=@]
+ =/ [=bloq =step] ?^(a a [a *step])
+ (div b (bex (mul (bex bloq) step)))
+::
+++ con :: binary or
+ ~/ %con
+ |= [a=@ b=@]
+ =+ [c=0 d=0]
+ |- ^- @
+ ?: ?&(=(0 a) =(0 b)) d
+ %= $
+ a (rsh 0 a)
+ b (rsh 0 b)
+ c +(c)
+ d %+ add d
+ %+ lsh [0 c]
+ ?& =(0 (end 0 a))
+ =(0 (end 0 b))
+ ==
+ ==
+::
+++ dis :: binary and
+ ~/ %dis
+ |= [a=@ b=@]
+ =| [c=@ d=@]
+ |- ^- @
+ ?: ?|(=(0 a) =(0 b)) d
+ %= $
+ a (rsh 0 a)
+ b (rsh 0 b)
+ c +(c)
+ d %+ add d
+ %+ lsh [0 c]
+ ?| =(0 (end 0 a))
+ =(0 (end 0 b))
+ ==
+ ==
+::
+++ mix :: binary xor
+ ~/ %mix
+ |= [a=@ b=@]
+ ^- @
+ =+ [c=0 d=0]
+ |-
+ ?: ?&(=(0 a) =(0 b)) d
+ %= $
+ a (rsh 0 a)
+ b (rsh 0 b)
+ c +(c)
+ d (add d (lsh [0 c] =((end 0 a) (end 0 b))))
+ ==
+::
+++ lth :: less
+ ~/ %lth
+ |= [a=@ b=@]
+ ^- ?
+ ?& !=(a b)
+ |-
+ ?| =(0 a)
+ ?& !=(0 b)
+ $(a (dec a), b (dec b))
+ == == ==
+::
+++ lte :: less or equal
+ ~/ %lte
+ |= [a=@ b=@]
+ |(=(a b) (lth a b))
+::
+++ gte :: greater or equal
+ ~/ %gte
+ |= [a=@ b=@]
+ ^- ?
+ !(lth a b)
+::
+++ gth :: greater
+ ~/ %gth
+ |= [a=@ b=@]
+ ^- ?
+ !(lte a b)
+::
+++ swp :: naive rev bloq order
+ ~/ %swp
+ |= [a=bloq b=@]
+ (rep a (flop (rip a b)))
+::
+++ met :: measure
+ ~/ %met
+ |= [a=bloq b=@]
+ ^- @
+ =+ c=0
+ |-
+ ?: =(0 b) c
+ $(b (rsh a b), c +(c))
+::
+++ end :: tail
+ ~/ %end
+ |= [a=bite b=@]
+ =/ [=bloq =step] ?^(a a [a *step])
+ (mod b (bex (mul (bex bloq) step)))
+::
+++ cat :: concatenate
+ ~/ %cat
+ |= [a=bloq b=@ c=@]
+ (add (lsh [a (met a b)] c) b)
+::
+++ cut :: slice
+ ~/ %cut
+ |= [a=bloq [b=step c=step] d=@]
+ (end [a c] (rsh [a b] d))
+::
+++ can :: assemble
+ ~/ %can
+ |= [a=bloq b=(list [p=step q=@])]
+ ^- @
+ ?~ b 0
+ (add (end [a p.i.b] q.i.b) (lsh [a p.i.b] $(b t.b)))
+::
+++ cad :: assemble specific
+ ~/ %cad
+ |= [a=bloq b=(list [p=step q=@])]
+ ^- [=step @]
+ :_ (can a b)
+ |-
+ ?~ b
+ 0
+ (add p.i.b $(b t.b))
+::
+++ rep :: assemble fixed
+ ~/ %rep
+ |= [a=bite b=(list @)]
+ =/ [=bloq =step] ?^(a a [a *step])
+ =| i=@ud
+ |- ^- @
+ ?~ b 0
+ %+ add $(i +(i), b t.b)
+ (lsh [bloq (mul step i)] (end [bloq step] i.b))
+::
+++ rip :: disassemble
+ ~/ %rip
+ |= [a=bite b=@]
+ ^- (list @)
+ ?: =(0 b) ~
+ [(end a b) $(b (rsh a b))]
+::
+::
+:: Lists
+::
+++ lent :: length
+ ~/ %lent
+ |= a=(list)
+ ^- @
+ =+ b=0
+ |-
+ ?~ a b
+ $(a t.a, b +(b))
+::
+++ slag :: suffix
+ ~/ %slag
+ |* [a=@ b=(list)]
+ |- ^+ b
+ ?: =(0 a) b
+ ?~ b ~
+ $(b t.b, a (dec a))
+::
+++ snag :: index
+ ~/ %snag
+ |* [a=@ b=(list)]
+ |- ^+ ?>(?=(^ b) i.b)
+ ?~ b
+ ~_ leaf+"snag-fail"
+ !!
+ ?: =(0 a) i.b
+ $(b t.b, a (dec a))
+::
+++ homo :: homogenize
+ |* a=(list)
+ ^+ =< $
+ |@ ++ $ ?:(*? ~ [i=(snag 0 a) t=$])
+ --
+ a
+::
+++ flop :: reverse
+ ~/ %flop
+ |* a=(list)
+ => .(a (homo a))
+ ^+ a
+ =+ b=`_a`~
+ |-
+ ?~ a b
+ $(a t.a, b [i.a b])
+::
+++ welp :: concatenate
+ ~/ %welp
+ =| [* *]
+ |@
+ ++ $
+ ?~ +<-
+ +<-(. +<+)
+ +<-(+ $(+<- +<->))
+ --
+::
+++ reap :: replicate
+ ~/ %reap
+ |* [a=@ b=*]
+ |- ^- (list _b)
+ ?~ a ~
+ [b $(a (dec a))]
+::
+:: Modular arithmetic
+::
+++ fe :: modulo bloq
+ |_ a=bloq
+ ++ rol |= [b=bloq c=@ d=@] ^- @ :: roll left
+ =+ e=(sit d)
+ =+ f=(bex (sub a b))
+ =+ g=(mod c f)
+ (sit (con (lsh [b g] e) (rsh [b (sub f g)] e)))
+ ++ sum |=([b=@ c=@] (sit (add b c))) :: wrapping add
+ ++ sit |=(b=@ (end a b)) :: enforce modulo
+ --
+::
+:: Hashes
+::
+++ muk :: standard murmur3
+ ~% %muk ..muk ~
+ =+ ~(. fe 5)
+ |= [syd=@ len=@ key=@]
+ =. syd (end 5 syd)
+ =/ pad (sub len (met 3 key))
+ =/ data (welp (rip 3 key) (reap pad 0))
+ =/ nblocks (div len 4) :: intentionally off-by-one
+ =/ h1 syd
+ =+ [c1=0xcc9e.2d51 c2=0x1b87.3593]
+ =/ blocks (rip 5 key)
+ =/ i nblocks
+ =. h1 =/ hi h1 |-
+ ?: =(0 i) hi
+ =/ k1 (snag (sub nblocks i) blocks) :: negative array index
+ =. k1 (sit (mul k1 c1))
+ =. k1 (rol 0 15 k1)
+ =. k1 (sit (mul k1 c2))
+ =. hi (mix hi k1)
+ =. hi (rol 0 13 hi)
+ =. hi (sum (sit (mul hi 5)) 0xe654.6b64)
+ $(i (dec i))
+ =/ tail (slag (mul 4 nblocks) data)
+ =/ k1 0
+ =/ tlen (dis len 3)
+ =. h1
+ ?+ tlen h1 :: fallthrough switch
+ %3 =. k1 (mix k1 (lsh [0 16] (snag 2 tail)))
+ =. k1 (mix k1 (lsh [0 8] (snag 1 tail)))
+ =. k1 (mix k1 (snag 0 tail))
+ =. k1 (sit (mul k1 c1))
+ =. k1 (rol 0 15 k1)
+ =. k1 (sit (mul k1 c2))
+ (mix h1 k1)
+ %2 =. k1 (mix k1 (lsh [0 8] (snag 1 tail)))
+ =. k1 (mix k1 (snag 0 tail))
+ =. k1 (sit (mul k1 c1))
+ =. k1 (rol 0 15 k1)
+ =. k1 (sit (mul k1 c2))
+ (mix h1 k1)
+ %1 =. k1 (mix k1 (snag 0 tail))
+ =. k1 (sit (mul k1 c1))
+ =. k1 (rol 0 15 k1)
+ =. k1 (sit (mul k1 c2))
+ (mix h1 k1)
+ ==
+ =. h1 (mix h1 len)
+ |^ (fmix32 h1)
+ ++ fmix32
+ |= h=@
+ =. h (mix h (rsh [0 16] h))
+ =. h (sit (mul h 0x85eb.ca6b))
+ =. h (mix h (rsh [0 13] h))
+ =. h (sit (mul h 0xc2b2.ae35))
+ =. h (mix h (rsh [0 16] h))
+ h
+ --
+::
+++ mug :: mug with murmur3
+ ~/ %mug
+ |= a=*
+ |^ ?@ a (mum 0xcafe.babe 0x7fff a)
+ =/ b (cat 5 $(a -.a) $(a +.a))
+ (mum 0xdead.beef 0xfffe b)
+ ::
+ ++ mum
+ |= [syd=@uxF fal=@F key=@]
+ =/ wyd (met 3 key)
+ =| i=@ud
+ |- ^- @F
+ ?: =(8 i) fal
+ =/ haz=@F (muk syd wyd key)
+ =/ ham=@F (mix (rsh [0 31] haz) (end [0 31] haz))
+ ?.(=(0 ham) ham $(i +(i), syd +(syd)))
+ --
+::
+++ gor :: mug order
+ ~/ %gor
+ |= [a=* b=*]
+ ^- ?
+ =+ [c=(mug a) d=(mug b)]
+ ?: =(c d)
+ (dor a b)
+ (lth c d)
+::
+++ mor :: more mug order
+ ~/ %mor
+ |= [a=* b=*]
+ ^- ?
+ =+ [c=(mug (mug a)) d=(mug (mug b))]
+ ?: =(c d)
+ (dor a b)
+ (lth c d)
+::
+++ dor :: tree order
+ ~/ %dor
+ |= [a=* b=*]
+ ^- ?
+ ?: =(a b) &
+ ?. ?=(@ a)
+ ?: ?=(@ b) |
+ ?: =(-.a -.b)
+ $(a +.a, b +.b)
+ $(a -.a, b -.b)
+ ?. ?=(@ b) &
+ (lth a b)
+::
+++ por :: parent order
+ ~/ %por
+ |= [a=@p b=@p]
+ ^- ?
+ ?: =(a b) &
+ =| i=@
+ |-
+ ?: =(i 2)
+ :: second two bytes
+ (lte a b)
+ :: first two bytes
+ =+ [c=(end 3 a) d=(end 3 b)]
+ ?: =(c d)
+ $(a (rsh 3 a), b (rsh 3 b), i +(i))
+ (lth c d)
+::
+:: Maps
+::
+++ by
+ ~/ %by
+ =| a=(tree (pair)) :: (map)
+ =* node ?>(?=(^ a) n.a)
+ |@
+ ++ get
+ ~/ %get
+ |* b=*
+ => .(b `_?>(?=(^ a) p.n.a)`b)
+ |- ^- (unit _?>(?=(^ a) q.n.a))
+ ?~ a
+ ~
+ ?: =(b p.n.a)
+ `q.n.a
+ ?: (gor b p.n.a)
+ $(a l.a)
+ $(a r.a)
+ ::
+ ++ put
+ ~/ %put
+ |* [b=* c=*]
+ |- ^+ a
+ ?~ a
+ [[b c] ~ ~]
+ ?: =(b p.n.a)
+ ?: =(c q.n.a)
+ a
+ a(n [b c])
+ ?: (gor b p.n.a)
+ =+ d=$(a l.a)
+ ?> ?=(^ d)
+ ?: (mor p.n.a p.n.d)
+ a(l d)
+ d(r a(l r.d))
+ =+ d=$(a r.a)
+ ?> ?=(^ d)
+ ?: (mor p.n.a p.n.d)
+ a(r d)
+ d(l a(r l.d))
+ ::
+ ++ del
+ ~/ %del
+ |* b=*
+ |- ^+ a
+ ?~ a
+ ~
+ ?. =(b p.n.a)
+ ?: (gor b p.n.a)
+ a(l $(a l.a))
+ a(r $(a r.a))
+ |- ^- [$?(~ _a)]
+ ?~ l.a r.a
+ ?~ r.a l.a
+ ?: (mor p.n.l.a p.n.r.a)
+ l.a(r $(l.a r.l.a))
+ r.a(l $(r.a l.r.a))
+ ::
+ ++ apt
+ =< $
+ ~/ %apt
+ =| [l=(unit) r=(unit)]
+ |. ^- ?
+ ?~ a &
+ ?& ?~(l & &((gor p.n.a u.l) !=(p.n.a u.l)))
+ ?~(r & &((gor u.r p.n.a) !=(u.r p.n.a)))
+ ?~ l.a &
+ &((mor p.n.a p.n.l.a) !=(p.n.a p.n.l.a) $(a l.a, l `p.n.a))
+ ?~ r.a &
+ &((mor p.n.a p.n.r.a) !=(p.n.a p.n.r.a) $(a r.a, r `p.n.a))
+ ==
+ --
+::
+++ on :: ordered map
+ ~/ %on
+ |* [key=mold val=mold]
+ => |%
+ +$ item [key=key val=val]
+ --
+ ::
+ ~% %comp +>+ ~
+ |= compare=$-([key key] ?)
+ ~% %core + ~
+ |%
+ ::
+ ++ apt
+ ~/ %apt
+ |= a=(tree item)
+ =| [l=(unit key) r=(unit key)]
+ |- ^- ?
+ ?~ a %.y
+ ?& ?~(l %.y (compare key.n.a u.l))
+ ?~(r %.y (compare u.r key.n.a))
+ ?~(l.a %.y &((mor key.n.a key.n.l.a) $(a l.a, l `key.n.a)))
+ ?~(r.a %.y &((mor key.n.a key.n.r.a) $(a r.a, r `key.n.a)))
+ ==
+ ::
+ ++ get
+ ~/ %get
+ |= [a=(tree item) b=key]
+ ^- (unit val)
+ ?~ a ~
+ ?: =(b key.n.a)
+ `val.n.a
+ ?: (compare b key.n.a)
+ $(a l.a)
+ $(a r.a)
+ ::
+ ++ has
+ ~/ %has
+ |= [a=(tree item) b=key]
+ ^- ?
+ !=(~ (get a b))
+ ::
+ ++ put
+ ~/ %put
+ |= [a=(tree item) =key =val]
+ ^- (tree item)
+ ?~ a [n=[key val] l=~ r=~]
+ ?: =(key.n.a key) a(val.n val)
+ ?: (compare key key.n.a)
+ =/ l $(a l.a)
+ ?> ?=(^ l)
+ ?: (mor key.n.a key.n.l)
+ a(l l)
+ l(r a(l r.l))
+ =/ r $(a r.a)
+ ?> ?=(^ r)
+ ?: (mor key.n.a key.n.r)
+ a(r r)
+ r(l a(r l.r))
+ --
+::
+:: Sets
+::
+++ in
+ ~/ %in
+ =| a=(tree) :: (set)
+ |@
+ ++ put
+ ~/ %put
+ |* b=*
+ |- ^+ a
+ ?~ a
+ [b ~ ~]
+ ?: =(b n.a)
+ a
+ ?: (gor b n.a)
+ =+ c=$(a l.a)
+ ?> ?=(^ c)
+ ?: (mor n.a n.c)
+ a(l c)
+ c(r a(l r.c))
+ =+ c=$(a r.a)
+ ?> ?=(^ c)
+ ?: (mor n.a n.c)
+ a(r c)
+ c(l a(r l.c))
+ ::
+ ++ del
+ ~/ %del
+ |* b=*
+ |- ^+ a
+ ?~ a
+ ~
+ ?. =(b n.a)
+ ?: (gor b n.a)
+ a(l $(a l.a))
+ a(r $(a r.a))
+ |- ^- [$?(~ _a)]
+ ?~ l.a r.a
+ ?~ r.a l.a
+ ?: (mor n.l.a n.r.a)
+ l.a(r $(l.a r.l.a))
+ r.a(l $(r.a l.r.a))
+ ::
+ ++ apt
+ =< $
+ ~/ %apt
+ =| [l=(unit) r=(unit)]
+ |. ^- ?
+ ?~ a &
+ ?& ?~(l & (gor n.a u.l))
+ ?~(r & (gor u.r n.a))
+ ?~(l.a & ?&((mor n.a n.l.a) $(a l.a, l `n.a)))
+ ?~(r.a & ?&((mor n.a n.r.a) $(a r.a, r `n.a)))
+ ==
+ --
+::
+:: Jugs
+::
+++ ju
+ =| a=(tree (pair * (tree))) :: (jug)
+ |@
+ ++ get
+ |* b=*
+ =+ c=(~(get by a) b)
+ ?~(c ~ u.c)
+ ::
+ ++ del
+ |* [b=* c=*]
+ ^+ a
+ =+ d=(get b)
+ =+ e=(~(del in d) c)
+ ?~ e
+ (~(del by a) b)
+ (~(put by a) b e)
+ ::
+ ++ put
+ |* [b=* c=*]
+ ^+ a
+ =+ d=(get b)
+ (~(put by a) b (~(put in d) c))
+ --
+--
diff --git a/mar/bill.hoon b/mar/bill.hoon
new file mode 100644
index 0000000..76cef34
--- /dev/null
+++ b/mar/bill.hoon
@@ -0,0 +1,34 @@
+|_ bil=(list dude:gall)
+++ grow
+ |%
+ ++ mime `^mime`[/text/x-bill (as-octs:mimes:html hoon)]
+ ++ noun bil
+ ++ hoon
+ ^- @t
+ |^ (crip (of-wall:format (wrap-lines (spit-duz bil))))
+ ::
+ ++ wrap-lines
+ |= taz=wall
+ ^- wall
+ ?~ taz ["~"]~
+ :- (weld ":~ " i.taz)
+ %- snoc :_ "=="
+ (turn t.taz |=(t=tape (weld " " t)))
+ ::
+ ++ spit-duz
+ |= duz=(list dude:gall)
+ ^- wall
+ (turn duz |=(=dude:gall ['%' (trip dude)]))
+ --
+ ++ txt (to-wain:format hoon)
+ --
+++ grab
+ |%
+ ++ noun (list dude:gall)
+ ++ mime
+ |= [=mite len=@ud tex=@]
+ ~_ tex
+ !<((list dude:gall) (slap !>(~) (ream tex)))
+ --
+++ grad %noun
+--
diff --git a/mar/hoon.hoon b/mar/hoon.hoon
new file mode 100644
index 0000000..428e105
--- /dev/null
+++ b/mar/hoon.hoon
@@ -0,0 +1,36 @@
+:::: /hoon/hoon/mar
+ ::
+/? 310
+::
+=, eyre
+|_ own=@t
+::
+++ grow :: convert to
+ |%
+ ++ mime `^mime`[/text/x-hoon (as-octs:mimes:html own)] :: convert to %mime
+ ++ hymn
+ ;html
+ ;head
+ ;title:"Source"
+ ;script@"//cdnjs.cloudflare.com/ajax/libs/codemirror/4.3.0/codemirror.js";
+ ;script@"/lib/syntax/hoon.js";
+ ;link(rel "stylesheet", href "//cdnjs.cloudflare.com/ajax/libs/".
+ "codemirror/4.3.0/codemirror.min.css");
+ ;link/"/lib/syntax/codemirror.css"(rel "stylesheet");
+ ==
+ ;body
+ ;textarea#src:"{(trip own)}"
+ ;script:'CodeMirror.fromTextArea(src, {lineNumbers:true, readOnly:true})'
+ ==
+ ==
+ ++ txt
+ (to-wain:format own)
+ --
+++ grab
+ |% :: convert from
+ ++ mime |=([p=mite q=octs] q.q)
+ ++ noun @t :: clam from %noun
+ ++ txt of-wain:format
+ --
+++ grad %txt
+--
diff --git a/mar/kelvin.hoon b/mar/kelvin.hoon
new file mode 100644
index 0000000..7f1b409
--- /dev/null
+++ b/mar/kelvin.hoon
@@ -0,0 +1,28 @@
+|_ kal=waft:clay
+++ grow
+ |%
+ ++ mime `^mime`[/text/x-kelvin (as-octs:mimes:html hoon)]
+ ++ noun kal
+ ++ hoon
+ %+ rap 3
+ %+ turn
+ %+ sort
+ ~(tap in (waft-to-wefts:clay kal))
+ |= [a=weft b=weft]
+ ?: =(lal.a lal.b)
+ (gte num.a num.b)
+ (gte lal.a lal.b)
+ |= =weft
+ (rap 3 '[%' (scot %tas lal.weft) ' ' (scot %ud num.weft) ']\0a' ~)
+ ::
+ ++ txt (to-wain:format hoon)
+ --
+++ grab
+ |%
+ ++ noun waft:clay
+ ++ mime
+ |= [=mite len=@ud tex=@]
+ (cord-to-waft:clay tex)
+ --
+++ grad %noun
+--
diff --git a/mar/mime.hoon b/mar/mime.hoon
new file mode 100644
index 0000000..83b4dae
--- /dev/null
+++ b/mar/mime.hoon
@@ -0,0 +1,32 @@
+::
+:::: /hoon/mime/mar
+ ::
+/? 310
+::
+|_ own=mime
+++ grow
+ ^?
+ |%
+ ++ jam `@`q.q.own
+ --
+::
+++ grab :: convert from
+ ^?
+ |%
+ ++ noun mime :: clam from %noun
+ ++ tape
+ |=(a=_"" [/application/x-urb-unknown (as-octt:mimes:html a)])
+ --
+++ grad
+ ^?
+ |%
+ ++ form %mime
+ ++ diff |=(mime +<)
+ ++ pact |=(mime +<)
+ ++ join |=([mime mime] `(unit mime)`~)
+ ++ mash
+ |= [[ship desk mime] [ship desk mime]]
+ ^- mime
+ ~|(%mime-mash !!)
+ --
+--
diff --git a/mar/noun.hoon b/mar/noun.hoon
new file mode 100644
index 0000000..ff5443e
--- /dev/null
+++ b/mar/noun.hoon
@@ -0,0 +1,22 @@
+::
+:::: /hoon/noun/mar
+ ::
+/? 310
+!:
+:::: A minimal noun mark
+|_ non=*
+++ grab |%
+ ++ noun *
+ --
+++ grow |%
+ ++ mime [/application/x-urb-jam (as-octs:mimes:html (jam non))]
+ --
+++ grad
+ |%
+ ++ form %noun
+ ++ diff |=(* +<)
+ ++ pact |=(* +<)
+ ++ join |=([* *] *(unit *))
+ ++ mash |=([[ship desk *] [ship desk *]] `*`~|(%noun-mash !!))
+ --
+--
diff --git a/mar/ship.hoon b/mar/ship.hoon
new file mode 100644
index 0000000..176bcad
--- /dev/null
+++ b/mar/ship.hoon
@@ -0,0 +1,20 @@
+|_ s=ship
+++ grad %noun
+++ grow
+ |%
+ ++ noun s
+ ++ json s+(scot %p s)
+ ++ mime
+ ^- ^mime
+ [/text/x-ship (as-octt:mimes:html (scow %p s))]
+
+ --
+++ grab
+ |%
+ ++ noun ship
+ ++ json (su:dejs:format ;~(pfix sig fed:ag))
+ ++ mime
+ |= [=mite len=@ tex=@]
+ (slav %p (snag 0 (to-wain:format tex)))
+ --
+--
diff --git a/sur/zodiac.hoon b/sur/zodiac.hoon
new file mode 100644
index 0000000..b71eae9
--- /dev/null
+++ b/sur/zodiac.hoon
@@ -0,0 +1,7 @@
+/+ metalib=metamask
+|%
++$ versioned-state
+ $: %0
+ sessions=sess:metalib
+==
+--
diff --git a/sys.kelvin b/sys.kelvin
new file mode 100644
index 0000000..1ec239e
--- /dev/null
+++ b/sys.kelvin
@@ -0,0 +1 @@
+[%zuse 410]
diff --git a/web/index.hoon b/web/index.hoon
new file mode 100644
index 0000000..9aede2e
--- /dev/null
+++ b/web/index.hoon
@@ -0,0 +1,465 @@
+|_ bowl:gall
+++ auth-styling
+ '''
+ @import url("https://rsms.me/inter/inter.css");
+ @font-face {
+ font-family: "Source Code Pro";
+ src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-regular.woff");
+ font-weight: 400;
+ font-display: swap;
+ }
+ :root {
+ --red-soft: #FFEFEC;
+ --red: #FF6240;
+ --gray-100: #E5E5E5;
+ --gray-400: #999999;
+ --gray-800: #333333;
+ --white: #FFFFFF;
+ }
+ html {
+ font-family: Inter, sans-serif;
+ height: 100%;
+ margin: 0;
+ width: 100%;
+ background: var(--white);
+ color: var(--gray-800);
+ -webkit-font-smoothing: antialiased;
+ line-height: 1.5;
+ font-size: 16px;
+ font-weight: 600;
+ display: flex;
+ flex-flow: row nowrap;
+ justify-content: center;
+ }
+ body {
+ display: flex;
+ flex-flow: column nowrap;
+ justify-content: center;
+ max-width: 300px;
+ padding: 1rem;
+ width: 100%;
+ }
+ body.local #eauth,
+ body.eauth #local {
+ display: none;
+ min-height: 100%;
+ }
+ #eauth input {
+ /*NOTE dumb hack to get approx equal height with #local */
+ margin-bottom: 15px;
+ }
+ body nav {
+ background: var(--gray-100);
+ border-radius: 2rem;
+ display: flex;
+ justify-content: space-around;
+ overflow: hidden;
+ margin-bottom: 1rem;
+ }
+ body nav div {
+ width: 50%;
+ padding: 0.5rem 1rem;
+ text-align: center;
+ cursor: pointer;
+ }
+ body.local nav div.local,
+ body.eauth nav div.eauth {
+ background: var(--gray-800);
+ color: var(--white);
+ cursor: default;
+ }
+ nav div.local {
+ border-right: none;
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+ }
+ nav div.eauth {
+ border-left: none;
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ }
+ body > *,
+ form > input {
+ width: 100%;
+ }
+ form {
+ display: flex;
+ flex-flow: column;
+ align-items: flex-start;
+ }
+ input {
+ background: var(--gray-100);
+ border: 2px solid transparent;
+ padding: 0.5rem;
+ border-radius: 0.5rem;
+ font-size: inherit;
+ color: var(--gray-800);
+ box-shadow: none;
+ width: 100%;
+ }
+ input:disabled {
+ background: var(--gray-100);
+ color: var(--gray-400);
+ }
+ input:focus {
+ outline: none;
+ background: var(--white);
+ border-color: var(--gray-400);
+ }
+ input:invalid:not(:focus) {
+ background: var(--red-soft);
+ border-color: var(--red);
+ outline: none;
+ color: var(--red);
+ }
+ button[type=submit] {
+ margin-top: 1rem;
+ }
+ button[type=submit], a.button {
+ font-size: 1rem;
+ padding: 0.5rem 1rem;
+ border-radius: 0.5rem;
+ background: var(--gray-800);
+ color: var(--white);
+ border: none;
+ font-weight: 600;
+ text-decoration: none;
+ }
+ input:invalid ~ button[type=submit] {
+ border-color: currentColor;
+ background: var(--gray-100);
+ color: var(--gray-400);
+ pointer-events: none;
+ }
+ span.guest, span.guest a {
+ color: var(--gray-400);
+ }
+ span.failed {
+ display: flex;
+ flex-flow: row nowrap;
+ height: 1rem;
+ align-items: center;
+ margin-top: 0.875rem;
+ color: var(--red);
+ }
+ span.failed svg {
+ height: 1rem;
+ margin-right: 0.25rem;
+ }
+ span.failed path {
+ fill: transparent;
+ stroke-width: 2px;
+ stroke-linecap: round;
+ stroke: currentColor;
+ }
+ .mono {
+ font-family: 'Source Code Pro', monospace;
+ }
+ @media all and (prefers-color-scheme: dark) {
+ :root {
+ --white: #000000;
+ --gray-800: #E5E5E5;
+ --gray-400: #808080;
+ --gray-100: #333333;
+ --red-soft: #7F1D1D;
+ }
+ }
+ @media screen and (min-width: 30em) {
+ html {
+ font-size: 14px;
+ }
+ }
+ '''
+++ favicon %+
+ weld "<svg width='10' height='10' viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'>"
+ "<circle r='3.09' cx='5' cy='5' /></svg>"
+++ landscape ^- manx
+=/ eauth=(unit ?) [~ %|]
+=/ failed=? .n
+=/ identity=identity:eyre [%ours ~]
+=/ redirect-url=(unit @t) ~
+=/ redirect-str ?~(redirect-url "" (trip u.redirect-url))
+ ;html
+ ;head
+ ;meta(charset "utf-8");
+ ;meta(name "viewport", content "width=device-width, initial-scale=1, shrink-to-fit=no");
+ ;link(rel "icon", type "image/svg+xml", href (weld "data:image/svg+xml;utf8," favicon));
+ ;title:"Urbit"
+ ;style:"{(trip auth-styling)}"
+ ;style:"{?^(eauth "" "nav \{ display: none; }")}"
+ ;script:"our = '{(scow %p our)}';"
+ ;script:'''
+ let name, pass;
+ function setup(isEauth) {
+ name = document.getElementById('name');
+ pass = document.getElementById('pass');
+ if (isEauth) goEauth(); else goLocal();
+ }
+ function goLocal() {
+ document.body.className = 'local';
+ pass.focus();
+ }
+ function goEauth() {
+ document.body.className = 'eauth';
+ name.focus();
+ }
+ function doEauth() {
+ if (name.value == our) {
+ event.preventDefault();
+ goLocal();
+ }
+ }
+ '''
+ ==
+ ;body
+ =class "{?:(=(`& eauth) "eauth" "local")}"
+ =onload "setup({?:(=(`& eauth) "true" "false")})"
+ ;div#local
+ ;p:"Urbit ID"
+ ;input(value "{(scow %p our)}", disabled "true", class "mono");
+ ;+ ?: =(%ours -.identity)
+ ;div
+ ;p:"Already authenticated"
+ ;a.button/"{(trip (fall redirect-url '/'))}":"Continue"
+ ==
+ ;form(action "/~/login", method "post", enctype "application/x-www-form-urlencoded")
+ ;p:"Access Key"
+ ;input
+ =type "password"
+ =name "password"
+ =id "pass"
+ =placeholder "sampel-ticlyt-migfun-falmel"
+ =class "mono"
+ =required "true"
+ =minlength "27"
+ =maxlength "27"
+ =pattern "((?:[a-z]\{6}-)\{3}(?:[a-z]\{6}))";
+ ;input(type "hidden", name "redirect", value redirect-str);
+ ;+ ?. failed ;span;
+ ;span.failed
+ ;svg(xmlns "http://www.w3.org/2000/svg", viewBox "0 0 16 16")
+ ;path(d "m8 8 4-4M8 8 4 4m4 4-4 4m4-4 4 4");
+ ==
+ Key is incorrect
+ ==
+ ;button(type "submit"):"Continue"
+ ==
+ ==
+ ;div#eauth
+ ;form(action "/~/login", method "post", onsubmit "return doEauth()")
+ ;p:"Urbit ID Metamask Login"
+ ;input.mono
+ =name "name"
+ =id "name"
+ =placeholder "{(scow %p our)}"
+ =required "true"
+ =minlength "4"
+ =maxlength "57"
+ =pattern "~((([a-z]\{6})\{1,2}-\{0,2})+|[a-z]\{3})";
+ ;p
+ ; You will be redirected to your own web interface to authorize
+ ; logging in to
+ ;span.mono:"{(scow %p our)}"
+ ; .
+ ==
+ ;input(type "hidden", name "redirect", value redirect-str);
+ ;button(name "eauth", type "submit"):"Continue"
+ ==
+ ==
+ ;* ?: ?=(%ours -.identity) ~
+ =+ as="proceed as{?:(?=(%fake -.identity) " guest" "")}"
+ ;+ ;span.guest.mono
+ ; Or try to
+ ;a/"{(trip (fall redirect-url '/'))}":"{as}"
+ ; .
+ ==
+ ==
+ ;script:'''
+ var failSpan = document.querySelector('.failed');
+ if (failSpan) {
+ document.querySelector("input[type=password]")
+ .addEventListener('keyup', function (event) {
+ failSpan.style.display = 'none';
+ });
+ }
+ '''
+ ==
+++ $
+=/ redirect-str "/forum"
+;html
+ ;head
+ ;meta(charset "utf-8");
+ ;meta(name "viewport", content "width=device-width, initial-scale=1, shrink-to-fit=no");
+ ;link(rel "icon", type "image/svg+xml", href (weld "data:image/svg+xml;utf8," favicon));
+ ==
+ ;body
+ ;main#login-page.white
+ ;h1.tc:"Login"
+ ;form#form(action "/~/login", method "POST")
+ ;h2.tc: Urbit OS (Azimuth via Arvo)
+ ;input.mono(type "text")
+ =name "name"
+ =id "name"
+ =placeholder "~sampel-palnet"
+ =required "true"
+ =minlength "4"
+ =maxlength "14"
+ =pattern "~((([a-z]\{6})\{1,2}-\{0,2})+|[a-z]\{3})";
+ ;input(type "hidden", name "redirect", value redirect-str);
+ ;button(name "eauth", type "submit"):"Login via Ship »"
+ ==
+ ;button(id "mauth"):"Login via 🦊MetaMask »"
+ ;script(type "module"):"{metamask-script}"
+ :: ;script(type "importmap"):"{import-script}"
+ ;div(id "wallet-points");
+ ;div(id "spinner");
+ ;h2.tc: Join the Urbit Network
+ ;div.tc.nudge
+ ;a.button/"https://redhorizon.com/join/2d55b768-a5f4-45cf-a4e5-a4302e05a1f9":"Get Urbit ID »"
+ ;p:"If you don't have an Urbit ID, get one for free from Red Horizon."
+ ==
+ ==
+ ==
+==
+++ import-script
+ ^~
+ %- trip
+'''
+ {"imports": {
+ "ethers": "/node_modules/ethers/"
+ }}
+'''
+++ metamask-script
+ ^~
+ %- trip
+'''
+ import { ethers } from "https://cdnjs.cloudflare.com/ajax/libs/ethers/6.7.0/ethers.min.js";
+
+
+ const AZIMUTH_ADDRESS = "0x223c067F8CF28ae173EE5CafEa60cA44C335fecB";
+ const AZIMUTH_ABI_MINI = [{
+ "constant": true,
+ "inputs": [{ "name": "_whose", "type": "address" }],
+ "name": "getOwnedPoints",
+ "outputs": [{ "name": "ownedPoints", "type": "uint32[]" }],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ }];
+
+ window.addEventListener("DOMContentLoaded", () => {
+
+ const spinner = document.getElementById("spinner");
+ const pointsDiv = document.getElementById("wallet-points");
+ const metamaskButton = document.getElementById("mauth");
+ metamaskButton.addEventListener("click", (event) => {
+ event.preventDefault(); // Prevent the form from submitting the default way
+ metamaskButton.disabled = true;
+ getProvider().then(provider => {
+ getPoints(provider).then(points => {
+ console.log({points})
+ points.forEach(point => {
+ const div = document.createElement("div");
+ div.innerText = `${point}`;
+ div.addEventListener("click", e => {
+ if (spinner.innerText) return
+ spinner.innerText = "Logging in..."
+ metamaskLogin(provider.address, point).then(res => {
+ spinner.innerText = `${res}`
+ })
+ })
+ pointsDiv.appendChild(div);
+ })
+ // why doesn't this work wtf
+ // for (const points of points){
+ // console.log({point})
+ // }
+
+ }).catch(e => {
+ metamaskButton.disabled = false;
+ })
+ })
+ });
+ });
+
+ async function fetchSecret() {
+ try {
+ const response = await fetch('/forum/metamask');
+ if (response.ok) {
+ const data = await response.json();
+ return data.challenge;
+ } else {
+ throw new Error('Failed to retrieve secret');
+ }
+ } catch (error) {
+ console.error('Error fetching secret:', error);
+ }
+ }
+
+
+ async function getProvider(){
+ if (typeof window.ethereum !== 'undefined') {
+ try {
+ const accounts = await window.ethereum.request({ method: "eth_requestAccounts" });
+ const account = accounts[0];
+ console.log("logged with metamask on account", account);
+ const provider = new ethers.BrowserProvider(window.ethereum)
+ const t1 = await provider.getBalance(account)
+ console.log({t1})
+ const signer = await provider.getSigner();
+ console.log({provider, signer})
+ return signer
+
+ } catch (error) {
+ alert("MetaMask initialization failed");
+ }
+ } else {
+ alert("MetaMask is not installed. Please install it to continue.");
+ }
+
+ }
+
+ async function metamaskLogin(account, point){
+ // Fetch the secret from the server
+ const secret = await fetchSecret();
+ console.log({secret});
+ const signature = await window.ethereum.request({
+ method: "personal_sign",
+ params: [secret, account],
+ });
+
+ const response = await fetch('/forum/auth', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ who: point,
+ secret: secret,
+ address: account,
+ signature: signature
+ }),
+ });
+
+ if (response.ok) {
+ // location.reload();
+ // window.location.replace('/forum');
+ return "done!"
+ } else {
+ alert("Login failed. Please try again.");
+ }
+
+ }
+
+ async function getPoints(provider){
+ const address = provider.address;
+ const contract = new ethers.Contract(
+ AZIMUTH_ADDRESS,
+ AZIMUTH_ABI_MINI,
+ provider,
+ );
+ const res = await contract.getOwnedPoints(address);
+ return res.toArray();
+ }
+'''
+--
diff --git a/web/router.hoon b/web/router.hoon
new file mode 100644
index 0000000..fdb6c43
--- /dev/null
+++ b/web/router.hoon
@@ -0,0 +1,101 @@
+/- sur=zodiac
+/+ server, metamask, sr=sortug
+/= login-page /web/index
+
+|_ [=bowl:gall eyre-id=@ta req=inbound-request:eyre state=versioned-state:sur]
+
+++ session-timeout ~d300
+++ session-cookie-string
+ |= [session=@uv extend=?]
+ ^- @t
+ %- crip
+ =; max-age=tape
+ :: "urbauth-{(scow %p src.bowl)}={(scow %uv session)}; Path=/; Max-Age={max-age}"
+ "ucm-{(scow %p src.bowl)}={(scow %uv session)}; Path=/; Max-Age={max-age}"
+ %+ scow:parsing:sr %ud
+ ?. extend 0
+ (div (msec:milly session-timeout) 1.000)
+
+++ get-file-at
+ |= [base=path file=path ext=@ta]
+ ^- (unit octs)
+ =/ =path
+ :* (scot %p our.bowl)
+ q.byk.bowl
+ (scot %da now.bowl)
+ (snoc (weld base file) ext)
+ ==
+ ?. .^(? %cu path) ~
+ %- some
+ %- as-octs:mimes:html
+ .^(@ %cx path)
+
+++ handle-get-request
+ |= [headers=header-list:http request-line:server]
+ ^- simple-payload:http
+ ?~ ext $(ext `%html, site [%index ~])
+ ?: ?=([%zodiac *] site) $(site +.site)
+ :: serve dynamic session.js
+ ::
+ ?: =([/session `%js] [site ext])
+ %- js-response:gen:server
+ %- as-octt:mimes:html
+ """
+ window.ship = '{(scow %p src.bowl)}';
+ """
+ =/ file=(unit octs)
+ (get-file-at /web site u.ext)
+ ?~ file ~& "file not found" not-found:gen:server
+ ?+ u.ext not-found:gen:server
+ %html (html-response:gen:server u.file)
+ %js (js-response:gen:server u.file)
+ %css (css-response:gen:server u.file)
+ %png (png-response:gen:server u.file)
+ %woff (woff-response:gen:server u.file)
+ %woff2 (woff2-response:gen:server u.file)
+ ==
+
+
+++ ebail
+ ^- (list card:agent:gall)
+ (send-response pbail)
+++ egive
+ |= pl=simple-payload:http
+ ^- (list card:agent:gall)
+ (send-response pl)
+++ pbail
+ %- html-response:gen:server
+ %- manx-to-octs:server
+ manx-bail
+++ manx-bail ^- manx ;div:"404"
+++ manx-payload
+ |= =manx
+ ^- simple-payload:http
+ %- html-response:gen:server
+ %- manx-to-octs:server manx
+
+++ send-response
+ |= =simple-payload:http
+ =/ cookie ['set-cookie' (session-cookie-string 0vublog .y)]
+ =. headers.response-header.simple-payload
+ [cookie headers.response-header.simple-payload]
+ %+ give-simple-payload:app:server eyre-id simple-payload
+
+
+++ route
+ =/ metalib ~(. metamask [sessions.state bowl])
+ =/ rl (parse-request-line:server url.request.req)
+ =/ sitepath=path /[(head site.rl)]
+ =/ pat=(pole knot) site.rl
+ ?+ pat ebail
+ [%zodiac %metamask rest=*] (serve-metamask-challenge:metalib eyre-id)
+ [%zodiac %auth rest=*] (process-metamask-auth:metalib eyre-id body.request.req)
+ [%zodiac ~] (send-response (manx-payload (login-page bowl)))
+ :: [site=@t *] (send-response (handle-get-request header-list.request.req rl))
+ ==
+
+++ eyre-binding-card
+|= =path
+ ~& > adding-binding=[path dap.bowl]
+ [%pass /eyre/connect %arvo %e %connect [~ path] dap.bowl]
+--