aboutsummaryrefslogtreecommitdiffstats
path: root/views/users/wallet.html
blob: a7d850e1d5c3db0825d9b1a44fb1d05cb5629546 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
{{#yield-scripts}}
  <script src="/bower/bootstrap3-dialog/dist/js/bootstrap-dialog.min.js"></script>
  <script src="/bower/jquery-serialize-object/dist/jquery.serialize-object.min.js"></script>
  <script src="/bower/crypto-js/crypto-js.js"></script>
  <script src="/js/bip38.min.js"></script>
  <script src="/js/bip39.min.js"></script>
  <script src="/js/wallet.js"></script>
{{/yield-scripts}}

<h1>Wallet</h1>

<div class="form-group has-feedback" style="display: none">
  <div class="input-group" style="width: 100%">
    <label for="password">Password:</label>
    <input id="password" name="password" type="password" data-password="true" class="form-control"  />
  </div>
</div>

<div class="wallet" style="display: none">
  <input type="hidden" id="username" name="username" value="{{user}}" />
  <input type="hidden" id="email" name="email" />

  <div class="well">
    <span id="fetching" class="lead"><i class="fa fa-refresh fa-spin"></i> Fetching balance...</span>
    <span id="balances" style="display: none">
      <b id="balance" class="lead">0</b> <b id="unit" class="lead"></b> 
      (<b id="fiat" class="lead">0 CAD</b>)
    </span>

    <div class="pull-right">
      <button id="manage" type="button" class="btn btn-default" data-toggle="tooltip" data-placement="bottom" title="Manage Keys">
        <i class="fa fa-cog fa-lg"></i>
      </button>
      <button id="backup" type="button" class="btn btn-default" data-toggle="tooltip" data-placement="bottom" title="Wallet Backup">
        <i class="fa fa-download fa-lg"></i>
      </button>
    </div>
  </div>

  <div id="keys_updated" class="alert alert-success" style="display: none">
    <button type="button" class="close" aria-label="Close"><span aria-hidden="true">&times;</span></button>
    Wallet keys updated
  </div>

  <div id="transaction_sent" class="alert alert-success" style="display: none">
    <button type="button" class="close" aria-label="Close"><span aria-hidden="true">&times;</span></button>
    <p>Transaction sent!</p><br />
    <button id="blockchain" class="btn btn-success">View on Blockchain</button>
  </div>

  <div id="keys" style="display: none">
    <h3>Settings</h3>

    <form>
      <div class="form-group has-feedback">
        <div class="input-group" style="width: 100%">
          <label for="key">
            Address or Key:
          </label>
          <textarea id="key" name="key" class="form-control" rows="5" data-key="true"></textarea>
        </div>
      </div>

      <input type="hidden" id="pubkey" name="pubkey" />
      <input type="hidden" id="privkey" name="privkey" />

      <div class="form-group has-feedback" style="display: none">
        <div class="input-group" style="width: 100%">
          <label for="keytype">Detected Type:</label>
          <select id="keytype" name="keytype" class="form-control">
            <option value="unknown">Unknown</option>
            <option value="address">Bitcoin Address (starts with 1 or 3)</option>
            <option value="wif">Private Key (starts with 5 or L or K)</option>
            <option value="xpub">HD Wallet Pubkey (starts with xpub)</option>
            <option value="xprv">HD Wallet Private Key (starts with xprv)</option>
            <option value="aes">AES Encrypted Private Key (starts with U)</option>
            <option value="bip38">BIP38 Encrypted Private Key (starts with 6)</option>
            <option value="bip39">BIP39 Mnemonic (series of 12-24 words)</option>
          </select>
        </div>
      </div>

      <button id="generate" class="btn btn-success pull-right" type="button"><i class="fa fa-bolt"></i> Create New Wallet</button>

      <input id="save" class="btn btn-primary" type="button" value="Save" />
      <input id="cancel" class="btn btn-default" type="button" value="Cancel" />
    </form>
  </div>

  <div id="withdrawal" style="display: none">
    <h3>Send Payment / Withdrawal</h3>

    <div id="invalid_keys" class="alert alert-danger" style="display: none">
      <button type="button" class="close" aria-label="Close"><span aria-hidden="true">&times;</span></button>
      Invalid passphrase or problem with private key
    </div>

    <form>
      <div class="form-group has-feedback">
        <div class="input-group">
          <label for="amount">Amount:</label>
          <input type="number" id="amount" name="amount" class="form-control" min="0" required />
          <span class="input-group-btn">
            <button id="currency_toggle" type="button" class="btn btn-primary"></button>
          </span>
          <span class="input-group-btn">
            <button id="max" type="button" class="btn btn-primary">Max</button>
          </span>
        </div>
        <div class="help-block with-errors"></div>
      </div>

      <div class="form-group has-feedback">
        <div class="input-group" style="width: 100%">
          <label for="amount">To Address:</label>
          <input type="text" id="recipient" name="recipient" class="form-control" data-address="true" required />
        </div>
        <div class="help-block with-errors"></div>
      </div>  
      
      <div class="form-group has-feedback">
        <div class="input-group" style="width: 100%">
          <label for="priority">Fee Priority for Confirmation Time:</label>
          <select id="priority" name="priority" class="form-control" required>
            <option value="low">Low</option>
            <option value="medium">Medium</option>
            <option value="high">High</option>
          </select>
        </div>
      </div>

      <input id="send" class="btn btn-primary" type="button" value="Calculate Fee and Confirm Transaction" />
    </form>
  </div>
</div>

<div class="dialog" style="display: none">
  <div style="margin-bottom: 10px">
    <label>Amount:</label> <span class="amount"></span><br />
    <label>Fee:</label> <span class="fee"></span><br />
    <label>To:</label> <span class="address"></span>
  </div>
</div>
an>(m, clear = true) => { if (recent.includes(m)) return; recent.push(m); setTimeout(() => (recent = []), 5000); if (clear) toast.pop(); toast.push(m, { theme: { "--toastBarBackground": "#F5F7FA", }, }); }; export let warning = (m, clear = true) => { if (clear) toast.pop(); toast.push(m, { theme: { "--toastBarBackground": "#FFCE22", }, }); }; export let fail = (m, clear = true) => { if (clear) toast.pop(); toast.push(m, { theme: { "--toastBarBackground": "#E93535", }, }); }; export let info = (m) => { toast.pop(); toast.push(m, { theme: { "--toastBarBackground": "white", }, }); }; export let login = async (user, cookies, ip) => { let maxAge = 380 * 24 * 60 * 60; let res = await fetch(base + "/login", { method: "POST", body: JSON.stringify(user), headers: { "content-type": "application/json", accept: "application/json", "cf-connecting-ip": ip, }, }); if (res.status === 401) { let text = await res.text(); if (text.startsWith("2fa")) throw new Error("2fa"); } let { user: u, token } = await res.json(); if (!token) throw new Error("Login failed"); let expires = new Date(); expires.setSeconds(expires.getSeconds() + maxAge); let opts = { path: "/", expires }; if (u.language) cookies.set("lang", u.language, opts); cookies.set("username", user.username, opts); cookies.set("token", token, opts); }; export let auth = (cookies) => ({ authorization: `Bearer ${cookies.get("token")}`, }); export let btc = (fiat, rate) => Math.round((fiat * sats) / rate); export let fiat = (amount, rate) => (amount * rate) / sats; export let fd = async (req) => { let obj = Object.fromEntries(await req.formData()); for (let k in obj) (obj[k] === "undefined" && (obj[k] = undefined)) || (obj[k] === "false" && (obj[k] = false)); return obj; }; export let f = (s, currency) => new Intl.NumberFormat("en-US", { style: "currency", currency, }) .format(s) .replace("CA", ""); export let s = (s) => new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }).format(s); export let sat = (s) => { s = Math.abs(s); let p = Math.floor(Math.log(s) / Math.LN10 + 0.000000001); let d = Math.floor((p + 1) / 3); return ( "⚡️" + (parseInt(s) > 0 ? new Intl.NumberFormat("en-US", { maximumFractionDigits: 2 }).format( s / Math.pow(1000, d) ) + ["", "K", "M", "G", "T", "P", "E", "Z"][d] : 0 ).toString() ); }; export let sats = 100000000; export let back = () => browser && (history.length ? history.go(-1) : goto("/")); export let focus = (el) => browser && screen.width > 1280 && setTimeout(() => el.focus(), 1); export let select = (el) => browser && screen.width > 1280 && setTimeout(() => el.select(), 1); export let sleep = (n) => new Promise((r) => setTimeout(r, n)); export let wait = async (f, n = 100, s = 300) => { let i = 0; while (!(await f()) && i < s) (await sleep(n)) && i++; if (i >= s) throw new Error("timeout"); return f(); }; export let stretch = async (password, salt) => crypto.subtle.deriveKey( { name: "PBKDF2", salt, iterations: 100000, hash: "SHA-256", }, await crypto.subtle.importKey( "raw", new TextEncoder().encode(password), { name: "PBKDF2" }, false, ["deriveBits", "deriveKey"] ), { name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"] ); function get_bigrams(string) { var s = string.toLowerCase(); var v = s.split(""); for (var i = 0; i < v.length; i++) { v[i] = s.slice(i, i + 2); } return v; } export function ss(str1, str2) { if (str1.length > 0 && str2.length > 0) { var pairs1 = get_bigrams(str1); var pairs2 = get_bigrams(str2); var union = pairs1.length + pairs2.length; var hits = 0; for (var x = 0; x < pairs1.length; x++) { for (var y = 0; y < pairs2.length; y++) { if (pairs1[x] == pairs2[y]) hit_count++; } } if (hits > 0) return (2.0 * hits) / union; } return 0.0; } export let types = { bitcoin: "bitcoin", liquid: "liquid", lightning: "lightning", internal: "internal", fund: "fund", }; export let ease = (t) => t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t; export function shuffleArray(array) { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } } export let closest = (a, n) => a.reduce((prev, curr) => Math.abs(curr - n) < Math.abs(prev - n) ? curr : prev ); export let isLiquid = (text) => text.startsWith("Az") || text.startsWith("lq1") || text.startsWith("VJL") || text.startsWith("VT") || text.startsWith("XR") || text.startsWith("XC") || ((text.startsWith("H") || text.startsWith("G") || text.startsWith("Q")) && text.length === 34) || (text.startsWith("ert1q") && text.length === 43) || (text.startsWith("ex1q") && text.length === 42) || text.startsWith("el1qq") || text.startsWith("lq1qq"); export function getCookie(name) { let nameEQ = name + "="; let ca = document.cookie.split(";"); for (let i = 0; i < ca.length; i++) { let c = ca[i]; while (c.charAt(0) === " ") c = c.substring(1); if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length); } return null; } export function setCookie(name, value, seconds) { const now = new Date(); now.setTime(now.getTime() + seconds * 1000); const expires = "expires=" + now.toUTCString(); document.cookie = name + "=" + value + ";" + expires + ";path=/"; }