withdrawls

This commit is contained in:
Tim Olson
2023-11-01 00:33:53 -04:00
parent ee61c96d38
commit 16e04b0f90
20 changed files with 438 additions and 189 deletions

View File

@@ -0,0 +1,34 @@
<template>
<div :class="['chain-block','chain-'+chainId]"><v-avatar class='chain-avatar' v-if="media.img" :image="media.img" rounded="0" size="1em"/>&nbsp;{{media.name}}</div>
</template>
<script setup>
import {useStore} from "@/store/store";
import {computed} from "vue";
const unsupportedMedia = {}
const chainMedia = {
1: {name: 'Ethereum', img: null},
42161: { name: 'Arbitrum One', img: '/arbitrum-logo.svg'},
31331: { name: 'Mockchain', img: null},
}
const s = useStore()
const props = defineProps(['chainId'])
const media = computed(()=>chainMedia[props.chainId] || unsupportedMedia)
</script>
<style lang="scss"> // NOT 'scoped'
@use "src/styles/vars" as *;
//.chain-avatar {
// max-height: 1em;
// max-width: 1em;
//}
.chain-block {
display: inline;
}
.chain-42161 {
color: #12aaff;
}
</style>

View File

@@ -1,24 +0,0 @@
<template>
<v-btn prepend-icon="mdi-lightbulb-on" text="Connect Wallet" @click="connectWallet" :disabled="disabled"/>
</template>
<script setup>
import {useStore} from "@/store/store";
import {ref} from "vue";
import {provider} from "@/blockchain/wallet.js";
const s = useStore()
const disabled = ref(false)
async function connectWallet() {
disabled.value = true
await provider.getSigner()
disabled.value = false
}
</script>
<style scoped lang="scss">
@use "src/styles/vars" as *;
</style>

View File

@@ -1,7 +1,12 @@
<template>
<v-btn v-if="permitted" rounded variant="text" size="small" density="compact" @click="copy()"
:class="error?'error':copied?'success':''"
:icon="error?'mdi-close-box-outline':copied?'mdi-check-circle-outline':'mdi-content-copy'"/>
<v-tooltip :model-value="!error&&copied" :open-on-hover="false">
<template v-slot:activator="{ props }">
<v-btn v-bind="props" v-if="permitted" rounded variant="text" size="small" density="compact" @click="copy()"
:class="error?'error':copied?'success':''"
:icon="error?'mdi-close-box-outline':copied?'mdi-check-circle-outline':'mdi-content-copy'"/>
</template>
<span>Copied!</span>
</v-tooltip>
</template>
<script setup lang="ts">

View File

@@ -0,0 +1,52 @@
<template>
<slot v-if="ok"/>
<phone-card v-if="!walletOk">
<v-card-title>Install Wallet</v-card-title>
<v-card-text>
A cryptocurrency wallet such as <a href="https://metamask.io/download/">MetaMask</a> is required to use Dexorder.
Please install a crypto wallet into your browser to experience the power of Dexorder.
</v-card-text>
<v-card-actions>
<v-btn prepend-icon="mdi-reload" text="Reload After Installing Wallet"/>
</v-card-actions>
</phone-card>
<phone-card v-if="walletOk && !providerOk">
<v-card-text>
Please log in to your crypto wallet.
</v-card-text>
<v-card-actions v-if="walletOk && !providerOk">
<v-btn prepend-icon="mdi-power" text="Connect Wallet" @click="connectWallet"/>
</v-card-actions>
</phone-card>
<phone-card v-if="walletOk && providerOk && !chainOk">
<v-card-title><v-icon icon="mdi-reload-alert" color="warning"/> Change Blockchain</v-card-title>
<v-card-text>
Dexorder works only with <blockchain chain-id="42161"/>. Please switch to the
<blockchain chain-id="42161"/> blockchain in your wallet.
</v-card-text>
</phone-card>
</template>
<script setup>
import {useStore} from "@/store/store";
import PhoneCard from "@/components/PhoneCard.vue";
import {connectWallet} from "@/blockchain/wallet.js";
import {computed} from "vue";
import Blockchain from "@/components/Blockchain.vue";
const s = useStore()
const walletOk = typeof window.ethereum !== 'undefined'
const providerOk = computed(()=>s.provider!==null)
const chainOk = computed(()=>providerOk.value && s.helper!==null)
const ok = computed(()=>{
return walletOk && providerOk.value && chainOk.value
})
</script>
<style scoped lang="scss">
@use "src/styles/vars" as *;
.arbitrum {
color: $arbitrum-color;
}
</style>

