Merge branch 'trade' of github.com:ping-pub/explorer into trade

# Conflicts:
#	src/views/OsmosisTrade.vue
This commit is contained in:
ding 2021-11-25 22:16:28 +08:00
commit d94a933325
6 changed files with 441 additions and 336 deletions

View File

@ -5,8 +5,45 @@
* @LastEditors: dingyiming * @LastEditors: dingyiming
* @LastEditTime: 2021-11-25 00:45:16 * @LastEditTime: 2021-11-25 00:45:16
*/ */
import { sha256 } from '@cosmjs/crypto'
import { toHex } from '@cosmjs/encoding'
import fetch from 'node-fetch' import fetch from 'node-fetch'
import { getLocalChains } from './data/data' import { formatTokenDenom, getLocalChains } from './data/data'
export const poolIds = {
1: true,
2: true,
3: true,
4: true,
5: true,
6: true,
7: true,
8: true,
9: true,
10: true,
13: true,
15: true,
461: true,
482: true,
497: true,
498: true,
548: true,
557: true,
558: true,
571: true,
572: true,
}
export function getPairName(pool, denomTrace, type = 'base') {
const index = type === 'base' ? 0 : 1
if (pool && pool.poolAssets) {
if (pool.poolAssets[index].token.denom.startsWith('ibc')) {
return formatTokenDenom(denomTrace[pool.poolAssets[index].token.denom].base_denom)
}
return formatTokenDenom(pool.poolAssets[index].token.denom)
}
return '-'
}
export default class OsmosAPI { export default class OsmosAPI {
constructor() { constructor() {
@ -76,7 +113,27 @@ export default class OsmosAPI {
// Custom Module // Custom Module
async getPools() { async getPools() {
return this.get('/osmosis/gamm/v1beta1/pools?pagination.limit=700') const tradeable = []
Object.keys(poolIds).forEach(k => {
if (poolIds[k]) {
tradeable.push(k)
}
})
return this.get('/osmosis/gamm/v1beta1/pools?pagination.limit=700').then(res => {
const output = res.pools.filter(x => tradeable.includes(x.id))
return output
})
}
async getDenomTraces() {
return this.get('/ibc/applications/transfer/v1beta1/denom_traces?pagination.limit=300').then(x => {
const combined = {}
x.denom_traces.forEach(item => {
const k = 'ibc/'.concat(toHex(sha256(new TextEncoder('utf-8').encode(`${item.path}/${item.base_denom}`))).toUpperCase())
combined[k] = item
})
return combined
})
} }
async getIncentivesPools() { async getIncentivesPools() {

View File

@ -295,14 +295,18 @@ const router = new VueRouter({
}, },
// 2. OSMOSIS // 2. OSMOSIS
{ {
path: '/:chain/osmosis/trade/:base?/:target?', path: '/:chain/osmosis/trade/:poolid?',
name: 'osmosis-trade', name: 'osmosis-trade',
component: () => import('@/views/OsmosisTrade.vue'), component: () => import('@/views/OsmosisTrade.vue'),
meta: { meta: {
pageTitle: 'Trade', pageTitle: 'Classic Trade',
breadcrumb: [ breadcrumb: [
{ {
text: 'Trade', text: 'DEX',
active: true,
},
{
text: 'Classic Trade',
active: true, active: true,
}, },
], ],

View File

@ -11,7 +11,7 @@
class="d-flex justify-content-begin align-items-center mb-1" class="d-flex justify-content-begin align-items-center mb-1"
> >
<b-button <b-button
id="popover-button-3" id="popover-trading-pairs"
variant="flat-primary" variant="flat-primary"
class="mr-3" class="mr-3"
@click="show = !show" @click="show = !show"
@ -21,29 +21,37 @@
<b-popover <b-popover
:show.sync="show" :show.sync="show"
target="popover-button-3" target="popover-trading-pairs"
placement="bottom" placement="bottom"
triggers="click" triggers="hover"
style="width:300px;" boundary="scrollParent"
boundary-padding="0"
> >
<template #title>
Pairs
</template>
<b-table <b-table
striped striped
hover hover
:small="true" :small="true"
:items="pairs" :items="pairs"
class="m-0" class="m-0 p-0"
> >
<template #cell(pair)="data"> <template #cell(pair)="data">
<router-link <router-link
:to="`/osmosis/osmosis/trade/${data.item.pair}`" :to="`/osmosis/osmosis/trade/${data.item.id}`"
> >
{{ data.item.pair }} {{ data.item.pair[0] }}/{{ data.item.pair[1] }}
</router-link> </router-link>
</template> </template>
<template #cell(price)="data">
<div class="text-right">
<small class="">{{ data.item.price }}</small>
</div>
</template>
<template #cell(change)="data">
<div class="text-right">
<small :class="data.item.change > 0 ? 'text-success': 'text-danger'">{{ data.item.change }}%</small>
</div>
</template>
</b-table> </b-table>
</b-popover> </b-popover>
<div class="mr-3 text-success font-weight-bolder"> <div class="mr-3 text-success font-weight-bolder">
@ -77,8 +85,8 @@
> >
<b-card> <b-card>
<Place <Place
:base="base" :pool.sync="current"
:target="target" :denom-trace="denomTrace"
/> />
</b-card> </b-card>
</b-col> </b-col>
@ -90,6 +98,8 @@
import { import {
BRow, BCol, BCard, BButton, BPopover, BTable, BRow, BCol, BCard, BButton, BPopover, BTable,
} from 'bootstrap-vue' } from 'bootstrap-vue'
import { getPairName } from '@/libs/osmos'
import { formatTokenDenom } from '@/libs/data'
import Place from './components/KlineTrade/Place.vue' import Place from './components/KlineTrade/Place.vue'
// import Kline from './components/kline/index.vue' // import Kline from './components/kline/index.vue'
import Kline from './components/tvjs/index.vue' import Kline from './components/tvjs/index.vue'
@ -108,28 +118,41 @@ export default {
data() { data() {
return { return {
show: false, show: false,
base: 'ATOM', pools: [],
target: 'OSMO', current: {},
pairs: [ denomTrace: [],
{ pair: 'ATOM/OSMO' },
{ pair: 'IRIS/OSMO' },
{ pair: 'AKT/OSMO' },
{ pair: 'ATOM/OSMO' },
{ pair: 'ATOM/OSMO' },
],
klineData: [], klineData: [],
} }
}, },
computed: { computed: {
base() {
return getPairName(this.current, this.denomTrace, 'base')
},
target() {
return getPairName(this.current, this.denomTrace, 'target')
},
pairs() {
const pairs = this.pools.map(x => {
const pair = x.poolAssets.map(t => {
if (t.token.denom.startsWith('ibc/')) {
return formatTokenDenom(this.denomTrace[t.token.denom] ? this.denomTrace[t.token.denom].base_denom : ' ')
}
return formatTokenDenom(t.token.denom)
})
return {
id: x.id,
pair,
price: this.getPrice(pair),
change: this.getChanges(pair),
}
})
return pairs
},
latestPrice() { latestPrice() {
const p1 = this.$store.state.chains.quotes[this.base] return this.getPrice([this.base, this.target])
const p2 = this.$store.state.chains.quotes[this.target]
return p1 && p2 ? (p1.usd / p2.usd).toFixed(4) : '-'
}, },
changesIn24H() { changesIn24H() {
const p1 = this.$store.state.chains.quotes[this.base] return this.getChanges([this.base, this.target])
const p2 = this.$store.state.chains.quotes[this.target]
return p1 && p2 ? (p1.usd_24h_change / p2.usd_24h_change).toFixed(2) : '-'
}, },
}, },
created() { created() {
@ -140,23 +163,37 @@ export default {
this.$http.osmosis.getOHCL4Pairs( this.$http.osmosis.getOHCL4Pairs(
this.$http.osmosis.getCoinGeckoId(base), this.$http.osmosis.getCoinGeckoId(base),
this.$http.osmosis.getCoinGeckoId(target), this.$http.osmosis.getCoinGeckoId(target),
) ).then(data => {
.then(data => { this.klineData = data
console.log(data) })
this.klineData = data this.$http.osmosis.getDenomTraces().then(x => {
}) this.denomTrace = x
})
this.$http.osmosis.getPools().then(x => {
this.pools = x
const id = this.$route.params.poolid || '1'
this.current = this.pools.find(p => p.id === id) || this.pools[0]
})
}, },
beforeRouteUpdate(to, from, next) { beforeRouteUpdate(to, from, next) {
const { base, target } = to.params const { poolid } = to.params
this.init(base, target) this.init(poolid)
console.log(base, target)
next() next()
// } // }
}, },
methods: { methods: {
init(base, target) { getPrice(symbol) {
this.base = base || 'ATOM' const p1 = this.$store.state.chains.quotes[symbol[0]]
this.target = target || 'OSMO' const p2 = this.$store.state.chains.quotes[symbol[1]]
return p1 && p2 ? (p1.usd / p2.usd).toFixed(4) : '-'
},
getChanges(symbol) {
const p1 = this.$store.state.chains.quotes[symbol[0]]
const p2 = this.$store.state.chains.quotes[symbol[1]]
return p1 && p2 ? (p1.usd_24h_change / p2.usd_24h_change).toFixed(2) : '-'
},
init(poolid) {
this.current = this.pools.find(p => p.id === poolid) || this.pools[0]
}, },
}, },
} }

View File

@ -12,277 +12,279 @@
@hidden="resetModal" @hidden="resetModal"
@ok="handleOk" @ok="handleOk"
@show="loadBalance" @show="loadBalance"
><b-overlay
:show="channels.length === 0"
rounded="sm"
> >
<template #overlay> <template #modal-header="" />
<div class="text-center"> <b-overlay
<p> :show="channels.length === 0"
IBC Module is not enabled. rounded="sm"
</p> >
</div> <template #overlay>
</template> <div class="text-center">
<validation-observer ref="simpleRules"> <p>
<b-form> IBC Module is not enabled.
<b-row> </p>
<b-col> </div>
<b-form-group </template>
label="Sender" <validation-observer ref="simpleRules">
label-for="Account" <b-form>
> <b-row>
<b-input-group class="mb-25"> <b-col>
<b-input-group-prepend is-text> <b-form-group
<b-avatar label="Sender"
:src="account?account.logo:''" label-for="Account"
size="18"
variant="light-primary"
rounded
/>
</b-input-group-prepend>
<b-form-input
:value="account?account.addr:address"
readonly
/>
</b-input-group>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Available Token"
label-for="Token"
>
<validation-provider
#default="{ errors }"
rules="required"
name="Token"
>
<b-form-select
v-model="token"
@change="tokenChange"
>
<template #first>
<b-form-select-option
value=""
>
-- Please select a token --
</b-form-select-option>
</template>
<b-form-select-option
v-for="item in balance"
:key="item.denom"
:value="item.denom"
>
{{ format(item) }}
</b-form-select-option>
</b-form-select>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Amount"
label-for="Amount"
>
<validation-provider
v-slot="{ errors }"
rules="required|regex:^([0-9\.]+)$"
name="amount"
> >
<b-input-group class="mb-25"> <b-input-group class="mb-25">
<b-input-group-prepend is-text>
<b-avatar
:src="account?account.logo:''"
size="18"
variant="light-primary"
rounded
/>
</b-input-group-prepend>
<b-form-input <b-form-input
id="Amount" :value="account?account.addr:address"
v-model="amount" readonly
:state="errors.length > 0 ? false:null" />
placeholder="Input a number" </b-input-group>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Available Token"
label-for="Token"
>
<validation-provider
#default="{ errors }"
rules="required"
name="Token"
>
<b-form-select
v-model="token"
@change="tokenChange"
>
<template #first>
<b-form-select-option
value=""
>
-- Please select a token --
</b-form-select-option>
</template>
<b-form-select-option
v-for="item in balance"
:key="item.denom"
:value="item.denom"
>
{{ format(item) }}
</b-form-select-option>
</b-form-select>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Amount"
label-for="Amount"
>
<validation-provider
v-slot="{ errors }"
rules="required|regex:^([0-9\.]+)$"
name="amount"
>
<b-input-group class="mb-25">
<b-form-input
id="Amount"
v-model="amount"
:state="errors.length > 0 ? false:null"
placeholder="Input a number"
type="number"
/>
<b-input-group-append is-text>
{{ printDenom() }}
</b-input-group-append>
</b-input-group>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Destination"
label-for="destination"
>
<validation-provider
#default="{ errors }"
rules="required"
name="destination"
>
<v-select
v-model="destination"
name="destination"
:options="destinationOptions"
placeholder="Select a channel"
/>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Recipient"
label-for="Recipient"
>
<validation-provider
#default="{ errors }"
rules="required"
name="recipient"
>
<b-input-group class="mb-25">
<b-form-input
id="Recipient"
v-model="recipient"
:state="errors.length > 0 ? false:null"
:placeholder="placeholder"
/>
</b-input-group>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Fee"
label-for="Fee"
>
<validation-provider
v-slot="{ errors }"
rules="required|integer"
name="fee"
>
<b-input-group>
<b-form-input v-model="fee" />
<b-input-group-append>
<b-form-select
v-model="feeDenom"
:options="feeDenoms"
value-field="denom"
text-field="denom"
/>
</b-input-group-append>
</b-input-group>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
<b-col cols="12">
<b-form-group>
<b-form-checkbox
v-model="advance"
name="advance"
value="true"
>
<small>Advance</small>
</b-form-checkbox>
</b-form-group>
</b-col>
</b-row>
<b-row v-if="advance">
<b-col cols="12">
<b-form-group
label="Gas"
label-for="gas"
>
<validation-provider
v-slot="{ errors }"
name="gas"
>
<b-form-input
id="gas"
v-model="gas"
type="number" type="number"
/> />
<b-input-group-append is-text> <small class="text-danger">{{ errors[0] }}</small>
{{ printDenom() }} </validation-provider>
</b-input-group-append> </b-form-group>
</b-input-group> </b-col>
<small class="text-danger">{{ errors[0] }}</small> <b-col cols="12">
</validation-provider> <b-form-group
</b-form-group> label="Memo"
</b-col> label-for="Memo"
</b-row>
<b-row>
<b-col>
<b-form-group
label="Destination"
label-for="destination"
>
<validation-provider
#default="{ errors }"
rules="required"
name="destination"
> >
<v-select <validation-provider
v-model="destination" v-slot="{ errors }"
name="destination" name="memo"
:options="destinationOptions"
placeholder="Select a channel"
/>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Recipient"
label-for="Recipient"
>
<validation-provider
#default="{ errors }"
rules="required"
name="recipient"
>
<b-input-group class="mb-25">
<b-form-input
id="Recipient"
v-model="recipient"
:state="errors.length > 0 ? false:null"
:placeholder="placeholder"
/>
</b-input-group>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Fee"
label-for="Fee"
>
<validation-provider
v-slot="{ errors }"
rules="required|integer"
name="fee"
>
<b-input-group>
<b-form-input v-model="fee" />
<b-input-group-append>
<b-form-select
v-model="feeDenom"
:options="feeDenoms"
value-field="denom"
text-field="denom"
/>
</b-input-group-append>
</b-input-group>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
<b-col cols="12">
<b-form-group>
<b-form-checkbox
v-model="advance"
name="advance"
value="true"
>
<small>Advance</small>
</b-form-checkbox>
</b-form-group>
</b-col>
</b-row>
<b-row v-if="advance">
<b-col cols="12">
<b-form-group
label="Gas"
label-for="gas"
>
<validation-provider
v-slot="{ errors }"
name="gas"
>
<b-form-input
id="gas"
v-model="gas"
type="number"
/>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
<b-col cols="12">
<b-form-group
label="Memo"
label-for="Memo"
>
<validation-provider
v-slot="{ errors }"
name="memo"
>
<b-form-input
id="Memo"
v-model="memo"
max="2"
/>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Wallet"
label-for="wallet"
>
<validation-provider
v-slot="{ errors }"
rules="required"
name="wallet"
>
<b-form-radio-group
v-model="wallet"
stacked
class="demo-inline-spacing"
> >
<b-form-radio <b-form-input
id="Memo"
v-model="memo"
max="2"
/>
<small class="text-danger">{{ errors[0] }}</small>
</validation-provider>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
label="Wallet"
label-for="wallet"
>
<validation-provider
v-slot="{ errors }"
rules="required"
name="wallet"
>
<b-form-radio-group
v-model="wallet" v-model="wallet"
name="wallet" stacked
value="keplr" class="demo-inline-spacing"
class="d-none d-md-block"
> >
Keplr <b-form-radio
</b-form-radio> v-model="wallet"
<b-form-radio name="wallet"
v-model="wallet" value="keplr"
name="wallet" class="d-none d-md-block"
value="ledgerUSB" >
> Keplr
<small>Ledger(USB)</small> </b-form-radio>
</b-form-radio> <b-form-radio
<b-form-radio v-model="wallet"
v-model="wallet" name="wallet"
name="wallet" value="ledgerUSB"
value="ledgerBle" >
class="mr-0" <small>Ledger(USB)</small>
> </b-form-radio>
<small>Ledger(Bluetooth)</small> <b-form-radio
</b-form-radio> v-model="wallet"
</b-form-radio-group> name="wallet"
<small class="text-danger">{{ errors[0] }}</small> value="ledgerBle"
</validation-provider> class="mr-0"
</b-form-group> >
</b-col> <small>Ledger(Bluetooth)</small>
</b-row> </b-form-radio>
</b-form> </b-form-radio-group>
</validation-observer> <small class="text-danger">{{ errors[0] }}</small>
{{ error }} </validation-provider>
</b-overlay></b-modal> </b-form-group>
</b-col>
</b-row>
</b-form>
</validation-observer>
{{ error }}
</b-overlay></b-modal>
</div> </div>
</template> </template>

View File

@ -18,8 +18,8 @@
</b-tabs> </b-tabs>
<PlaceForm <PlaceForm
:type="tabIndex" :type="tabIndex"
:base="base" :pool.sync="pool"
:target="target" :denom-trace="denomTrace"
/> />
</div> </div>
</template> </template>
@ -37,13 +37,13 @@ export default {
PlaceForm, PlaceForm,
}, },
props: { props: {
base: { pool: {
type: String, type: Object,
required: true, default: () => {},
}, },
target: { denomTrace: {
type: String, type: [Array, Object],
required: true, default: () => [],
}, },
}, },
data: () => ({ data: () => ({

View File

@ -141,6 +141,7 @@ import {
} from 'bootstrap-vue' } from 'bootstrap-vue'
import FeatherIcon from '@/@core/components/feather-icon/FeatherIcon.vue' import FeatherIcon from '@/@core/components/feather-icon/FeatherIcon.vue'
import { /* abbrAddress, */ formatTokenAmount, getLocalAccounts } from '@/libs/data' import { /* abbrAddress, */ formatTokenAmount, getLocalAccounts } from '@/libs/data'
import { getPairName } from '@/libs/osmos'
import DepositeWindow from './DepositeWindow.vue' import DepositeWindow from './DepositeWindow.vue'
export default { export default {
@ -160,13 +161,13 @@ export default {
type: Number, type: Number,
required: true, required: true,
}, },
base: { pool: {
type: String, type: Object,
required: true, default: () => {},
}, },
target: { denomTrace: {
type: String, type: [Array, Object],
required: true, default: () => [],
}, },
}, },
data() { data() {
@ -176,11 +177,18 @@ export default {
total: 0, total: 0,
slippage: 0.05, slippage: 0.05,
marks: [0, 0.01, 0.025, 0.05], marks: [0, 0.01, 0.025, 0.05],
baseAmount: 0, balance: {},
targetAmount: 0, // base: '',
// target: '',
} }
}, },
computed: { computed: {
base() {
return getPairName(this.pool, this.denomTrace, 'base')
},
target() {
return getPairName(this.pool, this.denomTrace, 'target')
},
price() { price() {
const p1 = this.$store.state.chains.quotes[this.base] const p1 = this.$store.state.chains.quotes[this.base]
const p2 = this.$store.state.chains.quotes[this.target] const p2 = this.$store.state.chains.quotes[this.target]
@ -190,28 +198,25 @@ export default {
return '' return ''
}, },
available() { available() {
const denom = this.$http.osmosis.getMinDenom(this.type === 0 ? this.base : this.target) if (this.pool && this.pool.poolAssets) {
return formatTokenAmount(this.type === 0 ? this.targetAmount : this.baseAmount, 2, denom) const mode = this.type === 1 ? 0 : 1
const { denom } = this.pool.poolAssets[mode].token
let amount = 0
this.balance.forEach(x => {
if (x.denom === denom) {
amount = x.amount
}
})
return formatTokenAmount(amount, 6, denom)
}
return 0
}, },
}, },
created() { created() {
this.initialAddress() this.initialAddress()
console.log('address', this.address)
this.$http.getBankBalances(this.address).then(res => { this.$http.getBankBalances(this.address).then(res => {
console.log(res, this.base, this.target)
if (res && res.length > 0) { if (res && res.length > 0) {
const baseHash = this.$http.osmosis.getIBCDenomHash(this.base) this.balance = res
const targetHash = this.$http.osmosis.getIBCDenomHash(this.target)
res.forEach(token => {
console.log('token:', token)
if (token.denom === baseHash) {
this.baseAmount = token.amount
}
if (token.denom === targetHash) {
this.targetAmount = token.amount
}
})
console.log(this.baseAmount, this.targetAmount)
} }
}) })
}, },