├── js ├── config.js ├── background │ ├── keychainify.js │ ├── ops │ │ ├── decode.js │ │ ├── signBuffer.js │ │ ├── signTx.js │ │ ├── signedCall.js │ │ ├── customJson.js │ │ ├── broadcast.js │ │ ├── vote.js │ │ ├── witness.js │ │ ├── createAccount.js │ │ ├── tokens.js │ │ ├── delegation.js │ │ ├── post.js │ │ ├── power.js │ │ ├── transfer.js │ │ ├── proposals.js │ │ └── authority.js │ ├── errors.js │ ├── autolock.js │ ├── dialog_lifecycle.js │ ├── transactions.js │ ├── context_menu.js │ ├── init.js │ └── auth.js ├── popup │ ├── com.js │ ├── preinit.js │ ├── power.js │ ├── rpc.js │ ├── settings.js │ ├── delegate.js │ ├── witness.js │ └── popup.js ├── libs │ ├── globalProps.js │ ├── hf21.js │ ├── encrypt.js │ ├── accountsList.js │ ├── rpcs.js │ ├── keychainify.js │ └── account.js ├── import.js ├── keychainify_content.js ├── dialog │ └── localize.js ├── web_interface.js └── steem_keychain.js ├── images ├── eye.png ├── key.png ├── bg_rc.png ├── claim.png ├── clear.png ├── close.png ├── copy.png ├── delete.png ├── edit.png ├── hide.png ├── info.png ├── link.png ├── lock.png ├── logo.png ├── logo2.png ├── plus.png ├── submit.png ├── voted.png ├── accounts.png ├── arobase.png ├── autolock.png ├── delegate.png ├── history.png ├── loading.gif ├── password.png ├── plus_key.png ├── powerup.png ├── rewards.png ├── settings.png ├── squares.png ├── transfer.png ├── uparrow.png ├── witness.png ├── add_account.png ├── bg-steam@2x.png ├── bg_voting.png ├── btn-bg_send.png ├── downarrow.png ├── icon_white.png ├── left-arrow.png ├── lock_wallet.png ├── logo-white.png ├── powerdown.png ├── preferences.png ├── small_logo.png ├── white-link.png ├── arobase-light.png ├── arobase_white.png ├── btn-bg_tokens.png ├── keychain_icon.png ├── keychain_logo.png ├── btn-bg_history.png ├── btn-bg_witness.png ├── hr-lewis-keogh3.png ├── icon_info_blue.png ├── icon_info_white.png ├── keychain_icon2.png ├── hr-lewis-keogh3.2.png ├── keychain_icon_small.png ├── icon_witness-vote.svg ├── import.svg ├── icon_witness-vote_default.svg └── error.svg ├── fonts └── Futura-Boo.otf ├── .gitignore ├── decode_wrapper ├── README ├── package.json └── decode_wrapper.js ├── css ├── import.css └── dialog.css ├── html ├── import.html └── dialog.html ├── LICENSE ├── manifest.json ├── vendor ├── md5.min.js └── ssc.min.js ├── example ├── main.js └── main.html └── README.md /js/config.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | mainNet: "ssc-mainnet1" 3 | }; 4 | -------------------------------------------------------------------------------- /images/eye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/eye.png -------------------------------------------------------------------------------- /images/key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/key.png -------------------------------------------------------------------------------- /images/bg_rc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/bg_rc.png -------------------------------------------------------------------------------- /images/claim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/claim.png -------------------------------------------------------------------------------- /images/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/clear.png -------------------------------------------------------------------------------- /images/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/close.png -------------------------------------------------------------------------------- /images/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/copy.png -------------------------------------------------------------------------------- /images/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/delete.png -------------------------------------------------------------------------------- /images/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/edit.png -------------------------------------------------------------------------------- /images/hide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/hide.png -------------------------------------------------------------------------------- /images/info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/info.png -------------------------------------------------------------------------------- /images/link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/link.png -------------------------------------------------------------------------------- /images/lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/lock.png -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/logo.png -------------------------------------------------------------------------------- /images/logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/logo2.png -------------------------------------------------------------------------------- /images/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/plus.png -------------------------------------------------------------------------------- /images/submit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/submit.png -------------------------------------------------------------------------------- /images/voted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/voted.png -------------------------------------------------------------------------------- /images/accounts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/accounts.png -------------------------------------------------------------------------------- /images/arobase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/arobase.png -------------------------------------------------------------------------------- /images/autolock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/autolock.png -------------------------------------------------------------------------------- /images/delegate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/delegate.png -------------------------------------------------------------------------------- /images/history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/history.png -------------------------------------------------------------------------------- /images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/loading.gif -------------------------------------------------------------------------------- /images/password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/password.png -------------------------------------------------------------------------------- /images/plus_key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/plus_key.png -------------------------------------------------------------------------------- /images/powerup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/powerup.png -------------------------------------------------------------------------------- /images/rewards.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/rewards.png -------------------------------------------------------------------------------- /images/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/settings.png -------------------------------------------------------------------------------- /images/squares.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/squares.png -------------------------------------------------------------------------------- /images/transfer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/transfer.png -------------------------------------------------------------------------------- /images/uparrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/uparrow.png -------------------------------------------------------------------------------- /images/witness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/witness.png -------------------------------------------------------------------------------- /fonts/Futura-Boo.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/fonts/Futura-Boo.otf -------------------------------------------------------------------------------- /images/add_account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/add_account.png -------------------------------------------------------------------------------- /images/bg-steam@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/bg-steam@2x.png -------------------------------------------------------------------------------- /images/bg_voting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/bg_voting.png -------------------------------------------------------------------------------- /images/btn-bg_send.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/btn-bg_send.png -------------------------------------------------------------------------------- /images/downarrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/downarrow.png -------------------------------------------------------------------------------- /images/icon_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/icon_white.png -------------------------------------------------------------------------------- /images/left-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/left-arrow.png -------------------------------------------------------------------------------- /images/lock_wallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/lock_wallet.png -------------------------------------------------------------------------------- /images/logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/logo-white.png -------------------------------------------------------------------------------- /images/powerdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/powerdown.png -------------------------------------------------------------------------------- /images/preferences.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/preferences.png -------------------------------------------------------------------------------- /images/small_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/small_logo.png -------------------------------------------------------------------------------- /images/white-link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/white-link.png -------------------------------------------------------------------------------- /images/arobase-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/arobase-light.png -------------------------------------------------------------------------------- /images/arobase_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/arobase_white.png -------------------------------------------------------------------------------- /images/btn-bg_tokens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/btn-bg_tokens.png -------------------------------------------------------------------------------- /images/keychain_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/keychain_icon.png -------------------------------------------------------------------------------- /images/keychain_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/keychain_logo.png -------------------------------------------------------------------------------- /images/btn-bg_history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/btn-bg_history.png -------------------------------------------------------------------------------- /images/btn-bg_witness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/btn-bg_witness.png -------------------------------------------------------------------------------- /images/hr-lewis-keogh3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/hr-lewis-keogh3.png -------------------------------------------------------------------------------- /images/icon_info_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/icon_info_blue.png -------------------------------------------------------------------------------- /images/icon_info_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/icon_info_white.png -------------------------------------------------------------------------------- /images/keychain_icon2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/keychain_icon2.png -------------------------------------------------------------------------------- /images/hr-lewis-keogh3.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/hr-lewis-keogh3.2.png -------------------------------------------------------------------------------- /images/keychain_icon_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattyIce/steem-keychain/HEAD/images/keychain_icon_small.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directory 2 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 3 | node_modules 4 | 5 | -------------------------------------------------------------------------------- /decode_wrapper/README: -------------------------------------------------------------------------------- 1 | # Generating decode.min.js 2 | # Needs node/npm. 3 | # Needs browserify. 4 | 5 | npm install 6 | browserify decode_wrapper.js > decode.min.js 7 | -------------------------------------------------------------------------------- /decode_wrapper/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "decode_wrapper", 3 | "version": "1.0.0", 4 | "description": "Small wrapper for key encryption methods.", 5 | "main": "decode_wrapper.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "license": "MIT", 10 | "dependencies": { 11 | "steem": "^0.7.1" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /js/background/keychainify.js: -------------------------------------------------------------------------------- 1 | chrome.webNavigation.onHistoryStateUpdated.addListener(function(details) { 2 | if (details.frameId === 0) { 3 | // Fires only when details.url === currentTab.url 4 | chrome.tabs.get(details.tabId, async function(tab) { 5 | if (await keychainify.isKeychainifyEnabled()) { 6 | keychainify.keychainifyUrl(tab); 7 | } 8 | }); 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /js/background/ops/decode.js: -------------------------------------------------------------------------------- 1 | const decodeMessage = data => { 2 | let message = null; 3 | let decoded = null; 4 | let error = null; 5 | try { 6 | decoded = window.decodeMemo(key, data.message); 7 | } catch (err) { 8 | error = err; 9 | } finally { 10 | return createMessage( 11 | error, 12 | decoded, 13 | data, 14 | chrome.i18n.getMessage("bgd_ops_decode"), 15 | chrome.i18n.getMessage("bgd_ops_decode_err") 16 | ); 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /js/background/ops/signBuffer.js: -------------------------------------------------------------------------------- 1 | const signBuffer = data => { 2 | let message = null; 3 | let signed = null; 4 | let error = null; 5 | try { 6 | signed = window.signBuffer(data.message, key); 7 | } catch (err) { 8 | error = err; 9 | } finally { 10 | return createMessage( 11 | error, 12 | signed, 13 | data, 14 | chrome.i18n.getMessage("bgd_ops_sign_success"), 15 | chrome.i18n.getMessage("bgd_ops_sign_error"), 16 | public 17 | ); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /js/background/ops/signTx.js: -------------------------------------------------------------------------------- 1 | const signTx = data => { 2 | return new Promise((resolve, reject) => { 3 | let result = null, err = null; 4 | 5 | try { 6 | result = steem.auth.signTransaction(data.tx, [key]); 7 | } catch (e) { err = e; } 8 | 9 | console.log(result, err); 10 | const err_message = beautifyErrorMessage(err); 11 | 12 | const message = createMessage( 13 | err, 14 | result, 15 | data, 16 | chrome.i18n.getMessage("bgd_ops_sign_tx"), 17 | err_message 18 | ); 19 | resolve(message); 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /images/icon_witness-vote.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /js/background/ops/signedCall.js: -------------------------------------------------------------------------------- 1 | const broadcastSignedCall = data => { 2 | return new Promise((resolve, reject) => { 3 | window.signedCall( 4 | data.method, 5 | data.params, 6 | data.username, 7 | key, 8 | (err, result) => { 9 | console.log(result, err); 10 | const err_message = beautifyErrorMessage(err); 11 | const message = createMessage( 12 | err, 13 | result, 14 | data, 15 | chrome.i18n.getMessage("bgd_ops_signed_call"), 16 | err_message 17 | ); 18 | resolve(message); 19 | } 20 | ); 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /images/import.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /js/background/errors.js: -------------------------------------------------------------------------------- 1 | // Send errors back to the content_script, it will forward it to website 2 | const sendErrors = (tab, error, message, display_msg, request) => { 3 | clearInterval(interval); 4 | interval = setInterval(() => { 5 | chrome.runtime.sendMessage({ 6 | command: "sendDialogError", 7 | msg: { 8 | success: false, 9 | error: error, 10 | result: null, 11 | data: request, 12 | message: message, 13 | display_msg: display_msg, 14 | request_id: request_id 15 | }, 16 | tab: tab 17 | }); 18 | }, 200); 19 | setTimeout(() => { 20 | clearInterval(interval); 21 | }, 2000); 22 | key = null; 23 | accounts = new AccountsList(); 24 | }; 25 | -------------------------------------------------------------------------------- /js/popup/com.js: -------------------------------------------------------------------------------- 1 | // Communication with the background 2 | 3 | // Send new autolock to background; 4 | function setAutolock(autolock) { 5 | chrome.runtime.sendMessage({ 6 | command: "sendAutolock", 7 | autolock: autolock 8 | }); 9 | } 10 | 11 | // get MK from background 12 | function getMK() { 13 | chrome.runtime.sendMessage({ 14 | command: "getMk" 15 | }); 16 | } 17 | 18 | // setRPC in the background 19 | function setRPC(rpc) { 20 | chrome.runtime.sendMessage({ 21 | command: "setRPC", 22 | rpc: rpc 23 | }); 24 | } 25 | 26 | setInterval(ping, 10000); 27 | // ping the background to show that the extension is not idle 28 | function ping() { 29 | chrome.runtime.sendMessage({ 30 | command: "ping" 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /css/import.css: -------------------------------------------------------------------------------- 1 | #file { 2 | width: 0.1px; 3 | height: 0.1px; 4 | opacity: 0; 5 | overflow: hidden; 6 | position: absolute; 7 | z-index: -1; 8 | } 9 | 10 | #file+label { 11 | display: inline-block; 12 | border-radius: 5px; 13 | width: 100%; 14 | text-align: center; 15 | font-size: 20px; 16 | line-height: 28px; 17 | color: white; 18 | font-family: Futura; 19 | text-transform: uppercase; 20 | border: solid 3px rgba(0, 156, 156); 21 | cursor: pointer; 22 | } 23 | 24 | #file_span { 25 | display: inline-block; 26 | margin-bottom: 30px; 27 | } 28 | 29 | #file:focus+label, 30 | #file+label:hover { 31 | background-color: #917FC6; 32 | border: solid 3px #917FC6; 33 | } 34 | 35 | #import_result { 36 | display: none; 37 | } -------------------------------------------------------------------------------- /js/background/ops/customJson.js: -------------------------------------------------------------------------------- 1 | const broadcastCustomJson = data => { 2 | return new Promise((resolve, reject) => { 3 | steem.broadcast.customJson( 4 | key, 5 | data.method.toLowerCase() == "active" ? [data.username] : null, 6 | data.method.toLowerCase() == "posting" ? [data.username] : null, 7 | data.id, 8 | data.json, 9 | (err, result) => { 10 | console.log(result, err); 11 | const err_message = beautifyErrorMessage(err); 12 | 13 | const message = createMessage( 14 | err, 15 | result, 16 | data, 17 | chrome.i18n.getMessage("bgd_ops_broadcast"), 18 | err_message 19 | ); 20 | resolve(message); 21 | } 22 | ); 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /js/background/ops/broadcast.js: -------------------------------------------------------------------------------- 1 | const broadcastData = data => { 2 | return new Promise((resolve, reject) => { 3 | const operations = data.operations; 4 | const broadcastKeys = {}; 5 | broadcastKeys[data.typeWif] = key; 6 | steem.broadcast.send( 7 | { 8 | operations, 9 | extensions: [] 10 | }, 11 | broadcastKeys, 12 | (err, result) => { 13 | console.log(result, err); 14 | const err_message = beautifyErrorMessage(err); 15 | 16 | const message = createMessage( 17 | err, 18 | result, 19 | data, 20 | chrome.i18n.getMessage("bgd_ops_broadcast"), 21 | err_message 22 | ); 23 | resolve(message); 24 | } 25 | ); 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /js/background/ops/vote.js: -------------------------------------------------------------------------------- 1 | const broadcastVote = data => { 2 | return new Promise((resolve, reject) => { 3 | steem.broadcast.vote( 4 | key, 5 | data.username, 6 | data.author, 7 | data.permlink, 8 | parseInt(data.weight), 9 | (err, result) => { 10 | console.log(result, err); 11 | const err_message = beautifyErrorMessage(err); 12 | const message = createMessage( 13 | err, 14 | result, 15 | data, 16 | chrome.i18n.getMessage("bgd_ops_vote", [ 17 | data.author, 18 | data.permlink, 19 | parseInt(data.weight) / 100 20 | ]), 21 | err_message 22 | ); 23 | resolve(message); 24 | } 25 | ); 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /js/background/ops/witness.js: -------------------------------------------------------------------------------- 1 | const broadcastWitnessVote = data => { 2 | return new Promise((resolve, reject) => { 3 | steem.broadcast.accountWitnessVote( 4 | key, 5 | data.username, 6 | data.witness, 7 | data.vote ? 1 : 0, 8 | (err, result) => { 9 | console.log(result, err); 10 | const err_message = beautifyErrorMessage(err); 11 | const message = createMessage( 12 | err, 13 | result, 14 | data, 15 | data.vote 16 | ? chrome.i18n.getMessage("bgd_ops_witness_voted", [data.witness]) 17 | : chrome.i18n.getMessage("bgd_ops_witness_unvoted", [data.witness]), 18 | err_message 19 | ); 20 | resolve(message); 21 | } 22 | ); 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /js/background/ops/createAccount.js: -------------------------------------------------------------------------------- 1 | const broadcastCreateClaimedAccount = data => { 2 | return new Promise((resolve, reject) => { 3 | steem.broadcast.createClaimedAccount( 4 | key, 5 | data.username, 6 | data.new_account, 7 | JSON.parse(data.owner), 8 | JSON.parse(data.active), 9 | JSON.parse(data.posting), 10 | data.memo, 11 | {}, 12 | [], 13 | (err, result) => { 14 | console.log(result, err); 15 | const err_message = beautifyErrorMessage(err); 16 | const message = createMessage( 17 | err, 18 | result, 19 | data, 20 | chrome.i18n.getMessage("bgd_ops_create_account", [data.new_account]), 21 | err_message 22 | ); 23 | resolve(message); 24 | } 25 | ); 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /decode_wrapper/decode_wrapper.js: -------------------------------------------------------------------------------- 1 | var signature = require("steem/lib/auth/ecc"); 2 | var steem = require("steem"); 3 | 4 | window.decodeMemo = (e, r) => steem.memo.decode(e, r); 5 | window.encodeMemo = (e, r, a) => steem.memo.encode(e, r, a); 6 | window.signBuffer = (e, r) => { 7 | // try to parse Buffer 8 | let buf = e; 9 | try { 10 | const o = JSON.parse(buf, (k, v) => { 11 | if ( 12 | v !== null && 13 | typeof v === "object" && 14 | "type" in v && 15 | v.type === "Buffer" && 16 | "data" in v && 17 | Array.isArray(v.data) 18 | ) { 19 | return new Buffer(v.data); 20 | } 21 | return v; 22 | }); 23 | if (Buffer.isBuffer(o)) { 24 | buf = o; 25 | } 26 | } catch (e) {} 27 | return signature.Signature.signBuffer(buf, r).toHex(); 28 | }; 29 | window.signedCall = (m, p, a, k, c) => steem.api.signedCall(m, p, a, k, c); 30 | -------------------------------------------------------------------------------- /js/background/ops/tokens.js: -------------------------------------------------------------------------------- 1 | const broadcastSendToken = data => { 2 | return new Promise((resolve, reject) => { 3 | const id = config.mainNet; 4 | const json = { 5 | contractName: "tokens", 6 | contractAction: "transfer", 7 | contractPayload: { 8 | symbol: data.currency, 9 | to: data.to, 10 | quantity: data.amount, 11 | memo: data.memo 12 | } 13 | }; 14 | steem.broadcast.customJson( 15 | key, 16 | [data.username], 17 | null, 18 | id, 19 | JSON.stringify(json), 20 | (err, result) => { 21 | console.log(result, err); 22 | const message = createMessage( 23 | err, 24 | result, 25 | data, 26 | chrome.i18n.getMessage("bgd_ops_tokens"), 27 | chrome.i18n.getMessage("bgd_ops_error_broadcasting") 28 | ); 29 | resolve(message); 30 | } 31 | ); 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /js/background/autolock.js: -------------------------------------------------------------------------------- 1 | const startAutolock = async autoLock => { 2 | //Receive autolock from the popup (upon registration or unlocking) 3 | autolock = autoLock; 4 | console.log(autolock); 5 | if (mk == null) return; 6 | if (!autolock || autolock.type == "default") return; 7 | if (autolock.type == "locked") { 8 | chrome.idle.setDetectionInterval(parseInt(autolock.mn) * 60); 9 | chrome.idle.onStateChanged.addListener(state => { 10 | console.log(state, autolock.type); 11 | if (state === "locked") { 12 | mk = null; 13 | console.log("lock"); 14 | } 15 | }); 16 | } else if (autolock.type == "idle") { 17 | restartIdleCounter(); 18 | } 19 | }; 20 | //Create Custom Idle Function 21 | const restartIdleCounter = () => { 22 | console.log("idleCounter", new Date().toISOString()); 23 | clearTimeout(timeoutIdle); 24 | timeoutIdle = setTimeout(() => { 25 | console.log("locked", new Date().toISOString()); 26 | mk = null; 27 | }, autolock.mn * 60000); 28 | }; 29 | -------------------------------------------------------------------------------- /images/icon_witness-vote_default.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /html/import.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 19 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /js/libs/globalProps.js: -------------------------------------------------------------------------------- 1 | class GlobalProps { 2 | constructor() { 3 | this.props = steem.api.getDynamicGlobalPropertiesAsync(); 4 | this.median = steem.api.getCurrentMedianHistoryPriceAsync(); 5 | this.fund = steem.api.getRewardFundAsync("post"); 6 | this.prices = this.initGetPrice(); 7 | } 8 | async getProp(key) { 9 | return (await this.props)[key]; 10 | } 11 | async getMedian() { 12 | return await this.median; 13 | } 14 | async getFund(key) { 15 | return (await this.fund)[key]; 16 | } 17 | async getSteemPrice() { 18 | const median = await this.getMedian(); 19 | return ( 20 | parseFloat(median.base.replace(" SBD", "")) / 21 | parseFloat(median.quote.replace(" STEEM", "")) 22 | ); 23 | } 24 | async initGetPrice() { 25 | return await Promise.all([ 26 | await getPriceSteemAsync(), 27 | await getPriceSBDAsync(), 28 | await getBTCPriceAsync() 29 | ]); 30 | } 31 | async getPrices() { 32 | const [steem, sbd, btc] = await this.prices; 33 | return [steem * btc, sbd * btc]; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Steem Monsters, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /images/error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /js/popup/preinit.js: -------------------------------------------------------------------------------- 1 | const rpcs = new Rpcs(); 2 | 3 | window.sk_params = { 4 | page: "main" 5 | }; 6 | 7 | function parseQueryString() { 8 | const queryString = window.location.search; 9 | const queryParamString = queryString.split("?").pop(); 10 | const queryParams = queryParamString.split("&"); 11 | for (let qi = 0; qi < queryParams.length; qi++) { 12 | const queryParam = queryParams[qi]; 13 | const keyValue = queryParam.split("="); 14 | if (keyValue[0] && keyValue[1]) { 15 | let value = keyValue[1]; 16 | value = value.replace(/\+/g, "%20"); 17 | value = decodeURIComponent(value); 18 | window.sk_params[keyValue[0]] = value; 19 | } 20 | } 21 | } 22 | 23 | parseQueryString(); 24 | initializeVisibility(true); 25 | 26 | // Check if we have mk or if accounts are stored to know if the wallet is locked unlocked or new. 27 | chrome.runtime.onMessage.addListener(function(msg, sender, sendResp) { 28 | if (msg.command == "sendBackMk") { 29 | chrome.storage.local.get(["accounts", "current_rpc"], function(items) { 30 | rpcs.setOptions(items.current_rpc || "DEFAULT"); 31 | if (!msg.mk) { 32 | if (!items.accounts) { 33 | showRegister(); 34 | } else { 35 | showUnlock(); 36 | } 37 | } else { 38 | mk = msg.mk; 39 | initializeMainMenu(); 40 | initializeVisibility(); 41 | } 42 | }); 43 | } 44 | }); 45 | -------------------------------------------------------------------------------- /js/background/ops/delegation.js: -------------------------------------------------------------------------------- 1 | const broadcastDelegation = data => { 2 | return new Promise((resolve, reject) => { 3 | steem.api.getDynamicGlobalPropertiesAsync().then(res => { 4 | let delegated_vest = null; 5 | if (data.unit == "SP") { 6 | const totalSteem = Number(res.total_vesting_fund_steem.split(" ")[0]); 7 | const totalVests = Number(res.total_vesting_shares.split(" ")[0]); 8 | delegated_vest = (parseFloat(data.amount) * totalVests) / totalSteem; 9 | delegated_vest = delegated_vest.toFixed(6); 10 | delegated_vest = delegated_vest.toString() + " VESTS"; 11 | } else { 12 | delegated_vest = data.amount + " VESTS"; 13 | } 14 | steem.broadcast.delegateVestingShares( 15 | key, 16 | data.username, 17 | data.delegatee, 18 | delegated_vest, 19 | (err, result) => { 20 | console.log(result, err); 21 | const err_message = beautifyErrorMessage(err); 22 | const message = createMessage( 23 | err, 24 | result, 25 | data, 26 | parseFloat(data.amount) === 0 27 | ? chrome.i18n.getMessage("bgd_ops_undelegate", [ 28 | data.delegatee, 29 | data.username 30 | ]) 31 | : chrome.i18n.getMessage("bgd_ops_delegate", [ 32 | data.amount, 33 | data.delegatee, 34 | data.username 35 | ]), 36 | err_message 37 | ); 38 | resolve(message); 39 | } 40 | ); 41 | }); 42 | }); 43 | }; 44 | -------------------------------------------------------------------------------- /js/libs/hf21.js: -------------------------------------------------------------------------------- 1 | //HF21 wrapper 2 | 3 | const createProposal = ( 4 | keys, 5 | creator, 6 | receiver, 7 | start_date, 8 | end_date, 9 | daily_pay, 10 | subject, 11 | permlink, 12 | extensions = "[]", 13 | callback 14 | ) => { 15 | let tx = { 16 | operations: [ 17 | [ 18 | "create_proposal", 19 | { 20 | creator, 21 | receiver, 22 | start_date, 23 | end_date, 24 | daily_pay, 25 | subject, 26 | permlink 27 | } 28 | ] 29 | ], 30 | extensions: JSON.parse(extensions) 31 | }; 32 | return broadcast(tx, keys, callback); 33 | }; 34 | 35 | const voteForProposal = ( 36 | keys, 37 | voter, 38 | proposal_ids, 39 | approve, 40 | extensions = "[]", 41 | callback 42 | ) => { 43 | let tx = { 44 | operations: [ 45 | [ 46 | "update_proposal_votes", 47 | { 48 | voter, 49 | proposal_ids: JSON.parse(proposal_ids), 50 | approve 51 | } 52 | ] 53 | ], 54 | extensions: JSON.parse(extensions) 55 | }; 56 | return broadcast(tx, keys, callback); 57 | }; 58 | 59 | const removeProposal = ( 60 | keys, 61 | proposal_owner, 62 | proposal_ids, 63 | extensions = "[]", 64 | callback 65 | ) => { 66 | let tx = { 67 | operations: [ 68 | [ 69 | "remove_proposal", 70 | { 71 | proposal_owner, 72 | proposal_ids: JSON.parse(proposal_ids) 73 | } 74 | ] 75 | ], 76 | extensions: JSON.parse(extensions) 77 | }; 78 | return broadcast(tx, keys, callback); 79 | }; 80 | 81 | const broadcast = (tx, keys, callback) => { 82 | steem.broadcast.send(tx, keys, (error, result) => { 83 | callback(error, result); 84 | }); 85 | }; 86 | -------------------------------------------------------------------------------- /js/import.js: -------------------------------------------------------------------------------- 1 | $("#dialog_header").text(chrome.i18n.getMessage("popup_html_import_keys")); 2 | $("#text_import").html(chrome.i18n.getMessage("import_html_text")); 3 | $("#proceed").text(chrome.i18n.getMessage("popup_html_import")); 4 | 5 | $("#proceed").click(async () => { 6 | const file = $("input").prop("files")[0]; 7 | if (file) { 8 | const base64 = await toBase64(file); 9 | const fileData = atob(base64); 10 | console.log(fileData); 11 | chrome.runtime.sendMessage({ 12 | command: "importKeys", 13 | fileData 14 | }); 15 | } 16 | }); 17 | $("#file").on("change", () => { 18 | $("#file_span").text($("#file").prop("files")[0].name); 19 | }); 20 | chrome.runtime.onMessage.addListener((msg, sender, sendResp) => { 21 | if (msg.command == "importResult") { 22 | $("#import_result").show(); 23 | $("#mod_content").hide(); 24 | if (msg.result) { 25 | $("#text_finish_import").html( 26 | chrome.i18n.getMessage("import_html_success") 27 | ); 28 | $("#dialog_header_result").text( 29 | chrome.i18n.getMessage("dialog_header_success") 30 | ); 31 | } else { 32 | $("#text_finish_import").html( 33 | chrome.i18n.getMessage("import_html_error") 34 | ); 35 | $("#dialog_header_result").text( 36 | chrome.i18n.getMessage("dialog_header_error") 37 | ); 38 | } 39 | $("#close").text(chrome.i18n.getMessage("dialog_ok")); 40 | $("#close").click(() => { 41 | window.close(); 42 | }); 43 | } 44 | }); 45 | 46 | const toBase64 = file => 47 | new Promise((resolve, reject) => { 48 | const reader = new FileReader(); 49 | reader.readAsDataURL(file); 50 | reader.onload = () => resolve(reader.result.split(",")[1]); 51 | reader.onerror = error => reject(error); 52 | }); 53 | -------------------------------------------------------------------------------- /js/background/ops/post.js: -------------------------------------------------------------------------------- 1 | const broadcastPost = data => { 2 | console.log("broadcastPost"); 3 | console.log(data); 4 | return new Promise((resolve, reject) => { 5 | if (data.comment_options == "") { 6 | steem.broadcast.comment( 7 | key, 8 | data.parent_username, 9 | data.parent_perm, 10 | data.username, 11 | data.permlink, 12 | data.title, 13 | data.body, 14 | data.json_metadata, 15 | (err, result) => { 16 | console.log(result, err); 17 | const err_message = beautifyErrorMessage(err); 18 | const message = createMessage( 19 | err, 20 | result, 21 | data, 22 | chrome.i18n.getMessage("bgd_ops_post"), 23 | err_message 24 | ); 25 | resolve(message); 26 | } 27 | ); 28 | } else { 29 | const operations = [ 30 | [ 31 | "comment", 32 | { 33 | parent_author: data.parent_username, 34 | parent_permlink: data.parent_perm, 35 | author: data.username, 36 | permlink: data.permlink, 37 | title: data.title, 38 | body: data.body, 39 | json_metadata: data.json_metadata 40 | } 41 | ], 42 | ["comment_options", JSON.parse(data.comment_options)] 43 | ]; 44 | steem.broadcast.send( 45 | { 46 | operations, 47 | extensions: [] 48 | }, 49 | { 50 | posting: key 51 | }, 52 | (err, result) => { 53 | console.log(result, err); 54 | const err_message = beautifyErrorMessage(err); 55 | 56 | const message = createMessage( 57 | err, 58 | result, 59 | data, 60 | chrome.i18n.getMessage("bgd_ops_post"), 61 | err_message 62 | ); 63 | resolve(message); 64 | } 65 | ); 66 | } 67 | }); 68 | }; 69 | -------------------------------------------------------------------------------- /js/background/ops/power.js: -------------------------------------------------------------------------------- 1 | const broadcastPowerUp = data => { 2 | return new Promise((resolve, reject) => { 3 | steem.broadcast.transferToVesting( 4 | key, 5 | data.username, 6 | data.recipient, 7 | `${data.steem} STEEM`, 8 | (err, result) => { 9 | console.log(result, err); 10 | const err_message = beautifyErrorMessage(err); 11 | const message = createMessage( 12 | err, 13 | result, 14 | data, 15 | chrome.i18n.getMessage("bgd_ops_pu", [data.steem, data.recipient]), 16 | err_message 17 | ); 18 | resolve(message); 19 | } 20 | ); 21 | }); 22 | }; 23 | 24 | const broadcastPowerDown = data => { 25 | return new Promise((resolve, reject) => { 26 | steem.api.getDynamicGlobalPropertiesAsync().then(res => { 27 | let vestingShares = null; 28 | const totalSteem = Number(res.total_vesting_fund_steem.split(" ")[0]); 29 | const totalVests = Number(res.total_vesting_shares.split(" ")[0]); 30 | vestingShares = (parseFloat(data.steem_power) * totalVests) / totalSteem; 31 | vestingShares = vestingShares.toFixed(6); 32 | vestingShares = vestingShares.toString() + " VESTS"; 33 | 34 | steem.broadcast.withdrawVesting( 35 | key, 36 | data.username, 37 | vestingShares, 38 | (err, result) => { 39 | console.log(result, err); 40 | const err_message = beautifyErrorMessage(err); 41 | const message = createMessage( 42 | err, 43 | result, 44 | data, 45 | parseFloat(data.steem_power) == 0 46 | ? chrome.i18n.getMessage("bgd_ops_pd_stop", [data.username]) 47 | : chrome.i18n.getMessage("bgd_ops_pd", [ 48 | data.steem_power, 49 | data.username 50 | ]), 51 | err_message 52 | ); 53 | resolve(message); 54 | } 55 | ); 56 | }); 57 | }); 58 | }; 59 | -------------------------------------------------------------------------------- /js/libs/encrypt.js: -------------------------------------------------------------------------------- 1 | // AES implementation using cryptojs 2 | 3 | var keySize = 256; 4 | var ivSize = 128; 5 | var iterations = 100; 6 | 7 | // We add an md5 hash to check if decryption is successful later on. 8 | function encryptJson(json, pwd) { 9 | json.hash = md5(json.list); 10 | var msg = encrypt(JSON.stringify(json), pwd); 11 | return msg; 12 | } 13 | 14 | // Decrypt and check the hash to confirm the decryption 15 | function decryptToJson(msg, pwd) { 16 | try { 17 | var decrypted = decrypt(msg, pwd).toString(CryptoJS.enc.Utf8); 18 | decrypted = JSON.parse(decrypted); 19 | if (decrypted.hash != null && decrypted.hash == md5(decrypted.list)) 20 | return decrypted; 21 | else { 22 | return null; 23 | } 24 | } catch (e) { 25 | return null; 26 | } 27 | } 28 | 29 | // AES encryption with master password 30 | function encrypt(msg, pass) { 31 | var salt = CryptoJS.lib.WordArray.random(128 / 8); 32 | var key = CryptoJS.PBKDF2(pass, salt, { 33 | keySize: keySize / 32, 34 | iterations: iterations 35 | }); 36 | 37 | var iv = CryptoJS.lib.WordArray.random(128 / 8); 38 | 39 | var encrypted = CryptoJS.AES.encrypt(msg, key, { 40 | iv: iv, 41 | padding: CryptoJS.pad.Pkcs7, 42 | mode: CryptoJS.mode.CBC 43 | }); 44 | // salt, iv will be hex 32 in length 45 | // append them to the ciphertext for use in decryption 46 | var transitmessage = salt.toString() + iv.toString() + encrypted.toString(); 47 | return transitmessage; 48 | } 49 | 50 | // AES decryption with master password 51 | function decrypt(transitmessage, pass) { 52 | var salt = CryptoJS.enc.Hex.parse(transitmessage.substr(0, 32)); 53 | var iv = CryptoJS.enc.Hex.parse(transitmessage.substr(32, 32)); 54 | var encrypted = transitmessage.substring(64); 55 | 56 | var key = CryptoJS.PBKDF2(pass, salt, { 57 | keySize: keySize / 32, 58 | iterations: iterations 59 | }); 60 | 61 | var decrypted = CryptoJS.AES.decrypt(encrypted, key, { 62 | iv: iv, 63 | padding: CryptoJS.pad.Pkcs7, 64 | mode: CryptoJS.mode.CBC 65 | }); 66 | return decrypted; 67 | } 68 | -------------------------------------------------------------------------------- /js/background/ops/transfer.js: -------------------------------------------------------------------------------- 1 | const broadcastTransfer = data => { 2 | return new Promise(async (resolve, reject) => { 3 | let ac = accountsList.get(data.username); 4 | let memo = data.memo || ""; 5 | let key_transfer = ac.getKey("active"); 6 | if (data.memo && data.memo.length > 0 && data.memo[0] == "#") { 7 | try { 8 | const receiver = await steem.api.getAccountsAsync([data.to]); 9 | const memoReceiver = receiver["0"].memo_key; 10 | memo = window.encodeMemo(ac.keys.memo, memoReceiver, memo); 11 | } catch (e) { 12 | console.log(e); 13 | } 14 | } 15 | steem.broadcast.transfer( 16 | key_transfer, 17 | data.username, 18 | data.to, 19 | data.amount + " " + data.currency, 20 | memo, 21 | (err, result) => { 22 | console.log(result, err); 23 | let err_message = ""; 24 | if (err) { 25 | console.log(err.data.stack[0].context.method); 26 | switch (err.data.stack[0].context.method) { 27 | case "adjust_balance": 28 | err_message = chrome.i18n.getMessage( 29 | "bgd_ops_transfer_adjust_balance", 30 | [data.currency] 31 | ); 32 | break; 33 | case "get_account": 34 | err_message = chrome.i18n.getMessage( 35 | "bgd_ops_transfer_get_account", 36 | [data.to] 37 | ); 38 | break; 39 | default: 40 | err_message = chrome.i18n.getMessage( 41 | "bgd_ops_error_broadcasting" 42 | ); 43 | break; 44 | } 45 | } 46 | const message = createMessage( 47 | err, 48 | result, 49 | data, 50 | chrome.i18n.getMessage("bgd_ops_transfer_success", [ 51 | data.amount, 52 | data.currency, 53 | data.username, 54 | data.to 55 | ]), 56 | err_message 57 | ); 58 | resolve(message); 59 | } 60 | ); 61 | }); 62 | }; 63 | -------------------------------------------------------------------------------- /js/background/dialog_lifecycle.js: -------------------------------------------------------------------------------- 1 | const createPopup = (callback, popupHtml = "html/dialog.html") => { 2 | let width = 350; 3 | confirmed = false; 4 | //Ensuring only one window is opened by the extension at a time. 5 | if (id_win != null) { 6 | removeWindow(id_win); 7 | id_win = null; 8 | } 9 | //Create new window on the top right of the screen 10 | chrome.windows.getCurrent(w => { 11 | console.log(popupHtml); 12 | chrome.windows.create( 13 | { 14 | url: chrome.runtime.getURL(popupHtml), 15 | type: "popup", 16 | height: 566, 17 | width: width, 18 | left: w.width - width + w.left, 19 | top: w.top 20 | }, 21 | win => { 22 | id_win = win.id; 23 | // Window create fails to take into account window size so it s updated afterwhile. 24 | chrome.windows.update(win.id, { 25 | height: 566, 26 | width: width, 27 | top: w.top, 28 | left: w.width - width + w.left 29 | }); 30 | 31 | if (typeof callback === "function") { 32 | clearInterval(interval); 33 | interval = setInterval(callback, 200); 34 | setTimeout(() => { 35 | clearInterval(interval); 36 | }, 2000); 37 | } 38 | } 39 | ); 40 | }); 41 | }; 42 | 43 | chrome.windows.onRemoved.addListener(id => { 44 | if (id == id_win && !confirmed) { 45 | console.log("error6"); 46 | chrome.tabs.sendMessage(tab, { 47 | command: "answerRequest", 48 | msg: { 49 | success: false, 50 | error: "user_cancel", 51 | result: null, 52 | data: request, 53 | message: chrome.i18n.getMessage("bgd_lifecycle_request_canceled"), 54 | request_id: request_id 55 | } 56 | }); 57 | } 58 | }); 59 | 60 | // check if win exists before removing it 61 | const removeWindow = id_win => { 62 | console.log(id_win); 63 | chrome.windows.getAll(windows => { 64 | const hasWin = windows.filter(win => { 65 | return win.id == id_win; 66 | }).length; 67 | if (hasWin) { 68 | chrome.windows.remove(id_win); 69 | } 70 | }); 71 | }; 72 | -------------------------------------------------------------------------------- /js/libs/accountsList.js: -------------------------------------------------------------------------------- 1 | class AccountsList { 2 | constructor() { 3 | this.accounts = {list: []}; 4 | } 5 | init(accounts, last_account) { 6 | if (accounts) { 7 | this.accounts = accounts; 8 | this.accounts.list = this.accounts.list.map(e => new Account(e)); 9 | 10 | // Sort the accounts by name 11 | this.accounts.list.sort((a, b) => 12 | a.getName() < b.getName() ? -1 : a.getName() > b.getName() ? 1 : 0 13 | ); 14 | 15 | // Add the last account selected to the front of the account list. 16 | if (last_account) { 17 | console.log(this.accounts.list, last_account); 18 | let last = this.accounts.list.find(a => a.account.name == last_account); 19 | console.log(last); 20 | if (last) { 21 | this.accounts.list.splice(this.accounts.list.indexOf(last), 1); 22 | this.accounts.list.unshift(last); 23 | } 24 | } 25 | } 26 | } 27 | getList() { 28 | return this.accounts.list || []; 29 | } 30 | get(name) { 31 | return this.getList().find(e => e.getName() === name); 32 | } 33 | getById(id) { 34 | return this.accounts.list[id]; 35 | } 36 | save(mk) { 37 | chrome.storage.local.set({ 38 | accounts: this.encrypt(mk) 39 | }); 40 | } 41 | clear() { 42 | chrome.storage.local.clear(); 43 | this.accounts = {}; 44 | } 45 | isEmpty() { 46 | return !this.accounts.list || !this.accounts.list.length; 47 | } 48 | import(accounts, mk) { 49 | console.log(this.accounts.list); 50 | for (const account of accounts) { 51 | if (!this.accounts.list.find(acc => acc.getName() === account.name)) 52 | this.accounts.list.push(new Account(account)); 53 | } 54 | this.save(mk); 55 | } 56 | encrypt(mk) { 57 | const accounts = { 58 | ...this.accounts, 59 | list: this.accounts.list.map(e => e.getObj()) 60 | }; 61 | return encryptJson(accounts, mk); 62 | } 63 | add(account) { 64 | if (!this.accounts.list) this.accounts.list = []; 65 | this.accounts.list.push(account); 66 | return this; 67 | } 68 | delete(i) { 69 | this.accounts.list.splice(i, 1); 70 | return this; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "__MSG_keychain__", 3 | "description": "__MSG_description__", 4 | "default_locale": "en", 5 | "version": "1.7.6", 6 | "permissions": [ 7 | "activeTab", 8 | "declarativeContent", 9 | "storage", 10 | "webNavigation", 11 | "tabs", 12 | "https://*/*", 13 | "notifications", 14 | "idle", 15 | "contextMenus" 16 | ], 17 | "browser_action": { 18 | "default_popup": "html/popup.html", 19 | "default_icon": "images/keychain_icon_small.png" 20 | }, 21 | "icons": { 22 | "16": "images/keychain_icon_small.png", 23 | "32": "images/keychain_icon_small.png" 24 | }, 25 | "background": { 26 | "scripts": [ 27 | "vendor/crypto-js.js", 28 | "vendor/md5.min.js", 29 | "js/libs/encrypt.js", 30 | "vendor/steem.min.js", 31 | "vendor/decode.min.js", 32 | "vendor/jquery.min.js", 33 | "js/config.js", 34 | "js/libs/keychainify.js", 35 | "js/libs/account.js", 36 | "js/libs/accountsList.js", 37 | "js/libs/hf21.js", 38 | "js/libs/rpcs.js", 39 | "js/background/autolock.js", 40 | "js/background/init.js", 41 | "js/background/dialog_lifecycle.js", 42 | "js/background/errors.js", 43 | "js/background/keychainify.js", 44 | "js/background/auth.js", 45 | "js/background/ops/authority.js", 46 | "js/background/ops/broadcast.js", 47 | "js/background/ops/signTx.js", 48 | "js/background/ops/createAccount.js", 49 | "js/background/ops/customJson.js", 50 | "js/background/ops/decode.js", 51 | "js/background/ops/delegation.js", 52 | "js/background/ops/post.js", 53 | "js/background/ops/power.js", 54 | "js/background/ops/proposals.js", 55 | "js/background/ops/signBuffer.js", 56 | "js/background/ops/signedCall.js", 57 | "js/background/ops/tokens.js", 58 | "js/background/ops/transfer.js", 59 | "js/background/ops/vote.js", 60 | "js/background/ops/witness.js", 61 | "js/background/transactions.js", 62 | "js/background/context_menu.js" 63 | ], 64 | "persistent": true 65 | }, 66 | "web_accessible_resources": [ 67 | "/images/logo.png", "js/steem_keychain.js" 68 | ], 69 | "content_scripts": [ 70 | { 71 | "matches": [ 72 | "https://*/*", "http://0.0.0.0:1337/*", "http://*/*" 73 | ], 74 | "js": ["vendor/jquery.min.js", "js/web_interface.js", "js/libs/keychainify.js", "js/keychainify_content.js"] 75 | } 76 | ], 77 | "manifest_version": 2 78 | } -------------------------------------------------------------------------------- /js/background/ops/proposals.js: -------------------------------------------------------------------------------- 1 | const broadcastCreateProposal = data => { 2 | return new Promise((resolve, reject) => { 3 | let keys = {}; 4 | keys[data.typeWif] = key; 5 | createProposal( 6 | keys, 7 | data.username, 8 | data.receiver, 9 | data.start, 10 | data.end, 11 | data.daily_pay, 12 | data.subject, 13 | data.permlink, 14 | data.extensions, 15 | (err, result) => { 16 | console.log(result, err); 17 | const err_message = beautifyErrorMessage(err); 18 | const message = createMessage( 19 | err, 20 | result, 21 | data, 22 | chrome.i18n.getMessage("bgd_ops_proposal_create"), 23 | err_message 24 | ); 25 | resolve(message); 26 | } 27 | ); 28 | }); 29 | }; 30 | 31 | const broadcastUpdateProposalVote = data => { 32 | return new Promise((resolve, reject) => { 33 | let voteKeys = {}; 34 | voteKeys[data.typeWif] = key; 35 | voteForProposal( 36 | voteKeys, 37 | data.username, 38 | data.proposal_ids, 39 | data.approve, 40 | data.extensions, 41 | (err, result) => { 42 | console.log(result, err); 43 | const err_message = beautifyErrorMessage(err); 44 | const message = createMessage( 45 | err, 46 | result, 47 | data, 48 | data.approve 49 | ? chrome.i18n.getMessage("bgd_ops_proposal_vote", [ 50 | JSON.parse(data.proposal_ids)[0] 51 | ]) 52 | : chrome.i18n.getMessage("bgd_ops_proposal_unvote", [ 53 | JSON.parse(data.proposal_ids)[0] 54 | ]), 55 | err_message 56 | ); 57 | resolve(message); 58 | } 59 | ); 60 | }); 61 | }; 62 | 63 | const broadcastRemoveProposal = data => { 64 | return new Promise((resolve, reject) => { 65 | let removeProposalKeys = {}; 66 | removeProposalKeys[data.typeWif] = key; 67 | removeProposal( 68 | removeProposalKeys, 69 | data.username, 70 | data.proposal_ids, 71 | data.extensions, 72 | (err, result) => { 73 | console.log(result, err); 74 | const err_message = beautifyErrorMessage(err); 75 | const message = createMessage( 76 | err, 77 | result, 78 | data, 79 | chrome.i18n.getMessage("bgd_ops_proposal_remove", [ 80 | JSON.parse(data.proposal_ids)[0] 81 | ]), 82 | err_message 83 | ); 84 | resolve(message); 85 | } 86 | ); 87 | }); 88 | }; 89 | -------------------------------------------------------------------------------- /js/popup/power.js: -------------------------------------------------------------------------------- 1 | async function preparePowerUpDown() { 2 | const SP = numberWithCommas(await activeAccount.getSP()) + " SP"; 3 | const STEEM = numberWithCommas(await activeAccount.getSteem()) + " STEEM"; 4 | $(".power_sp").html(SP); 5 | $(".power_steem").html(STEEM); 6 | const [ 7 | withdrawn, 8 | total_withdrawing, 9 | next_vesting_withdrawal 10 | ] = await activeAccount.getPowerDown(); 11 | 12 | if (total_withdrawing !== 0 && withdrawn !== total_withdrawing) { 13 | $("#powerdown_div .power") 14 | .eq(1) 15 | .show(); 16 | $("#powering_down").html(withdrawn + " / " + total_withdrawing + " SP"); 17 | $("#powering_down").attr( 18 | "title", 19 | chrome.i18n.getMessage("popup_next_powerdown", [next_vesting_withdrawal]) 20 | ); 21 | } else { 22 | $("#powerdown_div .power") 23 | .eq(1) 24 | .hide(); 25 | } 26 | 27 | if (!activeAccount.hasKey("active")) { 28 | $("#power_up").addClass("disabled"); 29 | $("#wrap_power_up").attr("title", chrome.i18n.getMessage("popup_pu_key")); 30 | $("#power_down").addClass("disabled"); 31 | $("#wrap_power_down").attr("title", chrome.i18n.getMessage("popup_pd_key")); 32 | } else { 33 | $("#power_up").removeClass("disabled"); 34 | $("#power_down").removeClass("disabled"); 35 | $("#wrap_power_up").removeAttr("title"); 36 | $("#wrap_power_down").removeAttr("title"); 37 | } 38 | $("#power_up") 39 | .unbind("click") 40 | .click(function() { 41 | const amount = parseFloat($("#amt_pu").val()).toFixed(3) + " STEEM"; 42 | $("#power_up").hide(); 43 | $("#powerup_loading").show(); 44 | activeAccount.powerUp(amount, $("#user_pu").val(), (err, result) => { 45 | console.log(err, result); 46 | $("#power_up").show(); 47 | $("#powerup_loading").hide(); 48 | if (err) { 49 | showError(chrome.i18n.getMessage("unknown_error")); 50 | } else { 51 | showConfirm(chrome.i18n.getMessage("popup_pu_success")); 52 | loadAccount(activeAccount.getName()); 53 | } 54 | }); 55 | }); 56 | 57 | $("#power_down") 58 | .unbind("click") 59 | .click(function() { 60 | $("#power_down").hide(); 61 | $("#powerdown_loading").show(); 62 | activeAccount.powerDown($("#amt_pd").val(), function(err, result) { 63 | console.log(err, result); 64 | $("#power_down").show(); 65 | $("#powerdown_loading").hide(); 66 | if (err) { 67 | showError(chrome.i18n.getMessage("unknown_error")); 68 | } else { 69 | if ($("#amt_pd").val() !== "0") 70 | showConfirm(chrome.i18n.getMessage("popup_pd_success")); 71 | else showConfirm(chrome.i18n.getMessage("popup_pd_stop")); 72 | loadAccount(activeAccount.getName()); 73 | } 74 | }); 75 | }); 76 | } 77 | -------------------------------------------------------------------------------- /js/keychainify_content.js: -------------------------------------------------------------------------------- 1 | let contentScript = { 2 | init: function() { 3 | contentScript.process.checkAnchors(); 4 | }, 5 | 6 | /** 7 | * This contains the logic for actually processing the content on the page 8 | */ 9 | process: { 10 | initialized: false, 11 | observer: null, 12 | observerConfig: { 13 | attributes: false, 14 | childList: true, 15 | subtree: true, 16 | characterData: false 17 | }, 18 | observerTimer: null, 19 | 20 | /** 21 | * Initializing the MutationObserver to support pages with lazy-loading. 22 | * Dynamically reacts to page change instead of regular polling. 23 | */ 24 | initObserver: function() { 25 | let body = document.body; 26 | 27 | // Using a MutationObserver to wait for a DOM change 28 | // This is to scan dynamically loaded content (lazyload of comments for example) 29 | contentScript.process.observer = new MutationObserver( 30 | (function(process) { 31 | return function(mutations) { 32 | mutations.forEach(function(mutation) { 33 | // Preventing multipl calls to checkAnchors() 34 | if (process.observerTimer) { 35 | window.clearTimeout(process.observerTimer); 36 | } 37 | 38 | // Lets wait for a DOM change 39 | process.observerTimer = window.setTimeout(function() { 40 | process.checkAnchors(); 41 | }, 500); 42 | }); 43 | }; 44 | })(contentScript.process) 45 | ); 46 | 47 | // Waiting for the DOM to be modified (lazy loading) 48 | contentScript.process.observer.observe( 49 | body, 50 | contentScript.process.observerConfig 51 | ); 52 | }, 53 | 54 | /** 55 | * Verify all anchors to find scammy links 56 | */ 57 | checkAnchors: function() { 58 | let anchors = document.querySelectorAll( 59 | "a[href]:not(.steem-keychain-checked)" 60 | ); 61 | 62 | for (let i = 0; i < anchors.length; i++) { 63 | let anchor = anchors[i]; 64 | 65 | if ( 66 | anchor.href && 67 | !anchor.classList.contains("steem-keychain-checked") && // That was not checked before 68 | keychainify.isUrlSupported(anchor.href) 69 | ) { 70 | anchor.addEventListener("click", async function(e) { 71 | e.preventDefault(); 72 | 73 | if (await keychainify.isKeychainifyEnabled()) { 74 | keychainify.keychainifyUrl(this.href); 75 | } else { 76 | window.location.href = this.href; 77 | } 78 | }); 79 | } 80 | 81 | anchor.classList.add("steem-keychain-checked"); 82 | } 83 | }, 84 | 85 | /** 86 | * Initialize the scanning process 87 | */ 88 | init: function() { 89 | contentScript.process.initObserver(); 90 | contentScript.process.checkAnchors(); 91 | } 92 | } 93 | }; 94 | 95 | contentScript.init(); 96 | -------------------------------------------------------------------------------- /js/libs/rpcs.js: -------------------------------------------------------------------------------- 1 | class Rpcs { 2 | constructor() { 3 | console.log("build"); 4 | this.currentRpc = "https://api.steemit.com"; 5 | this.awaitRollback = false; 6 | this.DEFAULT_RPC_API = "https://api.steemkeychain.com/rpc"; 7 | this.list = this.initList(); 8 | } 9 | 10 | async initList() { 11 | let listRPC = []; 12 | const RPCs = ["DEFAULT", "https://api.steemit.com", "https://steemd.minnowsupportproject.org", "TESTNET"]; 13 | return new Promise(resolve => { 14 | chrome.storage.local.get(["rpc"], items => { 15 | const local = items.rpc; 16 | listRPC = local != undefined ? JSON.parse(local).concat(RPCs) : RPCs; 17 | const currentrpc = this.current_rpc || "https://api.steemit.com"; 18 | const list = [currentrpc].concat( 19 | listRPC.filter(e => { 20 | return e != currentrpc; 21 | }) 22 | ); 23 | resolve(list); 24 | }); 25 | }); 26 | } 27 | 28 | async getList() { 29 | return await this.list; 30 | } 31 | 32 | async setOptions(rpc, awaitRollback = false) { 33 | console.log("option:", rpc); 34 | if (rpc === this.currentRpc) { 35 | console.log("Same RPC"); 36 | return; 37 | } 38 | const list = await this.getList(); 39 | const newRpc = list.includes(rpc) ? rpc : this.currentRpc; 40 | if (newRpc === "TESTNET") { 41 | steem.api.setOptions({ 42 | url: "https://testnet.steemitdev.com", 43 | transport: "http", 44 | useAppbaseApi: true 45 | }); 46 | steem.config.set("address_prefix", "TST"); 47 | steem.config.set( 48 | "chain_id", 49 | "46d82ab7d8db682eb1959aed0ada039a6d49afa1602491f93dde9cac3e8e6c32" 50 | ); 51 | } else { 52 | if (newRpc === "DEFAULT") { 53 | let rpc; 54 | try { 55 | rpc = (await this.getDefaultRPC()).rpc || this.list[1]; 56 | console.log(`Using ${rpc} as default.`); 57 | } catch (e) { 58 | rpc = this.currentRpc; 59 | } 60 | console.log("rpc", rpc); 61 | steem.api.setOptions({ 62 | url: rpc, 63 | useAppbaseApi: true 64 | }); 65 | } else { 66 | console.log("a", newRpc); 67 | steem.api.setOptions({ 68 | url: newRpc, 69 | useAppbaseApi: true 70 | }); 71 | } 72 | steem.config.set("address_prefix", "STM"); 73 | steem.config.set( 74 | "chain_id", 75 | "0000000000000000000000000000000000000000000000000000000000000000" 76 | ); 77 | } 78 | this.previousRpc = this.currentRpc; 79 | this.currentRpc = newRpc; 80 | console.log(`Now using ${this.currentRpc}, previous: ${this.previousRpc}`); 81 | this.awaitRollback = awaitRollback; 82 | return; 83 | } 84 | 85 | rollback() { 86 | if (this.awaitRollback) { 87 | console.log("Rolling back to user defined rpc"); 88 | this.setOptions(this.previousRpc); 89 | this.awaitRollback = false; 90 | } 91 | return; 92 | } 93 | 94 | async getDefaultRPC() { 95 | return $.ajax({ 96 | url: this.DEFAULT_RPC_API, 97 | type: "GET" 98 | }); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /vendor/md5.min.js: -------------------------------------------------------------------------------- 1 | !function(n){"use strict";function t(n,t){var r=(65535&n)+(65535&t);return(n>>16)+(t>>16)+(r>>16)<<16|65535&r}function r(n,t){return n<>>32-t}function e(n,e,o,u,c,f){return t(r(t(t(e,n),t(u,f)),c),o)}function o(n,t,r,o,u,c,f){return e(t&r|~t&o,n,t,u,c,f)}function u(n,t,r,o,u,c,f){return e(t&o|r&~o,n,t,u,c,f)}function c(n,t,r,o,u,c,f){return e(t^r^o,n,t,u,c,f)}function f(n,t,r,o,u,c,f){return e(r^(t|~o),n,t,u,c,f)}function i(n,r){n[r>>5]|=128<>>9<<4)]=r;var e,i,a,d,h,l=1732584193,g=-271733879,v=-1732584194,m=271733878;for(e=0;e>5]>>>t%32&255);return r}function d(n){var t,r=[];for(r[(n.length>>2)-1]=void 0,t=0;t>5]|=(255&n.charCodeAt(t/8))<16&&(o=i(o,8*n.length)),r=0;r<16;r+=1)u[r]=909522486^o[r],c[r]=1549556828^o[r];return e=i(u.concat(d(t)),512+8*t.length),a(i(c.concat(e),640))}function g(n){var t,r,e="";for(r=0;r>>4&15)+"0123456789abcdef".charAt(15&t);return e}function v(n){return unescape(encodeURIComponent(n))}function m(n){return h(v(n))}function p(n){return g(m(n))}function s(n,t){return l(v(n),v(t))}function C(n,t){return g(s(n,t))}function A(n,t,r){return t?r?s(t,n):C(t,n):r?m(n):p(n)}"function"==typeof define&&define.amd?define(function(){return A}):"object"==typeof module&&module.exports?module.exports=A:n.md5=A}(this); 2 | //# sourceMappingURL=md5.min.js.map -------------------------------------------------------------------------------- /js/background/ops/authority.js: -------------------------------------------------------------------------------- 1 | const broadcastAddAccountAuthority = data => { 2 | return new Promise((resolve, reject) => { 3 | steem.broadcast.addAccountAuth( 4 | { 5 | signingKey: key, 6 | username: data.username, 7 | authorizedUsername: data.authorizedUsername, 8 | role: data.role.toLowerCase(), 9 | weight: parseInt(data.weight) 10 | }, 11 | (err, result) => { 12 | console.log(result, err); 13 | const err_message = beautifyErrorMessage(err); 14 | const message = createMessage( 15 | err, 16 | result, 17 | data, 18 | chrome.i18n.getMessage("bgd_ops_add_auth", [ 19 | data.role.toLowerCase(), 20 | data.authorizedUsername, 21 | data.username 22 | ]), 23 | err_message 24 | ); 25 | resolve(message); 26 | } 27 | ); 28 | }); 29 | }; 30 | 31 | const broadcastRemoveAccountAuthority = data => { 32 | return new Promise((resolve, reject) => { 33 | steem.broadcast.removeAccountAuth( 34 | { 35 | signingKey: key, 36 | username: data.username, 37 | authorizedUsername: data.authorizedUsername, 38 | role: data.role.toLowerCase() 39 | }, 40 | (err, result) => { 41 | console.log(result, err); 42 | const err_message = beautifyErrorMessage(err); 43 | const message = createMessage( 44 | err, 45 | result, 46 | data, 47 | chrome.i18n.getMessage("bgd_ops_remove_auth", [ 48 | data.role.toLowerCase(), 49 | data.authorizedUsername, 50 | data.username 51 | ]), 52 | err_message 53 | ); 54 | resolve(message); 55 | } 56 | ); 57 | }); 58 | }; 59 | 60 | const broadcastAddKeyAuthority = data => { 61 | return new Promise((resolve, reject) => { 62 | steem.broadcast.addKeyAuth( 63 | { 64 | signingKey: key, 65 | username: data.username, 66 | authorizedKey: data.authorizedKey, 67 | role: data.role.toLowerCase(), 68 | weight: parseInt(data.weight) 69 | }, 70 | (err, result) => { 71 | console.log(result, err); 72 | const err_message = beautifyErrorMessage(err); 73 | const message = createMessage( 74 | err, 75 | result, 76 | data, 77 | chrome.i18n.getMessage("bgd_ops_add_key_auth", [ 78 | data.authorizedKey, 79 | chrome.i18n.getMessage(data.role.toLowerCase()), 80 | data.username, 81 | data.weight 82 | ]), 83 | err_message 84 | ); 85 | resolve(message); 86 | } 87 | ); 88 | }); 89 | }; 90 | 91 | const broadcastRemoveKeyAuthority = data => { 92 | return new Promise((resolve, reject) => { 93 | steem.broadcast.removeKeyAuth( 94 | { 95 | signingKey: key, 96 | username: data.username, 97 | authorizedKey: data.authorizedKey, 98 | role: data.role.toLowerCase() 99 | }, 100 | (err, result) => { 101 | console.log(result, err); 102 | const err_message = beautifyErrorMessage(err); 103 | const message = createMessage( 104 | err, 105 | result, 106 | data, 107 | chrome.i18n.getMessage("bgd_ops_remove_key_auth", [ 108 | data.authorizedKey, 109 | chrome.i18n.getMessage(data.role.toLowerCase()), 110 | data.username 111 | ]), 112 | err_message 113 | ); 114 | resolve(message); 115 | } 116 | ); 117 | }); 118 | }; 119 | -------------------------------------------------------------------------------- /js/popup/rpc.js: -------------------------------------------------------------------------------- 1 | async function loadRPC(current_rpc) { 2 | const listRPC = await rpcs.getList(); 3 | console.log(listRPC); 4 | $("#custom_select_rpc").html(""); 5 | $("#pref_div .usernames .select-selected").remove(); 6 | $("#pref_div .usernames .select-items").remove(); 7 | 8 | $("#custom_select_rpc select").html( 9 | listRPC.reduce((acc, val) => { 10 | return acc + ""; 11 | }, "") 12 | ); 13 | $("#custom_select_rpc select").append( 14 | `` 15 | ); 16 | initiateCustomSelect(); 17 | 18 | if (current_rpc === "TESTNET") { 19 | $("#currency_send select") 20 | .children("option:first") 21 | .text("TESTS"); 22 | $("#currency_send select") 23 | .children("option:first") 24 | .val("TESTS"); 25 | $("#currency_send select") 26 | .children("option:nth-child(2)") 27 | .text("TBD"); 28 | $("#currency_send select") 29 | .children("option:nth-child(2)") 30 | .val("TBD"); 31 | $("#wallet_currency .wallet_currency") 32 | .eq(0) 33 | .text("TESTS"); 34 | $("#wallet_currency .wallet_currency") 35 | .eq(1) 36 | .text("TBD"); 37 | $("#wallet_currency .wallet_currency") 38 | .eq(2) 39 | .text("TP"); 40 | } else { 41 | $("#currency_send select") 42 | .children("option:first") 43 | .text("STEEM"); 44 | $("#currency_send select") 45 | .children("option:first") 46 | .val("STEEM"); 47 | $("#currency_send select") 48 | .children("option:nth-child(2)") 49 | .text("SBD"); 50 | $("#currency_send select") 51 | .children("option:nth-child(2)") 52 | .val("SBD"); 53 | $("#wallet_currency .wallet_currency") 54 | .eq(0) 55 | .text("STEEM"); 56 | $("#wallet_currency .wallet_currency") 57 | .eq(1) 58 | .text("SBD"); 59 | $("#wallet_currency .wallet_currency") 60 | .eq(2) 61 | .text("SP"); 62 | } 63 | } 64 | 65 | function switchRPC(rpc) { 66 | rpcs.setOptions(rpc); 67 | setRPC(rpc); 68 | chrome.storage.local.set({ 69 | current_rpc: rpc 70 | }); 71 | } 72 | 73 | function addNewRPC(rpc) { 74 | chrome.storage.local.get(["rpc"], function(items) { 75 | let customRPCs = []; 76 | if (items.rpc != undefined) customRPCs = JSON.parse(items.rpc); 77 | customRPCs.push(rpc); 78 | chrome.storage.local.set( 79 | { 80 | rpc: JSON.stringify(customRPCs) 81 | }, 82 | function() { 83 | $(".success_div") 84 | .html(chrome.i18n.getMessage("popup_rpc_added")) 85 | .show(); 86 | showCustomRPC(); 87 | setTimeout(function() { 88 | $(".success_div") 89 | .html("") 90 | .hide(); 91 | }, 5000); 92 | } 93 | ); 94 | }); 95 | } 96 | 97 | function showCustomRPC() { 98 | $("#custom_rpc").empty(); 99 | chrome.storage.local.get(["rpc"], function(items) { 100 | if (items.rpc) { 101 | let rpcs = JSON.parse(items.rpc); 102 | for (rpc of rpcs) { 103 | $("#custom_rpc").append( 104 | "
" + 105 | rpc + 106 | "
" 107 | ); 108 | } 109 | $(".deleteCustomRPC") 110 | .unbind("click") 111 | .click(function() { 112 | rpcs = rpcs.filter(e => { 113 | return ( 114 | e != 115 | $(this) 116 | .prev() 117 | .html() 118 | ); 119 | }); 120 | chrome.storage.local.set( 121 | { 122 | rpc: JSON.stringify(rpcs) 123 | }, 124 | function() { 125 | showCustomRPC(); 126 | } 127 | ); 128 | }); 129 | } 130 | }); 131 | } 132 | -------------------------------------------------------------------------------- /js/dialog/localize.js: -------------------------------------------------------------------------------- 1 | $("title").text(chrome.i18n.getMessage("keychain")); 2 | 3 | $("#error-ok").text(chrome.i18n.getMessage("dialog_ok")); 4 | $("#yes-unlock").text(chrome.i18n.getMessage("dialog_unlock")); 5 | $("#no-unlock").text(chrome.i18n.getMessage("dialog_cancel")); 6 | 7 | $("#tx_loading").prepend(chrome.i18n.getMessage("dialog_broadcasting")); 8 | 9 | // Info titles 10 | $("#username") 11 | .prev("h3") 12 | .text(chrome.i18n.getMessage("dialog_account")); 13 | $("#proposal_ids") 14 | .prev("h3") 15 | .text(chrome.i18n.getMessage("dialog_proposal_ids")); 16 | $("#approve") 17 | .prev("h3") 18 | .text(chrome.i18n.getMessage("dialog_approve")); 19 | $("#wif") 20 | .prev("h3") 21 | .text(chrome.i18n.getMessage("dialog_key")); 22 | $("#author") 23 | .prev("h3") 24 | .text(chrome.i18n.getMessage("dialog_author")); 25 | $("#receiver") 26 | .prev("h3") 27 | .text(chrome.i18n.getMessage("dialog_receiver")); 28 | $("#authorized_account") 29 | .prev("h3") 30 | .text(chrome.i18n.getMessage("dialog_auth_account")); 31 | $("#authorized_key") 32 | .prev("h3") 33 | .text(chrome.i18n.getMessage("dialog_auth_key")); 34 | $("#role") 35 | .prev("h3") 36 | .text(chrome.i18n.getMessage("dialog_role")); 37 | $("#perm") 38 | .prev("h3") 39 | .text(chrome.i18n.getMessage("dialog_permlink")); 40 | $("#weight") 41 | .prev("h3") 42 | .text(chrome.i18n.getMessage("dialog_weight")); 43 | $("#custom_key") 44 | .prev("h3") 45 | .text(chrome.i18n.getMessage("dialog_key")); 46 | $("#to") 47 | .prev("h3") 48 | .text(chrome.i18n.getMessage("dialog_to")); 49 | $("#amount") 50 | .prev("h3") 51 | .text(chrome.i18n.getMessage("dialog_amount")); 52 | $("#balance_title").text(chrome.i18n.getMessage("dialog_balance")); 53 | $("#balance_after_title").text(chrome.i18n.getMessage("dialog_future_balance")); 54 | $("#daily_pay") 55 | .prev("h3") 56 | .text(chrome.i18n.getMessage("dialog_daily_pay")); 57 | $("#memo") 58 | .prev("h3") 59 | .text(chrome.i18n.getMessage("dialog_memo")); 60 | $("#title") 61 | .prev("h3") 62 | .text(chrome.i18n.getMessage("dialog_title")); 63 | $("#permlink") 64 | .prev("h3") 65 | .text(chrome.i18n.getMessage("dialog_permlink")); 66 | $("#period_f") 67 | .prev("h3") 68 | .text(chrome.i18n.getMessage("dialog_period")); 69 | $("#extensions") 70 | .prev("h3") 71 | .text(chrome.i18n.getMessage("dialog_extensions")); 72 | $("#json_metadata") 73 | .prev("h3") 74 | .text(chrome.i18n.getMessage("dialog_meta")); 75 | $("#parent_username") 76 | .prev("h3") 77 | .text(chrome.i18n.getMessage("dialog_pu")); 78 | $("#parent_url") 79 | .prev("h3") 80 | .text(chrome.i18n.getMessage("dialog_pp")); 81 | $("#delegatee") 82 | .prev("h3") 83 | .text(chrome.i18n.getMessage("dialog_delegatee")); 84 | $("#amt_sp") 85 | .prev("h3") 86 | .text(chrome.i18n.getMessage("dialog_amount")); 87 | $("#message_sign") 88 | .prev("h3") 89 | .text(chrome.i18n.getMessage("dialog_message")); 90 | $("#witness") 91 | .prev("h3") 92 | .text(chrome.i18n.getMessage("dialog_witness")); 93 | $("#voteWit") 94 | .prev("h3") 95 | .text(chrome.i18n.getMessage("dialog_witness")); 96 | 97 | // Buttons 98 | $("#cancel").text(chrome.i18n.getMessage("dialog_cancel")); 99 | $("#proceed").text(chrome.i18n.getMessage("dialog_confirm")); 100 | 101 | //Togglable 102 | $("#body_toggle").html(chrome.i18n.getMessage("dialog_body_toggle")); 103 | $("#options_toggle").html(chrome.i18n.getMessage("dialog_options_toggle")); 104 | $("#custom_data").html(chrome.i18n.getMessage("dialog_data_toggle")); 105 | 106 | //options 107 | $("#max_accepted_payout").prepend(chrome.i18n.getMessage("dialog_max_payout")); 108 | $("#pct_sbd").prepend(chrome.i18n.getMessage("dialog_percentage_sbd")); 109 | $("#allow_votes_div").prepend(chrome.i18n.getMessage("dialog_allow_votes")); 110 | $("#curation_rewards").prepend(chrome.i18n.getMessage("dialog_allow_curation")); 111 | $("#beneficiaries_div").prepend(chrome.i18n.getMessage("dialog_beneficiaries")); 112 | -------------------------------------------------------------------------------- /js/background/transactions.js: -------------------------------------------------------------------------------- 1 | const performTransaction = async (data, tab, no_confirm) => { 2 | let message = null; 3 | try { 4 | console.log('-- PERFORMING TRANSACTION --'); 5 | console.log(data); 6 | if (data.rpc) rpc.setOptions(data.rpc, true); 7 | switch (data.type) { 8 | case "custom": 9 | message = await broadcastCustomJson(data); 10 | break; 11 | case "vote": 12 | message = await broadcastVote(data); 13 | break; 14 | case "transfer": 15 | message = await broadcastTransfer(data); 16 | break; 17 | case "post": 18 | console.log("post"); 19 | message = await broadcastPost(data); 20 | break; 21 | case "addAccountAuthority": 22 | message = await broadcastAddAccountAuthority(data); 23 | break; 24 | case "removeAccountAuthority": 25 | message = await broadcastRemoveAccountAuthority(data); 26 | break; 27 | case "addKeyAuthority": 28 | message = await broadcastAddKeyAuthority(data); 29 | break; 30 | case "removeKeyAuthority": 31 | message = await broadcastRemoveKeyAuthority(data); 32 | break; 33 | case "broadcast": 34 | message = await broadcastData(data); 35 | break; 36 | case "createClaimedAccount": 37 | message = await broadcastCreateClaimedAccount(data); 38 | break; 39 | case "signedCall": 40 | message = await broadcastSignedCall(data); 41 | break; 42 | case "delegation": 43 | message = await broadcastDelegation(data); 44 | break; 45 | case "witnessVote": 46 | message = await broadcastWitnessVote(data); 47 | break; 48 | case "powerUp": 49 | message = await broadcastPowerUp(data); 50 | break; 51 | case "powerDown": 52 | message = await broadcastPowerDown(data); 53 | break; 54 | case "sendToken": 55 | message = await broadcastSendToken(data); 56 | break; 57 | case "createProposal": 58 | message = await broadcastCreateProposal(data); 59 | break; 60 | case "updateProposalVote": 61 | message = await broadcastUpdateProposalVote(data); 62 | break; 63 | case "removeProposal": 64 | message = await broadcastRemoveProposal(data); 65 | break; 66 | case "decode": 67 | message = await decodeMessage(data); 68 | break; 69 | case "signBuffer": 70 | message = await signBuffer(data); 71 | break; 72 | case "signTx": 73 | message = await signTx(data); 74 | break; 75 | } 76 | 77 | chrome.tabs.sendMessage(tab, message); 78 | } catch (e) { 79 | console.log("error", e); 80 | sendErrors( 81 | tab, 82 | e, 83 | chrome.i18n.getMessage("unknown_error"), 84 | chrome.i18n.getMessage("unknown_error"), 85 | data 86 | ); 87 | } finally { 88 | if (no_confirm) { 89 | if (id_win != null) { 90 | removeWindow(id_win); 91 | } 92 | } else chrome.runtime.sendMessage(message); 93 | key = null; 94 | accounts = new AccountsList(); 95 | rpc.rollback(); 96 | } 97 | }; 98 | 99 | const createMessage = ( 100 | err, 101 | result, 102 | data, 103 | success_message, 104 | fail_message, 105 | publicKey 106 | ) => { 107 | return { 108 | command: "answerRequest", 109 | msg: { 110 | success: !err, 111 | error: err, 112 | result: result, 113 | data: data, 114 | message: !err ? success_message : fail_message, 115 | request_id, 116 | publicKey 117 | } 118 | }; 119 | }; 120 | 121 | const beautifyErrorMessage = err => { 122 | console.log(err); 123 | if (!err) return null; 124 | let error = err.message.split("xception:")[1].replace(".rethrow", "."); 125 | if (error.replace(" ", "") === "") 126 | return chrome.i18n.getMessage("unknown_error"); 127 | return `${chrome.i18n.getMessage("bgd_ops_error")} : ${error}`; 128 | }; 129 | -------------------------------------------------------------------------------- /js/popup/settings.js: -------------------------------------------------------------------------------- 1 | // For accounts settings 2 | 3 | // Change master password 4 | $("#confirm_change_pwd").click(function() { 5 | if (mk === $("#old_pwd").val()) { 6 | if ($("#new_pwd").val() === $("#confirm_new_pwd").val()) { 7 | if ( 8 | !$("#new_pwd") 9 | .val() 10 | .match(/^(.{0,7}|[^0-9]*|[^A-Z]*|[^a-z]*|[a-zA-Z0-9]*)$/) 11 | ) { 12 | mk = $("#new_pwd").val(); 13 | accountsList.save(mk); 14 | sendMk(mk); 15 | initializeVisibility(); 16 | showConfirm(chrome.i18n.getMessage("popup_master_changed")); 17 | } else showError(chrome.i18n.getMessage("popup_pwd_stronger")); 18 | } else showError(chrome.i18n.getMessage("popup_pwd_match")); 19 | } else showError(chrome.i18n.getMessage("popup_wrong_current_pwd")); 20 | }); 21 | 22 | // Set "remember choice" Preferences 23 | function setPreferences(name) { 24 | chrome.storage.local.get(["no_confirm"], function(items) { 25 | try { 26 | const pref = JSON.parse(items.no_confirm); 27 | $("#pref").html(""); 28 | if (!pref[name] || Object.keys(pref[name]).length == 0) 29 | $("#pref").html(chrome.i18n.getMessage("popup_html_no_pref")); 30 | for (let obj in pref[name]) { 31 | $("#pref").append( 32 | `

