vault upgrades; fees; refactoring

This commit is contained in:
Tim
2024-06-17 02:39:12 -04:00
parent fd8f20c4d4
commit 104b798d4f
15 changed files with 260 additions and 86 deletions

View File

@@ -16,9 +16,6 @@
<div id="app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.5.4/socket.io.js"></script>
<script type="module" src="/src/main.js"></script>
<!-- HubSpot -->
<script type="text/javascript" id="hs-script-loader" async defer src="//js-na1.hs-scripts.com/46028804.js"></script>
</body>
</html>

View File

@@ -1,6 +1,8 @@
<template>
<router-view/>
<router-view/>
<HubSpot/>
</template>
<script setup>
import HubSpot from "@/components/HubSpot.vue";
</script>

View File

@@ -1,5 +1,14 @@
export const factoryAbi = [
'function deployVault() returns (address payable vault)',
'function deployVault(uint8 num) returns (address payable vault)',
'function deployVault(address owner) returns (address payable vault)',
'function deployVault(address owner, uint8 num) returns (address payable vault)',
'function logic() view returns (address)',
'function upgrader() view returns (address)',
'function proposedLogicActivationTimestamp() view returns (uint32)',
'function proposedLogic() view returns (address)',
'function upgradeLogic(address newLogic)',
]
export const queryHelperAbi = [
@@ -55,11 +64,11 @@ const Tranche = `(
bool minIsBarrier,
bool maxIsBarrier,
bool marketOrder,
bool _reserved5,
bool _reserved6,
bool minIsRatio,
bool maxIsRatio,
bool _reserved7,
uint8 _reserved8,
uint32 _reserved16,
uint16 rateLimitFraction,
uint24 rateLimitPeriod,
uint32 startTime,
uint32 endTime,
uint32 minIntercept,
@@ -67,6 +76,7 @@ const Tranche = `(
uint32 maxIntercept,
uint32 maxSlope
)`
const SwapOrder = `(
address tokenIn,
address tokenOut,
@@ -75,19 +85,23 @@ const SwapOrder = `(
uint256 minFillAmount,
bool amountIsInput,
bool outputDirectlyToOwner,
uint64 chainOrder,
uint64 conditionalOrder,
${Tranche}[] tranches
)`
export const vaultAbi = [
'function version() view returns (uint8)',
'function version() pure returns (uint8)',
'function logic() view returns (address)',
'function upgrade(address)',
'function feeManager() view returns (address)',
'function withdraw(uint256 amount)',
'function withdrawTo(address payable recipient, uint256 amount)',
'function withdraw(address token, uint256 amount)',
'function withdrawTo(address token, address recipient, uint256 amount)',
'function numSwapOrders() view returns (uint64 num)',
`function placeDexorder(${SwapOrder})`,
`function placeDexorders(${SwapOrder}[], uint8 ocoMode)`,
`function placementFee(${SwapOrder} order) view returns (uint256 orderFee, uint256 gasFee)`,
`function placeDexorder(${SwapOrder}) payable`,
`function placeDexorders(${SwapOrder}[], uint8 ocoMode) payable`,
'function cancelDexorder(uint64 orderIndex)',
'function cancelAllDexorders()',
// `function swapOrderStatus(uint64 orderIndex) view returns (${SwapOrderStatus} memory status)`,

View File

@@ -2,7 +2,7 @@ import {uint32max, uint64max} from "@/misc.js";
import {decodeIEE754, encodeIEE754} from "@/common.js";
export const MAX_FRACTION = 65535;
export const NO_CHAIN = uint64max;
export const NO_CONDITIONAL_ORDER = uint64max;
export const NO_OCO = uint64max;
export const DISTANT_PAST = 0
export const DISTANT_FUTURE = uint32max
@@ -15,7 +15,7 @@ export const DISTANT_FUTURE = uint32max
// uint256 minFillAmount; // if a tranche has less than this amount available to fill, it is considered completed
// bool amountIsInput;
// bool outputDirectlyToOwner;
// uint64 chainOrder; // use NO_CHAIN for no chaining. chainOrder index must be < than this order's index for safety (written first) and chainOrder state must be Template
// uint64 conditionalOrder; // use NO_CONDITIONAL_ORDER for no chaining. conditionalOrder index must be < than this order's index for safety (written first) and conditionalOrder state must be Template
// Tranche[] tranches;
// }
// struct Route {
@@ -23,7 +23,7 @@ export const DISTANT_FUTURE = uint32max
// uint24 fee;
// }
export function newOrder(tokenIn, tokenOut, exchange, fee, amount, amountIsInput, tranches,
minFillAmount=null, outputDirectlyToOwner = false, chainOrder = NO_CHAIN) {
minFillAmount=null, outputDirectlyToOwner = false, conditionalOrder = NO_CONDITIONAL_ORDER) {
amountIsInput = !!amountIsInput // force convert to bool
outputDirectlyToOwner = !!outputDirectlyToOwner // force convert to bool
amount = BigInt(amount)
@@ -34,7 +34,7 @@ export function newOrder(tokenIn, tokenOut, exchange, fee, amount, amountIsInput
return {
tokenIn, tokenOut, route:{exchange, fee},
amount, minFillAmount, amountIsInput,
outputDirectlyToOwner, chainOrder, tranches
outputDirectlyToOwner, conditionalOrder, tranches
}
}
@@ -46,11 +46,11 @@ export function newOrder(tokenIn, tokenOut, exchange, fee, amount, amountIsInput
// bool minIsBarrier;
// bool maxIsBarrier;
// bool marketOrder; // if true, both min and max lines are ignored, and minIntercept is treated as a maximum slippage value (use positive numbers)
// bool _reserved5;
// bool _reserved6;
// bool minIsRatio;
// bool maxIsRatio;
// bool _reserved7;
// uint8 _reserved8;
// uint32 _reserved16;
// uint16 rateLimitFraction;
// uint24 rateLimitPeriod;
//
// uint32 startTime; // use DISTANT_PAST to disable
// uint32 endTime; // use DISTANT_FUTURE to disable
@@ -69,12 +69,16 @@ export function newTranche({
endTimeIsRelative = false,
endTime = DISTANT_FUTURE,
minIsBarrier = false,
minIsRatio = false,
minIntercept = 0,
slippage = 0, // may also set minIntercept instead
minSlope = 0,
maxIsBarrier = false,
maxIsRatio = false,
maxIntercept = 0,
maxSlope = 0,
rateLimitFraction = 0,
rateLimitPeriod = 0,
} = {}) {
if( minIntercept === 0 && minSlope === 0 && maxIntercept === 0 && maxSlope === 0 )
marketOrder = true
@@ -90,7 +94,7 @@ export function newTranche({
fraction: Math.min(MAX_FRACTION, Math.round(fraction)), marketOrder,
startTimeIsRelative, startTime, endTimeIsRelative, endTime,
minIsBarrier, minIntercept, minSlope, maxIsBarrier, maxIntercept, maxSlope,
_reserved5: false, _reserved6: false, _reserved7: false, _reserved8: 0, _reserved16: 0,
minIsRatio, maxIsRatio, _reserved7: false, rateLimitFraction, rateLimitPeriod,
}
}
@@ -110,10 +114,6 @@ export const OrderState = {
Error: 99,
}
export function orderIsOpen(order) {
return isOpen(order.state)
}
export function isOpen(state) {
return state >= 1 && state < 3
}
@@ -121,7 +121,7 @@ export function isOpen(state) {
export function parseOrderStatus(chainId, status) {
let [
order,
fillFeeBP,
fillFeeHalfBps,
state,
start,
ocoGroup,
@@ -129,14 +129,17 @@ export function parseOrderStatus(chainId, status) {
filledOut,
trancheFilledIn,
trancheFilledOut,
trancheActivationTime,
] = status
order = parseOrder(order)
filledIn = BigInt(filledIn)
filledOut = BigInt(filledOut)
trancheFilledIn = trancheFilledIn.map((f)=>BigInt(f))
trancheFilledOut = trancheFilledOut.map((f)=>BigInt(f))
trancheActivationTime = trancheActivationTime.map((v)=>Number(v))
return {
chainId, order, fillFeeBP, state, start, ocoGroup, filledIn, filledOut, trancheFilledIn, trancheFilledOut,
chainId, order, fillFeeHalfBps, state, start, ocoGroup,
filledIn, filledOut, trancheFilledIn, trancheFilledOut, trancheActivationTime
}
}
@@ -150,7 +153,7 @@ export function parseOrder(order) {
minFillAmount,
amountIsInput,
outputDirectlyToOwner,
chainOrder,
conditionalOrder,
tranches,
] = order
route = parseRoute(route)
@@ -158,7 +161,7 @@ export function parseOrder(order) {
minFillAmount = BigInt(minFillAmount)
tranches = tranches.map(parseTranche)
return {
tokenIn, tokenOut, route, amount, minFillAmount, amountIsInput, outputDirectlyToOwner, chainOrder, tranches
tokenIn, tokenOut, route, amount, minFillAmount, amountIsInput, outputDirectlyToOwner, conditionalOrder, tranches
}
}
@@ -175,11 +178,11 @@ export function parseTranche(tranche) {
minIsBarrier,
maxIsBarrier,
marketOrder,
_reserved5,
_reserved6,
minIsRatio,
maxIsRatio,
_reserved7,
_reserved8,
_reserved16,
rateLimitFraction,
rateLimitPeriod,
startTime,
endTime,
minIntercept,
@@ -193,6 +196,7 @@ export function parseTranche(tranche) {
maxSlope = decodeIEE754(maxSlope)
return {
fraction, startTimeIsRelative, endTimeIsRelative, minIsBarrier, maxIsBarrier, marketOrder,
minIsRatio, maxIsRatio, rateLimitFraction, rateLimitPeriod,
startTime, endTime, minIntercept, minSlope, maxIntercept, maxSlope,
}
}

View File

@@ -3,10 +3,10 @@ import {useStore} from "@/store/store";
import {socket} from "@/socket.js";
import {contractOrNull, vaultAddress} from "@/blockchain/contract.js";
import {SingletonCoroutine, uuid} from "@/misc.js";
import {vaultAbi} from "@/blockchain/abi.js";
import {factoryAbi, vaultAbi} from "@/blockchain/abi.js";
import {defineStore} from "pinia";
import {ref} from "vue";
import {metadataMap} from "@/version.js";
import {metadataMap, version} from "@/version.js";
export let provider = null
@@ -198,6 +198,7 @@ function discoverVaults(owner) {
const doDiscoverVaults = new SingletonCoroutine(_discoverVaults, 50, false)
async function _discoverVaults(owner) {
const result = []
const versions = []
const s = useStore()
if( !owner || !s.chainId || !s.account) {
s.vaults = []
@@ -205,38 +206,34 @@ async function _discoverVaults(owner) {
}
// todo multi-vault scan
// console.log('_discoverVaults',owner)
const num = 0
const addr = vaultAddress(s.factory, s.vaultInitCodeHash, owner, num)
// console.log(`vault ${num} at`, addr)
if( addr === null ) {
s.vaults = []
return
}
console.log('provider', provider)
if (!provider) {
console.log('No provider')
return // do not change whatever was already found
}
const vault = new ethers.Contract(addr, vaultAbi, provider)
let version = -1
try {
version = await vault.version();
if( Number(version) === 1 ) {
console.log(`found vault ${num} at ${addr}`)
result.push(addr)
for (let num=0; num < 1; num++) {
const num = 0
const addr = vaultAddress(s.factory, s.vaultInitCodeHash, owner, num)
// console.log(`vault ${num} at`, addr)
if (addr === null) // no more vaults
break
console.log('provider', provider)
if (!provider) {
console.log('No provider')
return // do not change whatever was already found
}
const vault = new ethers.Contract(addr, vaultAbi, provider)
try {
const version = Number(await vault.version())
console.log(`found vault #${num} v${version} at ${addr}`)
result.push(addr)
versions.push(version)
} catch (e) {
if (e.value === '0x' && e.code === 'BAD_DATA' || e.revert === null && e.code === 'CALL_EXCEPTION')
console.log(`no vault ${num} at ${addr}`)
else
console.error(`discoverVaults failed`, e)
return // do not change what was already found todo is this correct?
}
else
console.error(`bad vault version ${version}`)
}
catch (e) {
if( e.value==='0x' && e.code==='BAD_DATA' || e.revert===null && e.code==='CALL_EXCEPTION' )
console.log(`no vault ${num} at ${addr}`)
else
console.error(`discoverVaults failed`, e)
return // do not change what was already found todo is this correct?
}
if( s.account === owner ) { // double-check the account since it could have changed during our await
s.vaults = result
s.vaultVersions = versions
if( useWalletStore().pendingOrders.length ) {
if (result.length)
flushOrders(result[0])
@@ -287,7 +284,6 @@ async function doEnsureVault(chainId, owner, num) {
socket.emit('ensureVault', chainId, owner, num)
}
}
// await sleep(5000) // prevent this process from running more than once every 5 seconds
const ensureVaultRoutine = new SingletonCoroutine(doEnsureVault, 100)
@@ -300,18 +296,27 @@ export const PendingOrderState = {
}
export async function pendOrder(order) {
export async function placementFee(vault, order) {
const v = new ethers.Contract(vault, vaultAbi, provider)
const [orderFee, gasFee] = await v.placementFee(order)
console.log('computed fees', orderFee, gasFee)
return [orderFee, gasFee]
}
export async function pendOrder(order, fee=null) {
const s = useStore()
const pend = {
id: uuid(),
chainId: s.chainId,
placementTime: Date.now()/1000,
fee: fee,
vault: s.vaults.length ? s.vaults[0] : null,
state: PendingOrderState.Submitted,
order
};
useWalletStore().pendingOrders.splice(0,0, pend)
console.log('pending order', pend.id, JSON.stringify(order))
console.log('pended order', pend.id, JSON.stringify(order))
ensureVault()
}
@@ -378,8 +383,12 @@ function pendOrderAsTransaction(pend) {
return null
}
}
if (pend.fee === null) {
const [orderFee, gasFee] = await placementFee(pend.vault, pend.order)
pend.fee = orderFee + gasFee
}
console.log('placing order', pend.id, pend.order)
const tx = await contract.placeDexorder(pend.order)
const tx = await contract.placeDexorder(pend.order, {value:pend.fee})
pend.tx = tx
pend.state = PendingOrderState.Sent
console.log(`order ${pend.id} sent transaction`, tx)
@@ -400,7 +409,7 @@ function pendOrderAsTransaction(pend) {
})
}
0
export function pendTransaction(sender, errHandler) {
const s = useStore()
s.transactionSenders.push([sender,errHandler])
@@ -462,3 +471,60 @@ function doSendTransaction(sender, signer, errHandler) {
}
})
}
export async function detectUpgrade() {
if (!provider) {
console.log('no provider!')
return 0
}
const s = useStore()
if (!s.vault) {
console.log('no vault logged in')
return 0
}
const info = version.chainInfo[s.chainId]
if (!info) {
console.log(`couldn't get chainInfo for ${s.chainId}`)
return 0
}
try {
console.log('factory', info.factory)
const factory = new ethers.Contract(info.factory, factoryAbi, provider)
const vault = new ethers.Contract(s.vault, vaultAbi, provider)
const vaultLogic = await vault.logic()
const latestLogic = await factory.logic()
// const [vaultLogic, latestLogic] = await Promise.all( vault.logic(), factory.logic() )
console.log('vaultLogic / latestLogic', vaultLogic, latestLogic)
if ( vaultLogic !== latestLogic ) {
s.upgrade = latestLogic
const logic = new ethers.Contract(latestLogic, vaultAbi, provider)
const version = await logic.version()
console.log(`found vault version ${version}`)
return version
}
}
catch (e) {
console.error('ignorable error while querying for an upgrade', e)
}
return 0
}
function upgradeSender(vault, logic) {
return async function (signer) {
const v = new ethers.Contract(vault, vaultAbi, signer)
v.upgrade(logic)
}
}
function upgradeError(e) {
console.error('error while upgrading vault', e)
}
export async function upgradeVault(vault, logic) {
pendTransaction(upgradeSender(vault, logic), upgradeError)
}

View File

@@ -359,7 +359,7 @@ function doHandleDrawingEvent(id, event) {
if (id in shapeCallbacks) {
invokeCallbacks(shapeCallbacks[id], 'onHide', id, shape)
}
} else if (event === 'show') {
} else if (event === 'showDialog') {
if (id in shapeCallbacks) {
invokeCallbacks(shapeCallbacks[id], 'onShow', id, shape)
}

View File

@@ -0,0 +1,17 @@
<template>
</template>
<script setup>
const importUrl = 'https://js-na1.hs-scripts.com/46028804.js'
const viteWsUrl = import.meta.env.VITE_WS_URL;
if (viteWsUrl.indexOf('localhost') === -1) {
console.log('loading HubSpot')
let script = document.createElement('script')
script.setAttribute('src', importUrl)
document.head.appendChild(script)
}
</script>

View File

@@ -43,7 +43,7 @@ function placeOrder() {
const route = os.route
const amt = FixedNumber.fromString(os.totalAmount.toString(), {decimals: os.amountToken.decimals}).value
const ts = props.tranches()
const order = newOrder(tokenIn, tokenOut, route.exchange, route.fee, amt, os.amountIsInput, ts) // todo: minAmount, outputToOwner, chainOrder
const order = newOrder(tokenIn, tokenOut, route.exchange, route.fee, amt, os.amountIsInput, ts) // todo: minAmount, outputToOwner, conditionalOrder
pendOrder(order)
route('Status')
}

View File

@@ -115,6 +115,7 @@
</suspense>
</td>
<td class="d-flex align-center text-left">
<!-- todo describe rate limits -->
<div class="text-right">
<div class="mx-3">{{ describeTrancheTime(item, true, t) }}</div>
<div class="mx-3">{{ describeTrancheTime(item, false, t) }}</div>
@@ -160,7 +161,6 @@ const s = useStore()
const ws = useWalletStore()
const props = defineProps(['vault'])
const vaultAddr = computed(()=>props.vault?props.vault:s.vault)
const inverted = ref({})
const datatableHeaders = [
@@ -191,11 +191,11 @@ const orders = computed(()=>{
// false, min is barrier
// false, max is barrier
// false, market order
// false, reserved
// false, reserved
// false, reserved
// 0, reserved
// 0, reserved
// 0, minIsRatio
// 0, maxIsRatio
// false, _reserved7
// 0, rateLimitFraction
// 0, rateLimitPeriod
// 0, start time
// 20, end time
// 730643660, min intercept

View File

@@ -0,0 +1,72 @@
<template>
<div v-if="upgradeVersion>0">
<v-alert type="info" closable rounded="0">
Vault Upgrade Available!
<v-btn @click="showDialog=true" size="small">
Upgrade Now
</v-btn>
</v-alert>
<v-dialog v-model="showDialog">
<v-card title="Upgrade Vault" width="25em" class="mx-auto">
<v-card-text>An upgrade to version {{upgradeVersion}} is available for your approval.</v-card-text>
<v-card-text><v-icon icon="mdi-information" color="yellow"/> Any currently open orders will continue to execute normally.</v-card-text>
<v-card-actions class="d-flex justify-center">
<v-btn color='primary' @click="doUpgrade">Upgrade Vault</v-btn>
<v-btn @click="showDialog=false">Cancel</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-alert v-if="upgradeResult==='SUCCESS'" type="info" closable rounded="0">
Vault Upgraded Successfully!
</v-alert>
<v-alert v-if="upgradeResult && upgradeResult!=='SUCCESS'" type="warning" closable rounded="0">
Vault upgrade failed! ({{upgradeResult}})
</v-alert>
</div>
</template>
<script setup>
import {ref} from "vue";
import {upgradeVault, detectUpgrade} from "@/blockchain/wallet.js";
import {useStore} from "@/store/store.js";
const s = useStore()
const upgradeVersion = ref(0)
const showDialog = ref(false)
const upgradeResult = ref(null)
detectUpgrade().then((version)=>{
if (version===0) {
console.log('Vault is the latest version')
}
else {
upgradeVersion.value = version
console.log(`Vault upgrade available to version ${version}`)
}
})
async function doUpgrade() {
showDialog.value = false
if (!s.upgrade) {
console.error('no upgrade available')
return
}
const vault = s.vault
if (!vault) {
console.error('no vault logged in')
return
}
upgradeVault(vault, s.upgrade).then(()=>{
upgradeVersion.value = 0
console.log('vault upgraded')
upgradeResult.value = 'SUCCESS'
}).catch((reason)=>{
upgradeResult.value = reason
})
}
</script>
<style scoped lang="scss">
</style>

View File

@@ -108,7 +108,7 @@ function buildOrder() {
// uint256 minFillAmount; // if a tranche has less than this amount available to fill, it is considered completed
// bool amountIsInput;
// bool outputDirectlyToOwner;
// uint64 chainOrder; // use NO_CHAIN for no chaining. chainOrder index must be < than this order's index for safety (written first) and chainOrder state must be Template
// uint64 conditionalOrder; // use NO_CONDITIONAL_ORDER for no chaining. conditionalOrder index must be < than this order's index for safety (written first) and conditionalOrder state must be Template
// Tranche[] tranches;
// }
const symbol = co.selectedSymbol

View File

@@ -303,9 +303,7 @@ const _extendLeft = ref(false)
const extendLeft = computed({
get() {return _extendLeft.value},
set(v) {
console.log('set extendLeft', v)
if (v !== _extendLeft.value) {
console.log('DO set extendLeft')
_extendLeft.value = v;
const b = {...props.builder}
b.extendLeft = v

View File

@@ -3,6 +3,7 @@
<toolbar :title="title" :icon="icon">
<slot name="toolbar"/>
</toolbar>
<upgrade-alert/>
<div class="overflow-y-auto">
<slot/>
</div>
@@ -12,6 +13,7 @@
<script setup>
import Toolbar from "@/components/chart/Toolbar.vue";
import UpgradeAlert from "@/components/UpgradeAlert.vue";
const props = defineProps(['title', 'icon'])

View File

@@ -1,7 +1,5 @@
<template>
<v-main>
<v-alert text="TESTNET FAKE COINS&nbsp;&nbsp;&nbsp;&nbsp;• UNI • ARB • WETH • WBTC • LINK • USDC •"
color="red" :closable="false" rounded="0" height="0"/>
<Alerts/>
<chart-view/>
</v-main>

View File

@@ -2,7 +2,7 @@
import {defineStore} from 'pinia'
import {knownTokens} from "@/knownTokens.js";
import {computed, ref} from "vue";
import {version} from "@/version.js";
import {version as versionMeta} from "@/version.js";
// USING THE STORE:
//
@@ -42,8 +42,8 @@ export const useStore = defineStore('app', ()=> {
const nav = ref(false) // controls opening navigation drawer
const _chainId = ref(Number(Object.keys(version.chainInfo)[0]))
const _chainInfo = ref(version.chainInfo)
const _chainId = ref(Number(Object.keys(versionMeta.chainInfo)[0]))
const _chainInfo = ref(versionMeta.chainInfo)
function getTokenList() {
const chains = _chainId.value in _chainInfo.value && _chainInfo.value[_chainId.value].tokens !== undefined ?
@@ -88,6 +88,7 @@ export const useStore = defineStore('app', ()=> {
const vaultInitCodeHash = computed(() => !chain.value ? null : chain.value.vaultInitCodeHash)
const account = ref(null)
const vaults = ref([])
const vaultVersions = ref([])
const transactionSenders = ref([]) // a list of function(signer) that send transactions
const errors = ref([])
const extraTokens = ref({})
@@ -96,6 +97,8 @@ export const useStore = defineStore('app', ()=> {
const orders = ref({}) // indexed by vault value is another dictionary with orderIndex as key and order status values
const vault = computed(() => vaults.value.length === 0 ? null : vaults.value[0] )
const upgrade = ref(null)
const version = computed( () => vaultVersions.value.length === 0 ? 0 : vaultVersions.value[0] )
const balances = computed( () => vault.value === null ? {} : vaultBalances.value[vault.value] || {} )
const vaultOrders = computed(()=> vault.value === null || (!vault.value in orders.value) ? {} : orders.value[vault.value] ? orders.value[vault.value] : [] )
const tokens = computed(getTokens)
@@ -126,8 +129,9 @@ export const useStore = defineStore('app', ()=> {
}
return {
nav, chainId, chainInfo, chain, provider, providerRef, vaultInitCodeHash, account, vaults, transactionSenders,
errors, extraTokens, poolPrices, vaultBalances, orders, vault, vaultOrders, tokens, factory, helper,
nav, chainId, chainInfo, chain, provider, providerRef, vaultInitCodeHash, account, vaults, vaultVersions,
transactionSenders, errors, extraTokens, poolPrices, vaultBalances, orders, vault, version, upgrade, vaultOrders,
tokens, factory, helper,
mockenv, mockCoins,
removeTransactionSender, error, closeError, addToken, clock, timeZone, balances,
}