kopia lustrzana https://github.com/bugout-dev/moonstream
Merge branch 'main' into mixpanel-improvements
commit
88b4bbadfe
|
@ -1,13 +1,14 @@
|
|||
import json
|
||||
import logging
|
||||
from typing import Dict, Any, List, Optional
|
||||
|
||||
from enum import Enum
|
||||
import boto3 # type: ignore
|
||||
from moonstreamdb.models import (
|
||||
EthereumAddress,
|
||||
EthereumLabel,
|
||||
)
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.orm import Session, query, query_expression
|
||||
|
||||
from . import data
|
||||
from .settings import ETHERSCAN_SMARTCONTRACTS_BUCKET
|
||||
|
@ -50,6 +51,62 @@ def get_contract_source_info(
|
|||
return None
|
||||
|
||||
|
||||
class LabelNames(Enum):
|
||||
ETHERSCAN_SMARTCONTRACT = "etherscan_smartcontract"
|
||||
COINMARKETCAP_TOKEN = "coinmarketcap_token"
|
||||
|
||||
|
||||
def get_ethereum_address_info(
|
||||
db_session: Session, address: str
|
||||
) -> Optional[data.EthereumAddressInfo]:
|
||||
query = db_session.query(EthereumAddress.id).filter(
|
||||
EthereumAddress.address == address
|
||||
)
|
||||
id = query.one_or_none()
|
||||
if id is None:
|
||||
return None
|
||||
|
||||
address_info = data.EthereumAddressInfo(address=address)
|
||||
etherscan_address_url = f"https://etherscan.io/address/{address}"
|
||||
blockchain_com_url = f"https://www.blockchain.com/eth/address/{address}"
|
||||
# Checking for token:
|
||||
coinmarketcap_label: Optional[EthereumLabel] = (
|
||||
db_session.query(EthereumLabel)
|
||||
.filter(EthereumLabel.address_id == id[0])
|
||||
.filter(EthereumLabel.label == LabelNames.COINMARKETCAP_TOKEN.value)
|
||||
.order_by(text("created_at desc"))
|
||||
.limit(1)
|
||||
.one_or_none()
|
||||
)
|
||||
if coinmarketcap_label is not None:
|
||||
address_info.token = data.EthereumTokenDetails(
|
||||
name=coinmarketcap_label.label_data["name"],
|
||||
symbol=coinmarketcap_label.label_data["symbol"],
|
||||
external_url=[
|
||||
coinmarketcap_label.label_data["coinmarketcap_url"],
|
||||
etherscan_address_url,
|
||||
blockchain_com_url,
|
||||
],
|
||||
)
|
||||
|
||||
# Checking for smart contract
|
||||
etherscan_label: Optional[EthereumLabel] = (
|
||||
db_session.query(EthereumLabel)
|
||||
.filter(EthereumLabel.address_id == id[0])
|
||||
.filter(EthereumLabel.label == LabelNames.ETHERSCAN_SMARTCONTRACT.value)
|
||||
.order_by(text("created_at desc"))
|
||||
.limit(1)
|
||||
.one_or_none()
|
||||
)
|
||||
if etherscan_label is not None:
|
||||
address_info.smart_contract = data.EthereumSmartContractDetails(
|
||||
name=etherscan_label.label_data["name"],
|
||||
external_url=[etherscan_address_url, blockchain_com_url],
|
||||
)
|
||||
|
||||
return address_info
|
||||
|
||||
|
||||
def get_address_labels(
|
||||
db_session: Session, start: int, limit: int, addresses: Optional[str] = None
|
||||
) -> data.AddressListLabelsResponse:
|
||||
|
|
|
@ -12,6 +12,7 @@ from .routes.subscriptions import app as subscriptions_api
|
|||
from .routes.users import app as users_api
|
||||
from .routes.txinfo import app as txinfo_api
|
||||
from .routes.streams import app as streams_api
|
||||
from .routes.address_info import app as addressinfo_api
|
||||
|
||||
from .settings import ORIGINS
|
||||
from .version import MOONSTREAM_VERSION
|
||||
|
@ -49,3 +50,4 @@ app.mount("/subscriptions", subscriptions_api)
|
|||
app.mount("/users", users_api)
|
||||
app.mount("/streams", streams_api)
|
||||
app.mount("/txinfo", txinfo_api)
|
||||
app.mount("/address_info", addressinfo_api)
|
||||
|
|
|
@ -157,6 +157,23 @@ class EthereumSmartContractSourceInfo(BaseModel):
|
|||
compiler_version: str
|
||||
|
||||
|
||||
class EthereumTokenDetails(BaseModel):
|
||||
name: Optional[str]
|
||||
symbol: Optional[str]
|
||||
external_url: List[str] = []
|
||||
|
||||
|
||||
class EthereumSmartContractDetails(BaseModel):
|
||||
name: Optional[str]
|
||||
external_url: List[str] = []
|
||||
|
||||
|
||||
class EthereumAddressInfo(BaseModel):
|
||||
address: str
|
||||
token: Optional[EthereumTokenDetails]
|
||||
smart_contract: Optional[EthereumSmartContractDetails]
|
||||
|
||||
|
||||
class TxinfoEthereumBlockchainResponse(BaseModel):
|
||||
tx: EthereumTransaction
|
||||
is_smart_contract_deployment: bool = False
|
||||
|
|
|
@ -91,6 +91,7 @@ class BugoutEventProvider:
|
|||
"""
|
||||
is_query_constrained = query.subscription_types or query.subscriptions
|
||||
relevant_subscriptions = user_subscriptions.get(self.event_type)
|
||||
|
||||
if (
|
||||
is_query_constrained and self.event_type not in query.subscription_types
|
||||
) or not relevant_subscriptions:
|
||||
|
@ -268,10 +269,46 @@ class BugoutEventProvider:
|
|||
return self.entry_event(search_results.results[0])
|
||||
|
||||
|
||||
class EthereumTXPoolProvider(BugoutEventProvider):
|
||||
def __init__(
|
||||
self,
|
||||
event_type: str,
|
||||
tags: Optional[List[str]] = None,
|
||||
batch_size: int = 100,
|
||||
timeout: float = 30.0,
|
||||
):
|
||||
|
||||
super().__init__(event_type, tags, batch_size, timeout)
|
||||
|
||||
def parse_filters(
|
||||
self, query: StreamQuery, user_subscriptions: Dict[str, List[BugoutResource]]
|
||||
) -> Optional[List[str]]:
|
||||
|
||||
is_query_constrained = query.subscription_types or query.subscriptions
|
||||
relevant_subscriptions = user_subscriptions.get(self.event_type)
|
||||
|
||||
if (
|
||||
is_query_constrained and self.event_type not in query.subscription_types
|
||||
) or not relevant_subscriptions:
|
||||
return None
|
||||
addresses = [
|
||||
subscription.resource_data["address"]
|
||||
for subscription in relevant_subscriptions
|
||||
]
|
||||
subscriptions_filters = []
|
||||
for adress in addresses:
|
||||
subscriptions_filters.extend(
|
||||
[f"?#from_address:{adress}", f"?#to_address:{adress}"]
|
||||
)
|
||||
|
||||
return subscriptions_filters
|
||||
|
||||
|
||||
whalewatch_provider = BugoutEventProvider(
|
||||
event_type="ethereum_whalewatch", tags=["crawl_type:ethereum_trending"]
|
||||
)
|
||||
|
||||
ethereum_txpool_provider = BugoutEventProvider(
|
||||
|
||||
ethereum_txpool_provider = EthereumTXPoolProvider(
|
||||
event_type="ethereum_txpool", tags=["client:ethereum-txpool-crawler-0"]
|
||||
)
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
import logging
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from sqlalchemy.sql.expression import true
|
||||
|
||||
from fastapi import FastAPI, Depends, Query, HTTPException
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from moonstreamdb.db import yield_db_session
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from .. import actions
|
||||
from .. import data
|
||||
from ..middleware import BroodAuthMiddleware
|
||||
from ..settings import DOCS_TARGET_PATH, ORIGINS, DOCS_PATHS
|
||||
from ..version import MOONSTREAM_VERSION
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
tags_metadata = [
|
||||
{"name": "addressinfo", "description": "Address public information."},
|
||||
{"name": "labels", "description": "Addresses label information."},
|
||||
]
|
||||
|
||||
app = FastAPI(
|
||||
title=f"Moonstream users API.",
|
||||
description="User, token and password handlers.",
|
||||
version=MOONSTREAM_VERSION,
|
||||
openapi_tags=tags_metadata,
|
||||
openapi_url="/openapi.json",
|
||||
docs_url=None,
|
||||
redoc_url=f"/{DOCS_TARGET_PATH}",
|
||||
)
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=ORIGINS,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
whitelist_paths: Dict[str, str] = {}
|
||||
whitelist_paths.update(DOCS_PATHS)
|
||||
app.add_middleware(BroodAuthMiddleware, whitelist=whitelist_paths)
|
||||
|
||||
|
||||
@app.get(
|
||||
"/ethereum_blockchain",
|
||||
tags=["addressinfo"],
|
||||
response_model=data.EthereumAddressInfo,
|
||||
)
|
||||
async def addressinfo_handler(
|
||||
address: str,
|
||||
db_session: Session = Depends(yield_db_session),
|
||||
) -> Optional[data.EthereumAddressInfo]:
|
||||
response = actions.get_ethereum_address_info(db_session, address)
|
||||
return response
|
||||
|
||||
|
||||
@app.get(
|
||||
"/labels/ethereum_blockchain",
|
||||
tags=["labels bul"],
|
||||
response_model=data.AddressListLabelsResponse,
|
||||
)
|
||||
async def addresses_labels_bulk_handler(
|
||||
addresses: Optional[str] = Query(None),
|
||||
start: int = Query(0),
|
||||
limit: int = Query(100),
|
||||
db_session: Session = Depends(yield_db_session),
|
||||
) -> data.AddressListLabelsResponse:
|
||||
"""
|
||||
Fetch labels with additional public information
|
||||
about known addresses.
|
||||
"""
|
||||
if limit > 100:
|
||||
raise HTTPException(
|
||||
status_code=406, detail="The limit cannot exceed 100 addresses"
|
||||
)
|
||||
try:
|
||||
addresses_response = actions.get_address_labels(
|
||||
db_session=db_session, start=start, limit=limit, addresses=addresses
|
||||
)
|
||||
except Exception as err:
|
||||
logger.error(f"Unable to get info about Ethereum addresses {err}")
|
||||
raise HTTPException(status_code=500)
|
||||
|
||||
return addresses_response
|
|
@ -10,7 +10,7 @@ from typing import Dict, Optional
|
|||
|
||||
from sqlalchemy.sql.expression import true
|
||||
|
||||
from fastapi import FastAPI, Depends, HTTPException, Query
|
||||
from fastapi import FastAPI, Depends
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from moonstreamdb.db import yield_db_session
|
||||
from moonstreamdb.models import EthereumAddress
|
||||
|
@ -27,7 +27,6 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
tags_metadata = [
|
||||
{"name": "txinfo", "description": "Ethereum transactions info."},
|
||||
{"name": "address info", "description": "Addresses public information."},
|
||||
]
|
||||
|
||||
app = FastAPI(
|
||||
|
@ -92,31 +91,3 @@ async def txinfo_ethereum_blockchain_handler(
|
|||
response.smart_contract_address = txinfo_request.tx.to_address
|
||||
response.is_smart_contract_call = True
|
||||
return response
|
||||
|
||||
|
||||
@app.get(
|
||||
"/addresses", tags=["address info"], response_model=data.AddressListLabelsResponse
|
||||
)
|
||||
async def addresses_labels_handler(
|
||||
addresses: Optional[str] = Query(None),
|
||||
start: int = Query(0),
|
||||
limit: int = Query(100),
|
||||
db_session: Session = Depends(yield_db_session),
|
||||
) -> data.AddressListLabelsResponse:
|
||||
"""
|
||||
Fetch labels with additional public information
|
||||
about known addresses.
|
||||
"""
|
||||
if limit > 100:
|
||||
raise HTTPException(
|
||||
status_code=406, detail="The limit cannot exceed 100 addresses"
|
||||
)
|
||||
try:
|
||||
addresses_response = actions.get_address_labels(
|
||||
db_session=db_session, start=start, limit=limit, addresses=addresses
|
||||
)
|
||||
except Exception as err:
|
||||
logger.error(f"Unable to get info about Ethereum addresses {err}")
|
||||
raise HTTPException(status_code=500)
|
||||
|
||||
return addresses_response
|
||||
|
|
|
@ -138,6 +138,7 @@ func PollTxpoolContent(gethClient *rpc.Client, interval int, reporter *humbug.Hu
|
|||
fmt.Sprintf("max_priority_fee_per_gas:%d", pendingTx.Transaction.MaxPriorityFeePerGas.ToInt()),
|
||||
fmt.Sprintf("max_fee_per_gas:%d", pendingTx.Transaction.MaxFeePerGas.ToInt()),
|
||||
fmt.Sprintf("gas:%d", pendingTx.Transaction.Gas),
|
||||
"crawl_type:ethereum_txpool",
|
||||
}
|
||||
report := humbug.Report{
|
||||
Title: ReportTitle,
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
python3
|
|
@ -1 +0,0 @@
|
|||
/usr/bin/python3
|
|
@ -1 +0,0 @@
|
|||
python3
|
|
@ -1 +0,0 @@
|
|||
lib
|
|
@ -1,3 +0,0 @@
|
|||
home = /home/neeraj/external/python/Python-3.9.6/bin
|
||||
include-system-site-packages = false
|
||||
version = 3.9.6
|
|
@ -1,4 +1,5 @@
|
|||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
|
||||
|
@ -8,6 +9,9 @@ from sqlalchemy import text
|
|||
from moonstreamdb.db import yield_db_session_ctx
|
||||
from moonstreamdb.models import EthereumAddress, EthereumLabel
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
COINMARKETCAP_API_KEY = os.environ.get("COINMARKETCAP_API_KEY")
|
||||
if COINMARKETCAP_API_KEY is None:
|
||||
raise ValueError("COINMARKETCAP_API_KEY environment variable must be set")
|
||||
|
@ -50,45 +54,70 @@ def identities_cmc_add_handler(args: argparse.Namespace) -> None:
|
|||
raise Exception(err)
|
||||
|
||||
if len(response["data"]) == 0:
|
||||
print("No more data, crawling finished")
|
||||
logger.info("No more data, crawling finished")
|
||||
break
|
||||
|
||||
with yield_db_session_ctx() as db_session:
|
||||
latest_address = (
|
||||
latest_address = 1
|
||||
latest_address_obj = (
|
||||
db_session.query(EthereumAddress.id)
|
||||
.order_by(text("id desc"))
|
||||
.limit(1)
|
||||
.one()
|
||||
)[0]
|
||||
.one_or_none()
|
||||
)
|
||||
if latest_address_obj is not None:
|
||||
latest_address = latest_address_obj[0]
|
||||
|
||||
for coin in response["data"]:
|
||||
if coin["platform"] is not None:
|
||||
if (
|
||||
coin["platform"]["id"] == 1027
|
||||
and coin["platform"]["token_address"] is not None
|
||||
):
|
||||
latest_address += 1
|
||||
eth_token_id = latest_address
|
||||
eth_token = EthereumAddress(
|
||||
id=eth_token_id,
|
||||
address=coin["platform"]["token_address"],
|
||||
token_address = coin["platform"]["token_address"]
|
||||
# Check if address already exists
|
||||
address = (
|
||||
db_session.query(EthereumAddress)
|
||||
.filter(EthereumAddress.address == token_address)
|
||||
.one_or_none()
|
||||
)
|
||||
db_session.add(eth_token)
|
||||
eth_token_label = EthereumLabel(
|
||||
label="coinmarketcap_token",
|
||||
address_id=eth_token_id,
|
||||
label_data={
|
||||
"name": coin["name"],
|
||||
"symbol": coin["symbol"],
|
||||
"coinmarketcap_url": f'https://coinmarketcap.com/currencies/{coin["slug"]}',
|
||||
},
|
||||
# Add new address
|
||||
if address is None:
|
||||
latest_address += 1
|
||||
eth_token_id = latest_address
|
||||
eth_token = EthereumAddress(
|
||||
id=eth_token_id,
|
||||
address=token_address,
|
||||
)
|
||||
db_session.add(eth_token)
|
||||
logger.info(f"Added {coin['name']} token")
|
||||
else:
|
||||
eth_token_id = address.id
|
||||
|
||||
label = (
|
||||
db_session.query(EthereumLabel)
|
||||
.filter(EthereumLabel.address_id == eth_token_id)
|
||||
.one_or_none()
|
||||
)
|
||||
db_session.add(eth_token_label)
|
||||
print(f"Added {coin['name']} token")
|
||||
if label is None:
|
||||
eth_token_label = EthereumLabel(
|
||||
label="coinmarketcap_token",
|
||||
address_id=eth_token_id,
|
||||
label_data={
|
||||
"name": coin["name"],
|
||||
"symbol": coin["symbol"],
|
||||
"coinmarketcap_url": f'https://coinmarketcap.com/currencies/{coin["slug"]}',
|
||||
},
|
||||
)
|
||||
db_session.add(eth_token_label)
|
||||
logger.info(f"Added label for {coin['name']} token")
|
||||
|
||||
db_session.commit()
|
||||
start_n += limit_n
|
||||
|
||||
print(f"Loop ended, starting new from {start_n} to {start_n + limit_n - 1}")
|
||||
logger.info(
|
||||
f"Loop ended, starting new from {start_n} to {start_n + limit_n - 1}"
|
||||
)
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
export MOONSTREAM_IPC_PATH=null
|
||||
export MOONSTREAM_CRAWL_WORKERS=4
|
||||
export MOONSTREAM_DB_URI="postgresql://<username>:<password>@<db_host>:<db_port>/<db_name>"
|
||||
|
||||
export MOONSTREAM_ETHERSCAN_TOKEN="TOKEN"
|
||||
export AWS_S3_SMARTCONTRACT_BUCKET=""
|
||||
export MOONSTREAM_ETHERSCAN_TOKEN="<Token for etherscan>"
|
||||
export AWS_S3_SMARTCONTRACT_BUCKET="<AWS S3 bucket for smart contracts>"
|
||||
export MOONSTREAM_HUMBUG_TOKEN="<Token for crawlers store data via Humbug>"
|
||||
export COINMARKETCAP_API_KEY="<API key to parse conmarketcap>"
|
||||
|
|
|
@ -21,6 +21,7 @@ const DefaultLayout = dynamic(() => import("../src/layouts"), {
|
|||
});
|
||||
import { useRouter } from "next/router";
|
||||
import NProgress from "nprogress";
|
||||
import { WHITE_LOGO_W_TEXT_URL } from "../src/core/constants";
|
||||
|
||||
export default function CachingApp({ Component, pageProps }) {
|
||||
const [queryClient] = useState(new QueryClient());
|
||||
|
@ -48,6 +49,10 @@ export default function CachingApp({ Component, pageProps }) {
|
|||
const getLayout =
|
||||
Component.getLayout || ((page) => <DefaultLayout>{page}</DefaultLayout>);
|
||||
|
||||
const headLinks = [
|
||||
{ rel: "preload", as: "image", href: WHITE_LOGO_W_TEXT_URL },
|
||||
];
|
||||
pageProps.preloads && headLinks.push(...pageProps.preloads);
|
||||
return (
|
||||
<>
|
||||
<style global jsx>{`
|
||||
|
@ -62,7 +67,7 @@ export default function CachingApp({ Component, pageProps }) {
|
|||
}
|
||||
`}</style>
|
||||
{pageProps.metaTags && <HeadSEO {...pageProps.metaTags} />}
|
||||
{pageProps.preloads && <HeadLinks links={pageProps.preloads} />}
|
||||
<HeadLinks links={headLinks} />
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AppContext>{getLayout(<Component {...pageProps} />)}</AppContext>
|
||||
</QueryClientProvider>
|
||||
|
|
|
@ -7,8 +7,6 @@ import {
|
|||
Stack,
|
||||
chakra,
|
||||
useMediaQuery,
|
||||
UnorderedList,
|
||||
ListItem,
|
||||
useBreakpointValue,
|
||||
} from "@chakra-ui/react";
|
||||
import { DEFAULT_METATAGS, AWS_ASSETS_PATH } from "../../src/core/constants";
|
||||
|
@ -133,47 +131,54 @@ const Product = () => {
|
|||
alignItems="center"
|
||||
pb={24}
|
||||
>
|
||||
<Stack mx={margin} my={12} maxW="1700px">
|
||||
<Heading as="h2" size="md" w="100%" px={12} py={2} borderTopRadius="xl">
|
||||
{`Why you'll love moonstream`}
|
||||
<Stack mx={margin} my={12} maxW="1700px" textAlign="justify">
|
||||
<Heading
|
||||
as="h2"
|
||||
size="md"
|
||||
placeSelf="center"
|
||||
px={12}
|
||||
py={2}
|
||||
borderTopRadius="xl"
|
||||
>
|
||||
{`Why you'll love Moonstream`}
|
||||
</Heading>
|
||||
<chakra.span pl={2} px={12} py={2}>
|
||||
<Text mb={2}>
|
||||
<Text mb={3}>
|
||||
We strive for financial inclusion. With cryptocurrencies becoming
|
||||
mainstream, now is the time for anyone with a computer and access to
|
||||
the Internet to utilize this opportunity to make passive income.
|
||||
We’re here to make it easier.
|
||||
</Text>
|
||||
<Text mb={2}>
|
||||
<Text mb={3}>
|
||||
Right now our source of data is Ethereum blockchain. Our goal is to
|
||||
provide a live view of the transactions taking place on every public
|
||||
blockchain - from the activity of specific accounts or smart
|
||||
contracts to updates about general market movements.
|
||||
</Text>
|
||||
<Text mb={2}>
|
||||
<Text mb={3}>
|
||||
This information comes from the blockchains themselves, from their
|
||||
mempools/transaction pools, and from centralized exchanges, social
|
||||
media, and the news. This forms a stream of information tailored to
|
||||
your specific needs.
|
||||
</Text>
|
||||
<Text mb={2}>
|
||||
<Text mb={3}>
|
||||
We’re giving you a macro view of the crypto market with direct
|
||||
access from Moonstream dashboards to execute transactions. You can
|
||||
also set up programs which execute (on- or off-chain) when your
|
||||
stream meets certain conditions.
|
||||
</Text>
|
||||
<Text mb={2}>
|
||||
<Text mb={3}>
|
||||
Moonstream is accessible through dashboard, API and webhooks.
|
||||
</Text>
|
||||
<Text mb={2}>
|
||||
<Text mb={3}>
|
||||
Moonstream’s financial inclusion goes beyond providing access to
|
||||
data. All of our work is open source as we do not believe that
|
||||
proprietary technologies are financially inclusive.
|
||||
</Text>
|
||||
<Text mb={2}>
|
||||
<Text mb={3}>
|
||||
You can read{" "}
|
||||
<Link
|
||||
textColor="primary.500"
|
||||
textColor="secondary.900"
|
||||
isExternal
|
||||
href="https://github.com/bugout-dev/moonstream"
|
||||
>
|
||||
|
@ -181,7 +186,7 @@ const Product = () => {
|
|||
</Link>{" "}
|
||||
and keep track of our progress using{" "}
|
||||
<Link
|
||||
textColor="primary.500"
|
||||
textColor="secondary.900"
|
||||
isExternal
|
||||
href="https://github.com/bugout-dev/moonstream/milestones"
|
||||
>
|
||||
|
@ -191,82 +196,6 @@ const Product = () => {
|
|||
</Text>
|
||||
</chakra.span>
|
||||
</Stack>
|
||||
<Stack mx={margin} mb={12} maxW="1700px" bgTra>
|
||||
<Heading as="h2" size="md" w="100%" px={12} py={2} borderTopRadius="xl">
|
||||
Product
|
||||
</Heading>
|
||||
<chakra.span pl={2} px={12} py={2}>
|
||||
<Text mb={2}>
|
||||
Moonstream is a product which helps anyone participate in
|
||||
decentralized finance. From the most sophisticated flash
|
||||
arbitrageurs to people looking for yield from currency that would
|
||||
otherwise lie dormant in their exchange accounts.
|
||||
</Text>
|
||||
<Text mb={2}>
|
||||
We aim to go far beyond raw transaction information, enriching our
|
||||
view with context from centralized exchanges, the news, social
|
||||
media, and smart contract analysis.
|
||||
</Text>
|
||||
<Text mb={2}>
|
||||
Moonstream users can subscribe to events from any blockchain - from
|
||||
the activity of specific accounts or smart contracts to updates
|
||||
about general market movements. This information comes from the
|
||||
blockchains themselves, from their <b>mempools/transaction</b>{" "}
|
||||
pools, and from centralized exchanges, social media, and the news.
|
||||
This forms a stream of information tailored to their specific needs.
|
||||
</Text>
|
||||
<Text mb={2}>
|
||||
They can use this information to execute transactions directly from
|
||||
the Moonstream frontend or they can set up programs which execute
|
||||
(on- or off-chain) when their stream meets certain conditions.
|
||||
</Text>
|
||||
<Text mb={2}>
|
||||
Moonstream will be accessible to software through our API and
|
||||
webhooks.
|
||||
</Text>
|
||||
<chakra.span>
|
||||
<Text>Moonstream customers are:</Text>
|
||||
<UnorderedList w="75%" pl={4}>
|
||||
<ListItem>
|
||||
<b>Development teams deploying decentralized applications -</b>
|
||||
They use Moonstream to analyze how users are calling their
|
||||
dapps, and set up alerts for suspicious activity.{" "}
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<b>Algorithmic funds - </b> They use Moonstream to execute
|
||||
transactions directly on-chain under prespecified conditions.
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<b>Crypto traders -</b> They use Moonstream to evaluate trading
|
||||
strategies based on data from centralized exchanges, the
|
||||
blockchain, and the transaction pool.
|
||||
</ListItem>
|
||||
</UnorderedList>
|
||||
</chakra.span>
|
||||
<Text my={2}>
|
||||
Moonstream’s financial inclusion goes beyond providing access to
|
||||
data. We also help validators and stakers on proof of stake chains
|
||||
earn rewards in excess of the validation rewards. We pay validators
|
||||
to send mempool/transaction pool data back to Moonstream, and they
|
||||
divide these payments between themselves and their stakers. This
|
||||
helps validators attract more stake on proof of stake blockchains
|
||||
like Algorand, Solana, and post-merge Ethereum. It also ensures that
|
||||
Moonstream users have access to the freshest and most geographically
|
||||
diverse transaction pool data on the market.
|
||||
</Text>
|
||||
<Text mb={2}>
|
||||
All of our work is open source as we do not believe that proprietary
|
||||
technologies are financially inclusive.{" "}
|
||||
<Link
|
||||
textColor="primary.500"
|
||||
isExternal
|
||||
href="https://github.com/bugout-dev/moonstream"
|
||||
>
|
||||
You can read our code on GitHub.
|
||||
</Link>
|
||||
</Text>
|
||||
</chakra.span>
|
||||
</Stack>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -155,7 +155,6 @@ const Product = () => {
|
|||
</Box>
|
||||
<Box
|
||||
w="full"
|
||||
h="full"
|
||||
py={48}
|
||||
backgroundImage={`url(${assets[`team`]})`}
|
||||
backgroundSize="cover"
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import React, { Fragment, useContext } from "react";
|
||||
import RouterLink from "next/link";
|
||||
import {
|
||||
Flex,
|
||||
Button,
|
||||
Image,
|
||||
ButtonGroup,
|
||||
Spacer,
|
||||
Link,
|
||||
IconButton,
|
||||
Flex,
|
||||
} from "@chakra-ui/react";
|
||||
import { HamburgerIcon } from "@chakra-ui/icons";
|
||||
import useModals from "../core/hooks/useModals";
|
||||
|
@ -22,107 +22,107 @@ const LandingNavbar = () => {
|
|||
const { toggleModal } = useModals();
|
||||
return (
|
||||
<>
|
||||
<>
|
||||
{ui.isMobileView && (
|
||||
<>
|
||||
<IconButton
|
||||
alignSelf="flex-start"
|
||||
colorScheme="primary"
|
||||
variant="solid"
|
||||
onClick={() => ui.setSidebarToggled(!ui.sidebarToggled)}
|
||||
icon={<HamburgerIcon />}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Flex
|
||||
pl={ui.isMobileView ? 2 : 8}
|
||||
justifySelf="flex-start"
|
||||
h="full"
|
||||
py={1}
|
||||
>
|
||||
<RouterLink href="/" passHref>
|
||||
<Link h="full">
|
||||
<Image
|
||||
h="full"
|
||||
src={WHITE_LOGO_W_TEXT_URL}
|
||||
alt="Moonstream logo"
|
||||
/>
|
||||
</Link>
|
||||
</RouterLink>
|
||||
</Flex>
|
||||
{ui.isMobileView && (
|
||||
<>
|
||||
<IconButton
|
||||
alignSelf="flex-start"
|
||||
colorScheme="primary"
|
||||
variant="solid"
|
||||
onClick={() => ui.setSidebarToggled(!ui.sidebarToggled)}
|
||||
icon={<HamburgerIcon />}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Flex
|
||||
pl={ui.isMobileView ? 2 : 8}
|
||||
justifySelf="flex-start"
|
||||
h="100%"
|
||||
py={1}
|
||||
flexBasis="200px"
|
||||
flexGrow={1}
|
||||
flexShirnk={1}
|
||||
id="Logo Container"
|
||||
>
|
||||
<RouterLink href="/" passHref>
|
||||
<Link
|
||||
as={Image}
|
||||
w="auto"
|
||||
h="full"
|
||||
justifyContent="left"
|
||||
src={WHITE_LOGO_W_TEXT_URL}
|
||||
alt="Moonstream logo"
|
||||
/>
|
||||
</RouterLink>
|
||||
</Flex>
|
||||
|
||||
{!ui.isMobileView && (
|
||||
<>
|
||||
<Spacer />
|
||||
<ButtonGroup
|
||||
variant="link"
|
||||
colorScheme="secondary"
|
||||
spacing={4}
|
||||
pr={16}
|
||||
>
|
||||
{ALL_NAV_PATHES.map((item, idx) => (
|
||||
<RouteButton
|
||||
key={`${idx}-${item.title}-landing-all-links`}
|
||||
variant="link"
|
||||
href={item.path}
|
||||
color="white"
|
||||
isActive={!!(router.pathname === item.path)}
|
||||
>
|
||||
{item.title}
|
||||
</RouteButton>
|
||||
))}
|
||||
{!ui.isMobileView && (
|
||||
<>
|
||||
<Spacer />
|
||||
<ButtonGroup
|
||||
variant="link"
|
||||
colorScheme="secondary"
|
||||
spacing={4}
|
||||
pr={16}
|
||||
>
|
||||
{ALL_NAV_PATHES.map((item, idx) => (
|
||||
<RouteButton
|
||||
key={`${idx}-${item.title}-landing-all-links`}
|
||||
variant="link"
|
||||
href={item.path}
|
||||
color="white"
|
||||
isActive={!!(router.pathname === item.path)}
|
||||
>
|
||||
{item.title}
|
||||
</RouteButton>
|
||||
))}
|
||||
|
||||
{ui.isLoggedIn && (
|
||||
<RouterLink href="/stream" passHref>
|
||||
<Button
|
||||
as={Link}
|
||||
colorScheme="secondary"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
fontWeight="400"
|
||||
borderRadius="2xl"
|
||||
>
|
||||
App
|
||||
</Button>
|
||||
</RouterLink>
|
||||
)}
|
||||
{!ui.isLoggedIn && (
|
||||
{ui.isLoggedIn && (
|
||||
<RouterLink href="/stream" passHref>
|
||||
<Button
|
||||
colorScheme="whiteAlpha"
|
||||
as={Link}
|
||||
colorScheme="secondary"
|
||||
variant="outline"
|
||||
onClick={() => toggleModal("register")}
|
||||
size="sm"
|
||||
fontWeight="400"
|
||||
borderRadius="2xl"
|
||||
>
|
||||
Get started
|
||||
App
|
||||
</Button>
|
||||
)}
|
||||
{!ui.isLoggedIn && (
|
||||
<Button
|
||||
color="white"
|
||||
onClick={() => toggleModal("login")}
|
||||
fontWeight="400"
|
||||
>
|
||||
Log in
|
||||
</Button>
|
||||
)}
|
||||
{ui.isLoggedIn && (
|
||||
<ChakraAccountIconButton
|
||||
variant="link"
|
||||
colorScheme="secondary"
|
||||
/>
|
||||
)}
|
||||
</ButtonGroup>
|
||||
</>
|
||||
)}
|
||||
{ui.isLoggedIn && ui.isMobileView && (
|
||||
<>
|
||||
<Spacer />
|
||||
<ChakraAccountIconButton variant="link" colorScheme="secondary" />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
</RouterLink>
|
||||
)}
|
||||
{!ui.isLoggedIn && (
|
||||
<Button
|
||||
colorScheme="whiteAlpha"
|
||||
variant="outline"
|
||||
onClick={() => toggleModal("register")}
|
||||
size="sm"
|
||||
fontWeight="400"
|
||||
borderRadius="2xl"
|
||||
>
|
||||
Get started
|
||||
</Button>
|
||||
)}
|
||||
{!ui.isLoggedIn && (
|
||||
<Button
|
||||
color="white"
|
||||
onClick={() => toggleModal("login")}
|
||||
fontWeight="400"
|
||||
>
|
||||
Log in
|
||||
</Button>
|
||||
)}
|
||||
{ui.isLoggedIn && (
|
||||
<ChakraAccountIconButton variant="link" colorScheme="secondary" />
|
||||
)}
|
||||
</ButtonGroup>
|
||||
</>
|
||||
)}
|
||||
{ui.isLoggedIn && ui.isMobileView && (
|
||||
<>
|
||||
<Spacer />
|
||||
<ChakraAccountIconButton variant="link" colorScheme="secondary" />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -19,16 +19,11 @@ const Navbar = () => {
|
|||
boxShadow={["sm", "md"]}
|
||||
alignItems="center"
|
||||
id="Navbar"
|
||||
minH={["3rem", "3rem", "3rem", "3rem", "3rem", "3rem"]}
|
||||
// overflow="initial"
|
||||
minH="3rem"
|
||||
maxH="3rem"
|
||||
bgColor="primary.1200"
|
||||
// flexWrap="wrap"
|
||||
direction={["row", "row", "row", null, "row"]}
|
||||
// zIndex={100}
|
||||
direction="row"
|
||||
w="100%"
|
||||
minW="100%"
|
||||
m={0}
|
||||
p={0}
|
||||
overflow="hidden"
|
||||
>
|
||||
<Suspense fallback={""}>
|
||||
|
|
Ładowanie…
Reference in New Issue