fees.js; DCABuilder gas warning

This commit is contained in:
tim
2025-03-20 14:10:09 -04:00
parent 5a4a67e726
commit d446d5ab11
6 changed files with 131 additions and 30 deletions

View File

@@ -13,6 +13,7 @@
"lint": "eslint . --fix --ignore-path .gitignore" "lint": "eslint . --fix --ignore-path .gitignore"
}, },
"dependencies": { "dependencies": {
"@isaacs/ttlcache": "^1.4.1",
"@mdi/font": "6.9.96", "@mdi/font": "6.9.96",
"color": "^4.2.3", "color": "^4.2.3",
"core-js": "^3.29.0", "core-js": "^3.29.0",

View File

@@ -1,9 +1,9 @@
import {nav, uuid} from "@/misc.js"; import {nav, uuid} from "@/misc.js";
import {newContract, vaultContract} from "@/blockchain/contract.js"; import {vaultContract} from "@/blockchain/contract.js";
import {ensureVault, provider, switchChain, useWalletStore} from "@/blockchain/wallet.js"; import {ensureVault, provider, switchChain, useWalletStore} from "@/blockchain/wallet.js";
import {toRaw} from "vue"; import {toRaw} from "vue";
import {useChartOrderStore} from "@/orderbuild.js"; import {useChartOrderStore} from "@/orderbuild.js";
import {timestamp} from "@/common.js"; import {placementFee} from "@/fees.js";
export const TransactionState = { export const TransactionState = {
Created: 0, // user requested a transaction Created: 0, // user requested a transaction
@@ -176,29 +176,6 @@ export class PlaceOrderTransaction extends Transaction {
} }
// todo move to orderlib
async function placementFee(vault, order, window = 300) {
// If the fees are about to change within `window` seconds of now, we send the higher native amount of the two fees.
// If the fees sent are too much, the vault will refund the sender.
const v = await vaultContract(vault, provider)
const feeManagerAddr = await v.feeManager()
const feeManager = await newContract(feeManagerAddr, 'IFeeManager', provider)
const [sched, changeTimestamp] = await Promise.all([feeManager.fees(), feeManager.proposedFeeActivationTime()])
console.log('sched', order, sched)
// single order placement selector
const placementFeeSelector = 'placementFee((address,address,(uint8,uint24),uint256,uint256,bool,bool,bool,uint64,(uint16,bool,bool,bool,bool,bool,bool,bool,bool,uint16,uint24,uint32,uint32,(uint32,uint32),(uint32,uint32))[]),(uint8,uint8,uint8,uint8,uint8))'
let [orderFee, gasFee] = await v[placementFeeSelector](order, [...sched])
console.log('placementFee', orderFee, gasFee)
if (Number(changeTimestamp) - timestamp() < window) {
const nextSched = await feeManager.proposedFees()
const [nextOrderFee, nextGasFee] = await v[placementFeeSelector](order, [...nextSched])
if (nextOrderFee + nextGasFee > orderFee + gasFee)
[orderFee, gasFee] = [nextOrderFee, nextGasFee]
}
return [orderFee, gasFee]
}
export class CancelOrderTransaction extends Transaction { export class CancelOrderTransaction extends Transaction {
constructor(chainId, index) { constructor(chainId, index) {
super(chainId, TransactionType.CancelOrder) super(chainId, TransactionType.CancelOrder)

View File

@@ -11,6 +11,7 @@
<div> <div>
<v-text-field label="Split into" type="number" variant="outlined" <v-text-field label="Split into" type="number" variant="outlined"
aria-valuemin="1" aria-valuemax="100" min="1" max="1000" step="1" aria-valuemin="1" aria-valuemax="100" min="1" max="1000" step="1"
:hint="partsGasHint" :persistent-hint="true"
v-model="parts" v-auto-select class="parts mr-3"> v-model="parts" v-auto-select class="parts mr-3">
<template v-slot:append-inner> <template v-slot:append-inner>
parts parts
@@ -45,16 +46,17 @@
<script setup> <script setup>
import {builderDefaults, DEFAULT_SLIPPAGE, useChartOrderStore} from "@/orderbuild.js"; import {builderDefaults, DEFAULT_SLIPPAGE, useChartOrderStore} from "@/orderbuild.js";
import {allocationText, ShapeType} from "@/charts/shape.js"; import {allocationText, ShapeType} from "@/charts/shape.js";
import {sideColor, vAutoSelect} from "@/misc.js"; import {sideColor, SingletonCoroutine, toPrecision, vAutoSelect} from "@/misc.js";
import {useStore} from "@/store/store.js"; import {useStore} from "@/store/store.js";
import {computed, ref, watchEffect} from "vue"; import {computed, ref, watchEffect} from "vue";
import {createShape, deleteShapeId, widget} from "@/charts/chart.js"; import {createShape, deleteShapeId, dragging, widget} from "@/charts/chart.js";
import AbsoluteTimeEntry from "@/components/AbsoluteTimeEntry.vue"; import AbsoluteTimeEntry from "@/components/AbsoluteTimeEntry.vue";
import BuilderPanel from "@/components/chart/BuilderPanel.vue"; import BuilderPanel from "@/components/chart/BuilderPanel.vue";
import {ohlcStart} from "@/charts/chart-misc.js"; import {ohlcStart} from "@/charts/chart-misc.js";
import Color from "color"; import Color from "color";
import OrderAmount from "@/components/chart/OrderAmount.vue"; import OrderAmount from "@/components/chart/OrderAmount.vue";
import {MAX_FRACTION, newTranche} from "@/blockchain/orderlib.js"; import {MAX_FRACTION, newTranche} from "@/blockchain/orderlib.js";
import {getFeeSchedule} from "@/fees.js";
const s = useStore() const s = useStore()
const co = useChartOrderStore() const co = useChartOrderStore()
@@ -72,6 +74,7 @@ const defaultTranches = 10
builderDefaults(props.builder, { builderDefaults(props.builder, {
startTime: s.clock, // todo relative 0 startTime: s.clock, // todo relative 0
endTime: s.clock + defaultTranches * co.intervalSecs,
interval: co.intervalSecs, interval: co.intervalSecs,
tranches: defaultTranches, tranches: defaultTranches,
percentage: 100/defaultTranches, percentage: 100/defaultTranches,
@@ -116,6 +119,17 @@ const parts = computed({
} }
}) })
const sched = ref(null)
const schedFetcher = new SingletonCoroutine(async (vault)=>sched.value = vault === null ? null : await getFeeSchedule(vault))
const partsGasHint = computed(()=>{
if (sched.value === null) {
schedFetcher.invoke(s.vault)
return null
}
return toPrecision(Number(sched.value.gasFee) * parts.value / 1e18) + ' ETH gas fee'
})
const intervalIsTotal = ref(false) const intervalIsTotal = ref(false)
const displayedInterval = computed({ const displayedInterval = computed({
get() { get() {
@@ -165,7 +179,6 @@ const endTime = computed({
const barStart = computed(()=>ohlcStart(startTime.value, props.builder.interval)) const barStart = computed(()=>ohlcStart(startTime.value, props.builder.interval))
const barEnd = computed(()=>ohlcStart(endTime.value, props.builder.interval)) const barEnd = computed(()=>ohlcStart(endTime.value, props.builder.interval))
let shapeWidth = barEnd.value - barStart.value // NOT reactively computed. We compare in onPoints()
function emitUpdatedPoints(a, b) { function emitUpdatedPoints(a, b) {
const updates = {} const updates = {}
@@ -234,7 +247,6 @@ function emitUpdate(changes) {
const text = computed(()=>{ const text = computed(()=>{
const o = props.order const o = props.order
console.log('check text', o.buy, o.amountIsTokenA)
return allocationText(o.buy, 1, o.amount, co.selectedSymbol.base.s, return allocationText(o.buy, 1, o.amount, co.selectedSymbol.base.s,
o.amountIsTokenA ? null : co.selectedSymbol.quote.s, parts.value, '\n') o.amountIsTokenA ? null : co.selectedSymbol.quote.s, parts.value, '\n')
}) })
@@ -260,6 +272,14 @@ setProperties()
watchEffect(setProperties) watchEffect(setProperties)
watchEffect(()=>{
const curBarStart = ohlcStart(s.clock, co.intervalSecs);
if (curBarStart > barStart.value && !dragging) { // check dragging late to ensure reactivity on bar start
const delta = curBarStart - props.builder.startTime
setPoints([{time: props.builder.startTime + delta}, {time: props.builder.endTime + delta}])
}
})
function deleteShapes() { function deleteShapes() {
deleteShapeId(shapeId) deleteShapeId(shapeId)
} }
@@ -272,7 +292,7 @@ td.weight {
padding-right: 1em; padding-right: 1em;
} }
.parts { .parts {
width: 8em; width: 10em;
} }
.interval { .interval {
width: 22em; width: 22em;

View File

@@ -0,0 +1,11 @@
<template>
</template>
<script setup>
</script>
<style scoped lang="scss">
</style>

87
src/fees.js Normal file
View File

@@ -0,0 +1,87 @@
import {newContract, vaultContract} from "@/blockchain/contract.js";
import {provider} from "@/blockchain/wallet.js";
import {timestamp} from "@/common.js";
import TTLCache from "@isaacs/ttlcache";
async function getFeeManagerContract(vaultContract) {
const feeManagerAddr = await vaultContract.feeManager()
return await newContract(feeManagerAddr, 'IFeeManager', provider);
}
export async function getFeeSchedule(vaultAddr) {
if (feeSchedCache.has(vaultAddr))
return feeSchedCache.get(vaultAddr)
const vault = await vaultContract(vaultAddr, provider)
const feeManager = await getFeeManagerContract(vault);
const [sched, changeTimestamp] = await Promise.all([feeManager.fees(), feeManager.proposedFeeActivationTime()])
const changing = Number(changeTimestamp)
const newSched = !changing ? null : await feeManager.proposedFees()
// if it's not changing, we have an hour (wait 55 minutes) until another fee change could happen
// otherwise, set the TTL to be a long TTL after the changeover
const noticePeriod = 55*60
const ttl = (!changing ? noticePeriod : (changing - timestamp() + noticePeriod))*1000 // milliseconds
const schedule = new FeeSchedule(sched, newSched);
feeSchedCache.set(vaultAddr, schedule, {ttl})
return schedule
}
export async function placementFee(vaultAddr, order, window = 300) {
// If the fees are about to change within `window` seconds of now, we send the higher native amount of the two fees.
// If the fees sent are too much, the vault will refund the sender.
const vault = await vaultContract(vaultAddr, provider)
const feeManager = await getFeeManagerContract(vault);
const [sched, changeTimestamp] = await Promise.all([feeManager.fees(), feeManager.proposedFeeActivationTime()])
console.log('sched', order, sched)
// single order placement selector
const placementFeeSelector = 'placementFee((address,address,(uint8,uint24),uint256,uint256,bool,bool,bool,uint64,(uint16,bool,bool,bool,bool,bool,bool,bool,bool,uint16,uint24,uint32,uint32,(uint32,uint32),(uint32,uint32))[]),(uint8,uint8,uint8,uint8,uint8))'
let [orderFee, gasFee] = await vault[placementFeeSelector](order, [...sched])
console.log('placementFee', orderFee, gasFee)
if (Number(changeTimestamp) - timestamp() < window) {
const nextSched = await feeManager.proposedFees()
const [nextOrderFee, nextGasFee] = await vault[placementFeeSelector](order, [...nextSched])
if (nextOrderFee + nextGasFee > orderFee + gasFee)
[orderFee, gasFee] = [nextOrderFee, nextGasFee]
}
return [orderFee, gasFee]
}
function schedToDict(sched) {
if (sched===null)
return null
const [ofee, oexp, gfee, gexp, ffee] = sched
console.log('sched', ofee, oexp, gfee, gexp, ffee)
return {
orderFee: BigInt(ofee) << BigInt(oexp), // in wei
gasFee: BigInt(gfee) << BigInt(gexp), // in wei
fillFee: Number(ffee) / 200, // float coefficient
}
}
export class FeeSchedule {
constructor(sched, nextSched=null) {
// if nextSched is set, the more expensive of the two fees will be returned
this.sched = schedToDict(sched)
this.nextSched = schedToDict(nextSched)
const _max = (method, ...args) => {
const curVal = this[method](this.sched,...args);
return this.nextSched === null ? curVal : Math.max(curVal, this[method](this.nextSched,...args))
}
this.gasFee = _max( '_gasFee')
this.orderFee = _max('_orderFee')
this.fillFee = _max('_fillFee')
}
_gasFee(sched) {return sched.gasFee}
_orderFee(sched) {return sched.orderFee}
_fillFee(sched) {return sched.fillFee}
}
const feeSchedCache = new TTLCache()

View File

@@ -315,6 +315,11 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3"
integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
"@isaacs/ttlcache@^1.4.1":
version "1.4.1"
resolved "https://registry.yarnpkg.com/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz#21fb23db34e9b6220c6ba023a0118a2dd3461ea2"
integrity sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==
"@jridgewell/gen-mapping@^0.3.5": "@jridgewell/gen-mapping@^0.3.5":
version "0.3.8" version "0.3.8"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142"