aion-gql-webcomponents
Version:
AION Webcomponents using AION GraphQL
720 lines (719 loc) • 33.2 kB
JavaScript
import '../../global/global.js';
import { Constant } from "../../common/Constant";
import { Transaction } from "../../common/Transaction";
import { TxnResponse } from "../../common/TxnResponse";
import { CryptoUtil } from "../../providers/util/CryptoUtil";
import { LedgerProvider } from "../../providers/impl/ledger/LedgerProvider";
import PrivateKeyWalletProvider from "../../providers/impl/PrivateKeyWalletProvider";
import KeystoreWalletProvider from "../../providers/impl/KeystoreWalletProvider";
import AionPayService from "./AionPayService";
import { TransactionUtil } from "../../providers/util/TransactionUtil";
export class AionPay {
constructor() {
this.default_button_text = "Pay";
this.to_readonly = false;
this.readonly = false;
this.unlockBy = "ledger";
this.visible = false;
this.inputDialogEnable = false;
this.txnInProgress = false;
this.txnDone = false;
this.showConfirm = false;
this.isNotification = false;
this.isError = false;
this.errors = [];
this.keystoreLoadingPercentage = 0;
this.gas = TransactionUtil.defaultNrgLimit;
this.gasPrice = TransactionUtil.defaultNrgPrice;
this.txnResponse = new TxnResponse();
this.handleHidePaymentDialog = this.handleHidePaymentDialog.bind(this);
this.handleShowPaymentDialog = this.handleShowPaymentDialog.bind(this);
this.handleHideTransactionInprogressDialog = this.handleHideTransactionInprogressDialog.bind(this);
this.handleCloseConfirmDialog = this.handleCloseConfirmDialog.bind(this);
this.handleHideError = this.handleHideError.bind(this);
this.handleHideNotification = this.handleHideNotification.bind(this);
this.handleResetData = this.handleResetData.bind(this);
this.handleShowInputDialog = this.handleShowInputDialog.bind(this);
this.handleHideInputDialog = this.handleHideInputDialog.bind(this);
this.signPayment = this.signPayment.bind(this);
this.confirmPayment = this.confirmPayment.bind(this);
this.handleUnlockBy = this.handleUnlockBy.bind(this);
this.handleFromInput = this.handleFromInput.bind(this);
this.handleToInput = this.handleToInput.bind(this);
this.handleValueInput = this.handleValueInput.bind(this);
this.handleNrgInput = this.handleNrgInput.bind(this);
this.handleNrgPriceInput = this.handleNrgPriceInput.bind(this);
this.handleMessageInput = this.handleMessageInput.bind(this);
this.handlePrivateKeyInput = this.handlePrivateKeyInput.bind(this);
this.handleKeyStoreFileSelected = this.handleKeyStoreFileSelected.bind(this);
this.handleKeystorePasswordInput = this.handleKeystorePasswordInput.bind(this);
this.handleUnlockKeystore = this.handleUnlockKeystore.bind(this);
this.handleDerivePublicKey = this.handleDerivePublicKey.bind(this);
this.submitRawTransansaction = this.submitRawTransansaction.bind(this);
this.handleLedgerConnect = this.handleLedgerConnect.bind(this);
}
componentWillLoad() {
if (this.to)
this._to = this.to.toLowerCase();
if (this.to)
this.to_readonly = true;
this.service = new AionPayService(this.gqlUrl);
}
refreshAndShow() {
this.handleResetData();
this.handleShowPaymentDialog();
}
showWithData(refId, to, value, data) {
this.handleResetData();
this._to = to;
this.value = value;
this.message = data;
this.readonly = true;
this.refId = refId;
this.handleShowPaymentDialog();
}
handleShowPaymentDialog() {
this.visible = true;
}
handleHidePaymentDialog() {
this.visible = false;
this.inputDialogEnable = false;
this.handleResetData();
}
handleShowInputDialog() {
if (!this.from) {
this.isError = true;
this.errors.push("Please select a wallet provider and unlock it.");
return;
}
this.visible = false;
this.inputDialogEnable = true;
}
handleHideInputDialog() {
this.inputDialogEnable = false;
}
handleHideTransactionInprogressDialog() {
this.txnInProgress = false;
this.txnDone = false;
this.visible = false;
this.handleResetData();
}
handleCloseConfirmDialog() {
this.showConfirm = false;
this.handleResetData();
}
handleHideError() {
this.isError = false;
this.errors.length = 0;
}
handleHideNotification() {
this.isNotification = false;
this.notification = null;
}
handleResetData() {
this.txnInProgress = false;
this.showConfirm = false;
this.txnDone = false;
this.visible = false;
this.resetFromAddressData();
if (!this.to_readonly) {
this._to = '';
}
this.value = 0;
this.message = '';
this.gas = TransactionUtil.defaultNrgLimit;
this.gasPrice = TransactionUtil.defaultNrgPrice;
this.isError = false;
this.errors.length = 0;
this.isNotification = false;
this.notification = '';
this.resetTxnResponse();
}
resetFromAddressData() {
this.from = '';
this.fromBalance = null;
this.privateKey = '';
this.privateKey = '';
this.keystore_password = '';
this.keystore = null;
this.keystoreLoadingPercentage = 0;
}
resetTxnResponse() {
this.txnResponse.txHash = '';
this.txnResponse.msgHash = '';
this.txnResponse.error = '';
this.txnResponse.txResult = '';
this.txnResponse.status = '';
}
handleUnlockBy(event) {
let oldValue = this.unlockBy;
this.unlockBy = event.target.value;
if (oldValue != this.unlockBy)
this.resetFromAddressData();
}
handleToInput(event) {
this._to = event.target.value;
if (event.target.validity.typeMismatch) {
console.log('this element is not valid');
}
}
handleFromInput(event) {
this.from = event.target.value;
if (event.target.validity.typeMismatch) {
console.log('this element is not valid');
}
}
handleValueInput(event) {
this.value = event.target.value;
if (event.target.validity.typeMismatch) {
console.log('this element is not valid');
}
}
handleNrgInput(event) {
this.gas = event.target.value;
}
handleNrgPriceInput(event) {
this.gasPrice = event.target.value;
}
handleMessageInput(event) {
this.message = event.target.value;
if (this.unlockBy == 'ledger') {
if (this.message.length > 50) {
if (!this.isError) {
this.isError = true;
this.errors.length = 0;
this.errors.push("Please keep your message within 50 characters while using ledger. Or, your transaction may fail.");
}
}
else {
if (this.isError) {
this.isError = false;
this.errors.length = 0;
}
}
}
}
handlePrivateKeyInput(event) {
this.privateKey = event.target.value;
if (event.target.validity.typeMismatch) {
console.log('this element is not valid');
}
}
async handleDerivePublicKey() {
if (!this.privateKey || this.privateKey.trim().length == 0)
return;
try {
this.handleHideError();
this.provider = null;
this.provider = new PrivateKeyWalletProvider(this.privateKey);
let [address] = await this.provider.unlock(null);
if (address)
this.from = address;
this.updateBalance();
}
catch (error) {
console.log(error);
this.from = '';
this.fromBalance = null;
this.isError = true;
this.errors.push("Public Key derivation failed: " + error.toString());
throw error;
}
}
async updateBalance() {
try {
let [balance, nrgPrice] = await this.service.fetchBalance(this.from);
if (balance)
this.fromBalance = CryptoUtil.convertnAmpBalanceToAION(balance);
if (nrgPrice)
this.gasPrice = nrgPrice;
}
catch (error) {
this.isError = true;
this.errors.push("Error getting balance for the address");
this.errors.push("[Reason] " + error);
return;
}
}
handleKeyStoreFileSelected(event) {
this.resetFromAddressData();
this.keystore = event.srcElement.files[0];
}
handleKeystorePasswordInput(event) {
this.keystore_password = event.target.value;
}
async handleUnlockKeystore() {
let reader = new FileReader();
try {
reader.readAsArrayBuffer(this.keystore);
}
catch (error) {
console.error("Error loading keystore file" + error);
this.isError = true;
this.errors.push("Invalid keystore file. " + error.toString());
}
let me = this;
reader.onload = async function () {
let content = reader.result;
me.handleHideError();
me.provider = new KeystoreWalletProvider(content, me.keystore_password);
try {
let [address] = await me.provider.unlock((progress) => {
me.keystoreLoadingPercentage = Math.round(progress);
});
me.from = address;
me.updateBalance();
}
catch (error) {
console.log("Error in opening keystore fie. " + error);
me.isError = true;
me.errors.push("Could not open the keystore file. " + error.toString());
}
};
}
async handleLedgerConnect() {
this.provider = new LedgerProvider();
try {
let [address] = await this.provider.unlock(null);
this.from = address;
this.updateBalance();
}
catch (e) {
this.isError = true;
this.errors.push(e.toString());
}
}
validateInput() {
this.handleHideError();
if (isNaN(this.value)) {
this.isError = true;
this.errors.push("Amount is not valid");
}
if (this.unlockBy == 'private_key') {
if (!this.privateKey || this.privateKey.trim().length == 0) {
this.isError = true;
this.errors.push("Private key can not be empty");
}
}
if (!this._to || this._to.trim().length == 0) {
this.isError = true;
this.errors.push("To address can not be empty");
}
return !this.isError;
}
async signPayment(e) {
e.preventDefault();
if (!this.validateInput()) {
console.log('not a valid input');
return;
}
console.log("All valid input");
if (!this._to) {
this.handleDerivePublicKey();
}
console.log("lets do signing first..");
this.amount = CryptoUtil.convertAIONTonAmpBalance(this.value);
let txn = new Transaction();
txn.to = this._to;
txn.value = this.amount;
if (this.message) {
txn.data = this.message;
}
txn.gas = this.gas + '';
txn.gasPrice = this.gasPrice + '';
let retVal = null;
try {
retVal = await this.service.fetchNonceNrg(this.from, txn);
}
catch (e) {
this.isError = true;
this.errors.push("Error to get nonce for the address");
this.errors.push("[Reason] " + e);
return;
}
if (!retVal) {
this.isError = true;
this.errors.push("Unable to get nonce and nrgPrice from AION kernel");
return;
}
txn.nonce = retVal[0];
let estimatedNrg = retVal[1];
if (estimatedNrg && estimatedNrg > 0) {
if (Number(txn.gas) < estimatedNrg) {
let r = confirm("Estimated Nrg : " + estimatedNrg + "\nDefault Nrg Limit : " + txn.gas
+ "\n\nDo you want to update the Nrg Limit to " + estimatedNrg + "?");
if (r == true) {
this.gas = estimatedNrg;
return;
}
else {
}
}
}
console.log("Fetching current nonce " + txn.nonce);
if (this.unlockBy == 'ledger') {
this.isNotification = true;
this.notification = "Please check your ledger device to confirm the transaction.";
}
try {
this.encodedTxn = await this.provider.sign(txn);
}
catch (error) {
console.log(error);
this.isError = true;
this.errors.push("Error in signing transaction. Please refresh and try again");
this.errors.push("[Reason] " + error);
throw error;
}
this.encodedTxn.input.from = this.from;
this.showConfirm = true;
this.visible = false;
this.inputDialogEnable = false;
console.log(this.encodedTxn);
}
async confirmPayment() {
this.showConfirm = false;
this.submitRawTransansaction(this.encodedTxn.rawTransaction);
}
async submitRawTransansaction(encodedTx) {
this.txnInProgress = true;
this.txnDone = false;
try {
this.transactionInProgress.emit({ refId: this.refId, data: encodedTx });
this.txnResponse = await this.service.sendRawTransaction(encodedTx);
console.log(this.txnResponse);
this.txnDone = true;
this.transactionCompleted.emit({ refId: this.refId, data: this.txnResponse });
}
catch (error) {
this.txnDone = true;
this.isError = true;
this.errors.push("Error sending the transaction");
this.errors.push("[Reason] " + error);
this.transactionFailed.emit({ refId: this.refId, data: error.toString() });
throw error;
}
finally {
}
}
renderError() {
return (h("div", { class: "error-section" }, this.isError ?
h("div", { class: "notification is-warning is-small" },
h("button", { class: "delete", onClick: this.handleHideError }),
h("ul", null, this.errors.map((msg) => h("li", null, msg)))) : null));
}
renderNotification() {
return (h("div", null, this.isNotification ?
h("div", { class: "notification is-info error-section" },
h("button", { class: "delete", onClick: this.handleHideNotification }),
this.notification) : null));
}
renderSelectProvider() {
return (h("div", null,
h("div", { class: "modal is-active" },
h("div", { class: "modal-background" }),
h("div", { class: "modal-card" },
h("header", { class: "modal-card-head" },
h("img", { src: Constant.aion_logo, class: "aion-image" }),
h("p", { class: "modal-card-title" }, "Choose your wallet provider"),
h("button", { class: "delete", "aria-label": "close", onClick: this.handleHidePaymentDialog }, "\u00D7")),
h("section", { class: "modal-card-body" },
this.renderError(),
h("div", { class: "field" },
h("div", { class: "control" },
h("label", { class: "label is-small" },
h("input", { type: "radio", name: "unlock_by", value: "ledger", checked: this.unlockBy === 'ledger', onClick: (event) => this.handleUnlockBy(event) }),
"\u00A0Ledger"),
h("label", { class: "label is-small" },
h("input", { type: "radio", name: "unlock_by", value: "keystore", checked: this.unlockBy === 'keystore', onClick: (event) => this.handleUnlockBy(event) }),
"\u00A0Keystore File"),
h("label", { class: "label is-small" },
h("input", { type: "radio", name: "unlock_by", value: "private_key", checked: this.unlockBy === 'private_key', onClick: (event) => this.handleUnlockBy(event) }),
"\u00A0Private Key"))),
h("hr", null),
h("div", { class: "form" },
this.renderUnlockOptions(),
h("div", { class: "field" },
h("label", { class: "label" }, "\u00A0\u00A0"),
h("div", { class: "control" },
h("input", { id: "from", placeholder: "From Address", class: "input is-small", value: this.from, onInput: (e) => this.handleFromInput(e), readOnly: true, disabled: true }),
this.fromBalance ?
h("label", { class: "label is-small from-balance is-pulled-right" },
"Balance: ",
this.fromBalance,
" AION") : null)))),
h("footer", { class: "modal-card-foot" },
h("button", { class: "button is-primary is-small is-rounded is-pulled-right", disabled: !this.from, onClick: this.handleShowInputDialog }, "Next"),
h("button", { class: "button is-danger is-small is-rounded is-right", onClick: this.handleHidePaymentDialog }, "Cancel"))))));
}
renderUnlockOptions() {
return (h("div", null,
this.unlockBy == 'private_key' ?
h("div", { class: "field" },
h("label", { class: "label is-small" }, "Private Key"),
h("div", { class: "control" },
h("input", { id: "private_key", placeholder: "Private Key", class: "input is-small", value: this.privateKey, type: "password", onInput: (e) => this.handlePrivateKeyInput(e), onBlur: this.handleDerivePublicKey }))) : null,
this.unlockBy == 'keystore' ?
this._renderUnlockByKeystore() : null,
this.unlockBy == 'ledger' ?
h("div", { class: "field" },
h("div", { class: "control" },
h("button", { class: "button is-small is-danger is-focused is-rounded is-outlined", onClick: this.handleLedgerConnect }, "Connect To Ledger"))) : null));
}
_renderUnlockByKeystore() {
return (h("div", null,
h("div", { class: "field is-small" },
h("label", { class: "label is-small", htmlFor: "private_key" }, "Key Store File"),
h("div", { class: "control" },
h("input", { id: "file-upload", class: "is-small", type: "file", accept: "*", onChange: (e) => this.handleKeyStoreFileSelected(e) }))),
h("div", { class: "field" },
h("label", { class: "label is-small", htmlFor: "keystore_password" }, "Passowrd"),
h("div", { class: "control" },
h("input", { id: "keystore_password", type: "password", class: "input is-small", value: this.keystore_password, onInput: this.handleKeystorePasswordInput }))),
h("div", { class: "field is-grouped" },
h("div", { class: "control is-pulled-right" },
h("button", { name: "unlock_button", class: "button is-small is-danger is-focused is-rounded is-outlined", onClick: this.handleUnlockKeystore }, "Unlock"))),
this.keystoreLoadingPercentage && this.keystoreLoadingPercentage != 0 && this.keystoreLoadingPercentage != 100 ?
h("div", { class: "modal is-active" },
h("div", { class: "modal-background " }),
h("div", { class: "modal-card" },
h("label", { class: "color-white" },
"Unlocking keystore ... ",
this.keystoreLoadingPercentage,
"%"),
h("progress", { class: "progress", value: this.keystoreLoadingPercentage, max: "100" },
this.keystoreLoadingPercentage,
"%")))
: null));
}
renderInputForm() {
return (h("div", null,
h("div", { class: "modal is-active" },
h("div", { class: "modal-background" }),
h("div", { class: "modal-card" },
h("header", { class: "modal-card-head" },
h("img", { src: Constant.aion_logo, class: "aion-image" }),
h("p", { class: "modal-card-title" }, "Transfer AION"),
h("button", { class: "delete", "aria-label": "close", onClick: this.handleHidePaymentDialog }, "\u00D7")),
h("section", { class: "modal-card-body form" },
this.renderError(),
this.renderNotification(),
h("div", { class: "field" },
h("label", { class: "label is-small", htmlFor: "from" }, "From"),
h("div", { class: "control" },
h("input", { id: "from", placeholder: "From Address", class: "input is-small", value: this.from, onInput: (e) => this.handleFromInput(e), readOnly: true, disabled: true }),
this.fromBalance ?
h("label", { class: "label is-small is-pulled-right from-balance" },
"Balance: ",
this.fromBalance,
" AION") : null)),
h("div", { class: "field" },
h("label", { class: "label is-small", htmlFor: "to" }, "To"),
h("div", { class: "control" },
h("input", { id: "to", placeholder: "To Address", class: "input is-small", value: this._to, onInput: this.handleToInput, readOnly: this.to_readonly || this.readonly }))),
h("div", { class: "field" },
h("label", { class: "label is-small", htmlFor: "value" }, "Amount"),
h("div", { class: "control" },
h("input", { id: "value", placeholder: "Enter amount", class: "input is-small", value: this.value, type: "number", onInput: this.handleValueInput, readonly: this.readonly }))),
h("div", { class: "columns" },
h("div", { class: "column" },
h("div", { class: "field" },
h("label", { class: "label is-small", htmlFor: "nrg" }, "Nrg Limit"),
h("div", { class: "control" },
h("input", { id: "nrg", placeholder: "Nrg Limit", class: "input is-small", value: this.gas, type: "number", onInput: this.handleNrgInput })))),
h("div", { class: "column" },
h("div", { class: "field" },
h("label", { class: "label is-small", htmlFor: "nrgPrice" }, "Nrg Price"),
h("div", { class: "control" },
h("input", { id: "nrgPrice", placeholder: "Nrg Price", class: "input is-small", value: this.gasPrice, type: "number", onInput: this.handleNrgPriceInput }))))),
h("div", { class: "field" },
h("label", { class: "label is-small", htmlFor: "message" }, "Message"),
h("div", { class: "control" },
h("textarea", { id: "message", class: "textarea is-small", placeholder: "Optional message", onInput: this.handleMessageInput, readonly: this.readonly }, this.message)))),
h("footer", { class: "modal-card-foot" },
h("button", { class: "button is-primary is-small is-rounded", onClick: this.signPayment }, "Next"),
h("button", { class: "button is-danger is-small is-rounded", onClick: this.handleHidePaymentDialog }, "Cancel"))))));
}
renderShowConfirmation() {
return (h("div", { class: "modal is-active" },
h("div", { class: "modal-background" }),
h("div", { class: "modal-card" },
h("header", { class: "modal-card-head" },
h("img", { src: Constant.aion_logo, class: "aion-image" }),
h("p", { class: "modal-card-title" }, "Confirm Transaction"),
h("button", { class: "delete", "aria-label": "close", onClick: this.handleHidePaymentDialog }, "\u00D7")),
h("section", { class: "modal-card-body form" },
this.renderError(),
h("div", { class: "columns" },
h("div", { class: "column is-1" },
h("label", { class: "lable" }, "From")),
h("div", { class: "column field" }, this.encodedTxn.input.from)),
h("div", { class: "columns" },
h("div", { class: "column is-1" }, "To"),
h("div", { class: "column field" }, this.encodedTxn.input.to)),
h("div", { class: "columns" },
h("div", { class: "column is-1" }, "Value"),
h("div", { class: "column field" },
CryptoUtil.convertnAmpBalanceToAION(this.encodedTxn.input.value),
" AION")),
h("div", { class: "columns" },
h("div", { class: "column is-1" }, "Nrg"),
h("div", { class: "column field" }, this.encodedTxn.input.gas)),
h("div", { class: "columns" },
h("div", { class: "column is-1" }, "Nrg Price"),
h("div", { class: "column field" }, this.encodedTxn.input.gasPrice)),
h("div", { class: "columns" },
h("div", { class: "column is-1" }, "Raw Transaction"),
h("div", { class: "column field" },
h("textarea", { class: "input is-small", rows: 10, readOnly: true }, this.encodedTxn.rawTransaction)))),
h("footer", { class: "modal-card-foot" },
h("button", { type: "button", class: "button is-primary is-small is-rounded", onClick: this.confirmPayment }, "Confirm"),
h("button", { type: "button", class: "button is-danger is-small is-rounded", onClick: this.handleHidePaymentDialog }, "Close")))));
}
renderTxnInProgress() {
return (h("div", null,
h("div", { class: "modal is-active" },
h("div", { class: "modal-background" }),
h("div", { class: "modal-card" },
h("header", { class: "modal-card-head" },
h("img", { src: Constant.aion_logo, class: "aion-image" }),
h("p", { class: "modal-card-title" }, "Sending AION"),
h("button", { class: "delete", "aria-label": "close", onClick: this.handleHidePaymentDialog }, "\u00D7")),
h("section", { class: "modal-card-body form" },
this.renderError(),
!this.txnDone ?
h("div", null,
h("div", { class: "spinner" }, "Loading ..."),
"\u00A0 ",
h("i", null, "Sending transaction and waiting for at least one block confirmation. Please wait ...")) :
h("div", null, this.txnResponse.txHash ?
h("span", null,
"Transaction Hash: ",
h("a", { href: Constant.explorer_base_url + "transaction/" + this.txnResponse.txHash, target: "_blank" }, this.txnResponse.txHash)) : h("span", null, "Transaction could not be completed")))))));
}
render() {
return (h("div", { class: "aion-pay" },
this.visible ?
this.renderSelectProvider() : null,
this.inputDialogEnable ?
this.renderInputForm() : null,
this.txnInProgress ?
this.renderTxnInProgress() : h("div", null),
this.showConfirm ?
this.renderShowConfirmation() : null,
h("div", { id: "pay", onClick: this.handleShowPaymentDialog },
h("slot", null,
h("button", { type: "button", class: "button pay-button is-primary" },
h("span", { class: "pay-button-text" },
h("img", { src: Constant.aion_logo, class: "img-valign" }),
this.buttonText ? this.buttonText : this.default_button_text))))));
}
static get is() { return "aion-pay"; }
static get encapsulation() { return "shadow"; }
static get properties() { return {
"_to": {
"state": true
},
"buttonText": {
"type": String,
"attr": "button-text"
},
"encodedTxn": {
"state": true
},
"errors": {
"state": true
},
"from": {
"state": true
},
"fromBalance": {
"state": true
},
"gas": {
"state": true
},
"gasPrice": {
"state": true
},
"gqlUrl": {
"type": String,
"attr": "gql-url"
},
"inputDialogEnable": {
"state": true
},
"isError": {
"state": true
},
"isNotification": {
"state": true
},
"keystore_password": {
"state": true
},
"keystoreLoadingPercentage": {
"state": true
},
"message": {
"state": true
},
"notification": {
"state": true
},
"privateKey": {
"state": true
},
"refreshAndShow": {
"method": true
},
"showConfirm": {
"state": true
},
"showWithData": {
"method": true
},
"to": {
"type": String,
"attr": "to"
},
"txnDone": {
"state": true
},
"txnInProgress": {
"state": true
},
"txnResponse": {
"state": true
},
"unlockBy": {
"state": true
},
"value": {
"state": true
},
"visible": {
"state": true
}
}; }
static get events() { return [{
"name": "TXN_COMPLETED",
"method": "transactionCompleted",
"bubbles": true,
"cancelable": true,
"composed": true
}, {
"name": "TXN_INPROGRESS",
"method": "transactionInProgress",
"bubbles": true,
"cancelable": true,
"composed": true
}, {
"name": "TXN_FAILED",
"method": "transactionFailed",
"bubbles": true,
"cancelable": true,
"composed": true
}]; }
static get style() { return "/**style-placeholder:aion-pay:**/"; }
}