View File

@@ -1,21 +0,0 @@
<template>
<!-- <v-card v-if="!s.account" prepend-icon="mdi-connection" title="Connect Wallet"-->
<!-- text="Please connect your wallet to an Arbitrum account.">-->
<!-- <v-card-actions><v-btn text="Connect Wallet" color="green" variant="elevated" @click="connectWallet" prepend-icon="mdi-power"/></v-card-actions>-->
<!-- </v-card>-->
<slot v-if="s.helper"/>
<v-card v-if="!s.helper" prepend-icon='mdi-reload-alert' title="Change Blockchain"
text="Dexorder works only with Arbitrum. Please choose the Arbitrum blockchain in your wallet."/>
</template>
<script setup>
import {useStore} from "@/store/store";
const s = useStore()
</script>
<style scoped lang="scss">
@use "src/styles/vars" as *;
</style>

View File

@@ -1,5 +1,5 @@
<template>
<v-btn prepend-icon="mdi-plus" text="New Order"/>
</template>
<script setup>

View File

@@ -1,8 +1,10 @@
<template>
<v-card class="d-none d-md-block" :elevation="4">
<!-- tablets and desktops get a card outline -->
<v-card class="d-none d-sm-block phone-card" :elevation="4">
<slot/>
</v-card>
<v-container class="d-md-none">
<!-- phones use the entire screen -->
<v-container class="d-sm-none">
<slot/>
</v-container>
</template>
@@ -16,4 +18,7 @@ const s = useStore()
<style scoped lang="scss">
@use "src/styles/vars" as *;
.phone-card {
max-width: $card-maxw;
}
</style>

View File

