feat: implement net_pairs (#495)

This commit is contained in:
Matt
2023-08-28 02:50:39 +02:00
committed by GitHub
parent f7c6a1dd0f
commit 830c263155
128 changed files with 1949 additions and 317 deletions

View File

@@ -76,6 +76,20 @@ func cliApp() *cli.App {
swapdPortFlag,
},
},
{
Name: "pairs",
Aliases: []string{"p"},
Usage: "List active pairs",
Action: runPairs,
Flags: []cli.Flag{
swapdPortFlag,
&cli.Uint64Flag{
Name: flagSearchTime,
Usage: "Duration of time to search for, in seconds",
Value: defaultDiscoverSearchTimeSecs,
},
},
},
{
Name: "balances",
Aliases: []string{"b"},
@@ -536,6 +550,39 @@ func runPeers(ctx *cli.Context) error {
return nil
}
func runPairs(ctx *cli.Context) error {
searchTime := ctx.Uint64(flagSearchTime)
c := newClient(ctx)
resp, err := c.Pairs(searchTime)
if err != nil {
return err
}
for i, a := range resp.Pairs {
var verified string
if a.Verified {
verified = "Yes"
} else {
verified = "No"
}
fmt.Printf("Pair %d:\n", i+1)
fmt.Printf(" Name: %s\n", a.Token.Symbol)
fmt.Printf(" Token: %s\n", a.Token.Address)
fmt.Printf(" Verified: %s\n", verified)
fmt.Printf(" Offers: %d\n", a.Offers)
fmt.Printf(" Reported Liquidity XMR: %f\n", a.ReportedLiquidityXMR)
fmt.Println()
}
if len(resp.Pairs) == 0 {
fmt.Println("[none]")
}
return nil
}
func runBalances(ctx *cli.Context) error {
c := newClient(ctx)

View File

@@ -147,3 +147,13 @@ type AddressesResponse struct {
type PeersResponse struct {
Addrs []string `json:"addresses" validate:"dive,required"`
}
// PairsRequest ...
type PairsRequest struct {
SearchTime uint64 `json:"searchTime"` // in seconds
}
// PairsResponse ...
type PairsResponse struct {
Pairs []*types.Pair
}

44
common/types/pairs.go Normal file
View File

@@ -0,0 +1,44 @@
// Copyright 2023 The AthanorLabs/atomic-swap Authors
// SPDX-License-Identifier: LGPL-3.0-only
package types
import (
"github.com/cockroachdb/apd/v3"
"github.com/athanorlabs/atomic-swap/coins"
)
// Pair represents a pair (Such as ETH / XMR)
type Pair struct {
ReportedLiquidityXMR *apd.Decimal `json:"reportedLiquidityXmr" validate:"required"`
EthAsset EthAsset `json:"ethAsset" validate:"required"`
Token coins.ERC20TokenInfo `json:"token" validate:"required"`
Offers uint64 `json:"offers" validate:"required"`
Verified bool `json:"verified" valdate:"required"`
}
// NewPair creates and returns a Pair
func NewPair(EthAsset EthAsset) *Pair {
pair := &Pair{
ReportedLiquidityXMR: apd.New(0, 0),
EthAsset: EthAsset,
// Always set to false for now until the verified-list
// is implemented
Verified: false,
}
return pair
}
// AddOffer adds an offer to a pair
func (pair *Pair) AddOffer(o *Offer) error {
_, err := coins.DecimalCtx().Add(pair.ReportedLiquidityXMR, pair.ReportedLiquidityXMR, o.MaxAmount)
if err != nil {
return err
}
pair.Offers++
return nil
}

View File

@@ -4,6 +4,7 @@
package rpc
import (
"context"
"fmt"
"net/http"
"time"
@@ -18,6 +19,8 @@ import (
"github.com/athanorlabs/atomic-swap/common/types"
"github.com/athanorlabs/atomic-swap/net/message"
"github.com/athanorlabs/atomic-swap/protocol/swap"
ethcommon "github.com/ethereum/go-ethereum/common"
)
const defaultSearchTime = time.Second * 12
@@ -35,19 +38,31 @@ type Net interface {
// NetService is the RPC service prefixed by net_.
type NetService struct {
ctx context.Context
net Net
xmrtaker XMRTaker
xmrmaker XMRMaker
pb ProtocolBackend
sm swap.Manager
isBootnode bool
}
// NewNetService ...
func NewNetService(net Net, xmrtaker XMRTaker, xmrmaker XMRMaker, sm swap.Manager, isBootnode bool) *NetService {
func NewNetService(
ctx context.Context,
net Net,
xmrtaker XMRTaker,
xmrmaker XMRMaker,
pb ProtocolBackend,
sm swap.Manager,
isBootnode bool,
) *NetService {
return &NetService{
ctx: ctx,
net: net,
xmrtaker: xmrtaker,
xmrmaker: xmrmaker,
pb: pb,
sm: sm,
isBootnode: isBootnode,
}
@@ -73,6 +88,75 @@ func (s *NetService) Peers(_ *http.Request, _ *interface{}, resp *rpctypes.Peers
return nil
}
// Pairs returns all currently available pairs from offers of all peers
func (s *NetService) Pairs(_ *http.Request, req *rpctypes.PairsRequest, resp *rpctypes.PairsResponse) error {
if s.isBootnode {
return errUnsupportedForBootnode
}
peerIDs, err := s.discover(&rpctypes.DiscoverRequest{
Provides: "",
SearchTime: req.SearchTime,
})
if err != nil {
return err
}
pairs := make(map[ethcommon.Address]*types.Pair)
for _, p := range peerIDs {
msg, err := s.net.Query(p)
if err != nil {
log.Debugf("Failed to query peer ID %s", p)
continue
}
if len(msg.Offers) == 0 {
continue
}
for _, o := range msg.Offers {
address := o.EthAsset.Address()
pair, exists := pairs[address]
if !exists {
pair = types.NewPair(o.EthAsset)
if pair.EthAsset.IsToken() {
tokenInfo, tokenInfoErr := s.pb.ETHClient().ERC20Info(s.ctx, address)
if tokenInfoErr != nil {
log.Debugf("Error while reading token info: %s", tokenInfoErr)
continue
}
pair.Token = *tokenInfo
} else {
pair.Token.Name = "Ether"
pair.Token.Symbol = "ETH"
pair.Token.NumDecimals = 18
pair.Verified = true
}
pairs[address] = pair
}
err = pair.AddOffer(o)
if err != nil {
return err
}
}
}
pairsArray := make([]*types.Pair, 0, len(pairs))
for _, pair := range pairs {
if pair.EthAsset.IsETH() {
pairsArray = append([]*types.Pair{pair}, pairsArray...)
} else {
pairsArray = append(pairsArray, pair)
}
}
resp.Pairs = pairsArray
return nil
}
// QueryAll discovers peers who provide a certain coin and queries all of them for their current offers.
func (s *NetService) QueryAll(_ *http.Request, req *rpctypes.QueryAllRequest, resp *rpctypes.QueryAllResponse) error {
if s.isBootnode {

View File

@@ -107,7 +107,15 @@ func NewServer(cfg *Config) (*Server, error) {
case DatabaseNamespace:
err = rpcServer.RegisterService(NewDatabaseService(cfg.RecoveryDB), DatabaseNamespace)
case NetNamespace:
netService = NewNetService(cfg.Net, cfg.XMRTaker, cfg.XMRMaker, swapManager, isBootnode)
netService = NewNetService(
serverCtx,
cfg.Net,
cfg.XMRTaker,
cfg.XMRMaker,
cfg.ProtocolBackend,
swapManager,
isBootnode,
)
err = rpcServer.RegisterService(netService, NetNamespace)
case PersonalName:
err = rpcServer.RegisterService(NewPersonalService(serverCtx, cfg.XMRMaker, cfg.ProtocolBackend), PersonalName)

View File

@@ -4,6 +4,7 @@
package rpcclient
import (
"context"
"testing"
"github.com/cockroachdb/apd/v3"
@@ -15,7 +16,12 @@ import (
)
func TestNet_Discover(t *testing.T) {
ns := rpc.NewNetService(new(mockNet), new(mockXMRTaker), nil, mockSwapManager(t), false)
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(func() {
cancel()
})
ns := rpc.NewNetService(ctx, new(mockNet), new(mockXMRTaker), nil, new(mockProtocolBackend), mockSwapManager(t), false)
req := &rpctypes.DiscoverRequest{
Provides: "",
@@ -29,7 +35,12 @@ func TestNet_Discover(t *testing.T) {
}
func TestNet_Query(t *testing.T) {
ns := rpc.NewNetService(new(mockNet), new(mockXMRTaker), nil, mockSwapManager(t), false)
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(func() {
cancel()
})
ns := rpc.NewNetService(ctx, new(mockNet), new(mockXMRTaker), nil, new(mockProtocolBackend), mockSwapManager(t), false)
req := &rpctypes.QueryPeerRequest{
PeerID: "12D3KooWDqCzbjexHEa8Rut7bzxHFpRMZyDRW1L6TGkL1KY24JH5",
@@ -43,7 +54,12 @@ func TestNet_Query(t *testing.T) {
}
func TestNet_TakeOffer(t *testing.T) {
ns := rpc.NewNetService(new(mockNet), new(mockXMRTaker), nil, mockSwapManager(t), false)
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(func() {
cancel()
})
ns := rpc.NewNetService(ctx, new(mockNet), new(mockXMRTaker), nil, new(mockProtocolBackend), mockSwapManager(t), false)
req := &rpctypes.TakeOfferRequest{
PeerID: "12D3KooWDqCzbjexHEa8Rut7bzxHFpRMZyDRW1L6TGkL1KY24JH5",

27
rpcclient/pairs.go Normal file
View File

@@ -0,0 +1,27 @@
// Copyright 2023 The AthanorLabs/atomic-swap Authors
// SPDX-License-Identifier: LGPL-3.0-only
package rpcclient
import (
"github.com/athanorlabs/atomic-swap/common/rpctypes"
)
// Pairs calls net_pairs to get pairs from all offers.
func (c *Client) Pairs(searchTime uint64) (*rpctypes.PairsResponse, error) {
const (
method = "net_pairs"
)
req := &rpctypes.PairsRequest{
SearchTime: searchTime,
}
res := &rpctypes.PairsResponse{}
if err := c.post(method, req, res); err != nil {
return nil, err
}
return res, nil
}

View File

@@ -129,6 +129,54 @@ func (s *IntegrationTestSuite) TestXMRMaker_Discover() {
require.Equal(s.T(), 0, len(peerIDs))
}
func (s *IntegrationTestSuite) TestXMRMaker_Pairs() {
ctx := context.Background()
bc := rpcclient.NewClient(ctx, defaultXMRMakerSwapdPort)
_, err := bc.MakeOffer(
coins.StrToDecimal("1"),
coins.StrToDecimal("2"),
coins.StrToExchangeRate("200"),
s.testToken,
false)
require.NoError(s.T(), err)
_, err = bc.MakeOffer(
coins.StrToDecimal("1"),
coins.StrToDecimal("2"),
coins.StrToExchangeRate("200"),
types.EthAssetETH,
false)
require.NoError(s.T(), err)
_, err = bc.MakeOffer(
coins.StrToDecimal("1"),
coins.StrToDecimal("2"),
coins.StrToExchangeRate("200"),
types.EthAssetETH,
false)
require.NoError(s.T(), err)
// Give offer advertisement time to propagate
require.NoError(s.T(), common.SleepWithContext(ctx, time.Second))
ac := rpcclient.NewClient(ctx, defaultXMRTakerSwapdPort)
pairs, err := ac.Pairs(3)
require.Equal(s.T(), len(pairs.Pairs), 2)
p1 := pairs.Pairs[0]
p2 := pairs.Pairs[1]
require.Equal(s.T(), p1.Offers, uint64(2))
require.Equal(s.T(), p2.Offers, uint64(1))
require.NoError(s.T(), err)
}
func (s *IntegrationTestSuite) TestXMRTaker_Query() {
s.testXMRTakerQuery(types.EthAssetETH)
}

9
ui/.gitignore vendored
View File

@@ -22,3 +22,12 @@ dist-ssr
*.njsproj
*.sln
*.sw?
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

13
ui/.prettierignore Normal file
View File

@@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

9
ui/.prettierrc Normal file
View File

@@ -0,0 +1,9 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"pluginSearchDirs": ["."],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

221
ui/.svelte-kit/ambient.d.ts vendored Normal file
View File

@@ -0,0 +1,221 @@
// this file is generated — do not edit it
/// <reference types="@sveltejs/kit" />
/**
* Environment variables [loaded by Vite](https://vitejs.dev/guide/env-and-mode.html#env-files) from `.env` files and `process.env`. Like [`$env/dynamic/private`](https://kit.svelte.dev/docs/modules#$env-dynamic-private), this module cannot be imported into client-side code. This module only includes variables that _do not_ begin with [`config.kit.env.publicPrefix`](https://kit.svelte.dev/docs/configuration#env) _and do_ start with [`config.kit.env.privatePrefix`](https://kit.svelte.dev/docs/configuration#env) (if configured).
*
* _Unlike_ [`$env/dynamic/private`](https://kit.svelte.dev/docs/modules#$env-dynamic-private), the values exported from this module are statically injected into your bundle at build time, enabling optimisations like dead code elimination.
*
* ```ts
* import { API_KEY } from '$env/static/private';
* ```
*
* Note that all environment variables referenced in your code should be declared (for example in an `.env` file), even if they don't have a value until the app is deployed:
*
* ```
* MY_FEATURE_FLAG=""
* ```
*
* You can override `.env` values from the command line like so:
*
* ```bash
* MY_FEATURE_FLAG="enabled" npm run dev
* ```
*/
declare module '$env/static/private' {
export const SHELL: string;
export const npm_command: string;
export const LSCOLORS: string;
export const WINDOWID: string;
export const npm_config_userconfig: string;
export const COLORTERM: string;
export const npm_config_cache: string;
export const LESS: string;
export const NVM_INC: string;
export const CONDA_EXE: string;
export const _CE_M: string;
export const I3SOCK: string;
export const NODE: string;
export const COLOR: string;
export const npm_config_local_prefix: string;
export const KITTY_PID: string;
export const npm_config_globalconfig: string;
export const EDITOR: string;
export const GTK_MODULES: string;
export const XDG_SEAT: string;
export const PWD: string;
export const LOGNAME: string;
export const XDG_SESSION_TYPE: string;
export const npm_config_init_module: string;
export const _: string;
export const XAUTHORITY: string;
export const DESKTOP_STARTUP_ID: string;
export const KITTY_PUBLIC_KEY: string;
export const MOTD_SHOWN: string;
export const HOME: string;
export const LANG: string;
export const LS_COLORS: string;
export const npm_package_version: string;
export const KITTY_WINDOW_ID: string;
export const INIT_CWD: string;
export const npm_lifecycle_script: string;
export const NVM_DIR: string;
export const XDG_SESSION_CLASS: string;
export const TERMINFO: string;
export const TERM: string;
export const npm_package_name: string;
export const ZSH: string;
export const _CE_CONDA: string;
export const npm_config_prefix: string;
export const USER: string;
export const CONDA_SHLVL: string;
export const DISPLAY: string;
export const npm_lifecycle_event: string;
export const SHLVL: string;
export const NVM_CD_FLAGS: string;
export const PAGER: string;
export const XDG_VTNR: string;
export const XDG_SESSION_ID: string;
export const npm_config_user_agent: string;
export const npm_execpath: string;
export const CONDA_PYTHON_EXE: string;
export const XDG_RUNTIME_DIR: string;
export const DEBUGINFOD_URLS: string;
export const npm_package_json: string;
export const npm_config_noproxy: string;
export const PATH: string;
export const npm_config_metrics_registry: string;
export const npm_config_node_gyp: string;
export const DBUS_SESSION_BUS_ADDRESS: string;
export const npm_config_global_prefix: string;
export const NVM_BIN: string;
export const MAIL: string;
export const npm_config_scripts_prepend_node_path: string;
export const KITTY_INSTALLATION_DIR: string;
export const npm_node_execpath: string;
export const OLDPWD: string;
export const NODE_ENV: string;
}
/**
* Similar to [`$env/static/private`](https://kit.svelte.dev/docs/modules#$env-static-private), except that it only includes environment variables that begin with [`config.kit.env.publicPrefix`](https://kit.svelte.dev/docs/configuration#env) (which defaults to `PUBLIC_`), and can therefore safely be exposed to client-side code.
*
* Values are replaced statically at build time.
*
* ```ts
* import { PUBLIC_BASE_URL } from '$env/static/public';
* ```
*/
declare module '$env/static/public' {
}
/**
* This module provides access to runtime environment variables, as defined by the platform you're running on. For example if you're using [`adapter-node`](https://github.com/sveltejs/kit/tree/master/packages/adapter-node) (or running [`vite preview`](https://kit.svelte.dev/docs/cli)), this is equivalent to `process.env`. This module only includes variables that _do not_ begin with [`config.kit.env.publicPrefix`](https://kit.svelte.dev/docs/configuration#env) _and do_ start with [`config.kit.env.privatePrefix`](https://kit.svelte.dev/docs/configuration#env) (if configured).
*
* This module cannot be imported into client-side code.
*
* ```ts
* import { env } from '$env/dynamic/private';
* console.log(env.DEPLOYMENT_SPECIFIC_VARIABLE);
* ```
*
* > In `dev`, `$env/dynamic` always includes environment variables from `.env`. In `prod`, this behavior will depend on your adapter.
*/
declare module '$env/dynamic/private' {
export const env: {
SHELL: string;
npm_command: string;
LSCOLORS: string;
WINDOWID: string;
npm_config_userconfig: string;
COLORTERM: string;
npm_config_cache: string;
LESS: string;
NVM_INC: string;
CONDA_EXE: string;
_CE_M: string;
I3SOCK: string;
NODE: string;
COLOR: string;
npm_config_local_prefix: string;
KITTY_PID: string;
npm_config_globalconfig: string;
EDITOR: string;
GTK_MODULES: string;
XDG_SEAT: string;
PWD: string;
LOGNAME: string;
XDG_SESSION_TYPE: string;
npm_config_init_module: string;
_: string;
XAUTHORITY: string;
DESKTOP_STARTUP_ID: string;
KITTY_PUBLIC_KEY: string;
MOTD_SHOWN: string;
HOME: string;
LANG: string;
LS_COLORS: string;
npm_package_version: string;
KITTY_WINDOW_ID: string;
INIT_CWD: string;
npm_lifecycle_script: string;
NVM_DIR: string;
XDG_SESSION_CLASS: string;
TERMINFO: string;
TERM: string;
npm_package_name: string;
ZSH: string;
_CE_CONDA: string;
npm_config_prefix: string;
USER: string;
CONDA_SHLVL: string;
DISPLAY: string;
npm_lifecycle_event: string;
SHLVL: string;
NVM_CD_FLAGS: string;
PAGER: string;
XDG_VTNR: string;
XDG_SESSION_ID: string;
npm_config_user_agent: string;
npm_execpath: string;
CONDA_PYTHON_EXE: string;
XDG_RUNTIME_DIR: string;
DEBUGINFOD_URLS: string;
npm_package_json: string;
npm_config_noproxy: string;
PATH: string;
npm_config_metrics_registry: string;
npm_config_node_gyp: string;
DBUS_SESSION_BUS_ADDRESS: string;
npm_config_global_prefix: string;
NVM_BIN: string;
MAIL: string;
npm_config_scripts_prepend_node_path: string;
KITTY_INSTALLATION_DIR: string;
npm_node_execpath: string;
OLDPWD: string;
NODE_ENV: string;
[key: `PUBLIC_${string}`]: undefined;
[key: `${string}`]: string | undefined;
}
}
/**
* Similar to [`$env/dynamic/private`](https://kit.svelte.dev/docs/modules#$env-dynamic-private), but only includes variables that begin with [`config.kit.env.publicPrefix`](https://kit.svelte.dev/docs/configuration#env) (which defaults to `PUBLIC_`), and can therefore safely be exposed to client-side code.
*
* Note that public dynamic environment variables must all be sent from the server to the client, causing larger network requests — when possible, use `$env/static/public` instead.
*
* ```ts
* import { env } from '$env/dynamic/public';
* console.log(env.PUBLIC_DEPLOYMENT_SPECIFIC_VARIABLE);
* ```
*/
declare module '$env/dynamic/public' {
export const env: {
[key: `PUBLIC_${string}`]: string | undefined;
}
}

View File

@@ -0,0 +1,21 @@
export { matchers } from './matchers.js';
export const nodes = [
() => import('./nodes/0'),
() => import('./nodes/1'),
() => import('./nodes/2'),
() => import('./nodes/3')
];
export const server_loads = [];
export const dictionary = {
"/": [2],
"/offers/[token]": [3]
};
export const hooks = {
handleError: (({ error }) => { console.error(error) }),
};
export { default as root } from '../root.svelte';

View File

@@ -0,0 +1 @@
export const matchers = {};

View File

@@ -0,0 +1 @@
export { default as component } from "../../../../src/routes/+layout.svelte";

View File

@@ -0,0 +1 @@
export { default as component } from "../../../../node_modules/@sveltejs/kit/src/runtime/components/error.svelte";

View File

@@ -0,0 +1 @@
export { default as component } from "../../../../src/routes/+page.svelte";

View File

@@ -0,0 +1,3 @@
import * as universal from "../../../../src/routes/offers/[token]/+page.ts";
export { universal };
export { default as component } from "../../../../src/routes/offers/[token]/+page.svelte";

View File

@@ -0,0 +1 @@
export { default as component } from "../../../../src/routes/pairs/+page.svelte";

View File

@@ -0,0 +1,54 @@
<!-- This file is generated by @sveltejs/kit — do not edit it! -->
<script>
import { setContext, afterUpdate, onMount } from 'svelte';
import { browser } from '$app/environment';
// stores
export let stores;
export let page;
export let constructors;
export let components = [];
export let form;
export let data_0 = null;
export let data_1 = null;
if (!browser) {
setContext('__svelte__', stores);
}
$: stores.page.set(page);
afterUpdate(stores.page.notify);
let mounted = false;
let navigated = false;
let title = null;
onMount(() => {
const unsubscribe = stores.page.subscribe(() => {
if (mounted) {
navigated = true;
title = document.title || 'untitled page';
}
});
mounted = true;
return unsubscribe;
});
</script>
{#if constructors[1]}
<svelte:component this={constructors[0]} bind:this={components[0]} data={data_0}>
<svelte:component this={constructors[1]} bind:this={components[1]} data={data_1} {form} />
</svelte:component>
{:else}
<svelte:component this={constructors[0]} bind:this={components[0]} data={data_0} {form} />
{/if}
{#if mounted}
<div id="svelte-announcer" aria-live="assertive" aria-atomic="true" style="position: absolute; left: 0; top: 0; clip: rect(0 0 0 0); clip-path: inset(50%); overflow: hidden; white-space: nowrap; width: 1px; height: 1px">
{#if navigated}
{title}
{/if}
</div>
{/if}

View File

@@ -0,0 +1,30 @@
import root from '../root.svelte';
import { set_building } from '__sveltekit/environment';
import { set_assets } from '__sveltekit/paths';
import { set_private_env, set_public_env } from '../../../node_modules/@sveltejs/kit/src/runtime/shared-server.js';
export const options = {
app_template_contains_nonce: false,
csp: {"mode":"auto","directives":{"upgrade-insecure-requests":false,"block-all-mixed-content":false},"reportOnly":{"upgrade-insecure-requests":false,"block-all-mixed-content":false}},
csrf_check_origin: true,
track_server_fetches: false,
embedded: false,
env_public_prefix: 'PUBLIC_',
env_private_prefix: '',
hooks: null, // added lazily, via `get_hooks`
preload_strategy: "modulepreload",
root,
service_worker: false,
templates: {
app: ({ head, body, assets, nonce, env }) => "<!DOCTYPE html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<!--\n\t\t<link rel=\"icon\" href=\"" + assets + "/favicon.png\" />\n\t\t-->\n\t\t<meta name=\"viewport\" content=\"width=device-width\" />\n\t\t" + head + "\n\t</head>\n\t<body data-sveltekit-preload-data=\"hover\">\n\t\t<div style=\"display: contents\">" + body + "</div>\n\t</body>\n</html>\n",
error: ({ status, message }) => "<!DOCTYPE html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<title>" + message + "</title>\n\n\t\t<style>\n\t\t\tbody {\n\t\t\t\t--bg: white;\n\t\t\t\t--fg: #222;\n\t\t\t\t--divider: #ccc;\n\t\t\t\tbackground: var(--bg);\n\t\t\t\tcolor: var(--fg);\n\t\t\t\tfont-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,\n\t\t\t\t\tUbuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\t\t\t\theight: 100vh;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t.error {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tmax-width: 32rem;\n\t\t\t\tmargin: 0 1rem;\n\t\t\t}\n\n\t\t\t.status {\n\t\t\t\tfont-weight: 200;\n\t\t\t\tfont-size: 3rem;\n\t\t\t\tline-height: 1;\n\t\t\t\tposition: relative;\n\t\t\t\ttop: -0.05rem;\n\t\t\t}\n\n\t\t\t.message {\n\t\t\t\tborder-left: 1px solid var(--divider);\n\t\t\t\tpadding: 0 0 0 1rem;\n\t\t\t\tmargin: 0 0 0 1rem;\n\t\t\t\tmin-height: 2.5rem;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t}\n\n\t\t\t.message h1 {\n\t\t\t\tfont-weight: 400;\n\t\t\t\tfont-size: 1em;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t@media (prefers-color-scheme: dark) {\n\t\t\t\tbody {\n\t\t\t\t\t--bg: #222;\n\t\t\t\t\t--fg: #ddd;\n\t\t\t\t\t--divider: #666;\n\t\t\t\t}\n\t\t\t}\n\t\t</style>\n\t</head>\n\t<body>\n\t\t<div class=\"error\">\n\t\t\t<span class=\"status\">" + status + "</span>\n\t\t\t<div class=\"message\">\n\t\t\t\t<h1>" + message + "</h1>\n\t\t\t</div>\n\t\t</div>\n\t</body>\n</html>\n"
},
version_hash: "cgqvn8"
};
export function get_hooks() {
return {};
}
export { set_assets, set_building, set_private_env, set_public_env };

View File

@@ -0,0 +1,46 @@
{
"compilerOptions": {
"paths": {
"$lib": [
"../src/lib"
],
"$lib/*": [
"../src/lib/*"
]
},
"rootDirs": [
"..",
"./types"
],
"importsNotUsedAsValues": "error",
"isolatedModules": true,
"preserveValueImports": true,
"lib": [
"esnext",
"DOM",
"DOM.Iterable"
],
"moduleResolution": "node",
"module": "esnext",
"target": "esnext",
"ignoreDeprecations": "5.0"
},
"include": [
"ambient.d.ts",
"./types/**/$types.d.ts",
"../vite.config.ts",
"../src/**/*.js",
"../src/**/*.ts",
"../src/**/*.svelte",
"../tests/**/*.js",
"../tests/**/*.ts",
"../tests/**/*.svelte"
],
"exclude": [
"../node_modules/**",
"./[!ambient.d.ts]**",
"../src/service-worker.js",
"../src/service-worker.ts",
"../src/service-worker.d.ts"
]
}

View File

@@ -1,32 +0,0 @@
{
"compilerOptions": {
"moduleResolution": "bundler",
"target": "ESNext",
"module": "ESNext",
/**
* svelte-preprocess cannot figure out whether you have
* a value or a type, so tell TypeScript to enforce using
* `import type` instead of `import` for Types.
*/
"verbatimModuleSyntax": true,
"isolatedModules": true,
"resolveJsonModule": true,
/**
* To have warnings / errors of the Svelte compiler at the
* correct position, enable source maps by default.
*/
"sourceMap": true,
"esModuleInterop": true,
"skipLibCheck": true,
/**
* Typecheck JS in `.svelte` and `.js` files by default.
* Disable this if you'd like to use dynamic types.
*/
"checkJs": true
},
/**
* Use global.d.ts instead of compilerOptions.types
* to avoid limiting type declarations.
*/
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
}

View File

@@ -4,20 +4,36 @@
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite",
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview"
"preview": "vite preview",
"test": "npm run test:integration && npm run test:unit",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"test:integration": "playwright test",
"test:unit": "vitest",
"lint": "prettier --plugin-search-dir . --check .",
"format": "prettier --plugin-search-dir . --write ."
},
"devDependencies": {
"@playwright/test": "^1.28.1",
"@sveltejs/adapter-auto": "^2.1.0",
"@sveltejs/kit": "^1.20.4",
"@sveltejs/vite-plugin-svelte": "^2.0.3",
"autoprefixer": "^10.4.14",
"postcss": "^8.4.23",
"postcss": "^8.4.24",
"postcss-load-config": "^4.0.1",
"svelte": "^3.57.0",
"prettier": "^2.8.0",
"prettier-plugin-svelte": "^2.10.1",
"svelte": "^4.0.0",
"svelte-check": "^3.4.3",
"svelte-heros-v2": "^0.4.2",
"svelte-preprocess": "^5.0.3",
"tailwindcss": "^3.3.1",
"vite": "^4.3.5"
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"vite": "^4.3.6",
"vitest": "^0.32.2"
},
"dependencies": {
"@popperjs/core": "^2.11.7",

12
ui/playwright.config.ts Normal file
View File

@@ -0,0 +1,12 @@
import type { PlaywrightTestConfig } from '@playwright/test';
const config: PlaywrightTestConfig = {
webServer: {
command: 'npm run build && npm run preview',
port: 4173
},
testDir: 'tests',
testMatch: /(.+\.)?(test|spec)\.[jt]s/
};
export default config;

View File

@@ -4,8 +4,8 @@ const autoprefixer = require("autoprefixer");
const config = {
plugins: [
//Some plugins, like tailwindcss/nesting, need to run before Tailwind,
tailwindcss(),
//But others, like autoprefixer, need to run after,
tailwindcss(), //But others, like autoprefixer, need to run after,
autoprefixer,
autoprefixer,
],
};

View File

@@ -2,14 +2,11 @@
import svelteLogo from './assets/svelte.svg'
import viteLogo from '/vite.svg'
import Navbar from './lib/Navbar.svelte'
import OffersTable from './lib/OffersTable.svelte'
import TakeDealDialog from './lib/TakeDealDialog.svelte'
import Offers from './lib/Offers.svelte'
</script>
<main>
<Navbar />
<OffersTable />
<TakeDealDialog />
</main>
<style>

12
ui/src/app.d.ts vendored Normal file
View File

@@ -0,0 +1,12 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface Platform {}
}
}
export {};

14
ui/src/app.html Normal file
View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<!--
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
-->
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@@ -1,18 +1,20 @@
/* Write your global styles here, in PostCSS syntax */
/* Write your global styles here, in PostCSS syntax */
@tailwind base;
@font-face {
font-family: 'Iosevka';
font-display: swap;
font-weight: 400;
font-stretch: normal;
font-style: normal;
src: url('assets/iosevka-regular.ttf') format('truetype');
font-family: "Iosevka";
font-display: swap;
font-weight: 400;
font-stretch: normal;
font-style: normal;
src: url("assets/iosevka-regular.ttf") format("truetype");
}
:root {
font-family: 'Iosevka', Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
font-family: "Iosevka", Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
@@ -80,4 +82,3 @@ button:focus-visible {
@tailwind components;
@tailwind utilities;

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 811 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 962 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 929 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 956 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="48.501175mm"
height="48.501175mm"
viewBox="0 0 48.501175 48.501175"
version="1.1"
id="svg5"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="unknown.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="1.5459244"
inkscape:cx="58.541027"
inkscape:cy="75.682875"
inkscape:window-width="1596"
inkscape:window-height="861"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs2" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-92.8564,-66.860764)">
<circle
style="fill:#e4e4e4;fill-opacity:1;stroke:none;stroke-width:0.499999;stroke-linecap:round;stroke-linejoin:round"
id="path236"
cx="117.10699"
cy="91.111351"
r="24.000587" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:30.088px;line-height:1.25;font-family:'Font Awesome 6 Free';-inkscape-font-specification:'Font Awesome 6 Free';fill:#a0a0a0;fill-opacity:1;stroke:none;stroke-width:0.752202"
x="107.70449"
y="102.39435"
id="text1576"><tspan
sodipodi:role="line"
id="tspan1574"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-family:'Font Awesome 6 Free';-inkscape-font-specification:'Font Awesome 6 Free Heavy';fill:#a0a0a0;fill-opacity:1;stroke-width:0.752202"
x="107.70449"
y="102.39435">?</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Some files were not shown because too many files have changed in this diff Show More