diff --git a/img/qortrvn.png b/img/qortrvn.png
new file mode 100644
index 00000000..be052ac1
Binary files /dev/null and b/img/qortrvn.png differ
diff --git a/qortal-ui-core/src/plugins/routes.js b/qortal-ui-core/src/plugins/routes.js
index 4310ad09..2567d201 100644
--- a/qortal-ui-core/src/plugins/routes.js
+++ b/qortal-ui-core/src/plugins/routes.js
@@ -21,6 +21,7 @@ const sendBtc = api.sendBtc
const sendLtc = api.sendLtc
const sendDoge = api.sendDoge
const sendDgb = api.sendDgb
+const sendRvn = api.sendRvn
export const routes = {
hello: async (req) => {
@@ -347,4 +348,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 00e6fd14..34929ee4 100644
--- a/qortal-ui-crypto/api/PhraseWallet.js
+++ b/qortal-ui-crypto/api/PhraseWallet.js
@@ -153,12 +153,28 @@ export default class PhraseWallet {
}
}).createWallet(new Uint8Array(dgbSeed), false, 'DGB');
+ // 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,
dgbWallet,
+ rvnWallet,
qoraAddress,
keyPair: {
publicKey: addrKeyPair.publicKey,
diff --git a/qortal-ui-crypto/api/api.js b/qortal-ui-crypto/api/api.js
index d9c469e1..30ca6d27 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, sendDgb } from './tradeRequest.js'
+export { tradeBotCreateRequest, tradeBotRespondRequest, signTradeBotTxn, deleteTradeOffer, sendBtc, sendLtc, sendDoge, sendDgb, 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 bef31a07..fb4a2700 100644
--- a/qortal-ui-crypto/api/tradeRequest.js
+++ b/qortal-ui-crypto/api/tradeRequest.js
@@ -115,3 +115,18 @@ export const sendDgb = (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/trade-portal/trade-portal.src.js b/qortal-ui-plugins/plugins/core/trade-portal/trade-portal.src.js
index c7560c1c..6c936b2d 100644
--- a/qortal-ui-plugins/plugins/core/trade-portal/trade-portal.src.js
+++ b/qortal-ui-plugins/plugins/core/trade-portal/trade-portal.src.js
@@ -350,6 +350,10 @@ class TradePortal extends LitElement {
background-image: url('/img/qortdgb.png');
}
+ .rvn.coinName:before {
+ background-image: url('/img/qortrvn.png');
+ }
+
.coinName {
display: inline-block;
height: 26px;
@@ -463,11 +467,27 @@ class TradePortal extends LitElement {
coinAmount: this.amountString
}
+ let ravencoin = {
+ name: "RAVENCOIN",
+ balance: "0",
+ coinCode: "RVN",
+ openOrders: [],
+ openFilteredOrders: [],
+ historicTrades: [],
+ myOrders: [],
+ myHistoricTrades: [],
+ myOfferingOrders: [],
+ openTradeOrders: null,
+ tradeOffersSocketCounter: 1,
+ coinAmount: this.amountString
+ }
+
this.listedCoins = new Map()
this.listedCoins.set("QORTAL", qortal)
this.listedCoins.set("LITECOIN", litecoin)
this.listedCoins.set("DOGECOIN", dogecoin)
this.listedCoins.set("DIGIBYTE", digibyte)
+ this.listedCoins.set("RAVENCOIN", ravencoin)
workers.set("QORTAL", {
tradesConnectedWorker: null,
@@ -489,6 +509,11 @@ class TradePortal extends LitElement {
handleStuckTradesConnectedWorker: null
})
+ workers.set("RAVENCOIN", {
+ tradesConnectedWorker: null,
+ handleStuckTradesConnectedWorker: null
+ })
+
this.selectedCoin = "LITECOIN"
this.selectedAddress = {}
this.config = {}
@@ -872,6 +897,7 @@ class TradePortal extends LitElement {
QORT / LTC
QORT / DOGE
QORT / DGB
+ QORT / RVN
@@ -1037,6 +1063,10 @@ class TradePortal extends LitElement {
case 'DIGIBYTE':
_url = `/crosschain/dgb/walletbalance?apiKey=${this.getApiKey()}`
_body = window.parent.reduxStore.getState().app.selectedAddress.dgbWallet.derivedMasterPublicKey
+ break
+ case 'RAVENCOIN':
+ _url = `/crosschain/rvn/walletbalance?apiKey=${this.getApiKey()}`
+ _body = window.parent.reduxStore.getState().app.selectedAddress.rvnWallet.derivedMasterPublicKey
break
default:
break
@@ -1481,6 +1511,72 @@ class TradePortal extends LitElement {
}
}
+ /**
+ * RavencoinACCTv1 TRADEBOT STATES
+ * - BOB_WAITING_FOR_AT_CONFIRM
+ * - BOB_WAITING_FOR_MESSAGE
+ * - BOB_WAITING_FOR_AT_REDEEM
+ * - BOB_DONE
+ * - BOB_REFUNDED
+ * - ALICE_WAITING_FOR_AT_LOCK
+ * - ALICE_DONE
+ * - ALICE_REFUNDING_A
+ * - ALICE_REFUNDED
+ *
+ * @param {[{}]} states
+ */
+
+ const RavencoinACCTv1 = (states) => {
+ // Reverse the states
+ states.reverse()
+ states.forEach((state) => {
+ if (state.creatorAddress === this.selectedAddress.address) {
+ if (state.tradeState == 'BOB_WAITING_FOR_AT_CONFIRM') {
+ this.changeTradeBotState(state, 'PENDING')
+ } else if (state.tradeState == 'BOB_WAITING_FOR_MESSAGE') {
+ this.changeTradeBotState(state, 'LISTED')
+ } else if (state.tradeState == 'BOB_WAITING_FOR_AT_REDEEM') {
+ this.changeTradeBotState(state, 'TRADING')
+ } else if (state.tradeState == 'BOB_DONE') {
+ this.handleCompletedState(state)
+ } else if (state.tradeState == 'BOB_REFUNDED') {
+ this.handleCompletedState(state)
+ } else if (state.tradeState == 'ALICE_WAITING_FOR_AT_LOCK') {
+ this.changeTradeBotState(state, 'BUYING')
+ } else if (state.tradeState == 'ALICE_DONE') {
+ this.handleCompletedState(state)
+ } else if (state.tradeState == 'ALICE_REFUNDING_A') {
+ this.changeTradeBotState(state, 'REFUNDING')
+ } else if (state.tradeState == 'ALICE_REFUNDED') {
+ this.handleCompletedState(state)
+ }
+ }
+ })
+ }
+
+ switch (this.selectedCoin) {
+ case 'BITCOIN':
+ BitcoinACCTv1(tradeStates)
+ break
+ case 'LITECOIN':
+ LitecoinACCTv1(tradeStates)
+ break
+ case 'DOGECOIN':
+ DogecoinACCTv1(tradeStates)
+ break
+ case 'RAVENCOIN':
+ RavencoinACCTv1(tradeStates)
+ break
+ default:
+ break
+ }
+
+ // Fill Historic Trades and Filter Stuck Trades
+ if (this.listedCoins.get(this.selectedCoin).tradeOffersSocketCounter === 1) {
+ setTimeout(() => this.filterStuckTrades(tradeStates), 50)
+ }
+ }
+
changeTradeBotState(state, tradeState) {
// Set Loading state
this.isLoadingMyOpenOrders = true
@@ -1762,6 +1858,9 @@ class TradePortal extends LitElement {
break
case 'DIGIBYTE':
_receivingAddress = this.selectedAddress.dgbWallet.address
+ break
+ case 'RAVENCOIN':
+ _receivingAddress = this.selectedAddress.rvnWallet.address
break
default:
break
@@ -1825,6 +1924,9 @@ class TradePortal extends LitElement {
break
case 'DIGIBYTE':
_foreignKey = this.selectedAddress.dgbWallet.derivedMasterPrivateKey
+ break
+ case 'RAVENCOIN':
+ _foreignKey = this.selectedAddress.rvnWallet.derivedMasterPrivateKey
break
default:
break
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 05547151..1ff75b59 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', 'dgb']
+const coinsNames = ['qort', 'btc', 'ltc', 'doge', 'dgb', 'rvn']
class MultiWallet extends LitElement {
static get properties() {
@@ -48,6 +48,8 @@ class MultiWallet extends LitElement {
dogeAmount: { type: Number },
dgbRecipient: { type: String },
dgbAmount: { type: Number },
+ rvnRecipient: { type: String },
+ rvnAmount: { type: Number },
errorMessage: { type: String },
successMessage: { type: String },
sendMoneyLoading: { type: Boolean },
@@ -58,6 +60,7 @@ class MultiWallet extends LitElement {
ltcFeePerByte: { type: Number },
dogeFeePerByte: { type: Number },
dgbFeePerByte: { type: Number },
+ rvnFeePerByte: { type: Number },
balanceString: { type: String }
}
}
@@ -428,6 +431,10 @@ class MultiWallet extends LitElement {
background-image: url('/img/dgb.png');
}
+ .rvn .currency-image {
+ background-image: url('/img/rvn.png');
+ }
+
.card-list {
margin-top: 20px;
}
@@ -561,6 +568,7 @@ class MultiWallet extends LitElement {
this.ltcRecipient = ''
this.dogeRecipient = ''
this.dgbRecipient = ''
+ this.rvnRecipient = ''
this.errorMessage = ''
this.successMessage = ''
this.sendMoneyLoading = false
@@ -572,6 +580,7 @@ class MultiWallet extends LitElement {
this.ltcAmount = 0
this.dogeAmount = 0
this.dgbAmount = 0
+ this.rvnAmount = 0
this.btcFeePerByte = 100
this.btcSatMinFee = 20
this.btcSatMaxFee = 150
@@ -584,6 +593,9 @@ class MultiWallet extends LitElement {
this.dgbFeePerByte = 100
this.dgbSatMinFee = 10
this.dgbSatMaxFee = 1000
+ this.rvnFeePerByte = 1125
+ this.rvnSatMinFee = 1000
+ this.rvnSatMaxFee = 10000
this.wallets = new Map()
@@ -604,6 +616,7 @@ class MultiWallet extends LitElement {
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('dgb').wallet = window.parent.reduxStore.getState().app.selectedAddress.dgbWallet
+ this.wallets.get('rvn').wallet = window.parent.reduxStore.getState().app.selectedAddress.rvnWallet
this._selectedWallet = 'qort'
@@ -653,6 +666,10 @@ class MultiWallet extends LitElement {
+
@@ -930,6 +947,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")}
+
+
+
@@ -1291,6 +1358,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")}
+
`
@@ -1557,6 +1700,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()
+ })
+
this.shadowRoot.getElementById('dgbRecipient').addEventListener('contextmenu', (event) => {
const getSelectedText = () => {
var text = ''
@@ -2002,6 +2191,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
@@ -2053,6 +2288,7 @@ class MultiWallet extends LitElement {
case 'ltc':
case 'doge':
case 'dgb':
+ case 'rvn':
const walletName = `${coin}Wallet`
parentEpml.request('apiCall', {
url: `/crosschain/${coin}/walletbalance?apiKey=${this.getApiKey()}`,
@@ -2105,6 +2341,8 @@ class MultiWallet extends LitElement {
return html`
this.openSendDoge()}> ${translate("walletpage.wchange17")} DOGE`
} else if ( this._selectedWallet === "dgb" ) {
return html`
this.openSendDgb()}> ${translate("walletpage.wchange17")} DGB`
+ } else if ( this._selectedWallet === "rvn" ) {
+ return html`
this.openSendRvn()}> ${translate("walletpage.wchange17")} RVN`
} else {
return html``
}
@@ -2130,6 +2368,10 @@ class MultiWallet extends LitElement {
this.shadowRoot.querySelector("#sendDgbDialog").show();
}
+ openSendRvn() {
+ this.shadowRoot.querySelector("#sendRvnDialog").show();
+ }
+
changeTheme() {
const checkTheme = localStorage.getItem('qortalTheme')
if (checkTheme === 'dark') {
@@ -2204,6 +2446,15 @@ class MultiWallet extends LitElement {
},
{ passive: true }
)
+ } else if (coin === 'dgb') {
+ this.transactionsGrid.addEventListener(
+ 'click',
+ (e) => {
+ let dgbItem = this.transactionsGrid.getEventContext(e).item
+ this.showDgbTransactionDetails(dgbItem, this.wallets.get(this._selectedWallet).transactions)
+ },
+ { passive: true }
+ )
}
this.pagesControl = this.shadowRoot.querySelector('#pages')
@@ -2221,6 +2472,8 @@ class MultiWallet extends LitElement {
render(this.renderDogeTransactions(this.wallets.get(this._selectedWallet).transactions, this._selectedWallet), this.transactionsDOM)
} else if (this._selectedWallet === 'dgb') {
render(this.renderDgbTransactions(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)
}
}
@@ -2541,6 +2794,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
@@ -2727,6 +3045,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