diff --git a/qortal-ui-core/src/plugins/routes.js b/qortal-ui-core/src/plugins/routes.js
index 37e68f98..8794da24 100644
--- a/qortal-ui-core/src/plugins/routes.js
+++ b/qortal-ui-core/src/plugins/routes.js
@@ -20,6 +20,7 @@ const cancelAllOffers = api.cancelAllOffers
const sendBtc = api.sendBtc
const sendLtc = api.sendLtc
const sendDoge = api.sendDoge
+const sendRvn = api.sendRvn
export const routes = {
hello: async (req) => {
@@ -333,4 +334,17 @@ export const routes = {
}
return response
},
+
+ sendRvn: async (req) => {
+ let response
+ try {
+ const res = await sendRvn(req.data)
+ response = res
+ } catch (e) {
+ console.error(e)
+ console.error(e.message)
+ response = e.message
+ }
+ return response
+ },
}
diff --git a/qortal-ui-crypto/api/PhraseWallet.js b/qortal-ui-crypto/api/PhraseWallet.js
index 86aaca35..4f2bd7e2 100644
--- a/qortal-ui-crypto/api/PhraseWallet.js
+++ b/qortal-ui-crypto/api/PhraseWallet.js
@@ -138,11 +138,27 @@ export default class PhraseWallet {
}
}).createWallet(new Uint8Array(dogeSeed), false, 'DOGE');
+ // Create Ravencoin HD Wallet
+ const rvnSeed = [...addrSeed];
+ const rvnWallet = new AltcoinHDWallet({
+ mainnet: {
+ private: 0x0488ADE4,
+ public: 0x0488B21E,
+ prefix: 0x3C
+ },
+ testnet: {
+ private: 0x04358394,
+ public: 0x043587CF,
+ prefix: 0x6F
+ }
+ }).createWallet(new Uint8Array(rvnSeed), false, 'RVN');
+
this._addresses[nonce] = {
address,
btcWallet,
ltcWallet,
dogeWallet,
+ rvnWallet,
qoraAddress,
keyPair: {
publicKey: addrKeyPair.publicKey,
diff --git a/qortal-ui-crypto/api/api.js b/qortal-ui-crypto/api/api.js
index 3fc50db4..2eb99bed 100644
--- a/qortal-ui-crypto/api/api.js
+++ b/qortal-ui-crypto/api/api.js
@@ -4,6 +4,6 @@ export { transactionTypes as transactions } from './transactions/transactions.js
export { processTransaction, createTransaction, computeChatNonce, signChatTransaction, signArbitraryTransaction } from './createTransaction.js'
-export { tradeBotCreateRequest, tradeBotRespondRequest, signTradeBotTxn, deleteTradeOffer, sendBtc, sendLtc, sendDoge } from './tradeRequest.js'
+export { tradeBotCreateRequest, tradeBotRespondRequest, signTradeBotTxn, deleteTradeOffer, sendBtc, sendLtc, sendDoge, sendRvn } from './tradeRequest.js'
export { cancelAllOffers } from './transactions/trade-portal/tradeoffer/cancelAllOffers.js'
diff --git a/qortal-ui-crypto/api/tradeRequest.js b/qortal-ui-crypto/api/tradeRequest.js
index 8eab79a6..49529287 100644
--- a/qortal-ui-crypto/api/tradeRequest.js
+++ b/qortal-ui-crypto/api/tradeRequest.js
@@ -101,3 +101,17 @@ export const sendDoge = (requestObject) => {
body: JSON.stringify(requestObject)
})
}
+
+// Send RVN
+export const sendRvn = (requestObject) => {
+ const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node];
+
+ return request(`/crosschain/rvn/send?apiKey=${myNode.apiKey}`, {
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(requestObject)
+ })
+}
diff --git a/qortal-ui-plugins/plugins/core/wallet/wallet-app.src.js b/qortal-ui-plugins/plugins/core/wallet/wallet-app.src.js
index a5df9107..65444625 100644
--- a/qortal-ui-plugins/plugins/core/wallet/wallet-app.src.js
+++ b/qortal-ui-plugins/plugins/core/wallet/wallet-app.src.js
@@ -25,7 +25,7 @@ import '@github/time-elements'
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
-const coinsNames = ['qort', 'btc', 'ltc', 'doge']
+const coinsNames = ['qort', 'btc', 'ltc', 'doge', 'rvn']
class MultiWallet extends LitElement {
static get properties() {
@@ -46,6 +46,8 @@ class MultiWallet extends LitElement {
ltcAmount: { type: Number },
dogeRecipient: { type: String },
dogeAmount: { type: Number },
+ rvnRecipient: { type: String },
+ rvnAmount: { type: Number },
errorMessage: { type: String },
successMessage: { type: String },
sendMoneyLoading: { type: Boolean },
@@ -55,6 +57,7 @@ class MultiWallet extends LitElement {
btcFeePerByte: { type: Number },
ltcFeePerByte: { type: Number },
dogeFeePerByte: { type: Number },
+ rvnFeePerByte: { type: Number },
balanceString: { type: String }
}
}
@@ -421,6 +424,10 @@ class MultiWallet extends LitElement {
background-image: url('/img/doge.png');
}
+ .rvn .currency-image {
+ background-image: url('/img/rvn.png');
+ }
+
.card-list {
margin-top: 20px;
}
@@ -553,6 +560,7 @@ class MultiWallet extends LitElement {
this.btcRecipient = ''
this.ltcRecipient = ''
this.dogeRecipient = ''
+ this.rvnRecipient = ''
this.errorMessage = ''
this.successMessage = ''
this.sendMoneyLoading = false
@@ -563,6 +571,7 @@ class MultiWallet extends LitElement {
this.btcAmount = 0
this.ltcAmount = 0
this.dogeAmount = 0
+ this.rvnAmount = 0
this.btcFeePerByte = 100
this.btcSatMinFee = 20
this.btcSatMaxFee = 150
@@ -572,6 +581,9 @@ class MultiWallet extends LitElement {
this.dogeFeePerByte = 1000
this.dogeSatMinFee = 100
this.dogeSatMaxFee = 10000
+ this.rvnFeePerByte = 1125
+ this.rvnSatMinFee = 1000
+ this.rvnSatMaxFee = 10000
this.wallets = new Map()
@@ -591,6 +603,7 @@ class MultiWallet extends LitElement {
this.wallets.get('btc').wallet = window.parent.reduxStore.getState().app.selectedAddress.btcWallet
this.wallets.get('ltc').wallet = window.parent.reduxStore.getState().app.selectedAddress.ltcWallet
this.wallets.get('doge').wallet = window.parent.reduxStore.getState().app.selectedAddress.dogeWallet
+ this.wallets.get('rvn').wallet = window.parent.reduxStore.getState().app.selectedAddress.rvnWallet
this._selectedWallet = 'qort'
@@ -603,6 +616,7 @@ class MultiWallet extends LitElement {
this.wallets.get('btc').wallet = window.parent.reduxStore.getState().app.selectedAddress.btcWallet
this.wallets.get('ltc').wallet = window.parent.reduxStore.getState().app.selectedAddress.ltcWallet
this.wallets.get('doge').wallet = window.parent.reduxStore.getState().app.selectedAddress.dogeWallet
+ this.wallets.get('rvn').wallet = window.parent.reduxStore.getState().app.selectedAddress.rvnWallet
})
parentEpml.subscribe('copy_menu_switch', async (value) => {
@@ -636,6 +650,10 @@ class MultiWallet extends LitElement {
Dogecoin
+
@@ -862,6 +880,56 @@ class MultiWallet extends LitElement {
+
+
+
${translate("walletpage.wchange5")}
+
+
+
+
${translate("walletpage.wchange6")}
+
+
+ ${translate("walletpage.wchange40")}
+ ${this.selectedTransaction.rvnTxnFlow === 'OUT' ? html`${translate("walletpage.wchange7")}` : html`${translate("walletpage.wchange8")}`}
+
+
${translate("walletpage.wchange9")}
+
+
+ ${this.selectedTransaction.rvnSender}
+
+
${translate("walletpage.wchange10")}
+
+
+ ${this.selectedTransaction.rvnReceiver}
+
+
${translate("walletpage.wchange12")}
+
+
+ ${(this.selectedTransaction.feeAmount / 1e8).toFixed(8)} RVN
+
+
${translate("walletpage.wchange37")}
+
+
+ ${(this.selectedTransaction.totalAmount / 1e8).toFixed(8)} RVN
+
+
${translate("walletpage.wchange14")}
+
+
${new Date(this.selectedTransaction.timestamp).toString()}
+
${translate("walletpage.wchange16")}
+
+
+ ${this.selectedTransaction.txHash}
+
+
+
+ ${translate("general.close")}
+
+
+
@@ -1148,6 +1216,82 @@ class MultiWallet extends LitElement {
${translate("general.close")}
+
+
+
+
+
![](/img/rvn.png)
+
${translate("walletpage.wchange17")} RVN
+
+
+
+ ${translate("walletpage.wchange18")}:
+ ${this.getSelectedWalletAddress()}
+
+
+ ${translate("walletpage.wchange19")}:
+ ${this.balanceString}
+
+
+ { this._checkAmount(e) }}"
+ id="rvnAmountInput"
+ label="${translate("walletpage.wchange11")} (RVN)"
+ type="number"
+ auto-validate="false"
+ value="${this.rvnAmount}"
+ >
+
+
+
+
+
+
+
+
+ ${translate("walletpage.wchange24")}: ${(this.rvnFeePerByte / 1e8).toFixed(8)} RVN
L${translate("walletpage.wchange25")}
+
+
(this.rvnFeePerByte = e.target.value)}"
+ id="rvnFeeSlider"
+ min="${this.rvnSatMinFee}"
+ max="${this.rvnSatMaxFee}"
+ value="${this.rvnFeePerByte}"
+ >
+
+
+
${this.errorMessage}
+
${this.successMessage}
+ ${this.sendMoneyLoading ? html`
` : ''}
+
+
+
+ ${translate("general.close")}
+
+
`
}
@@ -1389,6 +1533,52 @@ class MultiWallet extends LitElement {
}
checkSelectedTextAndShowMenu()
})
+
+ this.shadowRoot.getElementById('rvnAmountInput').addEventListener('contextmenu', (event) => {
+ const getSelectedText = () => {
+ var text = ''
+ if (typeof window.getSelection != 'undefined') {
+ text = window.getSelection().toString()
+ } else if (typeof this.shadowRoot.selection != 'undefined' && this.shadowRoot.selection.type == 'Text') {
+ text = this.shadowRoot.selection.createRange().text
+ }
+ return text
+ }
+ const checkSelectedTextAndShowMenu = () => {
+ let selectedText = getSelectedText()
+ if (selectedText && typeof selectedText === 'string') {
+ } else {
+ this.pasteMenu(event, 'rvnAmountInput')
+ this.isPasteMenuOpen = true
+ event.preventDefault()
+ event.stopPropagation()
+ }
+ }
+ checkSelectedTextAndShowMenu()
+ })
+
+ this.shadowRoot.getElementById('rvnRecipient').addEventListener('contextmenu', (event) => {
+ const getSelectedText = () => {
+ var text = ''
+ if (typeof window.getSelection != 'undefined') {
+ text = window.getSelection().toString()
+ } else if (typeof this.shadowRoot.selection != 'undefined' && this.shadowRoot.selection.type == 'Text') {
+ text = this.shadowRoot.selection.createRange().text
+ }
+ return text
+ }
+ const checkSelectedTextAndShowMenu = () => {
+ let selectedText = getSelectedText()
+ if (selectedText && typeof selectedText === 'string') {
+ } else {
+ this.pasteMenu(event, 'rvnRecipient')
+ this.isPasteMenuOpen = true
+ event.preventDefault()
+ event.stopPropagation()
+ }
+ }
+ checkSelectedTextAndShowMenu()
+ })
}
renderFetchText() {
@@ -1766,6 +1956,52 @@ class MultiWallet extends LitElement {
this.showWallet()
}
+ async sendRvn() {
+ const rvnAmount = this.shadowRoot.getElementById('rvnAmountInput').value
+ let rvnRecipient = this.shadowRoot.getElementById('rvnRecipient').value
+ const xprv58 = this.wallets.get(this._selectedWallet).wallet.derivedMasterPrivateKey
+
+ this.sendMoneyLoading = true
+ this.btnDisable = true
+
+ const makeRequest = async () => {
+ const opts = {
+ xprv58: xprv58,
+ receivingAddress: rvnRecipient,
+ ravencoinAmount: rvnAmount,
+ feePerByte: (this.rvnFeePerByte / 1e8).toFixed(8),
+ }
+ const response = await parentEpml.request('sendRvn', opts)
+ return response
+ }
+
+ const manageResponse = (response) => {
+ if (response.length === 64) {
+ this.shadowRoot.getElementById('rvnAmountInput').value = 0
+ this.shadowRoot.getElementById('rvnRecipient').value = ''
+ this.errorMessage = ''
+ this.rvnRecipient = ''
+ this.rvnAmount = 0
+ this.successMessage = this.renderSuccessText()
+ this.sendMoneyLoading = false
+ this.btnDisable = false
+ } else if (response === false) {
+ this.errorMessage = this.renderFailText()
+ this.sendMoneyLoading = false
+ this.btnDisable = false
+ throw new Error(txnResponse)
+ } else {
+ this.errorMessage = response.message
+ this.sendMoneyLoading = false
+ this.btnDisable = false
+ throw new Error(response)
+ }
+ }
+ const res = await makeRequest()
+ manageResponse(res)
+ this.showWallet()
+ }
+
async showWallet() {
this.transactionsDOM.hidden = true
this.loading = true
@@ -1816,6 +2052,7 @@ class MultiWallet extends LitElement {
case 'btc':
case 'ltc':
case 'doge':
+ case 'rvn':
const walletName = `${coin}Wallet`
parentEpml.request('apiCall', {
url: `/crosschain/${coin}/walletbalance?apiKey=${this.getApiKey()}`,
@@ -1866,6 +2103,8 @@ class MultiWallet extends LitElement {
return html`
this.openSendLtc()}> ${translate("walletpage.wchange17")} LTC`
} else if ( this._selectedWallet === "doge" ) {
return html`
this.openSendDoge()}> ${translate("walletpage.wchange17")} DOGE`
+ } else if ( this._selectedWallet === "rvn" ) {
+ return html`
this.openSendRvn()}> ${translate("walletpage.wchange17")} RVN`
} else {
return html``
}
@@ -1887,6 +2126,10 @@ class MultiWallet extends LitElement {
this.shadowRoot.querySelector("#sendDogeDialog").show();
}
+ openSendRvn() {
+ this.shadowRoot.querySelector("#sendRvnDialog").show();
+ }
+
changeTheme() {
const checkTheme = localStorage.getItem('qortalTheme')
if (checkTheme === 'dark') {
@@ -1952,6 +2195,15 @@ class MultiWallet extends LitElement {
},
{ passive: true }
)
+ } else if (coin === 'rvn') {
+ this.transactionsGrid.addEventListener(
+ 'click',
+ (e) => {
+ let rvnItem = this.transactionsGrid.getEventContext(e).item
+ this.showRvnTransactionDetails(rvnItem, this.wallets.get(this._selectedWallet).transactions)
+ },
+ { passive: true }
+ )
}
this.pagesControl = this.shadowRoot.querySelector('#pages')
@@ -1967,6 +2219,8 @@ class MultiWallet extends LitElement {
render(this.renderLtcTransactions(this.wallets.get(this._selectedWallet).transactions, this._selectedWallet), this.transactionsDOM)
} else if (this._selectedWallet === 'doge') {
render(this.renderDogeTransactions(this.wallets.get(this._selectedWallet).transactions, this._selectedWallet), this.transactionsDOM)
+ } else if (this._selectedWallet === 'rvn') {
+ render(this.renderRvnTransactions(this.wallets.get(this._selectedWallet).transactions, this._selectedWallet), this.transactionsDOM)
}
}
@@ -2222,6 +2476,71 @@ class MultiWallet extends LitElement {
`
}
+ renderRvnTransactions(transactions, coin) {
+ return html`
+
${translate("walletpage.wchange38")}
+
+ {
+ render(html`check`, root)
+ }}
+ >
+
+ {
+ render(html` ${translate("walletpage.wchange40")} ${data.item.inputs[0].address === this.wallets.get(this._selectedWallet).wallet.address ? html`${translate("walletpage.wchange7")}` : html`${translate("walletpage.wchange8")}`} `, root)
+ }}
+ >
+
+ {
+ render(html`${data.item.inputs[0].address}`, root)
+ }}
+ >
+
+ {
+ render(html`${data.item.outputs[0].address}`, root)
+ }}
+ >
+
+
+ {
+ const amount = (Number(data.item.totalAmount) / 1e8).toFixed(8)
+ render(html`${amount}`, root)
+ }}
+ >
+
+ {
+ const time = new Date(data.item.timestamp * 1000)
+ render(html` `, root)
+ }}
+ >
+
+
+
+ `
+ }
+
async updateItemsFromPage(page, changeWallet = false) {
if (page === undefined) {
return
@@ -2394,6 +2713,20 @@ class MultiWallet extends LitElement {
})
}
+ showRvnTransactionDetails(myTransaction, allTransactions) {
+ allTransactions.forEach((transaction) => {
+ if (myTransaction.txHash === transaction.txHash) {
+ let rvnTxnFlow = myTransaction.inputs[0].address === this.wallets.get(this._selectedWallet).wallet.address ? 'OUT' : 'IN'
+ let rvnSender = myTransaction.inputs[0].address
+ let rvnReceiver = myTransaction.outputs[0].address
+ this.selectedTransaction = { ...transaction, rvnTxnFlow, rvnSender, rvnReceiver }
+ if (this.selectedTransaction.txHash.length != 0) {
+ this.shadowRoot.querySelector('#showRvnTransactionDetailsDialog').show()
+ }
+ }
+ })
+ }
+
isEmptyArray(arr) {
if (!arr) {
return true