L
ling-wallet
↓ 0 downloadsLing Wallet: a post-quantum privacy-coin wallet, full node and HyperTorus miner for LNG
Keywords wallet, cryptocurrency, lng, node, miner, ling
lingfu install ling-walletCode / Files (116)
ling/ui/send.ling
# send.ling — SEND: compose with a live recipient lookup, review (fingerprint
# check), authorize with the password, then signing / broadcast progress.
fn send_phase() { return st_get("send_phase", "compose") }
fn send_go(p) {
st_set("send_phase", p)
st_set("send_err", "")
focus_next_frame()
}
fn send_reset() {
tf_clear("send_to")
tf_clear("send_amt")
tf_clear("send_memo")
tf_clear("send_pw")
st_set("send_phase", "compose")
st_set("send_err", "")
st_set("send_lookup", [])
st_set("send_lookup_kind", "")
st_set("send_p", ())
}
# The lookup result when it is verified for the current address, else [].
fn send_verified() {
bind l = st_get("send_lookup", [])
if list_get(l, 0) == "verified" && list_get(l, 1) == tf_get("send_to") { return l }
return []
}
fn send_draw(ax, ay, aw, ah) {
bind ph = send_phase()
bind idx = 0
if ph == "review" { bind idx = 1 }
if ph == "authorize" { bind idx = 2 }
if ph == "progress" { bind idx = 3 }
steps(ax, ay - 8, ["Compose", "Review", "Authorize", "Broadcast"], idx, C_ORANGE)
bind y = ay + 40
bind h = ah - 40
if ph == "compose" { send_compose(ax, y, aw, h) }
if ph == "review" { send_review(ax, y, aw, h) }
if ph == "authorize" { send_authorize(ax, y, aw, h) }
if ph == "progress" { send_progress(ax, y, aw, h) }
}
fn send_compose(ax, ay, aw, ah) {
bind s = d_summary()
bind unlocked = 0
if s != () { bind unlocked = s.unlocked }
bind side_w = 300
bind pw = aw - side_w - 24
bind inn = panel(ax, ay, pw, ah, "RECIPIENT & AMOUNT", C_ORANGE)
bind x = list_get(inn, 0)
bind iw = list_get(inn, 2)
bind y = list_get(inn, 1) + 24
bind fw = iw - 112
bind r = labeled("send_to", x, y, fw, 44, "Pay to (short address)", "lng1…", C_ORANGE, "address")
if btn(x + fw + 12, y, 100, 44, "Paste", C_ORANGE) { tf_set("send_to", tf_filter("address", clip_paste())) }
bind to = tf_get("send_to")
if list_get(r, 0) { st_set("send_err", "") }
if to == "" {
st_set("send_lookup", [])
st_set("send_lookup_kind", "")
} else {
bind l = be_lookup(to)
st_set("send_lookup", l)
st_set("send_lookup_kind", list_get(l, 0))
}
bind y = y + 56
bind sy = y
fill(x, sy, iw, 84, C_BG_DEEP)
outline(x, sy, iw, 84, C_LINE_FAINT, 1)
bind lk = st_get("send_lookup", [])
bind kind = list_get(lk, 0)
if to == "" {
t_mid(F_UI, 16, x + 20, sy + 42, "Paste the address you were given. Its key bundle is checked before you can pay.", C_TEXT_FAINT)
} else if kind == "pending" {
spinner(x + 40, sy + 42, 14, u_now(), C_ORANGE)
t_text(F_UI, 18, x + 72, sy + 38, "Looking up the recipient…", C_TEXT_STRONG)
t_text(F_UI, 15, x + 72, sy + 60, "Fetching their key bundle from peers.", C_TEXT_DIM)
} else if kind == "verified" {
fill(x, sy, 3, 84, C_GREEN)
identicon(x + 16, sy + 12, 60, list_get(lk, 2))
bind vx = x + 96
label(vx, sy + 26, "Fingerprint", C_TEXT_DIM)
draw_fingerprint(vx + 104, sy + 28, 26, fingerprint(list_get(lk, 2)), C_TEXT_STRONG)
icon_draw("check", vx, sy + 56, C_GREEN)
badge(vx + 24, sy + 56, "Verified", C_GREEN, true)
t_right(F_MONO, 18, x + iw - 16, sy + 61, "id " + list_get(lk, 3), C_TEXT_FAINT)
} else {
fill(x, sy, 3, 84, C_RED)
icon_draw("warn", x + 20, sy + 42, C_RED)
t_text(F_UI, 18, x + 52, sy + 38, "Can't pay this address", lift(C_RED, 0.2))
t_text(F_UI, 15, x + 52, sy + 60, list_get(lk, 1), C_TEXT)
}
bind y = y + 84 + 40
bind ra = field_amount(x, y, fw)
if btn(x + fw + 12, y, 100, 44, "Max", C_ORANGE) {
bind fee = 0
if len(send_verified()) > 0 {
bind pr = be_prepare(to, unlocked, tf_get("send_memo"))
bind fee = match pr {
ok(p) => p.fee,
bad(e) => 0,
}
}
tf_set("send_amt", format_lng(max(0, unlocked - fee)))
}
bind amt_txt = tf_get("send_amt")
bind amt = parse_lng(amt_txt)
bind y = y + 64
bind over = amt > unlocked
t_text(F_UI, 15, x, y, "Available " + format_lng(unlocked) + " LNG", sel(over, C_RED, C_TEXT_DIM))
if amt_txt != "" && amt < 0 { t_right(F_UI, 15, x + fw, y, "Up to 8 decimal places", C_RED) }
bind y = y + 36
bind rm = labeled("send_memo", x, y, iw, 44, "Memo (optional)", "e.g. invoice number", C_ORANGE, "memo")
bind y = y + 68
if st_get("send_err", "") != "" { t_text(F_UI, 16, x, y, st_get("send_err", ""), C_RED) }
bind verified = len(send_verified()) > 0
bind can_send = verified && amt > 0 && amt <= unlocked
bind by = ay + ah - 20 - 44 - 4
bind submit = list_get(r, 1) || list_get(ra, 1) || list_get(rm, 1)
if button(x + iw - 200, by, 200, 44, "Review", C_ORANGE, "primary", can_send, true, "") || (submit && can_send) {
bind pr = be_prepare(to, amt, tf_get("send_memo"))
match pr {
ok(p) => send_prepared(p),
bad(e) => st_set("send_err", e),
}
}
if !can_send && to != "" {
t_right(F_UI, 15, x + iw - 216, by + 27, sel(verified, "Enter an amount you can cover", "Waiting for a verified recipient"), C_TEXT_FAINT)
}
bind inn2 = panel(ax + pw + 24, ay, side_w, ah, "BEFORE YOU SEND", C_ORANGE)
bind sx = list_get(inn2, 0)
bind sw = list_get(inn2, 2)
bind cy = list_get(inn2, 1) + 12
bind names = ["Address", "Verified recipient", "Amount"]
bind subs = ["Paste the short address you were given.", "Its key bundle matches the address.", "Within your unlocked balance."]
bind oks = [to != "", verified, can_send]
bind i = 0
while i < 3 {
bind good = list_get(oks, i)
if good { cross(sx + 2, cy + 2, 20, 20, C_GREEN, 3, 0.6) }
outline(sx, cy, 24, 24, sel(good, C_GREEN, C_LINE), 1)
t_text(F_UI, 18, sx + 38, cy + 17, list_get(names, i), sel(good, C_TEXT_STRONG, C_TEXT))
bind hh = t_para(F_UI, 15, sx + 38, cy + 26, sw - 38, 18, list_get(subs, i), C_TEXT_DIM)
bind cy = cy + 30 + hh + 16
bind i = i + 1
}
bind cy = cy + 8
hline(sx, cy, sw, C_LINE_FAINT, 1)
t_para(F_UI, 15, sx, cy + 16, sw, 19, "Payments can't be reversed. Check the fingerprint with the recipient on the next step.", C_TEXT_FAINT)
}
fn field_amount(x, y, fw) {
bind r = labeled_pad("send_amt", x, y, fw, 44, "Amount", "0.0", C_ORANGE, "amount", 44)
t_mid(F_UI, 16, x + fw - 44, y + 22, "LNG", C_TEXT_DIM)
return r
}
fn labeled_pad(id, x, y, w, h, text, placeholder, accent, kind, pad) {
label(x, y - 9, text, C_TEXT_DIM)
return field(id, x, y, w, h, placeholder, accent, kind, pad)
}
fn send_prepared(p) {
st_set("send_p", p)
send_go("review")
}
fn send_review(ax, ay, aw, ah) {
bind p = st_get("send_p", ())
bind inn = panel(ax, ay, aw, ah, "REVIEW", C_ORANGE)
bind x = list_get(inn, 0)
bind iw = list_get(inn, 2)
bind lw = floor(iw * 52 / 100)
bind top = list_get(inn, 1) + 8
bind rx = x + lw + 32
bind rw = iw - lw - 32
label(x, top + 12, "Recipient", C_TEXT_DIM)
identicon(x, top + 28, 112, p.address_hash)
bind lines = grouped(p.to, 3)
bind i = 0
while i < len(lines) {
t_text(F_MONO, 24, x + 132, top + 50 + i * 24, list_get(lines, i), C_TEXT_STRONG)
bind i = i + 1
}
bind fy = top + 28 + 112 + 40
label(x, fy, "Fingerprint", C_TEXT_DIM)
draw_fingerprint(x, fy + 38, 40, fingerprint(p.address_hash), C_TEXT_STRONG)
bind y = fy + 64
bind vy = y + 24 + callout(x, y, lw, C_ORANGE, "eye", "Compare this fingerprint with the recipient", "Ask them to read theirs aloud (call or a second app). If any group differs, cancel: you'd be paying someone else.")
bind lk = send_verified()
if len(lk) > 0 {
icon_draw("check", x, vy + 8, C_GREEN)
t_text(F_UI, 16, x + 26, vy + 14, "Verified", C_TEXT)
label(x + 26, vy + 42, "Identity key", C_TEXT_DIM)
t_text(F_MONO, 22, x + 136, vy + 43, list_get(lk, 3), C_TEXT)
}
bind y = top
bind y = kv(rx, y, rw, "Amount", format_lng(p.amount) + " LNG", C_TEXT_STRONG, true)
bind y = kv(rx, y, rw, "Network fee", format_lng(p.fee) + " LNG", C_TEXT, true)
bind y = kv(rx, y, rw, "Total", format_lng(p.amount + p.fee) + " LNG", lift(C_ORANGE, 0.2), true)
bind y = kv(rx, y, rw, "Size", num_str(round(p.size_bytes / 100) / 10) + " KB", C_TEXT, true)
bind y = kv(rx, y, rw, "Ring size", num_str(p.ring_size) + " members", C_TEXT, true)
bind y = kv(rx, y, rw, "Inputs / outputs", num_str(p.inputs) + " in · " + num_str(p.outputs) + " out", C_TEXT, true)
bind memo = tf_get("send_memo")
bind y = kv(rx, y, rw, "Memo", sel(memo == "", "-", memo), C_TEXT, false) + 8
if p.reduced {
callout(rx, y, rw, C_RED, "warn", "Reduced ring", "Not enough outputs exist yet for some denominations, so the ring is smaller than 11.")
} else {
icon_draw("shield", rx, y + 10, C_GREEN)
t_text(F_UI, 15, rx + 26, y + 15, "Full-size rings for every input.", C_TEXT_DIM)
}
bind by = ay + ah - 20 - 44 - 4
if button(x, by, 140, 44, "Edit", C_TEXT_DIM, "plain", true, false, "back") || u_key("escape") {
send_go("compose")
return ()
}
if button(rx + rw - 220, by, 220, 44, "Fingerprint matches", C_ORANGE, "primary", true, true, "check") {
tf_clear("send_pw")
send_go("authorize")
}
}
fn send_card(ax, ay, aw, title) {
bind w = min(aw, 640)
bind x = ax + floor((aw - w) / 2)
return panel(x, ay, w, 420, title, C_ORANGE)
}
fn send_authorize(ax, ay, aw, ah) {
bind p = st_get("send_p", ())
bind inn = send_card(ax, ay, aw, "AUTHORIZE")
bind x = list_get(inn, 0)
bind iw = list_get(inn, 2)
bind y = list_get(inn, 1) + 16
label(x, y, "You are sending", C_TEXT_DIM)
big_amount(x, y + 44, 34, p.amount, C_TEXT_STRONG)
bind y = y + 76
identicon(x, y, 40, p.address_hash)
t_text(F_MONO, 22, x + 54, y + 17, short_mid(p.to, 12, 6), C_TEXT)
draw_fingerprint(x + 54, y + 38, 20, fingerprint(p.address_hash), C_TEXT_DIM)
bind y = y + 88
bind r = labeled("send_pw", x, y, iw, 44, "Wallet password", "", C_ORANGE, "password")
bind y = y + 64
if st_get("send_err", "") != "" {
t_text(F_UI, 16, x, y + 4, st_get("send_err", ""), C_RED)
} else {
t_text(F_UI, 15, x, y + 4, "Your password unlocks the spend key to sign this payment.", C_TEXT_FAINT)
}
bind by = ay + 420 - 20 - 44 - 4
if button(x, by, 140, 44, "Back", C_TEXT_DIM, "plain", true, false, "back") || u_key("escape") {
tf_clear("send_pw")
send_go("review")
return ()
}
if button(x + iw - 200, by, 200, 44, "Sign & send", C_ORANGE, "primary", true, true, "") || list_get(r, 1) {
bind res = be_confirm(p.id, tf_get("send_pw"))
match res {
ok(v) => send_signed(),
bad(e) => send_auth_bad(e),
}
}
}
fn send_signed() {
tf_clear("send_pw")
st_set("send_err", "")
st_set("send_phase", "progress")
}
fn send_auth_bad(e) {
st_set("send_err", e)
st_set("tf_all", "send_pw")
}
fn send_progress(ax, ay, aw, ah) {
bind p = st_get("send_p", ())
bind inn = send_card(ax, ay, aw, "BROADCAST")
bind x = list_get(inn, 0)
bind iw = list_get(inn, 2)
bind y0 = list_get(inn, 1)
bind pr = be_progress(p.id)
bind kind = list_get(pr, 0)
bind done = false
if kind == "sent" {
bind cx = x + floor(iw / 2) - 36
bind cy = y0 + 52
outline(cx, cy, 72, 72, C_GREEN, 1)
brackets(cx - 4, cy - 4, 80, 80, 14, lift(C_GREEN, 0.3), 1)
cross(cx + 3, cy + 3, 66, 66, C_GREEN, 3, 0.3)
bind mx = cx + 36
bind my = cy + 36
bind o = 0
while o < 3 {
line(mx - 16, my + o - 1, mx - 5, my + 10 + o, C_TEXT_STRONG, 1)
line(mx - 5, my + 10 + o, mx + 17, my - 12 + o, C_TEXT_STRONG, 1)
bind o = o + 1
}
t_center(F_HEAD, 20, x, y0 + 150, iw, 30, "SENT", C_TEXT_STRONG)
t_center(F_UI, 17, x, y0 + 186, iw, 24, format_lng(p.amount) + " LNG is on its way.", C_TEXT_DIM)
bind id = list_get(pr, 1)
label(x, y0 + 240, "Transaction id", C_TEXT_DIM)
t_text(F_MONO, 20, x, y0 + 266, substr(id, 0, 32), C_TEXT)
t_text(F_MONO, 20, x, y0 + 286, substr(id, 32, 32), C_TEXT)
if copy_btn(x + iw - copy_btn_w("Copy txid"), y0 + 252, "Copy txid", C_ORANGE) { clip_copy(id, "Transaction id") }
bind done = true
} else if kind == "failed" {
callout(x, y0 + 40, iw, C_RED, "warn", "Send failed", list_get(pr, 1))
bind done = true
} else {
spinner(x + floor(iw / 2), y0 + 90, 36, u_now(), C_ORANGE)
t_center(F_HEAD, 20, x, y0 + 150, iw, 30, "SIGNING", C_TEXT_STRONG)
t_center(F_UI, 17, x, y0 + 190, iw, 24, "Signing " + num_str(p.inputs) + " inputs, " + num_str(p.ring_size) + " ring members each…", C_TEXT_DIM)
t_center(F_UI, 15, x, y0 + 216, iw, 24, "This can take a few seconds.", C_TEXT_FAINT)
}
if done {
if button(x + iw - 200, ay + 420 - 20 - 44 - 4, 200, 44, "New payment", C_ORANGE, "primary", true, true, "") {
send_reset()
focus_next_frame()
}
}
}