kopia lustrzana https://github.com/bugout-dev/moonstream
Merge branch 'main' into mixpanel-improvements
commit
88b4bbadfe
|
@ -1,13 +1,14 @@
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from typing import Dict, Any, List, Optional
|
from typing import Dict, Any, List, Optional
|
||||||
|
from enum import Enum
|
||||||
import boto3 # type: ignore
|
import boto3 # type: ignore
|
||||||
from moonstreamdb.models import (
|
from moonstreamdb.models import (
|
||||||
EthereumAddress,
|
EthereumAddress,
|
||||||
EthereumLabel,
|
EthereumLabel,
|
||||||
)
|
)
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy import text
|
||||||
|
from sqlalchemy.orm import Session, query, query_expression
|
||||||
|
|
||||||
from . import data
|
from . import data
|
||||||
from .settings import ETHERSCAN_SMARTCONTRACTS_BUCKET
|
from .settings import ETHERSCAN_SMARTCONTRACTS_BUCKET
|
||||||
|
@ -50,6 +51,62 @@ def get_contract_source_info(
|
||||||
return None
|
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(
|
def get_address_labels(
|
||||||
db_session: Session, start: int, limit: int, addresses: Optional[str] = None
|
db_session: Session, start: int, limit: int, addresses: Optional[str] = None
|
||||||
) -> data.AddressListLabelsResponse:
|
) -> data.AddressListLabelsResponse:
|
||||||
|
|
|
@ -12,6 +12,7 @@ from .routes.subscriptions import app as subscriptions_api
|
||||||
from .routes.users import app as users_api
|
from .routes.users import app as users_api
|
||||||
from .routes.txinfo import app as txinfo_api
|
from .routes.txinfo import app as txinfo_api
|
||||||
from .routes.streams import app as streams_api
|
from .routes.streams import app as streams_api
|
||||||
|
from .routes.address_info import app as addressinfo_api
|
||||||
|
|
||||||
from .settings import ORIGINS
|
from .settings import ORIGINS
|
||||||
from .version import MOONSTREAM_VERSION
|
from .version import MOONSTREAM_VERSION
|
||||||
|
@ -49,3 +50,4 @@ app.mount("/subscriptions", subscriptions_api)
|
||||||
app.mount("/users", users_api)
|
app.mount("/users", users_api)
|
||||||
app.mount("/streams", streams_api)
|
app.mount("/streams", streams_api)
|
||||||
app.mount("/txinfo", txinfo_api)
|
app.mount("/txinfo", txinfo_api)
|
||||||
|
app.mount("/address_info", addressinfo_api)
|
||||||
|
|
|
@ -157,6 +157,23 @@ class EthereumSmartContractSourceInfo(BaseModel):
|
||||||
compiler_version: str
|
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):
|
class TxinfoEthereumBlockchainResponse(BaseModel):
|
||||||
tx: EthereumTransaction
|
tx: EthereumTransaction
|
||||||
is_smart_contract_deployment: bool = False
|
is_smart_contract_deployment: bool = False
|
||||||
|
|
|
@ -91,6 +91,7 @@ class BugoutEventProvider:
|
||||||
"""
|
"""
|
||||||
is_query_constrained = query.subscription_types or query.subscriptions
|
is_query_constrained = query.subscription_types or query.subscriptions
|
||||||
relevant_subscriptions = user_subscriptions.get(self.event_type)
|
relevant_subscriptions = user_subscriptions.get(self.event_type)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
is_query_constrained and self.event_type not in query.subscription_types
|
is_query_constrained and self.event_type not in query.subscription_types
|
||||||
) or not relevant_subscriptions:
|
) or not relevant_subscriptions:
|
||||||
|
@ -268,10 +269,46 @@ class BugoutEventProvider:
|
||||||
return self.entry_event(search_results.results[0])
|
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(
|
whalewatch_provider = BugoutEventProvider(
|
||||||
event_type="ethereum_whalewatch", tags=["crawl_type:ethereum_trending"]
|
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"]
|
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 sqlalchemy.sql.expression import true
|
||||||
|
|
||||||
from fastapi import FastAPI, Depends, HTTPException, Query
|
from fastapi import FastAPI, Depends
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from moonstreamdb.db import yield_db_session
|
from moonstreamdb.db import yield_db_session
|
||||||
from moonstreamdb.models import EthereumAddress
|
from moonstreamdb.models import EthereumAddress
|
||||||
|
@ -27,7 +27,6 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
tags_metadata = [
|
tags_metadata = [
|
||||||
{"name": "txinfo", "description": "Ethereum transactions info."},
|
{"name": "txinfo", "description": "Ethereum transactions info."},
|
||||||
{"name": "address info", "description": "Addresses public information."},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
|
@ -92,31 +91,3 @@ async def txinfo_ethereum_blockchain_handler(
|
||||||
response.smart_contract_address = txinfo_request.tx.to_address
|
response.smart_contract_address = txinfo_request.tx.to_address
|
||||||
response.is_smart_contract_call = True
|
response.is_smart_contract_call = True
|
||||||
return response
|
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_priority_fee_per_gas:%d", pendingTx.Transaction.MaxPriorityFeePerGas.ToInt()),
|
||||||
fmt.Sprintf("max_fee_per_gas:%d", pendingTx.Transaction.MaxFeePerGas.ToInt()),
|
fmt.Sprintf("max_fee_per_gas:%d", pendingTx.Transaction.MaxFeePerGas.ToInt()),
|
||||||
fmt.Sprintf("gas:%d", pendingTx.Transaction.Gas),
|
fmt.Sprintf("gas:%d", pendingTx.Transaction.Gas),
|
||||||
|
"crawl_type:ethereum_txpool",
|
||||||
}
|
}
|
||||||
report := humbug.Report{
|
report := humbug.Report{
|
||||||
Title: ReportTitle,
|
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 argparse
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
@ -8,6 +9,9 @@ from sqlalchemy import text
|
||||||
from moonstreamdb.db import yield_db_session_ctx
|
from moonstreamdb.db import yield_db_session_ctx
|
||||||
from moonstreamdb.models import EthereumAddress, EthereumLabel
|
from moonstreamdb.models import EthereumAddress, EthereumLabel
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
COINMARKETCAP_API_KEY = os.environ.get("COINMARKETCAP_API_KEY")
|
COINMARKETCAP_API_KEY = os.environ.get("COINMARKETCAP_API_KEY")
|
||||||
if COINMARKETCAP_API_KEY is None:
|
if COINMARKETCAP_API_KEY is None:
|
||||||
raise ValueError("COINMARKETCAP_API_KEY environment variable must be set")
|
raise ValueError("COINMARKETCAP_API_KEY environment variable must be set")
|
||||||
|
@ -50,29 +54,52 @@ def identities_cmc_add_handler(args: argparse.Namespace) -> None:
|
||||||
raise Exception(err)
|
raise Exception(err)
|
||||||
|
|
||||||
if len(response["data"]) == 0:
|
if len(response["data"]) == 0:
|
||||||
print("No more data, crawling finished")
|
logger.info("No more data, crawling finished")
|
||||||
break
|
break
|
||||||
|
|
||||||
with yield_db_session_ctx() as db_session:
|
with yield_db_session_ctx() as db_session:
|
||||||
latest_address = (
|
latest_address = 1
|
||||||
|
latest_address_obj = (
|
||||||
db_session.query(EthereumAddress.id)
|
db_session.query(EthereumAddress.id)
|
||||||
.order_by(text("id desc"))
|
.order_by(text("id desc"))
|
||||||
.limit(1)
|
.limit(1)
|
||||||
.one()
|
.one_or_none()
|
||||||
)[0]
|
)
|
||||||
|
if latest_address_obj is not None:
|
||||||
|
latest_address = latest_address_obj[0]
|
||||||
|
|
||||||
for coin in response["data"]:
|
for coin in response["data"]:
|
||||||
if coin["platform"] is not None:
|
if coin["platform"] is not None:
|
||||||
if (
|
if (
|
||||||
coin["platform"]["id"] == 1027
|
coin["platform"]["id"] == 1027
|
||||||
and coin["platform"]["token_address"] is not None
|
and coin["platform"]["token_address"] is not None
|
||||||
):
|
):
|
||||||
|
token_address = coin["platform"]["token_address"]
|
||||||
|
# Check if address already exists
|
||||||
|
address = (
|
||||||
|
db_session.query(EthereumAddress)
|
||||||
|
.filter(EthereumAddress.address == token_address)
|
||||||
|
.one_or_none()
|
||||||
|
)
|
||||||
|
# Add new address
|
||||||
|
if address is None:
|
||||||
latest_address += 1
|
latest_address += 1
|
||||||
eth_token_id = latest_address
|
eth_token_id = latest_address
|
||||||
eth_token = EthereumAddress(
|
eth_token = EthereumAddress(
|
||||||
id=eth_token_id,
|
id=eth_token_id,
|
||||||
address=coin["platform"]["token_address"],
|
address=token_address,
|
||||||
)
|
)
|
||||||
db_session.add(eth_token)
|
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()
|
||||||
|
)
|
||||||
|
if label is None:
|
||||||
eth_token_label = EthereumLabel(
|
eth_token_label = EthereumLabel(
|
||||||
label="coinmarketcap_token",
|
label="coinmarketcap_token",
|
||||||
address_id=eth_token_id,
|
address_id=eth_token_id,
|
||||||
|
@ -83,12 +110,14 @@ def identities_cmc_add_handler(args: argparse.Namespace) -> None:
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
db_session.add(eth_token_label)
|
db_session.add(eth_token_label)
|
||||||
print(f"Added {coin['name']} token")
|
logger.info(f"Added label for {coin['name']} token")
|
||||||
|
|
||||||
db_session.commit()
|
db_session.commit()
|
||||||
start_n += limit_n
|
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)
|
time.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,7 @@
|
||||||
export MOONSTREAM_IPC_PATH=null
|
export MOONSTREAM_IPC_PATH=null
|
||||||
export MOONSTREAM_CRAWL_WORKERS=4
|
export MOONSTREAM_CRAWL_WORKERS=4
|
||||||
export MOONSTREAM_DB_URI="postgresql://<username>:<password>@<db_host>:<db_port>/<db_name>"
|
export MOONSTREAM_DB_URI="postgresql://<username>:<password>@<db_host>:<db_port>/<db_name>"
|
||||||
|
export MOONSTREAM_ETHERSCAN_TOKEN="<Token for etherscan>"
|
||||||
export MOONSTREAM_ETHERSCAN_TOKEN="TOKEN"
|
export AWS_S3_SMARTCONTRACT_BUCKET="<AWS S3 bucket for smart contracts>"
|
||||||
export AWS_S3_SMARTCONTRACT_BUCKET=""
|
|
||||||
export MOONSTREAM_HUMBUG_TOKEN="<Token for crawlers store data via Humbug>"
|
export MOONSTREAM_HUMBUG_TOKEN="<Token for crawlers store data via Humbug>"
|
||||||
export COINMARKETCAP_API_KEY="<API key to parse conmarketcap>"
|
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 { useRouter } from "next/router";
|
||||||
import NProgress from "nprogress";
|
import NProgress from "nprogress";
|
||||||
|
import { WHITE_LOGO_W_TEXT_URL } from "../src/core/constants";
|
||||||
|
|
||||||
export default function CachingApp({ Component, pageProps }) {
|
export default function CachingApp({ Component, pageProps }) {
|
||||||
const [queryClient] = useState(new QueryClient());
|
const [queryClient] = useState(new QueryClient());
|
||||||
|
@ -48,6 +49,10 @@ export default function CachingApp({ Component, pageProps }) {
|
||||||
const getLayout =
|
const getLayout =
|
||||||
Component.getLayout || ((page) => <DefaultLayout>{page}</DefaultLayout>);
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<style global jsx>{`
|
<style global jsx>{`
|
||||||
|
@ -62,7 +67,7 @@ export default function CachingApp({ Component, pageProps }) {
|
||||||
}
|
}
|
||||||
`}</style>
|
`}</style>
|
||||||
{pageProps.metaTags && <HeadSEO {...pageProps.metaTags} />}
|
{pageProps.metaTags && <HeadSEO {...pageProps.metaTags} />}
|
||||||
{pageProps.preloads && <HeadLinks links={pageProps.preloads} />}
|
<HeadLinks links={headLinks} />
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<AppContext>{getLayout(<Component {...pageProps} />)}</AppContext>
|
<AppContext>{getLayout(<Component {...pageProps} />)}</AppContext>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
|
|
|
@ -7,8 +7,6 @@ import {
|
||||||
Stack,
|
Stack,
|
||||||
chakra,
|
chakra,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
UnorderedList,
|
|
||||||
ListItem,
|
|
||||||
useBreakpointValue,
|
useBreakpointValue,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import { DEFAULT_METATAGS, AWS_ASSETS_PATH } from "../../src/core/constants";
|
import { DEFAULT_METATAGS, AWS_ASSETS_PATH } from "../../src/core/constants";
|
||||||
|
@ -133,47 +131,54 @@ const Product = () => {
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
pb={24}
|
pb={24}
|
||||||
>
|
>
|
||||||
<Stack mx={margin} my={12} maxW="1700px">
|
<Stack mx={margin} my={12} maxW="1700px" textAlign="justify">
|
||||||
<Heading as="h2" size="md" w="100%" px={12} py={2} borderTopRadius="xl">
|
<Heading
|
||||||
{`Why you'll love moonstream`}
|
as="h2"
|
||||||
|
size="md"
|
||||||
|
placeSelf="center"
|
||||||
|
px={12}
|
||||||
|
py={2}
|
||||||
|
borderTopRadius="xl"
|
||||||
|
>
|
||||||
|
{`Why you'll love Moonstream`}
|
||||||
</Heading>
|
</Heading>
|
||||||
<chakra.span pl={2} px={12} py={2}>
|
<chakra.span pl={2} px={12} py={2}>
|
||||||
<Text mb={2}>
|
<Text mb={3}>
|
||||||
We strive for financial inclusion. With cryptocurrencies becoming
|
We strive for financial inclusion. With cryptocurrencies becoming
|
||||||
mainstream, now is the time for anyone with a computer and access to
|
mainstream, now is the time for anyone with a computer and access to
|
||||||
the Internet to utilize this opportunity to make passive income.
|
the Internet to utilize this opportunity to make passive income.
|
||||||
We’re here to make it easier.
|
We’re here to make it easier.
|
||||||
</Text>
|
</Text>
|
||||||
<Text mb={2}>
|
<Text mb={3}>
|
||||||
Right now our source of data is Ethereum blockchain. Our goal is to
|
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
|
provide a live view of the transactions taking place on every public
|
||||||
blockchain - from the activity of specific accounts or smart
|
blockchain - from the activity of specific accounts or smart
|
||||||
contracts to updates about general market movements.
|
contracts to updates about general market movements.
|
||||||
</Text>
|
</Text>
|
||||||
<Text mb={2}>
|
<Text mb={3}>
|
||||||
This information comes from the blockchains themselves, from their
|
This information comes from the blockchains themselves, from their
|
||||||
mempools/transaction pools, and from centralized exchanges, social
|
mempools/transaction pools, and from centralized exchanges, social
|
||||||
media, and the news. This forms a stream of information tailored to
|
media, and the news. This forms a stream of information tailored to
|
||||||
your specific needs.
|
your specific needs.
|
||||||
</Text>
|
</Text>
|
||||||
<Text mb={2}>
|
<Text mb={3}>
|
||||||
We’re giving you a macro view of the crypto market with direct
|
We’re giving you a macro view of the crypto market with direct
|
||||||
access from Moonstream dashboards to execute transactions. You can
|
access from Moonstream dashboards to execute transactions. You can
|
||||||
also set up programs which execute (on- or off-chain) when your
|
also set up programs which execute (on- or off-chain) when your
|
||||||
stream meets certain conditions.
|
stream meets certain conditions.
|
||||||
</Text>
|
</Text>
|
||||||
<Text mb={2}>
|
<Text mb={3}>
|
||||||
Moonstream is accessible through dashboard, API and webhooks.
|
Moonstream is accessible through dashboard, API and webhooks.
|
||||||
</Text>
|
</Text>
|
||||||
<Text mb={2}>
|
<Text mb={3}>
|
||||||
Moonstream’s financial inclusion goes beyond providing access to
|
Moonstream’s financial inclusion goes beyond providing access to
|
||||||
data. All of our work is open source as we do not believe that
|
data. All of our work is open source as we do not believe that
|
||||||
proprietary technologies are financially inclusive.
|
proprietary technologies are financially inclusive.
|
||||||
</Text>
|
</Text>
|
||||||
<Text mb={2}>
|
<Text mb={3}>
|
||||||
You can read{" "}
|
You can read{" "}
|
||||||
<Link
|
<Link
|
||||||
textColor="primary.500"
|
textColor="secondary.900"
|
||||||
isExternal
|
isExternal
|
||||||
href="https://github.com/bugout-dev/moonstream"
|
href="https://github.com/bugout-dev/moonstream"
|
||||||
>
|
>
|
||||||
|
@ -181,7 +186,7 @@ const Product = () => {
|
||||||
</Link>{" "}
|
</Link>{" "}
|
||||||
and keep track of our progress using{" "}
|
and keep track of our progress using{" "}
|
||||||
<Link
|
<Link
|
||||||
textColor="primary.500"
|
textColor="secondary.900"
|
||||||
isExternal
|
isExternal
|
||||||
href="https://github.com/bugout-dev/moonstream/milestones"
|
href="https://github.com/bugout-dev/moonstream/milestones"
|
||||||
>
|
>
|
||||||
|
@ -191,82 +196,6 @@ const Product = () => {
|
||||||
</Text>
|
</Text>
|
||||||
</chakra.span>
|
</chakra.span>
|
||||||
</Stack>
|
</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>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -155,7 +155,6 @@ const Product = () => {
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
w="full"
|
w="full"
|
||||||
h="full"
|
|
||||||
py={48}
|
py={48}
|
||||||
backgroundImage={`url(${assets[`team`]})`}
|
backgroundImage={`url(${assets[`team`]})`}
|
||||||
backgroundSize="cover"
|
backgroundSize="cover"
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import React, { Fragment, useContext } from "react";
|
import React, { Fragment, useContext } from "react";
|
||||||
import RouterLink from "next/link";
|
import RouterLink from "next/link";
|
||||||
import {
|
import {
|
||||||
Flex,
|
|
||||||
Button,
|
Button,
|
||||||
Image,
|
Image,
|
||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
Spacer,
|
Spacer,
|
||||||
Link,
|
Link,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
Flex,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import { HamburgerIcon } from "@chakra-ui/icons";
|
import { HamburgerIcon } from "@chakra-ui/icons";
|
||||||
import useModals from "../core/hooks/useModals";
|
import useModals from "../core/hooks/useModals";
|
||||||
|
@ -21,7 +21,6 @@ const LandingNavbar = () => {
|
||||||
const ui = useContext(UIContext);
|
const ui = useContext(UIContext);
|
||||||
const { toggleModal } = useModals();
|
const { toggleModal } = useModals();
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<>
|
<>
|
||||||
{ui.isMobileView && (
|
{ui.isMobileView && (
|
||||||
<>
|
<>
|
||||||
|
@ -37,17 +36,22 @@ const LandingNavbar = () => {
|
||||||
<Flex
|
<Flex
|
||||||
pl={ui.isMobileView ? 2 : 8}
|
pl={ui.isMobileView ? 2 : 8}
|
||||||
justifySelf="flex-start"
|
justifySelf="flex-start"
|
||||||
h="full"
|
h="100%"
|
||||||
py={1}
|
py={1}
|
||||||
|
flexBasis="200px"
|
||||||
|
flexGrow={1}
|
||||||
|
flexShirnk={1}
|
||||||
|
id="Logo Container"
|
||||||
>
|
>
|
||||||
<RouterLink href="/" passHref>
|
<RouterLink href="/" passHref>
|
||||||
<Link h="full">
|
<Link
|
||||||
<Image
|
as={Image}
|
||||||
|
w="auto"
|
||||||
h="full"
|
h="full"
|
||||||
|
justifyContent="left"
|
||||||
src={WHITE_LOGO_W_TEXT_URL}
|
src={WHITE_LOGO_W_TEXT_URL}
|
||||||
alt="Moonstream logo"
|
alt="Moonstream logo"
|
||||||
/>
|
/>
|
||||||
</Link>
|
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
|
@ -108,10 +112,7 @@ const LandingNavbar = () => {
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{ui.isLoggedIn && (
|
{ui.isLoggedIn && (
|
||||||
<ChakraAccountIconButton
|
<ChakraAccountIconButton variant="link" colorScheme="secondary" />
|
||||||
variant="link"
|
|
||||||
colorScheme="secondary"
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</>
|
</>
|
||||||
|
@ -123,7 +124,6 @@ const LandingNavbar = () => {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -19,16 +19,11 @@ const Navbar = () => {
|
||||||
boxShadow={["sm", "md"]}
|
boxShadow={["sm", "md"]}
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
id="Navbar"
|
id="Navbar"
|
||||||
minH={["3rem", "3rem", "3rem", "3rem", "3rem", "3rem"]}
|
minH="3rem"
|
||||||
// overflow="initial"
|
maxH="3rem"
|
||||||
bgColor="primary.1200"
|
bgColor="primary.1200"
|
||||||
// flexWrap="wrap"
|
direction="row"
|
||||||
direction={["row", "row", "row", null, "row"]}
|
|
||||||
// zIndex={100}
|
|
||||||
w="100%"
|
w="100%"
|
||||||
minW="100%"
|
|
||||||
m={0}
|
|
||||||
p={0}
|
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
>
|
>
|
||||||
<Suspense fallback={""}>
|
<Suspense fallback={""}>
|
||||||
|
|
Ładowanie…
Reference in New Issue