Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 30 additions & 9 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Project Overview

This is the Tangle dApp monorepo - a collection of decentralized applications serving as the frontend for the Tangle Network, a Substrate-based cryptocurrency network in the Polkadot ecosystem. Tangle is a layer 1 for on-demand services where developers can build and monetize decentralized services using Tangle Blueprints.
This is the Tangle dApp monorepo - a collection of decentralized applications for the Tangle Operator Layer for AI services, built on the TNT EVM protocol stack (`tnt-core`).

The monorepo uses Nx for fast, extensible building with `apps/` containing interfaces and `libs/` containing shared code.

Expand Down Expand Up @@ -45,32 +45,36 @@ yarn generate:release # Review version bumps and changelog
## Architecture & Key Concepts

### Applications (apps/)
- **tangle-dapp**: Main dApp for managing Tangle Network assets and MPC services
- **tangle-cloud**: Cloud interface for Tangle services
- **leaderboard**: Validator leaderboard application

- **tangle-dapp**: Main dApp for staking, delegation, rewards, migration claims, and wallet flows
- **tangle-cloud**: Operator/developer interface for blueprint and service lifecycle management
- **leaderboard**: Points and participation leaderboard

### Libraries (libs/)

- **abstract-api-provider**: Base classes unifying API across providers
- **api-provider-environment**: React contexts, app events, error handling
- **browser-utils**: Browser utilities (fetch, download, logger, storage)
- **dapp-config**: Chain/wallet configurations for dApps
- **dapp-types**: Shared TypeScript types and interfaces
- **icons**: Shared icon components
- **polkadot-api-provider**: Substrate/Polkadot provider for blockchain interaction
- **polkadot-api-provider**: Legacy chain provider used only by migration-claim flows
- **solana-api-provider**: Solana blockchain provider
- **tangle-shared-ui**: Tangle-specific logic, hooks, utilities (shared between dApps)
- **ui-components**: Generic reusable UI components
- **web3-api-provider**: EVM provider for blockchain interaction

### Tech Stack

- **Frontend**: Vite, TypeScript, React, TailwindCSS
- **Blockchain**: EVM + PolkadotJS (metadata-driven runtime types)
- **Blockchain**: EVM-first (`viem`/`wagmi`) with limited PolkadotJS usage for migration-claim interoperability
- **Build System**: Nx monorepo
- **Styling**: TailwindCSS with custom preset

## Development Guidelines

### Execution Posture (Senior IC / Tech Lead)

- Default to ownership and execution. When a goal is clear, proceed immediately without asking permission to continue.
- Prefer decisive action over proposal loops. Bring work to completion end-to-end (implementation, verification, reporting).
- Escalate only for true external blockers (missing credentials, unavailable infrastructure, irreversible risk), and name the exact blocker.
Expand All @@ -79,7 +83,16 @@ yarn generate:release # Review version bumps and changelog
- Avoid “do you want me to…” phrasing when the expected next step is obvious from context.
- For launch-flow-impacting changes, follow `docs/harness-engineering-spec.md` and complete `docs/harness-engineering-checklist.md` before requesting merge.

### Harness Release Process (Succinct)

- Scope launch-impacting work to explicit flow IDs in `docs/launch-readiness-board.csv`.
- Run harness suite: `yarn test:wallet-flows` and inspect `suite/report.json` + `suite/release-matrix.md`.
- Enforce gate: `yarn test:wallet-flows:gate` (or `:strict` when required).
- Critical flows (`FLOW-001,002,005,010,011,013,014,018,019`) must be `happy-path-pass` unless exception owner/ETA is documented.
- Include matrix summary and gate output in PR using the harness section in `.github/PULL_REQUEST_TEMPLATE.md`.

### Wallet Flow Reliability (agent-browser-driver)

- Treat wallet E2E as environment-first: do not trust flow results until local chain + indexer + dApp are confirmed on the same network.
- Minimum readiness gate before running wallet flows:
- `http://127.0.0.1:8545` responds to `eth_chainId` with `0x7a69` (31337)
Expand All @@ -91,6 +104,7 @@ yarn generate:release # Review version bumps and changelog
- For local wallet runs, prefer persistent seeded profile + automated prompt settling, and ensure funding checks are active for connected local accounts.