${chrome.i18n.getMessage("popup_website")}: ${obj}

` 33 | ); 34 | var display_names = { 35 | broadcast: chrome.i18n.getMessage("popup_broadcast"), 36 | signTx: chrome.i18n.getMessage("popup_sign_tx"), 37 | addAccountAuthority: chrome.i18n.getMessage("popup_add_auth"), 38 | removeAccountAuthority: chrome.i18n.getMessage("popup_remove_auth"), 39 | custom: chrome.i18n.getMessage("popup_custom"), 40 | decode: chrome.i18n.getMessage("popup_decode"), 41 | signBuffer: chrome.i18n.getMessage("popup_sign_buffer"), 42 | signedCall: chrome.i18n.getMessage("popup_signed_call"), 43 | post: chrome.i18n.getMessage("popup_post"), 44 | vote: chrome.i18n.getMessage("popup_vote") 45 | }; 46 | var site_container = $( 47 | '
' 48 | ); 49 | for (let sub in pref[name][obj]) { 50 | site_container.append( 51 | "
" + 52 | display_names[sub] + 53 | "
" 60 | ); 61 | } 62 | 63 | $("#pref").append(site_container); 64 | } 65 | 66 | $(".deletePref").click(function() { 67 | const user = $(this) 68 | .attr("id") 69 | .split(",")[0]; 70 | const domain = $(this) 71 | .attr("id") 72 | .split(",")[1]; 73 | const type = $(this) 74 | .attr("id") 75 | .split(",")[2]; 76 | delete pref[user][domain][type]; 77 | if (Object.keys(pref[user][domain]).length == 0) 78 | delete pref[user][domain]; 79 | chrome.storage.local.set( 80 | { 81 | no_confirm: JSON.stringify(pref) 82 | }, 83 | function() { 84 | setPreferences(name); 85 | } 86 | ); 87 | }); 88 | } catch (e) {} 89 | }); 90 | } 91 | 92 | $("#import_keys").click(() => { 93 | importKeys(); 94 | }); 95 | 96 | const importKeys = () => { 97 | chrome.windows.getCurrent(w => { 98 | chrome.windows.create( 99 | { 100 | url: chrome.runtime.getURL("html/import.html"), 101 | type: "popup", 102 | height: 566, 103 | focused: true, 104 | width: 350, 105 | left: w.width - 350 + w.left, 106 | top: w.top 107 | }, 108 | w => w.update() 109 | ); 110 | }); 111 | }; 112 | 113 | $("#export_keys").click(() => { 114 | var data = new Blob([accountsList.encrypt(mk)], {type: "text/plain"}); 115 | var url = window.URL.createObjectURL(data); 116 | const a = document.createElement("a"); 117 | a.href = url; 118 | a.download = "accounts.kc"; 119 | a.click(); 120 | }); 121 | -------------------------------------------------------------------------------- /js/background/context_menu.js: -------------------------------------------------------------------------------- 1 | const contextMenus = { 2 | menuActions: { 3 | contexts: { 4 | link: { 5 | transferToUser: { 6 | description: chrome.i18n.getMessage("bgd_context_transfer_user"), 7 | action: "transferToUser" 8 | }, 9 | memoMessageUser: { 10 | description: chrome.i18n.getMessage("bgd_context_memo_user"), 11 | action: "memoMessageUser" 12 | }, 13 | tipUser: { 14 | description: chrome.i18n.getMessage("bgd_context_tip_user"), 15 | action: "tipUser" 16 | } 17 | }, 18 | page: { 19 | transferToUser: { 20 | description: chrome.i18n.getMessage("bgd_context_transfer_author"), 21 | action: "transferToUser" 22 | }, 23 | memoMessageUser: { 24 | description: chrome.i18n.getMessage("bgd_context_memo_author"), 25 | action: "memoMessageUser" 26 | }, 27 | tipUser: { 28 | description: chrome.i18n.getMessage("bgd_context_tip_author"), 29 | action: "tipUser" 30 | } 31 | } 32 | }, 33 | 34 | actions: { 35 | transferToUser: function(url) { 36 | const user = url 37 | .split("@") 38 | .pop() 39 | .split("/")[0]; 40 | createPopup(null, `html/popup.html?page=send_div&to=${user}&noback=1`); 41 | }, 42 | memoMessageUser: function(url) { 43 | const user = url 44 | .split("@") 45 | .pop() 46 | .split("/")[0]; 47 | createPopup( 48 | null, 49 | `html/popup.html?page=send_div&to=${user}&amount=0.001&noback=1` 50 | ); 51 | }, 52 | tipUser: function(url) { 53 | const user = url 54 | .split("@") 55 | .pop() 56 | .split("/")[0]; 57 | console.log(url, user); 58 | createPopup( 59 | null, 60 | // @TODO make a settings UI for customizing default MEMO message and tipping amount 61 | `html/popup.html?page=send_div&to=${user}&amount=1&memo=I+appreciate+your+work+and+this+is+a+little+symbolic+tip+to+show+support.&noback=1` 62 | ); 63 | } 64 | } 65 | }, 66 | 67 | init: function() { 68 | console.log("initializing context menu"); 69 | const contexts = ["link", "page"]; 70 | 71 | for (let i = 0; i < contexts.length; i++) { 72 | const context = contexts[i]; 73 | 74 | for (let menuActionName in contextMenus.menuActions.contexts[context]) { 75 | if ( 76 | contextMenus.menuActions.contexts[context].hasOwnProperty( 77 | menuActionName 78 | ) 79 | ) { 80 | const menuAction = 81 | contextMenus.menuActions.contexts[context][menuActionName]; 82 | const title = menuAction.description; 83 | 84 | if (context === "link") { 85 | contextMenus.menuActions.contexts[context][ 86 | menuActionName 87 | ].menuItemId = chrome.contextMenus.create({ 88 | title: title, 89 | contexts: [context], 90 | onclick: contextMenus.genericOnClick, 91 | targetUrlPatterns: ["https://*/@*"] // @TODO make a better filter, maybe with a whitelist of known dApps 92 | }); 93 | } else if (context === "page") { 94 | contextMenus.menuActions.contexts[context][ 95 | menuActionName 96 | ].menuItemId = chrome.contextMenus.create({ 97 | title: title, 98 | contexts: [context], 99 | onclick: contextMenus.genericOnClick, 100 | documentUrlPatterns: ["https://*/@*", "https://*/*/@*"] // @TODO make a better filter, maybe with a whitelist of known dApps 101 | }); 102 | } 103 | } 104 | } 105 | } 106 | }, 107 | 108 | genericOnClick: function(info, tab) { 109 | for (let context in contextMenus.menuActions.contexts) { 110 | if (contextMenus.menuActions.contexts.hasOwnProperty(context)) { 111 | for (let menuName in contextMenus.menuActions.contexts[context]) { 112 | if ( 113 | contextMenus.menuActions.contexts[context].hasOwnProperty(menuName) 114 | ) { 115 | const menuAction = 116 | contextMenus.menuActions.contexts[context][menuName]; 117 | if (menuAction.menuItemId === info.menuItemId) { 118 | const url = info[context + "Url"]; 119 | contextMenus.menuActions.actions[menuAction.action]( 120 | url, 121 | menuAction 122 | ); 123 | } 124 | } 125 | } 126 | } 127 | } 128 | } 129 | }; 130 | contextMenus.init(); 131 | -------------------------------------------------------------------------------- /js/background/init.js: -------------------------------------------------------------------------------- 1 | let mk = null; 2 | let id_win = null; 3 | let key = null; 4 | let confirmed = false; 5 | let tab = null; 6 | let request = null; 7 | let request_id = null; 8 | let accountsList = new AccountsList(); 9 | let timeoutIdle = null; 10 | let autolock = null; 11 | let interval = null; 12 | let rpc = new Rpcs(); 13 | // Lock after the browser is idle for more than 10 minutes 14 | 15 | chrome.storage.local.get(["current_rpc", "autolock"], function(items) { 16 | if (items.autolock) startAutolock(JSON.parse(items.autolock)); 17 | rpc.setOptions(items.current_rpc || "DEFAULT"); 18 | }); 19 | 20 | //Listen to the other parts of the extension 21 | 22 | const chromeMessageHandler = (msg, sender, sendResp) => { 23 | // Send mk upon request from the extension popup. 24 | restartIdleCounterIfNeeded(autolock, msg); 25 | switch (msg.command) { 26 | case "getMk": 27 | chrome.runtime.sendMessage({ 28 | command: "sendBackMk", 29 | mk: mk 30 | }); 31 | break; 32 | case "stopInterval": 33 | clearInterval(interval); 34 | break; 35 | case "setRPC": 36 | rpc.setOptions(msg.rpc); 37 | break; 38 | case "sendMk": 39 | //Receive mk from the popup (upon registration or unlocking) 40 | mk = msg.mk; 41 | break; 42 | case "sendAutolock": 43 | startAutolock(JSON.parse(msg.autolock)); 44 | break; 45 | case "sendRequest": 46 | // Receive request (website -> content_script -> background) 47 | // create a window to let users confirm the transaction 48 | tab = sender.tab.id; 49 | checkBeforeCreate(msg.request, tab, msg.domain); 50 | request = msg.request; 51 | request_id = msg.request_id; 52 | break; 53 | case "unlockFromDialog": 54 | // Receive unlock request from dialog 55 | unlockFromDialog(msg); 56 | break; 57 | case "acceptTransaction": 58 | if (msg.keep) saveNoConfirm(msg); 59 | confirmed = true; 60 | performTransaction(msg.data, msg.tab, false); 61 | // upon receiving the confirmation from user, perform the transaction and notify content_script. Content script will then notify the website. 62 | break; 63 | case "importKeys": 64 | try { 65 | chrome.storage.local.get(["accounts"], function(items) { 66 | const decrypt = decryptToJson(items.accounts, mk); 67 | if (!decrypt) 68 | chrome.runtime.sendMessage({ 69 | command: "importResult", 70 | result: false 71 | }); 72 | accountsList.init(decrypt); 73 | const accounts = decryptToJson(msg.fileData, mk); 74 | console.log(accounts); 75 | accountsList.import(accounts.list, mk); 76 | chrome.runtime.sendMessage({ 77 | command: "importResult", 78 | result: true 79 | }); 80 | }); 81 | } catch (e) { 82 | console.log(e); 83 | chrome.runtime.sendMessage({ 84 | command: "importResult", 85 | result: false 86 | }); 87 | } 88 | break; 89 | } 90 | }; 91 | 92 | const saveNoConfirm = msg => { 93 | chrome.storage.local.get(["no_confirm"], function(items) { 94 | let keep = 95 | items.no_confirm == null || items.no_confirm == undefined 96 | ? {} 97 | : JSON.parse(items.no_confirm); 98 | if (keep[msg.data.username] == undefined) { 99 | keep[msg.data.username] = {}; 100 | } 101 | if (keep[msg.data.username][msg.domain] == undefined) { 102 | keep[msg.data.username][msg.domain] = {}; 103 | } 104 | keep[msg.data.username][msg.domain][msg.data.type] = true; 105 | chrome.storage.local.set({ 106 | no_confirm: JSON.stringify(keep) 107 | }); 108 | }); 109 | }; 110 | 111 | const unlockFromDialog = msg => { 112 | chrome.storage.local.get(["accounts"], function(items) { 113 | if (!items.accounts) { 114 | sendErrors( 115 | msg.tab, 116 | "no_wallet", 117 | chrome.i18n.getMessage("bgd_init_no_wallet"), 118 | "", 119 | msg.data 120 | ); 121 | } else { 122 | if (decryptToJson(items.accounts, msg.mk) != null) { 123 | mk = msg.mk; 124 | startAutolock(autolock); 125 | checkBeforeCreate(msg.data, msg.tab, msg.domain); 126 | } else { 127 | chrome.runtime.sendMessage({ 128 | command: "wrongMk" 129 | }); 130 | } 131 | } 132 | }); 133 | }; 134 | 135 | const restartIdleCounterIfNeeded = (autolock, msg) => { 136 | if ( 137 | autolock != null && 138 | autolock.type == "idle" && 139 | (msg.command == "getMk" || 140 | msg.command == "setRPC" || 141 | msg.command == "sendMk" || 142 | msg.command == "sendRequest" || 143 | msg.command == "acceptTransaction" || 144 | msg.command == "ping") 145 | ) 146 | restartIdleCounter(); 147 | }; 148 | 149 | chrome.runtime.onMessage.addListener(chromeMessageHandler); 150 | -------------------------------------------------------------------------------- /js/popup/delegate.js: -------------------------------------------------------------------------------- 1 | const prepareDelegationTab = async () => { 2 | $("#send_del") 3 | .unbind("click") 4 | .click(function() { 5 | $("#send_del").hide(); 6 | $("#del_loading").show(); 7 | activeAccount.delegateSP( 8 | $("#amt_del").val(), 9 | $("#username_del").val(), 10 | function(err, result) { 11 | console.log(err, result); 12 | $("#send_del").show(); 13 | $("#del_loading").hide(); 14 | if (err) { 15 | showError(chrome.i18n.getMessage("unknown_error")); 16 | } else { 17 | showConfirm(chrome.i18n.getMessage("popup_delegate_success")); 18 | loadAccount(activeAccount.getName()); 19 | } 20 | } 21 | ); 22 | }); 23 | const [delegatees, delegators] = [ 24 | await activeAccount.getDelegatees(), 25 | await activeAccount.getDelegators() 26 | ]; 27 | console.log(delegatees, delegators); 28 | if (!activeAccount.hasKey("active")) { 29 | $("#send_del").addClass("disabled"); 30 | $("#wrap_send_del").attr( 31 | "title", 32 | chrome.i18n.getMessage("popup_delegate_key") 33 | ); 34 | $("#edit_del").addClass("disabled"); 35 | $("#wrap_edit_del").attr( 36 | "title", 37 | chrome.i18n.getMessage("popup_delegate_key") 38 | ); 39 | } else { 40 | $("#send_del").removeClass("disabled"); 41 | $("#edit_del").removeClass("disabled"); 42 | $("#wrap_edit_del").removeAttr("title"); 43 | $("#wrap_send_del").removeAttr("title"); 44 | } 45 | 46 | displayDelegationMain(delegators, delegatees); 47 | displayOutgoingDelegations(delegatees); 48 | displayIncomingDelegations(delegators); 49 | }; 50 | 51 | function getSumOutgoing(delegatees) { 52 | return delegatees.reduce(function(total, elt) { 53 | return total + parseFloat(elt.sp); 54 | }, 0); 55 | } 56 | 57 | function getSumIncoming(delegators) { 58 | return delegators.reduce(function(total, elt) { 59 | return total + parseFloat(elt.sp); 60 | }, 0); 61 | } 62 | 63 | const displayIncomingDelegations = delegators => { 64 | const sumIncoming = getSumIncoming(delegators); 65 | delegators = delegators.sort(function(a, b) { 66 | return b.sp - a.sp; 67 | }); 68 | $("#total_incoming span") 69 | .eq(1) 70 | .html(numberWithCommas(sumIncoming.toFixed(3)) + " SP"); 71 | $("#list_incoming").empty(); 72 | for (delegator of delegators) { 73 | $("#list_incoming").append( 74 | "
@" + 75 | delegator.delegator + 76 | "" + 77 | numberWithCommas(delegator.sp) + 78 | "
" 79 | ); 80 | } 81 | }; 82 | 83 | const displayDelegationMain = async (delegators, delegatees) => { 84 | const sumIncoming = getSumIncoming(delegators); 85 | const sumOutgoing = getSumOutgoing(delegatees); 86 | $("#incoming_del").html("+ " + numberWithCommas(sumIncoming.toFixed(3))); 87 | $("#outgoing_del").html("- " + numberWithCommas(sumOutgoing.toFixed(3))); 88 | $("#available_del").html( 89 | numberWithCommas( 90 | ((await activeAccount.getSP()) - 5 - sumOutgoing).toFixed(3) 91 | ) 92 | ); 93 | }; 94 | 95 | const displayOutgoingDelegations = delegatees => { 96 | const sumOutgoing = getSumOutgoing(delegatees); 97 | $("#total_outgoing span") 98 | .eq(1) 99 | .html("- " + numberWithCommas(sumOutgoing.toFixed(3)) + " SP"); 100 | $("#list_outgoing").empty(); 101 | for (delegatee of delegatees) { 102 | $("#list_outgoing").append( 103 | "
@" + 104 | delegatee.delegatee + 105 | "" + 106 | numberWithCommas(delegatee.sp) + 107 | "
" 108 | ); 109 | } 110 | 111 | $(".line_outgoing img") 112 | .unbind("click") 113 | .click(function() { 114 | $("#outgoing_del_div").hide(); 115 | $("#edit_del_div").show(); 116 | let that = $(this); 117 | let this_delegatee = delegatees.filter(function(elt) { 118 | return ( 119 | elt.delegatee == 120 | that 121 | .parent() 122 | .children() 123 | .eq(0) 124 | .html() 125 | .replace("@", "") 126 | ); 127 | }); 128 | showEditDiv(this_delegatee); 129 | }); 130 | }; 131 | 132 | const showEditDiv = async delegatees => { 133 | const delegatee = delegatees[0]; 134 | $("#this_outgoing_del").html( 135 | numberWithCommas(parseFloat(delegatee.sp)) + " SP" 136 | ); 137 | $("#this_available_del").html( 138 | numberWithCommas( 139 | ( 140 | parseFloat( 141 | $("#available_del") 142 | .html() 143 | .replace(",", "") 144 | ) + parseFloat(delegatee.sp) 145 | ).toFixed(3) 146 | ) + " SP" 147 | ); 148 | $("#username_del span").html(delegatee.delegatee); 149 | $("#edit_del") 150 | .unbind("click") 151 | .click(function() { 152 | $("#edit_del").hide(); 153 | $("#edit_del_loading").show(); 154 | activeAccount.delegateSP( 155 | $("#amt_edit_del").val(), 156 | delegatee.delegatee, 157 | function(err, result) { 158 | console.log(err, result); 159 | $("#edit_del").show(); 160 | $("#edit_del_loading").hide(); 161 | if (err) { 162 | showError(chrome.i18n.getMessage("unknown_error")); 163 | } else { 164 | showConfirm( 165 | chrome.i18n.getMessage("popup_delegate_change_success") 166 | ); 167 | loadAccount(activeAccount.getName()); 168 | $("#edit_del_div").hide(); 169 | $("#outgoing_del_div").show(); 170 | } 171 | } 172 | ); 173 | }); 174 | }; 175 | -------------------------------------------------------------------------------- /js/popup/witness.js: -------------------------------------------------------------------------------- 1 | let witness_ranks = null; 2 | 3 | async function prepareWitnessDiv(witness_votes, proxy) { 4 | witness_ranks = await getWitnessRanks(); 5 | $("#votes_remaining span").html(30 - witness_votes.length); 6 | if (proxy != "") { 7 | $("#proxy div").html(`${chrome.i18n.getMessage("popup_proxy")}: @${proxy}`); 8 | $("#proxy").show(); 9 | } else $("#proxy").hide(); 10 | if (!activeAccount.hasKey("active")) $("#proxy div").addClass("no_active"); 11 | else $("#proxy div").removeClass("no_active"); 12 | 13 | $("#list_wit").empty(); 14 | 15 | if (witness_votes) { 16 | for (wit of witness_votes) { 17 | const isActive = 18 | witness_ranks && witness_ranks.find(e => e.name == wit) 19 | ? "active" 20 | : "disabled"; 21 | $("#list_wit").append( 22 | "
@" + 23 | wit + 24 | "" + 25 | isActive + 26 | "
" 29 | ); 30 | } 31 | } 32 | 33 | $("#top100_div").empty(); 34 | 35 | if (witness_ranks) { 36 | let i = 0; 37 | for (wit of witness_ranks) { 38 | const isVoted = witness_votes.includes(wit.name) 39 | ? "wit-vote wit-voted" 40 | : "wit-vote wit-not-voted"; 41 | i++; 42 | if (i <= 100) 43 | $("#top100_div").append( 44 | "
" + 45 | wit.rank + 46 | "@" + 47 | wit.name + 48 | "
" 51 | ); 52 | } 53 | } 54 | 55 | if (!activeAccount.hasKey("active")) $(".wit-vote").addClass("no_cursor"); 56 | else $(".wit-vote").removeClass("no_cursor"); 57 | 58 | $("#proxy div") 59 | .unbind("click") 60 | .click(function() { 61 | $("#proxy").hide(); 62 | steem.broadcast.accountWitnessProxy( 63 | activeAccount.getKey("active"), 64 | activeAccount.getName(), 65 | "", 66 | function(err, result) { 67 | console.log(err, result); 68 | } 69 | ); 70 | }); 71 | 72 | $(".wit-vote") 73 | .unbind("click") 74 | .click(function() { 75 | const voted_wit = $(this).hasClass("wit-voted"); 76 | const that = this; 77 | console.log(voted_wit); 78 | $(that).addClass("wit-loading"); 79 | steem.broadcast.accountWitnessVote( 80 | activeAccount.getKey("active"), 81 | activeAccount.getName(), 82 | $(this) 83 | .prev() 84 | .html() 85 | .replace("@", ""), 86 | $(this).hasClass("wit-voted") ? 0 : 1, 87 | function(err, result) { 88 | console.log(err, result); 89 | $(that).removeClass("wit-loading"); 90 | if (err == null) { 91 | if (voted_wit) { 92 | console.log("unvoted"); 93 | $(that).removeClass("wit-voted"); 94 | $(that).addClass("wit-not-voted"); 95 | $("#votes_remaining span").html( 96 | parseInt($("#votes_remaining span").html()) + 1 97 | ); 98 | } else { 99 | console.log("voted"); 100 | $(that).removeClass("wit-not-voted"); 101 | $(that).addClass("wit-voted"); 102 | $("#votes_remaining span").html( 103 | parseInt($("#votes_remaining span").html()) - 1 104 | ); 105 | } 106 | } 107 | } 108 | ); 109 | }); 110 | 111 | $(".witness-row img") 112 | .unbind("click") 113 | .click(function() { 114 | const acc = $(this) 115 | .parent() 116 | .find(".witName") 117 | .html() 118 | .replace("@", ""); 119 | const that = this; 120 | $(that).attr("src", "../images/loading.gif"); 121 | steem.broadcast.accountWitnessVote( 122 | activeAccount.getKey("active"), 123 | activeAccount.getName(), 124 | acc, 125 | 0, 126 | function(err, result) { 127 | $(that).attr("src", "../images/delete.png"); 128 | if (err == null) { 129 | showConfirm( 130 | chrome.i18n.getMessage("popup_success_unvote_wit", [acc]) 131 | ); 132 | loadAccount(activeAccount.getName()); 133 | } else showError(chrome.i18n.getMessage("unknown_error")); 134 | } 135 | ); 136 | }); 137 | 138 | $("#vote_wit") 139 | .unbind("click") 140 | .click(function() { 141 | $("#vote_wit").hide(); 142 | $("#wit_loading").show(); 143 | if ($("#witness_div select option:selected").val() === "Wit") { 144 | steem.broadcast.accountWitnessVote( 145 | activeAccount.getKey("active"), 146 | activeAccount.getName(), 147 | $("#wit-username").val(), 148 | 1, 149 | function(err, result) { 150 | $("#vote_wit").show(); 151 | $("#wit_loading").hide(); 152 | if (!err) { 153 | showConfirm( 154 | chrome.i18n.getMessage("popup_success_wit", [ 155 | $("#wit-username").val() 156 | ]) 157 | ); 158 | loadAccount(activeAccount.getName()); 159 | } else showError(chrome.i18n.getMessage("unknown_error")); 160 | } 161 | ); 162 | } else { 163 | steem.broadcast.accountWitnessProxy( 164 | activeAccount.getKey("active"), 165 | activeAccount.getName(), 166 | $("#wit-username").val(), 167 | function(err, result) { 168 | $("#wit_loading").hide(); 169 | $("#vote_wit").show(); 170 | if (!err) { 171 | showConfirm( 172 | chrome.i18n.getMessage("popup_success_proxy", [ 173 | $("#wit-username").val() 174 | ]) 175 | ); 176 | loadAccount(activeAccount.getName()); 177 | } else { 178 | console.log(err); 179 | showError(chrome.i18n.getMessage("unknown_error")); 180 | } 181 | } 182 | ); 183 | } 184 | }); 185 | } 186 | -------------------------------------------------------------------------------- /js/libs/keychainify.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @type {{requestTransfer: keychainify.requestTransfer, initBackground: keychainify.initBackground, isKeychainifyEnabled: (function(): Promise), getVarsFromURL: (function(*)), requestWitnessVote: keychainify.requestWitnessVote, keychainifyUrl: keychainify.keychainifyUrl, requestDelegation: keychainify.requestDelegation, dispatchRequest: keychainify.dispatchRequest}} 4 | */ 5 | const keychainify = { 6 | /** 7 | * Checks local storage for whether the feature has been disabled by the user 8 | * @returns {Promise} 9 | */ 10 | isKeychainifyEnabled: function() { 11 | return new Promise(function(resolve, reject) { 12 | try { 13 | chrome.storage.local.get(["keychainify_enabled"], function(items) { 14 | const featureStatus = 15 | items.hasOwnProperty("keychainify_enabled") && 16 | items.keychainify_enabled; 17 | resolve(featureStatus); 18 | }); 19 | } catch (err) { 20 | reject(err); 21 | } 22 | }); 23 | }, 24 | 25 | isUrlSupported: function(url) { 26 | return url.includes("steemconnect.com/sign/transfer"); 27 | }, 28 | 29 | /** 30 | * Transform a known URL to a Keychain operation 31 | * @param tab 32 | */ 33 | keychainifyUrl: function(tab) { 34 | let url; 35 | if (typeof tab === "string") { 36 | url = tab; 37 | tab = null; 38 | } else { 39 | url = tab.url; 40 | } 41 | 42 | const vars = keychainify.getVarsFromURL(url); 43 | let payload = {}, 44 | defaults = {}; 45 | 46 | switch (true) { 47 | /** 48 | * Transfer fund 49 | */ 50 | case (url.includes("steemconnect.com/sign/transfer")): 51 | defaults = { 52 | from: null, 53 | to: null, 54 | amount: 0, 55 | memo: "", 56 | currency: "STEEM" 57 | }; 58 | 59 | payload = Object.assign(defaults, vars); 60 | 61 | [payload.amount, payload.currency] = vars.amount.split(" "); 62 | keychainify.requestTransfer( 63 | tab, 64 | payload.from, 65 | payload.to, 66 | payload.amount, 67 | payload.memo, 68 | payload.currency 69 | ); 70 | break; 71 | 72 | /** 73 | * Delegate Steem Power 74 | */ 75 | case (url.includes("steemconnect.com/sign/delegate-vesting-shares")): 76 | // @TODO currently Steem Keychain does not allow null delegator account. Awaiting https://github.com/MattyIce/steem-keychain/issues/101 to continue 77 | //let [amount, unit] = vars.vesting_shares.split(' '); 78 | //keychainify.requestDelegation(null, vars.delegatee, amount, unit, null); 79 | window.location.href = url; 80 | break; 81 | 82 | case (url.includes("steemconnect.com/sign/account-witness-vote")): 83 | // @TODO currently Steem Keychain does not allow null voter account. Awaiting https://github.com/MattyIce/steem-keychain/issues/101 to continue 84 | //keychainify.requestWitnessVote(null, vars.witness, vars.approve); 85 | window.location.href = url; 86 | break; 87 | } 88 | }, 89 | 90 | /** 91 | * Dispatch a Keychain operation 92 | * @param tab 93 | * @param request 94 | */ 95 | dispatchRequest: function(tab, request) { 96 | const now = new Date().getTime(); 97 | 98 | if (tab) { 99 | chromeMessageHandler( 100 | { 101 | command: "sendRequest", 102 | request: request, 103 | domain: window.location.hostname, 104 | request_id: now 105 | }, 106 | { 107 | tab: tab 108 | } 109 | ); 110 | } else { 111 | chrome.runtime.sendMessage({ 112 | command: "sendRequest", 113 | request: request, 114 | domain: window.location.hostname, 115 | request_id: now 116 | }); 117 | } 118 | }, 119 | 120 | /** 121 | * Requesting a Keychain transfer operation 122 | * @param tab 123 | * @param account 124 | * @param to 125 | * @param amount 126 | * @param memo 127 | * @param currency 128 | * @param enforce 129 | */ 130 | requestTransfer: function( 131 | tab, 132 | account, 133 | to, 134 | amount, 135 | memo, 136 | currency, 137 | enforce = false 138 | ) { 139 | const request = { 140 | type: "transfer", 141 | username: account, 142 | to: to, 143 | amount: amount, 144 | memo: memo, 145 | enforce: enforce, 146 | currency: currency 147 | }; 148 | 149 | keychainify.dispatchRequest(tab, request); 150 | }, 151 | 152 | /** 153 | * Requesting a Keychain witness vote operation 154 | * @param tab 155 | * @param username 156 | * @param witness 157 | * @param vote 158 | */ 159 | requestWitnessVote: function(tab, username, witness, vote) { 160 | var request = { 161 | type: "witnessVote", 162 | username: username, 163 | witness: witness, 164 | vote: vote 165 | }; 166 | keychainify.dispatchRequest(tab, equest); 167 | }, 168 | 169 | /** 170 | * Requesting a Keychain delegation operation 171 | * @param tab 172 | * @param username 173 | * @param delegatee 174 | * @param amount 175 | * @param unit 176 | */ 177 | requestDelegation: function(tab, username, delegatee, amount, unit) { 178 | const request = { 179 | type: "delegation", 180 | username: username, 181 | delegatee: delegatee, 182 | amount: amount, 183 | unit: unit 184 | }; 185 | 186 | keychainify.dispatchRequest(tab, request); 187 | }, 188 | 189 | /** 190 | * Parsing the query string 191 | * @param url 192 | */ 193 | getVarsFromURL: function(url) { 194 | const argsParsed = {}; 195 | 196 | if (url.indexOf("?") !== -1) { 197 | const query = url.split("?").pop(); 198 | const args = query.split("&"); 199 | let arg, kvp, key, value; 200 | 201 | for (let i = 0; i < args.length; i++) { 202 | arg = args[i]; 203 | if (arg.indexOf("=") === -1) { 204 | argsParsed[decodeURIComponent(arg)] = true; 205 | } else { 206 | kvp = arg.split("="); 207 | key = decodeURIComponent(kvp[0]); 208 | value = decodeURIComponent(kvp[1]); 209 | argsParsed[key] = value; 210 | } 211 | } 212 | } 213 | 214 | return argsParsed; 215 | } 216 | }; 217 | -------------------------------------------------------------------------------- /html/dialog.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /js/libs/account.js: -------------------------------------------------------------------------------- 1 | class Account { 2 | constructor(obj) { 3 | this.account = obj || {}; 4 | } 5 | init() { 6 | this.info = steem.api.getAccountsAsync([this.account.name]); 7 | this.props = new GlobalProps(); 8 | this.delegatees = getDelegatees(this.account.name); 9 | this.delegators = getDelegators(this.account.name); 10 | } 11 | getObj() { 12 | return this.account; 13 | } 14 | getName() { 15 | return this.account.name; 16 | } 17 | getKeys() { 18 | return this.account.keys; 19 | } 20 | getKey(key) { 21 | return this.account.keys[key]; 22 | } 23 | hasKey(key) { 24 | return this.account.keys.hasOwnProperty(key); 25 | } 26 | setKey(key, val) { 27 | this.account.keys[key] = val; 28 | } 29 | deleteKey(key) { 30 | delete this.account.keys[key]; 31 | delete this.account.keys[`${key}Pubkey`]; 32 | } 33 | async getAccountInfos() { 34 | return (await this.info)[0]; 35 | } 36 | async getAccountInfo(key) { 37 | const info = (await this.info)[0]; 38 | return info[key]; 39 | } 40 | async getAvailableRewards() { 41 | this.reward_sbd = await this.getAccountInfo("reward_sbd_balance"); 42 | this.reward_vests = await this.getAccountInfo("reward_vesting_balance"); 43 | const reward_sp = (await this.toSP(this.reward_vests)) + " SP"; 44 | this.reward_steem = await this.getAccountInfo("reward_steem_balance"); 45 | let rewardText = chrome.i18n.getMessage("popup_account_redeem") + ":
"; 46 | if (getValFromString(reward_sp) != 0) rewardText += reward_sp + " / "; 47 | if (getValFromString(this.reward_sbd) != 0) 48 | rewardText += this.reward_sbd + " / "; 49 | if (getValFromString(this.reward_steem) != 0) 50 | rewardText += this.reward_steem + " / "; 51 | rewardText = rewardText.slice(0, -3); 52 | return [this.reward_sbd, reward_sp, this.reward_steem, rewardText]; 53 | } 54 | async toSP(vests) { 55 | return steem.formatter 56 | .vestToSteem( 57 | vests, 58 | await this.props.getProp("total_vesting_shares"), 59 | await this.props.getProp("total_vesting_fund_steem") 60 | ) 61 | .toFixed(3); 62 | } 63 | 64 | claimRewards(callback) { 65 | steem.broadcast.claimRewardBalance( 66 | this.getKey("posting"), 67 | this.getName(), 68 | this.reward_steem, 69 | this.reward_sbd, 70 | this.reward_vests, 71 | callback 72 | ); 73 | } 74 | 75 | async getVotingMana() { 76 | const vm = await getVotingMana(await this.getAccountInfos()); 77 | const full = getTimeBeforeFull(vm * 100); 78 | return [vm, full]; 79 | } 80 | 81 | async getSteem() { 82 | return (await this.getAccountInfo("balance")).replace(" STEEM", ""); 83 | } 84 | 85 | async getSBD() { 86 | return (await this.getAccountInfo("sbd_balance")).replace(" SBD", ""); 87 | } 88 | 89 | async getSP() { 90 | return await this.toSP( 91 | (await this.getAccountInfo("vesting_shares")).replace(" VESTS", "") 92 | ); 93 | } 94 | 95 | async getRC() { 96 | return await getRC(this.account.name); 97 | } 98 | 99 | async getVotingDollars(percentage) { 100 | return await getVotingDollarsPerAccount( 101 | percentage, 102 | await this.getAccountInfos(), 103 | (await this.props.getFund("reward_balance")).replace("STEEM", ""), 104 | (await this.props.getFund("recent_claims")).replace("STEEM", ""), 105 | await this.props.getSteemPrice(), 106 | await this.props.getProp("vote_power_reserve_rate"), 107 | false 108 | ); 109 | } 110 | 111 | async getAccountValue() { 112 | const [steem, sbd] = await this.props.getPrices(); 113 | return ( 114 | numberWithCommas( 115 | "$ " + 116 | ( 117 | sbd * parseInt(await this.getSBD()) + 118 | steem * 119 | (parseInt(await this.getSP()) + parseInt(await this.getSteem())) 120 | ).toFixed(2) 121 | ) + "\t USD" 122 | ); 123 | } 124 | 125 | async getTransfers() { 126 | const result = await steem.api.getAccountHistoryAsync( 127 | this.getName(), 128 | -1, 129 | 1000 130 | ); 131 | let transfers = result.filter(tx => tx[1].op[0] === "transfer"); 132 | transfers = transfers.slice(-10).reverse(); 133 | return transfers; 134 | } 135 | 136 | async getPowerDown() { 137 | const totalSteem = Number( 138 | (await this.props.getProp("total_vesting_fund_steem")).split(" ")[0] 139 | ); 140 | const totalVests = Number( 141 | (await this.props.getProp("total_vesting_shares")).split(" ")[0] 142 | ); 143 | const withdrawn = ( 144 | (((await this.getAccountInfo("withdrawn")) / totalVests) * totalSteem) / 145 | 1000000 146 | ).toFixed(0); 147 | const total_withdrawing = ( 148 | (((await this.getAccountInfo("to_withdraw")) / totalVests) * totalSteem) / 149 | 1000000 150 | ).toFixed(0); 151 | const next_vesting_withdrawal = await this.getAccountInfo( 152 | "next_vesting_withdrawal" 153 | ); 154 | return [withdrawn, total_withdrawing, next_vesting_withdrawal]; 155 | } 156 | 157 | async powerDown(sp, callback) { 158 | const totalSteem = Number( 159 | (await this.props.getProp("total_vesting_fund_steem")).split(" ")[0] 160 | ); 161 | const totalVests = Number( 162 | (await this.props.getProp("total_vesting_shares")).split(" ")[0] 163 | ); 164 | let vestingShares = (parseFloat(sp) * totalVests) / totalSteem; 165 | vestingShares = vestingShares.toFixed(6); 166 | vestingShares = vestingShares.toString() + " VESTS"; 167 | 168 | steem.broadcast.withdrawVesting( 169 | this.getKey("active"), 170 | this.getName(), 171 | vestingShares, 172 | callback 173 | ); 174 | } 175 | 176 | powerUp(amount, to, callback) { 177 | steem.broadcast.transferToVesting( 178 | this.getKey("active"), 179 | this.getName(), 180 | to, 181 | amount, 182 | callback 183 | ); 184 | } 185 | 186 | async getDelegatees() { 187 | const that = this; 188 | let delegatees = await this.delegatees; 189 | delegatees = delegatees.filter(function(elt) { 190 | return elt.vesting_shares != 0; 191 | }); 192 | if (delegatees.length > 0) 193 | delegatees = await Promise.all( 194 | delegatees.map(async elt => { 195 | elt.sp = parseFloat( 196 | await this.toSP( 197 | parseFloat(elt.vesting_shares.replace(" VESTS", "")) 198 | ) 199 | ).toFixed(3); 200 | return elt; 201 | }) 202 | ); 203 | return delegatees; 204 | } 205 | async getDelegators() { 206 | const that = this; 207 | let delegators = (await this.delegators) || []; 208 | delegators = delegators.filter(function(elt) { 209 | return elt.vesting_shares != 0; 210 | }); 211 | if (delegators.length > 0) 212 | delegators = await Promise.all( 213 | delegators.map(async elt => { 214 | const sp = await that.toSP(elt.vesting_shares + " VESTS"); 215 | elt.sp = parseFloat(sp).toFixed(3); 216 | return elt; 217 | }) 218 | ); 219 | return delegators; 220 | } 221 | async delegateSP(amount, to, callback) { 222 | const totalSteem = Number( 223 | (await this.props.getProp("total_vesting_fund_steem")).split(" ")[0] 224 | ); 225 | const totalVests = Number( 226 | (await this.props.getProp("total_vesting_shares")).split(" ")[0] 227 | ); 228 | let delegated_vest = (parseFloat(amount) * totalVests) / totalSteem; 229 | delegated_vest = delegated_vest.toFixed(6); 230 | delegated_vest = delegated_vest.toString() + " VESTS"; 231 | steem.broadcast.delegateVestingShares( 232 | activeAccount.getKey("active"), 233 | activeAccount.getName(), 234 | to, 235 | delegated_vest, 236 | callback 237 | ); 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /js/background/auth.js: -------------------------------------------------------------------------------- 1 | const checkBeforeCreate = (request, tab, domain) => { 2 | if (mk == null) { 3 | // Check if locked 4 | const callback = () => { 5 | console.log("locked"); 6 | chrome.runtime.sendMessage({ 7 | command: "sendDialogError", 8 | msg: { 9 | success: false, 10 | error: "locked", 11 | result: null, 12 | data: request, 13 | message: chrome.i18n.getMessage("bgd_auth_locked"), 14 | display_msg: chrome.i18n.getMessage("bgd_auth_locked_desc") 15 | }, 16 | tab: tab, 17 | domain: domain 18 | }); 19 | }; 20 | createPopup(callback); 21 | } else { 22 | chrome.storage.local.get( 23 | ["accounts", "no_confirm", "current_rpc"], 24 | function(items) { 25 | const {memo, username, type, enforce} = request; 26 | // Check user 27 | if (!items.accounts) { 28 | createPopup(() => { 29 | sendErrors( 30 | tab, 31 | "no_wallet", 32 | chrome.i18n.getMessage("bgd_init_no_wallet"), 33 | "", 34 | request 35 | ); 36 | }); 37 | } else { 38 | // Check that user and wanted keys are in the wallet 39 | accountsList.init(decryptToJson(items.accounts, mk)); 40 | let account = null; 41 | if (type === "transfer") { 42 | let tr_accounts = accountsList 43 | .getList() 44 | .filter(e => e.hasKey("active")) 45 | .map(e => e.getName()); 46 | console.log(tr_accounts, "a"); 47 | 48 | const encode = memo && memo.length > 0 && memo[0] == "#"; 49 | const enforced = enforce || encode; 50 | if (encode) account = accountsList.get(username); 51 | // If a username is specified, check that its active key has been added to the wallet 52 | if ( 53 | enforced && 54 | username && 55 | !accountsList.get(username).hasKey("active") 56 | ) { 57 | createPopup(() => { 58 | console.log("error1"); 59 | sendErrors( 60 | tab, 61 | "user_cancel", 62 | chrome.i18n.getMessage("bgd_auth_canceled"), 63 | chrome.i18n.getMessage("bgd_auth_transfer_no_active", [ 64 | username 65 | ]), 66 | request 67 | ); 68 | }); 69 | } else if (encode && !account.hasKey("memo")) { 70 | createPopup(() => { 71 | console.log("error2"); 72 | sendErrors( 73 | tab, 74 | "user_cancel", 75 | chrome.i18n.getMessage("bgd_auth_canceled"), 76 | chrome.i18n.getMessage("bgd_auth_transfer_no_memo", [ 77 | username 78 | ]), 79 | request 80 | ); 81 | }); 82 | } else if (tr_accounts.length == 0) { 83 | createPopup(() => { 84 | console.log("error3"); 85 | sendErrors( 86 | tab, 87 | "user_cancel", 88 | chrome.i18n.getMessage("bgd_auth_canceled"), 89 | chrome.i18n.getMessage("bgd_auth_transfer_no_active", [ 90 | username 91 | ]), 92 | request 93 | ); 94 | }); 95 | } else { 96 | console.log("b", tr_accounts); 97 | const callback = () => { 98 | chrome.runtime.sendMessage({ 99 | command: "sendDialogConfirm", 100 | data: request, 101 | domain, 102 | accounts: tr_accounts, 103 | tab, 104 | testnet: items.current_rpc === "TESTNET" 105 | }); 106 | }; 107 | createPopup(callback); 108 | } 109 | } else { 110 | if (!accountsList.get(username)) { 111 | const callback = () => { 112 | console.log("error4"); 113 | sendErrors( 114 | tab, 115 | "user_cancel", 116 | chrome.i18n.getMessage("bgd_auth_canceled"), 117 | chrome.i18n.getMessage("bgd_auth_no_account", [username]), 118 | request 119 | ); 120 | }; 121 | createPopup(callback); 122 | } else { 123 | account = accountsList.get(username); 124 | let typeWif = getRequiredWifType(request); 125 | let req = request; 126 | req.key = typeWif; 127 | 128 | if (req.type == "custom") req.method = typeWif; 129 | 130 | if (req.type == "broadcast") { 131 | req.typeWif = typeWif; 132 | } 133 | 134 | if (!account.hasKey(typeWif)) { 135 | createPopup(() => { 136 | console.log("error5"); 137 | sendErrors( 138 | tab, 139 | "user_cancel", 140 | chrome.i18n.getMessage("bgd_auth_canceled"), 141 | chrome.i18n.getMessage("bgd_auth_no_key", [ 142 | username, 143 | typeWif 144 | ]), 145 | request 146 | ); 147 | }); 148 | } else { 149 | public = account.getKey(`${typeWif}Pubkey`); 150 | key = account.getKey(typeWif); 151 | if ( 152 | !hasNoConfirm( 153 | items.no_confirm, 154 | req, 155 | domain, 156 | items.current_rpc 157 | ) 158 | ) { 159 | const callback = () => { 160 | chrome.runtime.sendMessage({ 161 | command: "sendDialogConfirm", 162 | data: req, 163 | domain, 164 | tab, 165 | testnet: items.current_rpc === "TESTNET" 166 | }); 167 | }; 168 | createPopup(callback); 169 | // Send the request to confirmation window 170 | } else { 171 | chrome.runtime.sendMessage({ 172 | command: "broadcastingNoConfirm" 173 | }); 174 | performTransaction(req, tab, true); 175 | } 176 | } 177 | } 178 | } 179 | } 180 | } 181 | ); 182 | } 183 | }; 184 | 185 | const hasNoConfirm = (arr, data, domain, current_rpc) => { 186 | try { 187 | if ( 188 | data.method == "active" || data.method == "Active" || 189 | arr == undefined || 190 | current_rpc === "TESTNET" || 191 | domain === "steemit.com" 192 | ) { 193 | return false; 194 | } else return JSON.parse(arr)[data.username][domain][data.type] == true; 195 | } catch (e) { 196 | console.log(e); 197 | return false; 198 | } 199 | }; 200 | 201 | // Get the key needed for each type of transaction 202 | const getRequiredWifType = request => { 203 | switch (request.type) { 204 | case "decode": 205 | case "signBuffer": 206 | return request.method.toLowerCase(); 207 | break; 208 | case "post": 209 | case "vote": 210 | return "posting"; 211 | break; 212 | case "custom": 213 | return request.method == null || request.method == undefined 214 | ? "posting" 215 | : request.method.toLowerCase(); 216 | break; 217 | case "addAccountAuthority": 218 | case "removeAccountAuthority": 219 | case "removeKeyAuthority": 220 | case "addKeyAuthority": 221 | case "broadcast": 222 | case "signTx": 223 | return request.method.toLowerCase(); 224 | case "signedCall": 225 | return request.typeWif.toLowerCase(); 226 | case "transfer": 227 | return "active"; 228 | break; 229 | case "sendToken": 230 | return "active"; 231 | break; 232 | case "delegation": 233 | return "active"; 234 | break; 235 | case "witnessVote": 236 | return "active"; 237 | break; 238 | case "powerUp": 239 | return "active"; 240 | break; 241 | case "powerDown": 242 | return "active"; 243 | break; 244 | case "createClaimedAccount": 245 | return "active"; 246 | break; 247 | case "createProposal": 248 | return "active"; 249 | break; 250 | case "removeProposal": 251 | return "active"; 252 | break; 253 | case "updateProposalVote": 254 | return "active"; 255 | break; 256 | } 257 | }; 258 | -------------------------------------------------------------------------------- /example/main.js: -------------------------------------------------------------------------------- 1 | // Send Handshake event 2 | $("#sw-handshake").click(function() { 3 | steem_keychain.requestHandshake(function() { 4 | console.log("Handshake received!"); 5 | }); 6 | }); 7 | 8 | // All transactions are sent via a swRequest event. 9 | 10 | // Send decryption request 11 | $("#send_decode").click(function() { 12 | steem_keychain.requestVerifyKey( 13 | $("#decode_user").val(), 14 | $("#decode_message").val(), 15 | $("#decode_method option:selected").text(), 16 | function(response) { 17 | console.log("main js response - verify key"); 18 | console.log(response); 19 | } 20 | ); 21 | }); 22 | 23 | // Send post request 24 | $("#send_post").click(function() { 25 | steem_keychain.requestPost( 26 | $("#post_username").val(), 27 | $("#post_title").val(), 28 | $("#post_body").val(), 29 | $("#post_pp").val(), 30 | $("#post_pu").val(), 31 | $("#post_json").val(), 32 | $("#post_perm").val(), 33 | $("#comment_options").val(), 34 | function(response) { 35 | console.log("main js response - post"); 36 | console.log(response); 37 | } 38 | ); 39 | }); 40 | 41 | // Send vote request 42 | $("#send_vote").click(function() { 43 | steem_keychain.requestVote( 44 | $("#vote_username").val(), 45 | $("#vote_perm").val(), 46 | $("#vote_author").val(), 47 | $("#vote_weight").val(), 48 | function(response) { 49 | console.log("main js response - vote"); 50 | console.log(response); 51 | } 52 | ); 53 | }); 54 | 55 | // Send Custom JSON request 56 | $("#send_custom").click(function() { 57 | console.log("click"); 58 | steem_keychain.requestCustomJson( 59 | $("#custom_username").val(), 60 | $("#custom_id").val(), 61 | $("#custom_method option:selected").text(), 62 | $("#custom_json").val(), 63 | $("#custom_message").val(), 64 | function(response) { 65 | console.log("main js response - custom JSON"); 66 | console.log(response); 67 | }, 68 | $("#custom_rpc").val() 69 | ); 70 | }); 71 | 72 | // Send transfer request 73 | $("#send_tra").click(function() { 74 | console.log("transfer"); 75 | steem_keychain.requestTransfer( 76 | $("#transfer_username").val(), 77 | $("#transfer_to").val(), 78 | $("#transfer_val").val(), 79 | $("#transfer_memo").val(), 80 | $("#transfer_currency option:selected").text(), 81 | function(response) { 82 | console.log("main js response - transfer"); 83 | console.log(response); 84 | }, 85 | $("#transfer_enforce").is(":checked") 86 | ); 87 | }); 88 | 89 | // Send tokens request 90 | $("#sendTokens").click(function() { 91 | steem_keychain.requestSendToken( 92 | $("#tokens_username").val(), 93 | $("#tokens_to").val(), 94 | $("#tokens_qt").val(), 95 | $("#tokens_memo").val(), 96 | $("#tokens_unit").val(), 97 | function(response) { 98 | console.log("main js response - tokens"); 99 | console.log(response); 100 | } 101 | ); 102 | }); 103 | 104 | // Send delegation 105 | $("#send_delegation").click(function() { 106 | steem_keychain.requestDelegation( 107 | $("#delegation_username").val(), 108 | $("#delegation_delegatee").val(), 109 | $("#delegation_sp").val(), 110 | $("#delegation_unit option:selected").text(), 111 | function(response) { 112 | console.log("main js response - delegation"); 113 | console.log(response); 114 | } 115 | ); 116 | }); 117 | 118 | $("#send_signature").click(function() { 119 | steem_keychain.requestSignBuffer( 120 | $("#sign_username").val(), 121 | $("#sign_message").val(), 122 | $("#sign_method option:selected").text(), 123 | function(response) { 124 | console.log("main js response - sign"); 125 | console.log(response); 126 | } 127 | ); 128 | }); 129 | 130 | $("#send_addauth").click(function() { 131 | steem_keychain.requestAddAccountAuthority( 132 | $("#addauth_username").val(), 133 | $("#addauth_authorized_username").val(), 134 | $("#addauth_role option:selected").text(), 135 | $("#addauth_weight").val(), 136 | function(response) { 137 | console.log("main js response - add auth"); 138 | console.log(response); 139 | } 140 | ); 141 | }); 142 | 143 | $("#send_removeauth").click(function() { 144 | steem_keychain.requestRemoveAccountAuthority( 145 | $("#removeauth_username").val(), 146 | $("#removeauth_authorized_username").val(), 147 | $("#removeauth_role option:selected").text(), 148 | function(response) { 149 | console.log("main js response - remove auth"); 150 | console.log(response); 151 | } 152 | ); 153 | }); 154 | 155 | $("#send_addkey").click(function() { 156 | console.log("add key"); 157 | steem_keychain.requestAddKeyAuthority( 158 | $("#addkey_username").val(), 159 | $("#addkey_authorized_key").val(), 160 | $("#addkey_role option:selected").text(), 161 | $("#addkey_weight").val(), 162 | function(response) { 163 | console.log("main js response - add auth key"); 164 | console.log(response); 165 | } 166 | ); 167 | }); 168 | 169 | $("#send_removekey").click(function() { 170 | steem_keychain.requestRemoveKeyAuthority( 171 | $("#removekey_username").val(), 172 | $("#removekey_authorized_key").val(), 173 | $("#removekey_role option:selected").text(), 174 | function(response) { 175 | console.log("main js response - remove auth key"); 176 | console.log(response); 177 | } 178 | ); 179 | }); 180 | 181 | $("#send_broadcast").click(function() { 182 | steem_keychain.requestBroadcast( 183 | $("#broadcast_username").val(), 184 | $("#broadcast_operations").val(), 185 | $("#broadcast_method option:selected").text(), 186 | function(response) { 187 | console.log("main js response - broadcast"); 188 | console.log(response); 189 | } 190 | ); 191 | }); 192 | 193 | $("#send_signed_call").click(function() { 194 | steem_keychain.requestSignedCall( 195 | $("#signed_call_username").val(), 196 | $("#signed_call_method").val(), 197 | JSON.parse($("#signed_call_params").val()), 198 | $("#signed_call_key_type option:selected").text(), 199 | function(response) { 200 | console.log("main js response - signed call"); 201 | console.log(response); 202 | } 203 | ); 204 | }); 205 | 206 | $("#send_witness_vote").click(function() { 207 | steem_keychain.requestWitnessVote( 208 | $("#witness_username").val(), 209 | $("#witness").val(), 210 | $("#vote_wit").is(":checked"), 211 | function(response) { 212 | console.log("main js response - witness vote"); 213 | console.log(response); 214 | } 215 | ); 216 | }); 217 | 218 | $("#send_pu").click(function() { 219 | steem_keychain.requestPowerUp( 220 | $("#pu_username").val(), 221 | $("#pu_recipient").val(), 222 | $("#pu_steem").val(), 223 | function(response) { 224 | console.log("main js response - power up"); 225 | console.log(response); 226 | } 227 | ); 228 | }); 229 | 230 | $("#send_pd").click(function() { 231 | steem_keychain.requestPowerDown( 232 | $("#pd_username").val(), 233 | $("#pd_sp").val(), 234 | function(response) { 235 | console.log("main js response - power down"); 236 | console.log(response); 237 | } 238 | ); 239 | }); 240 | 241 | $("#send_create_claimed").click(function() { 242 | steem_keychain.requestCreateClaimedAccount( 243 | $("#create_claimed_username").val(), 244 | $("#create_claimed_new_username").val(), 245 | $("#create_claimed_owner").val(), 246 | $("#create_claimed_active").val(), 247 | $("#create_claimed_posting").val(), 248 | $("#create_claimed_memo").val(), 249 | function(response) { 250 | console.log("main js response - create claimed account"); 251 | console.log(response); 252 | } 253 | ); 254 | }); 255 | 256 | $("#send_cp").click(function() { 257 | steem_keychain.requestCreateProposal( 258 | $("#cp_username").val(), 259 | $("#cp_receiver").val(), 260 | $("#cp_subject").val(), 261 | $("#cp_permlink").val(), 262 | $("#cp_daily_pay").val(), 263 | $("#cp_start").val(), 264 | $("#cp_end").val(), 265 | $("#cp_extensions").val(), 266 | function(response) { 267 | console.log("main js response - create proposal"); 268 | console.log(response); 269 | } 270 | ); 271 | }); 272 | 273 | $("#send_rp").click(function() { 274 | steem_keychain.requestRemoveProposal( 275 | $("#rp_username").val(), 276 | $("#rp_proposal_ids").val(), 277 | $("#cp_extensions").val(), 278 | function(response) { 279 | console.log("main js response - remove proposal"); 280 | console.log(response); 281 | } 282 | ); 283 | }); 284 | 285 | $("#send_vp").click(function() { 286 | steem_keychain.requestUpdateProposalVote( 287 | $("#vp_username").val(), 288 | $("#vp_proposal_ids").val(), 289 | $("#vp_approve").is(":checked"), 290 | $("#vp_extensions").val(), 291 | function(response) { 292 | console.log("main js response - update proposal votes"); 293 | console.log(response); 294 | } 295 | ); 296 | }); 297 | -------------------------------------------------------------------------------- /css/dialog.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: Futura; 3 | src: url("../fonts/Futura-Boo.otf"); 4 | font-weight: 400; 5 | } 6 | 7 | html { 8 | width: 350px; 9 | height: 566px; 10 | overflow: hidden; 11 | border-radius: 1px; 12 | margin: 0 !important; 13 | padding: 0 !important; 14 | background-image: linear-gradient(328deg, #3df9b4, #00d3cc, #a779ff) !important; 15 | background-repeat: no-repeat; 16 | background-attachment: fixed; 17 | top: 0; 18 | left: 0; 19 | position: relative; 20 | word-wrap: break-word; 21 | } 22 | 23 | body { 24 | height: 100%; 25 | font-family: Futura !important; 26 | font-weight: 400; 27 | padding: 0 !important; 28 | width: 100%; 29 | margin: 0 !important; 30 | overflow: hidden; 31 | } 32 | 33 | .cloud { 34 | background: url("../images/bg-steam@2x.png") no-repeat; 35 | background-size: cover; 36 | width: 350px; 37 | height: 436px; 38 | position: absolute; 39 | bottom: 0; 40 | left: 0; 41 | z-index: 1; 42 | } 43 | 44 | .modal-content { 45 | width: 100%; 46 | height: 100%; 47 | box-sizing: border-box !important; 48 | position: absolute; 49 | margin: 0 !important; 50 | padding: 20px 32px !important; 51 | line-height: 22px; 52 | font-size: 16px; 53 | text-align: left; 54 | text-overflow: clip; 55 | white-space: normal; 56 | color: white; 57 | z-index: 2; 58 | } 59 | 60 | h2 { 61 | display: inline-block; 62 | margin: 10px 0 20px 0; 63 | width: 100%; 64 | font-size: 20px; 65 | line-height: 25px; 66 | text-transform: uppercase; 67 | } 68 | 69 | h2::before { 70 | content: ""; 71 | width: 41.21px; 72 | height: 30px; 73 | opacity: 0.8; 74 | background-image: url("../images/keychain_icon2.png"); 75 | background-repeat: no-repeat; 76 | background-size: 41.21px 30px; 77 | padding-right: 60px; 78 | vertical-align: 0%; 79 | background-size: contain; 80 | } 81 | 82 | h3.dialog-message { 83 | text-align: center; 84 | margin-top: 0; 85 | margin-bottom: 10px; 86 | background-color: rgba(0, 156, 156, 0.4); 87 | padding: 5px 0 10px 0; 88 | } 89 | 90 | h3 .small { 91 | text-transform: none; 92 | font-size: 80%; 93 | cursor: pointer; 94 | } 95 | 96 | #modal-body-msg .msg-data>div, 97 | #modal-body-msg .msg-data>h3, 98 | #modal-body-msg .msg-data>img, 99 | #transfer_acct_list { 100 | display: none; 101 | } 102 | 103 | #error_dialog { 104 | margin-bottom: 40px; 105 | } 106 | 107 | .modal-body-error { 108 | width: 100%; 109 | box-sizing: border-box; 110 | } 111 | 112 | .input_container { 113 | width: 100%; 114 | position: relative; 115 | padding: 0; 116 | margin: 0; 117 | } 118 | 119 | .input_img { 120 | position: absolute; 121 | bottom: 30.07px; 122 | left: 20.41px; 123 | width: 23.85px; 124 | height: 29.93px; 125 | } 126 | 127 | .input_container input { 128 | width: 100%; 129 | box-sizing: border-box; 130 | height: 48.43px; 131 | margin-bottom: 20px; 132 | background: rgba(255, 255, 255, 0.65); 133 | border: 1px solid rgba(255, 255, 255, 0.65); 134 | padding-left: 65px; 135 | border-radius: 5px; 136 | color: #009C9C; 137 | font-size: 20px; 138 | } 139 | 140 | button { 141 | border-radius: 5px; 142 | width: 100%; 143 | height: 48.43px; 144 | text-align: center; 145 | font-size: 20px; 146 | line-height: 28px; 147 | color: white; 148 | font-family: Futura; 149 | text-transform: uppercase; 150 | background-color: rgba(0, 156, 156); 151 | border: solid 1px rgba(0, 156, 156); 152 | cursor: pointer; 153 | } 154 | 155 | #yes-unlock { 156 | margin-bottom: 20px; 157 | } 158 | 159 | .unlock { 160 | display: none; 161 | } 162 | 163 | #modal-body-msg {} 164 | 165 | #modal-body-msg .msg-data { 166 | max-height: 285px; 167 | overflow-y: scroll; 168 | background-color: rgba(0, 156, 156, 0.8); 169 | padding: 10px; 170 | } 171 | 172 | ::-webkit-scrollbar { 173 | width: 0px; 174 | /* remove scrollbar space */ 175 | background: transparent; 176 | /* optional: just make scrollbar invisible */ 177 | } 178 | 179 | /* optional: show position indicator in red */ 180 | ::-webkit-scrollbar-thumb { 181 | background: #FF0000; 182 | } 183 | 184 | #modal-body-msg h3 { 185 | font-size: 16px; 186 | line-height: 16px; 187 | text-transform: uppercase; 188 | margin: 0 0 5px 0; 189 | } 190 | 191 | #modal-body-msg .msg-data div { 192 | font-size: 16px; 193 | line-height: 16px; 194 | font-style: italic; 195 | margin-bottom: 20px; 196 | } 197 | 198 | .keep_checkbox { 199 | display: block; 200 | height: 37px; 201 | margin-bottom: 25px; 202 | } 203 | 204 | /* The container */ 205 | .checkbox_container { 206 | display: block; 207 | position: relative; 208 | padding-left: 55px; 209 | font-size: 16px; 210 | line-height: 16px; 211 | cursor: pointer; 212 | -webkit-user-select: none; 213 | -moz-user-select: none; 214 | -ms-user-select: none; 215 | user-select: none; 216 | } 217 | 218 | .checkbox_container div { 219 | margin-top: 5px; 220 | color: #006060; 221 | } 222 | 223 | .checkbox_container div:first-of-type { 224 | color: white; 225 | margin-top: 0px; 226 | text-transform: uppercase; 227 | } 228 | 229 | /* Hide the browser's default checkbox */ 230 | .checkbox_container input { 231 | position: absolute; 232 | opacity: 0; 233 | cursor: pointer; 234 | } 235 | 236 | /* Create a custom checkbox */ 237 | .checkmark { 238 | position: absolute; 239 | top: 7px; 240 | left: 0; 241 | height: 32px; 242 | width: 32px; 243 | background-color: rgba(255, 255, 255, 0.65); 244 | } 245 | 246 | /* Create the checkmark/indicator (hidden when not checked) */ 247 | .checkmark:after { 248 | content: ""; 249 | position: absolute; 250 | display: none; 251 | } 252 | 253 | /* Show the checkmark when checked */ 254 | .checkbox_container input:checked~.checkmark:after { 255 | display: block; 256 | } 257 | 258 | /* Style the checkmark/indicator */ 259 | .checkbox_container .checkmark:after { 260 | left: 11.52px; 261 | top: 6.4px; 262 | width: 6.4px; 263 | height: 11.8px; 264 | border: solid #009C9C; 265 | border-width: 0 3px 3px 0; 266 | -webkit-transform: rotate(45deg); 267 | -ms-transform: rotate(45deg); 268 | transform: rotate(45deg); 269 | } 270 | 271 | #confirm_footer { 272 | display: none; 273 | width: 100%; 274 | min-height: 169px; 275 | position: absolute; 276 | box-sizing: border-box; 277 | bottom: 0; 278 | background-color: rgba(0, 156, 156, 0.8); 279 | margin-left: -32px; 280 | padding: 10px 32px 60px 32px; 281 | } 282 | 283 | #confirm_footer button { 284 | width: 138px; 285 | } 286 | 287 | #confirm_footer button:first-of-type { 288 | margin-right: 6px; 289 | background-color: #006060; 290 | } 291 | 292 | #proceed:hover { 293 | background-color: #917FC6; 294 | } 295 | 296 | #keep_label { 297 | font-size: 16px; 298 | line-height: 16px; 299 | text-transform: none; 300 | } 301 | 302 | #tx_loading { 303 | text-align: center; 304 | } 305 | 306 | #tx_loading img { 307 | width: 50px; 308 | } 309 | 310 | .custom-select { 311 | position: relative; 312 | width: 100% !important; 313 | line-height: 2; 314 | height: 48.43px; 315 | margin-bottom: 20px; 316 | border: 1px solid rgba(0, 96, 96, 0.29); 317 | border-radius: 5px; 318 | margin-top: 20px; 319 | } 320 | 321 | .custom-select select { 322 | display: none; 323 | /*hide original SELECT element:*/ 324 | } 325 | 326 | .select-selected { 327 | background-color: rgba(0, 96, 96, 0.29); 328 | padding-left: 20px !important; 329 | } 330 | 331 | /*style the arrow inside the select element:*/ 332 | .select-selected:after { 333 | position: absolute; 334 | content: ""; 335 | top: 22px; 336 | right: 10px; 337 | width: 0; 338 | height: 0; 339 | border: 10px solid transparent; 340 | border-color: #88F7FB transparent transparent transparent; 341 | } 342 | 343 | /*point the arrow upwards when the select box is open (active):*/ 344 | .select-selected.select-arrow-active:after { 345 | border-color: transparent transparent #88F7FB transparent; 346 | top: 11px; 347 | } 348 | 349 | /*style the items (options), including the selected item:*/ 350 | .select-items div, 351 | .select-selected { 352 | box-sizing: border-box; 353 | color: #88F7FB; 354 | font-family: Futura; 355 | font-size: 20px; 356 | margin-bottom: 0px; 357 | line-height: 28px; 358 | padding: 9px 0px 8px 20px; 359 | border: 1px solid transparent; 360 | /*border-color: transparent transparent rgba(0, 0, 0, 0.1) transparent;*/ 361 | cursor: pointer; 362 | border-radius: 3px; 363 | } 364 | 365 | /*style items (options):*/ 366 | .select-items { 367 | position: absolute; 368 | background-color: rgba(0, 96, 96, 0.84); 369 | top: 100%; 370 | left: 0; 371 | margin-bottom: 0; 372 | right: 0; 373 | z-index: 99; 374 | } 375 | 376 | .select-items div { 377 | margin-bottom: 0; 378 | } 379 | 380 | /*hide the items when the select box is closed:*/ 381 | .select-hide { 382 | display: none; 383 | } 384 | 385 | .select-items div:hover, 386 | .same-as-selected { 387 | background-color: rgba(0, 96, 96, 1); 388 | } 389 | 390 | .balance_loading { 391 | width: 15px 392 | } 393 | 394 | #steemit { 395 | display: none 396 | } 397 | 398 | #balance, #balance_after { 399 | display: none 400 | } -------------------------------------------------------------------------------- /example/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Steem Keychain 6 | 7 | 8 | 9 |

Handshake

10 | 11 |

Decode memo

12 | 13 | 18 | 19 | 20 |

Post

21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |

Vote

31 | 32 | 33 | 34 | 35 | 36 |

Custom_JSON

37 | 38 | 39 | 43 | 44 | 45 | 46 | 47 |

Transfer

48 | 49 | 50 | 51 | 52 | 53 | 57 | 58 |

Sign message

59 | 60 | 61 | 66 | 67 |

Add Account Authority

68 | 69 | 70 | 74 | 75 | 76 |

Remove Account Authority

77 | 78 | 79 | 83 | 84 |

Add Key Authority

85 | 86 | 87 | 91 | 92 | 93 |

Remove Key Authority

94 | 95 | 96 | 100 | 101 |

Broadcast

102 | 103 | 104 | 109 | 110 |

Broadcast Create New claimed account

111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 |

Signed Call

119 | 120 | 121 | 122 | 127 | 128 | 129 |

Delegate

130 | 131 | 132 | 133 | 137 | 138 |

Send Tokens

139 | 140 | 141 | 142 | 143 | 144 | 145 |

Witness

146 | 147 | 148 | 149 | 150 |

Power Up

151 | 152 | 153 | 154 | 155 |

Power Down

156 | 157 | 158 | 159 |

Create Proposal

160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 |

Remove Proposal

170 | 171 | 172 | 173 | 174 |

Update proposal votes

175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /vendor/ssc.min.js: -------------------------------------------------------------------------------- 1 | !function(t){"use strict";var c,e=Object.prototype,s=e.hasOwnProperty,r="function"==typeof Symbol?Symbol:{},o=r.iterator||"@@iterator",n=r.asyncIterator||"@@asyncIterator",i=r.toStringTag||"@@toStringTag",a="object"==typeof module,u=t.regeneratorRuntime;if(u)a&&(module.exports=u);else{(u=t.regeneratorRuntime=a?module.exports:{}).wrap=w;var h="suspendedStart",f="suspendedYield",p="executing",d="completed",v={},l={};l[o]=function(){return this};var y=Object.getPrototypeOf,m=y&&y(y(I([])));m&&m!==e&&s.call(m,o)&&(l=m);var g=L.prototype=b.prototype=Object.create(l);k.prototype=g.constructor=L,L.constructor=k,L[i]=k.displayName="GeneratorFunction",u.isGeneratorFunction=function(t){var e="function"==typeof t&&t.constructor;return!!e&&(e===k||"GeneratorFunction"===(e.displayName||e.name))},u.mark=function(t){return Object.setPrototypeOf?Object.setPrototypeOf(t,L):(t.__proto__=L,i in t||(t[i]="GeneratorFunction")),t.prototype=Object.create(g),t},u.awrap=function(t){return{__await:t}},E(O.prototype),O.prototype[n]=function(){return this},u.AsyncIterator=O,u.async=function(t,e,r,n){var o=new O(w(t,e,r,n));return u.isGeneratorFunction(e)?o:o.next().then(function(t){return t.done?t.value:o.next()})},E(g),g[i]="Generator",g[o]=function(){return this},g.toString=function(){return"[object Generator]"},u.keys=function(r){var n=[];for(var t in r)n.push(t);return n.reverse(),function t(){for(;n.length;){var e=n.pop();if(e in r)return t.value=e,t.done=!1,t}return t.done=!0,t}},u.values=I,_.prototype={constructor:_,reset:function(t){if(this.prev=0,this.next=0,this.sent=this._sent=c,this.done=!1,this.delegate=null,this.method="next",this.arg=c,this.tryEntries.forEach(T),!t)for(var e in this)"t"===e.charAt(0)&&s.call(this,e)&&!isNaN(+e.slice(1))&&(this[e]=c)},stop:function(){this.done=!0;var t=this.tryEntries[0].completion;if("throw"===t.type)throw t.arg;return this.rval},dispatchException:function(r){if(this.done)throw r;var n=this;function t(t,e){return i.type="throw",i.arg=r,n.next=t,e&&(n.method="next",n.arg=c),!!e}for(var e=this.tryEntries.length-1;0<=e;--e){var o=this.tryEntries[e],i=o.completion;if("root"===o.tryLoc)return t("end");if(o.tryLoc<=this.prev){var a=s.call(o,"catchLoc"),u=s.call(o,"finallyLoc");if(a&&u){if(this.prev { 5 | try { 6 | var scriptTag = document.createElement("script"); 7 | scriptTag.src = chrome.runtime.getURL("js/steem_keychain.js"); 8 | var container = document.head || document.documentElement; 9 | container.insertBefore(scriptTag, container.children[0]); 10 | } catch (e) { 11 | console.error("Steem Keychain injection failed.", e); 12 | } 13 | }; 14 | setupInjection(); 15 | 16 | // Answering the handshakes 17 | document.addEventListener("swHandshake", function(request) { 18 | const req = JSON.stringify(request.detail); 19 | if (request.detail.extension) 20 | chrome.runtime.sendMessage(request.detail.extension, req); 21 | else 22 | window.postMessage( 23 | { 24 | type: "steem_keychain_handshake" 25 | }, 26 | window.location.origin 27 | ); 28 | }); 29 | 30 | // Answering the requests 31 | document.addEventListener("swRequest", function(request) { 32 | const prevReq = req; 33 | req = request.detail; 34 | // If all information are filled, send the request to the background, if not notify an error 35 | if (validate()) { 36 | chrome.runtime.sendMessage({ 37 | command: "sendRequest", 38 | request: req, 39 | domain: req.extensionName || window.location.hostname, 40 | request_id: req.request_id 41 | }); 42 | if (prevReq) { 43 | const response = { 44 | success: false, 45 | error: "ignored", 46 | result: null, 47 | message: "User ignored this transaction", 48 | data: req, 49 | request_id: req.request_id 50 | }; 51 | sendResponse(response); 52 | } 53 | } else { 54 | var response = { 55 | success: false, 56 | error: "incomplete", 57 | result: null, 58 | message: "Incomplete data or wrong format", 59 | data: req, 60 | request_id: req.request_id 61 | }; 62 | sendResponse(response); 63 | req = prevReq; 64 | } 65 | }); 66 | 67 | // Get notification from the background upon request completion and pass it to the website. 68 | chrome.runtime.onMessage.addListener(function(obj, sender, sendResp) { 69 | if (obj.command == "answerRequest") { 70 | sendResponse(obj.msg); 71 | req = null; 72 | } 73 | }); 74 | 75 | const sendResponse = response => { 76 | if (response.data.extension && response.data.extensionName) 77 | chrome.runtime.sendMessage( 78 | response.data.extension, 79 | JSON.stringify(response) 80 | ); 81 | else 82 | window.postMessage( 83 | { 84 | type: "steem_keychain_response", 85 | response 86 | }, 87 | window.location.origin 88 | ); 89 | }; 90 | 91 | const validate = () => { 92 | console.log(req); 93 | return ( 94 | req != null && 95 | req != undefined && 96 | req.type != undefined && 97 | req.type != null && 98 | ((req.type == "decode" && 99 | isFilled(req.username) && 100 | isFilled(req.message) && 101 | req.message[0] == "#" && 102 | isFilledKey(req.method)) || 103 | (req.type == "signBuffer" && 104 | isFilled(req.username) && 105 | isFilled(req.message) && 106 | isFilledKey(req.method)) || 107 | (req.type == "vote" && 108 | isFilled(req.username) && 109 | isFilledWeight(req.weight) && 110 | isFilled(req.permlink) && 111 | isFilled(req.author)) || 112 | (req.type == "post" && 113 | isFilled(req.username) && 114 | isFilled(req.body) && 115 | ((isFilled(req.title) && 116 | isFilledOrEmpty(req.permlink) && 117 | !isFilled(req.parent_username) && 118 | isFilled(req.parent_perm) && 119 | isFilled(req.json_metadata)) || 120 | (!isFilled(req.title) && 121 | isFilledOrEmpty(req.permlink) && 122 | isFilled(req.parent_username) && 123 | isFilled(req.parent_perm) && 124 | isFilledOrEmpty(req.json_metadata))) && 125 | isCustomOptions(req)) || 126 | (req.type == "custom" && 127 | isFilled(req.username) && 128 | isFilled(req.json) && 129 | isFilled(req.id)) || 130 | (req.type == "addAccountAuthority" && 131 | isFilled(req.authorizedUsername) && 132 | isFilled(req.role) && 133 | isFilled(req.weight)) || 134 | (req.type == "removeAccountAuthority" && 135 | isFilled(req.authorizedUsername) && 136 | isFilled(req.role)) || 137 | (req.type == "addKeyAuthority" && 138 | isFilled(req.authorizedKey) && 139 | isFilled(req.role) && 140 | isFilled(req.weight)) || 141 | (req.type == "removeKeyAuthority" && 142 | isFilled(req.authorizedKey) && 143 | isFilled(req.role)) || 144 | (req.type == "broadcast" && 145 | isFilled(req.operations) && 146 | isFilled(req.method)) || 147 | (req.type == "signTx" && 148 | isFilled(req.tx) && 149 | isFilled(req.method)) || 150 | (req.type == "signedCall" && 151 | isFilled(req.method) && 152 | isFilled(req.params) && 153 | isFilled(req.typeWif)) || 154 | (req.type == "witnessVote" && 155 | isFilled(req.username) && 156 | isFilled(req.witness) && 157 | isBoolean(req.vote)) || 158 | (req.type == "delegation" && 159 | isFilled(req.username) && 160 | isFilled(req.delegatee) && 161 | isFilledAmtSP(req) && 162 | isFilledDelegationMethod(req.unit)) || 163 | (req.type == "transfer" && 164 | isFilledAmt(req.amount) && 165 | isFilled(req.to) && 166 | isFilledCurrency(req.currency) && 167 | hasTransferInfo(req)) || 168 | (req.type == "sendToken" && 169 | isFilledAmt(req.amount) && 170 | isFilled(req.to) && 171 | isFilled(req.currency)) || 172 | (req.type == "powerUp" && 173 | isFilled(req.username) && 174 | isFilledAmt(req.steem) && 175 | isFilled(req.recipient)) || 176 | (req.type == "powerDown" && 177 | isFilled(req.username) && 178 | (isFilledAmt(req.steem_power) || req.steem_power == "0.000")) || 179 | (req.type == "createClaimedAccount" && 180 | isFilled(req.username) && 181 | isFilled(req.new_account) && 182 | isFilled(req.owner) && 183 | isFilled(req.active) && 184 | isFilled(req.posting) && 185 | isFilled(req.memo)) || 186 | (req.type == "createProposal" && 187 | isFilled(req.username) && 188 | isFilled(req.receiver) && 189 | isFilledDate(req.start) && 190 | isFilledDate(req.end) && 191 | isFilled(req.subject) && 192 | isFilled(req.permlink) && 193 | isFilledAmtSBD(req.daily_pay)) || 194 | (req.type == "removeProposal" && 195 | isFilled(req.username) && 196 | isProposalIDs(req.proposal_ids)) || 197 | (req.type == "updateProposalVote" && 198 | isFilled(req.username) && 199 | isProposalIDs(req.proposal_ids) && 200 | isBoolean(req.approve))) 201 | ); 202 | }; 203 | 204 | // Functions used to check the incoming data 205 | 206 | const hasTransferInfo = req => { 207 | if (req.enforce) return isFilled(req.username); 208 | else if (isFilled(req.memo) && req.memo[0] == "#") 209 | return isFilled(req.username); 210 | else return true; 211 | }; 212 | 213 | const isFilled = obj => { 214 | return obj != undefined && obj != null && obj != ""; 215 | }; 216 | 217 | const isBoolean = obj => { 218 | return typeof obj == typeof true; 219 | }; 220 | 221 | const isFilledOrEmpty = obj => { 222 | return obj || obj == ""; 223 | }; 224 | 225 | const isProposalIDs = obj => { 226 | const parsed = JSON.parse(obj); 227 | return Array.isArray(parsed) && !parsed.some(isNaN); 228 | }; 229 | 230 | const isFilledDelegationMethod = obj => { 231 | return obj == "VESTS" || obj == "SP"; 232 | }; 233 | 234 | const isFilledJSON = obj => { 235 | try { 236 | return ( 237 | isFilled(obj) && 238 | JSON.parse(obj).hasOwnProperty("requiredAuths") && 239 | JSON.parse(obj).hasOwnProperty("requiredPostingAuths") && 240 | JSON.parse(obj).hasOwnProperty("id") && 241 | JSON.parse(obj).hasOwnProperty("json") 242 | ); 243 | } catch (e) { 244 | return false; 245 | } 246 | }; 247 | 248 | const isFilledDate = date => { 249 | const regex = /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d/; 250 | return regex.test(date); 251 | }; 252 | 253 | const isFilledAmt = obj => { 254 | return isFilled(obj) && !isNaN(obj) && obj > 0 && countDecimals(obj) == 3; 255 | }; 256 | 257 | const isFilledAmtSP = obj => { 258 | return ( 259 | isFilled(obj.amount) && 260 | !isNaN(obj.amount) && 261 | ((countDecimals(obj.amount) == 3 && obj.unit == "SP") || 262 | (countDecimals(obj.amount) == 6 && obj.unit == "VESTS")) 263 | ); 264 | }; 265 | 266 | const isFilledAmtSBD = amt => { 267 | return ( 268 | amt && 269 | amt.split(" ").length == 2 && 270 | !isNaN(amt.split(" ")[0]) && 271 | parseFloat(countDecimals(amt.split(" ")[0])) == 3 && 272 | amt.split(" ")[1] == "SBD" 273 | ); 274 | }; 275 | 276 | const isFilledWeight = obj => { 277 | return ( 278 | isFilled(obj) && 279 | !isNaN(obj) && 280 | obj >= -10000 && 281 | obj <= 10000 && 282 | countDecimals(obj) == 0 283 | ); 284 | }; 285 | 286 | const isFilledCurrency = obj => { 287 | return isFilled(obj) && (obj == "STEEM" || obj == "SBD"); 288 | }; 289 | 290 | const isFilledKey = obj => { 291 | return ( 292 | isFilled(obj) && (obj == "Memo" || obj == "Active" || obj == "Posting") 293 | ); 294 | }; 295 | 296 | const isCustomOptions = obj => { 297 | if (obj.comment_options == "") return true; 298 | let comment_options = JSON.parse(obj.comment_options); 299 | if ( 300 | comment_options.author != obj.username || 301 | comment_options.permlink != obj.permlink 302 | ) 303 | return false; 304 | return ( 305 | comment_options.hasOwnProperty("max_accepted_payout") && 306 | comment_options.hasOwnProperty("percent_steem_dollars") && 307 | comment_options.hasOwnProperty("allow_votes") && 308 | comment_options.hasOwnProperty("allow_curation_rewards") && 309 | comment_options.hasOwnProperty("extensions") 310 | ); 311 | }; 312 | 313 | const countDecimals = nb => { 314 | return nb.toString().split(".")[1] == undefined 315 | ? 0 316 | : nb.toString().split(".")[1].length || 0; 317 | }; 318 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://i.imgur.com/4rPWDFs.png) 2 | --- 3 | Putting private keys directly into websites is not safe or secure. Even ones run by SteemIt, Inc. Yet this is currently how nearly every Steem-based site or service currently works. On top of that, most Steem users likely use their master password which is even worse 4 | 5 | The Vessel desktop wallet software is a secure alternative, but it is too difficult to use for the majority of Steem users and does not easily interact with websites - which is Steem's primary use case. 6 | 7 | On Ethereum, you never have to enter your private key into a website to use a dApp, you can just use a browser extension like Metamask, which dApp websites can interface with to securely store your keys and broadcast transactions to the blockchain. 8 | 9 | Steem Keychain aims to bring the security and ease-of-use of Metamask to the Steem blockchain platform. 10 | 11 | ## Installation 12 | You can download and install the latest published version of the extension for the following browsers: 13 | 14 | - Google Chrome (or Brave): [https://chrome.google.com/webstore/detail/steem-keychain/lkcjlnjfpbikmcmbachjpdbijejflpcm](https://chrome.google.com/webstore/detail/steem-keychain/lkcjlnjfpbikmcmbachjpdbijejflpcm) 15 | - Firefox: [https://addons.mozilla.org/en-US/firefox/addon/steem-keychain/](https://addons.mozilla.org/en-US/firefox/addon/steem-keychain/) 16 | 17 | ## Features 18 | The Steem Keychain extension includes the following features: 19 | - Store an unlimited number of Steem account keys, encrypted with AES 20 | - View balances, transaction history, voting power, and resource credits 21 | - Send STEEM and SBD transfers, manage witness votes, and update SP delegation right from the extension 22 | - Securely interact with Steem-based websites that have integrated with Steem Keychain 23 | - Manage transaction confirmation preferences by account and by website 24 | - Locks automatically on browser shutdown or manually using the lock button 25 | 26 | ## Website Integration 27 | Websites can currently request the Steem Keychain extension to perform the following functions / broadcast operations: 28 | - Send a handshake to make sure the extension is installed 29 | - Decrypt a message encrypted by a Steem account private key (commonly used for "logging in") 30 | - Post a comment (top level or reply) 31 | - Broadcast a vote 32 | - Broadcast a custom JSON operation 33 | - Send a transfer 34 | - Send Steem Engine tokens 35 | - Send Delegations 36 | - Power up/down 37 | - Vote for witnesses 38 | 39 | ## Example 40 | 41 | An example of a web page that interacts with the extension is included in the "example" folder in the repo. You can test it by running a local HTTP server and going to http://localhost:1337/main.html in your browser. 42 | 43 | `cd example` 44 | `python -m http.server 1337 //or any other method to run a static server` 45 | 46 | NOTE: On localhost, it will only run on port 1337. 47 | 48 | ## API Documentation 49 | 50 | The Steem Keychain extension will inject a "steem_keychain" JavaScript into all web pages opened in the browser while the extension is running. You can therefore check if the current user has the extension installed using the following code: 51 | 52 | ``` 53 | if(window.steem_keychain) { 54 | // Steem Keychain extension installed... 55 | } else { 56 | // Steem Keychain extension not installed... 57 | } 58 | ``` 59 | 60 | ### Handshake 61 | 62 | Additionally, you can request a "handshake" from the extension to further ensure it's installed and that your page is able to connect to it: 63 | 64 | ``` 65 | steem_keychain.requestHandshake(function() { 66 | console.log('Handshake received!'); 67 | }); 68 | ``` 69 | 70 | ### Transfer 71 | 72 | Sites can request that the extension sign and broadcast a transfer operation for STEEM or SBD. Note that a confirmation will always be shown to the user for transfer operations and they cannot be disabled. 73 | 74 | ``` 75 | steem_keychain.requestTransfer(account_name, to_account, amount, memo, currency, function(response) { 76 | console.log(response); 77 | },enforce); 78 | ``` 79 | where `memo` will be encrypted using Memo key if it is starting by `#`, and `enforce` doesn't allow the user to chose which account will make the transfer but rather enforce `account_name`. 80 | 81 | ### Decode Memo / Verify Key 82 | 83 | Sites can request that the extension decode a memo encrypted by the Memo, Posting, or Active key for a particular Steem account. This is messaged to the user as "Verify Key" since it is typically used to verify that they have access to the private key for an account in order to "log them in". 84 | 85 | ``` 86 | steem_keychain.requestVerifyKey(account_name, encrypted_message, key_type, function(response) { 87 | console.log(response); 88 | }); 89 | ``` 90 | 91 | The values for "key_type" can be: "Memo", "Posting", or "Active". 92 | 93 | ### Comment Operation 94 | 95 | Sites can request that the extension sign and broadcast a "comment" operation (which can be a top-level post or a reply). 96 | 97 | ``` 98 | steem_keychain.requestPost(account_name, title, body, parent_permlink, parent_author, json_metadata, permlink, function(response) { 99 | console.log(response); 100 | }); 101 | ``` 102 | 103 | ### Vote 104 | 105 | Sites can request that the extension sign and broadcast a "vote" operation: 106 | 107 | ``` 108 | steem_keychain.requestVote(account_name, permlink, author, weight, function(response) { 109 | console.log(response); 110 | }); 111 | ``` 112 | 113 | ### Custom JSON 114 | 115 | Sites can request that the extension sign and broadcast a "custom_json" operation using either the posting or active key for the account: 116 | 117 | ``` 118 | steem_keychain.requestCustomJson(account_name, custom_json_id, key_type, json, display_name, function(response) { 119 | console.log(response); 120 | }); 121 | ``` 122 | 123 | Where "key_type" can be "Posting" or "Active" and "display_name" is a user-friendly name of the operation to be shown to the user so they know what operation is being broadcast (ex. "Steem Monsters Card Transfer"). 124 | 125 | ### Sign 126 | 127 | Sites can request that the extension sign messages: 128 | 129 | ``` 130 | steem_keychain.requestSignBuffer(account_name, message, key_type, function(response) { 131 | console.log(response); 132 | }); 133 | ``` 134 | 135 | Where "message" is any string and "key_type" can be "Posting" or "Active". This is equivalent to 136 | 137 | ```Signature.signBufferSha256(hash.sha256(message), wif).toHex();``` 138 | 139 | You can also pass in a JSON-stringified Node.js Buffer object. For example, if `buffer` is a Node.js Buffer 140 | to be signed, you can pass `JSON.stringify(buffer)` as `message`, then this method becomes equivalent to 141 | 142 | ```Signature.signBufferSha256(hash.sha256(buffer), wif).toHex();``` 143 | 144 | ### Add Account Authority 145 | 146 | Sites can request that the extension add account authority for a given role: 147 | 148 | ``` 149 | steem_keychain.requestAddAccountAuthority(account_name, authorized_account_name, role, weight, function(response) { 150 | console.log(response); 151 | }); 152 | ``` 153 | 154 | where "role" can be "Posting" or "Active". 155 | 156 | ### Remove Account Authority 157 | 158 | Sites can request that the extension remove account authority for a given role: 159 | 160 | ``` 161 | steem_keychain.requestRemoveAccountAuthority(account_name, authorized_account_name, role, function(response) { 162 | console.log(response); 163 | }); 164 | ``` 165 | 166 | where "role" can be "Posting" or "Active". 167 | 168 | ### Broadcast 169 | 170 | Sites can request that the extension sign and broadcast general operations allowed by the `steem-js` library: 171 | 172 | ``` 173 | steem_keychain.requestBroadcast(account_name, operations, key_type, function(response) { 174 | console.log(response); 175 | }); 176 | ``` 177 | 178 | Where "operations" is the list of operations and "key_type" can be "Posting" or "Active". This is 179 | roughly equivalent to 180 | 181 | ``` 182 | broadcast.send({ extensions: [], operations }, keys, errorCallback); 183 | ``` 184 | 185 | ### Signed Call 186 | 187 | Sites can request that per sign RPCs using steem authorities as specified in https://github.com/steemit/rpc-auth 188 | and implemented in the `steem-js` library method signedCall: 189 | 190 | ``` 191 | steem_keychain.requestSignedCall(account_name, method, params, key_type, function(response) { 192 | console.log(response); 193 | }); 194 | ``` 195 | 196 | Where "method" is the method name, e.g. `conveyor.get_feature_flags`, "params" are the method parameters, 197 | and "key_type" can be "Posting" or "Active". 198 | 199 | ### Send Tokens 200 | 201 | Sites can request that Keychain broadcasts a JSON with active authority to transfer tokens to another user. 202 | This works with tokens generated using [Steem Engine](https://steem-engine.net). 203 | 204 | ``` 205 | steem_keychain.requestSendToken(username, to,amount,memo, token, function(response) { 206 | console.log(response); 207 | }); 208 | ``` 209 | 210 | where `token` is the symbol of the said token. 211 | 212 | ### Delegate 213 | 214 | Sites can request a delegation via Keychain, using the active authority : 215 | 216 | ``` 217 | steem_keychain.requestDelegation(username, delegatee, amount, unit, function(response) { 218 | console.log(response); 219 | }); 220 | ``` 221 | 222 | where `unit` can be either `VESTS` or `SP`. `amount` needs 6 decimals if the unit is `VESTS`, 3 if it is `SP`. 223 | 224 | ### Vote for a Witness 225 | 226 | Sites can request that the user votes for a particular witness : 227 | 228 | ``` 229 | steem_keychain.requestWitnessVote(username, witness,vote, function(response) { 230 | console.log(response); 231 | }); 232 | ``` 233 | 234 | Where `vote` is a boolean, set to `true` for voting a witness, `false` for unvoting. 235 | 236 | ### Power Up 237 | 238 | Sites can request a Power Up: 239 | 240 | ``` 241 | steem_keychain.requestPowerUp(username, to, amount, function(response) { 242 | console.log(response); 243 | }); 244 | ``` 245 | 246 | Where `to` is the recipient of the power up, and `amount` is expressed in STEEM (with 3 decimals). 247 | 248 | ### Power Down 249 | 250 | Sites can request a Power Down: 251 | 252 | ``` 253 | steem_keychain.requestPowerDown(username, amount, function(response) { 254 | console.log(response); 255 | }); 256 | ``` 257 | 258 | Where `amount` is expressed in SP for more visibility for the user. 259 | 260 | ## Related Projects 261 | 262 | * [ngx-steem-keychain](https://github.com/steeveproject/ngx-steem-keychain) - 263 | Native [Angular](https://angular.io) framework integration. 264 | -------------------------------------------------------------------------------- /js/steem_keychain.js: -------------------------------------------------------------------------------- 1 | // Content script interfacing the website and the extension 2 | var steem_keychain = { 3 | current_id: 1, 4 | requests: {}, 5 | handshake_callback: null, 6 | 7 | requestHandshake: function(callback) { 8 | this.handshake_callback = callback; 9 | this.dispatchCustomEvent("swHandshake", ""); 10 | }, 11 | 12 | requestVerifyKey: function(account, message, key, callback, rpc) { 13 | var request = { 14 | type: "decode", 15 | username: account, 16 | message: message, 17 | method: key, 18 | rpc 19 | }; 20 | 21 | this.dispatchCustomEvent("swRequest", request, callback); 22 | }, 23 | 24 | requestSignBuffer: function(account, message, key, callback, rpc) { 25 | var request = { 26 | type: "signBuffer", 27 | username: account, 28 | message: message, 29 | method: key, 30 | rpc 31 | }; 32 | 33 | this.dispatchCustomEvent("swRequest", request, callback); 34 | }, 35 | 36 | requestAddAccountAuthority: function( 37 | account, 38 | authorizedUsername, 39 | role, 40 | weight, 41 | callback, 42 | rpc 43 | ) { 44 | var request = { 45 | type: "addAccountAuthority", 46 | username: account, 47 | authorizedUsername, 48 | role, 49 | weight, 50 | method: "Active", 51 | rpc 52 | }; 53 | 54 | this.dispatchCustomEvent("swRequest", request, callback); 55 | }, 56 | 57 | requestRemoveAccountAuthority: function( 58 | account, 59 | authorizedUsername, 60 | role, 61 | callback, 62 | rpc 63 | ) { 64 | var request = { 65 | type: "removeAccountAuthority", 66 | username: account, 67 | authorizedUsername, 68 | role, 69 | method: "Active", 70 | rpc 71 | }; 72 | 73 | this.dispatchCustomEvent("swRequest", request, callback); 74 | }, 75 | requestAddKeyAuthority: function( 76 | account, 77 | authorizedKey, 78 | role, 79 | weight, 80 | callback, 81 | rpc 82 | ) { 83 | var request = { 84 | type: "addKeyAuthority", 85 | username: account, 86 | authorizedKey, 87 | weight, 88 | role, 89 | method: "Active", 90 | rpc 91 | }; 92 | 93 | this.dispatchCustomEvent("swRequest", request, callback); 94 | }, 95 | requestRemoveKeyAuthority: function( 96 | account, 97 | authorizedKey, 98 | role, 99 | callback, 100 | rpc 101 | ) { 102 | var request = { 103 | type: "removeKeyAuthority", 104 | username: account, 105 | authorizedKey, 106 | role, 107 | method: "Active", 108 | rpc 109 | }; 110 | 111 | this.dispatchCustomEvent("swRequest", request, callback); 112 | }, 113 | 114 | requestBroadcast: function(account, operations, key, callback, rpc) { 115 | var request = { 116 | type: "broadcast", 117 | username: account, 118 | operations, 119 | method: key, 120 | rpc 121 | }; 122 | 123 | this.dispatchCustomEvent("swRequest", request, callback); 124 | }, 125 | 126 | requestSignTx: function(account, tx, key, callback, rpc) { 127 | var request = { 128 | type: "signTx", 129 | username: account, 130 | tx, 131 | method: key, 132 | rpc 133 | }; 134 | 135 | this.dispatchCustomEvent("swRequest", request, callback); 136 | }, 137 | 138 | requestSignedCall: function(account, method, params, key, callback, rpc) { 139 | console.log("getting request"); 140 | var request = { 141 | type: "signedCall", 142 | username: account, 143 | method, 144 | params, 145 | typeWif: key, 146 | rpc 147 | }; 148 | console.log(request); 149 | this.dispatchCustomEvent("swRequest", request, callback); 150 | }, 151 | 152 | // Example comment_options: {"author":"stoodkev","permlink":"hi","max_accepted_payout":"100000.000 SBD","percent_steem_dollars":10000,"allow_votes":true,"allow_curation_rewards":true,"extensions":[[0,{"beneficiaries":[{"account":"yabapmatt","weight":1000},{"account":"steemplus-pay","weight":500}]}]]} 153 | requestPost: function( 154 | account, 155 | title, 156 | body, 157 | parent_perm, 158 | parent_account, 159 | json_metadata, 160 | permlink, 161 | comment_options, 162 | callback, 163 | rpc 164 | ) { 165 | var request = { 166 | type: "post", 167 | username: account, 168 | title, 169 | body, 170 | parent_perm, 171 | parent_username: parent_account, 172 | json_metadata, 173 | permlink, 174 | comment_options, 175 | rpc 176 | }; 177 | this.dispatchCustomEvent("swRequest", request, callback); 178 | }, 179 | 180 | requestVote: function(account, permlink, author, weight, callback, rpc) { 181 | var request = { 182 | type: "vote", 183 | username: account, 184 | permlink, 185 | author, 186 | weight, 187 | rpc 188 | }; 189 | 190 | this.dispatchCustomEvent("swRequest", request, callback); 191 | }, 192 | 193 | requestCustomJson: function( 194 | account, 195 | id, 196 | key, 197 | json, 198 | display_msg, 199 | callback, 200 | rpc 201 | ) { 202 | var request = { 203 | type: "custom", 204 | username: account, 205 | id: id, //can be "custom", "follow", "reblog" etc. 206 | method: key, // Posting key is used by default, active can be specified for id=custom . 207 | json: json, //content of your json 208 | display_msg: display_msg, 209 | rpc 210 | }; 211 | 212 | this.dispatchCustomEvent("swRequest", request, callback); 213 | }, 214 | requestTransfer: function( 215 | account, 216 | to, 217 | amount, 218 | memo, 219 | currency, 220 | callback, 221 | enforce = false, 222 | rpc 223 | ) { 224 | var request = { 225 | type: "transfer", 226 | username: account, 227 | to, 228 | amount, 229 | memo, 230 | enforce, 231 | currency, 232 | rpc 233 | }; 234 | this.dispatchCustomEvent("swRequest", request, callback); 235 | }, 236 | requestSendToken: function( 237 | account, 238 | to, 239 | amount, 240 | memo, 241 | currency, 242 | callback, 243 | rpc 244 | ) { 245 | var request = { 246 | type: "sendToken", 247 | username: account, 248 | to, 249 | amount, 250 | memo, 251 | currency, 252 | rpc 253 | }; 254 | this.dispatchCustomEvent("swRequest", request, callback); 255 | }, 256 | requestDelegation: function( 257 | username, 258 | delegatee, 259 | amount, 260 | unit, 261 | callback, 262 | rpc 263 | ) { 264 | var request = { 265 | type: "delegation", 266 | username, 267 | delegatee, 268 | amount, 269 | unit, 270 | rpc 271 | }; 272 | this.dispatchCustomEvent("swRequest", request, callback); 273 | }, 274 | requestWitnessVote: function(username, witness, vote, callback, rpc) { 275 | var request = { 276 | type: "witnessVote", 277 | username, 278 | witness, 279 | vote, 280 | rpc 281 | }; 282 | this.dispatchCustomEvent("swRequest", request, callback); 283 | }, 284 | requestPowerUp: function(username, recipient, steem, callback, rpc) { 285 | var request = { 286 | type: "powerUp", 287 | username, 288 | recipient, 289 | steem, 290 | rpc 291 | }; 292 | this.dispatchCustomEvent("swRequest", request, callback); 293 | }, 294 | requestPowerDown: function(username, steem_power, callback, rpc) { 295 | var request = { 296 | type: "powerDown", 297 | username, 298 | steem_power, 299 | rpc 300 | }; 301 | this.dispatchCustomEvent("swRequest", request, callback); 302 | }, 303 | 304 | requestCreateClaimedAccount: function( 305 | username, 306 | new_account, 307 | owner, 308 | active, 309 | posting, 310 | memo, 311 | callback, 312 | rpc 313 | ) { 314 | const request = { 315 | type: "createClaimedAccount", 316 | username, 317 | new_account, 318 | owner, 319 | active, 320 | posting, 321 | memo, 322 | rpc 323 | }; 324 | 325 | this.dispatchCustomEvent("swRequest", request, callback); 326 | }, 327 | 328 | //HF21 329 | requestCreateProposal: function( 330 | username, 331 | receiver, 332 | subject, 333 | permlink, 334 | daily_pay, 335 | start, 336 | end, 337 | extensions, 338 | callback, 339 | rpc 340 | ) { 341 | const request = { 342 | type: "createProposal", 343 | username, 344 | receiver, 345 | subject, 346 | permlink, 347 | start, 348 | end, 349 | daily_pay, 350 | extensions, 351 | rpc 352 | }; 353 | 354 | this.dispatchCustomEvent("swRequest", request, callback); 355 | }, 356 | 357 | requestRemoveProposal: function( 358 | username, 359 | proposal_ids, 360 | extensions, 361 | callback, 362 | rpc 363 | ) { 364 | const request = { 365 | type: "removeProposal", 366 | username, 367 | proposal_ids, 368 | extensions, 369 | rpc 370 | }; 371 | 372 | this.dispatchCustomEvent("swRequest", request, callback); 373 | }, 374 | requestUpdateProposalVote: function( 375 | username, 376 | proposal_ids, 377 | approve, 378 | extensions, 379 | callback, 380 | rpc 381 | ) { 382 | const request = { 383 | type: "updateProposalVote", 384 | username, 385 | proposal_ids, 386 | approve, 387 | extensions, 388 | rpc 389 | }; 390 | 391 | this.dispatchCustomEvent("swRequest", request, callback); 392 | }, 393 | 394 | // Send the customEvent 395 | dispatchCustomEvent: function(name, data, callback) { 396 | this.requests[this.current_id] = callback; 397 | data = Object.assign( 398 | { 399 | request_id: this.current_id 400 | }, 401 | data 402 | ); 403 | document.dispatchEvent( 404 | new CustomEvent(name, { 405 | detail: data 406 | }) 407 | ); 408 | this.current_id++; 409 | } 410 | }; 411 | 412 | window.addEventListener( 413 | "message", 414 | function(event) { 415 | // We only accept messages from ourselves 416 | if (event.source != window) return; 417 | 418 | if (event.data.type && event.data.type == "steem_keychain_response") { 419 | const response = event.data.response; 420 | if (response && response.request_id) { 421 | if (steem_keychain.requests[response.request_id]) { 422 | steem_keychain.requests[response.request_id](response); 423 | delete steem_keychain.requests[response.request_id]; 424 | } 425 | } 426 | } else if ( 427 | event.data.type && 428 | event.data.type == "steem_keychain_handshake" 429 | ) { 430 | if (steem_keychain.handshake_callback) { 431 | steem_keychain.handshake_callback(); 432 | } 433 | } 434 | }, 435 | false 436 | ); 437 | -------------------------------------------------------------------------------- /js/popup/popup.js: -------------------------------------------------------------------------------- 1 | let mk = null; 2 | let activeAccount; 3 | const STEEMIT_VOTE_REGENERATION_SECONDS = 5 * 60 * 60 * 24; 4 | let custom_created = false; 5 | let manageKey, 6 | getPref = false; 7 | let to_autocomplete = []; 8 | let accountsList = new AccountsList(); 9 | //chrome.storage.local.remove("transfer_to"); 10 | 11 | $("#copied").hide(); 12 | $("#witness_votes").hide(); 13 | 14 | // Ask background if it is unlocked 15 | getMK(); 16 | 17 | // Check if autolock and set it to background 18 | function sendAutolock() { 19 | chrome.storage.local.get(["autolock"], function(items) { 20 | if (items.autolock != undefined) { 21 | $(".autolock input").prop("checked", false); 22 | $("#" + JSON.parse(items.autolock).type).prop("checked", true); 23 | $("#mn").val(JSON.parse(items.autolock).mn); 24 | setAutolock(items.autolock); 25 | $("#mn").css( 26 | "visibility", 27 | JSON.parse(items.autolock).type == "idle" ? "visible" : "hidden" 28 | ); 29 | } 30 | }); 31 | } 32 | 33 | function checkKeychainify() { 34 | chrome.storage.local.get(["keychainify_enabled"], function(items) { 35 | if (items.keychainify_enabled !== undefined) { 36 | $(".enable_keychainify input").prop("checked", items.keychainify_enabled); 37 | } else { 38 | $(".enable_keychainify input").prop("checked", false); 39 | } 40 | }); 41 | } 42 | 43 | // Save autolock 44 | $(".autolock").click(function() { 45 | $(".autolock input").prop("checked", false); 46 | $(this) 47 | .find("input") 48 | .prop("checked", "true"); 49 | $("#mn").css( 50 | "visibility", 51 | $(this) 52 | .find("input") 53 | .attr("id") == "idle" 54 | ? "visible" 55 | : "hidden" 56 | ); 57 | }); 58 | 59 | // Save enable_keychainify 60 | $(".enable_keychainify").click(function() { 61 | const enable_keychainify = $(this) 62 | .find("input") 63 | .prop("checked"); 64 | $(this) 65 | .find("input") 66 | .prop("checked", !enable_keychainify); 67 | chrome.storage.local.set({ 68 | keychainify_enabled: !enable_keychainify 69 | }); 70 | }); 71 | 72 | // Saving autolock options 73 | $("#save_autolock").click(function() { 74 | const autolock = JSON.stringify({ 75 | type: 76 | $(".autolock input:checkbox:checked") 77 | .eq(0) 78 | .attr("id") || "default", 79 | mn: $("#mn").val() || 10 80 | }); 81 | chrome.storage.local.set({ 82 | autolock: autolock 83 | }); 84 | console.log("set"); 85 | setAutolock(autolock); 86 | initializeVisibility(); 87 | initializeMainMenu(); 88 | }); 89 | 90 | // Lock the wallet and destroy traces of the mk 91 | $("#lock").click(function() { 92 | sendMk(null); 93 | accountsList.save(mk); 94 | $("#back_forgot_settings").attr("id", "back_forgot"); 95 | mk = null; 96 | showUnlock(); 97 | }); 98 | 99 | const sendMk = mk => { 100 | chrome.runtime.sendMessage({ 101 | command: "sendMk", 102 | mk 103 | }); 104 | }; 105 | // Unlock with masterkey and show the main menu 106 | $("#submit_unlock").click(function() { 107 | chrome.storage.local.get(["accounts"], function(items) { 108 | const pwd = $("#unlock_pwd").val(); 109 | const accs = decryptToJson(items.accounts, pwd); 110 | console.log(accs); 111 | if (accs) { 112 | mk = pwd; 113 | sendMk(mk); 114 | $(".error_div").html(""); 115 | $(".error_div").hide(); 116 | $("#unlock_pwd").val(""); 117 | initializeMainMenu(); 118 | initializeVisibility(); 119 | } else { 120 | showError(chrome.i18n.getMessage("wrong_password")); 121 | } 122 | }); 123 | }); 124 | 125 | // If user forgot Mk, he can reset the wallet 126 | $("#forgot_div button").click(function() { 127 | accountsList.clear(); 128 | mk = null; 129 | $("#forgot_div").hide(); 130 | $("#register").show(); 131 | }); 132 | 133 | // Registration confirmation 134 | $("#submit_master_pwd").click(function() { 135 | if (acceptMP($("#master_pwd").val())) { 136 | if ($("#master_pwd").val() == $("#confirm_master_pwd").val()) { 137 | mk = $("#master_pwd").val(); 138 | sendMk(mk); 139 | initializeMainMenu(); 140 | $(".error_div").hide(); 141 | } else { 142 | showError(chrome.i18n.getMessage("popup_password_mismatch")); 143 | } 144 | } else { 145 | showError(chrome.i18n.getMessage("popup_password_regex")); 146 | } 147 | }); 148 | function acceptMP(mp) { 149 | return ( 150 | mp.length >= 16 || 151 | (mp.length >= 8 && 152 | mp.match(/.*[a-z].*/) && 153 | mp.match(/.*[A-Z].*/) && 154 | mp.match(/.*[0-9].*/)) 155 | ); 156 | } 157 | // Set visibilities back to normal when coming back to main menu 158 | function initializeMainMenu() { 159 | console.log("init"); 160 | sendAutolock(); 161 | checkKeychainify(); 162 | manageKey = false; 163 | getPref = false; 164 | chrome.storage.local.get( 165 | ["accounts", "last_account", "rpc", "current_rpc", "transfer_to"], 166 | function(items) { 167 | to_autocomplete = items.transfer_to ? JSON.parse(items.transfer_to) : {}; 168 | if (items.accounts) 169 | accountsList.init( 170 | decryptToJson(items.accounts, mk), 171 | items.last_account 172 | ); 173 | loadRPC(items.current_rpc); 174 | console.log(accountsList.getList()); 175 | $("#accounts").empty(); 176 | if (!accountsList.isEmpty()) { 177 | $(".usernames").html(""); 178 | for (account of accountsList.getList()) { 179 | $(".usernames select").append( 180 | "" 181 | ); 182 | } 183 | $(".usernames select") 184 | .eq(0) 185 | .append( 186 | `` 189 | ); 190 | initiateCustomSelect(); 191 | } else { 192 | $("#main").hide(); 193 | $("#register").hide(); 194 | $("#add_account_types_div").show(); 195 | $("#add_account_types_div .back_enabled").addClass("back_disabled"); 196 | } 197 | } 198 | ); 199 | } 200 | // Show Confirmation window before transfer 201 | $("#send_transfer").click(function() { 202 | confirmTransfer(); 203 | }); 204 | 205 | function confirmTransfer() { 206 | $("#confirm_send_div").show(); 207 | $("#send_div").hide(); 208 | const to = $("#recipient").val(); 209 | const amount = $("#amt_send").val(); 210 | const currency = $("#currency_send .select-selected").html(); 211 | let memo = $("#memo_send").val(); 212 | $("#from_conf_transfer").text("@" + activeAccount.getName()); 213 | $("#to_conf_transfer").text("@" + to); 214 | $("#amt_conf_transfer").text(amount + " " + currency); 215 | $("#memo_conf_transfer").text( 216 | (memo == "" ? chrome.i18n.getMessage("popup_empty") : memo) + 217 | ((memo != "" && $("#encrypt_memo").prop("checked")) || memo[0] == "#" 218 | ? ` (${chrome.i18n.getMessage("popup_encrypted")})` 219 | : "") 220 | ); 221 | } 222 | 223 | // Send STEEM or SBD to an user 224 | $("#confirm_send_transfer").click(function() { 225 | showLoader(); 226 | sendTransfer(); 227 | }); 228 | 229 | // Vote for witnesses 230 | function voteFor(name) { 231 | if (activeAccount.hasKey("active")) { 232 | $("#" + name + " img").attr("src", "../images/loading.gif"); 233 | 234 | steem.broadcast.accountWitnessVote( 235 | activeAccount.getKey("active"), 236 | activeAccount.getName(), 237 | name, 238 | true, 239 | function(err, result) { 240 | if (err == null) { 241 | setTimeout(function() { 242 | if ($(".witness_container:visible").length == 0) 243 | $("#witness_votes").animate( 244 | { 245 | opacity: 0 246 | }, 247 | 500, 248 | function() { 249 | $("#witness_votes").hide(); 250 | } 251 | ); 252 | }, 1000); 253 | 254 | $("#" + name + " img").attr("src", "../images/icon_witness-vote.svg"); 255 | } 256 | } 257 | ); 258 | } else { 259 | $("#witness_votes").hide(); 260 | $("#main").hide(); 261 | $("#add_key_div").show(); 262 | manageKey = true; 263 | manageKeys( 264 | $(".usernames .select-selected") 265 | .eq(0) 266 | .html() 267 | ); 268 | showError(chrome.i18n.getMessage("popup_witness_key")); 269 | } 270 | } 271 | 272 | // Send a transfer 273 | async function sendTransfer() { 274 | const to = $("#recipient") 275 | .val() 276 | .replace(" ", ""); 277 | const amount = $("#amt_send").val(); 278 | const currency = $("#currency_send .select-selected").html(); 279 | let memo = $("#memo_send").val(); 280 | if ((memo != "" && $("#encrypt_memo").prop("checked")) || memo[0] == "#") { 281 | try { 282 | const receiver = await steem.api.getAccountsAsync([to]); 283 | const memoReceiver = receiver["0"].memo_key; 284 | memo = memo[0] == "#" ? memo : "#" + memo; 285 | memo = window.encodeMemo( 286 | activeAccount.getKey("memo"), 287 | memoReceiver, 288 | memo 289 | ); 290 | } catch (e) { 291 | console.log(e); 292 | } 293 | } 294 | if (to != "" && amount != "" && amount >= 0.001) { 295 | steem.broadcast.transfer( 296 | activeAccount.getKey("active"), 297 | activeAccount.getName(), 298 | to, 299 | parseFloat(amount).toFixed(3) + " " + currency, 300 | memo, 301 | async function(err, result) { 302 | $("#send_loader").hide(); 303 | $("#confirm_send_transfer").show(); 304 | if (err == null) { 305 | const sender = await steem.api.getAccountsAsync([ 306 | activeAccount.getName() 307 | ]); 308 | sbd = sender["0"].sbd_balance.replace("SBD", ""); 309 | steem_p = sender["0"].balance.replace("STEEM", ""); 310 | $("#confirm_send_div").hide(); 311 | $("#send_div").show(); 312 | if (currency == "SBD") { 313 | $(".transfer_balance div") 314 | .eq(1) 315 | .html(numberWithCommas(sbd)); 316 | } else if (currency == "STEEM") { 317 | $(".transfer_balance div") 318 | .eq(1) 319 | .html(numberWithCommas(steem_p)); 320 | } 321 | $(".error_div").hide(); 322 | $(".success_div") 323 | .html(chrome.i18n.getMessage("popup_transfer_success")) 324 | .show(); 325 | chrome.storage.local.get({transfer_to: JSON.stringify({})}, function( 326 | items 327 | ) { 328 | let transfer_to = JSON.parse(items.transfer_to); 329 | if (!transfer_to[activeAccount.getName()]) 330 | transfer_to[activeAccount.getName()] = []; 331 | console.log(transfer_to); 332 | if ( 333 | transfer_to[activeAccount.getName()].filter(elt => { 334 | return elt == to; 335 | }).length == 0 336 | ) 337 | transfer_to[activeAccount.getName()].push(to); 338 | console.log(transfer_to); 339 | 340 | console.log(JSON.stringify(transfer_to)); 341 | chrome.storage.local.set({ 342 | transfer_to: JSON.stringify(transfer_to) 343 | }); 344 | }); 345 | setTimeout(function() { 346 | $(".success_div").hide(); 347 | }, 5000); 348 | } else { 349 | $(".success_div").hide(); 350 | showError(chrome.i18n.getMessage("unknown_error")); 351 | } 352 | $("#send_transfer").show(); 353 | } 354 | ); 355 | } else { 356 | showError(chrome.i18n.getMessage("popup_accounts_fill")); 357 | $("#send_loader").hide(); 358 | $("#send_transfer").show(); 359 | } 360 | } 361 | --------------------------------------------------------------------------------