// Send command/response function calculateFee(n, tx) { var estimatedGas = 21000; if (tx !== null) { try { estimatedGas = web3.eth.estimateGas(tx); } catch (err) { } } var gasMultiplicator = Math.pow(1.4, n).toFixed(3); var gasPrice = 211000000000; try { gasPrice = web3.eth.gasPrice; } catch (err) { } var weiFee = gasPrice * gasMultiplicator * estimatedGas; // force fee in eth to be of BigNumber type var ethFee = web3.toBigNumber(web3.fromWei(weiFee, "ether")); // always display 7 decimal places return ethFee.toFixed(7); } function calculateGasPrice(n) { var gasMultiplicator = Math.pow(1.4, n).toFixed(3); var gasPrice = 211000000000; try { gasPrice = web3.eth.gasPrice; } catch (err) { } return gasPrice * gasMultiplicator; } status.defineSubscription( "calculatedFee", {value: ["sliderValue"], tx: ["transaction"]}, function (params) { return calculateFee(params.value, params.tx); } ); function getFeeExplanation(n) { return I18n.t('send_explanation') + I18n.t('send_explanation_' + (n + 2)); } status.defineSubscription( "feeExplanation", {value: ["sliderValue"]}, function(params) { return getFeeExplanation(params.value); } ); function amountParameterBox(groupChat, params, context) { if (!params["bot-db"]) { params["bot-db"] = {}; } var contactAddress; if (groupChat) { if (params["bot-db"]["public"] && params["bot-db"]["public"]["recipient"]) { contactAddress = params["bot-db"]["public"]["recipient"]["address"]; } else { contactAddress = null; } } else { contactAddress = context.to; } var txData; var amount; var amountIndex = groupChat ? 1 : 0; try { amount = params.args[amountIndex].replace(",", "."); txData = { to: contactAddress, value: web3.toWei(amount) || 0 }; } catch (err) { amount = null; txData = { to: contactAddress, value: 0 }; } var sliderValue = params["bot-db"]["sliderValue"] || 0; try { status.setDefaultDb({ transaction: txData, calculatedFee: calculateFee(sliderValue, txData), feeExplanation: getFeeExplanation(sliderValue), sliderValue: sliderValue }); } catch (err) { status.setDefaultDb({ transaction: txData, calculatedFee: "0", feeExplanation: "", sliderValue: sliderValue }); } return { title: I18n.t('send_title'), showBack: true, markup: status.components.scrollView( { keyboardShouldPersistTaps: "always" }, [status.components.view( { flex: 1 }, [ status.components.text( { style: { fontSize: 14, color: "rgb(147, 155, 161)", paddingTop: 12, paddingLeft: 16, paddingRight: 16, paddingBottom: 20 } }, I18n.t('send_specify_amount') ), status.components.touchable( { onPress: status.components.dispatch([status.events.FOCUS_INPUT, []]) }, status.components.view( { flexDirection: "row", alignItems: "center", textAlign: "center", justifyContent: "center" }, [ status.components.text( { font: "light", numberOfLines: 1, ellipsizeMode: "tail", style: { maxWidth: 250, fontSize: 38, marginLeft: 8, color: "black" } }, amount || "0.00" ), status.components.text( { font: "light", style: { fontSize: 38, marginLeft: 8, color: "rgb(147, 155, 161)" } }, I18n.t('eth') ), ] ) ), status.components.text( { style: { fontSize: 14, color: "rgb(147, 155, 161)", paddingTop: 14, paddingLeft: 16, paddingRight: 16, paddingBottom: 5 } }, I18n.t('send_fee') ), status.components.view( { flexDirection: "row" }, [ status.components.text( { style: { fontSize: 17, color: "black", paddingLeft: 16 } }, [status.components.subscribe(["calculatedFee"])] ), status.components.text( { style: { fontSize: 17, color: "rgb(147, 155, 161)", paddingLeft: 4, paddingRight: 4 } }, I18n.t('eth') ) ] ), status.components.slider( { maximumValue: 2, minimumValue: -2, onSlidingComplete: status.components.dispatch( [status.events.UPDATE_DB, "sliderValue"] ), step: 1, style: { marginLeft: 16, marginRight: 16 } } ), status.components.view( { flexDirection: "row" }, [ status.components.text( { style: { flex: 1, fontSize: 14, color: "rgb(147, 155, 161)", paddingLeft: 16, alignSelf: "flex-start" } }, I18n.t('send_cheaper') ), status.components.text( { style: { flex: 1, fontSize: 14, color: "rgb(147, 155, 161)", paddingRight: 16, alignSelf: "flex-end", textAlign: "right" } }, I18n.t('send_faster') ) ] ), status.components.text( { style: { fontSize: 14, color: "black", paddingTop: 16, paddingLeft: 16, paddingRight: 16, paddingBottom: 16, lineHeight: 24 } }, [status.components.subscribe(["feeExplanation"])] ) ] )] ) }; } var recipientSendParam = { name: "recipient", type: status.types.TEXT, suggestions: function (params) { return { title: I18n.t('send_title'), markup: status.components.chooseContact(I18n.t('send_choose_recipient'), "recipient", 0) }; } }; function amountSendParam(groupChat) { return { name: "amount", type: status.types.NUMBER, suggestions: amountParameterBox.bind(this, groupChat) }; } var paramsPersonalSend = [amountSendParam(false)]; var paramsGroupSend = [recipientSendParam, amountSendParam(true)]; function validateSend(validateRecipient, params, context) { if (!params["bot-db"]) { params["bot-db"] = {}; } if (validateRecipient) { if (!params["bot-db"]["public"] || !params["bot-db"]["public"]["recipient"] || !params["bot-db"]["public"]["recipient"]["address"]) { return { markup: status.components.validationMessage( "Wrong address", "Recipient address must be specified" ) }; } } if (!params["amount"]) { return { markup: status.components.validationMessage( I18n.t('validation_title'), I18n.t('validation_amount_specified') ) }; } var amount = params["amount"].replace(",", "."); var amountSplitted = amount.split("."); if (amountSplitted.length === 2 && amountSplitted[1].length > 18) { return { markup: status.components.validationMessage( I18n.t('validation_title'), I18n.t('validation_amount_is_too_small') ) }; } if (isNaN(parseFloat(params.amount.replace(",", ".")))) { return { markup: status.components.validationMessage( I18n.t('validation_title'), I18n.t('validation_invalid_number') ) }; } try { var val = web3.toWei(amount, "ether"); if (val < 0) { throw new Error(); } } catch (err) { return { markup: status.components.validationMessage( I18n.t('validation_title'), I18n.t('validation_invalid_number') ) }; } try { var balance = web3.eth.getBalance(context.from); if (isNaN(balance)) { throw new Error(); } } catch (err) { return { markup: status.components.validationMessage( I18n.t('validation_internal_title'), I18n.t('validation_balance') ) }; } var fee = calculateFee( params["bot-db"]["sliderValue"], { to: context.to, value: val } ); if (bn(val).plus(bn(web3.toWei(fee, "ether"))).greaterThan(bn(balance))) { return { markup: status.components.validationMessage( I18n.t('validation_title'), I18n.t('validation_insufficient_amount') + web3.fromWei(balance, "ether") + " ETH)" ) }; } } function handleSend(groupChat, params, context) { var val = web3.toWei(params["amount"].replace(",", "."), "ether"); var gasPrice = calculateGasPrice(params["bot-db"]["sliderValue"]); var data = { from: context.from, value: val, gas: web3.toBigNumber(21000) }; if (groupChat) { data.to = params["bot-db"]["public"]["recipient"]["address"]; } else { data.to = context.to; } if (gasPrice) { data.gasPrice = gasPrice; } web3.eth.sendTransaction(data, function(error, hash) { if (error) { // Do nothing, as error handling will be done as response to transaction.failed event from go } else { status.sendSignal("handler-result", { status: "success", hash: hash, origParams: context["orig-params"] }); } }); // async handler, so we don't return anything immediately } function previewSend(showRecipient, params, context) { var amountStyle = { fontSize: 36, color: "#000000", height: 40 }; var amount = status.components.view( { flexDirection: "column", alignItems: "flex-end", maxWidth: 250 }, [status.components.text( { style: amountStyle, numberOfLines: 1, ellipsizeMode: "tail", font: "light" }, status.localizeNumber(params.amount, context.delimiter, context.separator) )]); var currency = status.components.view( { style: { flexDirection: "column", justifyContent: "flex-end", paddingBottom: 0 } }, [status.components.text( { style: { color: "#9199a0", fontSize: 16, lineHeight: 18, marginLeft: 7.5 } }, I18n.t('eth') )] ); var amountRow = status.components.view( { style: { flexDirection: "row", justifyContent: "space-between", marginTop: 8, marginBottom: 8 } }, [amount, currency] ); var markup = [amountRow]; if (showRecipient && params["bot-db"] && params["bot-db"]["public"] && params["bot-db"]["public"]["recipient"] && context["chat"]["group-chat"] === true) { var recipientRow = status.components.text( { style: { color: "#9199a0", fontSize: 14, lineHeight: 18 } }, I18n.t('send_sending_to') + " " + params["bot-db"]["public"]["recipient"]["name"] ); markup.push(recipientRow); } return { markup: status.components.view( { style: { flexDirection: "column" } }, markup ) }; } function shortPreviewSend(params, context) { return { markup: status.components.chatPreviewText( {}, I18n.t('send_title') + ": " + status.localizeNumber(params.amount, context.delimiter, context.separator) + " ETH" ) }; } var personalSend = { name: "send", scope: ["global", "personal-chats", "registered", "humans"], icon: "money_white", color: "#5fc48d", title: I18n.t('send_title'), description: I18n.t('send_description'), params: paramsPersonalSend, validator: validateSend.bind(this, false), handler: handleSend.bind(this, false), asyncHandler: true, preview: previewSend.bind(this, false), shortPreview: shortPreviewSend }; var groupSend = { name: "send", scope: ["global", "group-chats", "registered", "humans"], icon: "money_white", color: "#5fc48d", title: I18n.t('send_title'), description: I18n.t('send_description'), params: paramsGroupSend, validator: validateSend.bind(this, true), handler: handleSend.bind(this, true), asyncHandler: true, preview: previewSend.bind(this, true), shortPreview: shortPreviewSend }; status.command(personalSend); status.response(personalSend); status.command(groupSend); status.response(groupSend); // Request command var recipientRequestParam = { name: "recipient", type: status.types.TEXT, suggestions: function (params) { return { title: I18n.t('request_title'), markup: status.components.chooseContact(I18n.t('send_choose_recipient'), "recipient", 0) }; } }; var amountRequestParam = { name: "amount", type: status.types.NUMBER }; var paramsPersonalRequest = [amountRequestParam]; var paramsGroupRequest = [recipientRequestParam, amountRequestParam]; function handlePersonalRequest(params, context) { var val = params["amount"].replace(",", "."); return { event: "request", request: { command: "send", params: { amount: val }, prefill: [val] } }; } function handleGroupRequest(params, context) { var val = params["amount"].replace(",", "."); return { event: "request", request: { command: "send", params: { recipient: context["current-account"]["name"], amount: val }, prefill: [context["current-account"]["name"], val], prefillBotDb: { public: { recipient: context["current-account"] } } } }; } function previewRequest(showRecipient, params, context) { var amountRow = status.components.text( {}, I18n.t('request_requesting') + " " + status.localizeNumber(params.amount, context.delimiter, context.separator) + " ETH" ); var markup = [amountRow]; if (showRecipient && params["bot-db"] && params["bot-db"]["public"] && params["bot-db"]["public"]["recipient"] && context["chat"]["group-chat"] === true) { var recipientRow = status.components.text( { style: { color: "#9199a0", fontSize: 14, lineHeight: 18 } }, I18n.t('request_requesting_from') + " " + params["bot-db"]["public"]["recipient"]["name"] ); markup.push(recipientRow); } return { markup: status.components.view( { style: { flexDirection: "column" } }, markup ) }; } function shortPreviewRequest(params, context) { return { markup: status.components.chatPreviewText( {}, I18n.t('request_requesting') + " " + status.localizeNumber(params.amount, context.delimiter, context.separator) + " ETH" ) }; } function validateRequest(validateRecipient, params) { if (!params["bot-db"]) { params["bot-db"] = {}; } if (validateRecipient) { if (!params["bot-db"]["public"] || !params["bot-db"]["public"]["recipient"] || !params["bot-db"]["public"]["recipient"]["address"]) { return { markup: status.components.validationMessage( "Wrong address", "Recipient address must be specified" ) }; } } if (!params["amount"]) { return { markup: status.components.validationMessage( I18n.t('validation_title'), I18n.t('validation_amount_specified') ) }; } var amount = params.amount.replace(",", "."); var amountSplitted = amount.split("."); if (amountSplitted.length === 2 && amountSplitted[1].length > 18) { return { markup: status.components.validationMessage( I18n.t('validation_title'), I18n.t('validation_amount_is_too_small') ) }; } if (isNaN(parseFloat(params.amount.replace(",", ".")))) { return { markup: status.components.validationMessage( I18n.t('validation_title'), I18n.t('validation_invalid_number') ) }; } try { var val = web3.toWei(amount, "ether"); if (val < 0) { throw new Error(); } } catch (err) { return { markup: status.components.validationMessage( I18n.t('validation_title'), I18n.t('validation_invalid_number') ) }; } } status.command({ name: "request", scope: ["global", "personal-chats", "registered", "humans"], icon: "money_white", color: "#5fc48d", title: I18n.t('request_title'), description: I18n.t('request_description'), params: paramsPersonalRequest, handler: handlePersonalRequest, preview: previewRequest.bind(null, false), shortPreview: shortPreviewRequest, validator: validateRequest.bind(null, false) }); status.command({ name: "request", scope: ["global", "group-chats", "registered", "humans"], icon: "money_white", color: "#5fc48d", title: I18n.t('request_title'), description: I18n.t('request_description'), params: paramsGroupRequest, handler: handleGroupRequest, preview: previewRequest.bind(null, true), shortPreview: shortPreviewRequest, validator: validateRequest.bind(null, true) });