### Code Style

- Use `const ... => {}` over `function ... () {}`
- React components: `const Component: FC<Props> = ({ prop1, prop2 }) => { ... }`
- Use `useMemo`/`useCallback` when appropriate (skip for simple calculations)
Expand All @@ -101,40 +115,47 @@ yarn generate:release # Review version bumps and changelog
- Avoid `as` type casting and `any` type

### Folder Structure (within apps)

- `utils/`: Utility functions (one function per file, same filename as function name)
- `components/`: Reusable "dumb" components specific to the app
- `containers/`: "Smart" components with business logic
- `hooks/`: React hooks for infrastructure logic
- `data/`: Data fetching hooks organized by domain (staking, liquid staking, etc.)
- `pages/`: Route pages for react-router-dom
- `abi/`: EVM ABI definitions for Substrate precompiles
- `abi/`: EVM ABI definitions for precompiles/contracts

### Important Notes

- **Localize changes**: Keep changes isolated to relevant projects unless shared libraries are involved
- **Package dependencies**: Don't assume packages exist - check imports or root `package.json` first
- **Number handling**: For values > u32 from chain, use `BN` or `bigint`. For u32 or smaller, use `.toNumber()`
- **Number handling**: Prefer `bigint`/`viem` primitives for chain values; avoid introducing new `BN` usage.
- **Monorepo scope**: Avoid cross-project changes unless working with shared libs
- **Storybook**: Considered legacy, avoid creating/modifying storybook files
- **Testing**: No testing libraries currently used or planned

### Branch Strategy

- Main development branch: `develop`
- Main branch for releases: `master`
- Release PRs should start with `[RELEASE]` in title

### Prerequisites

- Node.js v18.18.x or later
- Yarn package manager (v4.7.0)

## Working with Specific Libraries

### tangle-shared-ui

Contains Tangle-specific logic shared between dApps. Use this for functionality tied to Tangle Network context.

### ui-components

Generic, reusable components not tied to any specific context. Should be usable across different dApps.

### API Providers
- Use `polkadot-api-provider` for Substrate/Polkadot interactions

