Compare commits

..

17 Commits

Author SHA1 Message Date
tim
bd37554b7d maxmind fix 2025-12-18 20:59:31 -04:00
tim
0a9b0cc631 maxmind fix 2025-12-18 20:47:59 -04:00
tim
eca99567d8 put app back on app.dexorder.com and corp site on dexorder.com with www redirecting to apex 2025-05-19 15:19:20 -04:00
tim
c95c26afd7 yarn upgrade 2025-05-06 22:27:38 -04:00
tim
008b6793d1 redis pub/sub fix 2025-05-06 22:27:14 -04:00
tim
7f4c301491 dotcom 2025-05-06 13:56:05 -04:00
tim
49dbd7f619 DEXORDER_BIND 2025-04-26 17:50:05 -04:00
tim
fbdb9703ea short share urls 2025-04-23 12:55:49 -04:00
tim
a560383ce0 share landing page touchup 2025-04-22 17:32:19 -04:00
tim
85b2e2dca7 share landing page bugfixes 2025-04-22 17:21:11 -04:00
tim
ff0d71054b order sharing 2025-04-22 16:15:14 -04:00
tim
0b29539e0a USD marks 2025-03-29 15:27:13 -04:00
tim
1a3638a087 excessive vault request squelching 2025-03-28 20:05:19 -04:00
tim
bd1fb627f2 US/PR unblocked 2025-03-26 17:18:15 -04:00
tim
bf31648a57 dead code cleanup 2025-02-24 19:12:48 -04:00
tim
5af7422b9d db records TOS acceptance 2025-01-30 12:25:14 -04:00
tim
2f5a626e5c approvals; vault creation by backend 2025-01-16 20:16:31 -04:00
18 changed files with 2151 additions and 363 deletions

View File

@@ -1,5 +1,6 @@
#DEXORDER_BIND=0.0.0.0
DEXORDER_PORT=3001
DEXORDER_CORS=http://localhost:3000
DEXORDER_APP_URL=http://localhost:3000
DEXORDER_DB_URL=postgresql://dexorder:redroxed@localhost:5432/dexorder
DEXORDER_REDIS_URL=redis://localhost:6379
@@ -11,11 +12,17 @@ DEXORDER_RPC_URL_42161=http://localhost:8545
# Mockchain
DEXORDER_DEPLOYMENT_31337=latest
DEXORDER_RPC_URL_31337=http://localhost:8545
# dev account #2
DEXORDER_ACCOUNTS_31337=0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a
# Devchain
DEXORDER_DEPLOYMENT_1337=latest
DEXORDER_RPC_URL_1337=http://localhost:8545
# dev account #2
DEXORDER_ACCOUNTS_1337=0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a
# use this in the URL to bypass IP country check for development / debug
# http://localhost:3000/...?approval=...
DEXORDER_REGION_APPROVAL=6ehWWH98diqv39gWZcPo
DEXORDER_SNAPSHOT_S3_BUCKET_NAME=snapshot
DEXORDER_SNAPSHOT_S3_ACCESS_KEY_ID=5c73e3059596e6726d22e7aa5d2d23d6
DEXORDER_SNAPSHOT_S3_SECRET_ACCESS_KEY=a543189cea7d87c891e5abef7fabd7c1759b5063a667b3f29217a6a496a7342a
DEXORDER_SNAPSHOT_S3_ENDPOINT=https://3d7004962a10203cb8fecacc6a8ff502.r2.cloudflarestorage.com
DEXORDER_SNAPSHOT_BASE_URL=https://3d7004962a10203cb8fecacc6a8ff502.r2.cloudflarestorage.com/snapshot

View File

