From 49f54867824c1df52006e344cbcb208bf486ce2e Mon Sep 17 00:00:00 2001 From: NikitaR13 <112944669+NikitaR13@users.noreply.github.com> Date: Thu, 23 Jan 2025 15:29:43 +0300 Subject: [PATCH] Add files via upload --- Web3-_--main/README.md | 2 + Web3-_--main/contracts/market/Market.go | 118 +++++++++++++++ Web3-_--main/contracts/nep11/OnlyFansNFT.py | 135 ++++++++++++++++++ Web3-_--main/contracts/nep17/MyNeoToken.py | 77 ++++++++++ Web3-_--main/services/api/Dockerfile | 18 +++ Web3-_--main/services/api/config.yaml | 10 ++ Web3-_--main/services/api/go.mod | 9 ++ .../api/internal/handlers/market_handler.go | 81 +++++++++++ .../api/internal/handlers/nft_handler.go | 86 +++++++++++ .../api/internal/handlers/token_handler.go | 49 +++++++ .../services/api/internal/models/market.go | 6 + .../services/api/internal/models/nft.go | 9 ++ .../services/api/internal/neo/neo_client.go | 44 ++++++ .../services/api/internal/utils/config.go | 36 +++++ .../services/api/internal/utils/logger.go | 38 +++++ .../services/api/internal/wallet/wallet.go | 15 ++ Web3-_--main/services/api/main.go | 66 +++++++++ .../services/api/scripts/local_run.sh | 5 + .../services/frostfs-uploader/Dockerfile | 14 ++ .../services/frostfs-uploader/config.yaml | 5 + Web3-_--main/services/frostfs-uploader/go.mod | 9 ++ .../internal/frostuploader/uploader.go | 28 ++++ .../internal/handlers/upload_handler.go | 65 +++++++++ .../services/frostfs-uploader/main.go | 32 +++++ .../frostfs-uploader/scripts/local_run.sh | 3 + Web3-_--main/services/indexer/Dockerfile | 14 ++ Web3-_--main/services/indexer/config.yaml | 4 + Web3-_--main/services/indexer/go.sum.go | 1 + .../internal/db/migrations/001_init.sql | 14 ++ .../indexer/internal/db/repository.go | 43 ++++++ .../indexer/internal/models/indexer_models.go | 16 +++ .../indexer/internal/subscriber/events.go | 51 +++++++ .../services/indexer/internal/utils/config.go | 36 +++++ .../services/indexer/internal/utils/logger.go | 23 +++ Web3-_--main/services/indexer/main.go | 23 +++ .../services/indexer/scripts/local_run.sh | 4 + 36 files changed, 1189 insertions(+) create mode 100644 Web3-_--main/README.md create mode 100644 Web3-_--main/contracts/market/Market.go create mode 100644 Web3-_--main/contracts/nep11/OnlyFansNFT.py create mode 100644 Web3-_--main/contracts/nep17/MyNeoToken.py create mode 100644 Web3-_--main/services/api/Dockerfile create mode 100644 Web3-_--main/services/api/config.yaml create mode 100644 Web3-_--main/services/api/go.mod create mode 100644 Web3-_--main/services/api/internal/handlers/market_handler.go create mode 100644 Web3-_--main/services/api/internal/handlers/nft_handler.go create mode 100644 Web3-_--main/services/api/internal/handlers/token_handler.go create mode 100644 Web3-_--main/services/api/internal/models/market.go create mode 100644 Web3-_--main/services/api/internal/models/nft.go create mode 100644 Web3-_--main/services/api/internal/neo/neo_client.go create mode 100644 Web3-_--main/services/api/internal/utils/config.go create mode 100644 Web3-_--main/services/api/internal/utils/logger.go create mode 100644 Web3-_--main/services/api/internal/wallet/wallet.go create mode 100644 Web3-_--main/services/api/main.go create mode 100644 Web3-_--main/services/api/scripts/local_run.sh create mode 100644 Web3-_--main/services/frostfs-uploader/Dockerfile create mode 100644 Web3-_--main/services/frostfs-uploader/config.yaml create mode 100644 Web3-_--main/services/frostfs-uploader/go.mod create mode 100644 Web3-_--main/services/frostfs-uploader/internal/frostuploader/uploader.go create mode 100644 Web3-_--main/services/frostfs-uploader/internal/handlers/upload_handler.go create mode 100644 Web3-_--main/services/frostfs-uploader/main.go create mode 100644 Web3-_--main/services/frostfs-uploader/scripts/local_run.sh create mode 100644 Web3-_--main/services/indexer/Dockerfile create mode 100644 Web3-_--main/services/indexer/config.yaml create mode 100644 Web3-_--main/services/indexer/go.sum.go create mode 100644 Web3-_--main/services/indexer/internal/db/migrations/001_init.sql create mode 100644 Web3-_--main/services/indexer/internal/db/repository.go create mode 100644 Web3-_--main/services/indexer/internal/models/indexer_models.go create mode 100644 Web3-_--main/services/indexer/internal/subscriber/events.go create mode 100644 Web3-_--main/services/indexer/internal/utils/config.go create mode 100644 Web3-_--main/services/indexer/internal/utils/logger.go create mode 100644 Web3-_--main/services/indexer/main.go create mode 100644 Web3-_--main/services/indexer/scripts/local_run.sh diff --git a/Web3-_--main/README.md b/Web3-_--main/README.md new file mode 100644 index 0000000..98afab8 --- /dev/null +++ b/Web3-_--main/README.md @@ -0,0 +1,2 @@ +# Web3 (^_^) +Web3 OnlyFans — это децентрализованная платформа для монетизации контента, которая объединяет инфлюенсеров и подписчиков. Платформа использует технологии блокчейна для обеспечения прозрачности, безопасности, приватности и прямых финансовых взаимодействий без посредников. diff --git a/Web3-_--main/contracts/market/Market.go b/Web3-_--main/contracts/market/Market.go new file mode 100644 index 0000000..9b8afd9 --- /dev/null +++ b/Web3-_--main/contracts/market/Market.go @@ -0,0 +1,118 @@ +package contract + +import ( + "github.com/nspcc-dev/neo-go/pkg/interop" + "github.com/nspcc-dev/neo-go/pkg/interop/contract" + "github.com/nspcc-dev/neo-go/pkg/interop/iterator" + "github.com/nspcc-dev/neo-go/pkg/interop/runtime" + "github.com/nspcc-dev/neo-go/pkg/interop/std" + "github.com/nspcc-dev/neo-go/pkg/interop/storage" +) + +const ( + ownerKey = "o" + tokenKey = "t" + nftKey = "n" + sellPrefix = "forSale_" +) + +// MarketDeployData структура для _deploy +type MarketDeployData struct { + Admin interop.Hash160 + Token interop.Hash160 + Nft interop.Hash160 +} + +func _deploy(data any, isUpdate bool) { + if isUpdate { + return + } + + initData := data.(MarketDeployData) + admin := initData.Admin + if len(admin) != 20 { + panic("invalid admin") + } + + ctx := storage.GetContext() + storage.Put(ctx, []byte(ownerKey), admin) + storage.Put(ctx, []byte(tokenKey), initData.Token) + storage.Put(ctx, []byte(nftKey), initData.Nft) +} + +// OnNEP11Payment вызывается при получении NFT +func OnNEP11Payment(from interop.Hash160, amount int, tokenId []byte, data any) { + if amount != 1 { + panic("Must be non-fungible (amount=1)") + } + ctx := storage.GetContext() + callingHash := runtime.GetCallingScriptHash() + nftHash := storage.Get(ctx, []byte(nftKey)).(interop.Hash160) + if !std.Equals(callingHash, nftHash) { + panic("NFT not recognized") + } + + // Выставляем NFT на продажу, если владелец хочет + // Цена берется из свойств NFT (fields: price) + saleKey := append([]byte(sellPrefix), tokenId...) + storage.Put(ctx, saleKey, []byte{1}) +} + +// OnNEP17Payment вызывается при получении токенов (оплата за NFT) +func OnNEP17Payment(from interop.Hash160, amount int, data any) { + ctx := storage.GetContext() + callingHash := runtime.GetCallingScriptHash() + myToken := storage.Get(ctx, []byte(tokenKey)).(interop.Hash160) + if !std.Equals(callingHash, myToken) { + panic("invalid token") + } + + tokenIdBytes, ok := data.([]byte) + if !ok { + panic("no tokenId in data") + } + tokenId := string(tokenIdBytes) + + saleKey := append([]byte(sellPrefix), tokenIdBytes...) + if storage.Get(ctx, saleKey) == nil { + panic("NFT not on sale") + } + + // Узнаем price из NFT + nftHash := storage.Get(ctx, []byte(nftKey)).(interop.Hash160) + props := contract.Call(nftHash, "properties", contract.ReadStates, tokenId).(map[string]any) + // price хранится как string => нужно конвертировать + price := std.Atoi(props["price"].(string)) + if amount < price { + panic("insufficient payment") + } + + // снимаем с продажи + storage.Delete(ctx, saleKey) + // передаем NFT покупателю + contract.Call(nftHash, "transfer", contract.All, from, tokenId, nil) +} + +// List вернет массив token_id, которые сейчас на продаже +func List() []string { + ctx := storage.GetContext() + iter := storage.Find(ctx, []byte(sellPrefix), storage.KeysOnly) + var result []string + for iterator.Next(iter) { + key := iterator.Key(iter).([]byte) + tokenId := key[len(sellPrefix):] + result = append(result, string(tokenId)) + } + return result +} + +// TransferTokens позволяет владельцу контракта забрать токены с контракта. +func TransferTokens(to interop.Hash160, amount int) { + ctx := storage.GetContext() + owner := storage.Get(ctx, []byte(ownerKey)).(interop.Hash160) + if !runtime.CheckWitness(owner) { + panic("not an owner") + } + myToken := storage.Get(ctx, []byte(tokenKey)).(interop.Hash160) + contract.Call(myToken, "transfer", contract.All, runtime.GetExecutingScriptHash(), to, amount, nil) +} diff --git a/Web3-_--main/contracts/nep11/OnlyFansNFT.py b/Web3-_--main/contracts/nep11/OnlyFansNFT.py new file mode 100644 index 0000000..625dedf --- /dev/null +++ b/Web3-_--main/contracts/nep11/OnlyFansNFT.py @@ -0,0 +1,135 @@ +from typing import Any, Dict, List +from boa3.builtin import public +from boa3.builtin.contract import abort +from boa3.builtin.interop.runtime import check_witness +from boa3.builtin.interop.storage import get, put, delete, find, FindOptions +from boa3.builtin.type import UInt160 + +TOKEN_PREFIX = b'token_' +OWNER_PREFIX = b'owner_' +SUPPLY_KEY = b'totalSupply' +ADMIN_KEY = b'admin' + +@public +def symbol() -> str: + return "OFNFT" # OnlyFans NFT + +@public +def name() -> str: + return "Web3OnlyFansNFT" + +@public +def totalSupply() -> int: + return get(SUPPLY_KEY).to_int() + +@public +def balanceOf(owner: UInt160) -> int: + return len(_getTokensOf(owner)) + +@public +def ownerOf(token_id: str) -> UInt160: + token_data = get(TOKEN_PREFIX + token_id.encode()) + if not token_data: + abort() + info = _deserialize_token(token_data) + return info['owner'] + +@public +def tokensOf(owner: UInt160) -> List[str]: + return _getTokensOf(owner) + +@public +def properties(token_id: str) -> Dict[str, Any]: + token_data = get(TOKEN_PREFIX + token_id.encode()) + if not token_data: + abort() + return _deserialize_token(token_data) + +@public +def transfer(to: UInt160, token_id: str, data: Any) -> bool: + if len(to) != 20: + abort() + + token_info = _readToken(token_id) + from_addr = token_info['owner'] + + if not check_witness(from_addr): + abort() + + if from_addr == to: + return True + + _removeTokenOf(from_addr, token_id) + _addTokenOf(to, token_id) + + token_info['owner'] = to + put(TOKEN_PREFIX + token_id.encode(), _serialize_token(token_info)) + return True + +@public +def mint(token_id: str, name: str, blurred_ref: str, full_ref: str, price: int) -> bool: + admin = get(ADMIN_KEY) + if not admin or len(admin) != 20: + abort() + + if not check_witness(admin.to_uint160()): + abort() + + existing = get(TOKEN_PREFIX + token_id.encode()) + if existing: + abort() # уже есть + + owner = admin.to_uint160() + + info = { + "owner": owner, + "name": name, + "blurred_ref": blurred_ref, + "full_ref": full_ref, + "price": price + } + put(TOKEN_PREFIX + token_id.encode(), _serialize_token(info)) + + current_supply = totalSupply() + put(SUPPLY_KEY, current_supply + 1) + _addTokenOf(owner, token_id) + + return True + +@public +def deploy(admin: UInt160): + if totalSupply() != 0: + return + put(ADMIN_KEY, admin) + +def _readToken(token_id: str) -> Dict[str, Any]: + token_data = get(TOKEN_PREFIX + token_id.encode()) + if not token_data: + abort() + return _deserialize_token(token_data) + +def _getTokensOf(owner: UInt160) -> List[str]: + prefix = OWNER_PREFIX + owner + result: List[str] = [] + iterator = find(prefix, options=FindOptions.KeysOnly) + while iterator.next(): + key = iterator.value + token_id = key[len(prefix):].decode() + result.append(token_id) + return result + +def _addTokenOf(owner: UInt160, token_id: str): + owner_key = OWNER_PREFIX + owner + token_id.encode() + put(owner_key, b'1') + +def _removeTokenOf(owner: UInt160, token_id: str): + owner_key = OWNER_PREFIX + owner + token_id.encode() + delete(owner_key) + +def _serialize_token(token_info: Dict[str, Any]) -> bytes: + from boa3.builtin import serialize + return serialize(token_info) + +def _deserialize_token(data: bytes) -> Dict[str, Any]: + from boa3.builtin import deserialize + return deserialize(data) diff --git a/Web3-_--main/contracts/nep17/MyNeoToken.py b/Web3-_--main/contracts/nep17/MyNeoToken.py new file mode 100644 index 0000000..0e3c26f --- /dev/null +++ b/Web3-_--main/contracts/nep17/MyNeoToken.py @@ -0,0 +1,77 @@ +from typing import Any +from boa3.builtin import public +from boa3.builtin.contract import Nep17TransferEvent, abort +from boa3.builtin.interop.contract import call_contract +from boa3.builtin.interop.runtime import calling_script_hash, check_witness, executing_script_hash +from boa3.builtin.interop.storage import get, put +from boa3.builtin.type import UInt160 + +# ------------------------------------------------------ +# Конфигурация токена +# ------------------------------------------------------ +TOKEN_SYMBOL = 'MYNEO' +TOKEN_DECIMALS = 0 + +TOTAL_SUPPLY_KEY = b'totalSupply' +BALANCE_PREFIX = b'balance_' +ADMIN_KEY = b'admin' # владелец смарт-контракта + +on_transfer = Nep17TransferEvent + +@public +def symbol() -> str: + return TOKEN_SYMBOL + +@public +def decimals() -> int: + return TOKEN_DECIMALS + +@public +def totalSupply() -> int: + return get(TOTAL_SUPPLY_KEY).to_int() + +@public +def balanceOf(account: UInt160) -> int: + return get(BALANCE_PREFIX + account).to_int() + +@public +def transfer(from_addr: UInt160, to_addr: UInt160, amount: int, data: Any) -> bool: + if amount < 0: + abort() + + if not check_witness(from_addr): + abort() + + if len(to_addr) != 20: + abort() + + from_balance = balanceOf(from_addr) + if from_balance < amount: + abort() + + if from_addr != to_addr and amount != 0: + put(BALANCE_PREFIX + from_addr, from_balance - amount) + to_balance = balanceOf(to_addr) + put(BALANCE_PREFIX + to_addr, to_balance + amount) + + on_transfer(from_addr, to_addr, amount) + + if data is not None: + # Вызываем onNEP17Payment, если у целевого контракта есть такой метод + call_contract(to_addr, 'onNEP17Payment', [from_addr, amount, data]) + + return True + +@public +def deploy(admin: UInt160): + """ + При первом деплое: устанавливаем админа и минтим 100_000_000 токенов. + """ + if totalSupply() != 0: + return # уже инициализировано + + put(ADMIN_KEY, admin) + minted = 100_000_000 + put(TOTAL_SUPPLY_KEY, minted) + put(BALANCE_PREFIX + admin, minted) + on_transfer(None, admin, minted) diff --git a/Web3-_--main/services/api/Dockerfile b/Web3-_--main/services/api/Dockerfile new file mode 100644 index 0000000..a005975 --- /dev/null +++ b/Web3-_--main/services/api/Dockerfile @@ -0,0 +1,18 @@ +# Dockerfile для API-сервиса +FROM golang:1.20 as builder + +WORKDIR /app +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . +RUN CGO_ENABLED=0 GOOS=linux go build -o /app/bin/api main.go + +# Финальный образ +FROM alpine:3.17 +WORKDIR /app +COPY --from=builder /app/bin/api /app/api +COPY config.yaml /app/config.yaml +EXPOSE 8080 + +ENTRYPOINT ["/app/api"] diff --git a/Web3-_--main/services/api/config.yaml b/Web3-_--main/services/api/config.yaml new file mode 100644 index 0000000..03d093e --- /dev/null +++ b/Web3-_--main/services/api/config.yaml @@ -0,0 +1,10 @@ +listenAddr: ":8080" +logLevel: "debug" + +neoRPC: "http://localhost:20332" +walletPath: "/app/wallets/dev.wallet.json" +walletPass: "dev-pass" + +nftContractHash: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" +marketContractHash: "cccccccccccccccccccccccccccccccccccccccc" +tokenContractHash: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" diff --git a/Web3-_--main/services/api/go.mod b/Web3-_--main/services/api/go.mod new file mode 100644 index 0000000..67067f5 --- /dev/null +++ b/Web3-_--main/services/api/go.mod @@ -0,0 +1,9 @@ +module web3-onlyfans/services/api + +go 1.20 + +require ( + github.com/gorilla/mux v1.8.0 + github.com/nspcc-dev/neo-go v0.99.0 + # ... +) diff --git a/Web3-_--main/services/api/internal/handlers/market_handler.go b/Web3-_--main/services/api/internal/handlers/market_handler.go new file mode 100644 index 0000000..b387eea --- /dev/null +++ b/Web3-_--main/services/api/internal/handlers/market_handler.go @@ -0,0 +1,81 @@ +package handlers + +import ( + "encoding/json" + "math/big" + "net/http" + "web3-onlyfans/services/api/internal/neo" + + "github.com/nspcc-dev/neo-go/pkg/util" + "web3-onlyfans/services/api/internal/utils" +) + +func MarketListHandler(w http.ResponseWriter, r *http.Request, neoCli *neo.NeoClient, logger utils.Logger, cfg *utils.Config) { + marketHash, err := util.Uint160DecodeStringLE(cfg.MarketContractHash) + if err != nil { + http.Error(w, "invalid market hash", http.StatusInternalServerError) + return + } + + invRes, err := neoCli.Actor.Reader().InvokeCall(marketHash, "List", nil, nil) + if err != nil { + logger.Errorf("invoke List error: %v", err) + http.Error(w, "market list error", http.StatusInternalServerError) + return + } + + tokenList, err := utils.DecodeStringArray(invRes.Stack[0]) + if err != nil { + logger.Errorf("decode array error: %v", err) + http.Error(w, "decode array error", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]any{ + "status": "ok", + "tokens": tokenList, + }) +} + +type MarketBuyRequest struct { + TokenID string `json:"token_id"` + Amount int64 `json:"amount"` +} + +func MarketBuyHandler(w http.ResponseWriter, r *http.Request, neoCli *neo.NeoClient, logger utils.Logger, cfg *utils.Config) { + var req MarketBuyRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "invalid JSON", http.StatusBadRequest) + return + } + + tokenHash, err := util.Uint160DecodeStringLE(cfg.TokenContractHash) + if err != nil { + http.Error(w, "invalid token contract hash", http.StatusInternalServerError) + return + } + + // Вызываем transfer NEP-17: (from=Actor, to=market, amount=req.Amount, data=req.TokenID) + dataBytes := []byte(req.TokenID) + amt := big.NewInt(req.Amount) + txHash, err := neoCli.Actor.Call(tokenHash, "transfer", []any{ + neoCli.Actor.Sender(), // from + cfg.MarketContractHash, // to + amt, // amount + dataBytes, // data + }, nil) + if err != nil { + logger.Errorf("market buy transfer error: %v", err) + http.Error(w, "market buy error", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]any{ + "status": "ok", + "txHash": txHash.StringBE(), + "tokenID": req.TokenID, + "amount": req.Amount, + }) +} diff --git a/Web3-_--main/services/api/internal/handlers/nft_handler.go b/Web3-_--main/services/api/internal/handlers/nft_handler.go new file mode 100644 index 0000000..945cc83 --- /dev/null +++ b/Web3-_--main/services/api/internal/handlers/nft_handler.go @@ -0,0 +1,86 @@ +package handlers + +import ( + "encoding/json" + "github.com/nspcc-dev/neo-go/pkg/util" + "net/http" + "web3-onlyfans/services/api/internal/neo" + "web3-onlyfans/services/api/internal/utils" +) + +type MintNFTRequest struct { + TokenID string `json:"token_id"` + Name string `json:"name"` + BlurredRef string `json:"blurred_ref"` + FullRef string `json:"full_ref"` + Price int `json:"price"` +} + +func MintNFTHandler(w http.ResponseWriter, r *http.Request, neoCli *neo.NeoClient, logger utils.Logger, cfg *utils.Config) { + var req MintNFTRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "invalid JSON", http.StatusBadRequest) + return + } + + nftHash, err := util.Uint160DecodeStringLE(cfg.NftContractHash) + if err != nil { + http.Error(w, "invalid NFT hash", http.StatusInternalServerError) + return + } + + txHash, err := neoCli.Actor.Call(nftHash, "mint", []any{ + req.TokenID, + req.Name, + req.BlurredRef, + req.FullRef, + req.Price, + }, nil) + if err != nil { + logger.Errorf("mint call error: %v", err) + http.Error(w, "mint call error", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]any{ + "status": "ok", + "txHash": txHash.StringBE(), + "tokenID": req.TokenID, + }) +} + +func NFTPropertiesHandler(w http.ResponseWriter, r *http.Request, neoCli *neo.NeoClient, logger utils.Logger, cfg *utils.Config) { + tokenID := r.URL.Query().Get("token_id") + if tokenID == "" { + http.Error(w, "missing token_id", http.StatusBadRequest) + return + } + + nftHash, err := util.Uint160DecodeStringLE(cfg.NftContractHash) + if err != nil { + http.Error(w, "invalid NFT hash", http.StatusInternalServerError) + return + } + + invRes, err := neoCli.Actor.Reader().InvokeCall(nftHash, "properties", []any{tokenID}, nil) + if err != nil { + logger.Errorf("invoke call error: %v", err) + http.Error(w, "invoke call error", http.StatusInternalServerError) + return + } + + // Разбираем stackitem.Map + propsMap, err := utils.DecodeStringMap(invRes.Stack[0]) + if err != nil { + logger.Errorf("decode error: %v", err) + http.Error(w, "decode error", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]any{ + "status": "ok", + "props": propsMap, + }) +} diff --git a/Web3-_--main/services/api/internal/handlers/token_handler.go b/Web3-_--main/services/api/internal/handlers/token_handler.go new file mode 100644 index 0000000..4d69378 --- /dev/null +++ b/Web3-_--main/services/api/internal/handlers/token_handler.go @@ -0,0 +1,49 @@ +package handlers + +import ( + "encoding/json" + "net/http" + "web3-onlyfans/services/api/internal/neo" + + "github.com/nspcc-dev/neo-go/pkg/util" + "web3-onlyfans/services/api/internal/utils" +) + +func TokenBalanceHandler(w http.ResponseWriter, r *http.Request, neoCli *neo.NeoClient, logger utils.Logger, cfg *utils.Config) { + accountParam := r.URL.Query().Get("account") + if accountParam == "" { + accountParam = neoCli.Actor.Sender().StringLE() + } + + tokenHash, err := util.Uint160DecodeStringLE(cfg.TokenContractHash) + if err != nil { + http.Error(w, "invalid token hash", http.StatusInternalServerError) + return + } + + accHash, err := util.Uint160DecodeStringLE(accountParam) + if err != nil { + http.Error(w, "invalid account param", http.StatusBadRequest) + return + } + + invRes, err := neoCli.Actor.Reader().InvokeCall(tokenHash, "balanceOf", []any{accHash}, nil) + if err != nil { + logger.Errorf("balanceOf error: %v", err) + http.Error(w, "balanceOf error", http.StatusInternalServerError) + return + } + + balanceI, err := utils.DecodeBigInteger(invRes.Stack[0]) + if err != nil { + http.Error(w, "decode balance error: "+err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]any{ + "status": "ok", + "account": accountParam, + "balance": balanceI.String(), + }) +} diff --git a/Web3-_--main/services/api/internal/models/market.go b/Web3-_--main/services/api/internal/models/market.go new file mode 100644 index 0000000..97c1f39 --- /dev/null +++ b/Web3-_--main/services/api/internal/models/market.go @@ -0,0 +1,6 @@ +package models + +type MarketItem struct { + TokenID string `json:"token_id"` + OnSale bool `json:"on_sale"` +} diff --git a/Web3-_--main/services/api/internal/models/nft.go b/Web3-_--main/services/api/internal/models/nft.go new file mode 100644 index 0000000..fc727e9 --- /dev/null +++ b/Web3-_--main/services/api/internal/models/nft.go @@ -0,0 +1,9 @@ +package models + +type NFTProperties struct { + Owner string `json:"owner"` + Name string `json:"name"` + BlurredRef string `json:"blurred_ref"` + FullRef string `json:"full_ref"` + Price int `json:"price"` +} diff --git a/Web3-_--main/services/api/internal/neo/neo_client.go b/Web3-_--main/services/api/internal/neo/neo_client.go new file mode 100644 index 0000000..00812ff --- /dev/null +++ b/Web3-_--main/services/api/internal/neo/neo_client.go @@ -0,0 +1,44 @@ +package neo + +import ( + "context" + "fmt" + + "github.com/nspcc-dev/neo-go/pkg/rpcclient" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" + "github.com/nspcc-dev/neo-go/pkg/wallet" +) + +type NeoClient struct { + Actor *actor.Actor + RPC *rpcclient.Client + Context context.Context +} + +func NewNeoClient(rpcEndpoint, walletPath, walletPass string) (*NeoClient, error) { + ctx := context.Background() + + rpcCli, err := rpcclient.New(ctx, rpcEndpoint, rpcclient.Options{}) + if err != nil { + return nil, fmt.Errorf("rpcclient new: %w", err) + } + w, err := wallet.NewWalletFromFile(walletPath) + if err != nil { + return nil, fmt.Errorf("wallet load: %w", err) + } + acc := w.GetAccount(w.GetChangeAddress()) + err = acc.Decrypt(walletPass, w.Scrypt) + if err != nil { + return nil, fmt.Errorf("wallet decrypt: %w", err) + } + act, err := actor.NewSimple(rpcCli, acc) + if err != nil { + return nil, fmt.Errorf("actor new: %w", err) + } + + return &NeoClient{ + Actor: act, + RPC: rpcCli, + Context: ctx, + }, nil +} diff --git a/Web3-_--main/services/api/internal/utils/config.go b/Web3-_--main/services/api/internal/utils/config.go new file mode 100644 index 0000000..4b6c2f5 --- /dev/null +++ b/Web3-_--main/services/api/internal/utils/config.go @@ -0,0 +1,36 @@ +package utils + +import ( + "os" + + "io/ioutil" +) + +type Config struct { + ListenAddr string `yaml:"listenAddr"` + LogLevel string `yaml:"logLevel"` + NeoRPC string `yaml:"neoRPC"` + WalletPath string `yaml:"walletPath"` + WalletPass string `yaml:"walletPass"` + NftContractHash string `yaml:"nftContractHash"` + MarketContractHash string `yaml:"marketContractHash"` + TokenContractHash string `yaml:"tokenContractHash"` +} + +func LoadConfig(path string) (*Config, error) { + content, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + var cfg Config + if err := yaml.Unmarshal(content, &cfg); err != nil { + return nil, err + } + + // Можно переопределить через ENV + if envListen := os.Getenv("API_LISTEN_ADDR"); envListen != "" { + cfg.ListenAddr = envListen + } + + return &cfg, nil +} diff --git a/Web3-_--main/services/api/internal/utils/logger.go b/Web3-_--main/services/api/internal/utils/logger.go new file mode 100644 index 0000000..dd1abe7 --- /dev/null +++ b/Web3-_--main/services/api/internal/utils/logger.go @@ -0,0 +1,38 @@ +package utils + +import ( + "log" + "strings" +) + +type Logger interface { + Debugf(format string, v ...interface{}) + Infof(format string, v ...interface{}) + Errorf(format string, v ...interface{}) + Fatalf(format string, v ...interface{}) +} + +type SimpleLogger struct { + level string +} + +func NewLogger(level string) Logger { + return &SimpleLogger{level: strings.ToLower(level)} +} + +func (l *SimpleLogger) Debugf(format string, v ...interface{}) { + if l.level == "debug" { + log.Printf("[DEBUG] "+format, v...) + } +} +func (l *SimpleLogger) Infof(format string, v ...interface{}) { + if l.level == "debug" || l.level == "info" { + log.Printf("[INFO] "+format, v...) + } +} +func (l *SimpleLogger) Errorf(format string, v ...interface{}) { + log.Printf("[ERROR] "+format, v...) +} +func (l *SimpleLogger) Fatalf(format string, v ...interface{}) { + log.Fatalf("[FATAL] "+format, v...) +} diff --git a/Web3-_--main/services/api/internal/wallet/wallet.go b/Web3-_--main/services/api/internal/wallet/wallet.go new file mode 100644 index 0000000..8397744 --- /dev/null +++ b/Web3-_--main/services/api/internal/wallet/wallet.go @@ -0,0 +1,15 @@ +package wallet + +import ( + "fmt" + + "github.com/nspcc-dev/neo-go/pkg/wallet" +) + +func CreateNewWallet(path, password string) error { + w, err := wallet.NewWallet(path) + if err != nil { + return fmt.Errorf("wallet new: %w", err) + } + return nil +} diff --git a/Web3-_--main/services/api/main.go b/Web3-_--main/services/api/main.go new file mode 100644 index 0000000..5a0d02d --- /dev/null +++ b/Web3-_--main/services/api/main.go @@ -0,0 +1,66 @@ +package main + +import ( + "log" + "net/http" + "os" + + "web3-onlyfans/services/api/internal/handlers" + "web3-onlyfans/services/api/internal/neo" + "web3-onlyfans/services/api/internal/utils" + + "github.com/gorilla/mux" +) + +func main() { + // Загружаем конфиг + configPath := os.Getenv("API_CONFIG_PATH") + if configPath == "" { + configPath = "./config.yaml" + } + cfg, err := utils.LoadConfig(configPath) + if err != nil { + log.Fatalf("failed to load config: %v", err) + } + + // Инициализируем логгер + logger := utils.NewLogger(cfg.LogLevel) + + // Инициализируем NeoClient (RPC + кошелёк) + neoCli, err := neo.NewNeoClient(cfg.NeoRPC, cfg.WalletPath, cfg.WalletPass) + if err != nil { + logger.Fatalf("failed to init neo client: %v", err) + } + + // Роуты + r := mux.NewRouter() + // NFT endpoints + r.HandleFunc("/nft/mint", func(w http.ResponseWriter, r *http.Request) { + handlers.MintNFTHandler(w, r, neoCli, logger, cfg) + }).Methods("POST") + + r.HandleFunc("/nft/properties", func(w http.ResponseWriter, r *http.Request) { + handlers.NFTPropertiesHandler(w, r, neoCli, logger, cfg) + }).Methods("GET") + + // Market endpoints + r.HandleFunc("/market/list", func(w http.ResponseWriter, r *http.Request) { + handlers.MarketListHandler(w, r, neoCli, logger, cfg) + }).Methods("GET") + + r.HandleFunc("/market/buy", func(w http.ResponseWriter, r *http.Request) { + handlers.MarketBuyHandler(w, r, neoCli, logger, cfg) + }).Methods("POST") + + // Token endpoints (например, посмотреть баланс) + r.HandleFunc("/token/balance", func(w http.ResponseWriter, r *http.Request) { + handlers.TokenBalanceHandler(w, r, neoCli, logger, cfg) + }).Methods("GET") + + // Запуск сервера + addr := cfg.ListenAddr + logger.Infof("API starting on %s", addr) + if err := http.ListenAndServe(addr, r); err != nil { + logger.Fatalf("server error: %v", err) + } +} diff --git a/Web3-_--main/services/api/scripts/local_run.sh b/Web3-_--main/services/api/scripts/local_run.sh new file mode 100644 index 0000000..4fe0ba0 --- /dev/null +++ b/Web3-_--main/services/api/scripts/local_run.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +# Локальный запуск API + +export API_CONFIG_PATH=./config.yaml +go run main.go diff --git a/Web3-_--main/services/frostfs-uploader/Dockerfile b/Web3-_--main/services/frostfs-uploader/Dockerfile new file mode 100644 index 0000000..d182cb8 --- /dev/null +++ b/Web3-_--main/services/frostfs-uploader/Dockerfile @@ -0,0 +1,14 @@ +FROM golang:1.20 as builder +WORKDIR /app +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . +RUN CGO_ENABLED=0 GOOS=linux go build -o /app/bin/uploader main.go + +FROM alpine:3.17 +WORKDIR /app +COPY --from=builder /app/bin/uploader /app/uploader +COPY config.yaml /app/config.yaml +EXPOSE 8081 +ENTRYPOINT ["/app/uploader"] diff --git a/Web3-_--main/services/frostfs-uploader/config.yaml b/Web3-_--main/services/frostfs-uploader/config.yaml new file mode 100644 index 0000000..24d11df --- /dev/null +++ b/Web3-_--main/services/frostfs-uploader/config.yaml @@ -0,0 +1,5 @@ +listenAddr: ":8081" +logLevel: "info" +frostfsEndpoint: "grpcs://localhost:8080" +frostfsPrivKey: "0000000000000000000000000000000..." +frostfsContainer: "containerID-here" diff --git a/Web3-_--main/services/frostfs-uploader/go.mod b/Web3-_--main/services/frostfs-uploader/go.mod new file mode 100644 index 0000000..21a6972 --- /dev/null +++ b/Web3-_--main/services/frostfs-uploader/go.mod @@ -0,0 +1,9 @@ +module web3-onlyfans/services/frostfs-uploader + +go 1.20 + +require ( + github.com/gorilla/mux v1.8.0 + github.com/nspcc-dev/neofs-sdk-go v1.2.1 + # ... +) diff --git a/Web3-_--main/services/frostfs-uploader/internal/frostuploader/uploader.go b/Web3-_--main/services/frostfs-uploader/internal/frostuploader/uploader.go new file mode 100644 index 0000000..f2085a4 --- /dev/null +++ b/Web3-_--main/services/frostfs-uploader/internal/frostuploader/uploader.go @@ -0,0 +1,28 @@ +package frostuploader + +import ( + "context" + "io" + + "github.com/nspcc-dev/neofs-sdk-go/client" + "github.com/nspcc-dev/neofs-sdk-go/client/object" + "github.com/nspcc-dev/neofs-sdk-go/container" +) + +func UploadFile(ctx context.Context, cli *client.Client, cntr container.ID, data []byte) (string, error) { + obj := object.New() + obj.SetPayload(data) + + writer, err := cli.ObjectPutInit(ctx, cntr, obj) + if err != nil { + return "", err + } + if _, err := writer.Write(data); err != nil { + return "", err + } + oid, err := writer.Close() + if err != nil { + return "", err + } + return oid.String(), nil +} diff --git a/Web3-_--main/services/frostfs-uploader/internal/handlers/upload_handler.go b/Web3-_--main/services/frostfs-uploader/internal/handlers/upload_handler.go new file mode 100644 index 0000000..c376df7 --- /dev/null +++ b/Web3-_--main/services/frostfs-uploader/internal/handlers/upload_handler.go @@ -0,0 +1,65 @@ +package handlers + +import ( + "context" + "encoding/json" + "io" + "net/http" + + "github.com/nspcc-dev/neofs-sdk-go/client" + "github.com/nspcc-dev/neofs-sdk-go/container" + "web3-onlyfans/services/frostfs-uploader/internal/frostuploader" + "web3-onlyfans/services/frostfs-uploader/internal/utils" +) + +func UploadHandler(w http.ResponseWriter, r *http.Request, cfg *utils.Config, logger utils.Logger) { + file, header, err := r.FormFile("file") + if err != nil { + http.Error(w, "no file found", http.StatusBadRequest) + return + } + defer file.Close() + + data, err := io.ReadAll(file) + if err != nil { + http.Error(w, "read file error", http.StatusInternalServerError) + return + } + + // Подключаемся к FrostFS + cli, err := client.New(client.WithDefaultPrivateKeyStr(cfg.FrostfsPrivKey)) + if err != nil { + logger.Errorf("client init: %v", err) + http.Error(w, "FrostFS init error", http.StatusInternalServerError) + return + } + defer cli.Close() + + if err = cli.Dial(cfg.FrostfsEndpoint); err != nil { + logger.Errorf("dial error: %v", err) + http.Error(w, "FrostFS dial error", http.StatusInternalServerError) + return + } + + cntr, err := container.IDFromString(cfg.FrostfsContainer) + if err != nil { + logger.Errorf("invalid container: %v", err) + http.Error(w, "invalid container", http.StatusInternalServerError) + return + } + + oid, err := frostuploader.UploadFile(context.Background(), cli, cntr, data) + if err != nil { + logger.Errorf("upload error: %v", err) + http.Error(w, "upload error", http.StatusInternalServerError) + return + } + + resp := map[string]any{ + "status": "ok", + "object_id": oid, + "filename": header.Filename, + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(resp) +} diff --git a/Web3-_--main/services/frostfs-uploader/main.go b/Web3-_--main/services/frostfs-uploader/main.go new file mode 100644 index 0000000..8e0252e --- /dev/null +++ b/Web3-_--main/services/frostfs-uploader/main.go @@ -0,0 +1,32 @@ +package main + +import ( + "log" + "net/http" + "os" + + "github.com/gorilla/mux" + "web3-onlyfans/services/frostfs-uploader/internal/utils" + "web3-onlyfans/services/frostfs-uploader/internal/handlers" +) + +func main() { + cfgPath := os.Getenv("FROSTFS_UPLOADER_CONFIG") + if cfgPath == "" { + cfgPath = "./config.yaml" + } + cfg, err := utils.LoadConfig(cfgPath) + if err != nil { + log.Fatalf("failed to load config: %v", err) + } + logger := utils.NewLogger(cfg.LogLevel) + + r := mux.NewRouter() + r.HandleFunc("/upload", func(w http.ResponseWriter, r *http.Request) { + handlers.UploadHandler(w, r, cfg, logger) + }).Methods("POST") + + addr := cfg.ListenAddr + logger.Infof("frostfs-uploader on %s", addr) + log.Fatal(http.ListenAndServe(addr, r)) +} diff --git a/Web3-_--main/services/frostfs-uploader/scripts/local_run.sh b/Web3-_--main/services/frostfs-uploader/scripts/local_run.sh new file mode 100644 index 0000000..71fbb36 --- /dev/null +++ b/Web3-_--main/services/frostfs-uploader/scripts/local_run.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +export FROSTFS_UPLOADER_CONFIG=./config.yaml +go run main.go diff --git a/Web3-_--main/services/indexer/Dockerfile b/Web3-_--main/services/indexer/Dockerfile new file mode 100644 index 0000000..cebe06b --- /dev/null +++ b/Web3-_--main/services/indexer/Dockerfile @@ -0,0 +1,14 @@ +FROM golang:1.20 as builder + +WORKDIR /app +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . +RUN CGO_ENABLED=0 GOOS=linux go build -o /app/bin/indexer main.go + +FROM alpine:3.17 +WORKDIR /app +COPY --from=builder /app/bin/indexer /app/indexer +COPY config.yaml /app/config.yaml +ENTRYPOINT ["/app/indexer"] \ No newline at end of file diff --git a/Web3-_--main/services/indexer/config.yaml b/Web3-_--main/services/indexer/config.yaml new file mode 100644 index 0000000..577eb2b --- /dev/null +++ b/Web3-_--main/services/indexer/config.yaml @@ -0,0 +1,4 @@ +neoRPC: "http://localhost:20332" +logLevel: "info" +dbDsn: "postgres://indexer:password@postgres:5432/indexerdb?sslmode=disable" +pollIntervalSeconds: 3 \ No newline at end of file diff --git a/Web3-_--main/services/indexer/go.sum.go b/Web3-_--main/services/indexer/go.sum.go new file mode 100644 index 0000000..c5d582c --- /dev/null +++ b/Web3-_--main/services/indexer/go.sum.go @@ -0,0 +1 @@ +package indexer diff --git a/Web3-_--main/services/indexer/internal/db/migrations/001_init.sql b/Web3-_--main/services/indexer/internal/db/migrations/001_init.sql new file mode 100644 index 0000000..9670dbf --- /dev/null +++ b/Web3-_--main/services/indexer/internal/db/migrations/001_init.sql @@ -0,0 +1,14 @@ +CREATE TABLE IF NOT EXISTS blocks ( + height BIGINT PRIMARY KEY, + time TIMESTAMP NOT NULL +); + +CREATE TABLE IF NOT EXISTS nft_transfers ( + id SERIAL PRIMARY KEY, + block_height BIGINT NOT NULL, + tx_hash VARCHAR(66) NOT NULL, + token_id TEXT NOT NULL, + from_addr VARCHAR(42), + to_addr VARCHAR(42), + timestamp TIMESTAMP NOT NULL + ); \ No newline at end of file diff --git a/Web3-_--main/services/indexer/internal/db/repository.go b/Web3-_--main/services/indexer/internal/db/repository.go new file mode 100644 index 0000000..ab37180 --- /dev/null +++ b/Web3-_--main/services/indexer/internal/db/repository.go @@ -0,0 +1,43 @@ +package db + +import ( + "database/sql" + "fmt" + _ "github.com/lib/pq" +) + +type Repository struct { + db *sql.DB +} + +func NewRepository(dsn string) (*Repository, error) { + db, err := sql.Open("postgres", dsn) + if err != nil { + return nil, err + } + if err := db.Ping(); err != nil { + return nil, err + } + return &Repository{db: db}, nil +} + +func (r *Repository) GetLastIndexedBlock() (int64, error) { + var height int64 + err := r.db.QueryRow(`SELECT height FROM blocks ORDER BY height DESC LIMIT 1`).Scan(&height) + if err == sql.ErrNoRows { + return 0, nil + } + return height, err +} + +func (r *Repository) SaveBlock(height int64) error { + _, err := r.db.Exec(`INSERT INTO blocks(height,time) VALUES($1,NOW())`, height) + return err +} + +func (r *Repository) SaveNFTTransfer(txHash string, blockHeight int64, tokenId, from, to string) error { + _, err := r.db.Exec(`INSERT INTO nft_transfers(block_height, tx_hash, token_id, from_addr, to_addr, timestamp) + VALUES($1, $2, $3, $4, $5, NOW())`, + blockHeight, txHash, tokenId, from, to) + return err +} diff --git a/Web3-_--main/services/indexer/internal/models/indexer_models.go b/Web3-_--main/services/indexer/internal/models/indexer_models.go new file mode 100644 index 0000000..008d063 --- /dev/null +++ b/Web3-_--main/services/indexer/internal/models/indexer_models.go @@ -0,0 +1,16 @@ +package models + +type BlockRecord struct { + Height int64 `json:"height"` + Time string `json:"time"` +} + +type NftTransferRecord struct { + ID int64 `json:"id"` + BlockHeight int64 `json:"block_height"` + TxHash string `json:"tx_hash"` + TokenId string `json:"token_id"` + FromAddr string `json:"from_addr"` + ToAddr string `json:"to_addr"` + Timestamp string `json:"timestamp"` +} diff --git a/Web3-_--main/services/indexer/internal/subscriber/events.go b/Web3-_--main/services/indexer/internal/subscriber/events.go new file mode 100644 index 0000000..b0b3512 --- /dev/null +++ b/Web3-_--main/services/indexer/internal/subscriber/events.go @@ -0,0 +1,51 @@ +package subscriber + +import ( + "context" + "time" + "web3-onlyfans/services/indexer/internal/utils" + + "github.com/nspcc-dev/neo-go/pkg/rpcclient" +) + +type BlockSubscriber struct { + cfg *utils.Config + logger utils.Logger + rpc *rpcclient.Client +} + +func NewBlockSubscriber(cfg *utils.Config, logger utils.Logger) (*BlockSubscriber, error) { + cli, err := rpcclient.New(context.Background(), cfg.NeoRPC, rpcclient.Options{}) + if err != nil { + return nil, err + } + return &BlockSubscriber{ + cfg: cfg, + logger: logger, + rpc: cli, + }, nil +} + +func (s *BlockSubscriber) Start() { + pollInterval := time.Duration(s.cfg.PollIntervalSeconds) * time.Second + for { + err := s.pollOnce() + if err != nil { + s.logger.Errorf("poll error: %v", err) + } + time.Sleep(pollInterval) + } +} + +func (s *BlockSubscriber) pollOnce() error { + // Здесь логика: узнаём текущий блок, сверяемся с локальным бд, + // проходимся по новым блокам, анализируем транзакции/нотификации. + + height, err := s.rpc.GetBlockCount() + if err != nil { + return err + } + s.logger.Debugf("current block count: %d", height) + // ... далее - обработка новых блоков (Tx, Notifications, Transfers). + return nil +} diff --git a/Web3-_--main/services/indexer/internal/utils/config.go b/Web3-_--main/services/indexer/internal/utils/config.go new file mode 100644 index 0000000..9da535e --- /dev/null +++ b/Web3-_--main/services/indexer/internal/utils/config.go @@ -0,0 +1,36 @@ +package utils + +import ( + "io/ioutil" + "os" + "strconv" + + "gopkg.in/yaml.v2" +) + +type Config struct { + NeoRPC string `yaml:"neoRPC"` + LogLevel string `yaml:"logLevel"` + DBDsn string `yaml:"dbDsn"` + PollIntervalSeconds int `yaml:"pollIntervalSeconds"` +} + +func LoadConfig(path string) (*Config, error) { + data, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + var c Config + if err := yaml.Unmarshal(data, &c); err != nil { + return nil, err + } + + // Переопределения через ENV + if e := os.Getenv("INDEXER_POLL_INTERVAL"); e != "" { + if val, err := strconv.Atoi(e); err == nil { + c.PollIntervalSeconds = val + } + } + + return &c, nil +} diff --git a/Web3-_--main/services/indexer/internal/utils/logger.go b/Web3-_--main/services/indexer/internal/utils/logger.go new file mode 100644 index 0000000..61595bd --- /dev/null +++ b/Web3-_--main/services/indexer/internal/utils/logger.go @@ -0,0 +1,23 @@ +package utils + +import ( + "log" + "strings" +) + +type Logger interface { + Debugf(format string, v ...interface{}) + Infof(format string, v ...interface{}) + Errorf(format string, v ...interface{}) + Fatalf(format string, v ...interface{}) +} + +type SimpleLogger struct { + level string +} + +func NewLogger(level string) Logger { + return &SimpleLogger{level: strings.ToLower(level)} +} + +// ... тот же код, что в API diff --git a/Web3-_--main/services/indexer/main.go b/Web3-_--main/services/indexer/main.go new file mode 100644 index 0000000..5005cb4 --- /dev/null +++ b/Web3-_--main/services/indexer/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "log" + "web3-onlyfans/services/indexer/internal/subscriber" + "web3-onlyfans/services/indexer/internal/utils" +) + +func main() { + cfg, err := utils.LoadConfig("./config.yaml") + if err != nil { + log.Fatalf("failed to load config: %v", err) + } + logger := utils.NewLogger(cfg.LogLevel) + + sub, err := subscriber.NewBlockSubscriber(cfg, logger) + if err != nil { + logger.Fatalf("failed to create subscriber: %v", err) + } + + logger.Infof("Indexer started, polling blocks from %s", cfg.NeoRPC) + sub.Start() +} diff --git a/Web3-_--main/services/indexer/scripts/local_run.sh b/Web3-_--main/services/indexer/scripts/local_run.sh new file mode 100644 index 0000000..4a7cd5e --- /dev/null +++ b/Web3-_--main/services/indexer/scripts/local_run.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +export INDEXER_CONFIG_PATH=./config.yaml +go run main.go \ No newline at end of file