4
1
mirror of https://github.com/Qortal/qortal-ui.git synced 2025-02-11 17:55:51 +00:00

add RVN wallet

This commit is contained in:
QuickMythril 2022-04-21 11:32:05 -04:00
parent d494827d71
commit dad6f06e95
5 changed files with 379 additions and 2 deletions

View File

@ -20,6 +20,7 @@ const cancelAllOffers = api.cancelAllOffers
const sendBtc = api.sendBtc const sendBtc = api.sendBtc
const sendLtc = api.sendLtc const sendLtc = api.sendLtc
const sendDoge = api.sendDoge const sendDoge = api.sendDoge
const sendRvn = api.sendRvn
export const routes = { export const routes = {
hello: async (req) => { hello: async (req) => {
@ -333,4 +334,17 @@ export const routes = {
} }
return response 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
},
} }

View File

@ -138,11 +138,27 @@ export default class PhraseWallet {
} }
}).createWallet(new Uint8Array(dogeSeed), false, 'DOGE'); }).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] = { this._addresses[nonce] = {
address, address,
btcWallet, btcWallet,
ltcWallet, ltcWallet,
dogeWallet, dogeWallet,
rvnWallet,
qoraAddress, qoraAddress,
keyPair: { keyPair: {
publicKey: addrKeyPair.publicKey, publicKey: addrKeyPair.publicKey,

View File

@ -4,6 +4,6 @@ export { transactionTypes as transactions } from './transactions/transactions.js
export { processTransaction, createTransaction, computeChatNonce, signChatTransaction, signArbitraryTransaction } from './createTransaction.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' export { cancelAllOffers } from './transactions/trade-portal/tradeoffer/cancelAllOffers.js'

View File

@ -101,3 +101,17 @@ export const sendDoge = (requestObject) => {
body: JSON.stringify(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)
})
}

View File

@ -25,7 +25,7 @@ import '@github/time-elements'
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) 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 { class MultiWallet extends LitElement {
static get properties() { static get properties() {
@ -46,6 +46,8 @@ class MultiWallet extends LitElement {
ltcAmount: { type: Number }, ltcAmount: { type: Number },
dogeRecipient: { type: String }, dogeRecipient: { type: String },
dogeAmount: { type: Number }, dogeAmount: { type: Number },
rvnRecipient: { type: String },
rvnAmount: { type: Number },
errorMessage: { type: String }, errorMessage: { type: String },
successMessage: { type: String }, successMessage: { type: String },
sendMoneyLoading: { type: Boolean }, sendMoneyLoading: { type: Boolean },
@ -55,6 +57,7 @@ class MultiWallet extends LitElement {
btcFeePerByte: { type: Number }, btcFeePerByte: { type: Number },
ltcFeePerByte: { type: Number }, ltcFeePerByte: { type: Number },
dogeFeePerByte: { type: Number }, dogeFeePerByte: { type: Number },
rvnFeePerByte: { type: Number },
balanceString: { type: String } balanceString: { type: String }
} }
} }
@ -421,6 +424,10 @@ class MultiWallet extends LitElement {
background-image: url('/img/doge.png'); background-image: url('/img/doge.png');
} }
.rvn .currency-image {
background-image: url('/img/rvn.png');
}
.card-list { .card-list {
margin-top: 20px; margin-top: 20px;
} }
@ -553,6 +560,7 @@ class MultiWallet extends LitElement {
this.btcRecipient = '' this.btcRecipient = ''
this.ltcRecipient = '' this.ltcRecipient = ''
this.dogeRecipient = '' this.dogeRecipient = ''
this.rvnRecipient = ''
this.errorMessage = '' this.errorMessage = ''
this.successMessage = '' this.successMessage = ''
this.sendMoneyLoading = false this.sendMoneyLoading = false
@ -563,6 +571,7 @@ class MultiWallet extends LitElement {
this.btcAmount = 0 this.btcAmount = 0
this.ltcAmount = 0 this.ltcAmount = 0
this.dogeAmount = 0 this.dogeAmount = 0
this.rvnAmount = 0
this.btcFeePerByte = 100 this.btcFeePerByte = 100
this.btcSatMinFee = 20 this.btcSatMinFee = 20
this.btcSatMaxFee = 150 this.btcSatMaxFee = 150
@ -572,6 +581,9 @@ class MultiWallet extends LitElement {
this.dogeFeePerByte = 1000 this.dogeFeePerByte = 1000
this.dogeSatMinFee = 100 this.dogeSatMinFee = 100
this.dogeSatMaxFee = 10000 this.dogeSatMaxFee = 10000
this.rvnFeePerByte = 1125
this.rvnSatMinFee = 1000
this.rvnSatMaxFee = 10000
this.wallets = new Map() 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('btc').wallet = window.parent.reduxStore.getState().app.selectedAddress.btcWallet
this.wallets.get('ltc').wallet = window.parent.reduxStore.getState().app.selectedAddress.ltcWallet 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('doge').wallet = window.parent.reduxStore.getState().app.selectedAddress.dogeWallet
this.wallets.get('rvn').wallet = window.parent.reduxStore.getState().app.selectedAddress.rvnWallet
this._selectedWallet = 'qort' 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('btc').wallet = window.parent.reduxStore.getState().app.selectedAddress.btcWallet
this.wallets.get('ltc').wallet = window.parent.reduxStore.getState().app.selectedAddress.ltcWallet 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('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) => { parentEpml.subscribe('copy_menu_switch', async (value) => {
@ -636,6 +650,10 @@ class MultiWallet extends LitElement {
<div class="currency-image"></div> <div class="currency-image"></div>
<div class="currency-text">Dogecoin</div> <div class="currency-text">Dogecoin</div>
</div> </div>
<div coin="rvn" class="currency-box rvn">
<div class="currency-image"></div>
<div class="currency-text">Ravencoin</div>
</div>
</div> </div>
</div> </div>
@ -862,6 +880,56 @@ class MultiWallet extends LitElement {
</mwc-button> </mwc-button>
</mwc-dialog> </mwc-dialog>
<mwc-dialog id="showRvnTransactionDetailsDialog" scrimClickAction="${this.showRvnTransactionDetailsLoading ? '' : 'close'}">
<div style="text-align: center;">
<h1>${translate("walletpage.wchange5")}</h1>
<hr />
</div>
<div id="transactionList">
<span class="title"> ${translate("walletpage.wchange6")} </span>
<br />
<div>
<span>${translate("walletpage.wchange40")}</span>
${this.selectedTransaction.rvnTxnFlow === 'OUT' ? html`<span class="color-out">${translate("walletpage.wchange7")}</span>` : html`<span class="color-in">${translate("walletpage.wchange8")}</span>`}
</div>
<span class="title"> ${translate("walletpage.wchange9")} </span>
<br />
<div>
<span>${this.selectedTransaction.rvnSender}</span>
</div>
<span class="title"> ${translate("walletpage.wchange10")} </span>
<br />
<div>
<span>${this.selectedTransaction.rvnReceiver}</span>
</div>
<span class="title"> ${translate("walletpage.wchange12")} </span>
<br />
<div>
<span>${(this.selectedTransaction.feeAmount / 1e8).toFixed(8)} RVN</span>
</div>
<span class="title"> ${translate("walletpage.wchange37")} </span>
<br />
<div>
<span>${(this.selectedTransaction.totalAmount / 1e8).toFixed(8)} RVN</span>
</div>
<span class="title"> ${translate("walletpage.wchange14")} </span>
<br />
<div><span>${new Date(this.selectedTransaction.timestamp).toString()}</span></div>
<span class="title"> ${translate("walletpage.wchange16")} </span>
<br />
<div>
<span>${this.selectedTransaction.txHash}</span>
</div>
</div>
<mwc-button
slot="primaryAction"
dialogAction="cancel"
class="red"
>
${translate("general.close")}
</mwc-button>
</mwc-dialog>
<mwc-dialog id="sendQortDialog"> <mwc-dialog id="sendQortDialog">
<div class="send-coin-dialog"> <div class="send-coin-dialog">
<div style="text-align: center;"> <div style="text-align: center;">
@ -1148,6 +1216,82 @@ class MultiWallet extends LitElement {
${translate("general.close")} ${translate("general.close")}
</mwc-button> </mwc-button>
</mwc-dialog> </mwc-dialog>
<mwc-dialog id="sendRvnDialog">
<div class="send-coin-dialog">
<div style="text-align: center;">
<img src="/img/rvn.png" width="32" height="32">
<h2>${translate("walletpage.wchange17")} RVN</h2>
<hr />
</div>
<p>
<span>${translate("walletpage.wchange18")}:</span><br />
<span style="font-weight: bold;">${this.getSelectedWalletAddress()}</span>
</p>
<p>
<span>${translate("walletpage.wchange19")}:</span><br />
<span style="font-weight: bold;">${this.balanceString}</span>
</p>
<p>
<mwc-textfield
style="width: 100%;"
required
@input="${(e) => { this._checkAmount(e) }}"
id="rvnAmountInput"
label="${translate("walletpage.wchange11")} (RVN)"
type="number"
auto-validate="false"
value="${this.rvnAmount}"
>
</mwc-textfield>
</p>
<p>
<mwc-textfield
style="width: 100%;"
required
id="rvnRecipient"
label="${translate("walletpage.wchange23")}"
type="text"
value="${this.rvnRecipient}"
>
</mwc-textfield>
</p>
<div style="margin-bottom: 0;">
<p style="margin-bottom: 0;">
${translate("walletpage.wchange24")}: <span style="font-weight: bold;">${(this.rvnFeePerByte / 1e8).toFixed(8)} RVN</span><br>L${translate("walletpage.wchange25")}
</p>
<paper-slider
class="blue"
style="width: 100%;"
pin
@change="${(e) => (this.rvnFeePerByte = e.target.value)}"
id="rvnFeeSlider"
min="${this.rvnSatMinFee}"
max="${this.rvnSatMaxFee}"
value="${this.rvnFeePerByte}"
>
</paper-slider>
</div>
<p style="color: red;">${this.errorMessage}</p>
<p style="color: green;">${this.successMessage}</p>
${this.sendMoneyLoading ? html` <paper-progress indeterminate style="width: 100%; margin: 4px;"></paper-progress> ` : ''}
<div class="buttons">
<div>
<vaadin-button ?disabled="${this.btnDisable}" theme="primary medium" style="width: 100%;" @click=${() => this.sendRvn()}>
<vaadin-icon icon="vaadin:arrow-forward" slot="prefix"></vaadin-icon>
${translate("walletpage.wchange17")} RVN
</vaadin-button>
</div>
</div>
</div>
<mwc-button
slot="primaryAction"
dialogAction="cancel"
class="red"
>
${translate("general.close")}
</mwc-button>
</mwc-dialog>
</div> </div>
` `
} }
@ -1389,6 +1533,52 @@ class MultiWallet extends LitElement {
} }
checkSelectedTextAndShowMenu() 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() { renderFetchText() {
@ -1766,6 +1956,52 @@ class MultiWallet extends LitElement {
this.showWallet() 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() { async showWallet() {
this.transactionsDOM.hidden = true this.transactionsDOM.hidden = true
this.loading = true this.loading = true
@ -1816,6 +2052,7 @@ class MultiWallet extends LitElement {
case 'btc': case 'btc':
case 'ltc': case 'ltc':
case 'doge': case 'doge':
case 'rvn':
const walletName = `${coin}Wallet` const walletName = `${coin}Wallet`
parentEpml.request('apiCall', { parentEpml.request('apiCall', {
url: `/crosschain/${coin}/walletbalance?apiKey=${this.getApiKey()}`, url: `/crosschain/${coin}/walletbalance?apiKey=${this.getApiKey()}`,
@ -1866,6 +2103,8 @@ class MultiWallet extends LitElement {
return html`<vaadin-button theme="primary large" style="width: 75%;" @click=${() => this.openSendLtc()}><vaadin-icon icon="vaadin:coin-piles" slot="prefix"></vaadin-icon> ${translate("walletpage.wchange17")} LTC</vaadin-button>` return html`<vaadin-button theme="primary large" style="width: 75%;" @click=${() => this.openSendLtc()}><vaadin-icon icon="vaadin:coin-piles" slot="prefix"></vaadin-icon> ${translate("walletpage.wchange17")} LTC</vaadin-button>`
} else if ( this._selectedWallet === "doge" ) { } else if ( this._selectedWallet === "doge" ) {
return html`<vaadin-button theme="primary large" style="width: 75%;" @click=${() => this.openSendDoge()}><vaadin-icon icon="vaadin:coin-piles" slot="prefix"></vaadin-icon> ${translate("walletpage.wchange17")} DOGE</vaadin-button>` return html`<vaadin-button theme="primary large" style="width: 75%;" @click=${() => this.openSendDoge()}><vaadin-icon icon="vaadin:coin-piles" slot="prefix"></vaadin-icon> ${translate("walletpage.wchange17")} DOGE</vaadin-button>`
} else if ( this._selectedWallet === "rvn" ) {
return html`<vaadin-button theme="primary large" style="width: 75%;" @click=${() => this.openSendRvn()}><vaadin-icon icon="vaadin:coin-piles" slot="prefix"></vaadin-icon> ${translate("walletpage.wchange17")} RVN</vaadin-button>`
} else { } else {
return html`` return html``
} }
@ -1887,6 +2126,10 @@ class MultiWallet extends LitElement {
this.shadowRoot.querySelector("#sendDogeDialog").show(); this.shadowRoot.querySelector("#sendDogeDialog").show();
} }
openSendRvn() {
this.shadowRoot.querySelector("#sendRvnDialog").show();
}
changeTheme() { changeTheme() {
const checkTheme = localStorage.getItem('qortalTheme') const checkTheme = localStorage.getItem('qortalTheme')
if (checkTheme === 'dark') { if (checkTheme === 'dark') {
@ -1952,6 +2195,15 @@ class MultiWallet extends LitElement {
}, },
{ passive: true } { 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') 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) render(this.renderLtcTransactions(this.wallets.get(this._selectedWallet).transactions, this._selectedWallet), this.transactionsDOM)
} else if (this._selectedWallet === 'doge') { } else if (this._selectedWallet === 'doge') {
render(this.renderDogeTransactions(this.wallets.get(this._selectedWallet).transactions, this._selectedWallet), this.transactionsDOM) 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`
<div style="padding-left:12px;" ?hidden="${!this.isEmptyArray(transactions)}"><span style="color: var(--black);">${translate("walletpage.wchange38")}</span></div>
<vaadin-grid theme="large" id="${coin}TransactionsGrid" ?hidden="${this.isEmptyArray(this.wallets.get(this._selectedWallet).transactions)}" page-size="25" all-rows-visible>
<vaadin-grid-column
auto-width
header="${translate("walletpage.wchange41")}"
.renderer=${(root, column, data) => {
render(html`<mwc-icon style="color: #00C851">check</mwc-icon>`, root)
}}
>
</vaadin-grid-column>
<vaadin-grid-column
auto-width
resizable
header="${translate("walletpage.wchange35")}"
.renderer=${(root, column, data) => {
render(html` ${translate("walletpage.wchange40")} ${data.item.inputs[0].address === this.wallets.get(this._selectedWallet).wallet.address ? html`<span class="color-out">${translate("walletpage.wchange7")}</span>` : html`<span class="color-in">${translate("walletpage.wchange8")}</span>`} `, root)
}}
>
</vaadin-grid-column>
<vaadin-grid-column
auto-width
resizable
header="${translate("walletpage.wchange9")}"
.renderer=${(root, column, data) => {
render(html`${data.item.inputs[0].address}`, root)
}}
>
</vaadin-grid-column>
<vaadin-grid-column
auto-width
resizable
header="${translate("walletpage.wchange10")}"
.renderer=${(root, column, data) => {
render(html`${data.item.outputs[0].address}`, root)
}}
>
</vaadin-grid-column>
<vaadin-grid-column auto-width resizable header="${translate("walletpage.wchange16")}" path="txHash"></vaadin-grid-column>
<vaadin-grid-column
auto-width
resizable
header="${translate("walletpage.wchange37")}"
.renderer=${(root, column, data) => {
const amount = (Number(data.item.totalAmount) / 1e8).toFixed(8)
render(html`${amount}`, root)
}}
>
</vaadin-grid-column>
<vaadin-grid-column
auto-width
resizable
header="${translate("walletpage.wchange14")}"
.renderer=${(root, column, data) => {
const time = new Date(data.item.timestamp * 1000)
render(html` <time-ago datetime=${time.toISOString()}> </time-ago> `, root)
}}
>
</vaadin-grid-column>
</vaadin-grid>
<div id="pages"></div>
`
}
async updateItemsFromPage(page, changeWallet = false) { async updateItemsFromPage(page, changeWallet = false) {
if (page === undefined) { if (page === undefined) {
return 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) { isEmptyArray(arr) {
if (!arr) { if (!arr) {
return true return true