@@ -1,9 +1,11 @@
import {countryForIP} from "./maxmind.js";
import {clientIP} from "./misc.js";
import {sql} from "./db.js";
const bannedCountries = [
'US', // FFS
'PR', // FFS (different country?!)
// 'US', // FFS
// 'PR', // FFS (different country?!)
// OFAC country bans
'CU', // Cuba
@@ -15,6 +17,13 @@ const bannedCountries = [
]
export async function approveTOS(socket, time, version, callback) {
const ipAddress = clientIP(socket)
await sql('insert into tosacceptance (ipaddr, time, version) values ($1,$2,$3)', ipAddress, time, version)
callback(true)
}
export function approveWallet(walletAddress) {
// todo OFAC lookup
return true
@@ -22,25 +31,26 @@ export function approveWallet(walletAddress) {
function approveIP(ipAddress) {
let country
try {
const country = countryForIP(ipAddress)
if (!country) return false
const approved = !bannedCountries.includes(country)
if (!approved)
// todo log ban & report
console.warn(`IP ${ipAddress} from ${country} is banned`)
return approved
country = countryForIP(ipAddress)
}
catch (e) {
console.warn(`IP lookup failed for ${ipAddress}: ${e.message}`)
return false
}
if (!country) return false
const approved = !bannedCountries.includes(country)
console.debug(`IP ${ipAddress} from ${country} is ${approved ? 'approved' : 'rejected'}`)
return approved
}
export function approveRegion(socket, bypass) {
const ipAddress = socket.handshake.address
const approved = bypass === '6ehWWH98diqv39gWZcPo' || approveIP(ipAddress)
const ipAddress = clientIP(socket)
const debug = bypass === process.env.DEXORDER_REGION_APPROVAL;
const approved = debug || approveIP(ipAddress)
socket.emit('approvedRegion', approved)
if(debug)
console.info(`approved admin at ${ipAddress}`)
}

View File

@@ -1,12 +1,19 @@
import {createClient} from "redis";
export const redis = createClient({
url: process.env.DEXORDER_REDIS_URL || 'redis://localhost:6379',
returnBuffers: false,
})
redis.on('error', (err) => console.log('Redis Client Error', err));
await redis.connect();
async function createRedisClient() {
const client = createClient({
url: process.env.DEXORDER_REDIS_URL || 'redis://localhost:6379',
returnBuffers: false,
})
client.on('error', (err) => console.log('Redis Client Error', err));
await client.connect();
return client
}
export const redis = await createRedisClient()
export const redisSubscriber = await createRedisClient()
export class CacheSet {
@@ -37,6 +44,10 @@ export class CacheDict {
async contains(chain, key) {
return await redis.hExists(`${chain}|${this.series}`, key)
}
async items(chain) {
return Object.entries(await redis.hGetAll(`${chain}|${this.series}`)).map(([k, v]) => [k, v === null ? null : '' + v])
}
}
@@ -83,3 +94,4 @@ export const vaultOpenOrders = new CacheDict('voo')
export const vaultRecentlyClosedOrders = new CacheDict('vrco')
export const orderFilled = new CacheDict('of')
export const ohlcs = new CacheDict('ohlc')
export const marks = new CacheDict('mark.usd')

View File

@@ -1,4 +1,19 @@
import fs from "fs";
import {marks} from "./cache.js";
export const chainInfo = JSON.parse(fs.readFileSync('../contract/version.json')).chainInfo
console.log('chainInfo', chainInfo)
export async function joinChain( socket, chainId ) {
try {
if (socket.chainId)
socket.leave(socket.chainId)
socket.join(chainId)
socket.chainId = chainId
const items = await marks.items(chainId);
for (let [token, mark] of items)
socket.emit('mark.usd', chainId, token, mark)
} catch (e) {
console.error('joinChain', e)
}
}

9
db.js
View File

@@ -22,7 +22,12 @@ export async function withDb(cb) {
}
export async function sql(query) {
return await withDb(async (db)=>await db.query(query) )
export function dbAddr(addr) {
// format an 0x-style address into postgres bytes
return '\\' + addr.slice(1)
}
export async function sql(query, ...params) {
return await withDb(async (db)=>await db.query(query, params) )
}

View File

@@ -6,6 +6,16 @@ import {metadata} from "./metadata.js";
export async function gib( chainId, owner, vault, tokenAmounts ) {
try {
return await doGib( chainId, owner, vault, tokenAmounts )
}
catch (e) {
console.error('gib failed', e)
}
}
export async function doGib( chainId, owner, vault, tokenAmounts ) {
if (!owner || !vault) return
if (chainId === 421614) {
// Arbitrum-Sepolia

11
init.js Normal file
View File

@@ -0,0 +1,11 @@
export function initLog(app) {
process.on('uncaughtException', (err) => {
console.error('Uncaught Exception:', err);
process.exit(1); // Exit with code 1
});
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
process.exit(1); // Exit with code 1
});
}

45
io.js
View File

@@ -1,15 +1,42 @@
import {createServer} from "http";
import {Server} from "socket.io";
import { createAdapter } from "@socket.io/redis-adapter";
import {redis} from "./cache.js";
import {redis, redisSubscriber} from "./cache.js";
import {fileURLToPath} from "url";
import path from "path";
import express from "express";
import {engine} from "express-handlebars";
import {initSnapShare} from "./snapshare.js";
import cors from "cors";
const options = {
const socketIoOptions = {
}
if( process.env.DEXORDER_CORS )
options['cors'] = {origin:process.env.DEXORDER_CORS}
export const httpServer = createServer()
export const io = new Server(httpServer, options)
const pubClient = redis.duplicate();
await pubClient.connect()
const adapter = createAdapter(pubClient, redis, {/*key:'socket.io'*/})
if( process.env.DEXORDER_APP_URL )
socketIoOptions['cors'] = {origin:process.env.DEXORDER_APP_URL}
// Setup Express
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const app = express();
app.engine('handlebars', engine({
defaultLayout: false,
}
));
app.set('view engine', 'handlebars');
app.set('views', path.join(__dirname, 'views')); // Set the views directory
app.use(express.static(path.join(__dirname, 'public')));
app.use(cors())
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something went wrong!');
});
initSnapShare(app)
export const httpServer = createServer(app)
export const io = new Server(httpServer, socketIoOptions)
const adapter = createAdapter(redis, redisSubscriber, {/*key:'socket.io'*/})
io.adapter(adapter)

39
main.js
View File

@@ -1,35 +1,14 @@
import 'dotenv/config'
import {lookupToken} from "./token.js";
import {httpServer, io} from "./io.js";
import {ensureVault, loginAddress} from "./vault.js";
import {subOHLCs, subPools, unsubOHLCs, unsubPools} from "./pool.js";
import {gib} from "./faucet.js";
import {approveRegion} from "./approval.js";
import {httpServer} from "./io.js";
import {initIO} from "./route.js";
import {initLog} from "./init.js";
// setup socket.io
io.on("connection", (socket) => {
socket.on('lookupToken', (chainId, address, callback) => {
lookupToken(chainId, address).then((result)=>callback(result)).catch(()=>callback(null))
})
socket.on('address', (chainId, address) => loginAddress(socket, chainId, address) )
socket.on('subPools', (chainId, addresses) => subPools(socket, chainId, addresses) )
socket.on('unsubPools', (chainId, addresses) => unsubPools(socket, chainId, addresses) )
socket.on('subOHLCs', async (chainId, poolPeriods) => await subOHLCs(socket, chainId, poolPeriods) )
socket.on('unsubOHLCs', (chainId, poolPeriods) => unsubOHLCs(socket, chainId, poolPeriods) )
socket.on('ensureVault', (chainId,owner,num) => ensureVault(socket, chainId, owner, num) )
socket.on('gib', async (chainId, owner, vault, tokenAmounts) => await gib(chainId, owner, vault, tokenAmounts))
socket.on('approveRegion', (bypass) => approveRegion(socket, bypass))
socket.join('public')
});
// io.on("disconnection", (socket)=>{
// socket.leave('public') // todo isn't this automatic?
// todo unsub pools etc?
// })
initLog();
initIO();
const port = parseInt(process.env.DEXORDER_PORT) || 3001;
httpServer.listen(port)
console.log('Started server on port '+port)
const bind = process.env.DEXORDER_BIND || 'localhost';
httpServer.listen(port, bind, ()=>{
console.log(`Started server on ${bind}:${port}`)
})

View File

@@ -3,6 +3,8 @@ import path from 'path';
import {extract} from "tar";
import {Reader} from '@maxmind/geoip2-node';
const ENABLE_MAXMIND=false
let ipdb = null
function setDbFile(file) {
const dbBuffer = fs.readFileSync(file);
@@ -112,6 +114,11 @@ async function downloadAndExtractMaxmindData(url, outputDir, tempDir, username,
* @return {Promise<void>} Resolves when the data check and optional update process is completed.
*/
async function checkAndUpdateMaxmindData(outputDir, tempDir, url, username, password) {
if (username === '' || password === '') {
console.log('No MaxMind credentials provided. Skipping MaxMind database update.');
return
}
console.log('Checking for MaxMind database updates.');
// Ensure the output directory exists
@@ -146,21 +153,25 @@ async function checkAndUpdateMaxmindData(outputDir, tempDir, url, username, pass
const url = 'https://download.maxmind.com/geoip/databases/GeoIP2-Country/download?suffix=tar.gz';
const outputDirectory = './maxmind';
const username = '1102431';
const password = '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';
// const username = process.env.MAXMIND_ACCOUNT_NUMBER;
// const password = process.env.MAXMIND_LICENSE_KEY;
// const username = '1102431';
// const password = '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';
const username = process.env.MAXMIND_ACCOUNT_NUMBER;
const password = process.env.MAXMIND_LICENSE_KEY;
await checkAndUpdateMaxmindData(outputDirectory, '/tmp', url, username, password);
if( ENABLE_MAXMIND ) {
await checkAndUpdateMaxmindData(outputDirectory, '/tmp', url, username, password);
setInterval(async () => {
try {
await checkAndUpdateMaxmindData(outputDirectory, '/tmp', url, username, password);
} catch (error) {
console.error('Error during MaxMind database update:', error);
}
}, 24 * 60 * 60 * 1000 + 1000); // 1 day + 1 second
setInterval(async () => {
try {
await checkAndUpdateMaxmindData(outputDirectory, '/tmp', url, username, password);
} catch (error) {
console.error('Error during MaxMind database update:', error);
}
}, 24 * 60 * 60 * 1000 + 1000); // 1 day + 1 second
}
export function countryForIP(ipAddress) {
@@ -176,5 +187,3 @@ export function countryForIP(ipAddress) {
}
}
const ip = '64.178.217.35'
console.log(`ip: ${ip}`, countryForIP(ip))

View File

@@ -2,3 +2,8 @@ import util from "util";
import fs from "fs";
export const readFile = (fileName) => util.promisify(fs.readFile)(fileName, 'utf8');
export function clientIP(socket) {
// X-Forwarded-For
return socket.handshake.headers['x-forwarded-for']?.split(',')[0] || socket.handshake.address;
}

View File

@@ -11,10 +11,15 @@
"author": "",
"license": "unlicensed",
"dependencies": {
"@aws-sdk/client-s3": "^3.787.0",
"@isaacs/ttlcache": "^1.4.1",
"@maxmind/geoip2-node": "^5.0.0",
"@socket.io/redis-adapter": "^8.2.1",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"ethers": "^6.7.1",
"express": "^5.1.0",
"express-handlebars": "^8.0.2",
"pg": "^8.11.3",
"redis": "^4.6.10",
"socket.io": "^4.7.2",

27
pool.js
View File

@@ -1,6 +1,6 @@
import {ohlcs, prices} from "./cache.js";
export function subPools( socket, chainId, addresses) {
export function subPools( socket, chainId, addresses ) {
for(const address of addresses) {
const room = `${chainId}|${address}`;
socket.join(room)
@@ -19,17 +19,22 @@ export function unsubPools( socket, chainId, addresses ) {
export async function subOHLCs( socket, chainId, poolPeriods) {
console.log('subOHLCs', chainId, poolPeriods)
for(const key of poolPeriods) {
const room = `${chainId}|${key}`;
socket.join(room)
let ohlc = await ohlcs.get(chainId,key);
console.log('got ohlc', ohlc)
if (typeof(ohlc)==='string') {
ohlc = JSON.parse(ohlc)
try {
console.log('subOHLCs', chainId, poolPeriods)
for (const key of poolPeriods) {
const room = `${chainId}|${key}`;
socket.join(room)
let ohlc = await ohlcs.get(chainId, key);
console.log('got ohlc', ohlc)
if (typeof (ohlc) === 'string') {
ohlc = JSON.parse(ohlc)
}
socket.emit('ohlc', chainId, key, ohlc)
console.log('joined room', room)
}
socket.emit('ohlc', chainId, key, ohlc)
console.log('joined room', room)
}
catch (e) {
console.error('subOHLCs', e)
}
}

31
route.js Normal file
View File

@@ -0,0 +1,31 @@
import {io} from "./io.js";
import {lookupToken} from "./token.js";
import {requestVault, loginAddress} from "./vault.js";
import {subOHLCs, subPools, unsubOHLCs, unsubPools} from "./pool.js";
import {gib} from "./faucet.js";
import {approveRegion, approveTOS} from "./approval.js";
import {joinChain} from "./chain.js";
import {share, shared} from "./snapshare.js";
// Server route handling
export function initIO() {
io.on("connection", (socket) => {
socket.on('lookupToken', (chainId, address, respond) => {
lookupToken(chainId, address).then((result) => respond(result)).catch(() => respond(null))
})
socket.on('address', (chainId, address) => loginAddress(socket, chainId, address))
socket.on('subPools', (chainId, addresses) => subPools(socket, chainId, addresses))
socket.on('unsubPools', (chainId, addresses) => unsubPools(socket, chainId, addresses))
socket.on('subOHLCs', async (chainId, poolPeriods) => await subOHLCs(socket, chainId, poolPeriods))
socket.on('unsubOHLCs', (chainId, poolPeriods) => unsubOHLCs(socket, chainId, poolPeriods))
socket.on('ensureVault', (chainId, owner, num) => requestVault(socket, chainId, owner, num))
socket.on('gib', async (chainId, owner, vault, tokenAmounts) => await gib(chainId, owner, vault, tokenAmounts))
socket.on('approveTOS', (time, version, callback) => approveTOS(socket, time, version, callback))
socket.on('approveRegion', (bypass) => approveRegion(socket, bypass))
socket.on('chain', async (chainId) => await joinChain(socket, chainId))
socket.on('share', async (data, snapshot, respond) => await share(socket, data, snapshot, respond))
socket.on('shared', async (code, respond) => await shared(socket, code, respond))
socket.join('public')
});
}

114
snapshare.js Normal file
View File

@@ -0,0 +1,114 @@
import {PutObjectCommand, S3Client} from "@aws-sdk/client-s3";
import cors from 'cors'
import crypto from "crypto";
import {sql} from "./db.js";
import {decodeBase62} from "../web/src/common.js";
const APP_URL = process.env.DEXORDER_APP_URL;
const SNAPSHOT_URL = process.env.DEXORDER_SNAPSHOT_URL;
const S3_BUCKET = process.env.DEXORDER_SNAPSHOT_S3_BUCKET_NAME;
const S3_ACCESS_KEY_ID = process.env.DEXORDER_SNAPSHOT_S3_ACCESS_KEY_ID;
const S3_SECRET_ACCESS_KEY = process.env.DEXORDER_SNAPSHOT_S3_SECRET_ACCESS_KEY;
const S3_ENDPOINT = process.env.DEXORDER_SNAPSHOT_S3_ENDPOINT; // e.g., 'https://<ACCOUNT_ID>.r2.cloudflarestorage.com/'
const s3 = new S3Client({
region: "auto",
endpoint: S3_ENDPOINT,
credentials: {
accessKeyId: S3_ACCESS_KEY_ID,
secretAccessKey: S3_SECRET_ACCESS_KEY,
}
});
function imageFilename(code) {
return 'share_' + code + '.png';
}
async function saveSnapshot(code, snapshot) {
await s3.send(new PutObjectCommand({
Bucket: S3_BUCKET,
Key: imageFilename(code),
Body: snapshot,
ContentType: 'image/png',
ACL: 'public-read', // or private, depending on your needs
}))
}
export async function share(socket, data, snapshot, respond) {
try {
const result = await sql('insert into sharedata (data) values ($1) returning id', data)
if (result.rowCount !== 1) {
console.error('insertion of sharedata failed', result)
respond(null)
}
const code = encodeURIComponent(result.rows[0].id)
respond(code);
saveSnapshot(code, snapshot).catch(e => console.error('save snapshot error', e))
}
catch (e) {
console.error('share error', e)
respond(null)
}
}
export async function shared(socket, code, respond) {
try {
const id = decodeBase62(code)
const result = await sql('select data from sharedata where id = $1', id)
if (result.rowCount !== 1) {
console.error('could not find share data', code)
respond(null)
}
const data = result.rows[0].data
console.log('shared data', data)
respond(data)
}
catch (e) {
console.error('shared error', e)
respond(null)
}
}
export function initSnapShare(app) {
// this URL is called by the frontend to upload order data and a snapshot image for use on a share page
app.post('/sharecode', cors({origin: process.env.DEXORDER_APP_URL}),
(req, res) => {
const chunks = [];
req.on('data', chunk => chunks.push(chunk))
req.on('error', (err) => res.status(500).send('Error reading body'))
req.on('end', () => {
const filename = crypto.randomUUID() + '.png';
const body = Buffer.concat(chunks);
s3.send(new PutObjectCommand({
Bucket: S3_BUCKET,
Key: filename,
Body: body,
ContentType: 'image/png',
ACL: 'public-read', // or private, depending on your needs
})).then(sent => {
res.send(filename)
}).catch(err => {
console.log('upload error', err)
res.status(500).send('error')
});
});
});
// this link returns a "share page" that shows the snapshot of the trade setup then redirects
// to the order page with the trade data loaded from the URL
app.get('/share/:code', (req, res) => {
const code = req.params.code;
const data = {
imageUrl: SNAPSHOT_URL + '/' + imageFilename(code),
redirectUrl: APP_URL + '/shared/' + code
};
res.render('share', data);
});
}

View File

@@ -1,10 +1,11 @@
import {ethers} from "ethers";
import {getProvider, getSigner} from "./blockchain.js";
import {vaultBalances, vaults} from './cache.js';
import {chainInfo} from "./chain.js";
import {sendVaultOrders} from "./order.js";
import {newContract} from "./contract.js";
import {approveWallet} from "./approval.js";
import {sql} from "./db.js";
import {clientIP} from "./misc.js";
import TTLCache from "@isaacs/ttlcache";
export function vaultAddress(chainId, owner, num=0) {
@@ -48,8 +49,10 @@ async function sendVaultInfo(socket, chainId, owner) {
}
export async function loginAddress(socket, chainId, address) {
if( socket.user_room !== undefined)
if( socket.user_room !== undefined) {
socket.leave(socket.user_room)
console.log('left user room', socket.user_room)
}
if( address ) {
const approved = approveWallet(address)
socket.emit('approvedWallet', approved)
@@ -62,70 +65,17 @@ export async function loginAddress(socket, chainId, address) {
}
}
const ensuring = {}
const requestVaultCooldown = new TTLCache({ttl:5 * 1000, checkAgeOnGet: true})
export async function ensureVault(socket, chainId, owner, num) {
if (!approveWallet(owner))
export async function requestVault(socket, chainId, owner, num) {
const key = [chainId, owner, num].join('|')
if (requestVaultCooldown.has(key))
return
const key = [chainId, owner, num]
if( key in ensuring ) {
console.log('squelching ensureVault since one is in-progress')
return
}
ensuring[key] = true
console.log('ensureVault', chainId, owner, num)
if( chainId in chainInfo ) {
const address = vaultAddress(chainId, owner, num)
console.log('vault addr', address)
if (!await vaults.contains(chainId,address)) {
try {
const vault = await createVault(chainId, owner, num)
if (vault !== null) {
console.log('created vault', vault)
socket.emit('vaults', chainId, owner, [vault])
await emitBalances(socket, chainId, vault)
}
else
console.error('got null vault for chainId', chainId)
} catch {
}
}
else
console.log('ensureVault', owner, 'exists:', address)
}
await sendVaultInfo(socket, chainId, owner)
delete ensuring[key]
requestVaultCooldown.set(key, true)
const ipAddress = clientIP(socket)
const time = new Date().toISOString();
const query = `insert into vaultcreationrequest (chain, owner, num, time, ipaddr) values (${chainId}, '${owner}', ${num}, '${time}', '${ipAddress}') ON CONFLICT DO NOTHING`;
console.log('query:', query)
await sql(query)
}
async function createVault(chainId, owner, num) {
if (!approveWallet(owner))
return
const signer = getSigner(chainId);
const factory = chainInfo[chainId].factory;
console.log('createVault', chainId, owner, num, factory, chainInfo[chainId].vaultInitCodeHash )
const deployer = await newContract(factory, 'IVaultFactory', signer)
const vaultAddr = vaultAddress(chainId, owner, num)
console.log(' ==> vault addr', vaultAddr )
try {
const tx = await deployer['deployVault(address,uint8)'](owner, num) // must specify which deployVault() to call
console.log(`deploying vault for ${owner} #${num} with tx ${tx.hash}`)
const result = await tx.wait()
if (result.status !== 1) {
// noinspection ExceptionCaughtLocallyJS
throw Error(`Vault deployment reverted. tx ${tx.hash}`)
}
}
catch (e) {
const vault = await newContract(vaultAddr, 'IVault', getProvider(chainId))
try {
const ver = await vault.version()
console.log(`vault already deployed at ${vaultAddr} with version ${ver}`)
}
catch (e2) {
console.error('could not deploy vault:', e)
return null
}
}
return vaultAddr
}

19
views/share.handlebars Normal file
View File

@@ -0,0 +1,19 @@
<html lang="en/US">
<head>
<title>Dexorder Trade Setup</title>
<meta property="og:title" content="Dexorder Trade Setup">
<meta property="og:description" content="Trade this setup on Dexorder">
<meta property="og:image" content="{{{imageUrl}}}">
<meta property="og:url" content="https://app.dexorder.com/">
<meta property="og:type" content="website">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Dexorder Trade Setup">
<meta name="twitter:description" content="Trade this setup on Dexorder">
<meta name="twitter:image" content="{{{imageUrl}}}">
</head>
<body>
<script language="javascript">window.location='{{{redirectUrl}}}'</script>
<img src="{{imageUrl}}" alt="Chart" width="100%"/>
</body>
</html>

1990
yarn.lock

File diff suppressed because it is too large Load Diff