Skip to content

feat: wallet connect + direct trading via gTrade (Closes #14)#24

Merged
e35ventura merged 7 commits intoentrius:mainfrom
MkDev11:feature/issue-14-wallet-gtrade
Mar 10, 2026
Merged

feat: wallet connect + direct trading via gTrade (Closes #14)#24
e35ventura merged 7 commits intoentrius:mainfrom
MkDev11:feature/issue-14-wallet-gtrade

Conversation

@MkDev11
Copy link
Copy Markdown
Contributor

@MkDev11 MkDev11 commented Mar 9, 2026

Summary

Add wallet connection and direct trading via gTrade (Gains Network) to the Tide Chart dashboard, closing the loop from Synth forecast intelligence to on-chain DeFi execution — all in one workflow, without leaving the site.

Users can now:

  1. View probabilistic forecasts for all Synth API assets — equities (SPY, NVDA, TSLA, AAPL, GOOGL), crypto (BTC, ETH, SOL), and gold (XAU)
  2. Connect their wallet (MetaMask or any EIP-1193 provider)
  3. Click Trade on any asset row to jump straight to the trade form with that asset pre-selected
  4. See real-time protocol guards — the Execute button is disabled with a clear reason if the trade would be rejected by gTrade
  5. Configure Take Profit, Stop Loss, and slippage before executing
  6. Open a leveraged long/short position directly through gTrade on Arbitrum One
  7. Monitor open positions from within the dashboard

What's New

Backend (gtrade.py — new file, 230 lines)

  • Pair mapping: Tide Chart tickers → gTrade pair names and group indices for all 9 Synth API assets (crypto group 0, stocks group 3, commodities group 4)
  • Per-group protocol limits (GROUP_LIMITS): min/max leverage, min position size ($1,500), max collateral — enforced server-side
  • Chain & contract configuration: Arbitrum One (42161), gTrade Diamond proxy, native USDC
  • Trade parameter validation: asset, direction, leverage (2-150x), collateral ($5-$100k USDC), minimum position size
  • Trade summary builder with computed position size
  • gTrade backend API proxy with 5-minute cache for trading variables
  • Open trades fetcher for wallet address
  • Pair index resolver using live gTrade backend data

Frontend (static/trading.js — new file, 596 lines)

  • Wallet connect/disconnect toggle via ethers.BrowserProvider (ethers.js v6)
  • Automatic Arbitrum chain switching (wallet_switchEthereumChain / wallet_addEthereumChain)
  • Silent auto-reconnect on page load via eth_accounts (no popup)
  • Live USDC balance display via on-chain balanceOf call
  • selectTradeAsset() — per-row Trade buttons scroll to form and pre-select the asset (works without wallet connected)
  • Trade form with pair dropdown, direction, leverage, collateral, Take Profit (%), Stop Loss (%), and configurable max slippage (0.1-5%, default 1%)
  • Client-side protocol guards (validateTradeClient) — Execute button is disabled with a descriptive reason whenever the trade would be rejected:
    • No wallet → "Connect Wallet"
    • Wrong chain → "Switch to Arbitrum"
    • Collateral empty/below $5/above max → reason shown
    • Leverage out of range → "Min Leverage: 2x" / "Max Leverage: 150x"
    • Position size below $1,500 → "Min Position: $1,500"
    • Insufficient USDC balance → "Insufficient USDC (have: $X.XX)"
  • Real-time position size display with warning color when below protocol minimum
  • Direction-aware Execute button styling (green for long, red for short)
  • Toast notification system: success/error/info toasts with auto-dismiss and HTML support (Arbiscan links)
  • Real-time trade preview: position size, direction badge, entry price, collateral, TP/SL levels, slippage, protocol info
  • Full trade execution flow:
    • Server-side validation → USDC allowance check → approve if needed → resolve pair index → openTrade on Diamond contract → tx confirmation with clickable Arbiscan link
  • Graceful fallback: if contract call fails, shows direct link to gTrade web app for the specific pair
  • Reactive listeners: accountsChanged and chainChanged events auto-update UI, balance, open positions, and trade button state
  • Open positions viewer: fetches and renders active trades from gTrade backend API

Dashboard (main.py — +211 lines)

  • Wallet & trading CSS: styled wallet button, chain badge, trade form, trade-row buttons, preview rows, toast notifications, position size display, direction-aware Execute button, open positions list
  • HTML sections: wallet connect button in header, per-row Trade buttons in rankings table (all 9 assets), trade form with TP/SL and slippage fields, position size display, toast container, open positions section
  • Script includes: ethers.js v6 CDN + trading.js via Flask static serving
  • 4 new Flask API routes:
    • GET /api/gtrade/config — contract addresses, pair mapping, leverage/collateral limits
    • POST /api/gtrade/validate-trade — server-side parameter validation with trade summary
    • GET /api/gtrade/resolve-pair?asset=SPY — resolves ticker to gTrade pair index via backend API
    • GET /api/gtrade/open-trades?address=0x... — proxies user's open positions from gTrade backend

Tests (tests/test_tool.py — +277 lines, 20 new tests)

  • test_gtrade_tradeable_assets — verifies 9 tradeable assets (equities + crypto + gold)
  • test_gtrade_is_tradeable — known + unknown assets
  • test_gtrade_validate_valid — valid params pass
  • test_gtrade_validate_invalid_asset — non-tradeable asset rejected
  • test_gtrade_validate_invalid_direction — invalid direction rejected
  • test_gtrade_validate_leverage_bounds — min/max leverage enforcement
  • test_gtrade_validate_collateral_bounds — min/max collateral enforcement
  • test_gtrade_build_trade_summary — summary fields and computed position size
  • test_gtrade_chain_config — chain ID, hex, name, RPC
  • test_gtrade_contract_config — contracts, pairs, limits
  • test_flask_gtrade_config_route — GET /api/gtrade/config returns correct shape
  • test_flask_gtrade_validate_trade_valid — valid trade returns summary with position_size_usd
  • test_flask_gtrade_validate_trade_invalid — invalid trade returns 400
  • test_flask_gtrade_validate_trade_below_min_position — position size below $1,500 rejected
  • test_flask_gtrade_resolve_pair_invalid — unknown asset returns 400
  • test_flask_gtrade_resolve_pair_valid — valid asset returns pair_index field
  • test_dashboard_html_contains_wallet_ui — HTML has wallet button, trade form, ethers.js, toast container, position size display, open positions
  • test_flask_gtrade_open_trades_invalid_address — invalid/missing address returns 400
  • test_fetch_open_trades_empty_address — empty address returns empty list

Documentation (README.md — updated)

  • Added Trading section explaining the full user flow (including Trade buttons, TP/SL, slippage)
  • Documented all 4 new API endpoints under "Trading (gTrade)" subsection (including open-trades)
  • Added "Trading Details" section: protocol, contract addresses, collateral, pairs, leverage range, configurable slippage, TP/SL, trade buttons, open positions, wallet support
  • Updated Technical Details with new dependencies (requests, ethers.js)

Related Issues

Closes #14

Type of Change

  • Bug fix
  • Improvement to existing tool
  • Documentation
  • Other (describe below)

Testing

  • Tested against Synth API
  • Manually tested
  • Tests added/updated
$ python3 -m flake8 gtrade.py main.py tests/test_tool.py
# 0 errors

$ python3 -m pytest tests/test_tool.py -v
# 54 passed in 1.54s (34 existing + 20 new)

Checklist

  • Code follows project style guidelines
  • Self-review completed
  • Changes are documented (if applicable)

Files Changed

File Status Lines Purpose
tools/tide-chart/gtrade.py New 230 gTrade backend module: pair mapping, per-group limits, validation, config, API proxy
tools/tide-chart/static/trading.js New 596 Frontend wallet + trading logic + protocol guards + toasts via ethers.js v6
tools/tide-chart/main.py Modified +211 CSS, HTML, trade buttons, position size display, toasts, 4 new Flask routes
tools/tide-chart/tests/test_tool.py Modified +277 20 new gTrade-specific tests
tools/tide-chart/README.md Modified +53/-4 Trading documentation (deliverable)
tools/tide-chart/.flake8 New 3 Lint config for inline HTML strings

Architecture

┌─────────────────────────────────────────────────────────┐
│                    Browser (Dashboard)                  │
│                                                         │
│  ┌──────────────┐  ┌──────────────┐  ┌───────────────┐  │
│  │ Forecast UI  │  │ Wallet UI    │  │ Trade Form    │  │
│  │ (Plotly)     │  │ (ethers.js)  │  │ (trading.js)  │  │
│  └──────┬───────┘  └──────┬───────┘  └───────┬───────┘  │
│         │                 │                   │         │
└─────────┼─────────────────┼───────────────────┼─────────┘
          │                 │                   │
          ▼                 ▼                   ▼
┌─────────────────┐  ┌────────────┐  ┌──────────────────┐
│  Flask Server   │  │  MetaMask  │  │ gTrade Diamond   │
│  (main.py)      │  │  Provider  │  │ (Arbitrum One)   │
│                 │  └────────────┘  └──────────────────┘
│  /api/data      │                         ▲
│  /api/probability│                        │
│  /api/gtrade/*  │─── gtrade.py ──────────►│
└────────┬────────┘       │
         │                ▼
         │         ┌────────────────┐
         │         │ gTrade Backend │
         │         │ (REST API)     │
         ▼         └────────────────┘
   ┌───────────┐
   │ Synth API │
   └───────────┘

Demo Video

https://www.loom.com/share/818c190f102842688aa6dd3234acd72e?t=95

@MkDev11 MkDev11 force-pushed the feature/issue-14-wallet-gtrade branch 5 times, most recently from 2651610 to 0e2ac6f Compare March 9, 2026 08:59
@ventura-oss
Copy link
Copy Markdown

I've updated the original issue (#14) to include all assets supported by the Synth API (Crypto, Gold, and Equities).

The live position tracker is a great addition! To get this to a mergeable state, the trading logic needs one final refinement: the UI should never allow a submission of a transaction that gTrade wouldn't accept. This means build-in guards to prevent things like leverage mismatches or orders that are too small/too large for the protocol. If the protocol won't accept the trade, the UI shouldn't even allow the 'Execute' button to be clicked.

@MkDev11 MkDev11 force-pushed the feature/issue-14-wallet-gtrade branch 7 times, most recently from 8873ac5 to 31364bd Compare March 10, 2026 00:02
@MkDev11
Copy link
Copy Markdown
Contributor Author

MkDev11 commented Mar 10, 2026

Fixed. The openTrade call had an incorrect ABI — wrong Trade struct layout, wrong number of parameters, and wrong precision values.

Changes

  • ABI: Corrected ITradingStorage.Trade struct to match the on-chain contract (verified function selector 0x5bfcc4f8 via OpenChain + keccak256)
  • Params: openTrade(trade, maxSlippageP, referrer) — was incorrectly passing 4 args instead of 3
  • Field fixes: long (not buy), added isCounterTrade, positionSizeToken, maxClosingSlippageP; removed non-existent fields
  • Precision: collateralAmount → 1e18 (not 1e6), slippageP → 1e3 (not 1e1)

All verified against live data from backend-arbitrum.gains.trade/open-trades and ABI encoding test.

@ventura-oss
Copy link
Copy Markdown

Screenshot from 2026-03-09 19-39-48

error message execution reverted (unknown custom error) (action="estimateGas", data="0x10906acb", reason=null, transaction={ "data": "0x5bfcc4f800000000000000000000...

@MkDev11 MkDev11 force-pushed the feature/issue-14-wallet-gtrade branch 2 times, most recently from 367acf1 to 0d1f5bb Compare March 10, 2026 01:00
@e35ventura
Copy link
Copy Markdown
Collaborator

Please make sure you can place and monitor trades. Also, please upload an updated demo video that showcases the tool working end-to-end, opening and closing a trade

@MkDev11 MkDev11 force-pushed the feature/issue-14-wallet-gtrade branch from 0d1f5bb to a9b877e Compare March 10, 2026 01:22
Add MetaMask wallet connection and gTrade (Gains Network) trading
integration to the Tide Chart dashboard on Arbitrum One.

New features:
- Wallet connect/disconnect with any EIP-1193 provider (MetaMask, Coinbase, Rabby)
- Automatic chain switching to Arbitrum One
- USDC balance display and spend approval
- Trade button per equity row in rankings table (click to pre-fill trade form)
- Native trade form: pair selection, long/short, leverage (2-150x), collateral
- Take Profit and Stop Loss percentage inputs
- Configurable max slippage (0.1%-5%, default 1%)
- Live trade preview with position size calculation
- Direct on-chain trade execution via gTrade Diamond contract
- Open positions viewer via gTrade backend API
- Clickable Arbiscan tx links on trade success
- Graceful fallback to gTrade web app on contract errors
- Account/chain change listeners for reactive UI updates
- Server-side trade parameter validation

New files:
- gtrade.py: pair mapping, chain config, validation, API proxy with caching
- static/trading.js: wallet connection, trade execution, UI state management

Modified files:
- main.py: wallet/trading CSS, HTML, ethers.js CDN, 4 new Flask routes
- tests/test_tool.py: 18 new gTrade-specific tests
- README.md: updated with trading documentation (deliverable)

New API routes:
- GET /api/gtrade/config
- POST /api/gtrade/validate-trade
- GET /api/gtrade/resolve-pair
- GET /api/gtrade/open-trades

Tests: 52 passing (18 new gTrade-specific tests)
@MkDev11 MkDev11 force-pushed the feature/issue-14-wallet-gtrade branch from a9b877e to 4bf20dc Compare March 10, 2026 01:48
…to-refresh

- Fetch live price from Chainlink on-chain feeds (BTC, ETH, SOL, etc.)
  instead of stale cached prices, preventing MarketOpenCanceled errors
- Pass current market price as openPrice in trade struct (1e10 precision)
  to fix BelowMin() revert on market orders
- Use gasLimit override to bypass estimateGas/MetaMask simulation
  (oracle callbacks always revert in static call context)
- Close trade with Chainlink expectedPrice in 1e10 precision
- Auto-poll Open Positions after open/close tx (every 3s for 15s)
- Increase default max slippage from 1% to 1.5%
- Add current_price to resolve-pair API endpoint
@MkDev11
Copy link
Copy Markdown
Contributor Author

MkDev11 commented Mar 10, 2026

@ventura-oss
Copy link
Copy Markdown

Does not show P&L while position is "open" the trading flow should feel native to the dashboard. And all transactions trying to close a trade fail.
Screenshot from 2026-03-10 10-19-50
Screenshot from 2026-03-10 10-20-23

@ventura-oss
Copy link
Copy Markdown

I tested this PR locally and found a blocking issue where closing Synthetic positions (e.g. Stocks, Commodities) always reverts on the gTrade Diamond contract.

The Bug:
When calculating the expectedPrice in closeTradeMarket, the script searches a hardcoded mapping containing Chainlink Oracle feeds for only BTC and ETH. When a different asset is selected, the oracle lookup fails, and expectedPrice defaults to 0. Passing _expectedPrice=0 to the contract causes it to instantly revert because it acts as a slippage guard mechanism to prevent front-running.

The Fix:
I fixed this locally by introducing a fallback mechanism. If the asset does not have a hardcoded on-chain feed, the script pulls the price directly from the UI's currentAssets cache (fetched via Synth API) and scales it to the required 1e10 precision for gTrade before sending the transaction. This successfully allows positions to close.

- Fix closeTradeMarket reverting: dynamic Chainlink feed resolution
  instead of hardcoded BTC/ETH map, with Synth API currentAssets
  fallback for stocks/commodities that lack on-chain feeds
- Add live unrealized P&L display for open positions using
  Chainlink + Synth API prices with leveraged P&L calculation
- Add trade history section with localStorage-based recording
  on successful close (gTrade history API unavailable)
- Deduplicate Chainlink feed logic: shared CHAINLINK_FEEDS constant
  and fetchChainlinkPrice/resolveFeedForPairIndex helpers
- Fix open position row layout (trade-row-info wrapper)
- Add CSS for P&L display, history badge, and row styling

All 54 tests pass.
@MkDev11
Copy link
Copy Markdown
Contributor Author

MkDev11 commented Mar 10, 2026

I tested this PR locally and found a blocking issue where closing Synthetic positions (e.g. Stocks, Commodities) always reverts on the gTrade Diamond contract.

The Bug: When calculating the expectedPrice in closeTradeMarket, the script searches a hardcoded mapping containing Chainlink Oracle feeds for only BTC and ETH. When a different asset is selected, the oracle lookup fails, and expectedPrice defaults to 0. Passing _expectedPrice=0 to the contract causes it to instantly revert because it acts as a slippage guard mechanism to prevent front-running.

The Fix: I fixed this locally by introducing a fallback mechanism. If the asset does not have a hardcoded on-chain feed, the script pulls the price directly from the UI's currentAssets cache (fetched via Synth API) and scales it to the required 1e10 precision for gTrade before sending the transaction. This successfully allows positions to close.

all the comments are fixed!

MkDev11 added 2 commits March 10, 2026 18:56
- Parse USDC Transfer event from close tx receipt to get actual
  amount returned by gTrade, compute real P&L as (returned - collateral)
  instead of raw leveraged price-change (which ignores all fees)
- If no Transfer event found (full loss), P&L = -collateral
- Label open position P&L as 'Est.' since it excludes fees
  (opening, closing, borrowing, spread, price impact)

54/54 tests pass.
- Snapshot USDC balanceOf before sending closeTradeMarket tx
- Read USDC balanceOf after tx confirmation
- Compute actual P&L as (balanceAfter - balanceBefore) - collateral
- This accounts for ALL gTrade fees (open/close/spread/borrowing/impact)
- Replaces unreliable Transfer event log parsing which failed when
  gTrade routes USDC through vault contracts

54/54 tests pass.
…t UI update

gTrade uses a two-step close: closeTradeMarket() initiates a price
request, then an oracle callback settles the trade and transfers USDC
in a SEPARATE transaction. Previous approaches failed because they
looked for balance changes / Transfer events in the initiation tx.

Now:
- Snapshot USDC balance before sending close tx
- After tx confirms, poll balanceOf every 2s for up to 30s
- When balance changes (oracle callback executed), compute real P&L
  as (balanceAfter - balanceBefore) - collateral
- If oracle is slow, save P&L as 'pending'
- Show settlement toast with actual P&L amount

Also:
- Add relative timestamps to trade history (e.g. '2m ago', '1h ago')
- Immediately remove closed position from DOM (no refresh needed)
- Handle 'pending' P&L display gracefully

54/54 tests pass.
@MkDev11
Copy link
Copy Markdown
Contributor Author

MkDev11 commented Mar 10, 2026

image

@MkDev11
Copy link
Copy Markdown
Contributor Author

MkDev11 commented Mar 10, 2026

@e35ventura it works well now

@e35ventura e35ventura merged commit e609d07 into entrius:main Mar 10, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tide Chart: Wallet Connect + Direct Trading via gTrade

4 participants