From 16e04b0f90f76be3838318d490a4eb2cc88ad6d6 Mon Sep 17 00:00:00 2001 From: Tim Olson <> Date: Wed, 1 Nov 2023 00:33:53 -0400 Subject: [PATCH] withdrawls --- src/blockchain/contract.js | 10 +- src/blockchain/token.js | 44 +++++++ src/blockchain/wallet.js | 113 ++++++++++------- src/components/Blockchain.vue | 34 ++++++ src/components/ConnectWallet.vue | 24 ---- src/components/CopyButton.vue | 11 +- src/components/NeedsProvider.vue | 52 ++++++++ src/components/NeedsQueryHelper.vue | 21 ---- .../{NeedsWallet.vue => NewOrder.vue} | 2 +- src/components/PhoneCard.vue | 9 +- src/components/TimedOrderEntry.vue | 27 +++-- src/components/TokenChoice.vue | 6 +- src/components/TokenRow.vue | 38 ++++-- src/components/Vault.vue | 114 ++++++++++-------- src/components/Withdraw.vue | 64 ++++++++++ src/layouts/default/View.vue | 3 +- src/misc.js | 11 +- src/store/store.js | 25 ++-- src/styles/style.scss | 7 +- src/styles/vars.scss | 12 +- 20 files changed, 438 insertions(+), 189 deletions(-) create mode 100644 src/blockchain/token.js create mode 100644 src/components/Blockchain.vue delete mode 100644 src/components/ConnectWallet.vue create mode 100644 src/components/NeedsProvider.vue delete mode 100644 src/components/NeedsQueryHelper.vue rename src/components/{NeedsWallet.vue => NewOrder.vue} (77%) create mode 100644 src/components/Withdraw.vue diff --git a/src/blockchain/contract.js b/src/blockchain/contract.js index 9f9bfef..6a6268f 100644 --- a/src/blockchain/contract.js +++ b/src/blockchain/contract.js @@ -1,7 +1,6 @@ import {ethers} from "ethers"; import {factoryAbi, queryHelperAbi, vaultAbi} from "@/blockchain/abi.js"; import {useStore} from "@/store/store.js"; -import {provider} from "@/blockchain/wallet.js"; export function vaultAddress( owner, num=0) { @@ -11,7 +10,7 @@ export function vaultAddress( owner, num=0) { } -function contractOrNull(addr,abi,provider) { +export function contractOrNull(addr,abi,provider) { try { return new ethers.Contract(addr,abi,provider) } @@ -22,16 +21,17 @@ function contractOrNull(addr,abi,provider) { export async function factoryContract() { const s = useStore() - return contractOrNull(s.factory, factoryAbi, provider) + return contractOrNull(s.factory, factoryAbi, s.provider) } export async function queryHelperContract() { const s = useStore() - return contractOrNull(s.helper, queryHelperAbi, provider) + return contractOrNull(s.helper, queryHelperAbi, s.provider) } export async function poolContract(addr) { - return contractOrNull(addr, poolAbi, provider) + const s = useStore() + return contractOrNull(addr, poolAbi, s.provider) } export async function vaultContract(num, signer) { diff --git a/src/blockchain/token.js b/src/blockchain/token.js new file mode 100644 index 0000000..d61f1d7 --- /dev/null +++ b/src/blockchain/token.js @@ -0,0 +1,44 @@ +import {socket} from "@/socket.js"; +import {useStore} from "@/store/store.js"; +import {erc20Abi} from "@/blockchain/abi.js"; +import {ethers} from "ethers"; + +export async function getToken(addr) { + const s = useStore() + if (!(addr in s.tokens)) + await addExtraToken(addr) + return s.tokens[addr] +} + +export async function addExtraToken(addr) { + const prom = new Promise((resolve) => { + const s = useStore() + const chainId = s.chainId + console.log('querying token', addr) + socket.emit('lookupToken', chainId, addr, (info) => { + console.log('server token info', info) + if (info !== null) { + s.addToken(chainId, info) + resolve(info) + } + else { + if( s.provider===null ) { + console.log('warning: token lookup cancelled due to null provider', addr) + resolve(null) + return + } + const token = new ethers.Contract(addr, erc20Abi, s.provider) + Promise.all( [token.symbol(), token.decimals()] ).then((symbol,decimals)=>{ + info = { + address: addr, + symbol: symbol, + decimals: decimals, + } + s.addToken(chainId, info) + resolve(info) + }) + } + }) + }) + return await prom +} diff --git a/src/blockchain/wallet.js b/src/blockchain/wallet.js index c43bc0f..4b63913 100644 --- a/src/blockchain/wallet.js +++ b/src/blockchain/wallet.js @@ -1,26 +1,26 @@ import {ethers} from "ethers"; -import {useStore} from "@/store/store"; +import {setProvider, useStore} from "@/store/store"; import {socket} from "@/socket.js"; -import {vaultContract} from "@/blockchain/contract.js"; +import {contractOrNull} from "@/blockchain/contract.js"; +import {vaultAbi} from "@/blockchain/abi.js"; -export let provider = null export function onChainChanged(chainId) { chainId = Number(chainId) console.log('chain changed', chainId) const store = useStore() if( chainId !== store.chainId ) { - store.chainId = chainId + const provider = new ethers.BrowserProvider(window.ethereum, chainId); + setProvider(provider, chainId) store.account = null - provider = new ethers.BrowserProvider(window.ethereum, chainId) provider.listAccounts().then(changeAccounts) - new ethers.Interface([ - // 'event DexorderSwapCreated' // todo - ]) + store.chainId = chainId // touch the chainId last. will cause any clients of the store's provider getter to refresh } } function changeAccounts(accounts) { + console.log('change accounts', accounts) + const store = useStore() if( accounts.length === 0 ) { store.account = null store.vaults = [] @@ -29,16 +29,15 @@ function changeAccounts(accounts) { else { const store = useStore() store.account = accounts[0].address - flushOrders() + flushTransactions() socket.emit('address', store.chainId, accounts[0].address) } } function onAccountsChanged(accounts) { - // console.log('accounts changed', accounts) const store = useStore() if (accounts.length === 0 || accounts[0] !== store.account) - changeAccounts(store, accounts); + changeAccounts(accounts); } export async function watchWallet() { @@ -82,31 +81,56 @@ const errorHandlingProxy = { export async function connectWallet() { - return provider.getSigner() + console.log('TODO connect wallet') + // eth_getaccounts } -export async function pendOrder(order) { +export function pendOrder(order) { console.log('order', JSON.stringify(order)) const s = useStore() - s.pendingOrders.push(order) - flushOrders() -} - - -export function flushOrders() { - // noinspection JSIgnoredPromiseFromCall - asyncFlushOrders() -} - -export async function asyncFlushOrders() { - const s = useStore() - const orders = s.pendingOrders - if (!orders.length) + const vault = s.vault; + if(vault === null ) { + console.error('vault was null during pendOrder') return + } + pendTransaction(async (signer)=> { + const contract = contractOrNull(vault, vaultAbi, signer) + if( contract === null ) { + console.error('vault contract was null while sending order transaction', vault) + return null + } + return await contract.placeOrder(order) + }) +} + + +export function pendTransaction(sender) { + const s = useStore() + s.transactionSenders.push(sender) + flushTransactions() +} + + +export function flushTransactions() { + // noinspection JSIgnoredPromiseFromCall + asyncFlushTransactions() +} + +export async function asyncFlushTransactions() { + // todo rework into flushTransactions() + const s = useStore() + if( s.provider === null ) { + console.log('warning: asyncFlushOrders() cancelled due to null provider') + return + } + const senders = s.transactionSenders + if (!senders.length) + return + console.log(`flushing ${s.transactionSenders.length} transactions`) let signer try { - signer = await provider.getSigner(); + signer = await s.provider.getSigner(); } catch (e) { // { // "code": -32002, @@ -119,28 +143,27 @@ export async function asyncFlushOrders() { socket.emit('ensureVault', s.chainId, await signer.getAddress(), 0) return } - const contract = await vaultContract(0, signer) - if (!contract) { - console.error(`no contract for vault 0 of ${signer.address}`) - return - } - for (const order of orders) - doPlaceOrder(s, contract, order) + for (const sender of senders) + doSendTransaction(sender, signer) } -function doPlaceOrder(s, contract, order) { - contract.placeOrder(order).then((tx)=>{ - console.log('placed order', tx) - s.removePendingOrder(order) +function doSendTransaction(sender, signer) { + const s = useStore(); + sender(signer).then((tx)=>{ + console.log('sent transaction', tx) + s.removeTransactionSender(sender) tx.wait().then((tr)=>console.log('tx receipt',tr)) }).catch((e)=>{ - if( e.info.error.code === 4001 ) { - console.log(`user rejected order`, order) - s.removePendingOrder(order) + if( e.info?.error?.code === 4001 ) { + console.log(`user rejected transaction`) + s.removeTransactionSender(sender) } else { - console.error('error placing order', order, e.reason, e.info) - s.removePendingOrder(order) + if( e.reason && e.info ) + console.error('error sending transaction', e.reason, e.info) + else + console.error('error sending transaction', e) + s.removeTransactionSender(sender) // todo retry? } }) @@ -150,5 +173,5 @@ socket.on('vaults', (vaults)=>{ const s = useStore() console.log('vaults', vaults) s.vaults = vaults - flushOrders() + flushTransactions() }) diff --git a/src/components/Blockchain.vue b/src/components/Blockchain.vue new file mode 100644 index 0000000..49aa0ce --- /dev/null +++ b/src/components/Blockchain.vue @@ -0,0 +1,34 @@ + + {{media.name}} + + + + + diff --git a/src/components/ConnectWallet.vue b/src/components/ConnectWallet.vue deleted file mode 100644 index ec154dd..0000000 --- a/src/components/ConnectWallet.vue +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - diff --git a/src/components/CopyButton.vue b/src/components/CopyButton.vue index a9f0f44..435b80d 100644 --- a/src/components/CopyButton.vue +++ b/src/components/CopyButton.vue @@ -1,7 +1,12 @@ - + + + + + Copied! + + + diff --git a/src/components/NeedsQueryHelper.vue b/src/components/NeedsQueryHelper.vue deleted file mode 100644 index 0b09f65..0000000 --- a/src/components/NeedsQueryHelper.vue +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - diff --git a/src/components/NeedsWallet.vue b/src/components/NewOrder.vue similarity index 77% rename from src/components/NeedsWallet.vue rename to src/components/NewOrder.vue index bdc5bc8..9e138bb 100644 --- a/src/components/NeedsWallet.vue +++ b/src/components/NewOrder.vue @@ -1,5 +1,5 @@ - + diff --git a/src/components/TokenChoice.vue b/src/components/TokenChoice.vue index f27bfa0..e365d65 100644 --- a/src/components/TokenChoice.vue +++ b/src/components/TokenChoice.vue @@ -1,5 +1,5 @@ - 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 = [] diff --git a/src/components/TokenRow.vue b/src/components/TokenRow.vue index e75b731..2cd7d85 100644 --- a/src/components/TokenRow.vue +++ b/src/components/TokenRow.vue @@ -1,10 +1,24 @@ - - {{token.symbol}} - {{token.name||''}} - {{fixed}} - + + + + {{ token.name || '' }} + {{ fixed }} + {{ token.symbol }} + + + + + + + + + onWithdraw(token.address)"/> + + + @@ -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) diff --git a/src/layouts/default/View.vue b/src/layouts/default/View.vue index 432af36..16648a4 100644 --- a/src/layouts/default/View.vue +++ b/src/layouts/default/View.vue @@ -4,7 +4,7 @@ - + @@ -14,6 +14,7 @@ import Alerts from "@/components/Alerts.vue"; import {VSkeletonLoader} from "vuetify/labs/VSkeletonLoader"; import {useStore} from "@/store/store.js"; +import NeedsProvider from "@/components/NeedsProvider.vue"; const store = useStore() diff --git a/src/misc.js b/src/misc.js index 7269867..ff2a8a7 100644 --- a/src/misc.js +++ b/src/misc.js @@ -1,3 +1,4 @@ +import {FixedNumber} from "ethers"; export class SingletonCoroutine { constructor(f, delay=10, retry=true) { @@ -42,4 +43,12 @@ export const vAutoSelect = { } } export const uint64max = 18446744073709551615n -export const uint32max = 4294967295n \ No newline at end of file +export const uint32max = 4294967295n + +export function tokenNumber(token, balance) { + return FixedNumber.fromValue(balance, token.decimals, {decimals:token.decimals, width: 256}) +} + +export function tokenFloat(token, balance) { + return tokenNumber(token,balance).toUnsafeFloat() +} diff --git a/src/store/store.js b/src/store/store.js index b987efa..431b0da 100644 --- a/src/store/store.js +++ b/src/store/store.js @@ -2,6 +2,14 @@ import { defineStore } from 'pinia' import {knownTokens} from "@/knownTokens.js"; +let rawProvider = null +let rawProviderChainId = null + +export function setProvider( provider, chainId ) { + rawProvider = provider + rawProviderChainId = chainId +} + export const useStore = defineStore('app', { state: () => ({ chainId: null, @@ -9,17 +17,18 @@ export const useStore = defineStore('app', { vaultInitCodeHash: null, account: null, vaults: [], - pendingOrders: [], // created but not yet sent to metamask. maybe waiting on vault creation. - errors: [{ - title: 'DANGER!', - text: 'This is early development (alpha) software. There could be severe bugs that lose all your money. Thank you for testing a SMALL amount!', - closeable: false - }], + transactionSenders: [], // a list of function(signer) that send transactions + errors: [ + // todo re-enable danger warning + // {title: 'DANGER!', text: 'This is early development (alpha) software. There could be severe bugs that lose all your money. Thank you for testing a SMALL amount!', closeable: true} + ], extraTokens: {}, poolPrices: {}, vaultBalances: {}, // indexed by vault addr then by token addr. value is an int }), getters: { + vault: (s)=>s.vaults.length===0 ? null : s.vaults[0], + provider: (s)=>s.chainId===rawProviderChainId ? rawProvider : null, chain: (s)=> !s.chainInfo ? null : (s.chainInfo[s.chainId] || null), tokens: (s)=>{ const chains = s.chainId in s.chainInfo && s.chainInfo[s.chainId].tokens !== undefined ? s.chainInfo[s.chainId].tokens : [] @@ -37,8 +46,8 @@ export const useStore = defineStore('app', { helper: (s)=>!s.chain?null:s.chain.helper, }, actions: { - removePendingOrder(order) { - this.pendingOrders = this.pendingOrders.filter((v) => v !== order) + removeTransactionSender(sender) { + this.transactionSenders = this.transactionSenders.filter((v) => v !== sender) }, error(title, text, closeable=true) { this.errors.push({title, text, closeable}) diff --git a/src/styles/style.scss b/src/styles/style.scss index 1401e73..044f490 100644 --- a/src/styles/style.scss +++ b/src/styles/style.scss @@ -33,8 +33,11 @@ justify-content: center; } + .v-text-field.text-end input { + text-align: end; + } } -.uniswap-pink { - color: v.$uniswap-pink; +.uniswap-color { + color: v.$uniswap-color; } diff --git a/src/styles/vars.scss b/src/styles/vars.scss index 290ee2b..0ed8990 100644 --- a/src/styles/vars.scss +++ b/src/styles/vars.scss @@ -4,10 +4,11 @@ $green: #00CC33; $red: #CC0033; $yellow: #ffcc00; $blue: #0033CC; -$white: #fffefd; // just a touch green -$black: #000102; +$white: #fdfffe; // just a touch greenblue +$black: #000201; // just a touch greenblue -$uniswap-pink: #ff007a; +$arbitrum-color: #12aaff; +$uniswap-color: #ff007a; $primary: $blue; $primary-50: transparentize($primary,0.5); @@ -31,7 +32,7 @@ $all-colors: ( green: $green, yellow: $yellow, red: $red, - uniswap: $uniswap-pink, + uniswap: $uniswap-color, ); $body-font-family: 'Saira Semi Condensed', monospace, sans-serif; // fairly geometric, horizontal s's, clean sans, readable @@ -50,8 +51,9 @@ $body-font-family: 'Saira Semi Condensed', monospace, sans-serif; // fairly geom //$body-font-family: 'Chakra Petch', sans-serif; //$heading-font-family: 'Tektur', sans-serif; $heading-font-family: 'Orbitron', sans-serif; +//$heading-font-family: 'Exo 2', sans-serif; //$heading-font-family: 'Quantico', sans-serif; //$heading-font-family: 'Chakra Petch', sans-serif; $sm-breakpoint: 600px; -$card-maxw: 25em; +$card-maxw: 34em;