Compare commits
54 Commits
0fdc45a031
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| ed03740852 | |||
| a3c1dfad2d | |||
| 9b410ace09 | |||
| 72b061749d | |||
| a42f228984 | |||
| 47b33a152d | |||
| ecffd976ac | |||
| f0f431b34e | |||
| b2457f0617 | |||
| 148b37dca3 | |||
| 1b83dad6b3 | |||
| a1bbb17b5d | |||
| 916c23e092 | |||
| 2c5116c5de | |||
| 441a514acc | |||
| 6f7388bb10 | |||
| fd7b9713ea | |||
| 7b5421e6e7 | |||
| 21f324aa12 | |||
| eeee9d9853 | |||
| 38fb66c694 | |||
| 14b8b50812 | |||
| f35b30e337 | |||
| 22f2e648a2 | |||
| 7973a1e8b7 | |||
| c50824adb6 | |||
| 257c476cc1 | |||
| 815109dec2 | |||
| 0673b01ac8 | |||
| 556554fbf3 | |||
| 94c7b6ddb4 | |||
| 715a43c097 | |||
| 0442b08623 | |||
| 0392e70b78 | |||
| a6bce1613b | |||
| 7626504480 | |||
| e86fbfa8e9 | |||
| 7d04d23a89 | |||
| dabf6dd60f | |||
| b1a864ce31 | |||
| 75a197947f | |||
| d446d5ab11 | |||
| 5a4a67e726 | |||
| 8750951de5 | |||
| cd84e7c3c9 | |||
| 5876efe29f | |||
| b9975cda10 | |||
| 826177c445 | |||
| 3baa74174d | |||
| b2ed48492b | |||
| ebf70dd10c | |||
| 488e9f45f1 | |||
| f5f53c6af4 | |||
| 2e49346533 |
395
.dependency-cruiser.cjs
Normal file
@@ -0,0 +1,395 @@
|
|||||||
|
/** @type {import('dependency-cruiser').IConfiguration} */
|
||||||
|
module.exports = {
|
||||||
|
forbidden: [
|
||||||
|
{
|
||||||
|
name: 'no-circular',
|
||||||
|
severity: 'warn',
|
||||||
|
comment:
|
||||||
|
'This dependency is part of a circular relationship. You might want to revise ' +
|
||||||
|
'your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ',
|
||||||
|
from: {},
|
||||||
|
to: {
|
||||||
|
circular: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'no-orphans',
|
||||||
|
comment:
|
||||||
|
"This is an orphan module - it's likely not used (anymore?). Either use it or " +
|
||||||
|
"remove it. If it's logical this module is an orphan (i.e. it's a config file), " +
|
||||||
|
"add an exception for it in your dependency-cruiser configuration. By default " +
|
||||||
|
"this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration " +
|
||||||
|
"files (.d.ts), tsconfig.json and some of the babel and webpack configs.",
|
||||||
|
severity: 'warn',
|
||||||
|
from: {
|
||||||
|
orphan: true,
|
||||||
|
pathNot: [
|
||||||
|
'(^|/)[.][^/]+[.](?:js|cjs|mjs|ts|cts|mts|json)$', // dot files
|
||||||
|
'[.]d[.]ts$', // TypeScript declaration files
|
||||||
|
'(^|/)tsconfig[.]json$', // TypeScript config
|
||||||
|
'(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$' // other configs
|
||||||
|
]
|
||||||
|
},
|
||||||
|
to: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'no-deprecated-core',
|
||||||
|
comment:
|
||||||
|
'A module depends on a node core module that has been deprecated. Find an alternative - these are ' +
|
||||||
|
"bound to exist - node doesn't deprecate lightly.",
|
||||||
|
severity: 'warn',
|
||||||
|
from: {},
|
||||||
|
to: {
|
||||||
|
dependencyTypes: [
|
||||||
|
'core'
|
||||||
|
],
|
||||||
|
path: [
|
||||||
|
'^v8/tools/codemap$',
|
||||||
|
'^v8/tools/consarray$',
|
||||||
|
'^v8/tools/csvparser$',
|
||||||
|
'^v8/tools/logreader$',
|
||||||
|
'^v8/tools/profile_view$',
|
||||||
|
'^v8/tools/profile$',
|
||||||
|
'^v8/tools/SourceMap$',
|
||||||
|
'^v8/tools/splaytree$',
|
||||||
|
'^v8/tools/tickprocessor-driver$',
|
||||||
|
'^v8/tools/tickprocessor$',
|
||||||
|
'^node-inspect/lib/_inspect$',
|
||||||
|
'^node-inspect/lib/internal/inspect_client$',
|
||||||
|
'^node-inspect/lib/internal/inspect_repl$',
|
||||||
|
'^async_hooks$',
|
||||||
|
'^punycode$',
|
||||||
|
'^domain$',
|
||||||
|
'^constants$',
|
||||||
|
'^sys$',
|
||||||
|
'^_linklist$',
|
||||||
|
'^_stream_wrap$'
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'not-to-deprecated',
|
||||||
|
comment:
|
||||||
|
'This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later ' +
|
||||||
|
'version of that module, or find an alternative. Deprecated modules are a security risk.',
|
||||||
|
severity: 'warn',
|
||||||
|
from: {},
|
||||||
|
to: {
|
||||||
|
dependencyTypes: [
|
||||||
|
'deprecated'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'no-non-package-json',
|
||||||
|
severity: 'error',
|
||||||
|
comment:
|
||||||
|
"This module depends on an npm package that isn't in the 'dependencies' section of your package.json. " +
|
||||||
|
"That's problematic as the package either (1) won't be available on live (2 - worse) will be " +
|
||||||
|
"available on live with an non-guaranteed version. Fix it by adding the package to the dependencies " +
|
||||||
|
"in your package.json.",
|
||||||
|
from: {},
|
||||||
|
to: {
|
||||||
|
dependencyTypes: [
|
||||||
|
'npm-no-pkg',
|
||||||
|
'npm-unknown'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'not-to-unresolvable',
|
||||||
|
comment:
|
||||||
|
"This module depends on a module that cannot be found ('resolved to disk'). If it's an npm " +
|
||||||
|
'module: add it to your package.json. In all other cases you likely already know what to do.',
|
||||||
|
severity: 'error',
|
||||||
|
from: {},
|
||||||
|
to: {
|
||||||
|
couldNotResolve: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'no-duplicate-dep-types',
|
||||||
|
comment:
|
||||||
|
"Likely this module depends on an external ('npm') package that occurs more than once " +
|
||||||
|
"in your package.json i.e. bot as a devDependencies and in dependencies. This will cause " +
|
||||||
|
"maintenance problems later on.",
|
||||||
|
severity: 'warn',
|
||||||
|
from: {},
|
||||||
|
to: {
|
||||||
|
moreThanOneDependencyType: true,
|
||||||
|
// as it's pretty common to have a type import be a type only import
|
||||||
|
// _and_ (e.g.) a devDependency - don't consider type-only dependency
|
||||||
|
// types for this rule
|
||||||
|
dependencyTypesNot: ["type-only"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/* rules you might want to tweak for your specific situation: */
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'not-to-spec',
|
||||||
|
comment:
|
||||||
|
'This module depends on a spec (test) file. The sole responsibility of a spec file is to test code. ' +
|
||||||
|
"If there's something in a spec that's of use to other modules, it doesn't have that single " +
|
||||||
|
'responsibility anymore. Factor it out into (e.g.) a separate utility/ helper or a mock.',
|
||||||
|
severity: 'error',
|
||||||
|
from: {},
|
||||||
|
to: {
|
||||||
|
path: '[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'not-to-dev-dep',
|
||||||
|
severity: 'error',
|
||||||
|
comment:
|
||||||
|
"This module depends on an npm package from the 'devDependencies' section of your " +
|
||||||
|
'package.json. It looks like something that ships to production, though. To prevent problems ' +
|
||||||
|
"with npm packages that aren't there on production declare it (only!) in the 'dependencies'" +
|
||||||
|
'section of your package.json. If this module is development only - add it to the ' +
|
||||||
|
'from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration',
|
||||||
|
from: {
|
||||||
|
path: '^(src)',
|
||||||
|
pathNot: '[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$'
|
||||||
|
},
|
||||||
|
to: {
|
||||||
|
dependencyTypes: [
|
||||||
|
'npm-dev',
|
||||||
|
],
|
||||||
|
// type only dependencies are not a problem as they don't end up in the
|
||||||
|
// production code or are ignored by the runtime.
|
||||||
|
dependencyTypesNot: [
|
||||||
|
'type-only'
|
||||||
|
],
|
||||||
|
pathNot: [
|
||||||
|
'node_modules/@types/'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'optional-deps-used',
|
||||||
|
severity: 'info',
|
||||||
|
comment:
|
||||||
|
"This module depends on an npm package that is declared as an optional dependency " +
|
||||||
|
"in your package.json. As this makes sense in limited situations only, it's flagged here. " +
|
||||||
|
"If you're using an optional dependency here by design - add an exception to your" +
|
||||||
|
"dependency-cruiser configuration.",
|
||||||
|
from: {},
|
||||||
|
to: {
|
||||||
|
dependencyTypes: [
|
||||||
|
'npm-optional'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'peer-deps-used',
|
||||||
|
comment:
|
||||||
|
"This module depends on an npm package that is declared as a peer dependency " +
|
||||||
|
"in your package.json. This makes sense if your package is e.g. a plugin, but in " +
|
||||||
|
"other cases - maybe not so much. If the use of a peer dependency is intentional " +
|
||||||
|
"add an exception to your dependency-cruiser configuration.",
|
||||||
|
severity: 'warn',
|
||||||
|
from: {},
|
||||||
|
to: {
|
||||||
|
dependencyTypes: [
|
||||||
|
'npm-peer'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
options: {
|
||||||
|
|
||||||
|
/* Which modules not to follow further when encountered */
|
||||||
|
doNotFollow: {
|
||||||
|
/* path: an array of regular expressions in strings to match against */
|
||||||
|
path: ['node_modules']
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Which modules to exclude */
|
||||||
|
// exclude : {
|
||||||
|
// /* path: an array of regular expressions in strings to match against */
|
||||||
|
// path: '',
|
||||||
|
// },
|
||||||
|
|
||||||
|
/* Which modules to exclusively include (array of regular expressions in strings)
|
||||||
|
dependency-cruiser will skip everything not matching this pattern
|
||||||
|
*/
|
||||||
|
// includeOnly : [''],
|
||||||
|
|
||||||
|
/* List of module systems to cruise.
|
||||||
|
When left out dependency-cruiser will fall back to the list of _all_
|
||||||
|
module systems it knows of. It's the default because it's the safe option
|
||||||
|
It might come at a performance penalty, though.
|
||||||
|
moduleSystems: ['amd', 'cjs', 'es6', 'tsd']
|
||||||
|
|
||||||
|
As in practice only commonjs ('cjs') and ecmascript modules ('es6')
|
||||||
|
are widely used, you can limit the moduleSystems to those.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// moduleSystems: ['cjs', 'es6'],
|
||||||
|
|
||||||
|
/*
|
||||||
|
false: don't look at JSDoc imports (the default)
|
||||||
|
true: dependency-cruiser will detect dependencies in JSDoc-style
|
||||||
|
import statements. Implies "parser": "tsc", so the dependency-cruiser
|
||||||
|
will use the typescript parser for JavaScript files.
|
||||||
|
|
||||||
|
For this to work the typescript compiler will need to be installed in the
|
||||||
|
same spot as you're running dependency-cruiser from.
|
||||||
|
*/
|
||||||
|
// detectJSDocImports: true,
|
||||||
|
|
||||||
|
/* prefix for links in html and svg output (e.g. 'https://github.com/you/yourrepo/blob/main/'
|
||||||
|
to open it on your online repo or `vscode://file/${process.cwd()}/` to
|
||||||
|
open it in visual studio code),
|
||||||
|
*/
|
||||||
|
// prefix: `vscode://file/${process.cwd()}/`,
|
||||||
|
|
||||||
|
/* false (the default): ignore dependencies that only exist before typescript-to-javascript compilation
|
||||||
|
true: also detect dependencies that only exist before typescript-to-javascript compilation
|
||||||
|
"specify": for each dependency identify whether it only exists before compilation or also after
|
||||||
|
*/
|
||||||
|
// tsPreCompilationDeps: false,
|
||||||
|
|
||||||
|
/* list of extensions to scan that aren't javascript or compile-to-javascript.
|
||||||
|
Empty by default. Only put extensions in here that you want to take into
|
||||||
|
account that are _not_ parsable.
|
||||||
|
*/
|
||||||
|
// extraExtensionsToScan: [".json", ".jpg", ".png", ".svg", ".webp"],
|
||||||
|
|
||||||
|
/* if true combines the package.jsons found from the module up to the base
|
||||||
|
folder the cruise is initiated from. Useful for how (some) mono-repos
|
||||||
|
manage dependencies & dependency definitions.
|
||||||
|
*/
|
||||||
|
// combinedDependencies: false,
|
||||||
|
|
||||||
|
/* if true leave symlinks untouched, otherwise use the realpath */
|
||||||
|
// preserveSymlinks: false,
|
||||||
|
|
||||||
|
/* TypeScript project file ('tsconfig.json') to use for
|
||||||
|
(1) compilation and
|
||||||
|
(2) resolution (e.g. with the paths property)
|
||||||
|
|
||||||
|
The (optional) fileName attribute specifies which file to take (relative to
|
||||||
|
dependency-cruiser's current working directory). When not provided
|
||||||
|
defaults to './tsconfig.json'.
|
||||||
|
*/
|
||||||
|
tsConfig: {
|
||||||
|
fileName: 'jsconfig.json'
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Webpack configuration to use to get resolve options from.
|
||||||
|
|
||||||
|
The (optional) fileName attribute specifies which file to take (relative
|
||||||
|
to dependency-cruiser's current working directory. When not provided defaults
|
||||||
|
to './webpack.conf.js'.
|
||||||
|
|
||||||
|
The (optional) `env` and `arguments` attributes contain the parameters
|
||||||
|
to be passed if your webpack config is a function and takes them (see
|
||||||
|
webpack documentation for details)
|
||||||
|
*/
|
||||||
|
// webpackConfig: {
|
||||||
|
// fileName: 'webpack.config.js',
|
||||||
|
// env: {},
|
||||||
|
// arguments: {}
|
||||||
|
// },
|
||||||
|
|
||||||
|
/* Babel config ('.babelrc', '.babelrc.json', '.babelrc.json5', ...) to use
|
||||||
|
for compilation
|
||||||
|
*/
|
||||||
|
// babelConfig: {
|
||||||
|
// fileName: '.babelrc',
|
||||||
|
// },
|
||||||
|
|
||||||
|
/* List of strings you have in use in addition to cjs/ es6 requires
|
||||||
|
& imports to declare module dependencies. Use this e.g. if you've
|
||||||
|
re-declared require, use a require-wrapper or use window.require as
|
||||||
|
a hack.
|
||||||
|
*/
|
||||||
|
// exoticRequireStrings: [],
|
||||||
|
|
||||||
|
/* options to pass on to enhanced-resolve, the package dependency-cruiser
|
||||||
|
uses to resolve module references to disk. The values below should be
|
||||||
|
suitable for most situations
|
||||||
|
|
||||||
|
If you use webpack: you can also set these in webpack.conf.js. The set
|
||||||
|
there will override the ones specified here.
|
||||||
|
*/
|
||||||
|
enhancedResolveOptions: {
|
||||||
|
/* What to consider as an 'exports' field in package.jsons */
|
||||||
|
exportsFields: ["exports"],
|
||||||
|
/* List of conditions to check for in the exports field.
|
||||||
|
Only works when the 'exportsFields' array is non-empty.
|
||||||
|
*/
|
||||||
|
conditionNames: ["import", "require", "node", "default", "types"],
|
||||||
|
/* The extensions, by default are the same as the ones dependency-cruiser
|
||||||
|
can access (run `npx depcruise --info` to see which ones that are in
|
||||||
|
_your_ environment). If that list is larger than you need you can pass
|
||||||
|
the extensions you actually use (e.g. [".js", ".jsx"]). This can speed
|
||||||
|
up module resolution, which is the most expensive step.
|
||||||
|
*/
|
||||||
|
// extensions: [".js", ".jsx", ".ts", ".tsx", ".d.ts"],
|
||||||
|
/* What to consider a 'main' field in package.json */
|
||||||
|
mainFields: ["module", "main", "types", "typings"],
|
||||||
|
/* A list of alias fields in package.jsons
|
||||||
|
|
||||||
|
See [this specification](https://github.com/defunctzombie/package-browser-field-spec) and
|
||||||
|
the webpack [resolve.alias](https://webpack.js.org/configuration/resolve/#resolvealiasfields)
|
||||||
|
documentation.
|
||||||
|
|
||||||
|
Defaults to an empty array (= don't use alias fields).
|
||||||
|
*/
|
||||||
|
// aliasFields: ["browser"],
|
||||||
|
},
|
||||||
|
|
||||||
|
/* skipAnalysisNotInRules will make dependency-cruiser execute
|
||||||
|
analysis strictly necessary for checking the rule set only.
|
||||||
|
|
||||||
|
See https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#skipanalysisnotinrules
|
||||||
|
for details
|
||||||
|
*/
|
||||||
|
skipAnalysisNotInRules: true,
|
||||||
|
|
||||||
|
reporterOptions: {
|
||||||
|
dot: {
|
||||||
|
/* pattern of modules that can be consolidated in the detailed
|
||||||
|
graphical dependency graph. The default pattern in this configuration
|
||||||
|
collapses everything in node_modules to one folder deep so you see
|
||||||
|
the external modules, but their innards.
|
||||||
|
*/
|
||||||
|
collapsePattern: 'node_modules/(?:@[^/]+/[^/]+|[^/]+)',
|
||||||
|
|
||||||
|
/* Options to tweak the appearance of your graph.See
|
||||||
|
https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#reporteroptions
|
||||||
|
for details and some examples. If you don't specify a theme
|
||||||
|
dependency-cruiser falls back to a built-in one.
|
||||||
|
*/
|
||||||
|
// theme: {
|
||||||
|
// graph: {
|
||||||
|
// /* splines: "ortho" gives straight lines, but is slow on big graphs
|
||||||
|
// splines: "true" gives bezier curves (fast, not as nice as ortho)
|
||||||
|
// */
|
||||||
|
// splines: "true"
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
archi: {
|
||||||
|
/* pattern of modules that can be consolidated in the high level
|
||||||
|
graphical dependency graph. If you use the high level graphical
|
||||||
|
dependency graph reporter (`archi`) you probably want to tweak
|
||||||
|
this collapsePattern to your situation.
|
||||||
|
*/
|
||||||
|
collapsePattern: '^(?:packages|src|lib(s?)|app(s?)|bin|test(s?)|spec(s?))/[^/]+|node_modules/(?:@[^/]+/[^/]+|[^/]+)',
|
||||||
|
|
||||||
|
/* Options to tweak the appearance of your graph. If you don't specify a
|
||||||
|
theme for 'archi' dependency-cruiser will use the one specified in the
|
||||||
|
dot section above and otherwise use the default one.
|
||||||
|
*/
|
||||||
|
// theme: { },
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"highlightFocused": true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// generated: dependency-cruiser@16.10.1 on 2025-04-24T20:50:32.854Z
|
||||||
@@ -1 +1,2 @@
|
|||||||
VITE_WS_URL=wss://ws.dexorder.trade
|
VITE_WS_URL=wss://ws.dexorder.com
|
||||||
|
VITE_SHARE_URL=https://app.dexorder.com
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
VITE_WS_URL=ws://localhost:3001
|
VITE_WS_URL=ws://localhost:3001
|
||||||
REQUIRE_AUTH=NOAUTH
|
VITE_SHARE_URL=http://localhost:3001
|
||||||
|
VITE_REQUIRE_APPROVAL=NO
|
||||||
|
|||||||
2
bin/depcruise
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
npx depcruise src
|
||||||
@@ -12,7 +12,16 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Orbitron&family=Saira+Semi+Condensed&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Orbitron&family=Saira+Semi+Condensed&display=swap" rel="stylesheet">
|
||||||
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<!-- Google tag (gtag.js) -->
|
||||||
|
<script async src="https://www.googletagmanager.com/gtag/js?id=G-L6F3Z6SBC7"></script>
|
||||||
|
<script>
|
||||||
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
function gtag(){dataLayer.push(arguments);}
|
||||||
|
gtag('js', new Date());
|
||||||
|
gtag('config', 'G-L6F3Z6SBC7');
|
||||||
|
</script>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.5.4/socket.io.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.5.4/socket.io.js"></script>
|
||||||
<script src="/charting_library/charting_library.js"></script>
|
<script src="/charting_library/charting_library.js"></script>
|
||||||
|
|||||||
@@ -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",
|
||||||
@@ -24,6 +25,7 @@
|
|||||||
"pinia-plugin-persistedstate": "^4.1.3",
|
"pinia-plugin-persistedstate": "^4.1.3",
|
||||||
"roboto-fontface": "*",
|
"roboto-fontface": "*",
|
||||||
"socket.io-client": "^4.7.2",
|
"socket.io-client": "^4.7.2",
|
||||||
|
"uuid": "^11.1.0",
|
||||||
"vue": "^3.2.0",
|
"vue": "^3.2.0",
|
||||||
"vue-router": "^4.0.0",
|
"vue-router": "^4.0.0",
|
||||||
"vue-scroll-picker": "^1.2.2",
|
"vue-scroll-picker": "^1.2.2",
|
||||||
@@ -32,6 +34,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^4.0.0",
|
"@vitejs/plugin-vue": "^4.0.0",
|
||||||
|
"dependency-cruiser": "^16.10.1",
|
||||||
"eslint": "^8.37.0",
|
"eslint": "^8.37.0",
|
||||||
"eslint-plugin-vue": "^9.3.0",
|
"eslint-plugin-vue": "^9.3.0",
|
||||||
"sass": "^1.60.0",
|
"sass": "^1.60.0",
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
BIN
public/logo/ico_black_clip.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
public/logo/ico_black_fill.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
public/logo/ico_green_clip.png
Normal file
|
After Width: | Height: | Size: 95 KiB |
BIN
public/logo/ico_green_fill.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
public/logo/ico_white_clip.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
public/logo/ico_white_fill.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
11
public/uniswap-logo.svg
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<svg width="641" height="640" viewBox="0 0 641 640" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M224.534 123.226C218.692 122.32 218.445 122.213 221.195 121.791C226.464 120.98 238.905 122.085 247.479 124.123C267.494 128.881 285.707 141.069 305.148 162.714L310.313 168.465L317.701 167.277C348.828 162.275 380.493 166.25 406.978 178.485C414.264 181.851 425.752 188.552 427.187 190.274C427.645 190.822 428.485 194.355 429.053 198.124C431.02 211.164 430.036 221.16 426.047 228.625C423.877 232.688 423.756 233.975 425.215 237.452C426.38 240.227 429.627 242.28 432.843 242.276C439.425 242.267 446.509 231.627 449.791 216.823L451.095 210.943L453.678 213.868C467.846 229.92 478.974 251.811 480.885 267.393L481.383 271.455L479.002 267.762C474.903 261.407 470.785 257.08 465.512 253.591C456.006 247.301 445.955 245.161 419.337 243.758C395.296 242.491 381.69 240.438 368.198 236.038C345.244 228.554 333.672 218.587 306.405 182.812C294.294 166.923 286.808 158.131 279.362 151.051C262.442 134.964 245.816 126.527 224.534 123.226Z" fill="#FF007A"/>
|
||||||
|
<path d="M432.61 158.704C433.215 148.057 434.659 141.033 437.562 134.62C438.711 132.081 439.788 130.003 439.954 130.003C440.12 130.003 439.621 131.877 438.844 134.167C436.733 140.392 436.387 148.905 437.84 158.811C439.686 171.379 440.735 173.192 454.019 186.769C460.25 193.137 467.497 201.168 470.124 204.616L474.901 210.886L470.124 206.405C464.282 200.926 450.847 190.24 447.879 188.712C445.89 187.688 445.594 187.705 444.366 188.927C443.235 190.053 442.997 191.744 442.84 199.741C442.596 212.204 440.897 220.204 436.797 228.203C434.58 232.529 434.23 231.606 436.237 226.723C437.735 223.077 437.887 221.474 437.876 209.408C437.853 185.167 434.975 179.339 418.097 169.355C413.821 166.826 406.776 163.178 402.442 161.249C398.107 159.32 394.664 157.639 394.789 157.514C395.267 157.038 411.727 161.842 418.352 164.39C428.206 168.181 429.833 168.672 431.03 168.215C431.832 167.909 432.22 165.572 432.61 158.704Z" fill="#FF007A"/>
|
||||||
|
<path d="M235.883 200.175C224.022 183.846 216.684 158.809 218.272 140.093L218.764 134.301L221.463 134.794C226.534 135.719 235.275 138.973 239.369 141.459C250.602 148.281 255.465 157.263 260.413 180.328C261.862 187.083 263.763 194.728 264.638 197.317C266.047 201.483 271.369 211.214 275.696 217.534C278.813 222.085 276.743 224.242 269.853 223.62C259.331 222.67 245.078 212.834 235.883 200.175Z" fill="#FF007A"/>
|
||||||
|
<path d="M418.223 321.707C362.793 299.389 343.271 280.017 343.271 247.331C343.271 242.521 343.437 238.585 343.638 238.585C343.84 238.585 345.985 240.173 348.404 242.113C359.644 251.128 372.231 254.979 407.076 260.062C427.58 263.054 439.119 265.47 449.763 269C483.595 280.22 504.527 302.99 509.518 334.004C510.969 343.016 510.118 359.915 507.766 368.822C505.91 375.857 500.245 388.537 498.742 389.023C498.325 389.158 497.917 387.562 497.81 385.389C497.24 373.744 491.355 362.406 481.472 353.913C470.235 344.257 455.137 336.569 418.223 321.707Z" fill="#FF007A"/>
|
||||||
|
<path d="M379.31 330.978C378.615 326.846 377.411 321.568 376.633 319.25L375.219 315.036L377.846 317.985C381.481 322.065 384.354 327.287 386.789 334.241C388.647 339.549 388.856 341.127 388.842 349.753C388.828 358.221 388.596 359.996 386.88 364.773C384.174 372.307 380.816 377.649 375.181 383.383C365.056 393.688 352.038 399.393 333.253 401.76C329.987 402.171 320.47 402.864 312.103 403.299C291.016 404.395 277.138 406.661 264.668 411.04C262.875 411.67 261.274 412.052 261.112 411.89C260.607 411.388 269.098 406.326 276.111 402.948C285.999 398.185 295.842 395.586 317.897 391.913C328.792 390.098 340.043 387.897 342.9 387.021C369.88 378.749 383.748 357.402 379.31 330.978Z" fill="#FF007A"/>
|
||||||
|
<path d="M404.719 376.105C397.355 360.273 395.664 344.988 399.698 330.732C400.13 329.209 400.824 327.962 401.242 327.962C401.659 327.962 403.397 328.902 405.103 330.05C408.497 332.335 415.303 336.182 433.437 346.069C456.065 358.406 468.966 367.959 477.74 378.873C485.423 388.432 490.178 399.318 492.467 412.593C493.762 420.113 493.003 438.206 491.074 445.778C484.99 469.653 470.85 488.406 450.682 499.349C447.727 500.952 445.075 502.269 444.788 502.275C444.501 502.28 445.577 499.543 447.18 496.191C453.965 482.009 454.737 468.214 449.608 452.859C446.467 443.457 440.064 431.985 427.135 412.596C412.103 390.054 408.417 384.054 404.719 376.105Z" fill="#FF007A"/>
|
||||||
|
<path d="M196.519 461.525C217.089 444.157 242.682 431.819 265.996 428.032C276.043 426.399 292.78 427.047 302.084 429.428C316.998 433.245 330.338 441.793 337.276 451.978C344.057 461.932 346.966 470.606 349.995 489.906C351.189 497.519 352.489 505.164 352.882 506.895C355.156 516.897 359.583 524.892 365.067 528.907C373.779 535.283 388.78 535.68 403.536 529.924C406.041 528.947 408.215 528.271 408.368 528.424C408.903 528.955 401.473 533.93 396.23 536.548C389.177 540.071 383.568 541.434 376.115 541.434C362.6 541.434 351.379 534.558 342.016 520.539C340.174 517.78 336.032 509.516 332.813 502.176C322.928 479.628 318.046 472.759 306.568 465.242C296.579 458.701 283.697 457.53 274.006 462.282C261.276 468.523 257.724 484.791 266.842 495.101C270.465 499.198 277.223 502.732 282.749 503.419C293.086 504.705 301.97 496.841 301.97 486.404C301.97 479.627 299.365 475.76 292.808 472.801C283.852 468.76 274.226 473.483 274.272 481.897C274.292 485.484 275.854 487.737 279.45 489.364C281.757 490.408 281.811 490.491 279.929 490.1C271.712 488.396 269.787 478.49 276.394 471.913C284.326 464.018 300.729 467.502 306.362 478.279C308.728 482.805 309.003 491.82 306.94 497.264C302.322 509.448 288.859 515.855 275.201 512.368C265.903 509.994 262.117 507.424 250.906 495.876C231.425 475.809 223.862 471.92 195.777 467.536L190.395 466.696L196.519 461.525Z" fill="#FF007A"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M49.6202 12.0031C114.678 90.9638 214.977 213.901 219.957 220.784C224.068 226.467 222.521 231.576 215.478 235.58C211.561 237.807 203.508 240.063 199.476 240.063C194.916 240.063 189.779 237.867 186.038 234.318C183.393 231.81 172.721 215.874 148.084 177.646C129.233 148.396 113.457 124.131 113.027 123.725C112.032 122.785 112.049 122.817 146.162 183.854C167.582 222.181 174.813 235.731 174.813 237.543C174.813 241.229 173.808 243.166 169.261 248.238C161.681 256.694 158.293 266.195 155.847 285.859C153.104 307.902 145.394 323.473 124.026 350.122C111.518 365.722 109.471 368.581 106.315 374.869C102.339 382.786 101.246 387.221 100.803 397.219C100.335 407.79 101.247 414.619 104.477 424.726C107.304 433.575 110.255 439.417 117.8 451.104C124.311 461.188 128.061 468.683 128.061 471.614C128.061 473.947 128.506 473.95 138.596 471.672C162.741 466.219 182.348 456.629 193.375 444.877C200.199 437.603 201.801 433.586 201.853 423.618C201.887 417.098 201.658 415.733 199.896 411.982C197.027 405.877 191.804 400.801 180.292 392.932C165.209 382.621 158.767 374.32 156.987 362.904C155.527 353.537 157.221 346.928 165.565 329.44C174.202 311.338 176.342 303.624 177.79 285.378C178.725 273.589 180.02 268.94 183.407 265.209C186.939 261.317 190.119 260 198.861 258.805C213.113 256.858 222.188 253.171 229.648 246.297C236.119 240.334 238.827 234.588 239.243 225.938L239.558 219.382L235.942 215.166C222.846 199.896 40.85 0 40.044 0C39.8719 0 44.1813 5.40178 49.6202 12.0031ZM135.412 409.18C138.373 403.937 136.8 397.195 131.847 393.902C127.167 390.79 119.897 392.256 119.897 396.311C119.897 397.548 120.582 398.449 122.124 399.243C124.72 400.579 124.909 402.081 122.866 405.152C120.797 408.262 120.964 410.996 123.337 412.854C127.162 415.849 132.576 414.202 135.412 409.18Z" fill="#FF007A"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M248.552 262.244C241.862 264.299 235.358 271.39 233.344 278.826C232.116 283.362 232.813 291.319 234.653 293.776C237.625 297.745 240.499 298.791 248.282 298.736C263.518 298.63 276.764 292.095 278.304 283.925C279.567 277.229 273.749 267.948 265.736 263.874C261.601 261.772 252.807 260.938 248.552 262.244ZM266.364 276.172C268.714 272.834 267.686 269.225 263.69 266.785C256.08 262.138 244.571 265.983 244.571 273.173C244.571 276.752 250.572 280.656 256.074 280.656C259.735 280.656 264.746 278.473 266.364 276.172Z" fill="#FF007A"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 7.9 KiB |
@@ -1,10 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<router-view/>
|
<router-view/>
|
||||||
<support-chat/>
|
<support-chat/>
|
||||||
|
<welcome-dialog v-model="prefs.newbie"/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import SupportChat from "@/components/SupportChat.vue";
|
import SupportChat from "@/components/SupportChat.vue";
|
||||||
import {detectChain} from "@/blockchain/wallet.js";
|
import WelcomeDialog from "@/components/WelcomeDialog.vue";
|
||||||
detectChain()
|
import {usePrefStore} from "@/store/store.js";
|
||||||
|
|
||||||
|
const prefs = usePrefStore()
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import {provider as walletProvider} from "@/blockchain/provider.js";
|
||||||
import {ethers} from "ethers";
|
import {ethers} from "ethers";
|
||||||
import {AbiURLCache} from "../common.js";
|
import {AbiURLCache} from "../common.js";
|
||||||
import {provider as walletProvider} from "@/blockchain/wallet.js";
|
|
||||||
|
|
||||||
export const abiCache = new AbiURLCache('/contract/out/')
|
export const abiCache = new AbiURLCache('/contract/out/')
|
||||||
|
|
||||||
|
|||||||
@@ -10,13 +10,31 @@ export function subOHLC( chainId, pool, period ) {
|
|||||||
// console.log('subOHLC', chainId, pool, period, ckey, ohlcSubCounts[ckey])
|
// console.log('subOHLC', chainId, pool, period, ckey, ohlcSubCounts[ckey])
|
||||||
if (!(ckey in ohlcSubCounts) || ohlcSubCounts[ckey] === 0) {
|
if (!(ckey in ohlcSubCounts) || ohlcSubCounts[ckey] === 0) {
|
||||||
ohlcSubCounts[ckey] = 1
|
ohlcSubCounts[ckey] = 1
|
||||||
console.log('subscribing OHLCs', chainId, key)
|
// console.log('subscribing OHLCs', chainId, key)
|
||||||
socket.emit('subOHLCs', chainId, [key])
|
socket.emit('subOHLCs', chainId, [key])
|
||||||
} else
|
} else
|
||||||
ohlcSubCounts[ckey]++
|
ohlcSubCounts[ckey]++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function refreshOHLCSubs() {
|
||||||
|
const keys = []
|
||||||
|
let chainId = null
|
||||||
|
for (const key of Object.keys(ohlcSubCounts)) {
|
||||||
|
const [curChainId, pool, period] = key.split('|')
|
||||||
|
if (chainId === null)
|
||||||
|
chainId = curChainId
|
||||||
|
else if (chainId !== curChainId) {
|
||||||
|
console.error('refreshOHLCSubs: mixed chainIds')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
keys.push(`${pool}|${period}`)
|
||||||
|
}
|
||||||
|
// console.log('refreshing OHLC subs', keys)
|
||||||
|
socket.emit('subOHLCs', chainId, keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export function unsubOHLC( chainId, pool, period ) {
|
export function unsubOHLC( chainId, pool, period ) {
|
||||||
const key = `${pool}|${period}`
|
const key = `${pool}|${period}`
|
||||||
const ckey = `${chainId}|${key}`
|
const ckey = `${chainId}|${key}`
|
||||||
@@ -27,7 +45,7 @@ export function unsubOHLC( chainId, pool, period ) {
|
|||||||
} else {
|
} else {
|
||||||
ohlcSubCounts[ckey]--
|
ohlcSubCounts[ckey]--
|
||||||
if (ohlcSubCounts[ckey] === 0) {
|
if (ohlcSubCounts[ckey] === 0) {
|
||||||
console.log('unsubscribing OHLCs', chainId, key)
|
// console.log('unsubscribing OHLCs', chainId, key)
|
||||||
// noinspection JSCheckFunctionSignatures
|
// noinspection JSCheckFunctionSignatures
|
||||||
socket.emit('unsubOHLCs', chainId, [key])
|
socket.emit('unsubOHLCs', chainId, [key])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
import {uint32max, uint64max} from "@/misc.js";
|
import {uint32max, uint64max} from "@/misc.js";
|
||||||
import {encodeIEE754} from "@/common.js";
|
import {encodeIEE754} from "@/common.js";
|
||||||
|
|
||||||
|
|
||||||
export const MAX_FRACTION = 65535;
|
export const MAX_FRACTION = 65535;
|
||||||
export const NO_CONDITIONAL_ORDER = uint64max;
|
export const NO_CONDITIONAL_ORDER = uint64max;
|
||||||
export const NO_OCO = uint64max;
|
export const NO_OCO = uint64max;
|
||||||
export const DISTANT_PAST = 0
|
export const DISTANT_PAST = 0
|
||||||
export const DISTANT_FUTURE = uint32max
|
export const DISTANT_FUTURE = uint32max
|
||||||
|
|
||||||
|
export const MIN_EXECUTION_TIME = 60 // give at least one full minute for each tranche to trigger
|
||||||
|
export const DEFAULT_SLIPPAGE = 0.0030;
|
||||||
|
export const MIN_SLIPPAGE = 0.0001;
|
||||||
|
|
||||||
// struct SwapOrder {
|
// struct SwapOrder {
|
||||||
// address tokenIn;
|
// address tokenIn;
|
||||||
// address tokenOut;
|
// address tokenOut;
|
||||||
@@ -31,7 +36,7 @@ export function newOrder(tokenIn, tokenOut, exchange, fee, amount, amountIsInput
|
|||||||
if (!tranches)
|
if (!tranches)
|
||||||
tranches = [newTranche({marketOrder: true})] // todo this is just a swap: issue warning?
|
tranches = [newTranche({marketOrder: true})] // todo this is just a swap: issue warning?
|
||||||
if( minFillAmount === null )
|
if( minFillAmount === null )
|
||||||
minFillAmount = amount / 100n // default to min trade size of 1%
|
minFillAmount = amount / 1000n // default to min trade size of 0.1%
|
||||||
return {
|
return {
|
||||||
tokenIn, tokenOut, route:{exchange, fee},
|
tokenIn, tokenOut, route:{exchange, fee},
|
||||||
amount, minFillAmount, amountIsInput,
|
amount, minFillAmount, amountIsInput,
|
||||||
@@ -81,11 +86,13 @@ export function newTranche({
|
|||||||
rateLimitFraction = 0,
|
rateLimitFraction = 0,
|
||||||
rateLimitPeriod = 0,
|
rateLimitPeriod = 0,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
if( minIntercept === 0 && minSlope === 0 && maxIntercept === 0 && maxSlope === 0 )
|
|
||||||
marketOrder = true
|
|
||||||
if( marketOrder ) {
|
if( marketOrder ) {
|
||||||
if (minIntercept !== 0 || minSlope !== 0 || maxIntercept !== 0 || maxSlope !== 0)
|
if (minIntercept !== 0 || minSlope !== 0 || maxIntercept !== 0 || maxSlope !== 0)
|
||||||
console.warn('Ignoring line information in a market order')
|
throw Error('Cannot set line information on a market order')
|
||||||
|
if (slippage === 0)
|
||||||
|
slippage = DEFAULT_SLIPPAGE
|
||||||
|
else if (slippage < MIN_SLIPPAGE)
|
||||||
|
slippage = MIN_SLIPPAGE
|
||||||
minIntercept = encodeIEE754(slippage) // this is the slippage field for market orders
|
minIntercept = encodeIEE754(slippage) // this is the slippage field for market orders
|
||||||
minSlope = 0
|
minSlope = 0
|
||||||
maxIntercept = 0
|
maxIntercept = 0
|
||||||
@@ -137,7 +144,7 @@ export function parseElaboratedOrderStatus(chainId, status) {
|
|||||||
|
|
||||||
|
|
||||||
export function parseOrderStatus(chainId, status) {
|
export function parseOrderStatus(chainId, status) {
|
||||||
console.log('parseOrderStatus', status)
|
// console.log('parseOrderStatus', status)
|
||||||
let [
|
let [
|
||||||
order,
|
order,
|
||||||
fillFeeHalfBps,
|
fillFeeHalfBps,
|
||||||
@@ -158,7 +165,7 @@ export function parseOrderStatus(chainId, status) {
|
|||||||
chainId, order, fillFeeHalfBps, state, startTime, startPrice, ocoGroup,
|
chainId, order, fillFeeHalfBps, state, startTime, startPrice, ocoGroup,
|
||||||
filledIn, filledOut, filled, trancheStatus,
|
filledIn, filledOut, filled, trancheStatus,
|
||||||
};
|
};
|
||||||
console.log('SwapOrderStatus', result)
|
// console.log('SwapOrderStatus', result)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,4 +257,3 @@ export function parseFeeSchedule(sched) {
|
|||||||
fillFee: fillFeeHalfBps/1_000_000 // fillFee is a multiplier on the filled volume. 0.0001 = 0.1% of the output token taken as a fee
|
fillFee: fillFeeHalfBps/1_000_000 // fillFee is a multiplier on the filled volume. 0.0001 = 0.1% of the output token taken as a fee
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import {socket} from "@/socket.js";
|
|
||||||
import {useStore} from "@/store/store.js";
|
import {useStore} from "@/store/store.js";
|
||||||
import {Exchange} from "@/blockchain/orderlib.js";
|
import {Exchange} from "@/blockchain/orderlib.js";
|
||||||
import {uniswapV3PoolAddress} from "@/blockchain/uniswap.js";
|
import {uniswapV3PoolAddress} from "@/blockchain/uniswap.js";
|
||||||
import {FixedNumber} from "ethers";
|
import {FixedNumber} from "ethers";
|
||||||
import {provider} from "@/blockchain/wallet.js";
|
|
||||||
import {newContract} from "@/blockchain/contract.js";
|
import {newContract} from "@/blockchain/contract.js";
|
||||||
|
import {provider} from "@/blockchain/provider.js";
|
||||||
|
import {socket} from "@/socket.js";
|
||||||
|
|
||||||
const subscriptionCounts = {} // key is route and value is a subscription counter
|
const subscriptionCounts = {} // key is route and value is a subscription counter
|
||||||
export const WIDE_PRICE_FORMAT = {decimals:38, width:512, signed:false}; // 38 decimals is 127 bits
|
export const WIDE_PRICE_FORMAT = {decimals:38, width:512, signed:false}; // 38 decimals is 127 bits
|
||||||
|
|||||||
3
src/blockchain/provider.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export let provider = null
|
||||||
|
|
||||||
|
export function setProvider(p) {provider = p}
|
||||||
@@ -2,7 +2,8 @@ import {Exchange} from "@/blockchain/orderlib.js";
|
|||||||
import {useOrderStore, useStore} from "@/store/store.js";
|
import {useOrderStore, useStore} from "@/store/store.js";
|
||||||
import {queryHelperContract} from "@/blockchain/contract.js";
|
import {queryHelperContract} from "@/blockchain/contract.js";
|
||||||
import {SingletonCoroutine} from "@/misc.js";
|
import {SingletonCoroutine} from "@/misc.js";
|
||||||
import {provider} from "@/blockchain/wallet.js";
|
|
||||||
|
import {provider} from "@/blockchain/provider.js";
|
||||||
|
|
||||||
|
|
||||||
export async function findRoute(helper, chainId, tokenA, tokenB) {
|
export async function findRoute(helper, chainId, tokenA, tokenB) {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import {socket} from "@/socket.js";
|
|
||||||
import {useStore} from "@/store/store.js";
|
import {useStore} from "@/store/store.js";
|
||||||
import {metadataMap} from "@/version.js";
|
import {metadataMap} from "@/version.js";
|
||||||
import {provider} from "@/blockchain/wallet.js";
|
|
||||||
import {newContract} from "@/blockchain/contract.js";
|
import {newContract} from "@/blockchain/contract.js";
|
||||||
|
import {provider} from "@/blockchain/provider.js";
|
||||||
|
import {socket} from "@/socket.js";
|
||||||
|
|
||||||
|
|
||||||
// synchronous version may return null but will trigger a lookup
|
// synchronous version may return null but will trigger a lookup
|
||||||
@@ -31,7 +31,16 @@ export async function getToken(chainId, addr) {
|
|||||||
return found
|
return found
|
||||||
if (!(addr in s.tokens))
|
if (!(addr in s.tokens))
|
||||||
await addExtraToken(chainId, addr)
|
await addExtraToken(chainId, addr)
|
||||||
return s.tokens[addr]
|
let result = s.tokens[addr]
|
||||||
|
if (!result) {
|
||||||
|
result = {
|
||||||
|
n: addr,
|
||||||
|
a: addr,
|
||||||
|
s: addr,
|
||||||
|
d: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,13 @@
|
|||||||
import {nav, uuid} from "@/misc.js";
|
import {provider} from "@/blockchain/provider.js";
|
||||||
import {newContract, vaultContract} from "@/blockchain/contract.js";
|
import {TransactionState, TransactionType} from "@/blockchain/transactionDecl.js";
|
||||||
import {ensureVault, provider, switchChain, useWalletStore} from "@/blockchain/wallet.js";
|
import {sleep, uuid} from "@/misc.js";
|
||||||
|
import {vaultContract} from "@/blockchain/contract.js";
|
||||||
|
import {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";
|
||||||
|
import {router} from "@/router/router.js";
|
||||||
|
|
||||||
export const TransactionState = {
|
|
||||||
Created: 0, // user requested a transaction
|
|
||||||
Proposed: 1, // tx is sent to the wallet
|
|
||||||
Signed: 2, // tx is awaiting blockchain mining
|
|
||||||
Rejected: 3, // user refused to sign the tx
|
|
||||||
Error: 3, // unknown error sending the tx to the wallet
|
|
||||||
Mined: 4, // transaction has been confirmed on-chain
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TransactionType = {
|
|
||||||
PlaceOrder: 1,
|
|
||||||
CancelOrder: 2,
|
|
||||||
CancelAll: 3,
|
|
||||||
Wrap: 4,
|
|
||||||
Unwrap: 5,
|
|
||||||
WithdrawNative: 6,
|
|
||||||
Withdraw: 7,
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Transaction {
|
export class Transaction {
|
||||||
constructor(chainId, type) {
|
constructor(chainId, type) {
|
||||||
@@ -37,11 +22,18 @@ export class Transaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
submit() {
|
submit() {
|
||||||
useWalletStore().transaction = this
|
console.log('submitting transaction', this.type)
|
||||||
ensureVault()
|
const ws = useWalletStore();
|
||||||
|
if ( ws.transaction !== null ) {
|
||||||
|
console.error('Transaction already in progress', ws.transaction)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ws.transaction = this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// "propose" means attach the transaction to a specific vault
|
||||||
propose(owner, vault) {
|
propose(owner, vault) {
|
||||||
|
console.log('transaction bind', owner, vault)
|
||||||
if (this.vault !== null && this.vault !== vault) {
|
if (this.vault !== null && this.vault !== vault) {
|
||||||
this.failed('proposed vault did not match withdrawl vault', vault, this.vault)
|
this.failed('proposed vault did not match withdrawl vault', vault, this.vault)
|
||||||
return
|
return
|
||||||
@@ -128,10 +120,16 @@ export class Transaction {
|
|||||||
this.failed('vault contract was null while sending order transaction')
|
this.failed('vault contract was null while sending order transaction')
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const tx = toRaw(await this.createTx(contract))
|
try {
|
||||||
this.signed(tx)
|
const tx = toRaw(await this.createTx(contract))
|
||||||
console.log(`sent transaction`, tx)
|
this.signed(tx)
|
||||||
tx.wait().then(this.mined.bind(this)).catch(this.failed.bind(this))
|
tx.wait().then(this.mined.bind(this)).catch(this.failed.bind(this))
|
||||||
|
console.log(`sent transaction`, tx)
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
this.failed(e)
|
||||||
|
return null
|
||||||
|
}
|
||||||
return this.tx
|
return this.tx
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +157,22 @@ export class PlaceOrderTransaction extends Transaction {
|
|||||||
|
|
||||||
|
|
||||||
async createTx(vaultContract) {
|
async createTx(vaultContract) {
|
||||||
this.fee = await placementFee(this.vault, this.order)
|
const tries = 65;
|
||||||
|
let i;
|
||||||
|
let success = false
|
||||||
|
for (i=0; !success && i<tries; i++ ) {
|
||||||
|
try {
|
||||||
|
console.error('getting placement fee', vaultContract, this.order)
|
||||||
|
this.fee = await placementFee(vaultContract, this.order)
|
||||||
|
success = true
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.warn('failed to get placement fee', e)
|
||||||
|
await sleep(1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!success)
|
||||||
|
throw Error('failed to get placement fee')
|
||||||
console.log('placing order', this.id, this.fee, this.order)
|
console.log('placing order', this.id, this.fee, this.order)
|
||||||
return await vaultContract.placeDexorder(this.order, {value: this.fee.reduce((a, b) => a + b)})
|
return await vaultContract.placeDexorder(this.order, {value: this.fee.reduce((a, b) => a + b)})
|
||||||
}
|
}
|
||||||
@@ -169,36 +182,14 @@ export class PlaceOrderTransaction extends Transaction {
|
|||||||
super.end(state)
|
super.end(state)
|
||||||
if (state === TransactionState.Mined) {
|
if (state === TransactionState.Mined) {
|
||||||
useChartOrderStore().resetOrders()
|
useChartOrderStore().resetOrders()
|
||||||
nav('Status')
|
// noinspection JSIgnoredPromiseFromCall
|
||||||
|
router.push({name: 'Status'})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 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)
|
||||||
@@ -277,4 +268,3 @@ export class UnwrapTransaction extends Transaction {
|
|||||||
return await vaultContract.unwrap(this.amount)
|
return await vaultContract.unwrap(this.amount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
17
src/blockchain/transactionDecl.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
export const TransactionState = {
|
||||||
|
Created: 0, // user requested a transaction
|
||||||
|
Proposed: 1, // tx is sent to the wallet
|
||||||
|
Signed: 2, // tx is awaiting blockchain mining
|
||||||
|
Rejected: 3, // user refused to sign the tx
|
||||||
|
Error: 3, // unknown error sending the tx to the wallet
|
||||||
|
Mined: 4, // transaction has been confirmed on-chain
|
||||||
|
}
|
||||||
|
export const TransactionType = {
|
||||||
|
PlaceOrder: 1,
|
||||||
|
CancelOrder: 2,
|
||||||
|
CancelAll: 3,
|
||||||
|
Wrap: 4,
|
||||||
|
Unwrap: 5,
|
||||||
|
WithdrawNative: 6,
|
||||||
|
Withdraw: 7,
|
||||||
|
}
|
||||||
@@ -1,18 +1,16 @@
|
|||||||
|
import {provider, setProvider} from "@/blockchain/provider.js";
|
||||||
import {BrowserProvider, ethers} from "ethers";
|
import {BrowserProvider, ethers} from "ethers";
|
||||||
import {useStore} from "@/store/store";
|
import {useStore} from "@/store/store";
|
||||||
import {socket} from "@/socket.js";
|
import {errorSuggestsMissingVault, SingletonCoroutine} from "@/misc.js";
|
||||||
import {SingletonCoroutine} from "@/misc.js";
|
|
||||||
import {newContract, vaultAddress, vaultContract} from "@/blockchain/contract.js";
|
import {newContract, vaultAddress, vaultContract} from "@/blockchain/contract.js";
|
||||||
import {defineStore} from "pinia";
|
import {defineStore} from "pinia";
|
||||||
import {ref} from "vue";
|
import {computed, ref} from "vue";
|
||||||
import {metadataMap, version} from "@/version.js";
|
import {metadataMap, version} from "@/version.js";
|
||||||
import {CancelAllTransaction, TransactionState} from "@/blockchain/transaction.js";
|
import {TransactionState, TransactionType} from "@/blockchain/transactionDecl.js";
|
||||||
|
import {track} from "@/track.js";
|
||||||
|
import {socket} from "@/socket.js";
|
||||||
|
|
||||||
|
|
||||||
export let provider = null
|
|
||||||
|
|
||||||
|
|
||||||
// DEPRECATED
|
|
||||||
export const useWalletStore = defineStore('wallet', ()=>{
|
export const useWalletStore = defineStore('wallet', ()=>{
|
||||||
// this is what the wallet is logged into. it could be different than the application's store.chainId.
|
// this is what the wallet is logged into. it could be different than the application's store.chainId.
|
||||||
const chainId = ref(0)
|
const chainId = ref(0)
|
||||||
@@ -29,7 +27,26 @@ export const useWalletStore = defineStore('wallet', ()=>{
|
|||||||
const pendingOrders = ref([])
|
const pendingOrders = ref([])
|
||||||
|
|
||||||
// NEW Format is a single Transaction class
|
// NEW Format is a single Transaction class
|
||||||
const transaction = ref(null)
|
const _tx = ref(null)
|
||||||
|
const transaction = computed({
|
||||||
|
get() {return _tx.value},
|
||||||
|
set(v) {
|
||||||
|
_tx.value = v;
|
||||||
|
if (v===null) {
|
||||||
|
console.log('clear transaction')
|
||||||
|
if (progressionInvoker!==null) {
|
||||||
|
clearTimeout(progressionInvoker)
|
||||||
|
progressionInvoker = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log('set transaction', v)
|
||||||
|
transactionProgressor.invoke();
|
||||||
|
if (progressionInvoker===null)
|
||||||
|
progressionInvoker = setInterval(()=>transactionProgressor.invoke(), 1000)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
chainId, pendingOrders, transaction,
|
chainId, pendingOrders, transaction,
|
||||||
@@ -40,6 +57,7 @@ export const useWalletStore = defineStore('wallet', ()=>{
|
|||||||
export function onChainChanged(chainId) {
|
export function onChainChanged(chainId) {
|
||||||
console.log('onChainChanged', chainId)
|
console.log('onChainChanged', chainId)
|
||||||
chainId = Number(chainId)
|
chainId = Number(chainId)
|
||||||
|
socket.emit('chain', chainId)
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const ws = useWalletStore()
|
const ws = useWalletStore()
|
||||||
if( chainId !== ws.chainId ) {
|
if( chainId !== ws.chainId ) {
|
||||||
@@ -49,7 +67,7 @@ export function onChainChanged(chainId) {
|
|||||||
console.log('app chain changed', chainId)
|
console.log('app chain changed', chainId)
|
||||||
store.chainId = chainId
|
store.chainId = chainId
|
||||||
store.account = null
|
store.account = null
|
||||||
provider = new BrowserProvider(window.ethereum, chainId)
|
setProvider(new BrowserProvider(window.ethereum, chainId))
|
||||||
updateAccounts(chainId, provider)
|
updateAccounts(chainId, provider)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -73,10 +91,14 @@ function changeAccounts(chainId, accounts) {
|
|||||||
const addr = accounts[0]
|
const addr = accounts[0]
|
||||||
if (addr !== store.account) {
|
if (addr !== store.account) {
|
||||||
console.log('account logged in', addr)
|
console.log('account logged in', addr)
|
||||||
|
track('login', {chainId, address: addr})
|
||||||
store.account = addr
|
store.account = addr
|
||||||
store.vaults = []
|
store.vaults = []
|
||||||
discoverVaults(addr)
|
// one of these two methods will call flushTransactions()
|
||||||
flushTransactions()
|
if (useWalletStore().transaction!==null)
|
||||||
|
ensureVault()
|
||||||
|
else
|
||||||
|
discoverVaults(addr)
|
||||||
socket.emit('address', chainId, addr)
|
socket.emit('address', chainId, addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,17 +121,17 @@ export function detectChain() {
|
|||||||
try {
|
try {
|
||||||
window.ethereum.on('chainChanged', onChainChanged);
|
window.ethereum.on('chainChanged', onChainChanged);
|
||||||
window.ethereum.on('accountsChanged', onAccountsChanged);
|
window.ethereum.on('accountsChanged', onAccountsChanged);
|
||||||
|
new ethers.BrowserProvider(window.ethereum).getNetwork().then((network)=>{
|
||||||
|
const chainId = network.chainId
|
||||||
|
onChainChanged(chainId)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
console.log('Could not connect change hooks to wallet', e)
|
console.log('Could not connect change hooks to wallet', e)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
new ethers.BrowserProvider(window.ethereum).getNetwork().then((network)=>{
|
|
||||||
const chainId = network.chainId
|
|
||||||
onChainChanged(chainId)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
detectChain()
|
||||||
|
|
||||||
const errorHandlingProxy = {
|
const errorHandlingProxy = {
|
||||||
get(target, prop, proxy) {
|
get(target, prop, proxy) {
|
||||||
@@ -153,7 +175,16 @@ export async function connectWallet(chainId) {
|
|||||||
await updateAccounts(chainId, p)
|
await updateAccounts(chainId, p)
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
if (e.reason!=='rejected') {
|
console.log('connectWallet error', e.reason, e)
|
||||||
|
if (e.reason==='rejected') {
|
||||||
|
const ws = useWalletStore();
|
||||||
|
const tx = ws.transaction
|
||||||
|
if (tx) {
|
||||||
|
tx.state = TransactionState.Rejected
|
||||||
|
ws.transaction = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
console.error(e, e.reason)
|
console.error(e, e.reason)
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
@@ -171,6 +202,7 @@ function discoverVaults(owner) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const doDiscoverVaults = new SingletonCoroutine(_discoverVaults, 50)
|
const doDiscoverVaults = new SingletonCoroutine(_discoverVaults, 50)
|
||||||
|
|
||||||
async function _discoverVaults(owner) {
|
async function _discoverVaults(owner) {
|
||||||
const result = []
|
const result = []
|
||||||
const versions = []
|
const versions = []
|
||||||
@@ -187,7 +219,6 @@ async function _discoverVaults(owner) {
|
|||||||
// console.log(`vault ${num} at`, addr)
|
// console.log(`vault ${num} at`, addr)
|
||||||
if (addr === null) // no more vaults
|
if (addr === null) // no more vaults
|
||||||
break
|
break
|
||||||
console.log('provider', provider)
|
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
console.log('No provider')
|
console.log('No provider')
|
||||||
return // do not change whatever was already found
|
return // do not change whatever was already found
|
||||||
@@ -199,20 +230,21 @@ async function _discoverVaults(owner) {
|
|||||||
result.push(addr)
|
result.push(addr)
|
||||||
versions.push(version)
|
versions.push(version)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.value === '0x' && e.code === 'BAD_DATA' || e.revert === null && e.code === 'CALL_EXCEPTION')
|
if (errorSuggestsMissingVault(e))
|
||||||
console.log(`no vault ${num} at ${addr}`)
|
console.log(`no vault ${num} at ${addr}`)
|
||||||
else
|
else
|
||||||
console.error(`discoverVaults failed`, e)
|
console.error(`discoverVaults failed`, e)
|
||||||
return // do not change what was already found todo is this correct?
|
return // do not change what was already found todo is this correct?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log('new account === owner?', s.account, owner)
|
||||||
if( s.account === owner ) { // double-check the account since it could have changed during our await
|
if( s.account === owner ) { // double-check the account since it could have changed during our await
|
||||||
s.vaults = result
|
s.vaults = result
|
||||||
s.vaultVersions = versions
|
s.vaultVersions = versions
|
||||||
if( useWalletStore().transaction ) {
|
if( useWalletStore().transaction ) {
|
||||||
const num = 0 // todo multiple vaults
|
const num = 0 // todo multiple vaults
|
||||||
if (result.length)
|
if (result.length)
|
||||||
flushOrders(s.chainId, owner, num, result[0])
|
flushWalletTransactions(s.chainId, owner, num, result[0])
|
||||||
else
|
else
|
||||||
ensureVault2(s.chainId, owner, num)
|
ensureVault2(s.chainId, owner, num)
|
||||||
}
|
}
|
||||||
@@ -254,7 +286,7 @@ async function doEnsureVault(chainId, owner, num) {
|
|||||||
if (s.vaults.length <= num)
|
if (s.vaults.length <= num)
|
||||||
await _discoverVaults(owner)
|
await _discoverVaults(owner)
|
||||||
if( s.vaults[num] )
|
if( s.vaults[num] )
|
||||||
flushOrders(chainId, owner, num, s.vaults[num])
|
flushWalletTransactions(chainId, owner, num, s.vaults[num])
|
||||||
else {
|
else {
|
||||||
console.log(`requesting vault ${owner} ${num}`)
|
console.log(`requesting vault ${owner} ${num}`)
|
||||||
socket.emit('ensureVault', chainId, owner, num)
|
socket.emit('ensureVault', chainId, owner, num)
|
||||||
@@ -276,15 +308,68 @@ export async function cancelOrder(vault, orderIndex) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function cancelAll(vault) {
|
async function progressTransactions() {
|
||||||
new CancelAllTransaction(useStore().chainId, vault).submit()
|
const s = useStore()
|
||||||
|
const ws = useWalletStore();
|
||||||
|
console.log('progressTransactions', ws.transaction)
|
||||||
|
if( ws.transaction===null )
|
||||||
|
return
|
||||||
|
if( s.account === null ) {
|
||||||
|
let signer = null
|
||||||
|
try {
|
||||||
|
console.log('account is null. requesting sign-in.')
|
||||||
|
signer = await provider.getSigner()
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.log('signer error', e.code, e.info?.error?.code)
|
||||||
|
if (e?.info?.error?.code === 4001) {
|
||||||
|
console.log('signer rejected')
|
||||||
|
signer = null
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
if (signer === null) {
|
||||||
|
console.log('setting tx state to rejected')
|
||||||
|
ws.transaction.state = TransactionState.Rejected
|
||||||
|
ws.transaction = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if( s.vault === null ) {
|
||||||
|
console.log('vault is null. requesting vault creation.')
|
||||||
|
ensureVault()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if( ws.transaction.state < TransactionState.Proposed )
|
||||||
|
ws.transaction.propose(s.account, s.vault)
|
||||||
|
if( ws.transaction.type === TransactionType.PlaceOrder ) {
|
||||||
|
flushWalletTransactions(s.chainId, s.account, 0, s.vault)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log('flushing transaction', ws.transaction.type)
|
||||||
|
if (ws.transaction.state < TransactionState.Proposed) {
|
||||||
|
pendTransaction(async (signer) => {
|
||||||
|
if (signer.address !== ws.transaction.owner) {
|
||||||
|
console.error('signer address does not match transaction owner', signer.address, ws.transaction.owner)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const contract = await vaultContract(ws.transaction.vault, signer)
|
||||||
|
return await ws.transaction.createTx(contract)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function flushOrders(chainId, owner, num, vault) {
|
const transactionProgressor = new SingletonCoroutine(progressTransactions, 10)
|
||||||
|
|
||||||
|
let progressionInvoker = null
|
||||||
|
|
||||||
|
|
||||||
|
export function flushWalletTransactions(chainId, owner, num, vault) {
|
||||||
const ws = useWalletStore();
|
const ws = useWalletStore();
|
||||||
if (ws.transaction!==null && ws.transaction.state < TransactionState.Proposed)
|
console.log('flushWalletTransactions', chainId, owner, num, vault)
|
||||||
ws.transaction.propose(owner, vault)
|
let needsFlush = ws.transaction !== null && ws.transaction.type !== TransactionType.PlaceOrder
|
||||||
let needsFlush = false
|
|
||||||
for( const pend of ws.pendingOrders ) {
|
for( const pend of ws.pendingOrders ) {
|
||||||
if (pend.vault === null)
|
if (pend.vault === null)
|
||||||
pend.vault = vault
|
pend.vault = vault
|
||||||
@@ -353,6 +438,7 @@ function pendOrderAsTransaction(pend) {
|
|||||||
|
|
||||||
|
|
||||||
export function pendTransaction(sender, errHandler) {
|
export function pendTransaction(sender, errHandler) {
|
||||||
|
console.log('pendTransaction')
|
||||||
const s = useStore()
|
const s = useStore()
|
||||||
s.transactionSenders.push([sender,errHandler])
|
s.transactionSenders.push([sender,errHandler])
|
||||||
flushTransactions()
|
flushTransactions()
|
||||||
@@ -367,13 +453,32 @@ export function flushTransactions() {
|
|||||||
|
|
||||||
async function asyncFlushTransactions() {
|
async function asyncFlushTransactions() {
|
||||||
const s = useStore()
|
const s = useStore()
|
||||||
|
const ws = useWalletStore()
|
||||||
|
console.log('flushTransactions', ws.transaction, s.vault)
|
||||||
|
if (ws.transaction !== null) {
|
||||||
|
if (s.vault === null) {
|
||||||
|
console.log('transaction doesn\'t have a vault. creating one.')
|
||||||
|
await ensureVault()
|
||||||
|
if (s.vault === null) {
|
||||||
|
console.error('vault could not be created')
|
||||||
|
const tx = ws.transaction
|
||||||
|
if (tx) {
|
||||||
|
tx.state = TransactionState.Error
|
||||||
|
ws.transaction = null
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if( provider === null ) {
|
if( provider === null ) {
|
||||||
console.log('warning: asyncFlushOrders() cancelled due to null provider')
|
console.log('warning: asyncFlushOrders() cancelled due to null provider')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const senders = s.transactionSenders
|
const senders = s.transactionSenders
|
||||||
if (!senders.length)
|
if (!senders.length) {
|
||||||
|
console.log('no transactionSenders!')
|
||||||
return
|
return
|
||||||
|
}
|
||||||
console.log(`flushing ${senders.length} transactions`)
|
console.log(`flushing ${senders.length} transactions`)
|
||||||
let signer
|
let signer
|
||||||
try {
|
try {
|
||||||
@@ -489,7 +594,7 @@ const _chainInfos = {
|
|||||||
1337: {
|
1337: {
|
||||||
"chainId": "0x539",
|
"chainId": "0x539",
|
||||||
"chainName": "Dexorder Alpha Testnet",
|
"chainName": "Dexorder Alpha Testnet",
|
||||||
"rpcUrls": ["https://rpc.alpha.dexorder.trade"],
|
"rpcUrls": ["https://rpc.alpha.dexorder.com"],
|
||||||
"nativeCurrency": {
|
"nativeCurrency": {
|
||||||
"name": "Test Ethereum",
|
"name": "Test Ethereum",
|
||||||
"symbol": "TETH",
|
"symbol": "TETH",
|
||||||
@@ -531,3 +636,26 @@ export async function addNetwork(chainId) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function addNetworkAndConnectWallet(chainId) {
|
||||||
|
try {
|
||||||
|
await switchChain(chainId)
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code === 4001) {
|
||||||
|
// explicit user rejection
|
||||||
|
return
|
||||||
|
} else if (e.code === 4902) {
|
||||||
|
try {
|
||||||
|
await addNetwork(chainId)
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`Could not add network ${chainId}`)
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
console.log('switchChain() failure', e)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await connectWallet(chainId)
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code !== 4001)
|
||||||
|
console.log('connectWallet() failed', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -52,4 +52,8 @@ export function dirtyItems(a, b) {
|
|||||||
for (const k of dirtyKeys(a, b))
|
for (const k of dirtyKeys(a, b))
|
||||||
result[k] = b[k]
|
result[k] = b[k]
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function copyPoints(points) {
|
||||||
|
return points.map((p)=>({time: p.time, price: p.price}))
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
import {useChartOrderStore} from "@/orderbuild.js";
|
import {useChartOrderStore} from "@/orderbuild.js";
|
||||||
import {invokeCallbacks, prototype} from "@/common.js";
|
import {invokeCallbacks, prototype} from "@/common.js";
|
||||||
import {DataFeed, feelessTickerKey, getAllSymbols, lookupSymbol} from "@/charts/datafeed.js";
|
import {DataFeed, defaultSymbol, feelessTickerKey, getAllSymbols, lookupSymbol} from "@/charts/datafeed.js";
|
||||||
import {intervalToSeconds, SingletonCoroutine} from "@/misc.js";
|
import {intervalToSeconds, secondsToInterval, SingletonCoroutine, toHuman, toPrecision} from "@/misc.js";
|
||||||
import {useStore} from "@/store/store.js";
|
import {usePrefStore, useStore} from "@/store/store.js";
|
||||||
import {tvCustomThemes} from "../../theme.js";
|
import {tvCustomThemes} from "../../theme.js";
|
||||||
|
|
||||||
export let widget = null
|
export let widget = null
|
||||||
export let chart = null
|
export let chart = null
|
||||||
export let crosshairPoint = null
|
export let crosshairPoint = null
|
||||||
|
export let defaultShapeHandler = null // if set, then TV events that dont have a registered shape handler get passed directly to this function
|
||||||
let symbolChangedCbs = [] // callbacks for TV's chart.onSymbolChanged()
|
let symbolChangedCbs = [] // callbacks for TV's chart.onSymbolChanged()
|
||||||
|
|
||||||
|
|
||||||
const s = useStore()
|
const s = useStore()
|
||||||
const co = useChartOrderStore()
|
const co = useChartOrderStore()
|
||||||
|
const prefs = usePrefStore()
|
||||||
|
|
||||||
export function addSymbolChangedCallback(cb) {
|
export function addSymbolChangedCallback(cb) {
|
||||||
symbolChangedCbs.push(cb)
|
symbolChangedCbs.push(cb)
|
||||||
@@ -23,11 +25,13 @@ export function removeSymbolChangedCallback(cb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function symbolChanged(symbol) {
|
function symbolChanged(symbol) {
|
||||||
const info = symbol===null ? null : lookupSymbol(symbol.ticker)
|
const info = symbol===null ? (defaultSymbol===null?'default':defaultSymbol) : lookupSymbol(symbol.ticker)
|
||||||
co.selectedSymbol = info
|
co.selectedSymbol = info
|
||||||
|
// console.log('setting prefs ticker', info.ticker)
|
||||||
|
prefs.selectedTicker = info.ticker
|
||||||
symbolChangedCbs.forEach((cb) => cb(info))
|
symbolChangedCbs.forEach((cb) => cb(info))
|
||||||
updateFeeDropdown()
|
updateFeeDropdown()
|
||||||
console.log('symbol changed', info)
|
// console.log('symbol changed', info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -53,15 +57,24 @@ export async function setSymbolTicker(ticker) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function changeInterval(interval, _timeframe) {
|
function changeInterval(interval) {
|
||||||
co.intervalSecs = intervalToSeconds(interval)
|
const secs = intervalToSeconds(interval)
|
||||||
DataFeed.intervalChanged(co.intervalSecs)
|
co.intervalSecs = secs
|
||||||
|
prefs.selectedTimeframe = interval
|
||||||
|
DataFeed.intervalChanged(secs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function changeIntervalSecs(secs) {
|
||||||
|
const interval = secondsToInterval(secs);
|
||||||
|
co.intervalSecs = secs
|
||||||
|
prefs.selectedTimeframe = interval
|
||||||
|
DataFeed.intervalChanged(secs)
|
||||||
|
}
|
||||||
|
|
||||||
function dataLoaded() {
|
function dataLoaded() {
|
||||||
const range = chartMeanRange()
|
const range = chartMeanRange()
|
||||||
console.log('new mean range', range,)
|
// console.log('new mean range', range,)
|
||||||
co.meanRange = range
|
co.meanRange = range
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,54 +92,75 @@ const subscribeEvents = [
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
let feeDropdown = null
|
let poolButtonTextElement = null
|
||||||
|
|
||||||
export function initFeeDropdown(w) {
|
function initFeeDropdown() {
|
||||||
widget = w
|
const button = widget.createButton()
|
||||||
widget.createDropdown(
|
button.setAttribute('title', 'See Pool Info and Choose Fee');
|
||||||
{
|
button.addEventListener('click', function () {
|
||||||
title: 'Fees',
|
co.showPoolSelection = true
|
||||||
tooltip: 'Choose Fee Tier',
|
});
|
||||||
items: [/*{title: 'Automatic Fee Selection', onSelect: () => {log('autofees')}}*/],
|
button.id = 'pool-button'
|
||||||
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28"><g fill="none" stroke="currentColor"><circle cx="10" cy="10" r="2.5"/><circle cx="18" cy="18" r="2.5"/><path stroke-linecap="square" d="M17.5 7.5l-7 13"/></g></svg>`,
|
|
||||||
}
|
button.style.height = '34px';
|
||||||
).then(dropdown => {
|
button.style.display = 'flex';
|
||||||
feeDropdown = dropdown;
|
button.style.alignItems = 'center';
|
||||||
updateFeeDropdown()
|
button.addEventListener('mouseover', () => {
|
||||||
})
|
button.style.backgroundColor = 'rgb(60,60,60)';
|
||||||
|
});
|
||||||
|
button.addEventListener('mouseout', () => {
|
||||||
|
button.style.backgroundColor = '';
|
||||||
|
});
|
||||||
|
button.style.margin = '2px 0';
|
||||||
|
button.style.borderRadius = '4px';
|
||||||
|
button.classList.add('pool-button')
|
||||||
|
|
||||||
|
let img = document.createElement('img');
|
||||||
|
img.src = '/arbitrum-logo.svg';
|
||||||
|
img.style.width = '1em';
|
||||||
|
img.style.marginRight = '0.2em';
|
||||||
|
button.appendChild(img);
|
||||||
|
|
||||||
|
img = document.createElement('img');
|
||||||
|
img.src = '/uniswap-logo.svg';
|
||||||
|
img.style.height = '1.25em';
|
||||||
|
img.style.marginRight = '0.2em';
|
||||||
|
img.style.backgroundColor = 'white';
|
||||||
|
img.style.borderRadius = '50%';
|
||||||
|
button.appendChild(img);
|
||||||
|
|
||||||
|
const span = document.createElement('span');
|
||||||
|
span.style.marginY = 'auto';
|
||||||
|
button.appendChild(span);
|
||||||
|
poolButtonTextElement = span
|
||||||
|
|
||||||
|
updateFeeDropdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateFeeDropdown() {
|
export function updateFeeDropdown() {
|
||||||
if (feeDropdown === null) return
|
if (poolButtonTextElement===null) return
|
||||||
const symbolItem = useChartOrderStore().selectedSymbol
|
const symbolItem = useChartOrderStore().selectedSymbol
|
||||||
let items
|
let text = ''
|
||||||
if (symbolItem === null)
|
text += (symbolItem.fee / 10000).toFixed(2) + '%'
|
||||||
items = [{title: '0.00%'}]
|
const index = symbolItem.feeGroup.findIndex((p) => p[1] === symbolItem.fee)
|
||||||
else {
|
if (symbolItem.liquiditySymbol) {
|
||||||
const feeGroup = symbolItem.feeGroup
|
const liq = symbolItem.liquidities[index]
|
||||||
items = feeGroup.map((p) => {
|
if (symbolItem.liquiditySymbol === 'USD')
|
||||||
const [_addr, fee] = p
|
text += ` $${toHuman(liq)}`
|
||||||
return {
|
else
|
||||||
title: (fee / 10000).toFixed(2) + '%',
|
text = ` ${toHuman(liq)} ${symbolItem.liquiditySymbol}`
|
||||||
onSelect: ()=>{
|
|
||||||
if (fee !== symbolItem.fee)
|
|
||||||
selectPool(fee)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
feeDropdown.applyOptions({items})
|
poolButtonTextElement.textContent = text
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectPool(fee) {
|
export function initTVButtons() {
|
||||||
const co = useChartOrderStore();
|
initFeeDropdown();
|
||||||
const s = co.selectedSymbol;
|
|
||||||
const ticker = feelessTickerKey(s.ticker) + '|' + fee
|
|
||||||
if (ticker !== s.ticker)
|
|
||||||
setSymbolTicker(ticker).catch((e)=>console.error('Could not change TV symbol to', ticker))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initWidget(el) {
|
export function initWidget(el) {
|
||||||
|
getAllSymbols()
|
||||||
|
const symbol = prefs.selectedTicker === null ? 'default' : prefs.selectedTicker
|
||||||
|
const interval = prefs.selectedTimeframe === null ? '15' : prefs.selectedTimeframe
|
||||||
widget = window.tvWidget = new TradingView.widget({
|
widget = window.tvWidget = new TradingView.widget({
|
||||||
|
|
||||||
// Widget Options
|
// Widget Options
|
||||||
@@ -134,21 +168,22 @@ export function initWidget(el) {
|
|||||||
library_path: "/charting_library/",
|
library_path: "/charting_library/",
|
||||||
// debug: true,
|
// debug: true,
|
||||||
autosize: true,
|
autosize: true,
|
||||||
symbol: 'default',
|
symbol,
|
||||||
interval: '15',
|
interval,
|
||||||
container: el,
|
container: el,
|
||||||
datafeed: DataFeed, // use this for ohlc
|
datafeed: DataFeed, // use this for ohlc
|
||||||
locale: "en",
|
locale: "en",
|
||||||
disabled_features: [],
|
disabled_features: ['main_series_scale_menu','display_market_status',],
|
||||||
enabled_features: ['saveload_separate_drawings_storage'],
|
enabled_features: ['saveload_separate_drawings_storage','snapshot_trading_drawings','show_exchange_logos','show_symbol_logos',],
|
||||||
drawings_access: {type: 'white', tools: [],}, // show no tools
|
// drawings_access: {type: 'white', tools: [],}, // show no tools
|
||||||
custom_themes: tvCustomThemes,
|
custom_themes: tvCustomThemes,
|
||||||
theme: useStore().theme,
|
theme: useStore().theme,
|
||||||
|
timezone: prefs.timezone,
|
||||||
|
|
||||||
// Chart Overrides
|
// Chart Overrides
|
||||||
// https://www.tradingview.com/charting-library-docs/latest/customization/overrides/chart-overrides
|
// https://www.tradingview.com/charting-library-docs/latest/customization/overrides/chart-overrides
|
||||||
overrides: {
|
overrides: {
|
||||||
// "mainSeriesProperties.priceAxisProperties.log": false,
|
"mainSeriesProperties.priceAxisProperties.log": false,
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -161,14 +196,24 @@ export function initWidget(el) {
|
|||||||
widget.subscribe('onSelectedLineToolChanged', onSelectedLineToolChanged)
|
widget.subscribe('onSelectedLineToolChanged', onSelectedLineToolChanged)
|
||||||
widget.subscribe('mouse_down', mouseDown)
|
widget.subscribe('mouse_down', mouseDown)
|
||||||
widget.subscribe('mouse_up', mouseUp)
|
widget.subscribe('mouse_up', mouseUp)
|
||||||
widget.headerReady().then(()=>initFeeDropdown(widget))
|
widget.headerReady().then(()=>initTVButtons())
|
||||||
widget.onChartReady(initChart)
|
widget.onChartReady(initChart)
|
||||||
console.log('tv widget initialized')
|
console.log('tv widget initialized')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function onChartReady(f) {
|
||||||
|
if (co.chartReady)
|
||||||
|
f(widget, chart)
|
||||||
|
else
|
||||||
|
chartInitCbs.push(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
let chartInitCbs = []
|
||||||
|
|
||||||
|
|
||||||
function initChart() {
|
function initChart() {
|
||||||
console.log('init chart')
|
// console.log('init chart')
|
||||||
chart = widget.activeChart()
|
chart = widget.activeChart()
|
||||||
const themeName = useStore().theme;
|
const themeName = useStore().theme;
|
||||||
widget.changeTheme(themeName).catch((e)=>console.warn(`Could not change theme to ${themeName}`, e))
|
widget.changeTheme(themeName).catch((e)=>console.warn(`Could not change theme to ${themeName}`, e))
|
||||||
@@ -191,7 +236,12 @@ function initChart() {
|
|||||||
}
|
}
|
||||||
changeInterval(widget.symbolInterval().interval)
|
changeInterval(widget.symbolInterval().interval)
|
||||||
co.chartReady = true
|
co.chartReady = true
|
||||||
console.log('chart ready')
|
setTimeout(()=>{
|
||||||
|
for (const cb of chartInitCbs)
|
||||||
|
cb(widget, chart)
|
||||||
|
chartInitCbs = []
|
||||||
|
}, 1)
|
||||||
|
// console.log('chart ready')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -236,13 +286,14 @@ let drawingCallbacks = null
|
|||||||
|
|
||||||
export function drawShape(shapeType, ...callbacks) {
|
export function drawShape(shapeType, ...callbacks) {
|
||||||
// puts the chart into a line-drawing mode for a new shape
|
// puts the chart into a line-drawing mode for a new shape
|
||||||
console.log('drawShape', callbacks, shapeType.name, shapeType.code)
|
// console.log('drawShape', callbacks, shapeType.name, shapeType.code)
|
||||||
if( drawingCallbacks )
|
if( drawingCallbacks )
|
||||||
invokeCallbacks(drawingCallbacks, 'onUndraw')
|
invokeCallbacks(drawingCallbacks, 'onUndraw')
|
||||||
drawingCallbacks = callbacks
|
drawingCallbacks = callbacks
|
||||||
drawingTool = null
|
drawingTool = null
|
||||||
previousDrawingTool = widget.selectedLineTool()
|
previousDrawingTool = widget.selectedLineTool()
|
||||||
co.drawing = true
|
co.drawing = true
|
||||||
|
co.drew = false
|
||||||
widget.selectLineTool(shapeType.code)
|
widget.selectLineTool(shapeType.code)
|
||||||
invokeCallbacks(callbacks, 'onDraw')
|
invokeCallbacks(callbacks, 'onDraw')
|
||||||
}
|
}
|
||||||
@@ -301,7 +352,7 @@ const shapeCallbacks = {}
|
|||||||
|
|
||||||
function onSelectedLineToolChanged() {
|
function onSelectedLineToolChanged() {
|
||||||
const tool = widget.selectedLineTool();
|
const tool = widget.selectedLineTool();
|
||||||
console.log('line tool changed', tool)
|
// console.log('line tool changed', tool)
|
||||||
if (drawingTool===null)
|
if (drawingTool===null)
|
||||||
drawingTool = tool
|
drawingTool = tool
|
||||||
else if (tool!==drawingTool && co.drawing)
|
else if (tool!==drawingTool && co.drawing)
|
||||||
@@ -351,9 +402,9 @@ function doHandleCrosshairMovement(point) {
|
|||||||
}
|
}
|
||||||
const points = structuredClone(shape.getPoints());
|
const points = structuredClone(shape.getPoints());
|
||||||
const lpbe = shape._model._linePointBeingEdited
|
const lpbe = shape._model._linePointBeingEdited
|
||||||
points[lpbe] = point
|
points[lpbe===null?0:lpbe] = point
|
||||||
// console.log('drag calling onPoints', points, shape, lpbe)
|
// console.log('calling onDrag', points, shape)
|
||||||
invokeCallbacks(shapeCallbacks[shapeId], 'onPoints', shapeId, shape, points)
|
invokeCallbacks(shapeCallbacks[shapeId], 'onDrag', shapeId, shape, points)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (draggingShapeIds.length > 0) {
|
else if (draggingShapeIds.length > 0) {
|
||||||
@@ -437,9 +488,11 @@ function doHandleDrawingEvent(id, event) {
|
|||||||
const props = shape.getProperties()
|
const props = shape.getProperties()
|
||||||
if (id in shapeCallbacks)
|
if (id in shapeCallbacks)
|
||||||
invokeCallbacks(shapeCallbacks[id], 'onProps', id, shape, props)
|
invokeCallbacks(shapeCallbacks[id], 'onProps', id, shape, props)
|
||||||
else
|
else {
|
||||||
// otherwise it's an event on a shape we don't "own"
|
// otherwise it's an event on a shape we don't "own" that could be being drawn
|
||||||
|
co.drew = true
|
||||||
console.log('warning: ignoring setProperties on TV shape', id, props)
|
console.log('warning: ignoring setProperties on TV shape', id, props)
|
||||||
|
}
|
||||||
} else if (event === 'move') {
|
} else if (event === 'move') {
|
||||||
if (id in shapeCallbacks) {
|
if (id in shapeCallbacks) {
|
||||||
invokeCallbacks(shapeCallbacks[id], 'onMove', id, shape)
|
invokeCallbacks(shapeCallbacks[id], 'onMove', id, shape)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import {provider} from "@/blockchain/provider.js";
|
||||||
import {convertTvResolution, loadOHLC} from './ohlc.js';
|
import {convertTvResolution, loadOHLC} from './ohlc.js';
|
||||||
import {metadata} from "@/version.js";
|
import {metadata} from "@/version.js";
|
||||||
import FlexSearch from "flexsearch";
|
import FlexSearch from "flexsearch";
|
||||||
@@ -5,8 +6,10 @@ import {useChartOrderStore} from "@/orderbuild.js";
|
|||||||
import {useStore} from "@/store/store.js";
|
import {useStore} from "@/store/store.js";
|
||||||
import {subOHLC, unsubOHLC} from "@/blockchain/ohlcs.js";
|
import {subOHLC, unsubOHLC} from "@/blockchain/ohlcs.js";
|
||||||
import {ohlcStart} from "@/charts/chart-misc.js";
|
import {ohlcStart} from "@/charts/chart-misc.js";
|
||||||
|
import {timestamp, withTimeout} from "@/common.js";
|
||||||
|
import {erc20Contract} from "@/blockchain/contract.js";
|
||||||
|
import {track} from "@/track.js";
|
||||||
|
|
||||||
import {timestamp} from "@/common.js";
|
|
||||||
|
|
||||||
const DEBUG_LOGGING = false
|
const DEBUG_LOGGING = false
|
||||||
const log = DEBUG_LOGGING ? console.log : ()=>{}
|
const log = DEBUG_LOGGING ? console.log : ()=>{}
|
||||||
@@ -61,7 +64,7 @@ const configurationData = {
|
|||||||
value: 'UNIv3',
|
value: 'UNIv3',
|
||||||
name: 'Uniswap v3',
|
name: 'Uniswap v3',
|
||||||
desc: 'Uniswap v3',
|
desc: 'Uniswap v3',
|
||||||
logo: 'https://upload.wikimedia.org/wikipedia/commons/e/e7/Uniswap_Logo.svg',
|
logo: '/uniswap-logo.svg',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
// The `symbols_types` arguments are used for the `searchSymbols` method if a user selects this symbol type
|
// The `symbols_types` arguments are used for the `searchSymbols` method if a user selects this symbol type
|
||||||
@@ -108,8 +111,10 @@ export function feelessTickerKey(ticker) {
|
|||||||
|
|
||||||
function addSymbol(chainId, p, base, quote, inverted) {
|
function addSymbol(chainId, p, base, quote, inverted) {
|
||||||
const symbol = base.s + '/' + quote.s
|
const symbol = base.s + '/' + quote.s
|
||||||
const fee = `${(p.f/10000).toFixed(2)}%`
|
// const fee = `${(p.f/10000).toFixed(2)}%`
|
||||||
const exchange = ['Uniswap v2', 'Uniswap v3'][p.e] + ' ' + fee
|
// const exchange = ['Uniswap v2', 'Uniswap v3'][p.e] + ' ' + fee
|
||||||
|
const exchange = ['Uniswap v2', 'Uniswap v3'][p.e]
|
||||||
|
const exchange_logo = '/uniswap-logo.svg'
|
||||||
const full_name = exchange + ':' + symbol // + '%' + formatFee(fee)
|
const full_name = exchange + ':' + symbol // + '%' + formatFee(fee)
|
||||||
const ticker = tickerKey(chainId, p.e, base.a, quote.a, p.f)
|
const ticker = tickerKey(chainId, p.e, base.a, quote.a, p.f)
|
||||||
// add the search index only if this is the natural, noninverted base/quote pair
|
// add the search index only if this is the natural, noninverted base/quote pair
|
||||||
@@ -120,7 +125,7 @@ function addSymbol(chainId, p, base, quote, inverted) {
|
|||||||
const symbolInfo = {
|
const symbolInfo = {
|
||||||
key: ticker, ticker,
|
key: ticker, ticker,
|
||||||
chainId, address: p.a, exchangeId: p.e,
|
chainId, address: p.a, exchangeId: p.e,
|
||||||
full_name, symbol, description, exchange, type, inverted, base, quote, decimals, x:p.x, fee:p.f,
|
full_name, symbol, description, exchange, exchange_logo, type, inverted, base, quote, decimals, x:p.x, fee:p.f,
|
||||||
};
|
};
|
||||||
_symbols[ticker] = symbolInfo
|
_symbols[ticker] = symbolInfo
|
||||||
const feelessKey = feelessTickerKey(ticker)
|
const feelessKey = feelessTickerKey(ticker)
|
||||||
@@ -131,8 +136,13 @@ function addSymbol(chainId, p, base, quote, inverted) {
|
|||||||
else
|
else
|
||||||
feeGroups[feelessKey] = [[symbolInfo.address, symbolInfo.fee]]
|
feeGroups[feelessKey] = [[symbolInfo.address, symbolInfo.fee]]
|
||||||
symbolInfo.feeGroup = feeGroups[feelessKey]
|
symbolInfo.feeGroup = feeGroups[feelessKey]
|
||||||
if (defaultSymbol===null && !invertedDefault(symbolInfo.base.a, symbolInfo.quote.a))
|
// if (defaultSymbol===null) {
|
||||||
|
// console.log(`invertedDefault(${symbolInfo.base.s}, ${symbolInfo.quote.s})`,invertedDefault(symbolInfo.base.a, symbolInfo.quote.a))
|
||||||
|
// }
|
||||||
|
if (defaultSymbol===null && !invertedDefault(symbolInfo.base.a, symbolInfo.quote.a)) {
|
||||||
|
console.log('setting default symbol', symbolInfo.base.s, symbolInfo.quote.s, symbolInfo.base.a, symbolInfo.quote.a)
|
||||||
defaultSymbol = _symbols[ticker]
|
defaultSymbol = _symbols[ticker]
|
||||||
|
}
|
||||||
log('new symbol', ticker, _symbols[ticker])
|
log('new symbol', ticker, _symbols[ticker])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,6 +335,14 @@ class RealtimeSubscription {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function getLiquidities(markToken, symbolItem) {
|
||||||
|
const token = await erc20Contract(markToken.a, provider)
|
||||||
|
const liquidities = await Promise.all(symbolItem.feeGroup.map(
|
||||||
|
async ([addr, fee]) => await token.balanceOf(addr)
|
||||||
|
))
|
||||||
|
return liquidities;
|
||||||
|
}
|
||||||
|
|
||||||
export const DataFeed = {
|
export const DataFeed = {
|
||||||
onReady(callback) {
|
onReady(callback) {
|
||||||
log('[onReady]: Method call');
|
log('[onReady]: Method call');
|
||||||
@@ -349,6 +367,8 @@ export const DataFeed = {
|
|||||||
result.push(_symbols[ticker])
|
result.push(_symbols[ticker])
|
||||||
seen[ticker] = true
|
seen[ticker] = true
|
||||||
}
|
}
|
||||||
|
if (userInput.length>=3)
|
||||||
|
track('search', {search_term: userInput})
|
||||||
onResultReadyCallback(result);
|
onResultReadyCallback(result);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -369,22 +389,59 @@ export const DataFeed = {
|
|||||||
onResolveErrorCallback,
|
onResolveErrorCallback,
|
||||||
extension
|
extension
|
||||||
) {
|
) {
|
||||||
log('[resolveSymbol]: Method call', symbolName);
|
console.log('resolveSymbol', symbolName);
|
||||||
const symbols = getAllSymbols();
|
const symbols = getAllSymbols();
|
||||||
const symbolItem = symbolName === 'default' ? defaultSymbol : symbols[symbolName]
|
const symbolItem = symbolName === 'default' ? defaultSymbol : symbols[symbolName]
|
||||||
|
if (symbolName==='default') {
|
||||||
|
console.log('using default symbol', defaultSymbol)
|
||||||
|
}
|
||||||
if (!symbolItem) {
|
if (!symbolItem) {
|
||||||
log('[resolveSymbol]: Cannot resolve symbol', symbolName);
|
console.log('[resolveSymbol]: Cannot resolve symbol', symbolName);
|
||||||
onResolveErrorCallback('cannot resolve symbol');
|
onResolveErrorCallback('cannot resolve symbol');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const co = useChartOrderStore();
|
const co = useChartOrderStore();
|
||||||
co.selectedSymbol = symbolItem
|
co.selectedSymbol = symbolItem
|
||||||
const feelessKey = feelessTickerKey(symbolItem.ticker)
|
|
||||||
const symbolsByFee = feeGroups[feelessKey]
|
let ticker = symbolItem.ticker
|
||||||
symbolsByFee.sort((a,b)=>a.fee-b.fee)
|
try {
|
||||||
const pool = symbolsByFee[Math.floor((symbolsByFee.length - 1)/2)] // median rounded down
|
if (!symbolItem.liquiditySymbol) {
|
||||||
// noinspection JSValidateTypes
|
// fetch liquidities and cache on the symbolItem
|
||||||
co.selectedPool = pool // todo remove
|
const inv = invertedDefault(symbolItem.base.a, symbolItem.quote.a)
|
||||||
|
const markToken = inv ? symbolItem.base : symbolItem.quote
|
||||||
|
const mark = useStore().markPrice(markToken.a)
|
||||||
|
const liquidities = await withTimeout(
|
||||||
|
getLiquidities(markToken, symbolItem),
|
||||||
|
3000,
|
||||||
|
'liquidity fetch timeout'
|
||||||
|
)
|
||||||
|
symbolItem.liquidities = liquidities.map(l => Number(l / 10n ** BigInt(markToken.d)))
|
||||||
|
if (mark) {
|
||||||
|
symbolItem.liquidities = symbolItem.liquidities.map(l => l * mark)
|
||||||
|
symbolItem.liquiditySymbol = 'USD'
|
||||||
|
} else {
|
||||||
|
symbolItem.liquiditySymbol = symbolItem.quote.s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const liqsAndFees = []
|
||||||
|
for (let i=0; i<symbolItem.feeGroup.length; i++) {
|
||||||
|
const [addr, fee] = symbolItem.feeGroup[i]
|
||||||
|
const liq = symbolItem.liquidities[i]
|
||||||
|
liqsAndFees.push([liq, fee])
|
||||||
|
if (fee === symbolItem.fee)
|
||||||
|
symbolItem.liquidity = liq
|
||||||
|
}
|
||||||
|
liqsAndFees.sort((a,b) => b[0] - a[0])
|
||||||
|
const highestLiquidityFee = liqsAndFees[0][1]
|
||||||
|
// console.log('liquidities', liqsAndFees)
|
||||||
|
// console.log('best liquidity', highestLiquidityFee, liqsAndFees[0][0], symbolItem.liquiditySymbol)
|
||||||
|
ticker = feelessTickerKey(ticker) + '|' + highestLiquidityFee
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
// use the median fee group instead
|
||||||
|
console.log('liquidity fetch error', error)
|
||||||
|
}
|
||||||
|
|
||||||
// LibrarySymbolInfo
|
// LibrarySymbolInfo
|
||||||
// https://www.tradingview.com/charting-library-docs/latest/api/interfaces/Charting_Library.LibrarySymbolInfo
|
// https://www.tradingview.com/charting-library-docs/latest/api/interfaces/Charting_Library.LibrarySymbolInfo
|
||||||
const symbolInfo = {
|
const symbolInfo = {
|
||||||
@@ -406,7 +463,7 @@ export const DataFeed = {
|
|||||||
// volume_precision: 2,
|
// volume_precision: 2,
|
||||||
data_status: 'streaming',
|
data_status: 'streaming',
|
||||||
};
|
};
|
||||||
log('[resolveSymbol]: Symbol resolved', symbolName);
|
// console.log('[resolveSymbol]: Symbol resolved', symbolName);
|
||||||
onSymbolResolvedCallback(symbolInfo)
|
onSymbolResolvedCallback(symbolInfo)
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -684,4 +741,4 @@ export const DataFeed = {
|
|||||||
|
|
||||||
|
|
||||||
let _rolloverBumper = null
|
let _rolloverBumper = null
|
||||||
let defaultSymbol = null
|
export let defaultSymbol = null
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ function addDay(timestamp) {
|
|||||||
function addMonth(timestamp) {
|
function addMonth(timestamp) {
|
||||||
const date = new Date(timestamp*1000)
|
const date = new Date(timestamp*1000)
|
||||||
const result = Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate()) / 1000
|
const result = Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate()) / 1000
|
||||||
console.log('addMonth', timestamp, result, new Date(timestamp*1000), new Date(result*1000))
|
// console.log('addMonth', timestamp, result, new Date(timestamp*1000), new Date(result*1000))
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ async function getUrl(url) {
|
|||||||
|
|
||||||
|
|
||||||
export async function loadOHLC (symbol, contract, from, to, tvRes) {
|
export async function loadOHLC (symbol, contract, from, to, tvRes) {
|
||||||
console.log('loadOHLC', tvRes, new Date(1000*from), new Date(1000*to), symbol, contract);
|
// console.log('loadOHLC', tvRes, new Date(1000*from), new Date(1000*to), symbol, contract);
|
||||||
let chainId
|
let chainId
|
||||||
let bars = [];
|
let bars = [];
|
||||||
let inverted = false;
|
let inverted = false;
|
||||||
@@ -179,7 +179,7 @@ export async function loadOHLC (symbol, contract, from, to, tvRes) {
|
|||||||
if (response.length) {
|
if (response.length) {
|
||||||
const [start,end,price] = response.split(',')
|
const [start,end,price] = response.split(',')
|
||||||
seriesStarts[baseUrl] = parseInt(start)
|
seriesStarts[baseUrl] = parseInt(start)
|
||||||
console.log(`Series ${baseUrl} starts at ${new Date(start*1000)}`)
|
// console.log(`Series ${baseUrl} starts at ${new Date(start*1000)}`)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.error(`Bad response while fetching ${baseUrl+'quote.csv'}`)
|
console.error(`Bad response while fetching ${baseUrl+'quote.csv'}`)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {DISTANT_FUTURE, DISTANT_PAST, MAX_FRACTION} from "@/blockchain/orderlib.js";
|
import {DISTANT_FUTURE, DISTANT_PAST, MAX_FRACTION} from "@/blockchain/orderlib.js";
|
||||||
import {allocationText, DLine, HLine} from "@/charts/shape.js";
|
import {allocationText, DLine, HLine} from "@/charts/shape.js";
|
||||||
import {createShape, deleteShapeId} from "@/charts/chart.js";
|
import {createShape, deleteShapeId, widget} from "@/charts/chart.js";
|
||||||
import {sideColor} from "@/misc.js";
|
import {sideColor} from "@/misc.js";
|
||||||
import {useChartOrderStore} from "@/orderbuild.js";
|
import {useChartOrderStore} from "@/orderbuild.js";
|
||||||
import {timestamp} from "@/common.js";
|
import {timestamp} from "@/common.js";
|
||||||
@@ -87,9 +87,11 @@ class TrancheShapes {
|
|||||||
price *= scale
|
price *= scale
|
||||||
// console.log('price', price)
|
// console.log('price', price)
|
||||||
const channel = buy?'low':'high';
|
const channel = buy?'low':'high';
|
||||||
const text = allocationText(buy, weight, amount, amountSymbol, '\n')
|
const text = allocationText(buy, weight, amount, amountSymbol, amountIsBase ? null : this.symbol.base.s, 1, '\n')
|
||||||
const s = createShape(buy?'arrow_up':'arrow_down', {time, price}, {channel,text,lock:true})
|
const color = sideColor(buy);
|
||||||
// console.log('created fill shape at', time, price)
|
const options = {channel,text,lock:true,overrides:{color: color, arrowColor: color}};
|
||||||
|
const s = createShape(buy?'arrow_up':'arrow_down', {time, price}, options)
|
||||||
|
console.log('created fill shape at', time, price, widget.activeChart().getShapeById(s).getProperties())
|
||||||
this.fills.push(s)
|
this.fills.push(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +124,7 @@ class TrancheShapes {
|
|||||||
// console.log('hline', price)
|
// console.log('hline', price)
|
||||||
const model = {
|
const model = {
|
||||||
price, breakout, color, extraText, textLocation,
|
price, breakout, color, extraText, textLocation,
|
||||||
allocation, maxAllocation, amount, amountSymbol,
|
allocation, maxAllocation, amount, amountSymbol, buy,
|
||||||
}
|
}
|
||||||
const s = new HLine(model, null, null, null, true)
|
const s = new HLine(model, null, null, null, true)
|
||||||
this.shapes.push(s)
|
this.shapes.push(s)
|
||||||
@@ -146,7 +148,7 @@ class TrancheShapes {
|
|||||||
extendLeft: t.startTime === DISTANT_PAST,
|
extendLeft: t.startTime === DISTANT_PAST,
|
||||||
extendRight: t.endTime === DISTANT_FUTURE,
|
extendRight: t.endTime === DISTANT_FUTURE,
|
||||||
breakout, color, extraText, textLocation,
|
breakout, color, extraText, textLocation,
|
||||||
allocation, maxAllocation, amount, amountSymbol,
|
allocation, maxAllocation, amount, amountSymbol, buy,
|
||||||
}
|
}
|
||||||
const s = new DLine(model, null, null, null, true)
|
const s = new DLine(model, null, null, null, true)
|
||||||
this.shapes.push(s)
|
this.shapes.push(s)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {invokeCallback, mixin} from "@/common.js";
|
|||||||
import {chart, createShape, deleteShapeId, dragging, draggingShapeIds, drawShape, widget} from "@/charts/chart.js";
|
import {chart, createShape, deleteShapeId, dragging, draggingShapeIds, drawShape, widget} from "@/charts/chart.js";
|
||||||
import Color from "color";
|
import Color from "color";
|
||||||
import {dirtyItems, dirtyPoints, nearestOhlcStart} from "@/charts/chart-misc.js";
|
import {dirtyItems, dirtyPoints, nearestOhlcStart} from "@/charts/chart-misc.js";
|
||||||
import {defined} from "@/misc.js";
|
import {defined, toPrecision} from "@/misc.js";
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -36,31 +36,35 @@ export const ShapeType = {
|
|||||||
HLine: {name: 'Horizontal Line', code: 'horizontal_line', drawingProp: 'linetoolhorzline'},
|
HLine: {name: 'Horizontal Line', code: 'horizontal_line', drawingProp: 'linetoolhorzline'},
|
||||||
VLine: {name: 'Vertical Line', code: 'vertical_line', drawingProp: 'linetoolvertline'},
|
VLine: {name: 'Vertical Line', code: 'vertical_line', drawingProp: 'linetoolvertline'},
|
||||||
PriceRange: {name: 'Price Range', code: 'price_range'},
|
PriceRange: {name: 'Price Range', code: 'price_range'},
|
||||||
|
DateRange: {name: 'Date Range', code: 'date_range', drawingProp: 'linetooldaterange'},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function allocationText(buy, weight, amount, symbol, separator = ' ') {
|
export function allocationText(buy, weight, amount, baseSymbol, amountSymbol = null, parts = 1, separator = ' ') {
|
||||||
// set breakout=true for a buy breakout and breakout=false for a sell breakout
|
|
||||||
const hasAmount = amount !== null && amount !== undefined && amount > 0
|
const hasAmount = amount !== null && amount !== undefined && amount > 0
|
||||||
if (hasAmount)
|
if (hasAmount)
|
||||||
amount = Number(amount)
|
amount = Number(amount)
|
||||||
const hasWeight = weight !== null && weight !== undefined && weight !== 1
|
const hasWeight = weight !== null && weight !== undefined && weight !== 1
|
||||||
if (hasWeight)
|
if (hasWeight)
|
||||||
weight = Number(weight)
|
weight = Number(weight)
|
||||||
if (buy===undefined)
|
|
||||||
console.error('allocation text buy', buy)
|
|
||||||
let text = buy === undefined ? '' : buy ? 'Buy ' : 'Sell '
|
let text = buy === undefined ? '' : buy ? 'Buy ' : 'Sell '
|
||||||
if (hasWeight)
|
if (hasWeight)
|
||||||
text += `${(weight * 100).toFixed(1)}%`
|
text += `${(weight * 100).toFixed(1)}%`
|
||||||
const hasSymbol = symbol !== null && symbol !== undefined
|
const hasSymbol = baseSymbol !== null && baseSymbol !== undefined
|
||||||
if (hasAmount && hasSymbol) {
|
if (hasAmount && hasSymbol) {
|
||||||
if (hasWeight)
|
if (hasWeight)
|
||||||
text += separator
|
text += separator
|
||||||
text += `${amount.toPrecision(3).toLocaleString('fullwide')} ${symbol}`
|
if (amountSymbol!==null && amountSymbol!==baseSymbol)
|
||||||
|
text += `${baseSymbol} worth ${toPrecision(amount,3)} ${amountSymbol}`
|
||||||
|
else
|
||||||
|
text += `${toPrecision(amount,3)} ${baseSymbol}`
|
||||||
}
|
}
|
||||||
|
if (parts > 1)
|
||||||
|
text += separator + `in ${Math.round(parts)} parts`
|
||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class Shape {
|
export class Shape {
|
||||||
|
|
||||||
constructor(type, onModel=null, onDelete=null, props=null, readonly=false, overrides={}) {
|
constructor(type, onModel=null, onDelete=null, props=null, readonly=false, overrides={}) {
|
||||||
@@ -110,6 +114,7 @@ export class Shape {
|
|||||||
this.model.maxAllocation = null
|
this.model.maxAllocation = null
|
||||||
// both amount and amountSymbol must be set in order to display amount text
|
// both amount and amountSymbol must be set in order to display amount text
|
||||||
this.model.amount = null
|
this.model.amount = null
|
||||||
|
this.model.baseSymbol = null
|
||||||
this.model.amountSymbol = null
|
this.model.amountSymbol = null
|
||||||
this.model.extraText = null
|
this.model.extraText = null
|
||||||
this.model.textLocation = null // defaults to 'above' if not set
|
this.model.textLocation = null // defaults to 'above' if not set
|
||||||
@@ -132,6 +137,8 @@ export class Shape {
|
|||||||
this.model.amount = model.amount
|
this.model.amount = model.amount
|
||||||
if (defined(model.amountSymbol))
|
if (defined(model.amountSymbol))
|
||||||
this.model.amountSymbol = model.amountSymbol
|
this.model.amountSymbol = model.amountSymbol
|
||||||
|
if (defined(model.baseSymbol))
|
||||||
|
this.model.baseSymbol = model.baseSymbol
|
||||||
if (defined(model.extraText))
|
if (defined(model.extraText))
|
||||||
this.model.extraText = model.extraText
|
this.model.extraText = model.extraText
|
||||||
if (defined(model.breakout))
|
if (defined(model.breakout))
|
||||||
@@ -164,9 +171,9 @@ export class Shape {
|
|||||||
newProps.linecolor = color
|
newProps.linecolor = color
|
||||||
|
|
||||||
// text label
|
// text label
|
||||||
let text = allocationText(this.model.buy, this.model.allocation, this.model.amount, this.model.amountSymbol)
|
let text = allocationText(this.model.buy, this.model.allocation, this.model.amount, this.model.baseSymbol, this.model.amountSymbol)
|
||||||
if (this.model.breakout)
|
if (this.model.breakout)
|
||||||
text += ' ' + (this.model.textLocation==='above' ? '▲Breakout▲' : '▼Breakout▼')
|
text += ' ' + (this.model.textLocation==='above' ? '▲Breakout▲' : '▼Breakdown▼')
|
||||||
if (this.model.extraText)
|
if (this.model.extraText)
|
||||||
text += ' '+this.model.extraText
|
text += ' '+this.model.extraText
|
||||||
if (this.debug) text = `${this.id} ` + text
|
if (this.debug) text = `${this.id} ` + text
|
||||||
@@ -324,14 +331,16 @@ export class Shape {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// diagonals need to override this to adjust their price as well.
|
// diagonals need to override this to adjust their price as well.
|
||||||
pointsToTvOhlcStart(points, periodSeconds=null) {
|
pointsToTvOhlcStart(points, periodSeconds = null) {
|
||||||
return points === null ? null : points.map((p) => {
|
return points === null ? null : points.map((p) => {
|
||||||
return {time: nearestOhlcStart(p.time, periodSeconds), price: p.price}
|
return {time: nearestOhlcStart(p.time, periodSeconds), price: p.price}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onPoints(points) {} // the control points of an existing shape were changed
|
onPoints(points) {} // the control points of an existing shape were changed
|
||||||
|
|
||||||
|
onDrag(points) { this.onPoints(points) }
|
||||||
|
|
||||||
setProps(props) {
|
setProps(props) {
|
||||||
if (!props || Object.keys(props).length===0) return
|
if (!props || Object.keys(props).length===0) return
|
||||||
if (this.debug) console.log('setProps', this.id, props)
|
if (this.debug) console.log('setProps', this.id, props)
|
||||||
@@ -381,7 +390,6 @@ export class Shape {
|
|||||||
onUndraw() {} // drawing was canceled by clicking on a different tool
|
onUndraw() {} // drawing was canceled by clicking on a different tool
|
||||||
onAddPoint() {} // the user clicked a point while drawing (that point is added to the points list)
|
onAddPoint() {} // the user clicked a point while drawing (that point is added to the points list)
|
||||||
onMove(points) {} // the shape was moved by dragging a drawing element not the control point
|
onMove(points) {} // the shape was moved by dragging a drawing element not the control point
|
||||||
onDrag(points) {}
|
|
||||||
onHide(props) {}
|
onHide(props) {}
|
||||||
onShow(props) {}
|
onShow(props) {}
|
||||||
onClick() {} // the shape was selected
|
onClick() {} // the shape was selected
|
||||||
@@ -476,16 +484,6 @@ class ShapeTVCallbacks {
|
|||||||
|
|
||||||
|
|
||||||
export class Line extends Shape {
|
export class Line extends Shape {
|
||||||
onDrag(points) {
|
|
||||||
const s = this.tvShape();
|
|
||||||
if (this.debug) {
|
|
||||||
console.log('shape', s.id, s)
|
|
||||||
console.log('currentMovingPoint', s._source.currentMovingPoint())
|
|
||||||
console.log('startMovingPoint', s._source.startMovingPoint())
|
|
||||||
console.log('isBeingEdited', s._source.isBeingEdited())
|
|
||||||
console.log('state', s._source.state())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -653,3 +651,17 @@ export class DLine extends Line {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class DateRange extends Shape {
|
||||||
|
constructor(model, onModel=null, onDelete=null, props=null) {
|
||||||
|
super(ShapeType.DateRange, onModel, onDelete, props)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
setModel(model) {
|
||||||
|
super.setModel(model);
|
||||||
|
if (model.startTime !== this.model.startTime || model.endTime !== this.model.endTime)
|
||||||
|
this.setPoints([{time: model.startTime}, {time: model.endTime}])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
export const NATIVE_TOKEN = '0x0000000000000000000000000000000000000001'
|
||||||
|
export const USD_FIAT = '0x0000000000000000000000000000000000000055' // We use 0x55 (ASCII 'U') to indicate the use of fiat USD
|
||||||
|
|
||||||
export function mixin(child, ...parents) {
|
export function mixin(child, ...parents) {
|
||||||
// child is modified directly, assigning fields from parents that are missing in child. parents fields are
|
// child is modified directly, assigning fields from parents that are missing in child. parents fields are
|
||||||
// assigned by parents order, highest priority first
|
// assigned by parents order, highest priority first
|
||||||
@@ -150,4 +153,38 @@ export function timestamp(date = null) {
|
|||||||
|
|
||||||
export function dateString(datetime) {
|
export function dateString(datetime) {
|
||||||
return datetime.toLocaleString({dateStyle: 'medium', timeStyle: 'short'})
|
return datetime.toLocaleString({dateStyle: 'medium', timeStyle: 'short'})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function logicalXOR(a, b) {
|
||||||
|
return (a || b) && !(a && b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base62
|
||||||
|
|
||||||
|
// base62.js
|
||||||
|
const base62charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||||
|
|
||||||
|
export function encodeBase62(num) {
|
||||||
|
if (num === 0) return base62charset[0];
|
||||||
|
let encoded = "";
|
||||||
|
while (num > 0) {
|
||||||
|
encoded = base62charset[num % 62] + encoded;
|
||||||
|
num = Math.floor(num / 62);
|
||||||
|
}
|
||||||
|
return encoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodeBase62(str) {
|
||||||
|
return str.split('').reverse().reduce((acc, char, i) =>
|
||||||
|
acc + base62charset.indexOf(char) * Math.pow(62, i), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function withTimeout(promise, timeoutDuration, fallbackErrorMessage) {
|
||||||
|
return Promise.race([
|
||||||
|
promise,
|
||||||
|
new Promise((_, reject) =>
|
||||||
|
setTimeout(() => reject(new Error(fallbackErrorMessage)), timeoutDuration)
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,17 +13,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {useStore} from "@/store/store";
|
import {usePrefStore, useStore} from "@/store/store";
|
||||||
import {computed} from "vue";
|
import {computed} from "vue";
|
||||||
import {DateTime, Info} from "luxon";
|
import {DateTime, Info} from "luxon";
|
||||||
|
|
||||||
const s = useStore()
|
const prefs = usePrefStore()
|
||||||
const props = defineProps(['modelValue'])
|
const props = defineProps(['modelValue'])
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
const hideDetails = true
|
const hideDetails = true
|
||||||
|
|
||||||
const now = computed(()=>DateTime.fromSeconds(props.modelValue?props.modelValue:0).setZone(s.timeZone))
|
const now = computed(()=>DateTime.fromSeconds(props.modelValue?props.modelValue:0).setZone(prefs.timezone))
|
||||||
|
|
||||||
const year = computed({
|
const year = computed({
|
||||||
get() { return now.value.year },
|
get() { return now.value.year },
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<v-alert class="d-sm-none" type="info" title="Mobile Screen" text="Dexorder is designed for desktop. Mobile coming soon!" rounded="0"/>
|
||||||
<v-alert v-if='!s.connected' icon="mdi-wifi-off" type="error" title="Not Connected" class="mb-3" rounded="0" density="compact"/>
|
<v-alert v-if='!s.connected' icon="mdi-wifi-off" type="error" title="Not Connected" class="mb-3" rounded="0" density="compact"/>
|
||||||
<v-alert v-for="e in s.errors" icon="mdi-alert" type="error" :title="e.title" :text="e.text" class="mb-3" :closable="e.closeable" rounded="0"/>
|
<v-alert v-for="e in s.errors" icon="mdi-alert" type="error" :title="e.title" :text="e.text" class="mb-3" :closable="e.closeable" rounded="0"/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {useStore} from "@/store/store";
|
import {useStore} from "@/store/store";
|
||||||
import {socket} from "@/socket.js";
|
|
||||||
import {useRoute} from "vue-router";
|
import {useRoute} from "vue-router";
|
||||||
|
import {socket} from "@/socket.js";
|
||||||
|
|
||||||
const s = useStore()
|
const s = useStore()
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<!--
|
||||||
<v-chip text="BETA" size='x-small' color="red" class="align-self-start" variant="text"/>
|
<v-chip text="BETA" size='x-small' color="red" class="align-self-start" variant="text"/>
|
||||||
|
-->
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-tooltip :model-value="!error&&copied" :open-on-hover="false" location="top">
|
<v-tooltip :model-value="!error&&copied" :open-on-hover="false" location="top">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<span v-bind="props" style="cursor: pointer" @click="copy()">
|
<div class="d-inline-flex align-center" v-bind="props" style="cursor: pointer" @click="copy()">
|
||||||
|
<slot>
|
||||||
|
<span :style="{maxWidth:width}" style="display: inline-block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">{{ text }}</span>
|
||||||
|
</slot>
|
||||||
<v-btn v-bind="props" v-if="permitted" rounded variant="text" size="small" density="compact" @click="copy()"
|
<v-btn v-bind="props" v-if="permitted" rounded variant="text" size="small" density="compact" @click="copy()"
|
||||||
:class="error?'error':copied?'success':''"
|
:class="error?'error':copied?'success':''"
|
||||||
:icon="error?'mdi-close-box-outline':copied?'mdi-check-circle-outline':'mdi-content-copy'"
|
:icon="error?'mdi-close-box-outline':copied?'mdi-check-circle-outline':'mdi-content-copy'"
|
||||||
:text="showText?text:''"
|
:text="showText?text:''"
|
||||||
/>
|
/>
|
||||||
<slot>{{text}}</slot>
|
</div>
|
||||||
</span>
|
|
||||||
</template>
|
</template>
|
||||||
<span>Copied!</span>
|
<span>Copied!</span>
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
@@ -17,7 +19,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {ref} from "vue";
|
import {ref} from "vue";
|
||||||
|
|
||||||
const props = defineProps({text:String, showText:{default:false}})
|
const props = defineProps({text:String, showText:{default:false}, width: {default:null}})
|
||||||
const permitted = ref(true)
|
const permitted = ref(true)
|
||||||
const copied = ref(false)
|
const copied = ref(false)
|
||||||
const error = ref(false)
|
const error = ref(false)
|
||||||
|
|||||||
65
src/components/DebugConsole.vue
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<template>
|
||||||
|
<div class="debug-console">
|
||||||
|
<div v-for="(entry, idx) in logs" :key="idx" :class="['log-entry', entry.type]">
|
||||||
|
[{{ entry.type }}] {{ entry.message }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, onBeforeUnmount } from 'vue';
|
||||||
|
|
||||||
|
const logs = ref([]);
|
||||||
|
|
||||||
|
function stringify(args) {
|
||||||
|
return args.map(arg => {
|
||||||
|
if (typeof arg === 'object') {
|
||||||
|
try { return JSON.stringify(arg); } catch { return String(arg); }
|
||||||
|
}
|
||||||
|
return String(arg);
|
||||||
|
}).join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
let original = {};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
['log', 'warn', 'error', 'info'].forEach(type => {
|
||||||
|
// Save the original method
|
||||||
|
original[type] = console[type];
|
||||||
|
console[type] = function(...args) {
|
||||||
|
logs.value.push({
|
||||||
|
type,
|
||||||
|
message: stringify(args)
|
||||||
|
});
|
||||||
|
// Optionally limit log length:
|
||||||
|
if (logs.value.length > 200) logs.value.shift();
|
||||||
|
original[type].apply(console, args);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
// Restore the original methods
|
||||||
|
Object.keys(original).forEach(type => {
|
||||||
|
if (original[type]) console[type] = original[type];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.debug-console {
|
||||||
|
background: #1a1a1a;
|
||||||
|
color: #0f0;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 10px;
|
||||||
|
max-height: 260px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
.log-entry { margin-bottom: 2px; }
|
||||||
|
.log-entry.warn { color: #ff0; }
|
||||||
|
.log-entry.error { color: #f55; }
|
||||||
|
.log-entry.info { color: #0cf; }
|
||||||
|
</style>
|
||||||
@@ -36,8 +36,8 @@
|
|||||||
import {useStore} from "@/store/store";
|
import {useStore} from "@/store/store";
|
||||||
import {computed, ref} from "vue";
|
import {computed, ref} from "vue";
|
||||||
import Btn from "@/components/Btn.vue";
|
import Btn from "@/components/Btn.vue";
|
||||||
import {socket} from "@/socket.js";
|
|
||||||
import {metadata} from "@/version.js";
|
import {metadata} from "@/version.js";
|
||||||
|
import {socket} from "@/socket.js";
|
||||||
|
|
||||||
const DISABLED_DURATION = 60_000;
|
const DISABLED_DURATION = 60_000;
|
||||||
|
|
||||||
|
|||||||
142
src/components/FloatingDiv.vue
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="floatingDiv"
|
||||||
|
:style="divStyle"
|
||||||
|
@mousedown="startDrag"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const floatingDiv = ref(null);
|
||||||
|
const position = reactive({ x: 0, y: 0 });
|
||||||
|
const isDragging = ref(false);
|
||||||
|
const isFloating = ref(false);
|
||||||
|
const dragStartOffset = reactive({ x: 0, y: 0 });
|
||||||
|
|
||||||
|
const divStyle = reactive({
|
||||||
|
position: "",
|
||||||
|
top: "",
|
||||||
|
left: "",
|
||||||
|
zIndex: "",
|
||||||
|
cursor: "default",
|
||||||
|
});
|
||||||
|
|
||||||
|
let activeComponent = null;
|
||||||
|
|
||||||
|
function positionKey() {
|
||||||
|
return props.id ? `floating-div-pos:${props.id}` : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function savePosition() {
|
||||||
|
if (props.id) {
|
||||||
|
localStorage.setItem(
|
||||||
|
positionKey(),
|
||||||
|
JSON.stringify({ x: position.x, y: position.y })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadPosition() {
|
||||||
|
if (props.id) {
|
||||||
|
const data = localStorage.getItem(positionKey());
|
||||||
|
if (data) {
|
||||||
|
try {
|
||||||
|
const { x, y } = JSON.parse(data);
|
||||||
|
position.x = x;
|
||||||
|
position.y = y;
|
||||||
|
divStyle.position = "fixed";
|
||||||
|
divStyle.top = `${position.y}px`;
|
||||||
|
divStyle.left = `${position.x}px`;
|
||||||
|
divStyle.zIndex = 9999;
|
||||||
|
isFloating.value = true;
|
||||||
|
} catch {
|
||||||
|
// Ignore corrupted data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const startDrag = (event) => {
|
||||||
|
if (event.shiftKey || event.ctrlKey) {
|
||||||
|
if (!isFloating.value) {
|
||||||
|
const rect = floatingDiv.value.getBoundingClientRect();
|
||||||
|
position.x = rect.left;
|
||||||
|
position.y = rect.top;
|
||||||
|
divStyle.position = "fixed";
|
||||||
|
divStyle.top = `${position.y}px`;
|
||||||
|
divStyle.left = `${position.x}px`;
|
||||||
|
divStyle.zIndex = 9999;
|
||||||
|
isFloating.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
isDragging.value = true;
|
||||||
|
dragStartOffset.x = event.clientX - position.x;
|
||||||
|
dragStartOffset.y = event.clientY - position.y;
|
||||||
|
divStyle.cursor = "move";
|
||||||
|
document.body.style.userSelect = "none";
|
||||||
|
|
||||||
|
activeComponent = floatingDiv;
|
||||||
|
|
||||||
|
window.addEventListener("mousemove", handleDrag);
|
||||||
|
window.addEventListener("mouseup", stopDrag);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDrag = (event) => {
|
||||||
|
if (isDragging.value && activeComponent === floatingDiv) {
|
||||||
|
position.x = event.clientX - dragStartOffset.x;
|
||||||
|
position.y = event.clientY - dragStartOffset.y;
|
||||||
|
divStyle.top = `${position.y}px`;
|
||||||
|
divStyle.left = `${position.x}px`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopDrag = () => {
|
||||||
|
if (isDragging.value && activeComponent === floatingDiv) {
|
||||||
|
isDragging.value = false;
|
||||||
|
divStyle.cursor = "default";
|
||||||
|
document.body.style.userSelect = "auto";
|
||||||
|
activeComponent = null;
|
||||||
|
|
||||||
|
savePosition();
|
||||||
|
|
||||||
|
window.removeEventListener("mousemove", handleDrag);
|
||||||
|
window.removeEventListener("mouseup", stopDrag);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOutsideDrag = (event) => {
|
||||||
|
if (event.key === "Shift" || event.key === "Control") {
|
||||||
|
stopDrag();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
document.addEventListener("keyup", handleOutsideDrag);
|
||||||
|
loadPosition();
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
document.removeEventListener("keyup", handleOutsideDrag);
|
||||||
|
window.removeEventListener("mousemove", handleDrag);
|
||||||
|
window.removeEventListener("mouseup", stopDrag);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div[ref="floatingDiv"] {
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
v-model="os.tranches" :rules="[validateRequired,validateTranches]">
|
v-model="os.tranches" :rules="[validateRequired,validateTranches]">
|
||||||
<template v-slot:append-inner>tranches</template>
|
<template v-slot:append-inner>tranches</template>
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
<v-text-field label='Skew' type="number" step="10" aria-valuemin="0" min="-100" max="100" variant="outlined"
|
<v-text-field label='Balance' type="number" step="10" aria-valuemin="0" min="-100" max="100" variant="outlined"
|
||||||
v-model="skew" clearable @click:clear="skew=0" suffix="%"/>
|
v-model="skew" clearable @click:clear="skew=0" suffix="%"/>
|
||||||
<!-- todo deadline -->
|
<!-- todo deadline -->
|
||||||
<v-table>
|
<v-table>
|
||||||
|
|||||||
@@ -1,19 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="d-inline-flex">
|
<div class="d-inline-flex">
|
||||||
<v-img :src="`dexorder_full_${s.theme}mode.svg`" width="6em" inline/>
|
<v-img v-if="variant==='full'" :src="`/logo/dexorder_full_${s.theme}mode.svg`" width="6em" inline/>
|
||||||
<beta/>
|
<v-img v-if="variant==='icon'" :src="`/logo/ico_${s.theme==='dark'?'black':'white'}_clip.png`" :width="width" inline/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|
||||||
import Beta from "@/components/Beta.vue";
|
|
||||||
import {useStore} from "@/store/store.js";
|
import {useStore} from "@/store/store.js";
|
||||||
|
import {computed} from "vue";
|
||||||
|
|
||||||
const s = useStore()
|
const s = useStore()
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
showTag: {type: Boolean, default: false}
|
variant: {type: String, default: 'full', /* 'icon', */ },
|
||||||
|
size: {type: String, default: 'medium'},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const width = computed(()=>{
|
||||||
|
return {
|
||||||
|
'x-small': '0.75em',
|
||||||
|
'small': '1.0em',
|
||||||
|
'medium': '1.25em',
|
||||||
|
'large': '1.75em',
|
||||||
|
'x-large': '2.5em',
|
||||||
|
}[props.size] || props.size;
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {useStore} from "@/store/store";
|
import {useStore} from "@/store/store";
|
||||||
import router from "@/router/index.js";
|
import {router} from "@/router/router.js";
|
||||||
const s = useStore()
|
const s = useStore()
|
||||||
|
|
||||||
function nav(path) {
|
function nav(path) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<v-card v-if="status!==Status.OK" rounded="0">
|
<v-card v-if="status!==Status.OK" rounded="0">
|
||||||
<v-card-title>
|
<v-card-title>
|
||||||
<!-- <v-icon icon="mdi-hand-wave" color="grey"/>-->
|
<!-- <v-icon icon="mdi-hand-wave" color="grey"/>-->
|
||||||
Welcome to Dexorder Beta!
|
Welcome to Dexorder!
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
Play with the order builder without an account by clicking on the <logo class="logo-small"/> logo or on
|
Play with the order builder without an account by clicking on the <logo class="logo-small"/> logo or on
|
||||||
@@ -40,11 +40,12 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {useStore} from "@/store/store";
|
import {useStore} from "@/store/store";
|
||||||
import {computed, ref} from "vue";
|
import {computed, ref} from "vue";
|
||||||
import {addNetwork, connectWallet, switchChain} from "@/blockchain/wallet.js";
|
import {addNetworkAndConnectWallet} from "@/blockchain/wallet.js";
|
||||||
import Btn from "@/components/Btn.vue";
|
import Btn from "@/components/Btn.vue";
|
||||||
import Logo from "@/components/Logo.vue";
|
import Logo from "@/components/Logo.vue";
|
||||||
import ApproveRegion from "@/components/ApproveRegion.vue";
|
import ApproveRegion from "@/components/ApproveRegion.vue";
|
||||||
import TermsOfService from "@/components/TermsOfService.vue";
|
import TermsOfService from "@/components/TermsOfService.vue";
|
||||||
|
import {track} from "@/track.js";
|
||||||
|
|
||||||
const s = useStore()
|
const s = useStore()
|
||||||
const disabled = ref(false)
|
const disabled = ref(false)
|
||||||
@@ -69,32 +70,7 @@ function reload() {
|
|||||||
async function connect() {
|
async function connect() {
|
||||||
disabled.value = true
|
disabled.value = true
|
||||||
try {
|
try {
|
||||||
try {
|
await addNetworkAndConnectWallet(s.chainId);
|
||||||
await switchChain(s.chainId)
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
if (e.code===4001) {
|
|
||||||
// explicit user rejection
|
|
||||||
return
|
|
||||||
}
|
|
||||||
else if (e.code===4902) {
|
|
||||||
try {
|
|
||||||
await addNetwork(s.chainId)
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.log(`Could not add network ${s.chainId}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
console.log('switchChain() failure',e)
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await connectWallet(s.chainId)
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
if (e.code!==4001)
|
|
||||||
console.log('connectWallet() failed', e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
disabled.value = false
|
disabled.value = false
|
||||||
|
|||||||
40
src/components/OneTimeHint.vue
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<template>
|
||||||
|
<v-tooltip v-model="show" :close-on-content-click="true" :close-on-back="false" :close-delay="null"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {computed, ref} from "vue";
|
||||||
|
import {usePrefStore} from "@/store/store.js";
|
||||||
|
|
||||||
|
const prefs = usePrefStore()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
name: {type: String, required: true},
|
||||||
|
when: {type: Boolean, default: true}, // optional conditional for when to show
|
||||||
|
after: {type: String, default: null}, // set to the name of another hint that must happen before this hint, to chain hints into a tutorial.
|
||||||
|
onComplete: {type: Function, default: null},
|
||||||
|
})
|
||||||
|
|
||||||
|
const forceClose = ref(false)
|
||||||
|
const shown = ref(false)
|
||||||
|
|
||||||
|
const show = computed({
|
||||||
|
get() {
|
||||||
|
const shownBefore = prefs.hints[props.name];
|
||||||
|
const whenOk = props.when;
|
||||||
|
const afterOk = props.after === null || prefs.hints[props.after];
|
||||||
|
const result = !forceClose.value && !shownBefore && whenOk && afterOk
|
||||||
|
// console.log(`show ${props.name}? ${result} <=`, !forceClose.value, whenOk, afterOk, prefs.hints)
|
||||||
|
if (result) {
|
||||||
|
shown.value = true
|
||||||
|
prefs.hints[props.name] = true
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
set(v) { if(!v) { forceClose.value=true; if (shown.value && props.onComplete) props.onComplete(); } }
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -11,7 +11,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</v-card-item>
|
</v-card-item>
|
||||||
<v-card-actions class="d-flex justify-space-evenly mb-4">
|
<v-card-actions class="d-flex justify-space-evenly mb-4">
|
||||||
<v-btn variant="outlined" color="red" @click="nav('Assets')">Cancel</v-btn>
|
<v-btn variant="outlined" color="red" @click="// noinspection JSIgnoredPromiseFromCall
|
||||||
|
router.push({name: 'Assets'})">Cancel</v-btn>
|
||||||
<v-btn variant="flat" color="green" :disabled="!valid()" @click="placeOrder">Place Dexorder</v-btn>
|
<v-btn variant="flat" color="green" :disabled="!valid()" @click="placeOrder">Place Dexorder</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</phone-card>
|
</phone-card>
|
||||||
@@ -23,12 +24,13 @@ import {useOrderStore, useStore} from "@/store/store";
|
|||||||
import PhoneCard from "@/components/PhoneCard.vue";
|
import PhoneCard from "@/components/PhoneCard.vue";
|
||||||
import Amount from "@/components/Amount.vue"
|
import Amount from "@/components/Amount.vue"
|
||||||
// noinspection ES6UnusedImports
|
// noinspection ES6UnusedImports
|
||||||
import {nav, SingletonCoroutine, vAutoSelect} from "@/misc.js";
|
import {SingletonCoroutine, vAutoSelect} from "@/misc.js";
|
||||||
import {newOrder} from "@/blockchain/orderlib.js";
|
import {newOrder} from "@/blockchain/orderlib.js";
|
||||||
import {FixedNumber} from "ethers";
|
import {FixedNumber} from "ethers";
|
||||||
import PairChoice from "@/components/PairChoice.vue";
|
import PairChoice from "@/components/PairChoice.vue";
|
||||||
import NeedsSigner from "@/components/NeedsSigner.vue";
|
import NeedsSigner from "@/components/NeedsSigner.vue";
|
||||||
import {useChartOrderStore} from "@/orderbuild.js";
|
import {useChartOrderStore} from "@/orderbuild.js";
|
||||||
|
import {router} from "@/router/router.js";
|
||||||
|
|
||||||
const s = useStore()
|
const s = useStore()
|
||||||
const os = useOrderStore()
|
const os = useOrderStore()
|
||||||
|
|||||||
84
src/components/PoolSelectionDialog.vue
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
<template>
|
||||||
|
<v-dialog v-model="co.showPoolSelection" max-width="300">
|
||||||
|
<v-card title="Pool Selection">
|
||||||
|
<v-card-text>
|
||||||
|
<div class="mb-2">
|
||||||
|
<v-avatar image="/arbitrum-logo.svg" size="x-small" class="mr-1"/> Arbitrum One
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<v-avatar image="/uniswap-logo.svg" size="x-small" class="mr-1" style="background-color: white"/> Uniswap v3
|
||||||
|
</div>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>Pool</td><td><scanner-button :addr="co.selectedSymbol.address"/></td></tr>
|
||||||
|
<tr><td>{{ co.selectedSymbol.base.s }}</td><td><scanner-button :addr="co.selectedSymbol.base.a"/></td></tr>
|
||||||
|
<tr><td>{{ co.selectedSymbol.quote.s }}</td><td><scanner-button :addr="co.selectedSymbol.quote.a"/></td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<v-table class="mt-4">
|
||||||
|
<thead>
|
||||||
|
<tr><th>Selected</th><th>Fee</th><td>Liquidity</td></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="([addr,fee],i) in co.selectedSymbol.feeGroup"
|
||||||
|
:class="co.selectedSymbol.fee === fee ? 'selected' : 'selectable'" @click="selectFee(fee)">
|
||||||
|
<td><v-icon v-if="co.selectedSymbol.fee===fee" icon="mdi-check"/></td>
|
||||||
|
<td>{{(fee/10_000).toFixed(2)}}%</td>
|
||||||
|
<td>{{ !co.selectedSymbol.liquiditySymbol ? '' :
|
||||||
|
co.selectedSymbol.liquiditySymbol === 'USD' ?
|
||||||
|
`$${toHuman(co.selectedSymbol.liquidities[i])}` :
|
||||||
|
toHuman(co.selectedSymbol.liquidities[i]) +' '+co.selectedSymbol.liquiditySymbol }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</v-table>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
|
||||||
|
import {toHuman} from "@/misc.js";
|
||||||
|
import ScannerButton from "@/components/ScannerButton.vue";
|
||||||
|
import {useChartOrderStore} from "@/orderbuild.js";
|
||||||
|
import {feelessTickerKey} from "@/charts/datafeed.js";
|
||||||
|
import {setSymbolTicker} from "@/charts/chart.js";
|
||||||
|
|
||||||
|
const co = useChartOrderStore()
|
||||||
|
|
||||||
|
|
||||||
|
function selectFee(fee) {
|
||||||
|
if (fee === co.selectedSymbol.fee) return
|
||||||
|
const symbol = co.selectedSymbol;
|
||||||
|
const ticker = feelessTickerKey(symbol.ticker) + '|' + fee
|
||||||
|
if (ticker !== symbol.ticker)
|
||||||
|
setSymbolTicker(ticker).catch((e) => console.error('Could not change TV symbol to', ticker))
|
||||||
|
co.showPoolSelection = false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@use "src/styles/vars" as *;
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding-right: 1em;
|
||||||
|
}
|
||||||
|
tr.selected {
|
||||||
|
|
||||||
|
}
|
||||||
|
tr.selectable {
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
background-color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.pool-button {
|
||||||
|
color: blue;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
34
src/components/ScannerButton.vue
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<template>
|
||||||
|
<div class="d-inline-flex align-center" style="cursor: pointer">
|
||||||
|
<span @click='click'
|
||||||
|
:style="{maxWidth: width, width}" style="white-space: nowrap; display: inline-block; overflow: hidden; text-overflow: ellipsis">{{addr}}</span>
|
||||||
|
<v-icon icon="mdi-open-in-new" size="x-small"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {computed} from "vue";
|
||||||
|
import {useStore} from "@/store/store.js";
|
||||||
|
|
||||||
|
const props = defineProps({chainId: {type: Number, default:null}, addr:String, width: {default:'5em'}})
|
||||||
|
const s = useStore()
|
||||||
|
|
||||||
|
function click() {
|
||||||
|
window.open(url.value, '_blank')
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = computed(()=>{
|
||||||
|
const chain = props.chainId ? props.chainId: s.chainId
|
||||||
|
switch (chain) {
|
||||||
|
case 31337:
|
||||||
|
case 1337:
|
||||||
|
case 42161:
|
||||||
|
return `https://arbiscan.io/address/${props.addr}`
|
||||||
|
}
|
||||||
|
throw new Error(`No scanner defined for chain ${chain}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
</style>
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<v-data-table :headers="datatableHeaders" :items="orders" item-value="id"
|
<v-data-table :headers="datatableHeaders" :items="orders" item-value="id"
|
||||||
:show-select="true" :show-expand="true" v-model="selected" select-strategy="single">
|
:show-select="false" :show-expand="true" v-model="selected" select-strategy="single"
|
||||||
|
:row-props="rowPropsHandler" :hover="true"
|
||||||
|
>
|
||||||
<template v-slot:item.startTime="{ value }">{{ timestampString(value) }}</template>
|
<template v-slot:item.startTime="{ value }">{{ timestampString(value) }}</template>
|
||||||
<template v-slot:item.input="{ item }">
|
<template v-slot:item.input="{ item }">
|
||||||
<span v-if="item.order.amountIsInput">
|
<span v-if="item.order.amountIsInput">
|
||||||
@@ -71,12 +73,6 @@
|
|||||||
</suspense>
|
</suspense>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:item.state="{ item }">
|
<template v-slot:item.state="{ item }">
|
||||||
<!--
|
|
||||||
<v-chip v-if="item.state===PendingOrderState.Submitted || item.state===PendingOrderState.Signing"
|
|
||||||
prepend-icon="mdi-signature">Wallet Signing</v-chip>
|
|
||||||
<v-chip v-if="item.state===PendingOrderState.Rejected" prepend-icon="mdi-cancel">Rejected</v-chip>
|
|
||||||
<v-chip v-if="item.state===PendingOrderState.Sent" prepend-icon="mdi-send">Sent</v-chip>
|
|
||||||
-->
|
|
||||||
<v-chip v-if="item.state===OrderState.Open" class="d-none d-lg-inline-flex" prepend-icon="mdi-dots-horizontal"
|
<v-chip v-if="item.state===OrderState.Open" class="d-none d-lg-inline-flex" prepend-icon="mdi-dots-horizontal"
|
||||||
color="green">Open
|
color="green">Open
|
||||||
</v-chip>
|
</v-chip>
|
||||||
@@ -90,28 +86,18 @@
|
|||||||
</v-chip>
|
</v-chip>
|
||||||
<v-chip v-if="item.state===OrderState.Expired" prepend-icon="mdi-progress-check" color="grey-darken-1">Partial
|
<v-chip v-if="item.state===OrderState.Expired" prepend-icon="mdi-progress-check" color="grey-darken-1">Partial
|
||||||
</v-chip>
|
</v-chip>
|
||||||
<v-chip v-if="item.state===OrderState.Underfunded" prepend-icon="mdi-alert" color="warning">Underfunded</v-chip>
|
<v-chip v-if="item.state===OrderState.Underfunded" prepend-icon="mdi-alert" color="warning">Underfunded
|
||||||
|
<v-tooltip :text="`This order is underfunded. Add more ${item.inSymbol} to your vault.`" location="top" activator="parent"></v-tooltip>
|
||||||
|
</v-chip>
|
||||||
<v-chip v-if="item.state===OrderState.Error" prepend-icon="mdi-alert" color="error">Error</v-chip>
|
<v-chip v-if="item.state===OrderState.Error" prepend-icon="mdi-alert" color="error">Error</v-chip>
|
||||||
</template>
|
</template>
|
||||||
<!-- <template v-slot:item.action="{item}">-->
|
|
||||||
<!-- <btn v-if="item.state===OrderState.Open" icon="mdi-cancel" color="red"-->
|
|
||||||
<!-- @click="cancelOrder(vaultAddr,item.index)">Cancel-->
|
|
||||||
<!-- </btn>-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<template v-slot:expanded-row="{item}">
|
<template v-slot:expanded-row="{item}">
|
||||||
<template v-for="(t, i) in item.order.tranches">
|
<template v-for="(t, i) in item.order.tranches">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-right" colspan="2">
|
<td class="text-right" colspan="2">
|
||||||
<div class="text-right">
|
|
||||||
<div v-if="s.clock < item.trancheStatus[i].startTime">
|
|
||||||
Activates {{ timestampString(item.trancheStatus[i].startTime) }}
|
|
||||||
</div>
|
|
||||||
<div v-if="s.clock < item.trancheStatus[i].endTime && item.trancheStatus[i].endTime < DISTANT_FUTURE">
|
|
||||||
Expires {{ timestampString(item.trancheStatus[i].endTime) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<div class="mx-3" v-if="t.marketOrder">market order</div>
|
<div class="mx-3" v-if="t.marketOrder && t.rateLimitPeriod === 0">market order</div>
|
||||||
|
<div class="mx-3" v-if="t.marketOrder && t.rateLimitPeriod !== 0">DCA {{Math.round(MAX_FRACTION/t.rateLimitFraction)}} parts</div>
|
||||||
<line-price class="mx-3" v-if="!t.marketOrder"
|
<line-price class="mx-3" v-if="!t.marketOrder"
|
||||||
:base="item.base" :quote="item.quote"
|
:base="item.base" :quote="item.quote"
|
||||||
:b="t.minLine.intercept" :m="t.minLine.slope" :is-breakout="!item.minIsLimit"
|
:b="t.minLine.intercept" :m="t.minLine.slope" :is-breakout="!item.minIsLimit"
|
||||||
@@ -121,7 +107,10 @@
|
|||||||
:b="t.maxLine.intercept" :m="t.maxLine.slope" :is-breakout="item.minIsLimit"
|
:b="t.maxLine.intercept" :m="t.maxLine.slope" :is-breakout="item.minIsLimit"
|
||||||
:show-btn="true"/>
|
:show-btn="true"/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-right" v-if="item.state===OrderState.Open">
|
||||||
|
<div>{{ describeTrancheTime(item, i, true) }}</div>
|
||||||
|
<div>{{ describeTrancheTime(item, i, false) }}</div>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<suspense>
|
<suspense>
|
||||||
@@ -176,17 +165,18 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import LinePrice from "@/components/LinePrice.vue";
|
import LinePrice from "@/components/LinePrice.vue";
|
||||||
import {useStore} from "@/store/store";
|
import {useStore} from "@/store/store";
|
||||||
import {computed, defineAsyncComponent, onUnmounted, ref, watch} from "vue";
|
import {computed, defineAsyncComponent, onMounted, onUnmounted, ref, watch} from "vue";
|
||||||
import Btn from "@/components/Btn.vue"
|
import Btn from "@/components/Btn.vue"
|
||||||
import {cancelOrder, useWalletStore} from "@/blockchain/wallet.js";
|
import {cancelOrder, useWalletStore} from "@/blockchain/wallet.js";
|
||||||
import {DISTANT_FUTURE, isOpen, OrderState} from "@/blockchain/orderlib.js";
|
import {DISTANT_FUTURE, isOpen, MAX_FRACTION, OrderState} from "@/blockchain/orderlib.js";
|
||||||
import Pulse from "@/components/Pulse.vue";
|
import Pulse from "@/components/Pulse.vue";
|
||||||
import {OrderShapes} from "@/charts/ordershapes.js";
|
import {OrderShapes} from "@/charts/ordershapes.js";
|
||||||
import {useChartOrderStore} from "@/orderbuild.js";
|
import {useChartOrderStore} from "@/orderbuild.js";
|
||||||
import {lookupSymbol, tickerForOrder} from "@/charts/datafeed.js";
|
import {lookupSymbol, tickerForOrder} from "@/charts/datafeed.js";
|
||||||
import {setSymbol} from "@/charts/chart.js";
|
import {addSymbolChangedCallback, removeSymbolChangedCallback, setSymbol} from "@/charts/chart.js";
|
||||||
import {uniswapV3AveragePrice} from "@/blockchain/uniswap.js";
|
import {uniswapV3AveragePrice} from "@/blockchain/uniswap.js";
|
||||||
import {timestampString} from "@/misc.js";
|
import {timestampString} from "@/misc.js";
|
||||||
|
import {metadataMap} from "@/version.js";
|
||||||
|
|
||||||
const PairPrice = defineAsyncComponent(()=>import("@/components/PairPrice.vue"))
|
const PairPrice = defineAsyncComponent(()=>import("@/components/PairPrice.vue"))
|
||||||
const TokenAmount = defineAsyncComponent(()=>import('./TokenAmount.vue'))
|
const TokenAmount = defineAsyncComponent(()=>import('./TokenAmount.vue'))
|
||||||
@@ -250,6 +240,16 @@ const datatableHeaders = [
|
|||||||
// {title: '', align: 'end', key: 'action'},
|
// {title: '', align: 'end', key: 'action'},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const rowPropsHandler = ({ item }) => ({
|
||||||
|
class: selected.value.indexOf(item.id) !== -1 ? 'selected-row' : '',
|
||||||
|
onClick: () => {
|
||||||
|
if (selected.value.indexOf(item.id) === -1)
|
||||||
|
selected.value = [item.id]
|
||||||
|
else
|
||||||
|
selected.value = []
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const orders = computed(()=>{
|
const orders = computed(()=>{
|
||||||
// example twap
|
// example twap
|
||||||
// status = [
|
// status = [
|
||||||
@@ -318,7 +318,6 @@ const orders = computed(()=>{
|
|||||||
st.id = `${vault}|${index}`
|
st.id = `${vault}|${index}`
|
||||||
st.index = parseInt(index)
|
st.index = parseInt(index)
|
||||||
// st.startTime = timestampString(st.startTime)
|
// st.startTime = timestampString(st.startTime)
|
||||||
console.log('starttime', st.startTime)
|
|
||||||
/*
|
/*
|
||||||
o.tranches = o.tranches.map((tranche)=>{
|
o.tranches = o.tranches.map((tranche)=>{
|
||||||
const t = {...tranche}
|
const t = {...tranche}
|
||||||
@@ -356,19 +355,21 @@ const orders = computed(()=>{
|
|||||||
for (const st of result) {
|
for (const st of result) {
|
||||||
let low, high;
|
let low, high;
|
||||||
// console.log('elab', st.order)
|
// console.log('elab', st.order)
|
||||||
const buy = st.order.tokenIn > st.order.tokenOut;
|
const o = st.order;
|
||||||
|
const buy = o.tokenIn > o.tokenOut;
|
||||||
if (buy) {
|
if (buy) {
|
||||||
low = st.order.tokenOut
|
low = o.tokenOut
|
||||||
high = st.order.tokenIn
|
high = o.tokenIn
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
low = st.order.tokenIn
|
low = o.tokenIn
|
||||||
high = st.order.tokenOut
|
high = o.tokenOut
|
||||||
}
|
}
|
||||||
st.base = st.order.inverted ? high : low;
|
st.base = o.inverted ? high : low;
|
||||||
st.quote = st.order.inverted ? low : high;
|
st.quote = o.inverted ? low : high;
|
||||||
st.minIsLimit = buy === st.order.inverted // whether limit/breakout is flipped
|
st.minIsLimit = buy === o.inverted // whether limit/breakout is flipped
|
||||||
// console.log('buy/inverted/minIsLimit', buy, st.order.inverted, st.minIsLimit)
|
const found = metadataMap[st.chainId][o.tokenIn]
|
||||||
|
st.inSymbol = found ? found.s : o.tokenIn
|
||||||
// console.log('elaborated', st)
|
// console.log('elaborated', st)
|
||||||
}
|
}
|
||||||
result.sort((a,b)=>b.startTime-a.startTime)
|
result.sort((a,b)=>b.startTime-a.startTime)
|
||||||
@@ -383,7 +384,6 @@ const orders = computed(()=>{
|
|||||||
function describeTrancheTime(st, trancheIndex, isStart) {
|
function describeTrancheTime(st, trancheIndex, isStart) {
|
||||||
const t = st.order.tranches[trancheIndex]
|
const t = st.order.tranches[trancheIndex]
|
||||||
const ts = st.trancheStatus[trancheIndex]
|
const ts = st.trancheStatus[trancheIndex]
|
||||||
console.log('describeTT', t, ts)
|
|
||||||
let result = ''
|
let result = ''
|
||||||
if( t.minIsBarrier || t.maxIsBarrier )
|
if( t.minIsBarrier || t.maxIsBarrier )
|
||||||
return 'barrier'
|
return 'barrier'
|
||||||
@@ -401,12 +401,16 @@ function describeTrancheTime(st, trancheIndex, isStart) {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function symbolChanged() {
|
||||||
|
selected.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(()=>addSymbolChangedCallback(symbolChanged))
|
||||||
|
onUnmounted(()=>removeSymbolChangedCallback(symbolChanged))
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@use "src/styles/vars" as *;
|
|
||||||
|
|
||||||
// columns
|
// columns
|
||||||
.num {
|
.num {
|
||||||
min-width: 1em;
|
min-width: 1em;
|
||||||
@@ -429,5 +433,12 @@ function describeTrancheTime(st, trancheIndex, isStart) {
|
|||||||
}
|
}
|
||||||
.cancel {
|
.cancel {
|
||||||
}
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@use "src/styles/settings" as *;
|
||||||
|
|
||||||
|
tr.selected-row {
|
||||||
|
background-color: #616161;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
if(window.location.hostname !== 'localhost') {
|
if(window.location.hostname !== 'localhost') {
|
||||||
window.$crisp = [];window.CRISP_WEBSITE_ID = "b153f30a-4b0b-49cc-8a38-989409a73acb";(function () {const d = document;const s = d.createElement("script");s.src = "https://client.crisp.chat/l.js";s.async = 1;d.getElementsByTagName("head")[0].appendChild(s);})();
|
window.$crisp=[];window.CRISP_WEBSITE_ID="a88f707f-adee-4960-b6de-c328c9d86945";(function(){const d=document;const s=d.createElement("script");s.src="https://client.crisp.chat/l.js";s.async=1;d.getElementsByTagName("head")[0].appendChild(s);})();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -22,8 +22,8 @@
|
|||||||
import {usePrefStore} from "@/store/store.js";
|
import {usePrefStore} from "@/store/store.js";
|
||||||
import {computed, ref, watch} from "vue";
|
import {computed, ref, watch} from "vue";
|
||||||
|
|
||||||
import {socket} from "@/socket.js";
|
|
||||||
import TosCard from "@/components/TosCard.vue";
|
import TosCard from "@/components/TosCard.vue";
|
||||||
|
import {socket} from "@/socket.js";
|
||||||
|
|
||||||
// UPDATE THIS WHEN CHANGING THE TOS
|
// UPDATE THIS WHEN CHANGING THE TOS
|
||||||
const CURRENT_VERSION='2024-11-18' // this must be a parseable date
|
const CURRENT_VERSION='2024-11-18' // this must be a parseable date
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
<v-card-text class="text-center">Last Updated November 18, 2024</v-card-text>
|
<v-card-text class="text-center">Last Updated November 18, 2024</v-card-text>
|
||||||
|
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
Please read these Terms of Service (the “<b>Terms</b>”) and our <a href="https://dexorder.trade/privacy-policy" target="dexorder">Privacy Policy</a> carefully because they govern your
|
Please read these Terms of Service (the “<b>Terms</b>”) and our <a href="https://dexorder.com/privacy-policy" target="dexorder">Privacy Policy</a> carefully because they govern your
|
||||||
use of the
|
use of the
|
||||||
website (and all subdomains and subpages thereon) located at dexorder.trade, including without limitation the
|
website (and all subdomains and subpages thereon) located at dexorder.com, including without limitation the
|
||||||
subdomains app.dexorder.trade and www.dexorder.trade (collectively, the “<b>Site</b>”), and the Dexorder web
|
subdomains app.dexorder.com and www.dexorder.com (collectively, the “<b>Site</b>”), and the Dexorder web
|
||||||
application graphical user interface and any other services accessible via the Site (together with the Site, web
|
application graphical user interface and any other services accessible via the Site (together with the Site, web
|
||||||
application, and other services, collectively, the “<b>Dexorder Service</b>”) offered by Dexorder Trading Services
|
application, and other services, collectively, the “<b>Dexorder Service</b>”) offered by Dexorder Trading Services
|
||||||
Ltd.
|
Ltd.
|
||||||
@@ -205,7 +205,7 @@
|
|||||||
(i) Subject to your compliance with these Terms, Dexorder will use its commercially
|
(i) Subject to your compliance with these Terms, Dexorder will use its commercially
|
||||||
reasonable efforts to provide you with access to the Dexorder Service and to cause your Interactions to be
|
reasonable efforts to provide you with access to the Dexorder Service and to cause your Interactions to be
|
||||||
executed on the applicable DEX in accordance with Dexorder’s Execution Policy located at
|
executed on the applicable DEX in accordance with Dexorder’s Execution Policy located at
|
||||||
<a href="https://dexorder.trade/execution-policy">https://dexorder.trade/execution-policy</a>
|
<a href="https://dexorder.com/execution-policy">https://dexorder.com/execution-policy</a>
|
||||||
(“<b>Execution Policy</b>”), however from time to time the Site and the Dexorder Service may be inaccessible or
|
(“<b>Execution Policy</b>”), however from time to time the Site and the Dexorder Service may be inaccessible or
|
||||||
inoperable for any
|
inoperable for any
|
||||||
reason, including, without limitation: (a) if an Interaction repeatedly fails to be executed (such as due to an
|
reason, including, without limitation: (a) if an Interaction repeatedly fails to be executed (such as due to an
|
||||||
@@ -690,7 +690,7 @@
|
|||||||
<v-card-title>18. Contact Information</v-card-title>
|
<v-card-title>18. Contact Information</v-card-title>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
If you have any questions about these Terms or the Dexorder Service, please contact Dexorder
|
If you have any questions about these Terms or the Dexorder Service, please contact Dexorder
|
||||||
at <a href="mailto:legal@dexorder.trade">legal@dexorder.trade</a> or <a href="mailto:support@dexorder.trade">support@dexorder.trade</a>.
|
at <a href="mailto:legal@dexorder.com">legal@dexorder.com</a> or <a href="mailto:support@dexorder.com">support@dexorder.com</a>.
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-text-field label="Tranches" type="number" variant="outlined" aria-valuemin="1" min="1" max="255"
|
<v-text-field label="Tranches" type="number" variant="outlined" aria-valuemin="1" min="1" max="255"
|
||||||
v-model="os.tranches" :rules="[validateRequired,validateTranches]" v-auto-select>
|
v-model="os.tranches" :rules="[validateRequired,validateTranches]" v-auto-select>
|
||||||
<template v-slot:append-inner>tranches</template>
|
<template v-slot:append-inner>tranches</template>
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
<v-text-field type="number" variant="outlined" :min="1" v-model="os.interval" class="interval"
|
<v-text-field type="number" variant="outlined" :min="1" v-model="os.interval" class="interval"
|
||||||
:label="os.intervalIsTotal ? 'Completion time' : 'Time between tranches'" v-auto-select>
|
:label="os.intervalIsTotal ? 'Completion time' : 'Time between tranches'" v-auto-select>
|
||||||
<template v-slot:prepend-inner>
|
<template v-slot:prepend-inner>
|
||||||
|
|||||||
@@ -34,10 +34,10 @@
|
|||||||
<v-card-item v-if="!empty">
|
<v-card-item v-if="!empty">
|
||||||
<v-table>
|
<v-table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<native-row v-if="nativeBalance" :chain-id="s.chainId" :addr="s.vault" :amount="nativeBalance"
|
<native-row v-if="nativeBalance && BigInt(nativeBalance)>0n" :chain-id="s.chainId" :addr="s.vault" :amount="nativeBalance"
|
||||||
:on-withdraw="onWithdrawNative" :on-wrap="()=>wrapShow=true"/>
|
:on-withdraw="onWithdrawNative" :on-wrap="()=>wrapShow=true"/>
|
||||||
<suspense v-for="(amount,addr) of balances">
|
<suspense v-for="(amount,addr) of balances">
|
||||||
<token-row v-if="BigInt(amount)!==0n" :chain-id="s.chainId" :addr="addr" :amount="amount" :onWithdraw="onWithdraw"/>
|
<token-row v-if="amount && BigInt(amount)>0n" :chain-id="s.chainId" :addr="addr" :amount="amount" :onWithdraw="onWithdraw"/>
|
||||||
</suspense>
|
</suspense>
|
||||||
</tbody>
|
</tbody>
|
||||||
</v-table>
|
</v-table>
|
||||||
@@ -60,12 +60,13 @@
|
|||||||
import {useStore} from "@/store/store.js";
|
import {useStore} from "@/store/store.js";
|
||||||
import {computed, defineAsyncComponent, onUnmounted, ref, watchEffect} from "vue";
|
import {computed, defineAsyncComponent, onUnmounted, ref, watchEffect} from "vue";
|
||||||
import {vaultAddress} from "@/blockchain/contract.js";
|
import {vaultAddress} from "@/blockchain/contract.js";
|
||||||
import {ensureVault, provider} from "@/blockchain/wallet.js";
|
import {ensureVault} from "@/blockchain/wallet.js";
|
||||||
import CopyButton from "@/components/CopyButton.vue";
|
import CopyButton from "@/components/CopyButton.vue";
|
||||||
import Withdraw from "@/components/Withdraw.vue";
|
import Withdraw from "@/components/Withdraw.vue";
|
||||||
import NativeRow from "@/components/NativeRow.vue";
|
import NativeRow from "@/components/NativeRow.vue";
|
||||||
import NativeWrap from "@/components/NativeWrap.vue";
|
import NativeWrap from "@/components/NativeWrap.vue";
|
||||||
import WithdrawNative from "@/components/WithdrawNative.vue";
|
import WithdrawNative from "@/components/WithdrawNative.vue";
|
||||||
|
import {provider} from "@/blockchain/provider.js";
|
||||||
|
|
||||||
const TokenRow = defineAsyncComponent(()=>import('./TokenRow.vue'))
|
const TokenRow = defineAsyncComponent(()=>import('./TokenRow.vue'))
|
||||||
const s = useStore()
|
const s = useStore()
|
||||||
@@ -89,7 +90,7 @@ const hasVault = computed(()=>s.vault!==null)
|
|||||||
const withdrawToken = ref(null)
|
const withdrawToken = ref(null)
|
||||||
const withdrawShow = ref(false)
|
const withdrawShow = ref(false)
|
||||||
async function onWithdraw(token) {
|
async function onWithdraw(token) {
|
||||||
console.log('withdraw', addr, token)
|
console.log('withdraw', addr.value, token)
|
||||||
withdrawToken.value = token
|
withdrawToken.value = token
|
||||||
withdrawShow.value = true
|
withdrawShow.value = true
|
||||||
}
|
}
|
||||||
|
|||||||
72
src/components/WelcomeDialog.vue
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<template>
|
||||||
|
<v-dialog v-model="modelValue" max-width="600">
|
||||||
|
<v-card title="Welcome to Dexorder!">
|
||||||
|
<template #prepend><logo variant="icon" size="large"/></template>
|
||||||
|
<v-card-text>
|
||||||
|
<div class="d-flex">
|
||||||
|
Dexorder powers up <div class="d-inline-flex align-self-baseline"><v-img src="/uniswap-logo.svg" width="1.25em" inline/></div>Uniswap with advanced ordering capabilities.
|
||||||
|
</div>
|
||||||
|
<p class="mt-2">
|
||||||
|
Dexorder gives you a personal trading vault smart contract that trades its tokens with Uniswap only when
|
||||||
|
your advanced order conditions are met.
|
||||||
|
</p>
|
||||||
|
<p class="mt-2">Level up your DeFi trading with:</p>
|
||||||
|
<v-list density="compact">
|
||||||
|
<v-list-item prepend-icon="mdi-clock-outline"><b>DCA / TWAP</b> <small>up to 1000 parts</small></v-list-item>
|
||||||
|
<v-list-item prepend-icon="mdi-ray-vertex"><b>Limit Ladders</b> <small>capture ranges</small></v-list-item>
|
||||||
|
<v-list-item prepend-icon="mdi-vector-line"><b>Diagonal Limits</b> <small>trade trends and channels</small></v-list-item>
|
||||||
|
<v-list-item prepend-icon="mdi-chart-line"><b>Breakout Orders</b> <small>buy <i>above</i> a price level</small></v-list-item>
|
||||||
|
<v-list-item prepend-icon="mdi-plus-minus"><b>Stop-loss</b> <small>coming soon</small></v-list-item>
|
||||||
|
<!-- <v-list-item prepend-icon="mdi-cancel">One-click Cancel All</v-list-item>-->
|
||||||
|
<!--
|
||||||
|
<v-list-item>
|
||||||
|
<template #prepend>
|
||||||
|
<v-avatar image="/arbitrum-logo.svg" size="1.5em" class="mr-4"/>
|
||||||
|
</template>
|
||||||
|
<template #default>
|
||||||
|
<b>Arbitrum One</b> support <small>fast and cheap</small>
|
||||||
|
</template>
|
||||||
|
</v-list-item>
|
||||||
|
-->
|
||||||
|
<v-list-item>
|
||||||
|
<template #prepend>
|
||||||
|
<v-avatar image="/uniswap-logo.svg" size="1.5em" class="mr-4" style="background-color: white"/>
|
||||||
|
</template>
|
||||||
|
<template #default>
|
||||||
|
<b>Uniswap</b> v3 support <small>high liquidity</small>
|
||||||
|
</template>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions class="justify-center mb-9">
|
||||||
|
<!-- <v-btn variant="text" @click="learnMore" class="justify-end">Learn More</v-btn>-->
|
||||||
|
<v-btn variant="tonal" color="primary" @click="tryIt" class="justify-center">Try It!</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {track} from "@/track.js";
|
||||||
|
import Logo from "@/components/Logo.vue";
|
||||||
|
|
||||||
|
const modelValue = defineModel()
|
||||||
|
|
||||||
|
function tryIt() {
|
||||||
|
track('tutorial_begin')
|
||||||
|
modelValue.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function learnMore() {
|
||||||
|
track('learn-more')
|
||||||
|
window.open('https://dexorder.com/introduction.html', 'dexorderwww')
|
||||||
|
modelValue.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
small {
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
<v-btn variant="text" text="max" @click="floatAmount=balanceFloat"/>
|
<v-btn variant="text" text="max" @click="floatAmount=balanceFloat"/>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:append-inner>
|
<template v-slot:append-inner>
|
||||||
<span>{{ token.s }}</span>
|
<span style="max-width: 6em">{{ token.s }}</span>
|
||||||
</template>
|
</template>
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
@@ -36,11 +36,17 @@ const s = useStore()
|
|||||||
const props = defineProps(['modelValue', 'vault', 'token'])
|
const props = defineProps(['modelValue', 'vault', 'token'])
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const balance = computed(() => {
|
const balance = computed(() => {
|
||||||
console.log('balance', props.vault, props.token, s.vaultBalances)
|
const b = s.getBalance(props.token?.a)
|
||||||
const b = s.vaultBalances[props.vault][props.token.address];
|
console.log('balance', b)
|
||||||
|
// const b = s.vaultBalances[props.vault][props.token.address];
|
||||||
return b === undefined ? 0n : BigInt(b)
|
return b === undefined ? 0n : BigInt(b)
|
||||||
})
|
})
|
||||||
const balanceFloat = computed(() => tokenFloat(props.token, balance.value))
|
const balanceFloat = computed(() => {
|
||||||
|
let balance = tokenFloat(props.token, s.getBalance(props.token?.a))
|
||||||
|
balance /= 10**props.token.d
|
||||||
|
console.log('balanceFloat', balance, s.getBalance(props.token?.a), props.token)
|
||||||
|
return balance
|
||||||
|
})
|
||||||
const floatAmount = ref(0)
|
const floatAmount = ref(0)
|
||||||
|
|
||||||
function withdraw() {
|
function withdraw() {
|
||||||
|
|||||||
@@ -4,10 +4,11 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {computed} from "vue";
|
import {computed} from "vue";
|
||||||
import DCABuilder from "@/components/chart/DCABuilder.vue";
|
import DateBuilder from "@/components/chart/DateBuilder.vue";
|
||||||
import LimitBuilder from "@/components/chart/LimitBuilder.vue";
|
import LimitBuilder from "@/components/chart/LimitBuilder.vue";
|
||||||
import MarketBuilder from "@/components/chart/MarketBuilder.vue";
|
import MarketBuilder from "@/components/chart/MarketBuilder.vue";
|
||||||
import DiagonalBuilder from "@/components/chart/DiagonalBuilder.vue";
|
import DiagonalBuilder from "@/components/chart/DiagonalBuilder.vue";
|
||||||
|
import DCABuilder from "@/components/chart/DCABuilder.vue";
|
||||||
|
|
||||||
const props = defineProps(['order', 'builder'])
|
const props = defineProps(['order', 'builder'])
|
||||||
|
|
||||||
@@ -21,6 +22,8 @@ const component = computed(()=>{
|
|||||||
return LimitBuilder
|
return LimitBuilder
|
||||||
case 'DiagonalBuilder':
|
case 'DiagonalBuilder':
|
||||||
return DiagonalBuilder
|
return DiagonalBuilder
|
||||||
|
case 'DateBuilder':
|
||||||
|
return DateBuilder
|
||||||
default:
|
default:
|
||||||
console.error('Unknown builder component '+props.builder.component)
|
console.error('Unknown builder component '+props.builder.component)
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -1,29 +1,32 @@
|
|||||||
<template>
|
<template>
|
||||||
<row-bar :color="builder.color">
|
<v-sheet dense style="overflow-y: hidden" class="pa-1 pb-2">
|
||||||
<color-band :color="builder.color"/>
|
<h3 class="ml-1">{{name}}</h3>
|
||||||
<slot/>
|
<div class="d-flex flex-row align-content-stretch">
|
||||||
<div class="align-self-center">
|
<div class="ml-2"> </div>
|
||||||
<v-menu>
|
<slot/>
|
||||||
<template v-slot:activator="{ props }">
|
<div class="align-self-center ml-auto mr-3 trashcan">
|
||||||
<v-btn variant="plain" v-bind="props" icon="mdi-dots-vertical"/>
|
<v-btn icon="mdi-delete" @click="showDeleteDialog=true"/>
|
||||||
</template>
|
</div>
|
||||||
<v-list>
|
|
||||||
<!-- <v-list-subheader :title="'Limit '+ (priceA?priceA.toPrecision(5):'')"/>-->
|
|
||||||
<v-list-item title="Delete" key="withdraw" value="withdraw" prepend-icon="mdi-delete" color="red"
|
|
||||||
@click="deleteMyBuilder"/>
|
|
||||||
</v-list>
|
|
||||||
</v-menu>
|
|
||||||
</div>
|
</div>
|
||||||
</row-bar>
|
<v-dialog v-model="showDeleteDialog" :max-width="300">
|
||||||
|
<v-card :title="`Delete ${name}?`" :text="`Do you want to delete this ${name}?`">
|
||||||
|
<v-card-actions>
|
||||||
|
<v-btn @click="showDeleteDialog=false">Keep</v-btn>
|
||||||
|
<v-btn variant="tonal" color="error" @click="deleteMyBuilder">Delete</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</v-sheet>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {builderFuncs, deleteBuilder} from "@/orderbuild.js";
|
import {builderFuncs, deleteBuilder} from "@/orderbuild.js";
|
||||||
import ColorBand from "@/components/chart/ColorBand.vue";
|
import ColorBand from "@/components/chart/ColorBand.vue";
|
||||||
import RowBar from "@/components/chart/RowBar.vue";
|
import RowBar from "@/components/chart/RowBar.vue";
|
||||||
import {onBeforeUnmount, onMounted, onUnmounted, onUpdated, watchEffect} from "vue";
|
import {onBeforeUnmount, onMounted, onUnmounted, onUpdated, ref, watchEffect} from "vue";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
name: String,
|
||||||
order: Object,
|
order: Object,
|
||||||
builder: Object,
|
builder: Object,
|
||||||
buildTranches: {type: Function},
|
buildTranches: {type: Function},
|
||||||
@@ -32,6 +35,7 @@ const props = defineProps({
|
|||||||
})
|
})
|
||||||
const emit = defineEmits(['update:builder'])
|
const emit = defineEmits(['update:builder'])
|
||||||
|
|
||||||
|
const showDeleteDialog = ref(false)
|
||||||
|
|
||||||
let lastId = props.builder.id
|
let lastId = props.builder.id
|
||||||
builderFuncs[props.builder.id] = props.buildTranches
|
builderFuncs[props.builder.id] = props.buildTranches
|
||||||
@@ -53,6 +57,7 @@ if (props.deleteShapes)
|
|||||||
onBeforeUnmount(props.deleteShapes)
|
onBeforeUnmount(props.deleteShapes)
|
||||||
|
|
||||||
function deleteMyBuilder() {
|
function deleteMyBuilder() {
|
||||||
|
showDeleteDialog.value = false;
|
||||||
deleteBuilder(props.order, props.builder);
|
deleteBuilder(props.order, props.builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,5 +65,11 @@ function deleteMyBuilder() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
@import "@/styles/vars";
|
||||||
|
|
||||||
|
.trashcan {
|
||||||
|
:hover {
|
||||||
|
color: $red;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="element" class="chart"/>
|
<div id="tv-widget" ref="element" class="chart"/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
// import "/public/datafeeds/udf/dist/bundle.js"
|
|
||||||
import {onMounted, ref} from "vue";
|
import {onMounted, ref} from "vue";
|
||||||
import {initWidget} from "@/charts/chart.js";
|
import {initWidget} from "@/charts/chart.js";
|
||||||
|
|
||||||
@@ -12,18 +11,8 @@ const element = ref()
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const el = element.value;
|
const el = element.value;
|
||||||
initWidget(el)
|
initWidget(el)
|
||||||
initShapes()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
function initShapes() {
|
|
||||||
// const c = widget.chart()
|
|
||||||
// for( const s of ss.shapes ) {
|
|
||||||
// const type = s.type.toLowerCase().replace(' ','_')
|
|
||||||
// console.log('create type', type)
|
|
||||||
// c.createMultipointShape(s.points, {shape:type})
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -1,63 +1,55 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<row-bar :color="color">
|
<row-bar :color="sideColor">
|
||||||
<color-band :color="color"/>
|
|
||||||
<div style="width: 100%" class="justify-start align-content-start">
|
<div style="width: 100%" class="justify-start align-content-start">
|
||||||
<v-text-field type="number" inputmode="numeric" pattern="[0-9]*\.?[0-9]*" v-model="order.amount" variant="outlined"
|
<order-amount :order="props.order" class="mt-2" :color="sideColor"/>
|
||||||
density="compact"
|
|
||||||
:hint="available" :persistent-hint="true"
|
|
||||||
min="0"
|
|
||||||
class="amount py-2" :color="color"
|
|
||||||
:label="order.amountIsTokenA ? 'Amount':('Value in '+co.selectedSymbol.quote.s)"
|
|
||||||
>
|
|
||||||
<template #prepend>
|
|
||||||
<v-btn variant="outlined" :color="color" class="ml-3"
|
|
||||||
:text="(order.buy ? 'Buy ' : 'Sell ') + co.selectedSymbol.base.s"
|
|
||||||
@click="order.buy=!order.buy"/>
|
|
||||||
</template>
|
|
||||||
<template #prepend-inner>
|
|
||||||
<v-btn variant="text" text="max" class="px-0" size="small"
|
|
||||||
:disabled="!maxAmount || order.amountIsTokenA===order.buy && !co.price" @click="setMax"/>
|
|
||||||
</template>
|
|
||||||
<template #append-inner>
|
|
||||||
<v-btn :text="order.amountIsTokenA?co.baseToken.s:co.quoteToken.s+' worth'"
|
|
||||||
:color="color" variant="text" @click="toggleAmountToken"/>
|
|
||||||
</template>
|
|
||||||
</v-text-field>
|
|
||||||
<template v-for="b in builders">
|
<template v-for="b in builders">
|
||||||
<builder-factory :order="order" :builder="b"/>
|
<builder-factory :order="order" :builder="b"/>
|
||||||
</template>
|
</template>
|
||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<div v-if="order.builders.length===0"> <!--todo remove gralpha limitation of one builder-->
|
<div v-if="order.builders.length===0"> <!--todo remove gralpha limitation of one builder-->
|
||||||
<v-tooltip text="Spread order across time" location="top">
|
<v-tooltip text="Up to 1000 equal parts spread across time" location="top">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<span v-bind="props">
|
<span v-bind="props">
|
||||||
<v-btn :color="color" variant="text" prepend-icon="mdi-clock-outline" @click="build(order,'DCABuilder')">DCA</v-btn>
|
<v-btn id="DCA-button" :class="order.buy?'green':'red'" variant="text" prepend-icon="mdi-clock-outline" @click="build(order,'DCABuilder')">DCA</v-btn>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
<v-tooltip text="Trade a price level" location="top">
|
<v-tooltip text="Trade a price level" location="top">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<span v-bind="props">
|
<span v-bind="props">
|
||||||
<v-btn :color="color" variant="text" prepend-icon="mdi-ray-vertex" @click="build(order,'LimitBuilder')">Limit</v-btn>
|
<v-btn id="LimitBuilder-button" :class="order.buy?'green':'red'" variant="text" prepend-icon="mdi-ray-vertex" @click="build(order,'LimitBuilder')">Limit</v-btn>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
<v-tooltip text="Trade trends and channels" location="top">
|
<v-tooltip text="Trade trends and channels" location="top">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<span v-bind="props">
|
<span v-bind="props">
|
||||||
<v-btn :color="color" variant="text" prepend-icon="mdi-vector-line" @click="build(order,'DiagonalBuilder')">Diagonal</v-btn>
|
<v-btn id="Diagonal-button" :class="order.buy?'green':'red'" variant="text" prepend-icon="mdi-vector-line" @click="build(order,'DiagonalBuilder')">Diagonal</v-btn>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
<v-tooltip text="Up to 10 weighted parts spaced out in time" location="top">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<span v-bind="props">
|
||||||
|
<v-btn id="Dates-button" :class="order.buy?'green':'red'" variant="text" prepend-icon="mdi-reorder-vertical" @click="build(order,'DateBuilder')">Dates</v-btn>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
<v-tooltip text="Coming Soon! Stoplosses and Takeprofits" location="top">
|
<v-tooltip text="Coming Soon! Stoplosses and Takeprofits" location="top">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<span v-bind="props">
|
<span v-bind="props">
|
||||||
<v-btn :color="color" variant="text" prepend-icon="mdi-plus-minus" disabled>Stoploss</v-btn>
|
<v-btn :class="order.buy?'green':'red'" variant="text" prepend-icon="mdi-plus-minus" disabled>Stoploss</v-btn>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
<!-- mdi-ray-start-end mdi-vector-polyline -->
|
<!-- mdi-ray-start-end mdi-vector-polyline -->
|
||||||
|
|
||||||
|
<!-- after="newbie"-->
|
||||||
|
<one-time-hint name="choose-builder" :activator="hintData.activator"
|
||||||
|
:text="hintData.text" location="top"
|
||||||
|
:when="!builtAny"/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -68,29 +60,34 @@
|
|||||||
|
|
||||||
import BuilderFactory from "@/components/chart/BuilderFactory.vue";
|
import BuilderFactory from "@/components/chart/BuilderFactory.vue";
|
||||||
import {builderFuncs, newBuilder, orderFuncs, useChartOrderStore} from "@/orderbuild.js";
|
import {builderFuncs, newBuilder, orderFuncs, useChartOrderStore} from "@/orderbuild.js";
|
||||||
import {useOrderStore, useStore} from "@/store/store.js";
|
import {useStore} from "@/store/store.js";
|
||||||
import {computed, onUnmounted, onUpdated, ref, watchEffect} from "vue";
|
import {computed, onUnmounted, onUpdated, ref, watchEffect} from "vue";
|
||||||
import {lightenColor, lightenColor2} from "@/misc.js";
|
import {toPrecision} from "@/misc.js";
|
||||||
import {useTheme} from "vuetify";
|
import {useTheme} from "vuetify";
|
||||||
import RowBar from "@/components/chart/RowBar.vue";
|
import RowBar from "@/components/chart/RowBar.vue";
|
||||||
import ColorBand from "@/components/chart/ColorBand.vue";
|
|
||||||
import Color from "color";
|
import Color from "color";
|
||||||
import {newOrder} from "@/blockchain/orderlib.js";
|
import {newOrder} from "@/blockchain/orderlib.js";
|
||||||
|
import OrderAmount from "@/components/chart/OrderAmount.vue";
|
||||||
|
import {track} from "@/track.js";
|
||||||
|
import OneTimeHint from "@/components/OneTimeHint.vue";
|
||||||
|
|
||||||
const props = defineProps(['order'])
|
const props = defineProps(['order'])
|
||||||
const s = useStore()
|
const s = useStore()
|
||||||
const co = useChartOrderStore()
|
const co = useChartOrderStore()
|
||||||
const os = useOrderStore()
|
|
||||||
|
|
||||||
const marketBuilder = newBuilder('MarketBuilder')
|
const marketBuilder = newBuilder('MarketBuilder')
|
||||||
|
|
||||||
|
console.log('chart order', props.order)
|
||||||
const builders = computed(()=>props.order.builders.length > 0 ? props.order.builders : [marketBuilder])
|
const builders = computed(()=>props.order.builders.length > 0 ? props.order.builders : [marketBuilder])
|
||||||
const tokenIn = computed(()=>props.order.buy ? co.quoteToken : co.baseToken)
|
const tokenIn = computed(()=>props.order.buy ? co.quoteToken : co.baseToken)
|
||||||
const tokenOut = computed(()=>props.order.buy ? co.baseToken : co.quoteToken)
|
const tokenOut = computed(()=>props.order.buy ? co.baseToken : co.quoteToken)
|
||||||
|
|
||||||
|
|
||||||
console.log('order', props.order)
|
const builtAny = ref(false)
|
||||||
|
|
||||||
function build(order, component, options={}) {
|
function build(order, component, options={}) {
|
||||||
|
track('build', {builder:component})
|
||||||
|
builtAny.value = true
|
||||||
order.builders.push(newBuilder(component, options))
|
order.builders.push(newBuilder(component, options))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +111,38 @@ function buildOrder() {
|
|||||||
const order = props.order
|
const order = props.order
|
||||||
console.log('buildOrder', order)
|
console.log('buildOrder', order)
|
||||||
|
|
||||||
if (!order.amount) return null
|
if (!order.amount)
|
||||||
|
return {order: null, warnings: ['Amount must be greater than 0']}
|
||||||
|
|
||||||
|
const symbol = co.selectedSymbol
|
||||||
|
const amountDec = order.amountIsTokenA ? symbol.base.d : symbol.quote.d
|
||||||
|
const amount = BigInt(Math.trunc(order.amount * 10 ** amountDec))
|
||||||
|
|
||||||
|
let warnings = []
|
||||||
|
const inAddr = order.buy ? symbol.quote.a : symbol.base.a
|
||||||
|
const inDec = order.buy ? symbol.quote.d : symbol.base.d
|
||||||
|
const available = s.getBalance(inAddr) / 10 ** inDec
|
||||||
|
|
||||||
|
let needed
|
||||||
|
if (order.amountIsTokenA && order.buy)
|
||||||
|
// need quote currency to buy an amount of base
|
||||||
|
needed = order.amount * co.price
|
||||||
|
else if (order.amountIsTokenA && !order.buy)
|
||||||
|
// sell an exact amount of base
|
||||||
|
needed = order.amount
|
||||||
|
else if (!order.amountIsTokenA && order.buy)
|
||||||
|
// need an exact amount of quote
|
||||||
|
needed = order.amount
|
||||||
|
else if (!order.amountIsTokenA && !order.buy)
|
||||||
|
// sell a quote amount worth of base
|
||||||
|
needed = order.amount / co.price
|
||||||
|
else
|
||||||
|
throw new Error('Invalid order')
|
||||||
|
const deficit = needed - available
|
||||||
|
if (deficit > 0) {
|
||||||
|
const inSymbol = order.buy ? symbol.quote.s : symbol.base.s
|
||||||
|
warnings.push(`Insufficient funds. Add ${toPrecision(deficit, 5)} ${inSymbol} to your vault to complete this order.`)
|
||||||
|
}
|
||||||
|
|
||||||
// struct SwapOrder {
|
// struct SwapOrder {
|
||||||
// address tokenIn;
|
// address tokenIn;
|
||||||
@@ -127,20 +155,24 @@ function buildOrder() {
|
|||||||
// 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
|
// 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;
|
// Tranche[] tranches;
|
||||||
// }
|
// }
|
||||||
const symbol = co.selectedSymbol
|
|
||||||
const amountDec = order.amountIsTokenA ? symbol.base.d : symbol.quote.d
|
|
||||||
const amount = BigInt(Math.trunc(order.amount * 10 ** amountDec))
|
|
||||||
const amountIsInput = !!(order.amountIsTokenA ^ order.buy)
|
const amountIsInput = !!(order.amountIsTokenA ^ order.buy)
|
||||||
|
|
||||||
let tranches = []
|
let tranches = []
|
||||||
for (const builder of builders.value) {
|
for (const builder of builders.value) {
|
||||||
console.log('builder', builder)
|
console.log('builder', builder)
|
||||||
const ts = builderFuncs[builder.id]()
|
const built = builderFuncs[builder.id]()
|
||||||
|
const ts = built.tranches
|
||||||
|
const ws = built.warnings
|
||||||
console.log('tranches', ts)
|
console.log('tranches', ts)
|
||||||
tranches = [...tranches, ...ts]
|
tranches = [...tranches, ...ts]
|
||||||
|
warnings = [...warnings, ...ws]
|
||||||
}
|
}
|
||||||
|
|
||||||
return newOrder(tokenIn.value.a, tokenOut.value.a, symbol.exchangeId, symbol.fee, amount, amountIsInput, symbol.inverted, tranches)
|
return {
|
||||||
|
warnings,
|
||||||
|
order: newOrder(tokenIn.value.a, tokenOut.value.a, symbol.exchangeId, symbol.fee,
|
||||||
|
amount, amountIsInput, symbol.inverted, tranches),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -156,7 +188,8 @@ onUnmounted(() => delete orderFuncs[lastId])
|
|||||||
|
|
||||||
|
|
||||||
const theme = useTheme().current
|
const theme = useTheme().current
|
||||||
const color = computed(()=>new Color(props.order.buy?theme.value.colors.success:theme.value.colors.error).darken(0.2).string())
|
const sideColor = computed(()=>new Color(props.order.buy?theme.value.colors.success:theme.value.colors.error).darken(0.2).string())
|
||||||
|
const color = computed(()=>theme.value.colors["on-background"])
|
||||||
// const lightColor = computed(() => lightenColor(color.value))
|
// const lightColor = computed(() => lightenColor(color.value))
|
||||||
// const faintColor = computed(() => lightenColor2(color.value))
|
// const faintColor = computed(() => lightenColor2(color.value))
|
||||||
// const colorStyle = computed(() => { return {'color': color.value} })
|
// const colorStyle = computed(() => { return {'color': color.value} })
|
||||||
@@ -164,55 +197,23 @@ const color = computed(()=>new Color(props.order.buy?theme.value.colors.success:
|
|||||||
// const lightColorStyle = computed(() => { return {'background-color': lightColor.value} })
|
// const lightColorStyle = computed(() => { return {'background-color': lightColor.value} })
|
||||||
// const faintColorStyle = computed(() => { return {'background-color': faintColor.value} })
|
// const faintColorStyle = computed(() => { return {'background-color': faintColor.value} })
|
||||||
|
|
||||||
const inToken = computed( ()=>props.order.buy ? co.quoteToken : co.baseToken )
|
// Tutorial Hint
|
||||||
const maxAmount = computed(()=>{
|
|
||||||
const token = inToken.value;
|
|
||||||
if (!token)
|
|
||||||
return null
|
|
||||||
const balance = s.balances[token.a]
|
|
||||||
if( !balance )
|
|
||||||
return null
|
|
||||||
const divisor = os.amountIsTotal ? 1 : os.tranches
|
|
||||||
return balance / 10**token.d / divisor
|
|
||||||
})
|
|
||||||
const available = computed(()=>{
|
|
||||||
const max = maxAmount.value
|
|
||||||
return max === null ? '' : `Available: ${maxAmount.value} ${tokenIn.value.s}`
|
|
||||||
})
|
|
||||||
|
|
||||||
const lastMaxValue = ref(-1)
|
let tutorial = 'limit'
|
||||||
|
|
||||||
function setMax() {
|
const _hintData = {
|
||||||
let amount = maxAmount.value
|
'limit': {
|
||||||
if (amount) {
|
activator: '#LimitBuilder-button',
|
||||||
const order = props.order
|
text: '↓ Try a Limit Ladder ↓'
|
||||||
const price = co.price
|
},
|
||||||
if (order.amountIsTokenA===order.buy) {
|
|
||||||
if (order.buy)
|
|
||||||
amount /= price
|
|
||||||
else
|
|
||||||
amount *= price
|
|
||||||
}
|
|
||||||
amount = Number(amount.toPrecision(7))
|
|
||||||
lastMaxValue.value = amount
|
|
||||||
order.amount = amount
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleAmountToken() {
|
const hintData = computed(()=>_hintData[tutorial] || _hintData['limit'])
|
||||||
const order = props.order
|
|
||||||
order.amountIsTokenA=!order.amountIsTokenA
|
|
||||||
if (order.amount === lastMaxValue.value)
|
|
||||||
setMax()
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.amount {
|
@use "src/styles/vars" as *;
|
||||||
max-width: 30em;
|
.v-btn.green:hover {color: $green !important;}
|
||||||
div.v-field {
|
.v-btn.red:hover {color: $red !important;}
|
||||||
padding-left: 0;
|
</style>
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -7,7 +7,13 @@
|
|||||||
Place Dexorder
|
Place Dexorder
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn variant="text" prepend-icon="mdi-delete" v-if="co.orders.length>0"
|
<v-btn variant="text" prepend-icon="mdi-delete" v-if="co.orders.length>0"
|
||||||
:disabled="!orderChanged" @click="cancelOrder">Reset</v-btn>
|
:disabled="!orderChanged" @click="resetOrder">Reset</v-btn>
|
||||||
|
<v-btn id="share-btn" variant="text" prepend-icon="mdi-share" v-if="co.orders.length>0"
|
||||||
|
:disabled="sharing"
|
||||||
|
@click="shareOrder">{{sharing?'Preparing...':'Share Order'}}</v-btn>
|
||||||
|
<v-tooltip activator="#share-btn" :text="shareError?'Error copying share link :(':'Copied share link!'" v-model="showSharedTooltip"
|
||||||
|
:open-on-hover="false" :open-on-click="false"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<div class="overflow-y-auto">
|
<div class="overflow-y-auto">
|
||||||
<needs-chart>
|
<needs-chart>
|
||||||
@@ -19,7 +25,30 @@
|
|||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-spacer/>
|
<v-spacer/>
|
||||||
<v-btn @click="()=>showResetDialog=false">Keep Existing</v-btn>
|
<v-btn @click="()=>showResetDialog=false">Keep Existing</v-btn>
|
||||||
<v-btn @click="()=>{co.resetOrders(); showResetDialog=false}" color="red" text="Reset Order"/>
|
<v-btn @click="doResetOrder" color="red" text="Reset Order"/>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
<v-dialog v-model="showWarnings" max-width="300">
|
||||||
|
<v-card prepend-icon="mdi-warning" title="Order Warnings">
|
||||||
|
<v-card-item>
|
||||||
|
<ul class="ml-5">
|
||||||
|
<li v-for="w of orderWarnings">{{w}}</li>
|
||||||
|
</ul>
|
||||||
|
</v-card-item>
|
||||||
|
<v-card-text>Continue placing this order?</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer/>
|
||||||
|
<v-btn @click="()=>showWarnings=false">Back</v-btn>
|
||||||
|
<v-btn @click="doPlaceOrder">Place Order</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
<v-dialog v-model="placementError" max-width="300">
|
||||||
|
<v-card prepend-icon="mdi-alert" title="Order Error" text="There was an error placing your order. Please try again or contact support.">
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer/>
|
||||||
|
<v-btn @click="()=>{placementError=false;ws.transaction=null}">OK</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
@@ -41,6 +70,9 @@ import {useWalletStore} from "@/blockchain/wallet.js";
|
|||||||
import ToolbarPane from "@/components/chart/ToolbarPane.vue";
|
import ToolbarPane from "@/components/chart/ToolbarPane.vue";
|
||||||
import NeedsChart from "@/components/NeedsChart.vue";
|
import NeedsChart from "@/components/NeedsChart.vue";
|
||||||
import {PlaceOrderTransaction} from "@/blockchain/transaction.js";
|
import {PlaceOrderTransaction} from "@/blockchain/transaction.js";
|
||||||
|
import {errorSuggestsMissingVault} from "@/misc.js";
|
||||||
|
import {track} from "@/track.js";
|
||||||
|
import {getShareUrl} from "@/share.js";
|
||||||
|
|
||||||
const s = useStore()
|
const s = useStore()
|
||||||
const co = useChartOrderStore()
|
const co = useChartOrderStore()
|
||||||
@@ -48,10 +80,10 @@ const os = useOrderStore()
|
|||||||
const ws = useWalletStore()
|
const ws = useWalletStore()
|
||||||
|
|
||||||
function changeSymbol(symbol) {
|
function changeSymbol(symbol) {
|
||||||
console.log('changeSymbol', symbol)
|
// console.log('changeSymbol', symbol)
|
||||||
os.tokenA = symbol.base
|
os.tokenA = symbol.base
|
||||||
os.tokenB = symbol.quote
|
os.tokenB = symbol.quote
|
||||||
routeFinder.invoke()
|
// routeFinder.invoke()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -78,10 +110,23 @@ const valid = computed(()=>{
|
|||||||
|
|
||||||
const orderChanged = computed(()=>!(co.orders.length===1 && co.orders[0].builders.length===0 && !co.orders[0].amount))
|
const orderChanged = computed(()=>!(co.orders.length===1 && co.orders[0].builders.length===0 && !co.orders[0].amount))
|
||||||
|
|
||||||
function cancelOrder() {
|
const showWarnings = ref(false)
|
||||||
|
const orderWarnings = ref([])
|
||||||
|
|
||||||
|
const placementError = ref(false)
|
||||||
|
|
||||||
|
function resetOrder() {
|
||||||
showResetDialog.value = true
|
showResetDialog.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function doResetOrder() {
|
||||||
|
co.resetOrders();
|
||||||
|
orderWarnings.value = []
|
||||||
|
showWarnings.value = false
|
||||||
|
showResetDialog.value = false
|
||||||
|
placementError.value = false
|
||||||
|
}
|
||||||
|
|
||||||
watchEffect(()=>{
|
watchEffect(()=>{
|
||||||
const removable = []
|
const removable = []
|
||||||
for (const order of ws.pendingOrders) {
|
for (const order of ws.pendingOrders) {
|
||||||
@@ -108,25 +153,92 @@ watchEffect(()=>{
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let built = []
|
||||||
|
|
||||||
async function placeOrder() {
|
async function placeOrder() {
|
||||||
|
track('place-order')
|
||||||
const chartOrders = co.orders;
|
const chartOrders = co.orders;
|
||||||
const built = []
|
const allWarns = []
|
||||||
|
built = []
|
||||||
for (const chartOrder of chartOrders) {
|
for (const chartOrder of chartOrders) {
|
||||||
console.log('chartOrder', chartOrder)
|
console.log('chartOrder', chartOrder)
|
||||||
const buildOrder = orderFuncs[chartOrder.id]
|
const buildOrder = orderFuncs[chartOrder.id]
|
||||||
const order = buildOrder()
|
const {order, warnings} = buildOrder()
|
||||||
built.push(order)
|
built.push(order)
|
||||||
|
allWarns.push(...warnings)
|
||||||
}
|
}
|
||||||
console.log('place orders', built)
|
|
||||||
|
if (allWarns.length > 0) {
|
||||||
|
orderWarnings.value = allWarns
|
||||||
|
showWarnings.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await doPlaceOrder()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function doPlaceOrder() {
|
||||||
|
console.log('place orders')
|
||||||
|
placementError.value = false
|
||||||
|
showWarnings.value = false
|
||||||
if (ws.transaction!==null) {
|
if (ws.transaction!==null) {
|
||||||
console.error('Transaction already in progress')
|
console.error('Transaction already in progress')
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
new PlaceOrderTransaction(s.chainId, toRaw(built[0])).submit()
|
const tx = new PlaceOrderTransaction(s.chainId, toRaw(built[0]));
|
||||||
|
tx.retries = 60
|
||||||
|
const oldFailed = tx.failed
|
||||||
|
tx.failed = function(e) {
|
||||||
|
console.error('place order failed', errorSuggestsMissingVault(e), e.code, e)
|
||||||
|
if (errorSuggestsMissingVault(e) && e.action === 'feeManager' && this.retries-- >= 0) {
|
||||||
|
s.creatingVault = true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
s.creatingVault = false
|
||||||
|
oldFailed.bind(this)(e)
|
||||||
|
placementError.value = e.info?.error?.code !== 4001
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tx.submit() // this assigns the tx to walletStore.transaction
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sharing = ref(false)
|
||||||
|
const showSharedTooltip = ref(false)
|
||||||
|
const shareError = ref(false)
|
||||||
|
const showShareDialog = ref(false)
|
||||||
|
const shareUrl = ref(null)
|
||||||
|
|
||||||
|
function shareOrder() {
|
||||||
|
sharing.value = true
|
||||||
|
track('share')
|
||||||
|
getShareUrl().then(url => {
|
||||||
|
shareError.value = url === null
|
||||||
|
if (url === null) {
|
||||||
|
sharing.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log('share url', url)
|
||||||
|
shareUrl.value = url
|
||||||
|
sharing.value = false
|
||||||
|
navigator.permissions.query({name: "clipboard-write"}).then((permission) => {
|
||||||
|
const permitted = permission.state === "granted" || permission.state === "prompt"
|
||||||
|
if (!permitted) {
|
||||||
|
showShareDialog.value = true
|
||||||
|
} else {
|
||||||
|
navigator.clipboard.writeText(url)
|
||||||
|
.then(() => {
|
||||||
|
showSharedTooltip.value = true
|
||||||
|
setTimeout(() => showSharedTooltip.value = false, 3000)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
showShareDialog.value = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}).catch(() => null)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss"> // NOT scoped
|
<style lang="scss"> // NOT scoped
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
<toolbar-pane title="Status" icon="mdi-format-list-bulleted-square">
|
<toolbar-pane title="Status" icon="mdi-format-list-bulleted-square">
|
||||||
<template #toolbar>
|
<template #toolbar>
|
||||||
<v-btn variant="text" v-if="s.vault" prepend-icon="mdi-delete-alert" color="red"
|
<v-btn variant="text" v-if="s.vault" prepend-icon="mdi-delete-alert" color="red"
|
||||||
@click="cancelAll(s.vault)" :disabled="!anyOrdersOpen"
|
@click="(async function (vault){
|
||||||
|
new CancelAllTransaction(useStore().chainId, vault).submit()
|
||||||
|
})(s.vault)" :disabled="!anyOrdersOpen"
|
||||||
text="Cancel All"/>
|
text="Cancel All"/>
|
||||||
</template>
|
</template>
|
||||||
<needs-signer>
|
<needs-signer>
|
||||||
@@ -16,10 +18,10 @@
|
|||||||
import ToolbarPane from "@/components/chart/ToolbarPane.vue";
|
import ToolbarPane from "@/components/chart/ToolbarPane.vue";
|
||||||
import Orders from "@/components/Status.vue";
|
import Orders from "@/components/Status.vue";
|
||||||
import NeedsSigner from "@/components/NeedsSigner.vue";
|
import NeedsSigner from "@/components/NeedsSigner.vue";
|
||||||
import {cancelAll} from "@/blockchain/wallet.js";
|
|
||||||
import {useStore} from "@/store/store.js";
|
import {useStore} from "@/store/store.js";
|
||||||
import {computed} from "vue";
|
import {computed} from "vue";
|
||||||
import {isOpen} from "@/blockchain/orderlib.js";
|
import {isOpen} from "@/blockchain/orderlib.js";
|
||||||
|
import {CancelAllTransaction} from "@/blockchain/transaction.js";
|
||||||
|
|
||||||
const s = useStore()
|
const s = useStore()
|
||||||
|
|
||||||
|
|||||||
@@ -1,245 +1,301 @@
|
|||||||
<template>
|
<template class="d-flex align-content-center flex-column" style="height: 100%; width: 100%;">
|
||||||
<rung-builder name='DCA' :order="order" :builder="builder" v-model="timeEndpoints"
|
<builder-panel :order="order" :builder="builder" :build-tranches="buildTranches"
|
||||||
:shape="VLine"
|
:adjust-shapes="adjustShapes" :delete-shapes="deleteShapes" name="DCA">
|
||||||
:mode="1" :flip="flipped" :orientation="0"
|
<div class="d-flex flex-column" style="width: 100%;">
|
||||||
:get-model-value="getModelValue" :set-model-value="setModelValue"
|
<div class="d-flex flex-row mb-5">
|
||||||
:get-points-value="getPointsValue"
|
<div class="align-self-center mr-3">Start:</div>
|
||||||
:set-values="setValues" :set-weights="setWeights"
|
<absolute-time-entry v-model="startTime"/>
|
||||||
:std-width="stdWidth" :build-tranches="buildTranches">
|
</div>
|
||||||
|
|
||||||
<!--
|
<div class="d-flex flex-row">
|
||||||
<v-list style="background-color: inherit">
|
<div>
|
||||||
<v-list-item v-for="t in absoluteTimes">{{t}}</v-list-item>
|
<v-text-field label="Split into" type="number" variant="outlined"
|
||||||
</v-list>
|
aria-valuemin="1" aria-valuemax="100" min="1" max="1000" step="1"
|
||||||
-->
|
:hint="partsGasHint" :persistent-hint="true"
|
||||||
|
:color="color"
|
||||||
|
v-model="parts" v-auto-select class="parts mr-3">
|
||||||
|
<template v-slot:append-inner>
|
||||||
|
parts
|
||||||
|
</template>
|
||||||
|
</v-text-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
<table>
|
<div class="mr-3">
|
||||||
<tbody>
|
<order-amount :order="props.order" :multiplier="props.builder.tranches" :color="color" style="width: 20em"/>
|
||||||
<tr>
|
</div>
|
||||||
<td>
|
|
||||||
<absolute-time-entry v-model="absTimeA"/>
|
|
||||||
</td>
|
|
||||||
<td class="weight">{{ weights.length ? allocationTexts[0] : '' }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="weights.length>2" v-for="i in weights.length-2" class="ml-5"> <!-- vue uses 1-based loops -->
|
|
||||||
<td class="d-flex justify-end pr-3">{{ dateStrings[i] }}</td>
|
|
||||||
<td class="weight">{{ allocationTexts[i] }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="weights.length>1">
|
|
||||||
<td>
|
|
||||||
<absolute-time-entry v-model="absTimeB"/>
|
|
||||||
</td>
|
|
||||||
<td class="weight">{{ allocationTexts[weights.length-1] }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
|
<div>
|
||||||
</rung-builder>
|
<v-text-field type="number" variant="outlined" :min="1" v-model="displayedInterval" class="interval"
|
||||||
|
:color="color"
|
||||||
|
:label="intervalIsTotal ? 'Total completion time' : 'Time between parts'" v-auto-select>
|
||||||
|
<template v-slot:prepend-inner>
|
||||||
|
<v-btn variant="outlined"
|
||||||
|
:text="intervalIsTotal ? 'All within' : 'Spaced apart by'" class="within mr-2"
|
||||||
|
style="width: 10em"
|
||||||
|
@click="intervalIsTotal=!intervalIsTotal"/>
|
||||||
|
</template>
|
||||||
|
<template v-slot:append-inner>
|
||||||
|
<v-btn variant="outlined" :text="timeUnitsStr" style="width: 5em"
|
||||||
|
@click="toggleTimeUnits" class="time-units"/>
|
||||||
|
</template>
|
||||||
|
</v-text-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</builder-panel>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {builderDefaults, DEFAULT_SLIPPAGE, MIN_EXECUTION_TIME, useChartOrderStore} from "@/orderbuild.js";
|
import {builderDefaults, useChartOrderStore} from "@/orderbuild.js";
|
||||||
import {allocationText, VLine} from "@/charts/shape.js";
|
import {allocationText, ShapeType} from "@/charts/shape.js";
|
||||||
import {sideColor} from "@/misc.js";
|
import {sideColor, SingletonCoroutine, toPrecision, vAutoSelect} from "@/misc.js";
|
||||||
import {useOrderStore, useStore} from "@/store/store.js";
|
import {useStore} from "@/store/store.js";
|
||||||
import {DISTANT_FUTURE, MAX_FRACTION, newTranche} from "@/blockchain/orderlib.js";
|
|
||||||
import RungBuilder from "@/components/chart/RungBuilder.vue";
|
|
||||||
import {computed, ref, watchEffect} from "vue";
|
import {computed, ref, watchEffect} from "vue";
|
||||||
import {chart, dragging} 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 {DateTime} from "luxon";
|
import BuilderPanel from "@/components/chart/BuilderPanel.vue";
|
||||||
|
import {ohlcStart} from "@/charts/chart-misc.js";
|
||||||
|
import Color from "color";
|
||||||
|
import OrderAmount from "@/components/chart/OrderAmount.vue";
|
||||||
|
import {DEFAULT_SLIPPAGE, MAX_FRACTION, newTranche} from "@/blockchain/orderlib.js";
|
||||||
|
import {getFeeSchedule} from "@/fees.js";
|
||||||
|
import {NATIVE_TOKEN} from "@/common.js";
|
||||||
|
|
||||||
const s = useStore()
|
const s = useStore()
|
||||||
const os = useOrderStore()
|
|
||||||
const co = useChartOrderStore()
|
const co = useChartOrderStore()
|
||||||
const props = defineProps(['order', 'builder'])
|
const props = defineProps(['order', 'builder'])
|
||||||
const emit = defineEmits(['update:builder'])
|
const emit = defineEmits(['update:builder'])
|
||||||
|
|
||||||
const minWidth = computed(()=>co.intervalSecs)
|
|
||||||
const stdWidth = computed(()=>10 * minWidth.value)
|
|
||||||
|
|
||||||
function computeDefaultColor() {
|
function computeDefaultColor() {
|
||||||
const index = props.order.builders.indexOf(props.builder)
|
const index = props.order.builders.indexOf(props.builder)
|
||||||
return sideColor(props.order.buy, index)
|
return sideColor(props.order.buy, index)
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultColor = computeDefaultColor()
|
const color = computed(()=>computeDefaultColor())
|
||||||
|
|
||||||
|
const defaultTranches = 10
|
||||||
|
|
||||||
builderDefaults(props.builder, {
|
builderDefaults(props.builder, {
|
||||||
timeA: s.clock, // todo relative 0
|
startTime: s.clock, // todo relative 0
|
||||||
timeB: null,
|
endTime: s.clock + defaultTranches * co.intervalSecs,
|
||||||
|
interval: co.intervalSecs,
|
||||||
|
tranches: defaultTranches,
|
||||||
|
percentage: 100/defaultTranches,
|
||||||
// relative: true, // todo
|
// relative: true, // todo
|
||||||
relative: false,
|
relative: false,
|
||||||
slippage: DEFAULT_SLIPPAGE,
|
slippage: DEFAULT_SLIPPAGE,
|
||||||
rungs: 1,
|
color: color.value,
|
||||||
skew: 0,
|
valid: true,
|
||||||
color: defaultColor,
|
|
||||||
buy: true,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const times = ref([])
|
function adjustShapes() {}
|
||||||
const weights = ref([])
|
|
||||||
|
|
||||||
const amountSymbol = computed(()=>props.order.amountIsTokenA ? co.selectedSymbol.base.s : co.selectedSymbol.quote.s )
|
function buildTranches() {
|
||||||
const allocationTexts = computed(()=>weights.value.map((w)=>allocationText(props.order.buy, w, w*props.order.amount, amountSymbol.value)))
|
const warnings = []
|
||||||
|
let interval = props.builder.interval
|
||||||
|
let parts = props.builder.tranches
|
||||||
|
if (interval < 60) {
|
||||||
|
interval = 60
|
||||||
|
parts = (props.builder.endTime - props.builder.startTime) / interval
|
||||||
|
warnings.push(`DCA parts cannot be closer than one minute apart. Using ${parts} parts.`)
|
||||||
|
}
|
||||||
|
const rate = Math.ceil(MAX_FRACTION / parts)
|
||||||
|
const endTime = Math.ceil(props.builder.endTime + 10*props.builder.tranches)
|
||||||
|
const startTime = Math.floor(props.builder.startTime);
|
||||||
|
const tranches = [newTranche({marketOrder:true,
|
||||||
|
startTime, endTime, rateLimitFraction: rate, rateLimitPeriod: Math.floor(interval),
|
||||||
|
slippage: props.builder.slippage})]
|
||||||
|
return {tranches, warnings}
|
||||||
|
}
|
||||||
|
|
||||||
const endTimes = computed(()=>{
|
const parts = computed({
|
||||||
if (props.builder.rungs === 1)
|
get() { return props.builder.tranches },
|
||||||
return DISTANT_FUTURE
|
set(v) {
|
||||||
const ts = times.value
|
v = Number(v)
|
||||||
const window = Math.max(MIN_EXECUTION_TIME, Math.floor((ts[ts.length-1]-ts[0])/props.builder.rungs))
|
v = Math.max(1, Math.min(1000,v))
|
||||||
return ts.map((t)=>t+window)
|
props.builder.tranches = v
|
||||||
})
|
props.builder.endTime = props.builder.startTime + props.builder.interval * v
|
||||||
const absoluteTimes = computed(()=>{
|
setPoints(null, true)
|
||||||
// console.log('absoluteTimes', props.builder.relative, times.value)
|
}
|
||||||
// if (!props.builder.relative)
|
|
||||||
return times.value
|
|
||||||
// const now = s.clock
|
|
||||||
// return times.value.map((t)=>now+t)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const dateStrings = computed(()=>absoluteTimes.value.map((t)=>{
|
const sched = ref(null)
|
||||||
const n = DateTime.fromSeconds(t).setZone(s.timeZone)
|
const schedFetcher = new SingletonCoroutine(async (vault)=>sched.value = vault === null ? null : await getFeeSchedule(vault))
|
||||||
const y = n.toLocaleString({year:'numeric'})
|
|
||||||
const m = n.toLocaleString({month:'long'})
|
|
||||||
const d = n.toLocaleString({day:'numeric'})
|
|
||||||
const h = n.toLocaleString({hour:'numeric', minute:'numeric'})
|
|
||||||
return `${y} ${m} ${d} ${h}`
|
|
||||||
}))
|
|
||||||
|
|
||||||
watchEffect(()=>{
|
const partsGasHint = computed(()=>{
|
||||||
// auto scroll
|
if (sched.value === null) {
|
||||||
if (!dragging && absoluteTimes.value.length) {
|
schedFetcher.invoke(s.vault)
|
||||||
const endTime = absoluteTimes.value[absoluteTimes.value.length-1]
|
return null
|
||||||
const range = chart.getVisibleRange()
|
}
|
||||||
const width = range.to - range.from
|
const ethFee = Number(sched.value.gasFee) * parts.value / 1e18;
|
||||||
const now = s.clock
|
const mark = s.markPrice(NATIVE_TOKEN)
|
||||||
const extra = (Math.max(0,endTime - now) + stdWidth.value/2) / width
|
if (mark)
|
||||||
// console.log('visrange', range, width, absV)
|
return '$' + Number(ethFee*mark).toFixed(2) + ' gas fee'
|
||||||
if (range.to < endTime) {
|
else
|
||||||
// console.log('scrolling')
|
return toPrecision(ethFee) + ' ETH gas fee'
|
||||||
chart.setVisibleRange({from: now - width, to: now}, {
|
})
|
||||||
percentRightMargin: Math.round(100 * extra),
|
|
||||||
applyDefaultRightMargin: false
|
const intervalIsTotal = ref(false)
|
||||||
})
|
const displayedInterval = computed({
|
||||||
|
get() {
|
||||||
|
let result = props.builder.interval / timeUnits[timeUnitIndex.value][1]
|
||||||
|
if (intervalIsTotal.value)
|
||||||
|
// express as per-part intervals
|
||||||
|
result *= props.builder.tranches
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
set(v) {
|
||||||
|
v = Number(v)
|
||||||
|
let newValue = v * timeUnits[timeUnitIndex.value][1];
|
||||||
|
if (intervalIsTotal.value)
|
||||||
|
newValue /= props.builder.tranches
|
||||||
|
if (Math.abs(newValue - props.builder.interval) >= Number.EPSILON) {
|
||||||
|
props.builder.interval = newValue
|
||||||
|
props.builder.endTime = props.builder.startTime + newValue * props.builder.tranches
|
||||||
|
setPoints(null, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const timeUnits = [['minutes', 60], ['hours', 3600], ['days', 86400]]
|
||||||
|
function defaultTimeUnit() {
|
||||||
|
let i=1
|
||||||
|
while( i < timeUnits.length && props.builder.interval >= timeUnits[i][1] )
|
||||||
|
i++
|
||||||
|
console.log('defaultTimeUnit', props.builder.interval, i-1)
|
||||||
|
return i-1
|
||||||
|
}
|
||||||
|
const _timeUnitIndex = ref(defaultTimeUnit());
|
||||||
|
const timeUnitIndex = computed({
|
||||||
|
get() {return _timeUnitIndex.value},
|
||||||
|
set(v) {_timeUnitIndex.value = v % timeUnits.length}
|
||||||
|
})
|
||||||
|
const timeUnitsStr = computed(()=>timeUnits[timeUnitIndex.value][0])
|
||||||
|
|
||||||
const absTimeA = computed({
|
function toggleTimeUnits() {
|
||||||
get() { return _timeEndpoints.value[0] },
|
timeUnitIndex.value += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const startTime = computed({
|
||||||
|
get() { return props.builder.relative ? s.clock + props.builder.startTime : props.builder.startTime },
|
||||||
|
set(v) {emitUpdate({startTime: v})}
|
||||||
|
})
|
||||||
|
|
||||||
|
const endTime = computed({
|
||||||
|
get() { return startTime.value + props.builder.interval * props.builder.tranches },
|
||||||
set(v) {
|
set(v) {
|
||||||
if (v!==null)
|
emitUpdate({endTime: v, interval: Math.abs(v - startTime.value)})
|
||||||
v = Number(v)
|
|
||||||
updateA(v)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const absTimeB = computed({
|
|
||||||
get() { return _timeEndpoints.value[1] },
|
|
||||||
set(v) {
|
|
||||||
if (v!==null)
|
|
||||||
v = Number(v)
|
|
||||||
updateB(v)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
const _timeEndpoints = ref([props.builder.timeA, props.builder.timeB])
|
const barStart = computed(()=>ohlcStart(startTime.value, props.builder.interval))
|
||||||
const timeEndpoints = computed({
|
const barEnd = computed(()=>ohlcStart(endTime.value, props.builder.interval))
|
||||||
get() { return _timeEndpoints.value},
|
|
||||||
set(v) {
|
function emitUpdatedPoints(a, b) {
|
||||||
const [a, b] = v
|
const updates = {}
|
||||||
update(a,b)
|
if (a.time !== barStart.value)
|
||||||
|
updates.startTime = a.time
|
||||||
|
// only set the end if it was moved relative to the start
|
||||||
|
if (b.time - a.time === barEnd.value - barStart.value)
|
||||||
|
// the end has moved exactly the same amount as the start. add a relative amount.
|
||||||
|
updates.endTime = a.time + (endTime.value - startTime.value)
|
||||||
|
else
|
||||||
|
updates.endTime = b.time
|
||||||
|
updates.interval = (b.time - a.time) / props.builder.tranches
|
||||||
|
emitUpdate(updates)
|
||||||
|
}
|
||||||
|
|
||||||
|
function setPoints(points=null, shapeCorrectionNeeded=false) {
|
||||||
|
if (points === null)
|
||||||
|
points = [{time:props.builder.startTime}, {time:props.builder.endTime}]
|
||||||
|
let [a, b] = points
|
||||||
|
const period = co.intervalSecs;
|
||||||
|
|
||||||
|
if (b.time < a.time) {
|
||||||
|
[a, b] = [b, a]
|
||||||
|
shapeCorrectionNeeded = true
|
||||||
|
} else if (b.time === a.time) {
|
||||||
|
b.time = a.time + period
|
||||||
|
shapeCorrectionNeeded = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const curBarStart = ohlcStart(s.clock, period);
|
||||||
|
if (a.time < curBarStart) {
|
||||||
|
const width = b.time - a.time;
|
||||||
|
a.time = s.clock
|
||||||
|
b.time = a.time + width
|
||||||
|
shapeCorrectionNeeded = true
|
||||||
|
}
|
||||||
|
emitUpdatedPoints(a, b);
|
||||||
|
if (shapeCorrectionNeeded) {
|
||||||
|
const [sa, sb] = shape.getPoints()
|
||||||
|
const aTime = ohlcStart(a.time, period);
|
||||||
|
const bTime = ohlcStart(b.time, period);
|
||||||
|
if (sa.time !== aTime || sb.time !== bTime) {
|
||||||
|
const aa = {time: aTime}
|
||||||
|
const bb = {time: bTime}
|
||||||
|
shape.setPoints([aa, bb])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const callbacks = {
|
||||||
|
onDrag(shapeId, shape, points) {
|
||||||
|
let [a, b] = points
|
||||||
|
if (b.time < a.time)
|
||||||
|
[a, b] = [b, a]
|
||||||
|
emitUpdatedPoints(a, b)
|
||||||
|
},
|
||||||
|
onPoints(shapeId, shape, points) {
|
||||||
|
setPoints(points, shape);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitUpdate(changes) {
|
||||||
|
for (const [k, v] of Object.entries(changes))
|
||||||
|
props.builder[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = computed(()=>{
|
||||||
|
const o = props.order
|
||||||
|
return allocationText(o.buy, 1, o.amount, co.selectedSymbol.base.s,
|
||||||
|
o.amountIsTokenA ? null : co.selectedSymbol.quote.s, parts.value, '\n')
|
||||||
|
})
|
||||||
|
|
||||||
|
let shapeId = createShape(ShapeType.DateRange, [{time:barStart.value}, {time:barEnd.value}], {}, callbacks)
|
||||||
|
let shape = widget.activeChart().getShapeById(shapeId)
|
||||||
|
function setProperties() {
|
||||||
|
const color = computeDefaultColor()
|
||||||
|
shape.setProperties({
|
||||||
|
extendTop: true,
|
||||||
|
extendBottom: true,
|
||||||
|
linecolor: color,
|
||||||
|
backgroundColor: new Color(color).alpha(0.2).toString(),
|
||||||
|
backgroundTransparency: 60,
|
||||||
|
customText: {
|
||||||
|
text: text.value,
|
||||||
|
visible: true,
|
||||||
|
color: color,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
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 updateA(a) {
|
deleteShapeId(shapeId)
|
||||||
update(a, _timeEndpoints.value[1], true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function updateB(b) {
|
|
||||||
update(_timeEndpoints.value[0], b, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function update(a, b, updatingA) {
|
|
||||||
if (updatingA) {
|
|
||||||
const minB = a + minWidth.value
|
|
||||||
if (b < minB)
|
|
||||||
b = minB
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const maxA = b - minWidth.value
|
|
||||||
if (a > maxA)
|
|
||||||
a = maxA
|
|
||||||
}
|
|
||||||
_timeEndpoints.value = [a, b]
|
|
||||||
const newBuilder = {...props.builder}
|
|
||||||
newBuilder.timeA = a
|
|
||||||
newBuilder.timeB = b
|
|
||||||
emit('update:builder', newBuilder)
|
|
||||||
}
|
|
||||||
|
|
||||||
const flipped = computed(()=>{
|
|
||||||
const a = props.builder.timeA
|
|
||||||
const b = props.builder.timeB
|
|
||||||
return a !== null && b !== null && a > b
|
|
||||||
})
|
|
||||||
|
|
||||||
function buildTranches() {
|
|
||||||
const order = props.order
|
|
||||||
const builder = props.builder
|
|
||||||
const tranches = []
|
|
||||||
|
|
||||||
console.log('buildTranches', builder, order, tranches)
|
|
||||||
const ts = times.value
|
|
||||||
const ets = endTimes.value
|
|
||||||
const ws = weights.value
|
|
||||||
for(let i=0; i<ts.length; i++) {
|
|
||||||
const t = newTranche({
|
|
||||||
fraction: ws[i] * MAX_FRACTION,
|
|
||||||
startTime: ts[i],
|
|
||||||
endTime: Math.max(ets[i],ts[i]+60), // always give at least 60 seconds of window to execute
|
|
||||||
slippage: builder.slippage,
|
|
||||||
})
|
|
||||||
tranches.push(t)
|
|
||||||
}
|
|
||||||
return tranches
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getModelValue(model) {
|
|
||||||
if(!model) {
|
|
||||||
console.log('getModelValue', model)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return model.time
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPointsValue(points) {
|
|
||||||
return points[0].price
|
|
||||||
}
|
|
||||||
|
|
||||||
function setModelValue(model, value) {
|
|
||||||
// console.log('DCA set model value', model, value)
|
|
||||||
// const v = value === null ? null : props.builder.relative ? s.clock + Math.round(value) : Math.round(value)
|
|
||||||
const v = value === null ? null : Math.round(value)
|
|
||||||
if (model.time !== v) {
|
|
||||||
// console.log('DCA do set time', v)
|
|
||||||
model.time = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setValues(values) {
|
|
||||||
times.value = values.map((t)=>Math.round(t))
|
|
||||||
}
|
|
||||||
|
|
||||||
function setWeights(ws) { weights.value = ws }
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -247,5 +303,11 @@ td.weight {
|
|||||||
padding-left: 1em;
|
padding-left: 1em;
|
||||||
padding-right: 1em;
|
padding-right: 1em;
|
||||||
}
|
}
|
||||||
|
.parts {
|
||||||
|
width: 10em;
|
||||||
|
}
|
||||||
|
.interval {
|
||||||
|
width: 22em;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
255
src/components/chart/DateBuilder.vue
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
<template class="d-flex align-content-center flex-column" style="height: 100%; width: 100%;">
|
||||||
|
<rung-builder name='Dates' :order="order" :builder="builder" v-model="timeEndpoints"
|
||||||
|
:shape="VLine"
|
||||||
|
:mode="1" :flip="flipped" :orientation="0"
|
||||||
|
:get-model-value="getModelValue" :set-model-value="setModelValue"
|
||||||
|
:get-points-value="getPointsValue"
|
||||||
|
:set-values="setValues" :set-weights="setWeights"
|
||||||
|
:std-width="stdWidth" :build-tranches="buildTranches">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<v-list style="background-color: inherit">
|
||||||
|
<v-list-item v-for="t in absoluteTimes">{{t}}</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<absolute-time-entry v-model="absTimeA"/>
|
||||||
|
</td>
|
||||||
|
<td class="weight">{{ weights.length ? allocationTexts[0] : '' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="weights.length>2" v-for="i in weights.length-2" class="ml-5"> <!-- vue uses 1-based loops -->
|
||||||
|
<td class="d-flex justify-end pr-3">{{ dateStrings[i] }}</td>
|
||||||
|
<td class="weight">{{ allocationTexts[i] }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="weights.length>1">
|
||||||
|
<td>
|
||||||
|
<absolute-time-entry v-model="absTimeB"/>
|
||||||
|
</td>
|
||||||
|
<td class="weight">{{ allocationTexts[weights.length-1] }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
</rung-builder>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {builderDefaults, useChartOrderStore} from "@/orderbuild.js";
|
||||||
|
import {allocationText, VLine} from "@/charts/shape.js";
|
||||||
|
import {sideColor} from "@/misc.js";
|
||||||
|
import {useOrderStore, usePrefStore, useStore} from "@/store/store.js";
|
||||||
|
import {DEFAULT_SLIPPAGE, DISTANT_FUTURE, MAX_FRACTION, MIN_EXECUTION_TIME, newTranche} from "@/blockchain/orderlib.js";
|
||||||
|
import RungBuilder from "@/components/chart/RungBuilder.vue";
|
||||||
|
import {computed, ref, watchEffect} from "vue";
|
||||||
|
import {chart, dragging} from "@/charts/chart.js";
|
||||||
|
import AbsoluteTimeEntry from "@/components/AbsoluteTimeEntry.vue";
|
||||||
|
import {DateTime} from "luxon";
|
||||||
|
|
||||||
|
const s = useStore()
|
||||||
|
const os = useOrderStore()
|
||||||
|
const co = useChartOrderStore()
|
||||||
|
const prefs = usePrefStore()
|
||||||
|
const props = defineProps(['order', 'builder'])
|
||||||
|
const emit = defineEmits(['update:builder'])
|
||||||
|
|
||||||
|
const minWidth = computed(()=>co.intervalSecs)
|
||||||
|
const stdWidth = computed(()=>10 * minWidth.value)
|
||||||
|
|
||||||
|
function computeDefaultColor() {
|
||||||
|
const index = props.order.builders.indexOf(props.builder)
|
||||||
|
return sideColor(props.order.buy, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultColor = computeDefaultColor()
|
||||||
|
|
||||||
|
builderDefaults(props.builder, {
|
||||||
|
timeA: s.clock, // todo relative 0
|
||||||
|
timeB: null,
|
||||||
|
// relative: true, // todo
|
||||||
|
relative: false,
|
||||||
|
slippage: DEFAULT_SLIPPAGE,
|
||||||
|
rungs: 1,
|
||||||
|
balance: 0,
|
||||||
|
color: defaultColor,
|
||||||
|
buy: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const times = ref([])
|
||||||
|
const weights = ref([])
|
||||||
|
|
||||||
|
const amountSymbol = computed(()=>props.order.amountIsTokenA ? co.selectedSymbol.base.s : co.selectedSymbol.quote.s )
|
||||||
|
const allocationTexts = computed(()=>weights.value.map((w)=>allocationText(props.order.buy, w, w * props.order.amount, co.selectedSymbol.base.s, amountSymbol.value)))
|
||||||
|
|
||||||
|
const endTimes = computed(()=>{
|
||||||
|
const ts = times.value
|
||||||
|
const window = Math.max(MIN_EXECUTION_TIME, Math.floor((ts[ts.length-1]-ts[0])/props.builder.rungs))
|
||||||
|
return ts.map((t)=>t+window)
|
||||||
|
})
|
||||||
|
const absoluteTimes = computed(()=>{
|
||||||
|
// console.log('absoluteTimes', props.builder.relative, times.value)
|
||||||
|
// if (!props.builder.relative)
|
||||||
|
return times.value
|
||||||
|
// const now = s.clock
|
||||||
|
// return times.value.map((t)=>now+t)
|
||||||
|
})
|
||||||
|
|
||||||
|
const dateStrings = computed(()=>absoluteTimes.value.map((t)=>{
|
||||||
|
const n = DateTime.fromSeconds(t).setZone(prefs.timezone)
|
||||||
|
const y = n.toLocaleString({year:'numeric'})
|
||||||
|
const m = n.toLocaleString({month:'long'})
|
||||||
|
const d = n.toLocaleString({day:'numeric'})
|
||||||
|
const h = n.toLocaleString({hour:'numeric', minute:'numeric'})
|
||||||
|
return `${y} ${m} ${d} ${h}`
|
||||||
|
}))
|
||||||
|
|
||||||
|
watchEffect(()=>{
|
||||||
|
// auto scroll
|
||||||
|
if (!dragging && absoluteTimes.value.length) {
|
||||||
|
const endTime = absoluteTimes.value[absoluteTimes.value.length-1]
|
||||||
|
const range = chart.getVisibleRange()
|
||||||
|
const width = range.to - range.from
|
||||||
|
const now = s.clock
|
||||||
|
const extra = (Math.max(0,endTime - now) + stdWidth.value/2) / width
|
||||||
|
// console.log('visrange', range, width, absV)
|
||||||
|
if (range.to < endTime) {
|
||||||
|
// console.log('scrolling')
|
||||||
|
chart.setVisibleRange({from: now - width, to: now}, {
|
||||||
|
percentRightMargin: Math.round(100 * extra),
|
||||||
|
applyDefaultRightMargin: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const absTimeA = computed({
|
||||||
|
get() { return _timeEndpoints.value[0] },
|
||||||
|
set(v) {
|
||||||
|
if (v!==null)
|
||||||
|
v = Number(v)
|
||||||
|
updateA(v)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const absTimeB = computed({
|
||||||
|
get() { return _timeEndpoints.value[1] },
|
||||||
|
set(v) {
|
||||||
|
if (v!==null)
|
||||||
|
v = Number(v)
|
||||||
|
updateB(v)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const _timeEndpoints = ref([props.builder.timeA, props.builder.timeB])
|
||||||
|
const timeEndpoints = computed({
|
||||||
|
get() { return _timeEndpoints.value},
|
||||||
|
set(v) {
|
||||||
|
const [a, b] = v
|
||||||
|
update(a,b, true, true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
function updateA(a) {
|
||||||
|
update(a, _timeEndpoints.value[1], true, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function updateB(b) {
|
||||||
|
update(_timeEndpoints.value[0], b, false, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function update(a, b, updateA, updateB) {
|
||||||
|
if (updateA) {
|
||||||
|
const minB = a + minWidth.value
|
||||||
|
if (b < minB)
|
||||||
|
b = minB
|
||||||
|
}
|
||||||
|
if (updateB) {
|
||||||
|
const maxA = b - minWidth.value
|
||||||
|
if (a > maxA)
|
||||||
|
a = maxA
|
||||||
|
}
|
||||||
|
_timeEndpoints.value = [a, b]
|
||||||
|
props.builder.timeA = a
|
||||||
|
props.builder.timeB = b
|
||||||
|
}
|
||||||
|
|
||||||
|
const flipped = computed(()=>{
|
||||||
|
const a = props.builder.timeA
|
||||||
|
const b = props.builder.timeB
|
||||||
|
return a !== null && b !== null && a > b
|
||||||
|
})
|
||||||
|
|
||||||
|
function buildTranches() {
|
||||||
|
const order = props.order
|
||||||
|
const builder = props.builder
|
||||||
|
const tranches = []
|
||||||
|
const warnings = []
|
||||||
|
|
||||||
|
console.log('buildTranches', builder, order, tranches)
|
||||||
|
const ts = times.value
|
||||||
|
const ets = endTimes.value
|
||||||
|
const ws = weights.value
|
||||||
|
console.log('buildTranches times ends weights', ts, ets, ws)
|
||||||
|
for(let i=0; i<ts.length; i++) {
|
||||||
|
const endTime = Math.max(ets[i],ts[i]+60);
|
||||||
|
console.log('time check', endTime, s.clock)
|
||||||
|
if (endTime <= s.clock)
|
||||||
|
warnings.push(`Tranche already expired at ${new Date(endTime*1000)}`)
|
||||||
|
const t = newTranche({
|
||||||
|
marketOrder: true,
|
||||||
|
slippage: builder.slippage,
|
||||||
|
fraction: ws[i] * MAX_FRACTION,
|
||||||
|
startTime: ts[i],
|
||||||
|
endTime, // always give at least 60 seconds of window to execute
|
||||||
|
})
|
||||||
|
tranches.push(t)
|
||||||
|
}
|
||||||
|
return {tranches, warnings}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getModelValue(model) {
|
||||||
|
if(!model) {
|
||||||
|
console.log('getModelValue', model)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return model.time
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPointsValue(points) {
|
||||||
|
return points[0].price
|
||||||
|
}
|
||||||
|
|
||||||
|
function setModelValue(model, value) {
|
||||||
|
// console.log('DCA set model value', model, value)
|
||||||
|
// const v = value === null ? null : props.builder.relative ? s.clock + Math.round(value) : Math.round(value)
|
||||||
|
const v = value === null ? null : Math.round(value)
|
||||||
|
if (model.time !== v) {
|
||||||
|
// console.log('DCA do set time', v)
|
||||||
|
model.time = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setValues(values) {
|
||||||
|
times.value = values.map((t)=>Math.round(t))
|
||||||
|
}
|
||||||
|
|
||||||
|
function setWeights(ws) { weights.value = ws }
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
td.weight {
|
||||||
|
padding-left: 1em;
|
||||||
|
padding-right: 1em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
:set-values="setLines" :set-weights="setWeights"
|
:set-values="setLines" :set-weights="setWeights"
|
||||||
:set-shapes="setShapes"
|
:set-shapes="setShapes"
|
||||||
:std-width="stdWidth" :build-tranches="buildTranches">
|
:std-width="stdWidth" :build-tranches="buildTranches">
|
||||||
<table>
|
<table v-if="!co.drawing">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td> </td>
|
<td> </td>
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
<v-text-field type="number" v-model="price1A" min="0"
|
<v-text-field type="number" v-model="price1A" min="0"
|
||||||
density="compact" hide-details variant="outlined"
|
density="compact" hide-details variant="outlined"
|
||||||
class="mx-1 my-2 price"
|
class="mx-1 my-2 price"
|
||||||
:color="color" :base-color="color"
|
:color="color"
|
||||||
label="Price"
|
label="Price"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
<v-text-field type="number" v-model="price1B" min="0"
|
<v-text-field type="number" v-model="price1B" min="0"
|
||||||
density="compact" hide-details variant="outlined"
|
density="compact" hide-details variant="outlined"
|
||||||
class="mx-1 my-2 price"
|
class="mx-1 my-2 price"
|
||||||
:color="color" :base-color="color"
|
:color="color"
|
||||||
label="Price"
|
label="Price"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
<v-text-field type="number" v-model="price2A" min="0"
|
<v-text-field type="number" v-model="price2A" min="0"
|
||||||
density="compact" hide-details variant="outlined"
|
density="compact" hide-details variant="outlined"
|
||||||
class="mx-1 my-2 price"
|
class="mx-1 my-2 price"
|
||||||
:color="color" :base-color="color"
|
:color="color"
|
||||||
label="Price"
|
label="Price"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
@@ -82,26 +82,34 @@
|
|||||||
<v-text-field type="number" v-model="price2B" min="0"
|
<v-text-field type="number" v-model="price2B" min="0"
|
||||||
density="compact" hide-details variant="outlined"
|
density="compact" hide-details variant="outlined"
|
||||||
class="mx-1 my-2 price"
|
class="mx-1 my-2 price"
|
||||||
:color="color" :base-color="color"
|
:color="color"
|
||||||
label="Price"
|
label="Price"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<one-time-hint name="click-chart" activator="#tv-widget" location="center"
|
||||||
|
:when="builder.lineA===null && !co.drew" text="Click the chart!"
|
||||||
|
:on-complete="()=>track('click-chart')"
|
||||||
|
/>
|
||||||
</rung-builder>
|
</rung-builder>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {applyLinePoints, builderDefaults, useChartOrderStore} from "@/orderbuild.js";
|
import {applyLinePoints, builderDefaults, useChartOrderStore} from "@/orderbuild.js";
|
||||||
import {sideColor} from "@/misc.js";
|
import {sideColor, toPrecisionOrNull} from "@/misc.js";
|
||||||
import {MAX_FRACTION, newTranche} from "@/blockchain/orderlib.js";
|
import {MAX_FRACTION, newTranche} from "@/blockchain/orderlib.js";
|
||||||
import RungBuilder from "@/components/chart/RungBuilder.vue";
|
import RungBuilder from "@/components/chart/RungBuilder.vue";
|
||||||
import {computed, ref} from "vue";
|
import {computed, ref} from "vue";
|
||||||
import {allocationText, DLine} from "@/charts/shape.js";
|
import {allocationText, DLine} from "@/charts/shape.js";
|
||||||
import {vectorEquals, vectorInterpolate} from "@/vector.js";
|
import {vectorEquals, vectorInterpolate} from "@/vector.js";
|
||||||
import AbsoluteTimeEntry from "@/components/AbsoluteTimeEntry.vue";
|
import AbsoluteTimeEntry from "@/components/AbsoluteTimeEntry.vue";
|
||||||
|
import {useStore} from "@/store/store.js";
|
||||||
|
import OneTimeHint from "@/components/OneTimeHint.vue";
|
||||||
|
import {track} from "@/track.js";
|
||||||
|
|
||||||
|
const s = useStore()
|
||||||
const co = useChartOrderStore()
|
const co = useChartOrderStore()
|
||||||
const props = defineProps(['order', 'builder'])
|
const props = defineProps(['order', 'builder'])
|
||||||
const emit = defineEmits(['update:builder'])
|
const emit = defineEmits(['update:builder'])
|
||||||
@@ -121,7 +129,7 @@ builderDefaults(props.builder, {
|
|||||||
extendLeft: false,
|
extendLeft: false,
|
||||||
extendRight: true,
|
extendRight: true,
|
||||||
rungs: 1,
|
rungs: 1,
|
||||||
skew: 0,
|
balance: 0,
|
||||||
breakout: false,
|
breakout: false,
|
||||||
color: defaultColor,
|
color: defaultColor,
|
||||||
buy: true,
|
buy: true,
|
||||||
@@ -132,6 +140,7 @@ function buildTranches() {
|
|||||||
const order = props.order
|
const order = props.order
|
||||||
const builder = props.builder
|
const builder = props.builder
|
||||||
const tranches = []
|
const tranches = []
|
||||||
|
const warnings = []
|
||||||
|
|
||||||
console.log('buildTranches', builder, order, _endpoints.value)
|
console.log('buildTranches', builder, order, _endpoints.value)
|
||||||
const la = _endpoints.value[0] // use the flatline format which is a vector of length 4, useful for vectorInterpolate()
|
const la = _endpoints.value[0] // use the flatline format which is a vector of length 4, useful for vectorInterpolate()
|
||||||
@@ -149,6 +158,9 @@ function buildTranches() {
|
|||||||
t.startTime = reversed ? line[2] : line[0]
|
t.startTime = reversed ? line[2] : line[0]
|
||||||
if (reversed ? !el : !er)
|
if (reversed ? !el : !er)
|
||||||
t.endTime = reversed ? line[0] : line[2]
|
t.endTime = reversed ? line[0] : line[2]
|
||||||
|
if (t.endTime <= s.clock)
|
||||||
|
warnings.push(`Tranche already expired at ${new Date(t.endTime*1000)}`)
|
||||||
|
|
||||||
// console.log('tranche start/end',
|
// console.log('tranche start/end',
|
||||||
// t.startTime === DISTANT_PAST ? 'PAST' : t.startTime,
|
// t.startTime === DISTANT_PAST ? 'PAST' : t.startTime,
|
||||||
// t.endTime === DISTANT_FUTURE ? 'FUTURE' : t.endTime)
|
// t.endTime === DISTANT_FUTURE ? 'FUTURE' : t.endTime)
|
||||||
@@ -157,7 +169,7 @@ function buildTranches() {
|
|||||||
}
|
}
|
||||||
// if( flipped.value )
|
// if( flipped.value )
|
||||||
// tranches.reverse()
|
// tranches.reverse()
|
||||||
return tranches
|
return {tranches, warnings}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -198,7 +210,7 @@ const time1A = computed({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const price1A = computed({
|
const price1A = computed({
|
||||||
get() { return _endpoints.value[0] === null ? null : _endpoints.value[0][1] },
|
get() { return toPrecisionOrNull(_endpoints.value[0] === null ? null : _endpoints.value[0][1], 6) },
|
||||||
set(v) {
|
set(v) {
|
||||||
const flatline0 = _endpoints.value[0];
|
const flatline0 = _endpoints.value[0];
|
||||||
update(
|
update(
|
||||||
@@ -220,7 +232,7 @@ const time1B = computed({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const price1B = computed({
|
const price1B = computed({
|
||||||
get() { return _endpoints.value[0] === null ? null : _endpoints.value[0][3] },
|
get() { return toPrecisionOrNull(_endpoints.value[0] === null ? null : _endpoints.value[0][3], 6) },
|
||||||
set(v) {
|
set(v) {
|
||||||
const flatline0 = _endpoints.value[0];
|
const flatline0 = _endpoints.value[0];
|
||||||
update(
|
update(
|
||||||
@@ -242,7 +254,7 @@ const time2A = computed({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const price2A = computed({
|
const price2A = computed({
|
||||||
get() { return _endpoints.value[1] === null ? null : _endpoints.value[1][1] },
|
get() { return toPrecisionOrNull(_endpoints.value[1] === null ? null : _endpoints.value[1][1], 6) },
|
||||||
set(v) {
|
set(v) {
|
||||||
const flatline = _endpoints.value[1];
|
const flatline = _endpoints.value[1];
|
||||||
update(
|
update(
|
||||||
@@ -264,7 +276,7 @@ const time2B = computed({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const price2B = computed({
|
const price2B = computed({
|
||||||
get() { return _endpoints.value[1] === null ? null : _endpoints.value[1][3] },
|
get() { return toPrecisionOrNull(_endpoints.value[1] === null ? null : _endpoints.value[1][3], 6) },
|
||||||
set(v) {
|
set(v) {
|
||||||
const flatline = _endpoints.value[1];
|
const flatline = _endpoints.value[1];
|
||||||
update(
|
update(
|
||||||
@@ -277,10 +289,8 @@ const price2B = computed({
|
|||||||
function update(a, b) { // a and b are lines of two points
|
function update(a, b) { // a and b are lines of two points
|
||||||
if (!vectorEquals(props.builder.lineA, a) || !vectorEquals(props.builder.lineB, b)) {
|
if (!vectorEquals(props.builder.lineA, a) || !vectorEquals(props.builder.lineB, b)) {
|
||||||
_endpoints.value = [flattenLine(a), flattenLine(b)]
|
_endpoints.value = [flattenLine(a), flattenLine(b)]
|
||||||
const newBuilder = {...props.builder}
|
props.builder.lineA = a
|
||||||
newBuilder.lineA = a
|
props.builder.lineB = b
|
||||||
newBuilder.lineB = b
|
|
||||||
emit('update:builder', newBuilder)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,7 +359,7 @@ function setWeights(ws) {
|
|||||||
|
|
||||||
const amountSymbol = computed(()=>props.order.amountIsTokenA ? co.selectedSymbol.base.s : co.selectedSymbol.quote.s )
|
const amountSymbol = computed(()=>props.order.amountIsTokenA ? co.selectedSymbol.base.s : co.selectedSymbol.quote.s )
|
||||||
|
|
||||||
const allocationTexts = computed(()=>weights.value.map((w)=>allocationText(props.order.buy, w, w*props.order.amount, amountSymbol.value)))
|
const allocationTexts = computed(()=>weights.value.map((w)=>allocationText(props.order.buy, w, w * props.order.amount, co.selectedSymbol.base.s, amountSymbol.value)))
|
||||||
|
|
||||||
const stdWidth = computed(()=>[0, co.meanRange, 0, co.meanRange])
|
const stdWidth = computed(()=>[0, co.meanRange, 0, co.meanRange])
|
||||||
|
|
||||||
@@ -399,12 +409,13 @@ function dirtyLine(a, b) {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
const name = computed(()=>props.builder.breakout?'Breakout':'Limit')
|
const name = computed(()=>props.builder.breakout?(props.order.buy?'Breakout':'Breakdown'):'Limit')
|
||||||
|
|
||||||
const description = computed(()=>{
|
const description = computed(()=>{
|
||||||
const buy = props.order.buy
|
const buy = props.order.buy
|
||||||
const above = buy === props.builder.breakout
|
const above = buy === props.builder.breakout
|
||||||
return (buy?'Buy ':'Sell ')+(above?'above':'below')+' the line'
|
const plural = props.builder.rungs > 1 ? 's' : ''
|
||||||
|
return (buy?'Buy ':'Sell ')+(above?'above':'below')+' the line'+(plural?'s':'')
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
11
src/components/chart/GasMeter.vue
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<template>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<rung-builder :name="(builder.breakout?'Breakout':'Limit')"
|
<rung-builder :name="(builder.breakout?(order.buy?'Breakout':'Breakdown'):'Limit')+(builder.rungs>1?' Ladder':'')"
|
||||||
:description="description"
|
:description="description"
|
||||||
:order="order" :builder="builder"
|
:order="order" :builder="builder"
|
||||||
v-model="priceEndpoints" :mode="0" :flip="flipped"
|
v-model="priceEndpoints" :mode="0" :flip="flipped"
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
:get-model-value="getModelValue" :set-model-value="setModelValue"
|
:get-model-value="getModelValue" :set-model-value="setModelValue"
|
||||||
:set-values="setPrices" :set-weights="setWeights"
|
:set-values="setPrices" :set-weights="setWeights"
|
||||||
:std-width="stdWidth" :build-tranches="buildTranches">
|
:std-width="stdWidth" :build-tranches="buildTranches">
|
||||||
<table>
|
<table class="rb">
|
||||||
<tbody>
|
<tbody>
|
||||||
<template v-if="prices.length>1">
|
<template v-if="prices.length>1">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -15,14 +15,13 @@
|
|||||||
<v-text-field type="number" v-model="higherPrice" min="0"
|
<v-text-field type="number" v-model="higherPrice" min="0"
|
||||||
density="compact" hide-details class="mx-1 my-2" variant="outlined"
|
density="compact" hide-details class="mx-1 my-2" variant="outlined"
|
||||||
label="Price"
|
label="Price"
|
||||||
:color="color" :base-color="color"
|
|
||||||
style="flex: 6em"
|
style="flex: 6em"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td class="weight">{{ allocationTexts[higherIndex] }}</td>
|
<td class="weight" style="vertical-align: bottom">{{ allocationTexts[higherIndex] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-for="i in innerIndexes" class="ml-5">
|
<tr v-for="i in innerIndexes" class="ml-5">
|
||||||
<td class="pl-5">{{ prices[i] }}</td>
|
<td class="pl-5">{{ toPrecision(prices[i],6) }}</td>
|
||||||
<td class="weight">{{ allocationTexts[i] }}</td>
|
<td class="weight">{{ allocationTexts[i] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
@@ -31,14 +30,17 @@
|
|||||||
<v-text-field type="number" v-model="lowerPrice" min="0"
|
<v-text-field type="number" v-model="lowerPrice" min="0"
|
||||||
density="compact" hide-details class="mx-1 my-2" variant="outlined"
|
density="compact" hide-details class="mx-1 my-2" variant="outlined"
|
||||||
label="Price"
|
label="Price"
|
||||||
:color="color" :base-color="color"
|
|
||||||
style="flex: 6em"
|
style="flex: 6em"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td class="weight">{{ weights.length ? allocationTexts[lowerIndex] : '' }}</td>
|
<td class="weight">{{ weights.length > 1 ? allocationTexts[lowerIndex] : '' }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<one-time-hint name="click-chart" activator="#tv-widget" location="center"
|
||||||
|
:when="priceA===null" text="Click the chart!"
|
||||||
|
:on-complete="()=>track('click-chart')"
|
||||||
|
/>
|
||||||
</rung-builder>
|
</rung-builder>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -50,6 +52,9 @@ import {MAX_FRACTION, newTranche} from "@/blockchain/orderlib.js";
|
|||||||
import RungBuilder from "@/components/chart/RungBuilder.vue";
|
import RungBuilder from "@/components/chart/RungBuilder.vue";
|
||||||
import {computed, ref} from "vue";
|
import {computed, ref} from "vue";
|
||||||
import {allocationText, HLine} from "@/charts/shape.js";
|
import {allocationText, HLine} from "@/charts/shape.js";
|
||||||
|
import OneTimeHint from "@/components/OneTimeHint.vue";
|
||||||
|
import {track} from "@/track.js";
|
||||||
|
import {toPrecision, toPrecisionOrNull} from "@/misc.js";
|
||||||
|
|
||||||
const s = useStore()
|
const s = useStore()
|
||||||
const os = useOrderStore()
|
const os = useOrderStore()
|
||||||
@@ -71,7 +76,7 @@ builderDefaults(props.builder, {
|
|||||||
priceA: null,
|
priceA: null,
|
||||||
priceB: null,
|
priceB: null,
|
||||||
rungs: 1,
|
rungs: 1,
|
||||||
skew: 0,
|
balance: 0,
|
||||||
breakout: false,
|
breakout: false,
|
||||||
color: defaultColor,
|
color: defaultColor,
|
||||||
buy: true,
|
buy: true,
|
||||||
@@ -81,6 +86,7 @@ function buildTranches() {
|
|||||||
const order = props.order
|
const order = props.order
|
||||||
const builder = props.builder
|
const builder = props.builder
|
||||||
const tranches = []
|
const tranches = []
|
||||||
|
const warnings = []
|
||||||
|
|
||||||
console.log('buildTranches', builder, order, tranches)
|
console.log('buildTranches', builder, order, tranches)
|
||||||
const ps = prices.value
|
const ps = prices.value
|
||||||
@@ -89,17 +95,16 @@ function buildTranches() {
|
|||||||
let p = ps[i]
|
let p = ps[i]
|
||||||
const w = ws[i]
|
const w = ws[i]
|
||||||
const t = newTranche({
|
const t = newTranche({
|
||||||
// todo start/end
|
|
||||||
fraction: w * MAX_FRACTION,
|
fraction: w * MAX_FRACTION,
|
||||||
})
|
})
|
||||||
const symbol = co.selectedSymbol
|
const symbol = co.selectedSymbol
|
||||||
console.log('symbol', symbol, p)
|
// console.log('symbol', symbol, p)
|
||||||
applyLinePoint(t, symbol, order.buy, p, builder.breakout)
|
applyLinePoint(t, symbol, order.buy, p, builder.breakout)
|
||||||
tranches.push(t)
|
tranches.push(t)
|
||||||
}
|
}
|
||||||
if (!flipped.value)
|
if (!flipped.value)
|
||||||
tranches.reverse()
|
tranches.reverse()
|
||||||
return tranches
|
return {tranches, warnings}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -134,10 +139,8 @@ const priceEndpoints = computed({
|
|||||||
|
|
||||||
function update(a, b) {
|
function update(a, b) {
|
||||||
_priceEndpoints.value = [a, b]
|
_priceEndpoints.value = [a, b]
|
||||||
const newBuilder = {...props.builder}
|
props.builder.priceA = a
|
||||||
newBuilder.priceA = a
|
props.builder.priceB = b
|
||||||
newBuilder.priceB = b
|
|
||||||
emit('update:builder', newBuilder)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const flipped = computed(()=>{
|
const flipped = computed(()=>{
|
||||||
@@ -147,7 +150,7 @@ const flipped = computed(()=>{
|
|||||||
})
|
})
|
||||||
|
|
||||||
const higherPrice = computed({
|
const higherPrice = computed({
|
||||||
get() { return flipped.value ? priceA.value : priceB.value },
|
get() { return toPrecisionOrNull(flipped.value ? priceA.value : priceB.value, 6) },
|
||||||
set(v) {
|
set(v) {
|
||||||
if (flipped.value)
|
if (flipped.value)
|
||||||
priceA.value = v
|
priceA.value = v
|
||||||
@@ -168,9 +171,7 @@ const innerIndexes = computed(()=>{
|
|||||||
})
|
})
|
||||||
|
|
||||||
const lowerPrice = computed({
|
const lowerPrice = computed({
|
||||||
get() {
|
get() {return toPrecisionOrNull(!flipped.value ? priceA.value : priceB.value, 6)},
|
||||||
return !flipped.value ? priceA.value : priceB.value
|
|
||||||
},
|
|
||||||
set(v) {
|
set(v) {
|
||||||
if (!flipped.value)
|
if (!flipped.value)
|
||||||
priceA.value = v
|
priceA.value = v
|
||||||
@@ -196,13 +197,14 @@ function setPrices(ps) {prices.value = ps}
|
|||||||
function setWeights(ws) { weights.value = ws }
|
function setWeights(ws) { weights.value = ws }
|
||||||
|
|
||||||
const amountSymbol = computed(()=>props.order.amountIsTokenA ? co.selectedSymbol.base.s : co.selectedSymbol.quote.s )
|
const amountSymbol = computed(()=>props.order.amountIsTokenA ? co.selectedSymbol.base.s : co.selectedSymbol.quote.s )
|
||||||
const allocationTexts = computed(()=>weights.value.map((w)=>allocationText(props.order.buy, w, w*props.order.amount, amountSymbol.value)))
|
const allocationTexts = computed(()=>weights.value.map((w)=>allocationText(props.order.buy, w, w * props.order.amount, co.selectedSymbol.base.s, amountSymbol.value)))
|
||||||
const color = computed(()=>props.builder.color ? props.builder.color : defaultColor)
|
const color = computed(()=>props.builder.color ? props.builder.color : defaultColor)
|
||||||
const stdWidth = computed(()=>co.meanRange)
|
const stdWidth = computed(()=>co.meanRange)
|
||||||
const description = computed(()=>{
|
const description = computed(()=>{
|
||||||
const buy = props.order.buy
|
const buy = props.order.buy
|
||||||
const above = buy === props.builder.breakout
|
const above = buy === props.builder.breakout
|
||||||
return (buy?'Buy ':'Sell ')+(above?'above':'below')+' the line'
|
const plural = props.builder.rungs > 1 ? 's' : ''
|
||||||
|
return (buy?'Buy ':'Sell ')+(above?'above':'below')+' the line'+plural
|
||||||
})
|
})
|
||||||
|
|
||||||
function getModelValue(model) {
|
function getModelValue(model) {
|
||||||
@@ -228,5 +230,16 @@ td.weight {
|
|||||||
padding-right: 0.5em;
|
padding-right: 0.5em;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
table.rb {
|
||||||
|
padding: 0;
|
||||||
|
border-spacing: 0;
|
||||||
|
tbody {
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -18,9 +18,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {builderDefaults, builderFuncs, DEFAULT_SLIPPAGE, useChartOrderStore} from "@/orderbuild.js";
|
import {builderDefaults, builderFuncs, useChartOrderStore} from "@/orderbuild.js";
|
||||||
import {computed, onMounted, onUnmounted} from "vue";
|
import {computed, onMounted, onUnmounted} from "vue";
|
||||||
import {newTranche} from "@/blockchain/orderlib.js";
|
import {DEFAULT_SLIPPAGE, newTranche} from "@/blockchain/orderlib.js";
|
||||||
|
|
||||||
const co = useChartOrderStore()
|
const co = useChartOrderStore()
|
||||||
const props = defineProps(['order', 'builder'])
|
const props = defineProps(['order', 'builder'])
|
||||||
@@ -36,8 +36,17 @@ const slippage = computed({
|
|||||||
set(v) {props.builder.slippage=v/100; emit('update:builder', props.builder)}
|
set(v) {props.builder.slippage=v/100; emit('update:builder', props.builder)}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const MIN_SLIPPAGE = 0.01;
|
||||||
|
|
||||||
function buildTranches() {
|
function buildTranches() {
|
||||||
return [newTranche({slippage: slippage.value/100})]
|
let warnings = []
|
||||||
|
if (slippage.value < MIN_SLIPPAGE)
|
||||||
|
warnings.push('Slippage will be set to the minimum of 0.01%')
|
||||||
|
const slip = Math.max(slippage.value, MIN_SLIPPAGE)
|
||||||
|
return {
|
||||||
|
tranches: [newTranche({marketOrder: true, slippage: slip / 100})],
|
||||||
|
warnings,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => builderFuncs[props.builder.id] = buildTranches)
|
onMounted(() => builderFuncs[props.builder.id] = buildTranches)
|
||||||
|
|||||||
135
src/components/chart/OrderAmount.vue
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
<template>
|
||||||
|
<div class="d-flex flex-row align-start">
|
||||||
|
<!--
|
||||||
|
<v-btn v-if="!multiplier"
|
||||||
|
variant="outlined" :color="color"
|
||||||
|
:text="(props.order.buy ? 'Buy ' : 'Sell ') + co.selectedSymbol.base.s"
|
||||||
|
@click="props.order.buy=!props.order.buy"
|
||||||
|
class="ml-3 mt-2 mr-2"
|
||||||
|
size="2.5em"
|
||||||
|
style="width: 9em"
|
||||||
|
/>
|
||||||
|
-->
|
||||||
|
<div class="d-flex flex-column align-center">
|
||||||
|
<v-switch v-if="!multiplier" v-model="switchModel" :color="sideColor" :base-color="sideColor" density="compact"
|
||||||
|
class="my-0 mx-3 clickable">
|
||||||
|
<template #prepend>
|
||||||
|
<span :style="order.buy?{color:theme.colors.success}:{}" @click="order.buy=true" class="bs-button buy">Buy<br/>{{co.selectedSymbol.base.s}}</span>
|
||||||
|
</template>
|
||||||
|
<template #append>
|
||||||
|
<span :style="!order.buy?{color:theme.colors.error}:{}" @click="order.buy=false" class="bs-button sell">Sell<br/>{{co.selectedSymbol.base.s}}</span>
|
||||||
|
</template>
|
||||||
|
</v-switch>
|
||||||
|
</div>
|
||||||
|
<v-text-field type="number" inputmode="numeric" pattern="[0-9]*\.?[0-9]*" v-model="amount" variant="outlined"
|
||||||
|
:hint="available" :persistent-hint="true"
|
||||||
|
min="0"
|
||||||
|
class="amount mx-3"
|
||||||
|
style="max-width: 20em"
|
||||||
|
:color="color"
|
||||||
|
:label="order.amountIsTokenA ?
|
||||||
|
(multiplier ? 'Amount each' : 'Amount') :
|
||||||
|
((multiplier ? 'Value each in ' : 'Value in ')+co.selectedSymbol.quote.s)">
|
||||||
|
<template #prepend-inner>
|
||||||
|
<v-btn variant="text" text="max" class="px-0" size="small"
|
||||||
|
:disabled="!maxAmount || order.amountIsTokenA===order.buy && !co.price" @click="setMax"/>
|
||||||
|
</template>
|
||||||
|
<template #append-inner>
|
||||||
|
<v-btn :text="order.amountIsTokenA?co.baseToken.s:co.quoteToken.s+' worth'"
|
||||||
|
variant="text" @click="toggleAmountToken" style="width: 7em"/>
|
||||||
|
</template>
|
||||||
|
</v-text-field>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {useChartOrderStore} from "@/orderbuild.js";
|
||||||
|
import {computed, ref} from "vue";
|
||||||
|
import {useStore} from "@/store/store.js";
|
||||||
|
import Color from "color";
|
||||||
|
import {useTheme} from "vuetify";
|
||||||
|
|
||||||
|
const props = defineProps(['order', 'multiplier', 'color'])
|
||||||
|
|
||||||
|
const s = useStore()
|
||||||
|
const co = useChartOrderStore()
|
||||||
|
|
||||||
|
const theme = useTheme().current
|
||||||
|
// console.log('theme', theme.value)
|
||||||
|
const sideColor = computed(()=>new Color(props.order.buy?theme.value.colors.success:theme.value.colors.error).darken(0.2).string())
|
||||||
|
const switchModel = computed({
|
||||||
|
get() {return !props.order.buy},
|
||||||
|
set(v) {props.order.buy=!v}
|
||||||
|
})
|
||||||
|
|
||||||
|
const amount = computed({
|
||||||
|
get() {
|
||||||
|
let result = props.order.amount
|
||||||
|
if (result !== null && props.multiplier)
|
||||||
|
result /= props.multiplier
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
set(v) {
|
||||||
|
if (v !== null && props.multiplier)
|
||||||
|
v *= props.multiplier
|
||||||
|
props.order.amount = v
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const available = computed(()=>{
|
||||||
|
const max = maxAmount.value
|
||||||
|
return max === null ? '' : `Available: ${maxAmount.value} ${inToken.value.s}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const inToken = computed( ()=>props.order.buy ? co.quoteToken : co.baseToken )
|
||||||
|
|
||||||
|
const maxAmount = computed(()=>{
|
||||||
|
const token = inToken.value;
|
||||||
|
if (!token)
|
||||||
|
return null
|
||||||
|
const balance = s.balances[token.a]
|
||||||
|
if( !balance )
|
||||||
|
return null
|
||||||
|
return balance / 10**token.d
|
||||||
|
})
|
||||||
|
|
||||||
|
const lastMaxValue = ref(-1)
|
||||||
|
|
||||||
|
function setMax() {
|
||||||
|
let amount = maxAmount.value
|
||||||
|
if (amount) {
|
||||||
|
const order = props.order
|
||||||
|
const price = co.price
|
||||||
|
if (order.amountIsTokenA===order.buy) {
|
||||||
|
if (order.buy)
|
||||||
|
amount /= price
|
||||||
|
else
|
||||||
|
amount *= price
|
||||||
|
}
|
||||||
|
amount = Number(amount.toPrecision(7))
|
||||||
|
lastMaxValue.value = amount
|
||||||
|
order.amount = amount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleAmountToken() {
|
||||||
|
const order = props.order
|
||||||
|
order.amountIsTokenA=!order.amountIsTokenA
|
||||||
|
if (order.amount === lastMaxValue.value)
|
||||||
|
setMax()
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@use "src/styles/vars" as *;
|
||||||
|
.amount {
|
||||||
|
max-width: 30em;
|
||||||
|
}
|
||||||
|
.bs-button {
|
||||||
|
text-align: center;
|
||||||
|
user-select: none;
|
||||||
|
&.buy:hover {color: $green;}
|
||||||
|
&.sell:hover {color: $red;}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,20 +1,50 @@
|
|||||||
<template>
|
<template>
|
||||||
<builder-panel :order="order" :builder="builder" :build-tranches="buildTranches"
|
<builder-panel :order="order" :builder="builder" :build-tranches="buildTranches"
|
||||||
:adjust-shapes="adjustShapes" :delete-shapes="deleteShapes">
|
:adjust-shapes="adjustShapes" :delete-shapes="deleteShapes" :name="name">
|
||||||
<div style="min-width: 4em; font-size: larger" :style="colorStyle"
|
<div style="min-width: 4em; font-size: larger" :style="colorStyle"
|
||||||
class="d-flex flex-column align-self-start ml-2">
|
class="d-flex flex-column">
|
||||||
|
<!--
|
||||||
<div class="flex-row align-items-center">
|
<div class="flex-row align-items-center">
|
||||||
<v-btn variant="outlined" style="width: 8em"
|
<v-btn variant="outlined" style="width: 8em"
|
||||||
@click="props.builder.breakout=!props.builder.breakout">{{ name }}</v-btn>
|
@click="()=>{if (props.builder.breakout!==undefined) props.builder.breakout=!props.builder.breakout}">{{ name }}</v-btn>
|
||||||
<div class="description w-100 text-center">{{description}}</div>
|
<div class="description w-100 text-center">{{description}}</div>
|
||||||
</div>
|
</div>
|
||||||
|
-->
|
||||||
|
<!--
|
||||||
|
<floating-div id="rungs" v-if="endpoints[0]">
|
||||||
|
-->
|
||||||
<v-text-field type="number" v-model="rungs"
|
<v-text-field type="number" v-model="rungs"
|
||||||
density="compact" hide-details class="mx-1 my-2" variant="outlined"
|
density="compact" hide-details class="mx-1 my-2" variant="outlined"
|
||||||
label="Rungs"
|
label="Rungs"
|
||||||
:color="color" :base-color="color" min="1" :max="MAX_RUNGS"
|
:color="color"
|
||||||
|
min="1" :max="MAX_RUNGS"
|
||||||
:disabled="rungsDisabled"
|
:disabled="rungsDisabled"
|
||||||
style="width: 6.6em;"
|
id="rungs"
|
||||||
/>
|
/>
|
||||||
|
<!--
|
||||||
|
</floating-div>
|
||||||
|
-->
|
||||||
|
<one-time-hint name="rungs" activator="#rungs" after="choose-builder"
|
||||||
|
text="↓ Try increasing rungs!" location="top"
|
||||||
|
:when="rungs===1&&endpoints[0]!==null"
|
||||||
|
:on-complete="()=>track('rungs')"
|
||||||
|
/>
|
||||||
|
<v-tooltip v-if="builder.breakout!==undefined"
|
||||||
|
:text="order.buy?'Breakout orders buy above the breakout line':'Breakdown orders sell below the breakdown line'">
|
||||||
|
<template #activator="{ props }">
|
||||||
|
<div v-bind="props">
|
||||||
|
<v-switch v-model="breakout" :label="order.buy?'Breakout':'Breakdown'"
|
||||||
|
persistent-hint :color="switchColor" :base-color="switchColor" hide-details direction="vertical"
|
||||||
|
density="compact"/>
|
||||||
|
<div class="mx-auto">
|
||||||
|
<span style="font-size: .7em; vertical-align: top"
|
||||||
|
:style="builder.breakout?{color:new Color(color).lighten(0.5).string()}:null">
|
||||||
|
{{ description }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<slot/>
|
<slot/>
|
||||||
@@ -23,16 +53,31 @@
|
|||||||
<v-icon icon="mdi-chat-alert-outline" color="grey" class="mr-1"/>
|
<v-icon icon="mdi-chat-alert-outline" color="grey" class="mr-1"/>
|
||||||
Click the chart!
|
Click the chart!
|
||||||
</div>
|
</div>
|
||||||
<div v-if="rungs>1" class="mx-2 d-flex align-center">
|
<div v-if="rungs>1" class="mx-2 d-flex justify-start">
|
||||||
<v-slider v-if="rungs>1" :direction="orientation?'vertical':'horizontal'" min="-100" max="100" v-model="skew100"
|
<div class="d-flex align-center mt-2">
|
||||||
class="no-slider-bg ml-2 mr-4" hide-details/>
|
<!--
|
||||||
<v-text-field type="number" v-model="skew100" min="-100" max="100"
|
<floating-div id="slider" v-if="endpoints[0]">
|
||||||
density="compact" hide-details variant="outlined" label="Skew" step="5"
|
-->
|
||||||
:color="color" :base-color="color" class="skew">
|
<div id="balance-slider">
|
||||||
<template v-slot:prepend>
|
<v-slider v-if="rungs>1" :direction="orientation?'vertical':'horizontal'" min="-100" max="100" v-model="balance100"
|
||||||
<v-btn icon="mdi-scale-balance" variant="plain" @click="builder.skew=0" :color="color"/>
|
class="no-slider-bg ml-2 mr-4" hide-details/>
|
||||||
</template>
|
</div>
|
||||||
</v-text-field>
|
<!--
|
||||||
|
</floating-div>
|
||||||
|
-->
|
||||||
|
<one-time-hint name="balance-slider" activator="#balance-slider" after="rungs"
|
||||||
|
text="↓ Slide the amount balance ↓" location="top"
|
||||||
|
:when="balance100===0"
|
||||||
|
:on-complete="()=>track('balance-slider')"
|
||||||
|
/>
|
||||||
|
<v-text-field type="number" v-model="balance100" min="-100" max="100"
|
||||||
|
density="compact" hide-details variant="outlined" label="Balance" step="5"
|
||||||
|
class="balance">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-btn icon="mdi-scale-balance" variant="plain" @click="builder.balance=0"/>
|
||||||
|
</template>
|
||||||
|
</v-text-field>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</builder-panel>
|
</builder-panel>
|
||||||
</template>
|
</template>
|
||||||
@@ -56,6 +101,10 @@ import {
|
|||||||
vectorNeg,
|
vectorNeg,
|
||||||
vectorSub
|
vectorSub
|
||||||
} from "@/vector.js";
|
} from "@/vector.js";
|
||||||
|
import {logicalXOR} from "@/common.js";
|
||||||
|
import OneTimeHint from "@/components/OneTimeHint.vue";
|
||||||
|
import {track} from "@/track.js";
|
||||||
|
import FloatingDiv from "@/components/FloatingDiv.vue";
|
||||||
|
|
||||||
const co = useChartOrderStore()
|
const co = useChartOrderStore()
|
||||||
const endpoints = defineModel('modelValue') // 2-item list of points/values
|
const endpoints = defineModel('modelValue') // 2-item list of points/values
|
||||||
@@ -68,7 +117,7 @@ const props = defineProps({
|
|||||||
stdWidth: [Number, Array],
|
stdWidth: [Number, Array],
|
||||||
shape: Function, // shape() -> Shape
|
shape: Function, // shape() -> Shape
|
||||||
mode: { type: Number, default: 0 }, // rung addition mode: 0 = split, 1 = extend
|
mode: { type: Number, default: 0 }, // rung addition mode: 0 = split, 1 = extend
|
||||||
flip: { type: Boolean, default: false }, // if true, the skew slider is flipped upside-down
|
flip: { type: Boolean, default: false }, // if true, the balance slider is flipped upside-down
|
||||||
orientation: { type: Number, default: 1 }, // 0 = horizontal slider, 1 = vertical
|
orientation: { type: Number, default: 1 }, // 0 = horizontal slider, 1 = vertical
|
||||||
// values may be scalars or vector arrays
|
// values may be scalars or vector arrays
|
||||||
getModelValue: Function, // getModelValue(model) -> value
|
getModelValue: Function, // getModelValue(model) -> value
|
||||||
@@ -80,16 +129,18 @@ const props = defineProps({
|
|||||||
|
|
||||||
const flippedSign = computed(()=>props.flip?-1:1)
|
const flippedSign = computed(()=>props.flip?-1:1)
|
||||||
|
|
||||||
const skew100 = computed( {
|
const balance100 = computed( {
|
||||||
get() {return flippedSign.value*props.builder.skew*100},
|
get() {return flippedSign.value*props.builder.balance*100},
|
||||||
set(v) {props.builder.skew = flippedSign.value*v/100; }
|
set(v) {
|
||||||
|
// if (v<-60) v = -60;
|
||||||
|
props.builder.balance = flippedSign.value*v/100; }
|
||||||
} )
|
} )
|
||||||
|
|
||||||
// validity checks
|
// validity checks
|
||||||
watchEffect(()=>{
|
watchEffect(()=>{
|
||||||
const rungs = props.builder.rungs
|
const rungs = props.builder.rungs
|
||||||
// const prev = props.builder.valid
|
// const prev = props.builder.valid
|
||||||
props.builder.valid = rungs >= 1 && endpoints.value[0] && (rungs < 2 || endpoints.value[1])
|
props.builder.valid = rungs >= 1 && endpoints.value[0] > 0 && (rungs < 2 || endpoints.value[1])
|
||||||
// console.log('valid?', prev, props.builder.valid, rungs, valueA.value, valueB.value)
|
// console.log('valid?', prev, props.builder.valid, rungs, valueA.value, valueB.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -101,8 +152,12 @@ watchEffect(()=>{
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const breakout = computed({
|
||||||
|
get() {return !logicalXOR(props.builder.breakout, props.order.buy)},
|
||||||
|
set(v) {props.builder.breakout = !logicalXOR(v, props.order.buy)},
|
||||||
|
})
|
||||||
|
|
||||||
function setEndpoints(a, b) {
|
function setEndpoints(a, b) {
|
||||||
// console.log('rb setting endpoints', devectorize(a), devectorize(b))
|
|
||||||
endpoints.value = [devectorize(a), devectorize(b)]
|
endpoints.value = [devectorize(a), devectorize(b)]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,8 +180,7 @@ const rungs = computed({
|
|||||||
r = Number(r)
|
r = Number(r)
|
||||||
const prevR = Number(props.builder.rungs)
|
const prevR = Number(props.builder.rungs)
|
||||||
props.builder.rungs = r
|
props.builder.rungs = r
|
||||||
// console.log('set rungs', prevR, r, a, b)
|
if ( prevR === 1 && r > 1 ) {
|
||||||
if ( r > 0 && vectorIsNull(b) ) {
|
|
||||||
// convert single shape to a range
|
// convert single shape to a range
|
||||||
if (props.mode===0) {
|
if (props.mode===0) {
|
||||||
const width = vectorize(props.stdWidth)
|
const width = vectorize(props.stdWidth)
|
||||||
@@ -143,7 +197,7 @@ const rungs = computed({
|
|||||||
else
|
else
|
||||||
throw Error(`Unknown rung mode ${props.mode}`)
|
throw Error(`Unknown rung mode ${props.mode}`)
|
||||||
}
|
}
|
||||||
else if ( r === 1 && !vectorIsNull(b) ) {
|
else if ( prevR > 1 && r === 1 ) {
|
||||||
// convert from a range to a single shape
|
// convert from a range to a single shape
|
||||||
if (props.mode===0)
|
if (props.mode===0)
|
||||||
a = vectorDiv(vectorAdd(a,b), 2)
|
a = vectorDiv(vectorAdd(a,b), 2)
|
||||||
@@ -194,14 +248,10 @@ const values = computed(()=>{
|
|||||||
|
|
||||||
|
|
||||||
const weights = computed(() => {
|
const weights = computed(() => {
|
||||||
// const skew = props.flip ? -props.builder.skew : props.builder.skew
|
// const balance = props.flip ? -props.builder.balance : props.builder.balance
|
||||||
const most = 0.998
|
const most = 0.998
|
||||||
let skew = -props.builder.skew
|
let balance = Math.min(most, Math.max(-most, -props.builder.balance))
|
||||||
if (skew <= -1)
|
const ws = linearWeights(props.builder.rungs, balance)
|
||||||
skew = -most
|
|
||||||
else if (skew >= 1)
|
|
||||||
skew = most
|
|
||||||
const ws = linearWeights(props.builder.rungs, skew)
|
|
||||||
if (props.setWeights)
|
if (props.setWeights)
|
||||||
props.setWeights(ws)
|
props.setWeights(ws)
|
||||||
return ws
|
return ws
|
||||||
@@ -227,8 +277,10 @@ const color = computed({
|
|||||||
props.builder.color = c.saturation <= maxLightness ? v : c.lightness(maxLightness).rgb().string()
|
props.builder.color = c.saturation <= maxLightness ? v : c.lightness(maxLightness).rgb().string()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
const switchColor = computed(()=>props.builder.breakout ? color.value : null)
|
||||||
const colorStyle = computed(() => {
|
const colorStyle = computed(() => {
|
||||||
return {'color': color.value}
|
// return {'color': color.value}
|
||||||
|
return {}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -261,9 +313,9 @@ function translateOnModel(shape) {
|
|||||||
if (!this.beingDragged())
|
if (!this.beingDragged())
|
||||||
return
|
return
|
||||||
const prev = getModelValue(oldModel)
|
const prev = getModelValue(oldModel)
|
||||||
const cur = vectorize(getModelValue(this.model))
|
const cur = vectorize(getModelValue(model))
|
||||||
const delta = vectorSub(cur, prev)
|
const delta = vectorSub(cur, prev)
|
||||||
// console.log('delta', shape.id, prev, cur, delta)
|
// console.log('translateOnModel delta', shape.id, prev, cur, delta)
|
||||||
let [a, b] = endpoints.value
|
let [a, b] = endpoints.value
|
||||||
a = vectorize(a)
|
a = vectorize(a)
|
||||||
if (!vectorIsZero(delta)) {
|
if (!vectorIsZero(delta)) {
|
||||||
@@ -330,6 +382,7 @@ function makeModel(index) {
|
|||||||
allocation: alloc,
|
allocation: alloc,
|
||||||
maxAllocation: Math.max(...weights.value),
|
maxAllocation: Math.max(...weights.value),
|
||||||
amount: props.order.amount * alloc,
|
amount: props.order.amount * alloc,
|
||||||
|
baseSymbol: co.selectedSymbol.base.s,
|
||||||
amountSymbol: amountSymbol.value,
|
amountSymbol: amountSymbol.value,
|
||||||
textLocation: above ? 'above' : 'below',
|
textLocation: above ? 'above' : 'below',
|
||||||
breakout: props.builder.breakout,
|
breakout: props.builder.breakout,
|
||||||
@@ -414,6 +467,8 @@ function deleteShapes() {
|
|||||||
|
|
||||||
if (!endpoints.value[0])
|
if (!endpoints.value[0])
|
||||||
shapeA.createOrDraw(); // initiate drawing mode
|
shapeA.createOrDraw(); // initiate drawing mode
|
||||||
|
else
|
||||||
|
adjustShapes()
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -427,7 +482,7 @@ if (!endpoints.value[0])
|
|||||||
:deep(.v-slider.no-slider-bg .v-slider-track__fill) {
|
:deep(.v-slider.no-slider-bg .v-slider-track__fill) {
|
||||||
background-color: inherit !important;
|
background-color: inherit !important;
|
||||||
}
|
}
|
||||||
.skew {
|
.balance {
|
||||||
min-width: 9em;
|
min-width: 9em;
|
||||||
max-width: 12em;
|
max-width: 12em;
|
||||||
}
|
}
|
||||||
|
|||||||
28
src/components/chart/Shared.vue
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<template>
|
||||||
|
<h2>Shared Order</h2>
|
||||||
|
<p>Loading the shared order into the app...</p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {loadShareUrl} from "@/share.js";
|
||||||
|
import {router} from "@/router/router.js";
|
||||||
|
import {useRoute} from "vue-router";
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const code = route.params.code
|
||||||
|
loadShareUrl(code).then((ok)=> {
|
||||||
|
if (ok) {
|
||||||
|
console.log('loaded share data',code)
|
||||||
|
router.replace('/order');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log('failed to load share data',code)
|
||||||
|
}
|
||||||
|
}).catch((e)=> {
|
||||||
|
console.error(e)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="d-flex mb-1 align-center w-100">
|
<div class="d-flex mb-1 align-center w-100">
|
||||||
<logo class="d-flex align-end clickable logo-large ml-1" @click="nav('Order')" :show-tag="true" max-height="32"/>
|
<logo class="d-flex align-end clickable logo-large ml-1" @click="// noinspection JSIgnoredPromiseFromCall
|
||||||
|
router.push({name: 'Order'})" :show-tag="true" max-height="32"/>
|
||||||
<slot/>
|
<slot/>
|
||||||
<div class="ml-auto d-flex align-center">
|
<div class="ml-auto d-flex align-center">
|
||||||
<span class="title mr-4">{{title}}</span>
|
<span class="title mr-4">{{title}}</span>
|
||||||
@@ -9,7 +10,7 @@
|
|||||||
<toolbar-button tooltip="Assets" icon="mdi-currency-btc" route="Assets"/>
|
<toolbar-button tooltip="Assets" icon="mdi-currency-btc" route="Assets"/>
|
||||||
<!-- mdi-format-list-checks mdi-format-list-bulleted-square -->
|
<!-- mdi-format-list-checks mdi-format-list-bulleted-square -->
|
||||||
<toolbar-button tooltip="Status" icon="mdi-format-list-checks" route="Status"/>
|
<toolbar-button tooltip="Status" icon="mdi-format-list-checks" route="Status"/>
|
||||||
<toolbar-button tooltip="About" icon="mdi-information-outline" href="https://dexorder.trade/" target="dexorder"/>
|
<toolbar-button tooltip="About" icon="mdi-information-outline" href="https://dexorder.com/" target="dexorderwww"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -17,7 +18,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import ToolbarButton from "@/components/chart/ToolbarButton.vue";
|
import ToolbarButton from "@/components/chart/ToolbarButton.vue";
|
||||||
import Logo from "@/components/Logo.vue";
|
import Logo from "@/components/Logo.vue";
|
||||||
import {nav} from "@/misc.js";
|
import {router} from "@/router/router.js";
|
||||||
|
|
||||||
const props = defineProps(['title', 'icon'])
|
const props = defineProps(['title', 'icon'])
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,9 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {computed} from "vue";
|
import {computed} from "vue";
|
||||||
import {useRoute} from "vue-router";
|
import {router} from "@/router/router.js";
|
||||||
import {nav} from "/src/misc.js"
|
|
||||||
|
|
||||||
const props = defineProps(['icon', 'route', 'tooltip', 'href', 'target'])
|
const props = defineProps(['icon', 'route', 'tooltip', 'href', 'target'])
|
||||||
const router = useRoute();
|
|
||||||
const isCurrent = computed(() => router.name === props.route)
|
const isCurrent = computed(() => router.name === props.route)
|
||||||
|
|
||||||
function click() {
|
function click() {
|
||||||
@@ -29,7 +27,8 @@ function click() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
nav(props.route)
|
// noinspection JSIgnoredPromiseFromCall
|
||||||
|
router.push({name: props.route})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
function openApp() {
|
function openApp() {
|
||||||
window.open('https://app.dexorder.trade/', 'dexorderapp')
|
window.open('https://app.dexorder.com/', 'dexorderapp')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,8 @@
|
|||||||
</v-card-text>
|
</v-card-text>
|
||||||
<div class="w-100 d-flex justify-center my-6 actions">
|
<div class="w-100 d-flex justify-center my-6 actions">
|
||||||
<app-btn/>
|
<app-btn/>
|
||||||
<v-btn prepend-icon="mdi-information-outline" variant="flat" text="How It Works" @click="nav('HowItWorks')"/>
|
<v-btn prepend-icon="mdi-information-outline" variant="flat" text="How It Works" @click="// noinspection JSIgnoredPromiseFromCall
|
||||||
|
router.push({name: 'HowItWorks'})"/>
|
||||||
</div>
|
</div>
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
@@ -36,10 +37,8 @@ import beta from "@/components/Beta.vue";
|
|||||||
import Soon from "@/components/Soon.vue";
|
import Soon from "@/components/Soon.vue";
|
||||||
import UniswapLogo from "@/corp/UniswapLogo.vue";
|
import UniswapLogo from "@/corp/UniswapLogo.vue";
|
||||||
import Logo from "@/components/Logo.vue";
|
import Logo from "@/components/Logo.vue";
|
||||||
import {nav} from "@/misc.js";
|
|
||||||
import AppBtn from "@/corp/AppBtn.vue";
|
import AppBtn from "@/corp/AppBtn.vue";
|
||||||
import Social from "@/components/Social.vue";
|
import {router} from "@/router/router.js";
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -49,7 +49,8 @@
|
|||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-actions class="justify-center my-6 actions">
|
<v-card-actions class="justify-center my-6 actions">
|
||||||
<app-btn/>
|
<app-btn/>
|
||||||
<v-btn prepend-icon="mdi-home-outline" variant="flat" text="Home" @click="nav('Home')"/>
|
<v-btn prepend-icon="mdi-home-outline" variant="flat" text="Home" @click="// noinspection JSIgnoredPromiseFromCall
|
||||||
|
router.push({name: 'Home'})"/>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
@@ -57,8 +58,8 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
|
||||||
import UniswapLogo from "@/corp/UniswapLogo.vue";
|
import UniswapLogo from "@/corp/UniswapLogo.vue";
|
||||||
import {nav} from "@/misc.js";
|
|
||||||
import AppBtn from "@/corp/AppBtn.vue";
|
import AppBtn from "@/corp/AppBtn.vue";
|
||||||
|
import {router} from "@/router/router.js";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -3,11 +3,13 @@
|
|||||||
<!-- <v-app-bar-nav-icon @click="s.nav=!s.nav" icon="mdi-plus"/>-->
|
<!-- <v-app-bar-nav-icon @click="s.nav=!s.nav" icon="mdi-plus"/>-->
|
||||||
|
|
||||||
<v-app-bar-title>
|
<v-app-bar-title>
|
||||||
<logo @click="nav('Home')" class="clickable"/>
|
<logo @click="// noinspection JSIgnoredPromiseFromCall
|
||||||
|
router.push({name: 'Home'})" class="clickable"/>
|
||||||
<social class="d-inline" size="small"/>
|
<social class="d-inline" size="small"/>
|
||||||
</v-app-bar-title>
|
</v-app-bar-title>
|
||||||
|
|
||||||
<v-btn @click="nav('HowItWorks')" prepend-icon="mdi-information-outline" text="How It Works"/>
|
<v-btn @click="// noinspection JSIgnoredPromiseFromCall
|
||||||
|
router.push({name: 'HowItWorks'})" prepend-icon="mdi-information-outline" text="How It Works"/>
|
||||||
<v-btn prepend-icon="mdi-arrow-up-bold" variant="tonal" color="primary" @click="openApp" text="Launch App"/>
|
<v-btn prepend-icon="mdi-arrow-up-bold" variant="tonal" color="primary" @click="openApp" text="Launch App"/>
|
||||||
|
|
||||||
</v-app-bar>
|
</v-app-bar>
|
||||||
@@ -16,13 +18,13 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {useTheme} from "vuetify";
|
import {useTheme} from "vuetify";
|
||||||
import Logo from "@/components/Logo.vue";
|
import Logo from "@/components/Logo.vue";
|
||||||
import {nav} from "@/misc.js";
|
|
||||||
import Social from "@/components/Social.vue";
|
import Social from "@/components/Social.vue";
|
||||||
|
import {router} from "@/router/router.js";
|
||||||
|
|
||||||
const theme = useTheme().current
|
const theme = useTheme().current
|
||||||
|
|
||||||
function openApp() {
|
function openApp() {
|
||||||
window.open('https://app.dexorder.trade/', 'dexorderapp')
|
window.open('https://app.dexorder.com/', 'dexorderapp')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
47
src/debug_console.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
(function() {
|
||||||
|
let debugDiv = document.createElement('div');
|
||||||
|
debugDiv.id = 'debug-log';
|
||||||
|
debugDiv.setAttribute(
|
||||||
|
'style',
|
||||||
|
`
|
||||||
|
background:#222 !important;
|
||||||
|
color:#0f0 !important;
|
||||||
|
padding:8px !important;
|
||||||
|
font-family:monospace !important;
|
||||||
|
font-size:14px !important;
|
||||||
|
position:fixed !important;
|
||||||
|
left:0 !important; right:0 !important;
|
||||||
|
bottom:0 !important;
|
||||||
|
max-height:35vh !important;
|
||||||
|
width:100vw !important;
|
||||||
|
overflow-y:auto !important;
|
||||||
|
z-index:9999 !important;
|
||||||
|
pointer-events:auto !important;
|
||||||
|
box-shadow:0 0 8px #000 !important;
|
||||||
|
touch-action: auto !important;
|
||||||
|
-webkit-overflow-scrolling: touch !important;
|
||||||
|
white-space: pre-wrap !important;
|
||||||
|
word-break: break-word !important;
|
||||||
|
`
|
||||||
|
);
|
||||||
|
document.body.appendChild(debugDiv);
|
||||||
|
|
||||||
|
function printLog(type, args) {
|
||||||
|
let msg = Array.from(args).map(a => {
|
||||||
|
try { return typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a); }
|
||||||
|
catch { return String(a); }
|
||||||
|
}).join(' ');
|
||||||
|
let line = document.createElement('div');
|
||||||
|
line.textContent = `[${type}] ${msg}`;
|
||||||
|
debugDiv.appendChild(line);
|
||||||
|
debugDiv.scrollTop = debugDiv.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
['trace', 'log', 'info', 'warn', 'error'].forEach(type => {
|
||||||
|
let orig = console[type];
|
||||||
|
console[type] = function(...args) {
|
||||||
|
printLog(type, args);
|
||||||
|
orig.apply(console, args);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
88
src/fees.js
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import {newContract, vaultContract} from "@/blockchain/contract.js";
|
||||||
|
import {timestamp} from "@/common.js";
|
||||||
|
import TTLCache from "@isaacs/ttlcache";
|
||||||
|
import {provider} from "@/blockchain/provider.js";
|
||||||
|
|
||||||
|
|
||||||
|
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(vaultContractOrAddr, 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 = typeof vaultContractOrAddr === 'string' ? await vaultContract(vaultContractOrAddr, provider) : vaultContractOrAddr
|
||||||
|
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()
|
||||||
|
console.log('nextSched', new Date(Number(changeTimestamp)*1000), nextSched)
|
||||||
|
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()
|
||||||
@@ -4,9 +4,13 @@
|
|||||||
<v-dialog v-model="showTransactionDialog" max-width="300">
|
<v-dialog v-model="showTransactionDialog" max-width="300">
|
||||||
<v-card :title="title">
|
<v-card :title="title">
|
||||||
<v-card-text v-if="description!==null">{{description}}</v-card-text>
|
<v-card-text v-if="description!==null">{{description}}</v-card-text>
|
||||||
<v-card-text>Confirm this {{noun}} in your wallet.</v-card-text>
|
<v-card-text v-if="!s.vault">Creating your trading vault smart contract. Please wait a few seconds...</v-card-text>
|
||||||
|
<v-card-text v-if="s.vault && s.creatingVault">Verifying your trading vault...</v-card-text>
|
||||||
|
<v-card-text v-if="s.vault && !s.creatingVault && ws.transaction !== null && ws.transaction.state === TransactionState.Proposed">Confirm this {{noun}} in your wallet.</v-card-text>
|
||||||
|
<v-card-text v-if="s.vault && !s.creatingVault && ws.transaction !== null && ws.transaction.state !== TransactionState.Proposed">Signed and sent! Waiting for blockchain confirmation...</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
|
<pool-selection-dialog/>
|
||||||
</v-app>
|
</v-app>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -15,8 +19,9 @@ import MainView from './MainView.vue'
|
|||||||
import {useStore} from "@/store/store.js";
|
import {useStore} from "@/store/store.js";
|
||||||
import {computed} from "vue";
|
import {computed} from "vue";
|
||||||
import {useWalletStore} from "@/blockchain/wallet.js";
|
import {useWalletStore} from "@/blockchain/wallet.js";
|
||||||
import {TransactionType} from "@/blockchain/transaction.js";
|
|
||||||
import {FixedNumber} from "ethers";
|
import {FixedNumber} from "ethers";
|
||||||
|
import PoolSelectionDialog from "@/components/PoolSelectionDialog.vue";
|
||||||
|
import {TransactionState, TransactionType} from "@/blockchain/transactionDecl.js";
|
||||||
|
|
||||||
const s = useStore()
|
const s = useStore()
|
||||||
const ws = useWalletStore()
|
const ws = useWalletStore()
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
<v-icon icon="mdi-arrow-up-bold" size="x-small" class="arrow" color="green"/>
|
<v-icon icon="mdi-arrow-up-bold" size="x-small" class="arrow" color="green"/>
|
||||||
<span class="clickable">dexorder</span>
|
<span class="clickable">dexorder</span>
|
||||||
</span>
|
</span>
|
||||||
<v-chip text="BETA" size="x-small" color="red" class="mx-1"/>
|
|
||||||
</v-app-bar-title>
|
</v-app-bar-title>
|
||||||
|
|
||||||
<v-btn icon="mdi-safe-square" color="grey-darken-2" text="Vault" @click="route('Assets')"></v-btn>
|
<v-btn icon="mdi-safe-square" color="grey-darken-2" text="Vault" @click="route('Assets')"></v-btn>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { createApp } from 'vue'
|
|||||||
// Plugins
|
// Plugins
|
||||||
import { registerPlugins } from '@/plugins'
|
import { registerPlugins } from '@/plugins'
|
||||||
import '@/styles/style.scss'
|
import '@/styles/style.scss'
|
||||||
import "./socket.js"
|
import "./socketInit.js"
|
||||||
import "./version.js"
|
import "./version.js"
|
||||||
|
|
||||||
BigInt.prototype.toJSON = function() { return this.toString() }
|
BigInt.prototype.toJSON = function() { return this.toString() }
|
||||||
|
|||||||
71
src/misc.js
@@ -3,13 +3,8 @@ import {usePrefStore, useStore} from "@/store/store.js";
|
|||||||
import {token} from "@/blockchain/token.js";
|
import {token} from "@/blockchain/token.js";
|
||||||
import Color from "color";
|
import Color from "color";
|
||||||
import {DateTime} from "luxon";
|
import {DateTime} from "luxon";
|
||||||
import router from "@/router/index.js";
|
|
||||||
import {dateString} from "@/common.js";
|
import {dateString} from "@/common.js";
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
export function nav(name) {
|
|
||||||
// noinspection JSIgnoredPromiseFromCall
|
|
||||||
router.push({name})
|
|
||||||
}
|
|
||||||
|
|
||||||
const QUOTE_SYMBOLS = [
|
const QUOTE_SYMBOLS = [
|
||||||
// in order of preference
|
// in order of preference
|
||||||
@@ -41,7 +36,7 @@ export class SingletonCoroutine {
|
|||||||
// console.log('invoke', arguments)
|
// console.log('invoke', arguments)
|
||||||
if (this.timeout === null)
|
if (this.timeout === null)
|
||||||
// noinspection JSCheckFunctionSignatures
|
// noinspection JSCheckFunctionSignatures
|
||||||
this.timeout = setTimeout(this.onTimeout, this.delay, this)
|
this.timeout = setTimeout(this.onTimeout, this.delay, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
async onTimeout(self) {
|
async onTimeout(self) {
|
||||||
@@ -71,7 +66,9 @@ export const uint32max = 4294967295
|
|||||||
export const uint64max = 18446744073709551615n
|
export const uint64max = 18446744073709551615n
|
||||||
|
|
||||||
export function tokenNumber(token, balance) {
|
export function tokenNumber(token, balance) {
|
||||||
return FixedNumber.fromValue(balance, token.decimals, {decimals: token.decimals, width: 256})
|
const dec = token ? token.decimals : 0
|
||||||
|
console.log('token dec', dec, balance)
|
||||||
|
return FixedNumber.fromValue(balance, dec, {decimals: dec, width: 256})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tokenFloat(token, balance) {
|
export function tokenFloat(token, balance) {
|
||||||
@@ -97,7 +94,7 @@ export function intervalString(seconds) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function timestampString(seconds) {
|
export function timestampString(seconds) {
|
||||||
const date = DateTime.fromSeconds(seconds).setZone(useStore().timeZone)
|
const date = DateTime.fromSeconds(seconds).setZone(usePrefStore().timezone)
|
||||||
return dateString(date)
|
return dateString(date)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,10 +150,7 @@ export function inversionPreference(chainId, base, quote) {
|
|||||||
|
|
||||||
export const sleep = ms => new Promise(r => setTimeout(r, ms))
|
export const sleep = ms => new Promise(r => setTimeout(r, ms))
|
||||||
|
|
||||||
export function uuid() {
|
export function uuid() {return uuidv4()}
|
||||||
// noinspection JSUnresolvedReference
|
|
||||||
return crypto.randomUUID();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function lightenColor(color, lightness = 85, alpha = null) {
|
export function lightenColor(color, lightness = 85, alpha = null) {
|
||||||
let c = new Color(color).hsl()
|
let c = new Color(color).hsl()
|
||||||
@@ -230,6 +224,21 @@ export function intervalToSeconds(interval) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function secondsToInterval(seconds) {
|
||||||
|
const units = [
|
||||||
|
[30 * 24 * 60 * 60, 'M'],
|
||||||
|
[7 * 24 * 60 * 60, 'W'],
|
||||||
|
[24 * 60 * 60, 'D'],
|
||||||
|
[60, ''],
|
||||||
|
[1, 'S'],
|
||||||
|
]
|
||||||
|
for( const [unit, suffix] of units)
|
||||||
|
if (seconds % unit === 0)
|
||||||
|
return `${seconds / unit}${suffix}`
|
||||||
|
throw Error(`invalid secondsToInterval ${seconds}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export function interpolate(a, b, zeroToOne) {
|
export function interpolate(a, b, zeroToOne) {
|
||||||
const d = (b-a)
|
const d = (b-a)
|
||||||
return a + d * zeroToOne
|
return a + d * zeroToOne
|
||||||
@@ -251,3 +260,39 @@ export function computeInterceptSlope(time0, price0, time1, price1) {
|
|||||||
export function defined(v) {
|
export function defined(v) {
|
||||||
return v !== undefined && v !== null
|
return v !== undefined && v !== null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toPrecision(value, significantDigits = 3) {
|
||||||
|
if (!isFinite(value)) return value.toString(); // Handle Infinity and NaN
|
||||||
|
if (value === 0) return "0"; // Special case for 0
|
||||||
|
const magnitude = Math.floor(Math.log10(Math.abs(value)));
|
||||||
|
const decimalsNeeded = Math.max(0, significantDigits - 1 - magnitude);
|
||||||
|
return value.toFixed(decimalsNeeded); // Use toFixed to completely avoid scientific notation
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toPrecisionOrNull(value, significantDigits = 3) {
|
||||||
|
if (value===null) return null
|
||||||
|
if (value===undefined) return undefined
|
||||||
|
return toPrecision(value, significantDigits)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toHuman(value, significantDigits = 2) {
|
||||||
|
if (!isFinite(value)) return value.toString(); // Handle Infinity and NaN
|
||||||
|
let suffix = ''
|
||||||
|
if (value >= 1_000_000_000) {
|
||||||
|
value /= 1_000_000_000
|
||||||
|
suffix = 'B'
|
||||||
|
}
|
||||||
|
else if (value >= 1_000_000) {
|
||||||
|
value /= 1_000_000
|
||||||
|
suffix = 'M'
|
||||||
|
}
|
||||||
|
else if (value >= 1_000) {
|
||||||
|
value /= 1_000
|
||||||
|
suffix = 'K'
|
||||||
|
}
|
||||||
|
return toPrecision(value, significantDigits) + suffix
|
||||||
|
}
|
||||||
|
|
||||||
|
export function errorSuggestsMissingVault(e) {
|
||||||
|
return e.value === '0x' && e.code === 'BAD_DATA' || e.revert === null && e.code === 'CALL_EXCEPTION';
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
import {getToken} from "@/blockchain/token.js";
|
import {getToken} from "@/blockchain/token.js";
|
||||||
|
|
||||||
let native = false // whether native browser notifications are allowed
|
let notificationsAllowed = false // whether native browser notifications are allowed
|
||||||
|
|
||||||
Notification.requestPermission()
|
if ('Notification' in window) {
|
||||||
.then(permission => {
|
Notification.requestPermission()
|
||||||
console.log(`notification permission: ${permission}`);
|
.then(permission => {
|
||||||
native = permission === 'granted'
|
notificationsAllowed = permission === 'granted'
|
||||||
})
|
if (!notificationsAllowed)
|
||||||
.catch(error => {
|
console.log(`notification permission denied: ${permission}`);
|
||||||
console.error(`notification permission error: ${error}`);
|
})
|
||||||
native = false;
|
.catch(error => {console.error(`notification permission error: ${error}`);});
|
||||||
});
|
}
|
||||||
|
|
||||||
|
|
||||||
export function notify(title, message=null) {
|
export function notify(title, message=null) {
|
||||||
if (native) {
|
if (notificationsAllowed) {
|
||||||
const options = {
|
const options = {
|
||||||
renotify: true,
|
renotify: true,
|
||||||
tag: title,
|
tag: title,
|
||||||
@@ -44,4 +44,4 @@ export async function notifyFillEvent(chainId, status, trancheIndex, fill) {
|
|||||||
` for ${quoteAmount.toPrecision(5)} ${quote.s} average ${average.toPrecision(5)} ${base.s}/${quote.s}}`;
|
` for ${quoteAmount.toPrecision(5)} ${quote.s} average ${average.toPrecision(5)} ${base.s}/${quote.s}}`;
|
||||||
// todo buy/sell arrow icon
|
// todo buy/sell arrow icon
|
||||||
notify(title)
|
notify(title)
|
||||||
}
|
}
|
||||||
@@ -7,10 +7,6 @@ import {computed, ref} from "vue";
|
|||||||
import Color from "color";
|
import Color from "color";
|
||||||
|
|
||||||
|
|
||||||
export const MIN_EXECUTION_TIME = 60 // give at least one full minute for each tranche to trigger
|
|
||||||
export const DEFAULT_SLIPPAGE = 0.0030;
|
|
||||||
|
|
||||||
|
|
||||||
// Builders are data objects which store a configuration state
|
// Builders are data objects which store a configuration state
|
||||||
// the component name must match a corresponding Vue component in the BuilderFactory.vue component, which is responsible
|
// the component name must match a corresponding Vue component in the BuilderFactory.vue component, which is responsible
|
||||||
// for instantiating the UI component for a given builder dictionary, based on its builder.component field.
|
// for instantiating the UI component for a given builder dictionary, based on its builder.component field.
|
||||||
@@ -45,6 +41,7 @@ function newDefaultOrder() {
|
|||||||
export const useChartOrderStore = defineStore('chart_orders', () => {
|
export const useChartOrderStore = defineStore('chart_orders', () => {
|
||||||
const chartReady = ref(false)
|
const chartReady = ref(false)
|
||||||
|
|
||||||
|
const showPoolSelection = ref(false) // if true, the pool information / fee choosing dialog is shown
|
||||||
const defaultOrder = newDefaultOrder()
|
const defaultOrder = newDefaultOrder()
|
||||||
const orders = ref([defaultOrder]) // order models in UI format
|
const orders = ref([defaultOrder]) // order models in UI format
|
||||||
const selectedOrder = ref(null)
|
const selectedOrder = ref(null)
|
||||||
@@ -56,7 +53,8 @@ export const useChartOrderStore = defineStore('chart_orders', () => {
|
|||||||
if (!selectedSymbol.value)
|
if (!selectedSymbol.value)
|
||||||
return null
|
return null
|
||||||
const s = useStore()
|
const s = useStore()
|
||||||
let result = s.poolPrices[[s.chainId, selectedSymbol.address]]
|
const key = [s.chainId, selectedSymbol.value.address];
|
||||||
|
let result = s.poolPrices[key]
|
||||||
if (selectedSymbol.value.inverted)
|
if (selectedSymbol.value.inverted)
|
||||||
result = 1 / result
|
result = 1 / result
|
||||||
return result
|
return result
|
||||||
@@ -65,6 +63,7 @@ export const useChartOrderStore = defineStore('chart_orders', () => {
|
|||||||
const meanRange = ref(1)
|
const meanRange = ref(1)
|
||||||
|
|
||||||
const drawing = ref(false)
|
const drawing = ref(false)
|
||||||
|
const drew = ref(true) // true if at least one of the points has been drawn already
|
||||||
|
|
||||||
function newOrder() {
|
function newOrder() {
|
||||||
const order = newDefaultOrder()
|
const order = newDefaultOrder()
|
||||||
@@ -94,7 +93,8 @@ export const useChartOrderStore = defineStore('chart_orders', () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
chartReady, selectedSymbol, intervalSecs, baseToken, quoteToken, price,
|
chartReady, selectedSymbol, intervalSecs, baseToken, quoteToken, price,
|
||||||
orders, drawing, newOrder, removeOrder, resetOrders, meanRange,
|
orders, drawing, drew, newOrder, removeOrder, resetOrders, meanRange,
|
||||||
|
showPoolSelection,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -181,6 +181,7 @@ export function timesliceTranches() {
|
|||||||
const start = Math.floor(i * (duration / Math.max((n - 1), 1)))
|
const start = Math.floor(i * (duration / Math.max((n - 1), 1)))
|
||||||
const end = start + window
|
const end = start + window
|
||||||
ts.push(newTranche({
|
ts.push(newTranche({
|
||||||
|
marketOrder: true,
|
||||||
fraction: amtPerTranche,
|
fraction: amtPerTranche,
|
||||||
startTimeIsRelative: true,
|
startTimeIsRelative: true,
|
||||||
startTime: start,
|
startTime: start,
|
||||||
@@ -193,8 +194,9 @@ export function timesliceTranches() {
|
|||||||
|
|
||||||
export function builderDefaults(builder, defaults) {
|
export function builderDefaults(builder, defaults) {
|
||||||
for (const k in defaults)
|
for (const k in defaults)
|
||||||
if (builder[k] === undefined)
|
if (!Object.prototype.hasOwnProperty.call(builder, k)) {
|
||||||
builder[k] = defaults[k] instanceof Function ? defaults[k]() : defaults[k]
|
builder[k] = defaults[k] instanceof Function ? defaults[k]() : defaults[k]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function linearWeights(num, skew) {
|
export function linearWeights(num, skew) {
|
||||||
|
|||||||
@@ -8,11 +8,15 @@
|
|||||||
import { loadFonts } from './webfontloader'
|
import { loadFonts } from './webfontloader'
|
||||||
import vuetify from './vuetify'
|
import vuetify from './vuetify'
|
||||||
import {pinia} from '../store/pinia.js'
|
import {pinia} from '../store/pinia.js'
|
||||||
import router from '../router'
|
import {setRouter} from '../router/router.js'
|
||||||
|
import {newRouter} from '../router/newRouter.js'
|
||||||
import vsp from "vue-scroll-picker";
|
import vsp from "vue-scroll-picker";
|
||||||
|
|
||||||
import "vue-scroll-picker/lib/style.css";
|
import "vue-scroll-picker/lib/style.css";
|
||||||
|
|
||||||
|
const router = newRouter();
|
||||||
|
setRouter(router)
|
||||||
|
|
||||||
export function registerPlugins (app) {
|
export function registerPlugins (app) {
|
||||||
loadFonts().catch((e)=>console.error('Could not load fonts!',e))
|
loadFonts().catch((e)=>console.error('Could not load fonts!',e))
|
||||||
app
|
app
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
darken1,
|
darken1,
|
||||||
darkMiddleShadeIndex,
|
darkMiddleShadeIndex,
|
||||||
light,
|
light,
|
||||||
lightMiddleShadeIndex, numShades, pageShade,
|
lightMiddleShadeIndex, numShades, surfaceShade,
|
||||||
printContrast
|
printContrast
|
||||||
} from "../../theme.js";
|
} from "../../theme.js";
|
||||||
|
|
||||||
@@ -28,13 +28,13 @@ function makeColors(isLight) {
|
|||||||
const ink = k[printContrast] // text color
|
const ink = k[printContrast] // text color
|
||||||
function darken(cols,shades) {return cols[base+(isLight?-shades:shades)]}
|
function darken(cols,shades) {return cols[base+(isLight?-shades:shades)]}
|
||||||
// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides
|
// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides
|
||||||
return {
|
const colors = {
|
||||||
background: k[pageShade],
|
background: k[0],
|
||||||
surface: k[pageShade],
|
surface: k[surfaceShade],
|
||||||
'surface-bright': k[pageShade],
|
'surface-bright': k[surfaceShade],
|
||||||
'surface-light': k[pageShade+2],
|
'surface-light': k[surfaceShade+2],
|
||||||
'surface-variant': k[14],
|
'surface-variant': k[14],
|
||||||
'on-surface-variant': k[pageShade+2],
|
'on-surface-variant': k[surfaceShade+2],
|
||||||
primary: c.greens[base],
|
primary: c.greens[base],
|
||||||
'primary-darken-1': darken(c.greens, darken1),
|
'primary-darken-1': darken(c.greens, darken1),
|
||||||
secondary: c.blues[base],
|
secondary: c.blues[base],
|
||||||
@@ -53,6 +53,8 @@ function makeColors(isLight) {
|
|||||||
"on-warning": ink,
|
"on-warning": ink,
|
||||||
"on-error": ink,
|
"on-error": ink,
|
||||||
}
|
}
|
||||||
|
// console.log('colors', isLight?'light':'dark', colors)
|
||||||
|
return colors;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lightColors = makeColors(true)
|
const lightColors = makeColors(true)
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
// Composables
|
|
||||||
import { createRouter, createWebHistory } from 'vue-router'
|
|
||||||
|
|
||||||
const routes = [
|
|
||||||
{
|
|
||||||
component: () => import('@/corp/CorpLayout.vue'),
|
|
||||||
path:'/home',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'Home',
|
|
||||||
path: '/home',
|
|
||||||
component: () => import('@/corp/Home.vue'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'HowItWorks',
|
|
||||||
path: '/home/how-it-works',
|
|
||||||
component: () => import('@/corp/HowItWorks.vue'),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
component: () => import('@/layouts/chart/ChartLayout.vue'),
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'App',
|
|
||||||
path: '/',
|
|
||||||
// route level code-splitting
|
|
||||||
// this generates a separate chunk (about.[hash].js) for this route
|
|
||||||
// which is lazy-loaded when the route is visited.
|
|
||||||
component: () => import('@/components/chart/ChartVault.vue'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Order',
|
|
||||||
path: '/order',
|
|
||||||
// route level code-splitting
|
|
||||||
// this generates a separate chunk (about.[hash].js) for this route
|
|
||||||
// which is lazy-loaded when the route is visited.
|
|
||||||
component: () => import('@/components/chart/ChartPlaceOrder.vue'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Assets',
|
|
||||||
path: '/assets',
|
|
||||||
// route level code-splitting
|
|
||||||
// this generates a separate chunk (about.[hash].js) for this route
|
|
||||||
// which is lazy-loaded when the route is visited.
|
|
||||||
component: () => import('@/components/chart/ChartVault.vue'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Status',
|
|
||||||
path: '/status',
|
|
||||||
// route level code-splitting
|
|
||||||
// this generates a separate chunk (about.[hash].js) for this route
|
|
||||||
// which is lazy-loaded when the route is visited.
|
|
||||||
component: () => import('@/components/chart/ChartStatus.vue'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const router = createRouter({
|
|
||||||
history: createWebHistory(process.env.BASE_URL),
|
|
||||||
routes,
|
|
||||||
})
|
|
||||||
|
|
||||||
export default router
|
|
||||||
10
src/router/newRouter.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
// Composables
|
||||||
|
import {createRouter, createWebHistory} from 'vue-router'
|
||||||
|
import {routes} from "@/router/routes.js";
|
||||||
|
|
||||||
|
export function newRouter() {
|
||||||
|
return createRouter({
|
||||||
|
history: createWebHistory(process.env.BASE_URL),
|
||||||
|
routes,
|
||||||
|
})
|
||||||
|
}
|
||||||
3
src/router/router.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export let router = null
|
||||||
|
|
||||||
|
export function setRouter (r) { router = r }
|
||||||
45
src/router/routes.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
export const routes = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
component: () => import('@/layouts/chart/ChartLayout.vue'),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'App',
|
||||||
|
path: '/',
|
||||||
|
// route level code-splitting
|
||||||
|
// this generates a separate chunk (about.[hash].js) for this route
|
||||||
|
// which is lazy-loaded when the route is visited.
|
||||||
|
component: () => import('@/components/chart/ChartPlaceOrder.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Shared',
|
||||||
|
path: '/shared/:code',
|
||||||
|
component: () => import('@/components/chart/Shared.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Order',
|
||||||
|
path: '/order',
|
||||||
|
// route level code-splitting
|
||||||
|
// this generates a separate chunk (about.[hash].js) for this route
|
||||||
|
// which is lazy-loaded when the route is visited.
|
||||||
|
component: () => import('@/components/chart/ChartPlaceOrder.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Assets',
|
||||||
|
path: '/assets',
|
||||||
|
// route level code-splitting
|
||||||
|
// this generates a separate chunk (about.[hash].js) for this route
|
||||||
|
// which is lazy-loaded when the route is visited.
|
||||||
|
component: () => import('@/components/chart/ChartVault.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Status',
|
||||||
|
path: '/status',
|
||||||
|
// route level code-splitting
|
||||||
|
// this generates a separate chunk (about.[hash].js) for this route
|
||||||
|
// which is lazy-loaded when the route is visited.
|
||||||
|
component: () => import('@/components/chart/ChartStatus.vue'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
88
src/share.js
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import {useChartOrderStore} from "@/orderbuild.js";
|
||||||
|
import {changeIntervalSecs, onChartReady, setSymbol, widget} from "@/charts/chart.js";
|
||||||
|
import {usePrefStore, useStore} from "@/store/store.js";
|
||||||
|
import {lookupSymbol} from "@/charts/datafeed.js";
|
||||||
|
import {track} from "@/track.js";
|
||||||
|
|
||||||
|
import {socket} from "@/socket.js";
|
||||||
|
|
||||||
|
export async function getShareUrl() {
|
||||||
|
const co = useChartOrderStore();
|
||||||
|
const s = useStore()
|
||||||
|
const sym = co.selectedSymbol
|
||||||
|
console.log('symbol', sym)
|
||||||
|
const data = {
|
||||||
|
version: 1,
|
||||||
|
chainId: s.chainId,
|
||||||
|
orders: co.orders,
|
||||||
|
symbol: {
|
||||||
|
base: {a: sym.base.a, s: sym.base.s},
|
||||||
|
quote: {a: sym.quote.a, s: sym.quote.s},
|
||||||
|
route: {
|
||||||
|
fee: sym.fee,
|
||||||
|
exchange: sym.exchangeId,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
period: co.intervalSecs,
|
||||||
|
}
|
||||||
|
const json = JSON.stringify(data)
|
||||||
|
console.log('sharing data', json, data)
|
||||||
|
const snapshot = await takeSnapshot()
|
||||||
|
|
||||||
|
const code = await new Promise((resolve)=>socket.emit('share', data, snapshot, resolve))
|
||||||
|
if (code===null) return null
|
||||||
|
|
||||||
|
return import.meta.env.VITE_SHARE_URL+ '/share/'+code;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadShareUrl(code) {
|
||||||
|
// console.log('loading share url', code)
|
||||||
|
const data = await new Promise((resolve, reject) => {
|
||||||
|
// Set a timeout (e.g., 8 seconds)
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
reject(new Error('Timed out waiting for response from server'));
|
||||||
|
}, 8000);
|
||||||
|
|
||||||
|
socket.emit('shared', code, (response) => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
resolve(response);
|
||||||
|
});
|
||||||
|
}).catch(err => {
|
||||||
|
// Optional: show error to user or log it
|
||||||
|
console.error('Failed to load shared URL:', err.message);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
if (data===null) return false
|
||||||
|
console.log('loaded shared orders data', data)
|
||||||
|
const co = useChartOrderStore();
|
||||||
|
const s = useStore()
|
||||||
|
const ticker = `${data.chainId}|${data.symbol.route.exchange}|${data.symbol.base.a}|${data.symbol.quote.a}|${data.symbol.route.fee}`;
|
||||||
|
const symbol = lookupSymbol(ticker)
|
||||||
|
if (symbol===null) {
|
||||||
|
console.error('could not find symbol for ticker', ticker)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
s.chainId = data.chainId
|
||||||
|
const prefs = usePrefStore()
|
||||||
|
prefs.selectedSymbol = ticker
|
||||||
|
for (const order of data.orders) {
|
||||||
|
// force amount to be zero so that the user MUST enter a size before confirming the placement
|
||||||
|
order.amount = 0
|
||||||
|
order.amountIsTokenA = true
|
||||||
|
order.valid = false
|
||||||
|
}
|
||||||
|
co.orders = data.orders
|
||||||
|
changeIntervalSecs(data.period)
|
||||||
|
onChartReady(()=>{
|
||||||
|
setSymbol(symbol)
|
||||||
|
.catch((e)=>console.error('could not set symbol', e))
|
||||||
|
})
|
||||||
|
track('shared')
|
||||||
|
console.log('loaded orders', s.chainId, co.orders)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function takeSnapshot() {
|
||||||
|
const screenshotCanvas = await widget.takeClientScreenshot();
|
||||||
|
return await new Promise((resolve) => screenshotCanvas.toBlob(resolve));
|
||||||
|
}
|
||||||
147
src/socket.js
@@ -1,145 +1,8 @@
|
|||||||
import {io} from "socket.io-client";
|
import {io} from "socket.io-client";
|
||||||
import {useStore} from "@/store/store.js";
|
|
||||||
import {flushOrders} from "@/blockchain/wallet.js";
|
|
||||||
import {parseElaboratedOrderStatus} from "@/blockchain/orderlib.js";
|
|
||||||
import { DataFeed } from "./charts/datafeed";
|
|
||||||
import {notifyFillEvent} from "@/notify.js";
|
|
||||||
|
|
||||||
export const socket = io(import.meta.env.VITE_WS_URL || undefined, {transports: ["websocket"]})
|
const socketOptions = {
|
||||||
|
transport: ['polling', 'websocket'],
|
||||||
socket.on('connect', () => {
|
pingInterval: 25000, // PING every 25 seconds
|
||||||
console.log(new Date(), 'ws connected')
|
pingTimeout: 60000 // Timeout if no PONG in 60 seconds
|
||||||
useStore().connected = true
|
|
||||||
})
|
|
||||||
|
|
||||||
socket.on('disconnect', () => {
|
|
||||||
console.log(new Date(), 'ws disconnected')
|
|
||||||
useStore().connected = false
|
|
||||||
})
|
|
||||||
|
|
||||||
socket.on('approvedRegion', (approved) => {
|
|
||||||
console.log('approved region', approved)
|
|
||||||
useStore().regionApproved = approved
|
|
||||||
})
|
|
||||||
|
|
||||||
socket.on('approvedWallet', (approved) => {
|
|
||||||
console.log('approved wallet', approved)
|
|
||||||
useStore().walletApproved = approved
|
|
||||||
})
|
|
||||||
|
|
||||||
socket.on('p', async (chainId, pool, price) => {
|
|
||||||
console.log('pool price from message', chainId, pool, price)
|
|
||||||
const s = useStore()
|
|
||||||
if( s.chainId !== chainId )
|
|
||||||
return
|
|
||||||
s.poolPrices[[chainId,pool]] = price
|
|
||||||
})
|
|
||||||
|
|
||||||
socket.on('ohlc', async (chainId, poolPeriod, ohlcs) => {
|
|
||||||
// console.log('pool bars', poolPeriod, ohlcs)
|
|
||||||
if (ohlcs && ohlcs.length) {
|
|
||||||
const split = poolPeriod.indexOf('|')
|
|
||||||
const pool = poolPeriod.slice(0,split)
|
|
||||||
useStore().poolPrices[[chainId, pool]] = parseFloat(ohlcs[ohlcs.length - 1][4]) // closing price
|
|
||||||
}
|
|
||||||
DataFeed.poolCallback(chainId, poolPeriod, ohlcs)
|
|
||||||
})
|
|
||||||
|
|
||||||
socket.on('vb', async (chainId, vault, balances) => {
|
|
||||||
const s = useStore()
|
|
||||||
if( s.chainId !== chainId )
|
|
||||||
return
|
|
||||||
console.log('vb', vault, balances)
|
|
||||||
s.vaultBalances[vault] = JSON.parse(balances)
|
|
||||||
console.log('vault balances', vault, s.vaultBalances[vault])
|
|
||||||
})
|
|
||||||
|
|
||||||
socket.on('vaults', (chainId, owner, vaults)=>{
|
|
||||||
const s = useStore()
|
|
||||||
console.log('vaults', chainId, owner, vaults)
|
|
||||||
if( s.chainId !== chainId || s.account !== owner )
|
|
||||||
return
|
|
||||||
if( vaults.length > s.vaults.length ) {
|
|
||||||
s.vaults = vaults
|
|
||||||
if( vaults.length ) {
|
|
||||||
const vault = vaults[0]
|
|
||||||
flushOrders(chainId, owner, 0, vault)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
function handleOrderStatus(chainId, vault, orderIndex, status) {
|
|
||||||
const s = useStore()
|
|
||||||
if( s.chainId !== chainId )
|
|
||||||
return
|
|
||||||
// message 'o' is a single order status
|
|
||||||
const parsed = parseElaboratedOrderStatus(chainId, status);
|
|
||||||
console.log('o', chainId, vault, orderIndex, status, parsed)
|
|
||||||
if( !(vault in s.orders) )
|
|
||||||
s.orders[vault] = {}
|
|
||||||
s.orders[vault][orderIndex] = parsed
|
|
||||||
}
|
}
|
||||||
|
export const socket = io(import.meta.env.VITE_WS_URL || undefined, socketOptions)
|
||||||
socket.on('os', (chainId, vault, orders) => {
|
|
||||||
// message 'os' has multiple order statuses
|
|
||||||
console.log('os', orders)
|
|
||||||
for( const [orderIndex, status] of orders )
|
|
||||||
handleOrderStatus(chainId, vault, orderIndex, status)
|
|
||||||
})
|
|
||||||
|
|
||||||
socket.on( 'o', handleOrderStatus)
|
|
||||||
|
|
||||||
socket.on( 'of', (chainId, vault, orderIndex, filled)=>{
|
|
||||||
const s = useStore()
|
|
||||||
if( s.chainId !== chainId )
|
|
||||||
return
|
|
||||||
console.log('of', chainId, vault, orderIndex, filled)
|
|
||||||
if( !(vault in s.orders) ) {
|
|
||||||
console.log('warning: got fill on an order in an unknown vault')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if( !(orderIndex in s.orders[vault]) ) {
|
|
||||||
console.log(`warning: orderIndex ${orderIndex} missing from vault ${vault}`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const status = s.orders[vault][orderIndex]
|
|
||||||
console.log('apply fills', status, filled)
|
|
||||||
|
|
||||||
let orderIn = 0n
|
|
||||||
let orderOut = 0n
|
|
||||||
for (const i in filled) {
|
|
||||||
const ts = status.trancheStatus[i]
|
|
||||||
let filledIn = 0n
|
|
||||||
let filledOut = 0n
|
|
||||||
const [activationTime, fills] = filled[i];
|
|
||||||
const numOld = ts.fills.length;
|
|
||||||
for (let i=0; i<fills.length; i++) {
|
|
||||||
const fill = fills[i]
|
|
||||||
let [tx, time, fi, fo, fee] = fill
|
|
||||||
fi = BigInt(fi)
|
|
||||||
fo = BigInt(fo)
|
|
||||||
fee = BigInt(fee)
|
|
||||||
filledIn += fi
|
|
||||||
filledOut += fo
|
|
||||||
if (i>=numOld) {
|
|
||||||
// new fill detected
|
|
||||||
const f = {tx, time, filledIn: fi, filledOut: fo, fee, filled: status.order.amountIsInput ? fi : fo};
|
|
||||||
console.log('new fill', f)
|
|
||||||
notifyFillEvent(chainId, status, i, f).catch((e)=>console.log('fill notification error', e))
|
|
||||||
ts.fills.push(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ts.filledIn = filledIn
|
|
||||||
ts.filledOut = filledOut
|
|
||||||
ts.activationTime = activationTime
|
|
||||||
orderIn += filledIn
|
|
||||||
orderOut += filledOut
|
|
||||||
}
|
|
||||||
status.filledIn = orderIn
|
|
||||||
status.filledOut = orderOut
|
|
||||||
status.filled = status.order.amountIsInput ? orderIn : orderOut
|
|
||||||
|
|
||||||
console.log('apply fills completed', status)
|
|
||||||
})
|
|
||||||
|
|||||||
165
src/socketInit.js
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import {socket} from "@/socket.js";
|
||||||
|
import {useStore} from "@/store/store.js";
|
||||||
|
import {flushWalletTransactions} from "@/blockchain/wallet.js";
|
||||||
|
import {parseElaboratedOrderStatus} from "@/blockchain/orderlib.js";
|
||||||
|
import {DataFeed} from "./charts/datafeed";
|
||||||
|
import {notifyFillEvent} from "@/notify.js";
|
||||||
|
import {refreshOHLCSubs} from "@/blockchain/ohlcs.js";
|
||||||
|
|
||||||
|
socket.on('connect', () => {
|
||||||
|
console.log('ws connected')
|
||||||
|
const s = useStore();
|
||||||
|
s.connected = true
|
||||||
|
if (s.chainId && s.account)
|
||||||
|
socket.emit('address', s.chainId, s.account)
|
||||||
|
refreshOHLCSubs()
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('disconnect', () => {
|
||||||
|
console.log('ws disconnected')
|
||||||
|
useStore().connected = false
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('connect_error', (err) => {
|
||||||
|
console.log('ws connect error', err)
|
||||||
|
useStore().connected = false
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('error', (err) => {
|
||||||
|
console.log('ws error', err)
|
||||||
|
useStore().connected = false
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('approvedRegion', (approved) => {
|
||||||
|
console.log('approved region', approved)
|
||||||
|
useStore().regionApproved = approved
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('approvedWallet', (approved) => {
|
||||||
|
console.log('approved wallet', approved)
|
||||||
|
useStore().walletApproved = approved
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('p', async (chainId, pool, price) => {
|
||||||
|
console.log('pool price from message', chainId, pool, price)
|
||||||
|
const s = useStore()
|
||||||
|
if( s.chainId !== chainId )
|
||||||
|
return
|
||||||
|
s.poolPrices[[chainId,pool]] = price
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('ohlc', async (chainId, poolPeriod, ohlcs) => {
|
||||||
|
// console.log('pool bars', poolPeriod, ohlcs)
|
||||||
|
if (ohlcs && ohlcs.length) {
|
||||||
|
const split = poolPeriod.indexOf('|')
|
||||||
|
const pool = poolPeriod.slice(0,split)
|
||||||
|
useStore().poolPrices[[chainId, pool]] = parseFloat(ohlcs[ohlcs.length - 1][4]) // closing price
|
||||||
|
}
|
||||||
|
DataFeed.poolCallback(chainId, poolPeriod, ohlcs)
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('vb', async (chainId, vault, balances) => {
|
||||||
|
const s = useStore()
|
||||||
|
if( s.chainId !== chainId )
|
||||||
|
return
|
||||||
|
console.log('vb', vault, balances)
|
||||||
|
s.vaultBalances[vault] = JSON.parse(balances)
|
||||||
|
console.log('vault balances', vault, s.vaultBalances[vault])
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('vaults', (chainId, owner, vaults)=>{
|
||||||
|
const s = useStore()
|
||||||
|
console.log('vaults', chainId, owner, vaults)
|
||||||
|
if( s.chainId !== chainId || s.account !== owner )
|
||||||
|
return
|
||||||
|
if( vaults.length > s.vaults.length ) {
|
||||||
|
s.vaults = vaults
|
||||||
|
if( vaults.length ) {
|
||||||
|
const vault = vaults[0]
|
||||||
|
flushWalletTransactions(chainId, owner, 0, vault)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('mark.usd', (chainId, token, value)=>{
|
||||||
|
const s = useStore()
|
||||||
|
s.markPrices[`${chainId}|${token}`] = Number(value)
|
||||||
|
// console.log('mark.usd', token, value)
|
||||||
|
})
|
||||||
|
|
||||||
|
function handleOrderStatus(chainId, vault, orderIndex, status) {
|
||||||
|
const s = useStore()
|
||||||
|
if( s.chainId !== chainId )
|
||||||
|
return
|
||||||
|
// message 'o' is a single order status
|
||||||
|
const parsed = parseElaboratedOrderStatus(chainId, status);
|
||||||
|
// console.log('o', chainId, vault, orderIndex, status, parsed)
|
||||||
|
if( !(vault in s.orders) )
|
||||||
|
s.orders[vault] = {}
|
||||||
|
s.orders[vault][orderIndex] = parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.on('os', (chainId, vault, orders) => {
|
||||||
|
// message 'os' has multiple order statuses
|
||||||
|
console.log('os', orders)
|
||||||
|
for( const [orderIndex, status] of orders )
|
||||||
|
handleOrderStatus(chainId, vault, orderIndex, status)
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on( 'o', handleOrderStatus)
|
||||||
|
|
||||||
|
socket.on( 'of', (chainId, vault, orderIndex, filled)=>{
|
||||||
|
const s = useStore()
|
||||||
|
if( s.chainId !== chainId )
|
||||||
|
return
|
||||||
|
console.log('of', chainId, vault, orderIndex, filled)
|
||||||
|
if( !(vault in s.orders) ) {
|
||||||
|
console.log('warning: got fill on an order in an unknown vault')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if( !(orderIndex in s.orders[vault]) ) {
|
||||||
|
console.log(`warning: orderIndex ${orderIndex} missing from vault ${vault}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = s.orders[vault][orderIndex]
|
||||||
|
console.log('apply fills', status, filled)
|
||||||
|
|
||||||
|
let orderIn = 0n
|
||||||
|
let orderOut = 0n
|
||||||
|
for (const i in filled) {
|
||||||
|
const ts = status.trancheStatus[i]
|
||||||
|
let filledIn = 0n
|
||||||
|
let filledOut = 0n
|
||||||
|
const [activationTime, fills] = filled[i];
|
||||||
|
const numOld = ts.fills.length;
|
||||||
|
for (let i=0; i<fills.length; i++) {
|
||||||
|
const fill = fills[i]
|
||||||
|
let [tx, time, fi, fo, fee] = fill
|
||||||
|
fi = BigInt(fi)
|
||||||
|
fo = BigInt(fo)
|
||||||
|
fee = BigInt(fee)
|
||||||
|
filledIn += fi
|
||||||
|
filledOut += fo
|
||||||
|
if (i>=numOld) {
|
||||||
|
// new fill detected
|
||||||
|
const f = {tx, time, filledIn: fi, filledOut: fo, fee, filled: status.order.amountIsInput ? fi : fo};
|
||||||
|
console.log('new fill', f)
|
||||||
|
notifyFillEvent(chainId, status, i, f).catch((e)=>console.log('fill notification error', e))
|
||||||
|
ts.fills.push(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ts.filledIn = filledIn
|
||||||
|
ts.filledOut = filledOut
|
||||||
|
ts.activationTime = activationTime
|
||||||
|
orderIn += filledIn
|
||||||
|
orderOut += filledOut
|
||||||
|
}
|
||||||
|
status.filledIn = orderIn
|
||||||
|
status.filledOut = orderOut
|
||||||
|
status.filled = status.order.amountIsInput ? orderIn : orderOut
|
||||||
|
|
||||||
|
console.log('apply fills completed', status)
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('initialized socketio')
|
||||||
@@ -38,7 +38,6 @@ const REQUIRE_APPROVAL = import.meta.env.VITE_REQUIRE_APPROVAL !== 'NO';
|
|||||||
export const useStore = defineStore('app', ()=> {
|
export const useStore = defineStore('app', ()=> {
|
||||||
const clock = ref(timestamp()) // the clock ticks infrequently enough to be mostly stable for user display
|
const clock = ref(timestamp()) // the clock ticks infrequently enough to be mostly stable for user display
|
||||||
setInterval(()=>clock.value=timestamp(), 10*1000) // 10 secs
|
setInterval(()=>clock.value=timestamp(), 10*1000) // 10 secs
|
||||||
const timeZone = ref('Etc/UTC')
|
|
||||||
|
|
||||||
const nav = ref(false) // controls opening navigation drawer
|
const nav = ref(false) // controls opening navigation drawer
|
||||||
const theme = ref('dark')
|
const theme = ref('dark')
|
||||||
@@ -102,11 +101,14 @@ export const useStore = defineStore('app', ()=> {
|
|||||||
const orders = ref({}) // indexed by vault value is another dictionary with orderIndex as key and order status values
|
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 vault = computed(() => vaults.value.length === 0 ? null : vaults.value[0] )
|
||||||
|
const creatingVault = ref(false) // used when a vault is first created but not yet responsive via metamask. If vault is not null, but creatingVault is true, then the vault is not yet ready for interaction.
|
||||||
const upgrade = ref(null)
|
const upgrade = ref(null)
|
||||||
const version = computed( () => vaultVersions.value.length === 0 ? 0 : vaultVersions.value[0] )
|
const version = computed( () => vaultVersions.value.length === 0 ? 0 : vaultVersions.value[0] )
|
||||||
const balances = computed( () => vault.value === null ? {} : vaultBalances.value[vault.value] || {} )
|
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 vaultOrders = computed(()=> vault.value === null || (!vault.value in orders.value) ? {} : orders.value[vault.value] ? orders.value[vault.value] : [] )
|
||||||
const tokens = computed(getTokens)
|
const tokens = computed(getTokens)
|
||||||
|
const markPrices = ref({}) // key: `${chainId}|${tokenAddr}` value: USD value
|
||||||
|
function markPrice(token) { return markPrices.value[`${chainId.value}|${token}`] }
|
||||||
const factory = computed(() => !chain.value ? null : chain.value.factory)
|
const factory = computed(() => !chain.value ? null : chain.value.factory)
|
||||||
const helper = computed(() => {console.log('chain helper', chain.value); return !chain.value ? null : chain.value.helper})
|
const helper = computed(() => {console.log('chain helper', chain.value); return !chain.value ? null : chain.value.helper})
|
||||||
const mockenv = computed(() => !chain.value ? null : chain.value.mockenv)
|
const mockenv = computed(() => !chain.value ? null : chain.value.mockenv)
|
||||||
@@ -133,14 +135,21 @@ export const useStore = defineStore('app', ()=> {
|
|||||||
this.extraTokens = extras
|
this.extraTokens = extras
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getBalance(tokenAddr) {
|
||||||
|
const found = this.balances[tokenAddr]
|
||||||
|
return found === undefined ? 0 : found
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
connected,
|
connected,
|
||||||
nav, chainId, chainInfo, chain, provider, providerRef, vaultInitCodeHash, account, vaults, vaultVersions,
|
nav, chainId, chainInfo, chain, provider, providerRef, vaultInitCodeHash, account, vaults, vaultVersions,
|
||||||
transactionSenders, errors, extraTokens, poolPrices, vaultBalances, orders, vault, version, upgrade, vaultOrders,
|
transactionSenders, errors, extraTokens, poolPrices, vaultBalances, orders, vault, version, upgrade, vaultOrders,
|
||||||
tokens, factory, helper, theme,
|
tokens, factory, helper, theme,
|
||||||
mockenv, mockCoins,
|
mockenv, mockCoins,
|
||||||
removeTransactionSender, error, closeError, addToken, clock, timeZone, balances,
|
removeTransactionSender, error, closeError, addToken, clock, balances,
|
||||||
approved, regionApproved, walletApproved,
|
approved, regionApproved, walletApproved,
|
||||||
|
getBalance, creatingVault,
|
||||||
|
markPrices, markPrice,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -206,8 +215,12 @@ export const usePrefStore = defineStore({
|
|||||||
state: ()=> {
|
state: ()=> {
|
||||||
// user preferences
|
// user preferences
|
||||||
const inverted = ref({})
|
const inverted = ref({})
|
||||||
|
const hints = ref({})
|
||||||
|
const newbie = ref(true)
|
||||||
const acceptedTos = ref('NO TOS ACCEPTED')
|
const acceptedTos = ref('NO TOS ACCEPTED')
|
||||||
return {inverted, acceptedTos,}
|
const selectedTicker = ref(null)
|
||||||
|
const selectedTimeframe = ref(null)
|
||||||
|
const timezone = ref('Etc/UTC')
|
||||||
|
return {inverted, acceptedTos, selectedTicker, selectedTimeframe, timezone, newbie, hints, }
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// these must also be set in vuetify.js for the "theme"
|
// these must also be set in vuetify.js for the "theme"
|
||||||
|
// see src/plugins/vuetify.js esp. makeColors()
|
||||||
@use 'sass:color';
|
@use 'sass:color';
|
||||||
|
|
||||||
// OFFICIAL DEXORDER PALETTE
|
// OFFICIAL DEXORDER PALETTE
|
||||||
|
|||||||
17
src/track.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
export let tracking_enabled = window.gtag !== undefined
|
||||||
|
|
||||||
|
if(tracking_enabled) {
|
||||||
|
// console.log('gtag', tracking_enabled)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log('tracking disabled')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function track(...args) {
|
||||||
|
if (tracking_enabled) {
|
||||||
|
try {
|
||||||
|
window.gtag('event', ...args)
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,8 +16,14 @@ const versionPromise = fetch('/contract/version.json').then(_json('version.json'
|
|||||||
const metadataPromise = fetch('/metadata.json').then(_json('metadata.json'))
|
const metadataPromise = fetch('/metadata.json').then(_json('metadata.json'))
|
||||||
|
|
||||||
export const version = await versionPromise
|
export const version = await versionPromise
|
||||||
console.log('version', version)
|
// console.log('version', version)
|
||||||
export const metadata = await metadataPromise
|
export const metadata = await metadataPromise
|
||||||
console.log('metadata', metadata)
|
// console.log('metadata', metadata)
|
||||||
|
|
||||||
|
export function dexorderAddress(chainId) { return version['chainInfo'][chainId]['dexorder'] }
|
||||||
|
export function factoryAddress(chainId) { return version['chainInfo'][chainId]['factory'] }
|
||||||
|
export function helperAddress(chainId) { return version['chainInfo'][chainId]['helper'] }
|
||||||
|
export function vaultInitCodeHash(chainId) { return version['chainInfo'][chainId]['vaultInitCodeHash'] }
|
||||||
|
|
||||||
|
// maps [chainId][addr] to pool or token metadata
|
||||||
export const metadataMap = buildMetadataMap(metadata)
|
export const metadataMap = buildMetadataMap(metadata)
|
||||||
|
|||||||
2
theme.js
@@ -24,7 +24,7 @@ export const darkMiddleShadeIndex = 9
|
|||||||
export const numShades = 20 // if you change this, see vuetify.js colors that hardcode indexes
|
export const numShades = 20 // if you change this, see vuetify.js colors that hardcode indexes
|
||||||
|
|
||||||
// these parameters are expressed in terms of numShades:
|
// these parameters are expressed in terms of numShades:
|
||||||
export const pageShade = 2
|
export const surfaceShade = 3
|
||||||
export const printContrast = 15;
|
export const printContrast = 15;
|
||||||
// vuetify darken. values are added/substracted from the middleShadeIndex. use positive numbers here.
|
// vuetify darken. values are added/substracted from the middleShadeIndex. use positive numbers here.
|
||||||
export const colorContrast = 4;
|
export const colorContrast = 4;
|
||||||
|
|||||||