灵符lingfu
L

ling-wallet

↓ 0 downloads

Ling Wallet: a post-quantum privacy-coin wallet, full node and HyperTorus miner for LNG

Windows · x86-64 ✕Linux · x86-64 ✕LingOS · x86-64LingOS · Pi 5 ARM64 ✕
Keywords wallet, cryptocurrency, lng, node, miner, ling
lingfu install ling-wallet
LicenseMIT
Authorsling-lin
READMECode / FilesVersionsDependency GraphDependentsSecurityBuildsSettings

Code / 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()
        }
    }
}

← Back to index