@@ -1,6 +1,6 @@
<template>
<NeedsQueryHelper>
<PhoneCard>
<needs-provider>
<phone-card>
<v-card-title class="big">DCA / TWAP</v-card-title>
<v-card-subtitle>Multiple tranches over a time range</v-card-subtitle>
<v-card-text>
@@ -20,7 +20,7 @@
<v-chip v-for="r in routes" variant="text">
{{ s.chain.name }}
<v-img src="https://upload.wikimedia.org/wikipedia/commons/e/e7/Uniswap_Logo.svg" width="1.5em"/>
<span class="uniswap-pink ml-0 mr-1">v3</span>
<span class="uniswap-color ml-0 mr-1">v3</span>
{{pairSymbol}} {{r.fee/10000}}%
</v-chip>
@@ -78,8 +78,8 @@
<v-btn variant="outlined" color="red">Cancel</v-btn>
<v-btn variant="flat" color="green" :disabled="!validOrder" @click="placeOrder">Place Order</v-btn>
</v-card-actions>
</PhoneCard>
</NeedsQueryHelper>
</phone-card>
</needs-provider>
</template>
<script setup>
@@ -90,15 +90,15 @@ import PhoneCard from "@/components/PhoneCard.vue";
import {queryHelperContract} from "@/blockchain/contract.js";
// noinspection ES6UnusedImports
import {SingletonCoroutine, vAutoSelect} from "@/misc.js";
import NeedsQueryHelper from "@/components/NeedsQueryHelper.vue";
import {Exchange, newOrder, newTimeConstraint, TimeMode} from "@/blockchain/orderlib.js";
import {FixedNumber} from "ethers";
import {pendOrder} from "@/blockchain/wallet.js";
import NeedsProvider from "@/components/NeedsProvider.vue";
const s = useStore()
const buy = ref(false)
let _tokenA = ref(s.tokens !== undefined && s.tokens.length >= 1 ? s.tokens[0] : null)
let _tokenB = ref(s.tokens !== undefined && s.tokens.length >= 2 ? s.tokens[1] : null)
let _tokenA = ref(Object.values(s.tokens).length >= 1 ? Object.values(s.tokens)[0] : null)
let _tokenB = ref(Object.values(s.tokens).length >= 2 ? Object.values(s.tokens)[1] : null)
const tokenA = computed({
get() {
return _tokenA.value
@@ -149,19 +149,22 @@ const limitIsMinimum = computed(() => !(buy.value ^ inverted.value))
const validOrder = computed(()=>amount.value > 0 && routes.value.length > 0 )
async function findRoute() {
console.log('finding route', _tokenA.value, _tokenB.value)
routes.value = []
if( !_tokenA.value || !_tokenB.value )
return
const helper = await queryHelperContract()
if( !helper )
if( !helper ) {
console.log('no helper')
return
}
routesPending.value = true
let rawRoutes
try {
rawRoutes = await helper.getRoutes(tokenA.value.address, tokenB.value.address)
}
catch (e) {
// console.log('routes exception', e)
console.log('routes exception', e)
routesPending.value = false
return
}
@@ -242,7 +245,7 @@ function validateMin(v) {
return true
}
async function placeOrder() {
function placeOrder() {
const ta = tokenA.value;
const tb = tokenB.value;
const tokenIn = buy.value ? tb.address : ta.address
@@ -278,7 +281,7 @@ async function placeOrder() {
ts.push([amtPerTranche, cs])
}
const order = newOrder(tokenIn, tokenOut, route.exchange, route.fee, amt, amountIsInput, ts)
await pendOrder(order)
pendOrder(order)
}
</script>

View File

@@ -1,5 +1,5 @@
<template>
<v-combobox :items="Object.values(s.tokens)" :auto-select-first="true"
<v-combobox :items="tokens" :auto-select-first="true"
item-title="symbol"
:filter-keys="['raw.name','raw.symbol','raw.address']"
:model-value="modelValue"
@@ -21,7 +21,7 @@
<script setup>
import {useStore as useStore2} from "@/store/store";
import {ref} from "vue";
import {computed, ref} from "vue";
import {ethers} from "ethers";
// noinspection ES6UnusedImports
import {vAutoSelect} from "@/misc.js";
@@ -32,6 +32,8 @@ const props = defineProps(['modelValue', 'label'])
const emit = defineEmits(['update:modelValue'])
const loading = ref(false)
const errors = ref([])
const tokens = computed(()=>Object.values(s.tokens))
function good() {
errors.value = []

View File

@@ -1,10 +1,24 @@
<template>
<tr>
<td><v-img v-if="imageSrc" :src="imageSrc"/></td>
<td>{{token.symbol}}</td>
<td class="d-none d-sm-table-cell">{{token.name||''}}</td>
<td>{{fixed}}</td>
<td><!-- todo actions --></td>
<td>
<v-avatar v-if="imageSrc" :image="imageSrc"/>
</td>
<td class="d-none d-sm-table-cell">{{ token.name || '' }}</td>
<td class="text-right">{{ fixed }}</td>
<td class="text-left">{{ token.symbol }}</td>
<!-- todo price and value columns -->
<td>
<v-menu>
<template v-slot:activator="{ props }">
<v-btn variant="plain" v-bind="props" icon="mdi-menu"/> <!-- mdi-dots-vertical -->
</template>
<v-list>
<v-list-subheader :title="token.symbol"/>
<v-list-item title="Withdraw" key="withdraw" value="withdraw" prepend-icon="mdi-arrow-down-bold"
@click="()=>onWithdraw(token.address)"/>
</v-list>
</v-menu>
</td>
</tr>
</template>
@@ -12,13 +26,17 @@
import {useStore} from "@/store/store";
import {getToken} from "@/blockchain/token.js";
import {FixedNumber} from "ethers";
import {computed} from "vue";
import {computed, ref} from "vue";
const s = useStore()
const props = defineProps(['addr','amount'])
const props = defineProps(['addr', 'amount', 'onWithdraw'])
const token = await getToken(props.addr)
console.log('token', props.addr, token)
const fixed = computed(()=>FixedNumber.fromValue(props.amount, token.decimals, {width:256, decimals: token.decimals}))
const imageSrc = computed(()=>null )
const fixed = computed(() => FixedNumber.fromValue(props.amount, token.decimals, {
width: 256,
decimals: token.decimals
}))
const imageSrc = computed(() => null)
const withdrawing = ref(false)
</script>
<style scoped lang="scss">

View File

@@ -1,58 +1,70 @@
<template>
<!-- todo we can use something like this for ethereum where the vault creation is too expensive to subsidize
<PhoneCard v-if="s.vault===null || s.vault.length === 0">
<v-card-title><v-icon color="warning" icon="mdi-alert"/>&nbsp;Setup&nbsp;Vault</v-card-title>
<v-card-subtitle>Create Your Own Personal Dexorder Vault</v-card-subtitle>
<v-card-text>
Dexorder never has access to your tokens. Instead, you create a personal
vault which acts like your Dexorder account.
Create your own personal asset vault to get started with Dexorder. This vault
acts like your Dexorder account. For security, only <i>you</i> can deposit
or withdraw tokens from your vault, and no token approvals are ever given to
Dexorder. Instead, Dexorder sends trade requests to your vault at the right
times, then your vault checks the validity of those trade requests before
trading directly from your vault to the dex liquidity pool. Dexorder never
has any access to the funds in your vault.
</v-card-text>
<v-card-text>
Creating your personal vault is a one-time setup operation. Your vault address
is unique to you and never changes. You may deposit or withdraw funds in your
vault at any time, and you may save your vault address in your wallet for
easy access.
</v-card-text>
</PhoneCard>
<needs-provider>
<!-- todo we can use something like this for ethereum where the vault creation is too expensive to subsidize
<PhoneCard v-if="s.vault===null || s.vault.length === 0">
<v-card-title><v-icon color="warning" icon="mdi-alert"/>&nbsp;Setup&nbsp;Vault</v-card-title>
<v-card-subtitle>Create Your Own Personal Dexorder Vault</v-card-subtitle>
<v-card-text>
Dexorder never has access to your tokens. Instead, you create a personal
vault which acts like your Dexorder account.
Create your own personal asset vault to get started with Dexorder. This vault
acts like your Dexorder account. For security, only <i>you</i> can deposit
or withdraw tokens from your vault, and no token approvals are ever given to
Dexorder. Instead, Dexorder sends trade requests to your vault at the right
times, then your vault checks the validity of those trade requests before
trading directly from your vault to the dex liquidity pool. Dexorder never
has any access to the funds in your vault.
</v-card-text>
<v-card-text>
Creating your personal vault is a one-time setup operation. Your vault address
is unique to you and never changes. You may deposit or withdraw funds in your
vault at any time, and you may save your vault address in your wallet for
easy access.
</v-card-text>
</PhoneCard>
-->
<v-card v-if="s.vaults.length<num">
<v-card-title>No Vault Yet</v-card-title>
<v-card-text v-if="num!==0"><!--todo-->Multiple vaults are not yet supported</v-card-text>
<v-card-text v-if="num===0">Create an order first, then your vault account will appear here to accept a deposit of trading funds.</v-card-text>
</v-card>
<v-card v-if="s.vaults.length>num">
<v-card-title>Vault {{s.vaults.length>1?'#'+(num+1):''}}</v-card-title> <!-- todo vault nicknames -->
<v-card-subtitle v-if="exists">{{addr}} <copy-button :text="addr"/></v-card-subtitle>
<v-card-text v-if="empty">
<p>There are no funds currently in your vault.</p>
<p>Send tokens to the address above to fund your vault.</p>
</v-card-text>
<v-card-item v-if="!empty">
<v-table>
<tbody>
<suspense v-for="(amount,addr) of balances">
<token-row :addr="addr" :amount="amount" :onWithdraw="onWithdraw"/>
</suspense>
</tbody>
</v-table>
</v-card-item>
</v-card>
<withdraw :vault="addr" :token="withdrawToken" v-model="withdrawShow"/>
<!--
<div>
addr {{ addr }}<br/>
empty {{ empty }}<br/>
exists {{ exists }}<br/>
</div>
-->
<v-card v-if="s.vaults.length<num">
<v-card-title>No Vault Yet</v-card-title>
<v-card-text v-if="num!==0"><!--todo-->Multiple vaults are not yet supported</v-card-text>
<v-card-text v-if="num===0">Create an order first, then your vault account will appear here to accept a deposit of trading funds.</v-card-text>
</v-card>
<v-card v-if="s.vaults.length>num">
<v-card-title>Vault {{s.vaults.length>1?'#'+(num+1):''}}</v-card-title> <!-- todo vault nicknames -->
<v-card-subtitle v-if="exists">{{addr}} <copy-button :text="addr"/></v-card-subtitle>
<v-card-text v-if="empty">
<p>There are no funds currently in your vault.</p>
<p>Send tokens to the address above to fund your vault.</p>
</v-card-text>
<v-card-item>
<v-table v-if="!empty">
<tbody>
<suspense v-for="(amount,addr) of balances">
<token-row :addr="addr" :amount="amount"/>
</suspense>
</tbody>
</v-table>
</v-card-item>
</v-card>
</needs-provider>
</template>
<script setup>
import {useStore} from "@/store/store.js";
import PhoneCard from "@/components/PhoneCard.vue";
import {computed, defineAsyncComponent} from "vue";
import {computed, defineAsyncComponent, ref} from "vue";
import {vaultAddress} from "@/blockchain/contract.js";
import CopyButton from "@/components/CopyButton.vue";
import NeedsProvider from "@/components/NeedsProvider.vue";
import Withdraw from "@/components/Withdraw.vue";
console.log('vault setup')
const TokenRow = defineAsyncComponent(()=>import('./TokenRow.vue'))
const s = useStore()
@@ -63,9 +75,17 @@ const balances = computed(()=>{
console.log('balances', addr.value, s.vaultBalances, bs)
return bs || {}
})
const tokenAddrs = computed(()=>Object.keys(balances))
const empty = computed(()=>Object.keys(balances.value).length===0)
const exists = computed(()=>s.vaults.length>0)
const withdrawToken = ref(null)
const withdrawShow = ref(false)
function onWithdraw(addr) {
const token = s.tokens[addr]
console.log('withdraw', addr, token)
withdrawToken.value = token
withdrawShow.value = true
}
</script>
<style scoped lang="scss">

View File

@@ -0,0 +1,64 @@
<template>
<v-dialog :model-value="modelValue" @update:modelValue="$emit('update:modelValue', $event)">
<v-card>
<v-card-title>
<v-icon icon="mdi-down-arrow"/>&nbsp;Withdraw {{ token.symbol }}
</v-card-title>
<v-card-item>
<v-text-field class="text-end" type="number" variant="outlined" :min="0" :max="balanceFloat"
v-model="floatAmount" :step="balanceFloat/10">
<template v-slot:prepend-inner>
<v-btn variant="text" text="max" @click="floatAmount=balanceFloat"/>
</template>
<template v-slot:append-inner>
<span>{{ token.symbol }}</span>
</template>
</v-text-field>
<v-card-actions>
<v-btn text="Cancel" @click="$emit('update:modelValue', false)"/>
<v-btn text="Withdraw" color="red" @click="withdraw"/>
</v-card-actions>
</v-card-item>
</v-card>
</v-dialog>
</template>
<script setup>
import {useStore} from "@/store/store";
import {computed, ref} from "vue";
import {tokenFloat} from "@/misc.js";
import {contractOrNull} from "@/blockchain/contract.js"
import {vaultAbi} from "@/blockchain/abi.js";
import {pendTransaction} from "@/blockchain/wallet.js";
import {FixedNumber} from "ethers";
const s = useStore()
const props = defineProps(['modelValue', 'vault', 'token'])
const emit = defineEmits(['update:modelValue'])
const balance = computed(() => {
console.log('balance', props.vault, props.token, s.vaultBalances)
return s.vaultBalances[props.vault][props.token.address] || 0
})
const balanceFloat = computed(() => tokenFloat(props.token, balance.value))
const floatAmount = ref(0)
function withdraw() {
const vaultAddr = props.vault
const valueStr = floatAmount.value.toString();
const amount = FixedNumber.fromString(valueStr,{decimals:props.token.decimals, width:256, signed: false}).value;
console.log('pending withdrawl', valueStr, amount, props.token.symbol)
if( amount === 0n )
return
pendTransaction(async (signer)=>{
const vault = contractOrNull(vaultAddr, vaultAbi, signer)
return await vault['withdraw(address,uint256)'](props.token.address, amount)
})
emit('update:modelValue', false)
}
</script>
<style scoped lang="scss">
@use "src/styles/vars" as *;
</style>