- Use `polkadot-api-provider` only where migration-claim compatibility requires it
- Use `web3-api-provider` for EVM interactions
- Use `abstract-api-provider` base classes when creating new providers
10 changes: 5 additions & 5 deletions apps/leaderboard/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@
- integrate cloud credits ([#3021](https://github.com/tangle-network/dapp/pull/3021))
- **tangle-cloud:** List operators in the Operators page ([#3005](https://github.com/tangle-network/dapp/pull/3005))
- **tangle-cloud:** List all blueprints ([#2987](https://github.com/tangle-network/dapp/pull/2987))
- **tangle-dapp:** Create restaking & services setup scripts ([#2986](https://github.com/tangle-network/dapp/pull/2986))
- **tangle-dapp:** Create staking & services setup scripts ([#2986](https://github.com/tangle-network/dapp/pull/2986))
- **tangle-dapp:** Add blueprint selection ([#2941](https://github.com/tangle-network/dapp/pull/2941))
- **tangle-dapp:** Add Protocol Stats Component ([#2966](https://github.com/tangle-network/dapp/pull/2966))
- **tangle-dapp:** Add Phantom wallet to dApp wallet provider ([#2885](https://github.com/tangle-network/dapp/pull/2885))

### 🩹 Fixes

- **tangle-dapp:** update result type and reduce refetch interval in useCredits ([#3026](https://github.com/tangle-network/dapp/pull/3026))
- **tangle-dapp:** Asset modal, restake action tabs & wallet dropdown fixes ([#3002](https://github.com/tangle-network/dapp/pull/3002))
- **tangle-dapp:** Asset modal, staking action tabs & wallet dropdown fixes ([#3002](https://github.com/tangle-network/dapp/pull/3002))
- **tangle-dapp:** Fix Theme Flickering, Disable Link ([#2953](https://github.com/tangle-network/dapp/pull/2953))

### 🏡 Chore
Expand All @@ -57,7 +57,7 @@
- bump @storybook/channels from 8.6.12 to 8.6.14 ([#3025](https://github.com/tangle-network/dapp/pull/3025))
- bump @radix-ui/react-tabs from 1.1.4 to 1.1.9 ([#3006](https://github.com/tangle-network/dapp/pull/3006))
- bump @vitest/ui from 3.1.1 to 3.1.2 ([#3007](https://github.com/tangle-network/dapp/pull/3007))
- **tangle-dapp:** Add Multiple RPC Endpoints Support for Polkadot APIs ([#2990](https://github.com/tangle-network/dapp/pull/2990))
- **tangle-dapp:** Add multiple RPC endpoint support for chain APIs ([#2990](https://github.com/tangle-network/dapp/pull/2990))
- Add initial Cursor rules ([#2998](https://github.com/tangle-network/dapp/pull/2998))
- bump framer-motion from 12.7.2 to 12.7.4 ([#2993](https://github.com/tangle-network/dapp/pull/2993))
- bump @hookform/resolvers from 3.10.0 to 5.0.1 ([#2972](https://github.com/tangle-network/dapp/pull/2972))
Expand All @@ -67,7 +67,7 @@
- bump typescript-eslint from 8.29.1 to 8.30.0 ([#2967](https://github.com/tangle-network/dapp/pull/2967))
- **tangle-dapp:** Update asset selection modal ([#2965](https://github.com/tangle-network/dapp/pull/2965))
- bump actions/create-github-app-token from 1 to 2 ([#2961](https://github.com/tangle-network/dapp/pull/2961))
- bump @polkadot/keyring from 13.3.1 to 13.4.3 ([#2962](https://github.com/tangle-network/dapp/pull/2962))
- bump keyring dependency from 13.3.1 to 13.4.3 ([#2962](https://github.com/tangle-network/dapp/pull/2962))
- **tangle-dapp:** Improve Vault Table ([#2956](https://github.com/tangle-network/dapp/pull/2956))

### 🎨 Styles
Expand Down Expand Up @@ -100,4 +100,4 @@

### ❤️ Thank You

- Trung-Tin Pham @AtelyPham
- Trung-Tin Pham @AtelyPham
2 changes: 1 addition & 1 deletion apps/leaderboard/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<!-- Keywords for Search Engines -->
<meta
name="keywords"
content="Tangle Network, leaderboard, blockchain rankings, staking, liquid staking, nominating, network contributors, decentralized network, Substrate, EVM"
content="Tangle Network, leaderboard, blockchain rankings, staking, blueprints, operators, services, network contributors, decentralized network, EVM"
/>

<!-- Open Graph Tags for Social Media -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,12 @@ export const SyncProgressIndicator = ({
}) => {
const { data, error, isPending } = useIndexingProgress(network);

const progress = useMemo(() => {
if (!data?.lastProcessedHeight || !data?.targetHeight) {
return 0;
}

// Round to 2 decimal places
return (
Math.round((data.lastProcessedHeight / data.targetHeight) * 100 * 100) /
100
);
}, [data?.lastProcessedHeight, data?.targetHeight]);

const isSynced = useMemo(() => {
if (!data?.lastProcessedHeight || !data?.targetHeight) {
return false;
}

return data.lastProcessedHeight === data.targetHeight;
}, [data?.lastProcessedHeight, data?.targetHeight]);

const displayContent = useMemo(() => {
if (isPending) {
return (
<>
<StatusIndicator variant="info" animated />
Loading indexing status...
Loading indexer activity...
</>
);
}
Expand All @@ -50,53 +30,41 @@ export const SyncProgressIndicator = ({
return (
<>
<CrossCircledIcon className="text-red-500" />
Error loading indexing status
Indexer status unavailable
</>
);
}

if (!data) {
return (
<>
<StatusIndicator variant="warning" />
No indexer metadata
</>
);
}

return (
<>
<StatusIndicator
variant={isSynced ? 'success' : 'info'}
animated={!isSynced}
/>
<StatusIndicator variant="info" animated />
<span className="flex items-center gap-1">
{isSynced ? 'Synced' : 'Indexing'}

Indexed block
<span className="inline-block">
{data?.lastProcessedHeight ? (
<SlidingNumber number={data.lastProcessedHeight} />
) : (
EMPTY_VALUE_PLACEHOLDER
)}
<SlidingNumber number={data.latestProcessedBlock} />
</span>

<span className="inline-block">of</span>

<span className="inline-block">
{data?.targetHeight ? (
<SlidingNumber number={data.targetHeight} />
<span className="items-center hidden sm:flex">
({' '}
{data.numEventsProcessed > 0 ? (
<SlidingNumber number={data.numEventsProcessed} />
) : (
EMPTY_VALUE_PLACEHOLDER
)}
</span>

<span className="items-center hidden sm:flex">
(<SlidingNumber number={progress} />
%)
)}{' '}
events)
</span>
</span>
</>
);
}, [
isPending,
error,
isSynced,
data?.lastProcessedHeight,
data?.targetHeight,
progress,
]);
}, [isPending, error, data]);

return (
<Chip
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { BLOCK_TIME_MS } from '@tangle-network/dapp-config/constants/tangle';
import { NetworkType } from '@tangle-network/tangle-shared-ui/graphql/graphql';
import {
executeEnvioGraphQL,
type EnvioNetwork,
} from '@tangle-network/tangle-shared-ui/utils/executeEnvioGraphQL';
import { useQuery as _useQuery } from '@tanstack/react-query';
import { INDEXING_PROGRESS_QUERY_KEY } from '../../../constants/query';

interface IndexingMetadata {
lastProcessedHeight: number;
targetHeight: number;
export interface IndexingMetadata {
firstEventBlockNumber: number;
latestProcessedBlock: number;
numEventsProcessed: number;
chainId: number;
}

// Envio chain_metadata query - uses the Envio-specific table
const INDEXING_PROGRESS_QUERY = `
query IndexingProgress {
chain_metadata {
Expand All @@ -20,50 +25,24 @@ const INDEXING_PROGRESS_QUERY = `
}
`;

const getEndpoint = (network: NetworkType): string => {
if (network === 'MAINNET') {
return (
import.meta.env.VITE_ENVIO_MAINNET_ENDPOINT ||
'http://localhost:8080/v1/graphql'
);
}
return (
import.meta.env.VITE_ENVIO_TESTNET_ENDPOINT ||
'http://localhost:8080/v1/graphql'
);
};

interface ChainMetadataRow {
first_event_block_number: number;
latest_processed_block: number;
num_events_processed: number;
chain_id: number;
}

const toEnvioNetwork = (network: NetworkType): EnvioNetwork => {
return network === 'MAINNET' ? 'mainnet' : 'testnet';
};

const fetcher = async (
network: NetworkType,
): Promise<IndexingMetadata | null> => {
const endpoint = getEndpoint(network);

const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify({
query: INDEXING_PROGRESS_QUERY,
}),
});

if (!response.ok) {
throw new Error('Network response was not ok');
}

const result = (await response.json()) as {
data: { chain_metadata: ChainMetadataRow[] };
errors?: Array<{ message: string }>;
};
const result = await executeEnvioGraphQL<
{ chain_metadata: ChainMetadataRow[] },
Record<string, never>
>(INDEXING_PROGRESS_QUERY, {}, toEnvioNetwork(network));

if (result.errors?.length) {
console.warn('GraphQL errors:', result.errors);
Expand All @@ -75,10 +54,11 @@ const fetcher = async (
return null;
}

// Envio tracks latest_processed_block, we estimate target as a bit ahead
return {
lastProcessedHeight: metadata.latest_processed_block,
targetHeight: metadata.latest_processed_block + 1, // Estimate target
firstEventBlockNumber: metadata.first_event_block_number,
latestProcessedBlock: metadata.latest_processed_block,
numEventsProcessed: metadata.num_events_processed,
chainId: metadata.chain_id,
};
};

Expand Down
Loading
Loading