From b0c68b4c427a6214c5de742975462f253d3b632a Mon Sep 17 00:00:00 2001 From: MoD Date: Fri, 6 Mar 2026 22:04:55 +0200 Subject: [PATCH 1/6] refactor: move contracts under Build > API, merge manuals into guides - Move docs/contracts/ to docs/build/api/contracts/ with Deployed Addresses as the section index (skip vendor README) - Merge docs/manuals/ into docs/guides/manuals/ - Remove Contracts and Manuals navbar links - Remove contractsSidebar and manualsSidebar - Update sync-contracts-docs.js output path and sidebar references - Update homepage and supported-chains links --- docs/build/api/_category_.json | 9 + docs/build/api/contracts/_category_.json | 10 + .../contracts/api-reference/_category_.json | 0 .../contracts/api-reference/app-registry.md | 2 +- .../api}/contracts/api-reference/faucet.md | 2 +- .../api-reference/interfaces/_category_.json | 0 .../api-reference/interfaces/ilock.md | 2 +- .../api-reference/interfaces/islash.md | 2 +- .../api}/contracts/api-reference/locker.md | 2 +- .../contracts/api-reference/node-registry.md | 2 +- .../api}/contracts/api-reference/treasury.md | 2 +- .../api-reference/yellow-governor.md | 2 +- .../contracts/api-reference/yellow-token.md | 2 +- docs/{ => build/api}/contracts/faq.md | 2 +- .../api/contracts/index.md} | 4 +- .../contracts/integration/_category_.json | 0 .../api}/contracts/integration/deployment.md | 2 +- .../api}/contracts/integration/events.md | 2 +- .../api}/contracts/integration/ui-spec.md | 2 +- .../api}/contracts/protocol/_category_.json | 0 .../api}/contracts/protocol/governance.md | 2 +- .../api}/contracts/protocol/overview.md | 2 +- .../api}/contracts/protocol/slashing.md | 2 +- .../api}/contracts/protocol/staking.md | 2 +- .../api}/contracts/protocol/treasury.md | 2 +- .../api}/contracts/sdk/_category_.json | 1 + .../api}/contracts/sdk/api-reference.md | 2 +- .../{ => build/api}/contracts/sdk/examples.md | 2 +- .../api}/contracts/sdk/getting-started.md | 2 +- docs/contracts/index.md | 180 ------------------ docs/guides/index.md | 8 + docs/guides/manuals/_category_.json | 9 + .../manuals/request-asset-support.md | 0 .../manuals/request-blockchain-support.md | 0 .../manuals/running-clearnode-locally.md | 0 docs/learn/introduction/supported-chains.mdx | 4 +- docs/manuals/_category_.json | 8 - docs/manuals/index.md | 13 -- docusaurus.config.ts | 20 +- package-lock.json | 33 +++- scripts/sync-contracts-docs.js | 65 ++++--- sidebars.ts | 16 -- src/components/HomepageFeatures/index.tsx | 2 +- src/pages/whitepaper.md | 10 + 44 files changed, 146 insertions(+), 288 deletions(-) create mode 100644 docs/build/api/_category_.json create mode 100644 docs/build/api/contracts/_category_.json rename docs/{ => build/api}/contracts/api-reference/_category_.json (100%) rename docs/{ => build/api}/contracts/api-reference/app-registry.md (98%) rename docs/{ => build/api}/contracts/api-reference/faucet.md (98%) rename docs/{ => build/api}/contracts/api-reference/interfaces/_category_.json (100%) rename docs/{ => build/api}/contracts/api-reference/interfaces/ilock.md (99%) rename docs/{ => build/api}/contracts/api-reference/interfaces/islash.md (97%) rename docs/{ => build/api}/contracts/api-reference/locker.md (98%) rename docs/{ => build/api}/contracts/api-reference/node-registry.md (97%) rename docs/{ => build/api}/contracts/api-reference/treasury.md (97%) rename docs/{ => build/api}/contracts/api-reference/yellow-governor.md (99%) rename docs/{ => build/api}/contracts/api-reference/yellow-token.md (95%) rename docs/{ => build/api}/contracts/faq.md (99%) rename docs/{contracts/addresses.md => build/api/contracts/index.md} (95%) rename docs/{ => build/api}/contracts/integration/_category_.json (100%) rename docs/{ => build/api}/contracts/integration/deployment.md (98%) rename docs/{ => build/api}/contracts/integration/events.md (99%) rename docs/{ => build/api}/contracts/integration/ui-spec.md (98%) rename docs/{ => build/api}/contracts/protocol/_category_.json (100%) rename docs/{ => build/api}/contracts/protocol/governance.md (99%) rename docs/{ => build/api}/contracts/protocol/overview.md (99%) rename docs/{ => build/api}/contracts/protocol/slashing.md (98%) rename docs/{ => build/api}/contracts/protocol/staking.md (99%) rename docs/{ => build/api}/contracts/protocol/treasury.md (98%) rename docs/{ => build/api}/contracts/sdk/_category_.json (83%) rename docs/{ => build/api}/contracts/sdk/api-reference.md (98%) rename docs/{ => build/api}/contracts/sdk/examples.md (99%) rename docs/{ => build/api}/contracts/sdk/getting-started.md (98%) delete mode 100644 docs/contracts/index.md create mode 100644 docs/guides/manuals/_category_.json rename docs/{ => guides}/manuals/request-asset-support.md (100%) rename docs/{ => guides}/manuals/request-blockchain-support.md (100%) rename docs/{ => guides}/manuals/running-clearnode-locally.md (100%) delete mode 100644 docs/manuals/_category_.json delete mode 100644 docs/manuals/index.md diff --git a/docs/build/api/_category_.json b/docs/build/api/_category_.json new file mode 100644 index 0000000..69cc463 --- /dev/null +++ b/docs/build/api/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "API", + "position": 3, + "link": { + "type": "generated-index" + }, + "collapsible": false, + "collapsed": false +} diff --git a/docs/build/api/contracts/_category_.json b/docs/build/api/contracts/_category_.json new file mode 100644 index 0000000..7cb0321 --- /dev/null +++ b/docs/build/api/contracts/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Contracts", + "position": 1, + "link": { + "type": "doc", + "id": "build/api/contracts/index" + }, + "collapsible": false, + "collapsed": false +} diff --git a/docs/contracts/api-reference/_category_.json b/docs/build/api/contracts/api-reference/_category_.json similarity index 100% rename from docs/contracts/api-reference/_category_.json rename to docs/build/api/contracts/api-reference/_category_.json diff --git a/docs/contracts/api-reference/app-registry.md b/docs/build/api/contracts/api-reference/app-registry.md similarity index 98% rename from docs/contracts/api-reference/app-registry.md rename to docs/build/api/contracts/api-reference/app-registry.md index d50ed36..a8d6abc 100644 --- a/docs/contracts/api-reference/app-registry.md +++ b/docs/build/api/contracts/api-reference/app-registry.md @@ -2,7 +2,7 @@ title: "AppRegistry" description: "App builder registry with slashing." sidebar_position: 4 -displayed_sidebar: contractsSidebar +displayed_sidebar: buildSidebar --- # AppRegistry diff --git a/docs/contracts/api-reference/faucet.md b/docs/build/api/contracts/api-reference/faucet.md similarity index 98% rename from docs/contracts/api-reference/faucet.md rename to docs/build/api/contracts/api-reference/faucet.md index eb4b4b3..4cc46a4 100644 --- a/docs/contracts/api-reference/faucet.md +++ b/docs/build/api/contracts/api-reference/faucet.md @@ -2,7 +2,7 @@ title: "Faucet" description: "Testnet token faucet." sidebar_position: 7 -displayed_sidebar: contractsSidebar +displayed_sidebar: buildSidebar --- # Faucet diff --git a/docs/contracts/api-reference/interfaces/_category_.json b/docs/build/api/contracts/api-reference/interfaces/_category_.json similarity index 100% rename from docs/contracts/api-reference/interfaces/_category_.json rename to docs/build/api/contracts/api-reference/interfaces/_category_.json diff --git a/docs/contracts/api-reference/interfaces/ilock.md b/docs/build/api/contracts/api-reference/interfaces/ilock.md similarity index 99% rename from docs/contracts/api-reference/interfaces/ilock.md rename to docs/build/api/contracts/api-reference/interfaces/ilock.md index 5cc6ea4..0277883 100644 --- a/docs/contracts/api-reference/interfaces/ilock.md +++ b/docs/build/api/contracts/api-reference/interfaces/ilock.md @@ -2,7 +2,7 @@ title: "ILock" description: "Lock/unlock/withdraw interface." sidebar_position: 1 -displayed_sidebar: contractsSidebar +displayed_sidebar: buildSidebar --- # ILock diff --git a/docs/contracts/api-reference/interfaces/islash.md b/docs/build/api/contracts/api-reference/interfaces/islash.md similarity index 97% rename from docs/contracts/api-reference/interfaces/islash.md rename to docs/build/api/contracts/api-reference/interfaces/islash.md index ff4b6ed..a0f6a26 100644 --- a/docs/contracts/api-reference/interfaces/islash.md +++ b/docs/build/api/contracts/api-reference/interfaces/islash.md @@ -2,7 +2,7 @@ title: "ISlash" description: "Slashing interface." sidebar_position: 2 -displayed_sidebar: contractsSidebar +displayed_sidebar: buildSidebar --- # ISlash diff --git a/docs/contracts/api-reference/locker.md b/docs/build/api/contracts/api-reference/locker.md similarity index 98% rename from docs/contracts/api-reference/locker.md rename to docs/build/api/contracts/api-reference/locker.md index 586883c..65245f3 100644 --- a/docs/contracts/api-reference/locker.md +++ b/docs/build/api/contracts/api-reference/locker.md @@ -2,7 +2,7 @@ title: "Locker" description: "Abstract lock/unlock/withdraw state machine." sidebar_position: 2 -displayed_sidebar: contractsSidebar +displayed_sidebar: buildSidebar --- # Locker diff --git a/docs/contracts/api-reference/node-registry.md b/docs/build/api/contracts/api-reference/node-registry.md similarity index 97% rename from docs/contracts/api-reference/node-registry.md rename to docs/build/api/contracts/api-reference/node-registry.md index fe94345..987498b 100644 --- a/docs/contracts/api-reference/node-registry.md +++ b/docs/build/api/contracts/api-reference/node-registry.md @@ -2,7 +2,7 @@ title: "NodeRegistry" description: "Node operator registry with voting power." sidebar_position: 3 -displayed_sidebar: contractsSidebar +displayed_sidebar: buildSidebar --- # NodeRegistry diff --git a/docs/contracts/api-reference/treasury.md b/docs/build/api/contracts/api-reference/treasury.md similarity index 97% rename from docs/contracts/api-reference/treasury.md rename to docs/build/api/contracts/api-reference/treasury.md index 3411c4e..1440a2a 100644 --- a/docs/contracts/api-reference/treasury.md +++ b/docs/build/api/contracts/api-reference/treasury.md @@ -2,7 +2,7 @@ title: "Treasury" description: "Secure vault for Foundation assets." sidebar_position: 6 -displayed_sidebar: contractsSidebar +displayed_sidebar: buildSidebar --- # Treasury diff --git a/docs/contracts/api-reference/yellow-governor.md b/docs/build/api/contracts/api-reference/yellow-governor.md similarity index 99% rename from docs/contracts/api-reference/yellow-governor.md rename to docs/build/api/contracts/api-reference/yellow-governor.md index b7dc230..87cb0da 100644 --- a/docs/contracts/api-reference/yellow-governor.md +++ b/docs/build/api/contracts/api-reference/yellow-governor.md @@ -2,7 +2,7 @@ title: "YellowGovernor" description: "OpenZeppelin Governor for protocol parameters." sidebar_position: 5 -displayed_sidebar: contractsSidebar +displayed_sidebar: buildSidebar --- # YellowGovernor diff --git a/docs/contracts/api-reference/yellow-token.md b/docs/build/api/contracts/api-reference/yellow-token.md similarity index 95% rename from docs/contracts/api-reference/yellow-token.md rename to docs/build/api/contracts/api-reference/yellow-token.md index 4b2e46a..902c55f 100644 --- a/docs/contracts/api-reference/yellow-token.md +++ b/docs/build/api/contracts/api-reference/yellow-token.md @@ -2,7 +2,7 @@ title: "YellowToken" description: "ERC-20 token with fixed 10B supply." sidebar_position: 1 -displayed_sidebar: contractsSidebar +displayed_sidebar: buildSidebar --- # YellowToken diff --git a/docs/contracts/faq.md b/docs/build/api/contracts/faq.md similarity index 99% rename from docs/contracts/faq.md rename to docs/build/api/contracts/faq.md index 5a78b69..c442169 100644 --- a/docs/contracts/faq.md +++ b/docs/build/api/contracts/faq.md @@ -2,7 +2,7 @@ title: "FAQ" description: "Frequently asked questions about Yellow Network smart contracts." sidebar_position: 7 -displayed_sidebar: contractsSidebar +displayed_sidebar: buildSidebar --- # Frequently Asked Questions diff --git a/docs/contracts/addresses.md b/docs/build/api/contracts/index.md similarity index 95% rename from docs/contracts/addresses.md rename to docs/build/api/contracts/index.md index b205a36..f79bcc9 100644 --- a/docs/contracts/addresses.md +++ b/docs/build/api/contracts/index.md @@ -1,8 +1,8 @@ --- title: "Deployed Addresses" description: "Mainnet and testnet contract addresses for Yellow Network." -sidebar_position: 2 -displayed_sidebar: contractsSidebar +sidebar_position: 1 +displayed_sidebar: buildSidebar --- # Deployed Addresses diff --git a/docs/contracts/integration/_category_.json b/docs/build/api/contracts/integration/_category_.json similarity index 100% rename from docs/contracts/integration/_category_.json rename to docs/build/api/contracts/integration/_category_.json diff --git a/docs/contracts/integration/deployment.md b/docs/build/api/contracts/integration/deployment.md similarity index 98% rename from docs/contracts/integration/deployment.md rename to docs/build/api/contracts/integration/deployment.md index 560ae51..eb32485 100644 --- a/docs/contracts/integration/deployment.md +++ b/docs/build/api/contracts/integration/deployment.md @@ -2,7 +2,7 @@ title: "Deployment" description: "Deploying contracts and configuration." sidebar_position: 1 -displayed_sidebar: contractsSidebar +displayed_sidebar: buildSidebar --- # Deployment diff --git a/docs/contracts/integration/events.md b/docs/build/api/contracts/integration/events.md similarity index 99% rename from docs/contracts/integration/events.md rename to docs/build/api/contracts/integration/events.md index 1c62073..70bfcc4 100644 --- a/docs/contracts/integration/events.md +++ b/docs/build/api/contracts/integration/events.md @@ -2,7 +2,7 @@ title: "Events" description: "Contract events for real-time subscriptions." sidebar_position: 2 -displayed_sidebar: contractsSidebar +displayed_sidebar: buildSidebar --- # Events diff --git a/docs/contracts/integration/ui-spec.md b/docs/build/api/contracts/integration/ui-spec.md similarity index 98% rename from docs/contracts/integration/ui-spec.md rename to docs/build/api/contracts/integration/ui-spec.md index 3acf506..20140f8 100644 --- a/docs/contracts/integration/ui-spec.md +++ b/docs/build/api/contracts/integration/ui-spec.md @@ -2,7 +2,7 @@ title: "UI Specification" description: "Frontend implementation guide." sidebar_position: 3 -displayed_sidebar: contractsSidebar +displayed_sidebar: buildSidebar --- # UI Specification diff --git a/docs/contracts/protocol/_category_.json b/docs/build/api/contracts/protocol/_category_.json similarity index 100% rename from docs/contracts/protocol/_category_.json rename to docs/build/api/contracts/protocol/_category_.json diff --git a/docs/contracts/protocol/governance.md b/docs/build/api/contracts/protocol/governance.md similarity index 99% rename from docs/contracts/protocol/governance.md rename to docs/build/api/contracts/protocol/governance.md index bf65e76..944fa07 100644 --- a/docs/contracts/protocol/governance.md +++ b/docs/build/api/contracts/protocol/governance.md @@ -2,7 +2,7 @@ title: "Governance" description: "On-chain parameter administration via YellowGovernor." sidebar_position: 2 -displayed_sidebar: contractsSidebar +displayed_sidebar: buildSidebar --- # Protocol Parameter Administration diff --git a/docs/contracts/protocol/overview.md b/docs/build/api/contracts/protocol/overview.md similarity index 99% rename from docs/contracts/protocol/overview.md rename to docs/build/api/contracts/protocol/overview.md index 5dd9466..d3ee258 100644 --- a/docs/contracts/protocol/overview.md +++ b/docs/build/api/contracts/protocol/overview.md @@ -2,7 +2,7 @@ title: "Protocol Overview" description: "Architecture, contracts, and how they fit together." sidebar_position: 1 -displayed_sidebar: contractsSidebar +displayed_sidebar: buildSidebar --- # Protocol Overview diff --git a/docs/contracts/protocol/slashing.md b/docs/build/api/contracts/protocol/slashing.md similarity index 98% rename from docs/contracts/protocol/slashing.md rename to docs/build/api/contracts/protocol/slashing.md index 394e729..0f88eaf 100644 --- a/docs/contracts/protocol/slashing.md +++ b/docs/build/api/contracts/protocol/slashing.md @@ -2,7 +2,7 @@ title: "Slashing" description: "Adjudicator slashing and cooldown mechanism." sidebar_position: 4 -displayed_sidebar: contractsSidebar +displayed_sidebar: buildSidebar --- # Slashing diff --git a/docs/contracts/protocol/staking.md b/docs/build/api/contracts/protocol/staking.md similarity index 99% rename from docs/contracts/protocol/staking.md rename to docs/build/api/contracts/protocol/staking.md index f448f84..0049c71 100644 --- a/docs/contracts/protocol/staking.md +++ b/docs/build/api/contracts/protocol/staking.md @@ -2,7 +2,7 @@ title: "Collateral & Staking" description: "Lock/unlock state machine for NodeRegistry and AppRegistry." sidebar_position: 3 -displayed_sidebar: contractsSidebar +displayed_sidebar: buildSidebar --- # Collateral diff --git a/docs/contracts/protocol/treasury.md b/docs/build/api/contracts/protocol/treasury.md similarity index 98% rename from docs/contracts/protocol/treasury.md rename to docs/build/api/contracts/protocol/treasury.md index 3558c0d..add28c0 100644 --- a/docs/contracts/protocol/treasury.md +++ b/docs/build/api/contracts/protocol/treasury.md @@ -2,7 +2,7 @@ title: "Treasury" description: "Foundation asset management." sidebar_position: 5 -displayed_sidebar: contractsSidebar +displayed_sidebar: buildSidebar --- # Treasury diff --git a/docs/contracts/sdk/_category_.json b/docs/build/api/contracts/sdk/_category_.json similarity index 83% rename from docs/contracts/sdk/_category_.json rename to docs/build/api/contracts/sdk/_category_.json index 6ea8e3e..3ece79c 100644 --- a/docs/contracts/sdk/_category_.json +++ b/docs/build/api/contracts/sdk/_category_.json @@ -1,6 +1,7 @@ { "label": "SDK", "position": 5, + "key": "contracts-sdk", "link": { "type": "generated-index" }, diff --git a/docs/contracts/sdk/api-reference.md b/docs/build/api/contracts/sdk/api-reference.md similarity index 98% rename from docs/contracts/sdk/api-reference.md rename to docs/build/api/contracts/sdk/api-reference.md index bdbd042..e962632 100644 --- a/docs/contracts/sdk/api-reference.md +++ b/docs/build/api/contracts/sdk/api-reference.md @@ -2,7 +2,7 @@ title: "SDK API Reference" description: "All exports: ABIs, addresses, and types." sidebar_position: 2 -displayed_sidebar: contractsSidebar +displayed_sidebar: buildSidebar --- # SDK — API Reference diff --git a/docs/contracts/sdk/examples.md b/docs/build/api/contracts/sdk/examples.md similarity index 99% rename from docs/contracts/sdk/examples.md rename to docs/build/api/contracts/sdk/examples.md index b00d999..c338051 100644 --- a/docs/contracts/sdk/examples.md +++ b/docs/build/api/contracts/sdk/examples.md @@ -2,7 +2,7 @@ title: "Examples" description: "Code samples for viem, ethers.js, and wagmi." sidebar_position: 3 -displayed_sidebar: contractsSidebar +displayed_sidebar: buildSidebar --- # SDK — Examples diff --git a/docs/contracts/sdk/getting-started.md b/docs/build/api/contracts/sdk/getting-started.md similarity index 98% rename from docs/contracts/sdk/getting-started.md rename to docs/build/api/contracts/sdk/getting-started.md index c49e6d1..8f5e48f 100644 --- a/docs/contracts/sdk/getting-started.md +++ b/docs/build/api/contracts/sdk/getting-started.md @@ -2,7 +2,7 @@ title: "Getting Started" description: "Install, import, and use the contracts SDK." sidebar_position: 1 -displayed_sidebar: contractsSidebar +displayed_sidebar: buildSidebar --- # SDK — Getting Started diff --git a/docs/contracts/index.md b/docs/contracts/index.md deleted file mode 100644 index b3602af..0000000 --- a/docs/contracts/index.md +++ /dev/null @@ -1,180 +0,0 @@ ---- -title: "Smart Contracts" -description: "Overview of Yellow Network smart contracts, governance, and on-chain infrastructure." -sidebar_position: 1 -displayed_sidebar: contractsSidebar ---- - -# What Is Yellow Network? - -A plain-language guide to Yellow Network, the YELLOW token, and how the system works. - ---- - -## The Problem - -Today's crypto ecosystem is fragmented. Assets live on dozens of separate blockchains — Ethereum, Arbitrum, Base, Polygon, BNB Chain, Linea, and more. If you want to move value between them, you typically rely on bridges (slow, often hacked) or centralised exchanges (single points of failure). Developers building apps face the same issue: they have to pick one chain and lose access to users and liquidity on every other chain. - -There is no shared infrastructure that lets brokers, exchanges, and applications transact across all of these networks in real time — the way internet service providers share a common routing layer to move data. - -## The Solution - -Yellow Network is that shared infrastructure. It is a **clearing and settlement layer** that sits on top of existing blockchains (a "Layer 3") and lets participants move digital assets across multiple chains through a single, unified system — without trusting a central intermediary. - -Think of it like the interbank clearing system (SWIFT, ACH) but for crypto: independent operators run the network, transactions clear in sub-second time off-chain, and final settlement is anchored to the security of public blockchains. - -All of the software, tools, and services that make up Yellow Network are developed, maintained, and supplied by **Layer3 Fintech Ltd.** - ---- - -## How It Works — The Three Layers - -Yellow Network has a three-layer architecture. Each layer has a distinct role: - -### Layer 1 — The Blockchain Foundation - -This is the base: public blockchains like Ethereum, Base, Arbitrum, Linea, BNB Chain, and Polygon. Yellow deploys smart contracts on each of these chains to handle: - -- **Asset custody** — when you deposit assets into Yellow Network, they are held in audited smart contracts on the blockchain, not by a company or individual. -- **Node registration** — node operators register on-chain by posting YELLOW tokens as a security deposit. -- **Dispute enforcement** — if someone cheats, the blockchain is the final court. Fraud proofs are submitted on-chain, and the cheater's collateral is automatically seized. - -You can think of Layer 1 as the vault and the judge — it holds the real assets and enforces the rules. - -### Layer 2 — The Clearnet (Off-Chain Ledger) - -This is where the speed comes from. Instead of recording every transaction on a blockchain (which is slow and expensive), Yellow Network runs an off-chain peer-to-peer ledger called the **Yellow Clearnet**. - -Here's how it works: - -1. **Independent node operators** run open-source software (called a "clearnode") on their own servers. There is no central server — the network is formed by these independent operators. - -2. **Every user account is guarded by a group of nodes**, not a single one. These nodes are selected algorithmically based on proximity in a shared address space (using a system called Kademlia, the same technology behind BitTorrent). The group collectively manages the account using **threshold cryptography** — meaning a supermajority of the group must agree before any balance can change. No single node can steal funds. - -3. **Security scales with value.** A small account might be guarded by 3 nodes. A high-value account could be guarded by up to 256. The protocol automatically ensures that the total collateral posted by the nodes guarding an account always exceeds the value in that account — so cheating always costs more than it gains. - -4. **A separate ring of watcher nodes** independently verifies every transaction. If the primary group tries to process a fraudulent transaction, the watchers catch it, produce a cryptographic fraud proof, and submit it to the blockchain. The cheating nodes lose their collateral automatically. - -The result: transactions clear in sub-second time (compared to minutes or hours on-chain), while retaining the security guarantees of the underlying blockchains. - -### Layer 3 — Applications - -On top of the Clearnet, developers build applications using the **Yellow SDK**. This is the user-facing layer: - -- **Yellow App Store** — a registry of applications built on the network. -- **Developer tools** — the SDK abstracts away the complexity of state channels, multi-chain settlement, and cryptographic signing. A developer can build an app that works across six blockchains without dealing with any of that directly. -- **Dispute resolution** — if there is a disagreement between a user and an application, independent arbitration forums can adjudicate the dispute, with outcomes enforced on-chain. - ---- - -## What Actually Happens When You Use It - -Here is a concrete example of a cross-chain transfer: - -1. **Alice** has 100 USDC on Ethereum deposited into Yellow Network. -2. She wants to send 50 USDC to **Bob**, who uses Base. -3. Alice's node cluster debits her account and produces a signed certificate. -4. The certificate is routed through the peer-to-peer network to Bob's node cluster. -5. Bob's cluster verifies the certificate and credits Bob's account. -6. Bob can now withdraw 50 USDC on Base (or any other supported chain). - -This entire process takes less than a second. No bridge. No centralised exchange. No on-chain gas fees for the transfer itself. The blockchains are only used for deposits, withdrawals, and dispute resolution. - ---- - -## The YELLOW Token - -YELLOW is the utility token that provides access to the goods and services supplied by Layer3 Fintech Ltd. within the Yellow Network. It has three specific functions: - -### 1. Mandatory Security Deposit for Node Operators - -Every node operator must post YELLOW tokens as collateral to register on the network. This is not optional — it is the mechanism that makes the network secure. - -- **Prevents spam attacks** — you cannot cheaply flood the network with malicious nodes because each one requires real collateral. -- **Deters fraud** — if a node participates in a fraudulent transaction, its collateral is automatically seized ("slashed") through on-chain fraud proofs. -- **Scales with responsibility** — as a node guards higher-value accounts, the protocol requires more collateral at risk. - -The minimum collateral starts at 10,000 YELLOW and scales up to 125,000 YELLOW as the network grows. - -### 2. Service Access Fee - -All network services — clearing, settlement, data delivery, app subscriptions — require the consumption of YELLOW as a service access fee. Users who hold YELLOW pay fees directly at a discounted rate. For users who do not yet hold YELLOW, an optional convenience mechanism allows payment in other assets (like ETH or USDT); independent third-party liquidity providers convert the payment into YELLOW before the protocol consumes it. - -Protocol fees from clearing and trading operations are locked into the collateral of the nodes that processed them — increasing those operators' slashing exposure. This strengthens network security over time. There is no fee distribution to passive token holders. - -### 3. Dispute Resolution Access - -App builders who register applications on the network post YELLOW as a service quality guarantee. Users who have disputes with an application pay a processing fee in YELLOW to access independent arbitration. If the dispute is upheld, the app builder's collateral can be slashed. - -### What YELLOW Is Not - -- It does not represent ownership in Layer3 Fintech Ltd. or any affiliated entity. -- It does not entitle holders to dividends, profit-sharing, or any form of financial return. -- It is not designed to maintain a stable value — there is no peg, no reserve backing, and no stabilisation mechanism. -- Holding YELLOW alone does not grant participation in protocol parameter administration — that requires actively operating a node. - -The total supply is fixed at **10 billion YELLOW**. No new tokens can ever be created, and there is no burn mechanism. - ---- - -## Who Runs the Network? - -Yellow Network is operated by **independent node operators** who run the open-source clearnode software on their own infrastructure. Layer3 Fintech Ltd. develops and maintains the software — but it does not operate the network itself. This is similar to how a software company distributes server software that thousands of independent hosting providers run on their own hardware. - -The protocol's on-chain smart contracts contain configurable parameters (security thresholds, fee levels, supported chains) that need to be updated as the network evolves. These parameters are administered by active node operators through a distributed multi-signature process — replacing what would otherwise be a single administrator key (a security risk). This parameter administration is an operational duty of running a node, not a right that comes with holding the token. - ---- - -## Key Numbers - -| Metric | Value | -|---|---| -| Total YELLOW supply | 10,000,000,000 (fixed) | -| Supported blockchains | Ethereum, Base, Arbitrum, Linea, BNB Chain, Polygon | -| Minimum node collateral | 10,000 YELLOW (scales to 125,000) | -| Max nodes per account | 256 | -| Min nodes per account | 3 | -| Collateral unlock period | 14 days | -| Transaction speed | Sub-second (off-chain clearing) | -| Fee range | 0.1% — 0.4% (dynamic) | - -## Token Allocation - -| Allocation | Percentage | Purpose | -|---|---|---| -| Founders | 10% | Subject to 6-month cliff and 60-month linear vesting | -| Token Sales | 12.5% | Distributed to participants who require YELLOW for service access | -| Community Treasury | 30% | Grants for app builders who consume YELLOW for services | -| Foundation Treasury | 20% | Funds ongoing R&D and delivery of Yellow Network services | -| Network Growth Incentives | 25% | Distributed automatically based on network scale | -| Ecosystem Accessibility Reserve | 2.5% | Ensures YELLOW remains accessible for its intended utility | - -The Foundation controls 50% of total supply (Community Treasury + Foundation Treasury), subject to linear vesting with quarterly reporting on all movements. - ---- - -## Security and Audits - -Yellow Network's smart contracts have been independently audited: - -- **Hacken (2024)** — Security assessment of the Ethereum smart contracts. No critical issues found. [Full report](https://hacken.io/audits/openware-yellow-network/sca-yellow-network-vault-sept-2024/) -- **GuardianAudits (ongoing)** — Auditing the decentralised ledger (Yellow Clearnet). [Reports](https://github.com/GuardianAudits/Audits/tree/main/Yellow%20Network/) - -The core state channel technology builds on academic research from statechannels.org and the VirtualApp framework, developed in collaboration with Consensys and other open-source contributors. - ---- - -## Where to Learn More - -- **Website:** [yellow.org](https://yellow.org/) -- **Developer docs:** [docs.yellow.org](https://docs.yellow.org/) -- **Protocol source code:** [github.com/layer-3/clearnet](https://github.com/layer-3/clearnet) -- **Documentation source:** [github.com/layer-3/docs](https://github.com/layer-3/docs) - ---- - -## Legal Entity - -Yellow Network's goods and services are supplied by **Layer3 Fintech Ltd.**, a company registered in the British Virgin Islands (Registration: 2092094), with its parent entity **Layer3 Foundation** based in the Cayman Islands. The sole director is Paul Parker. The applicable law and competent court for the token offering is Ireland. - -YELLOW is classified as a **utility token** under EU Regulation 2023/1114 (MiCA), Article 3(1)(9) — a token that is only intended to provide access to a good or a service supplied by its issuer. It is not a financial instrument, not an asset-referenced token, not an e-money token, and not a security. diff --git a/docs/guides/index.md b/docs/guides/index.md index 6b80a03..5f86ab5 100644 --- a/docs/guides/index.md +++ b/docs/guides/index.md @@ -11,3 +11,11 @@ displayed_sidebar: guidesSidebar **[Multi-Party App Sessions](/docs/guides/multi-party-app-sessions)** - Learn how to create, manage, and close multi-party application sessions using the Yellow Network and VirtualApp protocol. + +## Manuals + +**[Running Clearnode Locally](/docs/guides/manuals/running-clearnode-locally)** - Run a Clearnode locally using Docker Compose for development and testing. + +**[Request Asset Support](/docs/guides/manuals/request-asset-support)** - Guide for requesting asset and token support in Clearnode environments. + +**[Request Blockchain Support](/docs/guides/manuals/request-blockchain-support)** - Guide for requesting blockchain network support in Clearnode environments. diff --git a/docs/guides/manuals/_category_.json b/docs/guides/manuals/_category_.json new file mode 100644 index 0000000..d553862 --- /dev/null +++ b/docs/guides/manuals/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "Manuals", + "position": 10, + "link": { + "type": "generated-index" + }, + "collapsible": false, + "collapsed": false +} diff --git a/docs/manuals/request-asset-support.md b/docs/guides/manuals/request-asset-support.md similarity index 100% rename from docs/manuals/request-asset-support.md rename to docs/guides/manuals/request-asset-support.md diff --git a/docs/manuals/request-blockchain-support.md b/docs/guides/manuals/request-blockchain-support.md similarity index 100% rename from docs/manuals/request-blockchain-support.md rename to docs/guides/manuals/request-blockchain-support.md diff --git a/docs/manuals/running-clearnode-locally.md b/docs/guides/manuals/running-clearnode-locally.md similarity index 100% rename from docs/manuals/running-clearnode-locally.md rename to docs/guides/manuals/running-clearnode-locally.md diff --git a/docs/learn/introduction/supported-chains.mdx b/docs/learn/introduction/supported-chains.mdx index 72ac7a1..d89c8b6 100644 --- a/docs/learn/introduction/supported-chains.mdx +++ b/docs/learn/introduction/supported-chains.mdx @@ -257,8 +257,8 @@ Contract addresses vary by blockchain. See the [deployment repository](https://g Need support for a blockchain or asset not listed here? -- **[Request Blockchain Support](/docs/manuals/request-blockchain-support)** — Guide for adding new blockchain networks -- **[Request Asset Support](/docs/manuals/request-asset-support)** — Guide for adding new tokens/assets +- **[Request Blockchain Support](/docs/guides/manuals/request-blockchain-support)** — Guide for adding new blockchain networks +- **[Request Asset Support](/docs/guides/manuals/request-asset-support)** — Guide for adding new tokens/assets --- diff --git a/docs/manuals/_category_.json b/docs/manuals/_category_.json deleted file mode 100644 index 176732f..0000000 --- a/docs/manuals/_category_.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "label": "Manuals", - "position": 4, - "link": { - "type": "doc", - "id": "manuals/index" - } -} \ No newline at end of file diff --git a/docs/manuals/index.md b/docs/manuals/index.md deleted file mode 100644 index 04d75bf..0000000 --- a/docs/manuals/index.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: Manuals -description: Comprehensive manuals and documentation -displayed_sidebar: manualsSidebar ---- - -# Manuals - -:::info Work in Progress -This section is currently under development. Comprehensive manuals and documentation will be available soon. -::: - -Coming soon: Detailed manuals covering all aspects of the platform. \ No newline at end of file diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 31b37d6..1508ee2 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -92,9 +92,7 @@ const config: Config = { mermaid: true, }, themes: ['@docusaurus/theme-mermaid'], - clientModules: [ - './src/clientModules/hideContractsOn05x.js', - ], + clientModules: [], plugins: [ [ 'docusaurus-lunr-search', @@ -139,18 +137,6 @@ const config: Config = { label: 'Protocol', position: 'left', }, - { - to: '/docs/contracts', - label: 'Contracts', - position: 'left', - className: 'navbar-contracts-link', - }, - { - type: 'doc', - docId: 'manuals/index', - label: 'Manuals', - position: 'left', - }, { type: 'doc', docId: 'guides/index', @@ -200,10 +186,6 @@ const config: Config = { label: 'Build', to: '/docs/build/quick-start', }, - { - label: 'Manuals', - to: '/docs/manuals', - }, { label: 'Guides', to: '/docs/guides', diff --git a/package-lock.json b/package-lock.json index fbd3b59..5715bc1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -172,6 +172,7 @@ "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.49.1.tgz", "integrity": "sha512-Nt9hri7nbOo0RipAsGjIssHkpLMHHN/P7QqENywAq5TLsoYDzUyJGny8FEiD/9KJUxtGH8blGpMedilI6kK3rA==", "license": "MIT", + "peer": true, "dependencies": { "@algolia/client-common": "5.49.1", "@algolia/requester-browser-xhr": "5.49.1", @@ -323,6 +324,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -2170,6 +2172,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -2192,6 +2195,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -2301,6 +2305,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -2722,6 +2727,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -3521,6 +3527,7 @@ "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.9.2.tgz", "integrity": "sha512-HbjwKeC+pHUFBfLMNzuSjqFE/58+rLVKmOU3lxQrpsxLBOGosYco/Q0GduBb0/jEMRiyEqjNT/01rRdOMWq5pw==", "license": "MIT", + "peer": true, "dependencies": { "@docusaurus/babel": "3.9.2", "@docusaurus/bundler": "3.9.2", @@ -3702,6 +3709,7 @@ "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.9.2.tgz", "integrity": "sha512-C5wZsGuKTY8jEYsqdxhhFOe1ZDjH0uIYJ9T/jebHwkyxqnr4wW0jTkB72OMqNjsoQRcb0JN3PcSeTwFlVgzCZg==", "license": "MIT", + "peer": true, "dependencies": { "@docusaurus/core": "3.9.2", "@docusaurus/logger": "3.9.2", @@ -4745,6 +4753,7 @@ "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.1.tgz", "integrity": "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==", "license": "MIT", + "peer": true, "dependencies": { "@types/mdx": "^2.0.0" }, @@ -5227,6 +5236,7 @@ "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -6139,6 +6149,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -6500,6 +6511,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6567,6 +6579,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -6612,6 +6625,7 @@ "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.49.1.tgz", "integrity": "sha512-X3Pp2aRQhg4xUC6PQtkubn5NpRKuUPQ9FPDQlx36SmpFwwH2N0/tw4c+NXV3nw3PsgeUs+BuWGP0gjz3TvENLQ==", "license": "MIT", + "peer": true, "dependencies": { "@algolia/abtesting": "1.15.1", "@algolia/client-abtesting": "5.49.1", @@ -7128,6 +7142,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -7436,6 +7451,7 @@ "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.2.tgz", "integrity": "sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@chevrotain/cst-dts-gen": "11.1.2", "@chevrotain/gast": "11.1.2", @@ -8149,6 +8165,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -8474,6 +8491,7 @@ "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10" } @@ -8883,6 +8901,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -10203,6 +10222,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -15624,6 +15644,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -16201,6 +16222,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -17104,6 +17126,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -17926,6 +17949,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -17935,6 +17959,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -17990,6 +18015,7 @@ "resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz", "integrity": "sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ==", "license": "MIT", + "peer": true, "dependencies": { "@types/react": "*" }, @@ -18018,6 +18044,7 @@ "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", "integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.12.13", "history": "^4.9.0", @@ -19934,7 +19961,8 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/tsyringe": { "version": "4.10.0", @@ -20015,6 +20043,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -20398,6 +20427,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -20645,6 +20675,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.4.tgz", "integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==", "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", diff --git a/scripts/sync-contracts-docs.js b/scripts/sync-contracts-docs.js index aa35d0f..7e98cc8 100644 --- a/scripts/sync-contracts-docs.js +++ b/scripts/sync-contracts-docs.js @@ -1,20 +1,20 @@ /** * Syncs smart-contract documentation from vendors/yellow/docs - * into docs/contracts/ with Docusaurus-compatible frontmatter + * into docs/build/api/contracts/ with Docusaurus-compatible frontmatter * and category metadata. * - * Generates the Deployed Addresses page from @yellow-org/contracts. + * Generates the Deployed Addresses page (as index) from @yellow-org/contracts. * * Usage: node scripts/sync-contracts-docs.js * - * Safe to re-run — it wipes docs/contracts/ and rebuilds from source. + * Safe to re-run — it wipes docs/build/api/contracts/ and rebuilds from source. */ const fs = require('fs'); const path = require('path'); const ROOT = path.resolve(__dirname, '..'); const SRC = path.join(ROOT, 'vendors', 'yellow', 'docs'); -const DEST = path.join(ROOT, 'docs', 'contracts'); +const DEST = path.join(ROOT, 'docs', 'build', 'api', 'contracts'); // --------------------------------------------------------------------------- // On-chain addresses from @yellow-org/contracts @@ -104,7 +104,7 @@ function copyWithFrontmatter(srcFile, destFile, { title, description, sidebarPos `title: "${title}"`, `description: "${description}"`, `sidebar_position: ${sidebarPosition}`, - `displayed_sidebar: contractsSidebar`, + `displayed_sidebar: buildSidebar`, '---', '', '', @@ -117,11 +117,12 @@ function copyWithFrontmatter(srcFile, destFile, { title, description, sidebarPos /** * Write a Docusaurus _category_.json file. */ -function writeCategory(dir, { label, position, link }) { +function writeCategory(dir, { label, position, link, key }) { ensureDir(dir); const data = { label, position, + ...(key && { key }), link: link || { type: 'generated-index' }, collapsible: false, collapsed: false, @@ -138,8 +139,8 @@ function generateAddressesPage() { '---', 'title: "Deployed Addresses"', 'description: "Mainnet and testnet contract addresses for Yellow Network."', - 'sidebar_position: 2', - 'displayed_sidebar: contractsSidebar', + 'sidebar_position: 1', + 'displayed_sidebar: buildSidebar', '---', '', '', @@ -194,19 +195,13 @@ function generateAddressesPage() { // --------------------------------------------------------------------------- const DOCS = { - // Top-level pages - index: { - src: 'what-is-yellow.md', - title: 'Smart Contracts', - description: 'Overview of Yellow Network smart contracts, governance, and on-chain infrastructure.', - sidebarPosition: 1, - }, - // 'addresses' is generated from @yellow-org/contracts — not copied from vendor + // 'index' (Deployed Addresses) is generated from @yellow-org/contracts — not copied from vendor + // README / what-is-yellow.md is intentionally skipped faq: { src: 'FAQ.md', title: 'FAQ', description: 'Frequently asked questions about Yellow Network smart contracts.', - sidebarPosition: 7, + sidebarPosition: 2, }, // Protocol @@ -343,10 +338,17 @@ const CATEGORIES = [ { dir: 'protocol', label: 'Protocol', position: 3 }, { dir: 'api-reference', label: 'Contract API Reference', position: 4 }, { dir: 'api-reference/interfaces', label: 'Interfaces', position: 8 }, - { dir: 'sdk', label: 'SDK', position: 5 }, + { dir: 'sdk', label: 'SDK', position: 5, key: 'contracts-sdk' }, { dir: 'integration', label: 'Integration', position: 6 }, ]; +// Parent category written outside DEST (in docs/build/api/) +const API_CATEGORY = { + dir: path.join(ROOT, 'docs', 'build', 'api'), + label: 'API', + position: 3, +}; + // --------------------------------------------------------------------------- // Main // --------------------------------------------------------------------------- @@ -359,12 +361,25 @@ function main() { process.exit(1); } - console.log('Syncing smart-contract docs from vendors/yellow/docs...\n'); + console.log('Syncing smart-contract docs from vendors/yellow/docs into docs/build/api/contracts/...\n'); // Clean and recreate destination cleanDir(DEST); - // Write category files + // Write parent API category (docs/build/api/) + writeCategory(API_CATEGORY.dir, { + label: API_CATEGORY.label, + position: API_CATEGORY.position, + }); + + // Write Contracts category at DEST root + writeCategory(DEST, { + label: 'Contracts', + position: 1, + link: { type: 'doc', id: 'build/api/contracts/index' }, + }); + + // Write sub-category files for (const cat of CATEGORIES) { writeCategory(path.join(DEST, cat.dir), { label: cat.label, @@ -374,11 +389,11 @@ function main() { // Generate addresses page from @yellow-org/contracts const addressesPage = generateAddressesPage(); - fs.writeFileSync(path.join(DEST, 'addresses.md'), addressesPage); + fs.writeFileSync(path.join(DEST, 'index.md'), addressesPage); if (addresses) { - console.log(' generated addresses.md (from @yellow-org/contracts)'); + console.log(' generated index.md (Deployed Addresses, from @yellow-org/contracts)'); } else { - console.warn(' generated addresses.md (WARNING: @yellow-org/contracts not installed)'); + console.warn(' generated index.md (Deployed Addresses, WARNING: @yellow-org/contracts not installed)'); } // Copy and transform each document from vendor @@ -406,7 +421,7 @@ function main() { } } - console.log(`\nSynced ${count} vendor docs + 1 generated into docs/contracts/`); + console.log(`\nSynced ${count} vendor docs + 1 generated into docs/build/api/contracts/`); console.log('\nStructure:'); // Print tree @@ -430,7 +445,7 @@ function main() { }); } - console.log('docs/contracts/'); + console.log('docs/build/api/contracts/'); printTree(DEST, ''); } diff --git a/sidebars.ts b/sidebars.ts index 5fee934..2462c88 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -84,14 +84,6 @@ const sidebars: SidebarsConfig = { }, ], - // Manuals section sidebar - manualsSidebar: [ - { - type: 'autogenerated', - dirName: 'manuals', - }, - ], - // Tutorials section sidebar tutorialsSidebar: [ { @@ -124,14 +116,6 @@ const sidebars: SidebarsConfig = { }, ], - // Contracts section sidebar - contractsSidebar: [ - { - type: 'autogenerated', - dirName: 'contracts', - }, - ], - // Default sidebar for standalone pages defaultSidebar: [ ], diff --git a/src/components/HomepageFeatures/index.tsx b/src/components/HomepageFeatures/index.tsx index ad2eca8..7047bc4 100644 --- a/src/components/HomepageFeatures/index.tsx +++ b/src/components/HomepageFeatures/index.tsx @@ -50,7 +50,7 @@ const FeatureList: FeatureItem[] = [ and contribute to decentralized infrastructure. ), - link: '/docs/manuals', + link: '/docs/guides/manuals/running-clearnode-locally', }, { title: 'Join the Community', diff --git a/src/pages/whitepaper.md b/src/pages/whitepaper.md index f5eb288..d18a214 100644 --- a/src/pages/whitepaper.md +++ b/src/pages/whitepaper.md @@ -298,3 +298,13 @@ Governance is non-transferable, non-delegable, and terminates immediately upon c --- *This is a public-facing summary of the Yellow Network Whitepaper v2.0. For the full regulatory whitepaper including MiCA compliance details, sustainability indicators, and complete legal disclosures, please contact [legal@layer3.foundation](mailto:legal@layer3.foundation).* + +--- + +## MiCA White Paper Filing History + +The following versions of the YELLOW crypto-asset white paper have been filed under the Markets in Crypto-Assets Regulation (EU) 2023/1114: + +| Version | Date of Notification | Status | Document | +|---|---|---|---| +| v1.2 | 2025-11-14 | Current | [YELLOW MiCA White Paper v1.2 (PDF)](/assets/YELLOW_MiCA_White_Paper_v.1.2.pdf) | From bcadabf83a732d79431cb5750fec6fcdbb94eef5 Mon Sep 17 00:00:00 2001 From: MoD Date: Fri, 6 Mar 2026 22:08:44 +0200 Subject: [PATCH 2/6] Fixing renaming issue --- src/pages/whitepaper.md | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/pages/whitepaper.md b/src/pages/whitepaper.md index d18a214..d5078fa 100644 --- a/src/pages/whitepaper.md +++ b/src/pages/whitepaper.md @@ -37,7 +37,7 @@ The Foundation controls 50% of total supply, subject to linear vesting. --- -## About the Offeror +## About the Issuer **Layer3 Fintech Ltd.** @@ -81,14 +81,7 @@ Yellow aims to create a decentralised clearing and settlement framework where bu Yellow is an open-source software framework developed through multi-entity collaboration: -| Entity | Function | -|---|---| -| Layer3 Fintech Ltd. (BVI) | Integration, Maintenance, and Supply of Goods and Services (Issuer) | -| Clearsync Ltd. (UK) | Core Technology Development | -| Helios Technologies SAS (France) | Core Technology Development | -| Openware SAS (France) | Core Technology Development | - -The core state channel technology builds on academic research from statechannels.org and the VirtualApp framework, developed in collaboration with Consensys and other open-source contributors. Approximately 60 core developers contribute across the open-source codebase, with over 100 third-party applications launched on the network in 2025. +The core state channel technology builds on academic research from statechannels.org and the Nitro framework, developed in collaboration with Consensys and other open-source contributors. Approximately 60 core developers contribute across the open-source codebase, with over 100 third-party applications launched on the network in 2025. ### Token Utility @@ -108,6 +101,7 @@ YELLOW is consumed when accessing protocol services: each protocol operation req **Reached:** **2024:** + - State Channel and Account Abstraction R&D - Launch of the Yellow Smart Account - Launch of the Yellow Wallet @@ -116,6 +110,7 @@ YELLOW is consumed when accessing protocol services: each protocol operation req - Finalization of the Core Protocol Architecture **2025:** + - Production Release of the Yellow SDK - Network Expansion to 6 New EVM Chains - Official Launch of the Yellow Builder Program @@ -124,6 +119,7 @@ YELLOW is consumed when accessing protocol services: each protocol operation req **Outlook:** **2025-2026:** + - Public Release of the NeoDAX Brokerage Software - Finalization of the Clearing Network Architecture - Public Release of the Yellow Clearing Network @@ -182,16 +178,19 @@ Applications built on top of the Ledger Layer. Application-level state updates a ### Protocols and Technical Standards **Layer 1 — On-chain standards** + - ERC-20 token standard on Ethereum - Custody smart contracts for on-chain asset custody and withdrawal settlement - NodeRegistry and AppRegistry contracts for node/application registration and collateral management **Layer 2 — Ledger Layer protocols** + - Peer-to-peer network protocol with deterministic, decentralised routing - Threshold signature protocol (BLS) requiring supermajority for state updates - Virtual State Channel protocol for off-chain balance management and cross-chain clearing **Layer 3 — Application Layer standards** + - AppLayer RPC API for application-to-clearnode interaction - AppLayerClient library for Virtual State Channel control and cryptographic signing - Yellow SDK abstracting channel management, multi-chain settlement, and unified balances (e.g., 50 USDC on Ethereum + 50 USDC on Base = 100 USDC unified balance) @@ -295,12 +294,6 @@ Governance is non-transferable, non-delegable, and terminates immediately upon c - [github.com/layer-3/clearnet](https://github.com/layer-3/clearnet) — Yellow network node repository (open-source client software and smart contracts) - [github.com/layer-3/docs](https://github.com/layer-3/docs) — Documentation website repository ---- - -*This is a public-facing summary of the Yellow Network Whitepaper v2.0. For the full regulatory whitepaper including MiCA compliance details, sustainability indicators, and complete legal disclosures, please contact [legal@layer3.foundation](mailto:legal@layer3.foundation).* - ---- - ## MiCA White Paper Filing History The following versions of the YELLOW crypto-asset white paper have been filed under the Markets in Crypto-Assets Regulation (EU) 2023/1114: From fe06bf766b7488f47216b3a2f8b66a47312e782c Mon Sep 17 00:00:00 2001 From: Maharshi Mishra Date: Sat, 7 Mar 2026 18:27:30 +0530 Subject: [PATCH 3/6] chore: polish branch - fix broken links, cleanup dead code, add link checker Made-with: Cursor --- docs/build/quick-start/index.md | 2 +- docs/build/sdk/typescript-compat/overview.mdx | 28 + docs/build/sdk/typescript/api-reference.mdx | 100 ++ docs/build/sdk/typescript/examples.mdx | 45 + docusaurus.config.ts | 1 - i18n/en/docusaurus-theme-classic/footer.json | 4 - package-lock.json | 33 +- package.json | 3 +- scripts/check-links.mjs | 1174 +++++++++++++++++ src/clientModules/hideContractsOn05x.js | 22 - .../version-0.5.x/build/quick-start/index.md | 2 +- .../learn/introduction/supported-chains.mdx | 4 +- 12 files changed, 1354 insertions(+), 64 deletions(-) create mode 100644 scripts/check-links.mjs delete mode 100644 src/clientModules/hideContractsOn05x.js diff --git a/docs/build/quick-start/index.md b/docs/build/quick-start/index.md index d397f78..75cad82 100644 --- a/docs/build/quick-start/index.md +++ b/docs/build/quick-start/index.md @@ -347,7 +347,7 @@ await app.sendPayment('100000', '0xPartnerAddress'); // Send 0.1 USDC Congratulations! You've built your first Yellow App. Here's what to explore next: -- **[Advanced Topics](../../learn/advanced/architecture)**: Learn about architecture, multi-party applications, and production deployment +- **[Advanced Topics](../../learn/introduction/architecture-at-a-glance)**: Learn about architecture, multi-party applications, and production deployment - **[API Reference](../../api-reference)**: Explore all available SDK methods and options ## Need Help? diff --git a/docs/build/sdk/typescript-compat/overview.mdx b/docs/build/sdk/typescript-compat/overview.mdx index 718aa23..a669ee4 100644 --- a/docs/build/sdk/typescript-compat/overview.mdx +++ b/docs/build/sdk/typescript-compat/overview.mdx @@ -103,6 +103,34 @@ await client.close(); | `toWalletQuorumSignature(signature)` | Prefix wallet signature for app-session quorum format | | `toSessionKeyQuorumSignature(signature)` | Prefix session key signature (`0xa2`) for quorum format | +### Security Token Locking + +| Method | Description | +|--------|-------------| +| `approveSecurityToken(amount, chainId?)` | Approve the locking contract to spend tokens | +| `escrowSecurityTokens(targetWallet, amount, chainId?)` | Lock tokens for a target address | +| `initiateSecurityTokensWithdrawal(chainId?)` | Start the unlock process | +| `cancelSecurityTokensWithdrawal(chainId?)` | Cancel a pending unlock (relock) | +| `withdrawSecurityTokens(destination, chainId?)` | Withdraw after the unlock period | +| `getLockedBalance(wallet?, chainId?)` | Query locked balance (returns `Decimal`) | + +The `chainId` parameter is optional and defaults to the chain configured at client creation. + +**Lifecycle:** `approve` → `escrow` → *(optionally)* `initiateWithdrawal` → *(wait)* → `withdraw` + +```typescript +import Decimal from 'decimal.js'; + +await client.approveSecurityToken(new Decimal('1000')); +await client.escrowSecurityTokens(walletAddress, new Decimal('1000')); + +const balance = await client.getLockedBalance(); +// To withdraw: initiate unlock, wait for period, then withdraw +await client.initiateSecurityTokensWithdrawal(); +// ... after unlock period ... +await client.withdrawSecurityTokens(destinationAddress); +``` + ### Session Keys | Method | Description | diff --git a/docs/build/sdk/typescript/api-reference.mdx b/docs/build/sdk/typescript/api-reference.mdx index 830eee2..2502cd2 100644 --- a/docs/build/sdk/typescript/api-reference.mdx +++ b/docs/build/sdk/typescript/api-reference.mdx @@ -148,6 +148,106 @@ const allowance = await client.checkTokenAllowance(80002n, '0xToken...', '0xOwne --- +## Security Token Locking + +Methods for interacting with the locking contract (NonSlashableAppRegistry). Used to lock YELLOW tokens as security collateral. + +The locking lifecycle is: **approve** → **lock** → *(optionally)* **unlock** → *(wait for unlock period)* → **withdraw**. At any point during the unlock period, you can **relock** to cancel the withdrawal. + +### `approveSecurityToken(chainId, amount)` + +Approves the locking contract to spend tokens on behalf of the caller. Must be called before `escrowSecurityTokens`. + +```typescript +await client.approveSecurityToken(11155111n, new Decimal('1000')); +``` + +**Parameters:** + +| Parameter | Type | Description | +|-----------|------|-------------| +| `chainId` | `bigint` | Blockchain network ID | +| `amount` | `Decimal` | Amount of tokens to approve | + +--- + +### `escrowSecurityTokens(targetWallet, blockchainId, amount)` + +Locks tokens into the locking contract for the specified target address. + +```typescript +await client.escrowSecurityTokens('0xTarget...', 11155111n, new Decimal('1000')); +``` + +**Parameters:** + +| Parameter | Type | Description | +|-----------|------|-------------| +| `targetWallet` | `string` | Address to lock tokens for | +| `blockchainId` | `bigint` | Blockchain network ID | +| `amount` | `Decimal` | Amount of tokens to lock | + +**Requires:** Token allowance via `approveSecurityToken()`. + +--- + +### `initiateSecurityTokensWithdrawal(blockchainId)` + +Starts the unlock process. After the unlock period elapses, `withdrawSecurityTokens` can be called. + +```typescript +await client.initiateSecurityTokensWithdrawal(11155111n); +``` + +--- + +### `cancelSecurityTokensWithdrawal(blockchainId)` + +Cancels a pending unlock and returns tokens to the locked state (relock). + +```typescript +await client.cancelSecurityTokensWithdrawal(11155111n); +``` + +--- + +### `withdrawSecurityTokens(blockchainId, destination)` + +Withdraws unlocked tokens to a destination address. Can only be called after the unlock period has fully elapsed. + +```typescript +await client.withdrawSecurityTokens(11155111n, '0xDestination...'); +``` + +**Parameters:** + +| Parameter | Type | Description | +|-----------|------|-------------| +| `blockchainId` | `bigint` | Blockchain network ID | +| `destination` | `string` | Address to receive withdrawn tokens | + +--- + +### `getLockedBalance(chainId, wallet)` + +Returns the locked token balance for a wallet. + +```typescript +const balance = await client.getLockedBalance(11155111n, '0xWallet...'); +console.log('Locked:', balance.toString()); +``` + +**Parameters:** + +| Parameter | Type | Description | +|-----------|------|-------------| +| `chainId` | `bigint` | Blockchain network ID | +| `wallet` | `string` | Address to check | + +**Returns:** `Decimal` — the locked balance adjusted for token decimals. + +--- + ## Node Information ```typescript diff --git a/docs/build/sdk/typescript/examples.mdx b/docs/build/sdk/typescript/examples.mdx index 84494e5..b3f4d29 100644 --- a/docs/build/sdk/typescript/examples.mdx +++ b/docs/build/sdk/typescript/examples.mdx @@ -347,6 +347,51 @@ async function setupSessionKey(client: Client) { } ``` +## Security Token Locking + +Lock YELLOW tokens as security collateral via the locking contract (NonSlashableAppRegistry). + +```typescript +import { Client, createSigners, withBlockchainRPC } from '@yellow-org/sdk'; +import Decimal from 'decimal.js'; + +async function lockSecurityTokens() { + const { stateSigner, txSigner } = createSigners(process.env.PRIVATE_KEY!); + const chainId = 11155111n; + + const client = await Client.create( + 'wss://clearnode.example.com/ws', + stateSigner, + txSigner, + withBlockchainRPC(chainId, 'https://rpc.sepolia.io') + ); + + const wallet = txSigner.getAddress(); + const amount = new Decimal('1000'); + + // 1. Approve the locking contract to spend tokens + await client.approveSecurityToken(chainId, amount); + + // 2. Lock tokens (for yourself or another address) + await client.escrowSecurityTokens(wallet, chainId, amount); + + // 3. Check locked balance + const balance = await client.getLockedBalance(chainId, wallet); + console.log('Locked balance:', balance.toString()); + + // 4. Initiate withdrawal (starts unlock period) + await client.initiateSecurityTokensWithdrawal(chainId); + + // 5a. Cancel if needed (relock) + await client.cancelSecurityTokensWithdrawal(chainId); + + // 5b. Or after unlock period elapses, withdraw + await client.withdrawSecurityTokens(chainId, wallet); + + await client.close(); +} +``` + ## Connection Monitoring ```typescript diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 1508ee2..b575108 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -92,7 +92,6 @@ const config: Config = { mermaid: true, }, themes: ['@docusaurus/theme-mermaid'], - clientModules: [], plugins: [ [ 'docusaurus-lunr-search', diff --git a/i18n/en/docusaurus-theme-classic/footer.json b/i18n/en/docusaurus-theme-classic/footer.json index a351286..d6cc919 100644 --- a/i18n/en/docusaurus-theme-classic/footer.json +++ b/i18n/en/docusaurus-theme-classic/footer.json @@ -23,10 +23,6 @@ "message": "Build", "description": "The label of footer link with label=Build linking to /docs/build/quick-start" }, - "link.item.label.Manuals": { - "message": "Manuals", - "description": "The label of footer link with label=Manuals linking to /docs/manuals" - }, "link.item.label.Guides": { "message": "Guides", "description": "The label of footer link with label=Guides linking to /docs/guides" diff --git a/package-lock.json b/package-lock.json index 5715bc1..fbd3b59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -172,7 +172,6 @@ "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.49.1.tgz", "integrity": "sha512-Nt9hri7nbOo0RipAsGjIssHkpLMHHN/P7QqENywAq5TLsoYDzUyJGny8FEiD/9KJUxtGH8blGpMedilI6kK3rA==", "license": "MIT", - "peer": true, "dependencies": { "@algolia/client-common": "5.49.1", "@algolia/requester-browser-xhr": "5.49.1", @@ -324,7 +323,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -2172,7 +2170,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -2195,7 +2192,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -2305,7 +2301,6 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", - "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -2727,7 +2722,6 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", - "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -3527,7 +3521,6 @@ "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.9.2.tgz", "integrity": "sha512-HbjwKeC+pHUFBfLMNzuSjqFE/58+rLVKmOU3lxQrpsxLBOGosYco/Q0GduBb0/jEMRiyEqjNT/01rRdOMWq5pw==", "license": "MIT", - "peer": true, "dependencies": { "@docusaurus/babel": "3.9.2", "@docusaurus/bundler": "3.9.2", @@ -3709,7 +3702,6 @@ "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.9.2.tgz", "integrity": "sha512-C5wZsGuKTY8jEYsqdxhhFOe1ZDjH0uIYJ9T/jebHwkyxqnr4wW0jTkB72OMqNjsoQRcb0JN3PcSeTwFlVgzCZg==", "license": "MIT", - "peer": true, "dependencies": { "@docusaurus/core": "3.9.2", "@docusaurus/logger": "3.9.2", @@ -4753,7 +4745,6 @@ "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.1.tgz", "integrity": "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==", "license": "MIT", - "peer": true, "dependencies": { "@types/mdx": "^2.0.0" }, @@ -5236,7 +5227,6 @@ "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -6149,7 +6139,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -6511,7 +6500,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6579,7 +6567,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -6625,7 +6612,6 @@ "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.49.1.tgz", "integrity": "sha512-X3Pp2aRQhg4xUC6PQtkubn5NpRKuUPQ9FPDQlx36SmpFwwH2N0/tw4c+NXV3nw3PsgeUs+BuWGP0gjz3TvENLQ==", "license": "MIT", - "peer": true, "dependencies": { "@algolia/abtesting": "1.15.1", "@algolia/client-abtesting": "5.49.1", @@ -7142,7 +7128,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -7451,7 +7436,6 @@ "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.2.tgz", "integrity": "sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@chevrotain/cst-dts-gen": "11.1.2", "@chevrotain/gast": "11.1.2", @@ -8165,7 +8149,6 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", - "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -8491,7 +8474,6 @@ "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10" } @@ -8901,7 +8883,6 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -10222,7 +10203,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -15644,7 +15624,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -16222,7 +16201,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -17126,7 +17104,6 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", - "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -17949,7 +17926,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -17959,7 +17935,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -18015,7 +17990,6 @@ "resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz", "integrity": "sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ==", "license": "MIT", - "peer": true, "dependencies": { "@types/react": "*" }, @@ -18044,7 +18018,6 @@ "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", "integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.12.13", "history": "^4.9.0", @@ -19961,8 +19934,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/tsyringe": { "version": "4.10.0", @@ -20043,7 +20015,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -20427,7 +20398,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -20675,7 +20645,6 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.4.tgz", "integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==", "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", diff --git a/package.json b/package.json index 4e3eff3..d126af7 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "sync:contracts": "node scripts/sync-contracts-docs.js", "version:remove": "node scripts/remove-version.js", "version:reset": "node scripts/reset-versions.js", - "version:release": "node scripts/release-version.js" + "version:release": "node scripts/release-version.js", + "check-links": "node scripts/check-links.mjs" }, "dependencies": { "@docusaurus/core": "3.9.2", diff --git a/scripts/check-links.mjs b/scripts/check-links.mjs new file mode 100644 index 0000000..f63e29c --- /dev/null +++ b/scripts/check-links.mjs @@ -0,0 +1,1174 @@ +#!/usr/bin/env node + +import fs from 'node:fs'; +import path from 'node:path'; + +const repoRoot = process.cwd(); +const docsRoot = path.join(repoRoot, 'docs'); +const versionedDocsRoot = path.join(repoRoot, 'versioned_docs', 'version-0.5.x'); +const publicRoot = path.join(repoRoot, 'static'); +const linkcheckIgnorePath = path.join(repoRoot, '.linkcheckignore'); + +const outputFormat = normalizeOutputFormat(process.env.LINK_CHECK_FORMAT ?? 'text'); +const externalEnabled = parseBooleanEnv('LINK_CHECK_EXTERNAL', true); +const externalConcurrency = parsePositiveIntEnv('LINK_CHECK_CONCURRENCY', 5); +const externalTimeoutMs = parsePositiveIntEnv('LINK_CHECK_TIMEOUT', 15_000); +const externalProgressEvery = parsePositiveIntEnv('LINK_CHECK_PROGRESS_EVERY', 10); +const warnOnBareInternalLinks = parseBooleanEnv('LINK_CHECK_WARN_BARE', true); +const externalSkipMatchers = [ + ...loadSkipMatchersFromFile(linkcheckIgnorePath), + ...parseSkipMatchers(process.env.LINK_CHECK_SKIP ?? ''), +]; + +const fencePatterns = [/```[\s\S]*?```/g, /~~~[\s\S]*?~~~/g]; +const commentPatterns = [/{\/\*[\s\S]*?\*\/}/g, //g]; +const inlineCodePattern = /`[^`\n]*`/g; +const indentedCodePattern = /^(?: {4}|\t).+$/gm; + +const markdownReferencePattern = /^\s*\[[^\]]+]:\s*(\S+)/gm; +const markdownAutolinkPattern = /<((?:https?:\/\/|mailto:|tel:)[^>\s]+)>/g; +const jsxAttributePattern = + /\b(href|src|url)\s*=\s*(?:"([^"]+)"|'([^']+)'|\{\s*"([^"]+)"\s*\}|\{\s*'([^']+)'\s*\})/g; +const htmlIdPattern = /\bid\s*=\s*(?:"([^"]+)"|'([^']+)')/g; + +const assetExtensions = new Set([ + '.avif', + '.bmp', + '.csv', + '.gif', + '.ico', + '.jpeg', + '.jpg', + '.json', + '.mp3', + '.mp4', + '.pdf', + '.png', + '.svg', + '.txt', + '.wav', + '.webm', + '.webp', + '.woff', + '.woff2', + '.xml', + '.yaml', + '.yml', + '.zip', +]); + +function parseBooleanEnv(name, fallback) { + const raw = process.env[name]; + if (raw === undefined) return fallback; + const value = raw.trim().toLowerCase(); + if (['0', 'false', 'no', 'off'].includes(value)) return false; + if (['1', 'true', 'yes', 'on'].includes(value)) return true; + return fallback; +} + +function parsePositiveIntEnv(name, fallback) { + const raw = process.env[name]; + if (raw === undefined) return fallback; + const parsed = Number.parseInt(raw, 10); + if (!Number.isFinite(parsed) || parsed <= 0) return fallback; + return parsed; +} + +function normalizeOutputFormat(raw) { + const normalized = raw.trim().toLowerCase(); + return normalized === 'json' ? 'json' : 'text'; +} + +function parseSkipEntries(chunks) { + const cleaned = chunks.map((chunk) => chunk.trim()).filter(Boolean); + + return cleaned.map((chunk) => { + if (chunk.startsWith('/') && chunk.endsWith('/') && chunk.length > 2) { + try { + return { type: 'regex', value: new RegExp(chunk.slice(1, -1), 'i') }; + } catch { + return { type: 'substring', value: chunk.toLowerCase() }; + } + } + return { type: 'substring', value: chunk.toLowerCase() }; + }); +} + +function parseSkipMatchers(raw) { + return parseSkipEntries(raw.split(',')); +} + +function loadSkipMatchersFromFile(filePath) { + if (!fs.existsSync(filePath)) return []; + const raw = fs.readFileSync(filePath, 'utf8'); + const chunks = raw + .split(/\r?\n/) + .map((line) => line.trim()) + .filter((line) => line && !line.startsWith('#')); + + return parseSkipEntries(chunks); +} + +function shouldSkipUrl(url) { + for (const matcher of externalSkipMatchers) { + if (matcher.type === 'regex' && matcher.value.test(url)) return true; + if (matcher.type === 'substring' && url.toLowerCase().includes(matcher.value)) return true; + } + return false; +} + +function toPosix(filePath) { + return filePath.split(path.sep).join(path.posix.sep); +} + +async function listMdxFiles(rootDir) { + const results = []; + const stack = [rootDir]; + + while (stack.length > 0) { + const current = stack.pop(); + const entries = await fs.promises.readdir(current, { withFileTypes: true }); + for (const entry of entries) { + const full = path.join(current, entry.name); + if (entry.isDirectory()) { + stack.push(full); + continue; + } + if (entry.isFile() && (entry.name.endsWith('.mdx') || entry.name.endsWith('.md'))) { + results.push(full); + } + } + } + + return results.sort((a, b) => a.localeCompare(b)); +} + +function stripMarkdownExtension(segment) { + return segment.replace(/\.mdx?$/i, ''); +} + +function normalizeRoute(route) { + let normalized = route.replace(/\\/g, '/').trim(); + if (normalized === '') return '/'; + if (!normalized.startsWith('/')) normalized = `/${normalized}`; + normalized = path.posix.normalize(normalized); + + if (!normalized.startsWith('/')) normalized = `/${normalized}`; + if (normalized.length > 1) normalized = normalized.replace(/\/+$/, ''); + + if (normalized.length > 1 && normalized.endsWith('/index')) { + normalized = normalized.slice(0, -'/index'.length); + if (normalized === '') normalized = '/'; + } + + return normalized || '/'; +} + +function fileToRoute(relativeMdxPath) { + const noExt = stripMarkdownExtension(toPosix(relativeMdxPath)); + if (noExt === 'index') return '/'; + if (noExt.endsWith('/index')) return normalizeRoute(`/${noExt.slice(0, -'/index'.length)}`); + return normalizeRoute(`/${noExt}`); +} + +function fileToBaseRoute(relativeMdxPath, route) { + const noExt = stripMarkdownExtension(toPosix(relativeMdxPath)); + if (noExt === 'index' || noExt.endsWith('/index')) return route; + return normalizeRoute(path.posix.dirname(route)); +} + +function createLineResolver(content) { + const lineStarts = [0]; + for (let index = 0; index < content.length; index += 1) { + if (content[index] === '\n') lineStarts.push(index + 1); + } + + return (index) => { + let low = 0; + let high = lineStarts.length - 1; + while (low <= high) { + const mid = Math.floor((low + high) / 2); + if (lineStarts[mid] <= index) low = mid + 1; + else high = mid - 1; + } + return high + 1; + }; +} + +function maskWithPatterns(content, patterns) { + let masked = content; + for (const pattern of patterns) { + pattern.lastIndex = 0; + masked = masked.replace(pattern, (segment) => segment.replace(/[^\n]/g, ' ')); + } + return masked; +} + +function decodeURIComponentSafe(value) { + if (!value) return value; + try { + return decodeURIComponent(value); + } catch { + return value; + } +} + +function parseFrontmatter(content) { + const match = content.match(/^\uFEFF?---[ \t]*\r?\n[\s\S]*?\r?\n---[ \t]*(?:\r?\n|$)/); + if (!match) { + return { + frontmatterRaw: '', + body: content, + }; + } + + return { + frontmatterRaw: match[0], + body: content.slice(match[0].length), + }; +} + +function maskFrontmatter(content) { + const { frontmatterRaw } = parseFrontmatter(content); + if (!frontmatterRaw) return content; + + return `${frontmatterRaw.replace(/[^\n]/g, ' ')}${content.slice(frontmatterRaw.length)}`; +} + +function parseFrontmatterTitle(frontmatterRaw) { + if (!frontmatterRaw) return ''; + + const lines = frontmatterRaw.split(/\r?\n/); + for (let index = 1; index < lines.length; index += 1) { + const line = lines[index]; + const match = line.match(/^\s*title\s*:\s*(.+?)\s*$/); + if (!match) continue; + + let title = match[1].trim(); + if ( + (title.startsWith('"') && title.endsWith('"')) || + (title.startsWith("'") && title.endsWith("'")) + ) { + title = title.slice(1, -1); + } + return title.trim(); + } + + return ''; +} + +function readBalancedParens(content, startIndex) { + let depth = 1; + let index = startIndex; + + while (index < content.length) { + const char = content[index]; + if (char === '\\') { + index += 2; + continue; + } + if (char === '(') depth += 1; + else if (char === ')') { + depth -= 1; + if (depth === 0) { + return { + value: content.slice(startIndex, index), + end: index, + }; + } + } + index += 1; + } + + return null; +} + +function parseMarkdownDestination(raw) { + const trimmed = raw.trim(); + if (!trimmed) return ''; + + if (trimmed.startsWith('<')) { + const end = trimmed.indexOf('>'); + if (end !== -1) return trimmed.slice(1, end).trim(); + } + + let index = 0; + while (index < trimmed.length) { + const char = trimmed[index]; + if (/\s/.test(char)) break; + if (char === '\\' && index + 1 < trimmed.length) { + index += 2; + continue; + } + index += 1; + } + + return trimmed.slice(0, index).replace(/\\([()])/g, '$1').trim(); +} + +function extractMarkdownInlineLinks(content, lineFromIndex) { + const links = []; + const openerPattern = /!?\[[^\]\n]*]\(/g; + + for (let match = openerPattern.exec(content); match; match = openerPattern.exec(content)) { + const openIndex = match.index; + const destinationStart = openIndex + match[0].length; + const parsed = readBalancedParens(content, destinationStart); + if (!parsed) continue; + + openerPattern.lastIndex = parsed.end + 1; + const target = parseMarkdownDestination(parsed.value); + if (!target) continue; + + links.push({ + target, + line: lineFromIndex(openIndex), + sourceType: 'markdown', + }); + } + + return links; +} + +function extractMarkdownReferenceLinks(content, lineFromIndex) { + const links = []; + markdownReferencePattern.lastIndex = 0; + for ( + let match = markdownReferencePattern.exec(content); + match; + match = markdownReferencePattern.exec(content) + ) { + const target = parseMarkdownDestination(match[1]); + if (!target) continue; + links.push({ + target, + line: lineFromIndex(match.index), + sourceType: 'markdown-reference', + }); + } + return links; +} + +function extractMarkdownAutolinks(content, lineFromIndex) { + const links = []; + markdownAutolinkPattern.lastIndex = 0; + for ( + let match = markdownAutolinkPattern.exec(content); + match; + match = markdownAutolinkPattern.exec(content) + ) { + links.push({ + target: match[1].trim(), + line: lineFromIndex(match.index), + sourceType: 'markdown-autolink', + }); + } + return links; +} + +function extractJsxAttributeLinks(content, lineFromIndex) { + const links = []; + jsxAttributePattern.lastIndex = 0; + for ( + let match = jsxAttributePattern.exec(content); + match; + match = jsxAttributePattern.exec(content) + ) { + const target = match[2] ?? match[3] ?? match[4] ?? match[5] ?? ''; + if (!target.trim()) continue; + links.push({ + target: target.trim(), + line: lineFromIndex(match.index), + sourceType: `jsx-${match[1]}`, + }); + } + return links; +} + +function extractAllLinks(content) { + const lineFromIndex = createLineResolver(content); + const frontmatterMasked = maskFrontmatter(content); + const maskedForLinks = maskWithPatterns(frontmatterMasked, [ + ...fencePatterns, + ...commentPatterns, + indentedCodePattern, + inlineCodePattern, + ]); + + return [ + ...extractMarkdownInlineLinks(maskedForLinks, lineFromIndex), + ...extractMarkdownReferenceLinks(maskedForLinks, lineFromIndex), + ...extractMarkdownAutolinks(maskedForLinks, lineFromIndex), + ...extractJsxAttributeLinks(maskedForLinks, lineFromIndex), + ]; +} + +function stripHeadingFormatting(rawHeading) { + return rawHeading + .replace(/\s+#+\s*$/, '') + .replace(/\s*\{#([A-Za-z0-9:_-]+)\}\s*$/, '') + .replace(/`([^`]+)`/g, '$1') + .replace(/\[([^\]]+)]\([^)]+\)/g, '$1') + .replace(/<[^>]+>/g, '') + .replace(/[*_~]/g, '') + .replace(/&[a-zA-Z0-9#]+;/g, '') + .trim(); +} + +function slugifyHeading(rawHeading) { + const normalized = stripHeadingFormatting(rawHeading) + .toLowerCase() + .normalize('NFKD') + .replace(/[\u0300-\u036f]/g, '') + .replace( + /[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g, + '', + ) + .replace(/\s/g, '-') + .replace(/^-+|-+$/g, ''); + + return normalized; +} + +function extractHeadings(content) { + const { frontmatterRaw, body } = parseFrontmatter(content); + const frontmatterTitle = parseFrontmatterTitle(frontmatterRaw); + const masked = maskWithPatterns(body, [...fencePatterns, ...commentPatterns, indentedCodePattern]); + const anchors = new Set(); + const slugCount = new Map(); + + if (frontmatterTitle) { + const titleSlug = slugifyHeading(frontmatterTitle); + if (titleSlug) { + anchors.add(titleSlug); + slugCount.set(titleSlug, 1); + } + } + + const lines = masked.split('\n'); + for (const line of lines) { + const headingMatch = line.match(/^\s{0,3}#{1,6}\s+(.+?)\s*$/); + if (!headingMatch) continue; + + const rawHeading = headingMatch[1].trim(); + if (!rawHeading) continue; + + const explicitIdMatch = rawHeading.match(/\{#([A-Za-z0-9:_-]+)\}\s*$/); + if (explicitIdMatch?.[1]) anchors.add(explicitIdMatch[1]); + + const slugBase = slugifyHeading(rawHeading); + if (!slugBase) continue; + + const current = slugCount.get(slugBase) ?? 0; + const slug = current === 0 ? slugBase : `${slugBase}-${current}`; + slugCount.set(slugBase, current + 1); + anchors.add(slug); + } + + htmlIdPattern.lastIndex = 0; + for (let match = htmlIdPattern.exec(masked); match; match = htmlIdPattern.exec(masked)) { + const id = (match[1] ?? match[2] ?? '').trim(); + if (id) anchors.add(id); + } + + return anchors; +} + +function splitTarget(rawTarget) { + const trimmed = rawTarget.trim().replace(/^<|>$/g, ''); + const hashIndex = trimmed.indexOf('#'); + const queryIndex = trimmed.indexOf('?'); + + const pathEnd = + hashIndex === -1 + ? queryIndex === -1 + ? trimmed.length + : queryIndex + : queryIndex === -1 + ? hashIndex + : Math.min(hashIndex, queryIndex); + + const pathname = trimmed.slice(0, pathEnd); + const query = + queryIndex === -1 ? '' : trimmed.slice(queryIndex + 1, hashIndex === -1 ? undefined : hashIndex); + const fragment = hashIndex === -1 ? '' : trimmed.slice(hashIndex + 1); + + return { normalized: trimmed, pathname, query, fragment }; +} + +function isSkippableScheme(target) { + return /^(mailto:|tel:|javascript:|data:)/i.test(target); +} + +function extractScheme(target) { + const match = target.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):/); + return match ? match[1].toLowerCase() : ''; +} + +function isExternalHttpTarget(target) { + return /^https?:\/\//i.test(target) || /^\/\/[^/]/.test(target); +} + +function normalizeExternalUrl(target) { + return target.startsWith('//') ? `https:${target}` : target; +} + +function hasAssetExtension(pathname) { + const ext = path.posix.extname(pathname).toLowerCase(); + return assetExtensions.has(ext); +} + +const docsRouteBasePath = '/docs'; + +function stripDocsBasePath(pathname) { + if (pathname === docsRouteBasePath) return '/'; + if (pathname.startsWith(docsRouteBasePath + '/')) return pathname.slice(docsRouteBasePath.length); + return pathname; +} + +function isBareInternalPath(pathname) { + if (!pathname) return false; + if (pathname.startsWith('/')) return false; + if (pathname.startsWith('./') || pathname.startsWith('../')) return false; + if (hasAssetExtension(pathname)) return false; + if (pathname.includes(':')) return false; + return true; +} + +function resolveDocsRoute(pathname, sourceBaseRoute) { + const normalizedPath = stripMarkdownExtension(pathname); + if (!normalizedPath) return sourceBaseRoute; + if (normalizedPath.startsWith('/')) return normalizeRoute(normalizedPath); + return normalizeRoute(path.posix.join(sourceBaseRoute, normalizedPath)); +} + +function resolveRelativeAssetPath(pathname, sourceAbsoluteFilePath) { + if (pathname.startsWith('/')) return path.join(publicRoot, pathname.replace(/^\//, '')); + return path.resolve(path.dirname(sourceAbsoluteFilePath), pathname); +} + +function summarizeAnchors(anchorSet, limit = 8) { + const anchors = [...anchorSet].sort((a, b) => a.localeCompare(b)); + if (anchors.length <= limit) return anchors; + return [...anchors.slice(0, limit), `... (+${anchors.length - limit} more)`]; +} + +const urlShortenerDomains = new Set([ + 'bit.ly', 'goo.gl', 't.co', 'tinyurl.com', 'wkf.ms', + 'forms.gle', 'is.gd', 'buff.ly', 'ow.ly', 'rb.gy', +]); + +const requestHeaders = { + 'user-agent': 'Mozilla/5.0 (compatible; Yellow-Docs-LinkChecker/1.0; +https://docs.yellow.org)', + 'accept': 'text/html,application/xhtml+xml,*/*;q=0.8', + 'accept-language': 'en-US,en;q=0.5', + 'accept-encoding': 'identity', +}; + +function isUrlShortener(url) { + try { + const hostname = new URL(url).hostname.toLowerCase(); + return urlShortenerDomains.has(hostname); + } catch { + return false; + } +} + +function createFinding({ severity, file, line, link, type, message, status, details }) { + return { severity, file, line, link, type, message, status, details }; +} + +async function requestUrl(url, { method, timeoutMs, followRedirects = false }) { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), timeoutMs); + + try { + const response = await fetch(url, { + method, + redirect: followRedirects ? 'follow' : 'manual', + signal: controller.signal, + headers: requestHeaders, + }); + + const location = response.headers.get('location') || ''; + + return { + kind: 'response', + method, + status: response.status, + statusText: response.statusText || '', + location, + }; + } catch (error) { + if (error instanceof Error && error.name === 'AbortError') { + return { + kind: 'timeout', + method, + error: `Timeout after ${timeoutMs}ms`, + }; + } + return { + kind: 'network', + method, + error: error instanceof Error ? error.message : String(error), + }; + } finally { + clearTimeout(timer); + } +} + +function resolveRedirectLocation(base, location) { + if (!location) return ''; + try { + return new URL(location, base).href; + } catch { + return location; + } +} + +async function followRedirectChain(url, { method, timeoutMs, maxHops = 5 }) { + const chain = []; + let current = url; + + for (let hop = 0; hop < maxHops; hop += 1) { + const result = await requestUrl(current, { method, timeoutMs, followRedirects: false }); + + if (result.kind !== 'response') { + return { finalResult: result, chain, finalUrl: current }; + } + + const status = result.status; + if (status >= 300 && status < 400 && result.location) { + const nextUrl = resolveRedirectLocation(current, result.location); + chain.push({ from: current, to: nextUrl, status }); + current = nextUrl; + continue; + } + + return { finalResult: result, chain, finalUrl: current }; + } + + const lastResult = await requestUrl(current, { method, timeoutMs, followRedirects: true }); + return { finalResult: lastResult, chain, finalUrl: current }; +} + +function classifyExternalResult(result) { + if (result.kind === 'timeout' || result.kind === 'network') { + return { + severity: 'warn', + status: '', + message: `${result.error} (${result.method}) - URL may work in a browser`, + }; + } + + const status = result.status; + const statusText = result.statusText || ''; + const statusLabel = statusText ? `HTTP ${status} ${statusText}` : `HTTP ${status}`; + + if (status >= 200 && status < 300) { + return { severity: 'ok', status: statusLabel, message: '' }; + } + + if (status >= 300 && status < 400) { + return { severity: 'ok', status: statusLabel, message: '' }; + } + + if (status === 403 || status === 429) { + return { + severity: 'warn', + status: statusLabel, + message: status === 403 ? 'HTTP 403 Forbidden (may be login-gated or rate-limited)' : 'HTTP 429 Too Many Requests', + }; + } + + if (status === 404 || status === 410) { + return { severity: 'error', status: statusLabel, message: statusLabel }; + } + + if (status >= 500) { + return { severity: 'error', status: statusLabel, message: statusLabel }; + } + + return { severity: 'warn', status: statusLabel, message: `${statusLabel} (unexpected status)` }; +} + +async function checkExternalUrl(url) { + if (shouldSkipUrl(url)) { + return { + severity: 'skip', + status: '', + message: 'Skipped by LINK_CHECK_SKIP', + redirectChain: [], + }; + } + + const maxAttempts = 2; + + for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { + let { finalResult, chain, finalUrl } = await followRedirectChain(url, { + method: 'HEAD', + timeoutMs: externalTimeoutMs, + }); + + if (finalResult.kind === 'response' && (finalResult.status === 405 || finalResult.status === 501)) { + ({ finalResult, chain, finalUrl } = await followRedirectChain(url, { + method: 'GET', + timeoutMs: externalTimeoutMs, + })); + } + + if (finalResult.kind !== 'response') { + const getChain = await followRedirectChain(url, { + method: 'GET', + timeoutMs: externalTimeoutMs, + }); + if (getChain.finalResult.kind === 'response') { + finalResult = getChain.finalResult; + chain = getChain.chain; + finalUrl = getChain.finalUrl; + } + } + + const classified = classifyExternalResult(finalResult); + + if (classified.severity === 'ok' || classified.severity === 'warn') { + let redirectInfo = ''; + if (chain.length > 0 && !isUrlShortener(url)) { + redirectInfo = finalUrl; + } + return { ...classified, redirectChain: chain, redirectTarget: redirectInfo }; + } + + const isRetryable = + finalResult.kind !== 'response' || finalResult.status >= 500; + + if (!isRetryable || attempt >= maxAttempts) { + return { ...classified, redirectChain: chain, redirectTarget: '' }; + } + } + + return { severity: 'warn', status: '', message: 'Exhausted retries', redirectChain: [], redirectTarget: '' }; +} + +async function runConcurrent(items, concurrency, worker, onItemDone) { + const outputs = new Map(); + const queue = [...items]; + const maxWorkers = Math.max(1, Math.min(concurrency, queue.length || 1)); + + const workers = Array.from({ length: maxWorkers }, async () => { + while (queue.length > 0) { + const item = queue.shift(); + if (item === undefined) continue; + const value = await worker(item); + outputs.set(item, value); + if (onItemDone) onItemDone(item, value); + } + }); + + await Promise.all(workers); + return outputs; +} + +function printFindingBlock(title, findings, labelOverride) { + if (findings.length === 0) return; + console.log(`${title} (${findings.length})\n`); + for (const finding of findings) { + console.log(` ${finding.file}:${finding.line}`); + console.log(` Link: ${finding.link}`); + console.log(` Type: ${finding.type}`); + if (finding.status) console.log(` Status: ${finding.status}`); + const label = labelOverride || (finding.severity === 'error' ? 'Error' : finding.severity === 'info' ? 'Info' : 'Warn'); + console.log(` ${label}: ${finding.message}`); + if (finding.details) console.log(` Found: ${finding.details}`); + console.log(''); + } +} + +function printReport({ stats, errors, warnings, infos }) { + console.log('=============================================================================='); + console.log('LINK CHECK REPORT'); + console.log('=============================================================================='); + console.log(''); + + if (errors.length === 0 && warnings.length === 0 && infos.length === 0) { + console.log('No broken links found.\n'); + } else { + printFindingBlock('BROKEN LINKS', errors); + printFindingBlock('WARNINGS', warnings); + printFindingBlock('REDIRECTS', infos, 'Info'); + } + + console.log('------------------------------------------------------------------------------'); + console.log( + `Summary: ${stats.files} files | ${stats.totalLinks} links | ${errors.length} errors | ${warnings.length} warnings | ${infos.length} redirects`, + ); + console.log( + ` Internal: ${stats.internal.checked} checked, ${stats.internal.broken} broken`, + ); + console.log( + ` Anchors: ${stats.anchors.checked} checked, ${stats.anchors.broken} broken`, + ); + + if (externalEnabled) { + console.log( + ` External: ${stats.external.checked} checked, ${stats.external.broken} broken, ${stats.external.warnings} warnings (cached: ${stats.external.unique} unique URLs, cache hits: ${stats.external.cacheHits}, skipped: ${stats.external.skipped})`, + ); + } else { + console.log( + ` External: ${stats.external.discovered} discovered, checking disabled via LINK_CHECK_EXTERNAL=false`, + ); + } + + console.log( + ` Assets: ${stats.assets.checked} checked, ${stats.assets.broken} broken`, + ); + console.log('=============================================================================='); +} + +function printJsonReport({ stats, errors, warnings, infos }) { + const payload = { + format: 'link-check-report-v1', + generatedAt: new Date().toISOString(), + summary: { + files: stats.files, + totalLinks: stats.totalLinks, + errors: errors.length, + warnings: warnings.length, + redirects: infos.length, + internal: stats.internal, + anchors: stats.anchors, + external: externalEnabled ? stats.external : { discovered: stats.external.discovered }, + assets: stats.assets, + }, + findings: { + errors, + warnings, + redirects: infos, + }, + config: { + linkCheckExternal: externalEnabled, + linkCheckConcurrency: externalConcurrency, + linkCheckTimeoutMs: externalTimeoutMs, + linkCheckFormat: outputFormat, + linkCheckWarnBare: warnOnBareInternalLinks, + }, + }; + + console.log(JSON.stringify(payload, null, 2)); +} + +async function run() { + const docsDirs = [docsRoot]; + if (fs.existsSync(versionedDocsRoot)) docsDirs.push(versionedDocsRoot); + + const allAbsoluteFiles = []; + for (const dir of docsDirs) { + const found = await listMdxFiles(dir); + allAbsoluteFiles.push(...found.map((f) => ({ absolute: f, root: dir }))); + } + + const files = allAbsoluteFiles + .map(({ absolute, root }) => path.relative(root, absolute)) + .sort((a, b) => a.localeCompare(b)); + const fileRoots = new Map(allAbsoluteFiles.map(({ absolute, root }) => [path.relative(root, absolute), root])); + + const records = new Map(); + const routeToRecord = new Map(); + + for (const relativeFile of files) { + const fileRoot = fileRoots.get(relativeFile) ?? docsRoot; + const absoluteFile = path.join(fileRoot, relativeFile); + const content = await fs.promises.readFile(absoluteFile, 'utf8'); + const route = fileToRoute(relativeFile); + const baseRoute = fileToBaseRoute(relativeFile, route); + const anchors = extractHeadings(content); + + const record = { + relativeFile, + absoluteFile, + content, + route, + baseRoute, + anchors, + }; + + records.set(relativeFile, record); + routeToRecord.set(route, record); + routeToRecord.set(normalizeRoute(`${route}/index`), record); + } + + const stats = { + files: files.length, + totalLinks: 0, + internal: { checked: 0, broken: 0 }, + anchors: { checked: 0, broken: 0 }, + external: { + discovered: 0, + checked: 0, + unique: 0, + cacheHits: 0, + skipped: 0, + broken: 0, + warnings: 0, + }, + assets: { checked: 0, broken: 0 }, + }; + + const findings = []; + const externalUsage = new Map(); + + for (const record of records.values()) { + const links = extractAllLinks(record.content); + + for (const link of links) { + const rawTarget = link.target.trim(); + if (!rawTarget) continue; + + const scheme = extractScheme(rawTarget); + if (isSkippableScheme(rawTarget)) continue; + if (scheme && !['http', 'https'].includes(scheme)) { + stats.totalLinks += 1; + stats.internal.checked += 1; + stats.internal.broken += 1; + findings.push( + createFinding({ + severity: 'error', + file: record.relativeFile, + line: link.line, + link: rawTarget, + type: 'scheme', + message: `Unsupported link scheme: ${scheme}:`, + }), + ); + continue; + } + + const parsed = splitTarget(rawTarget); + if (!parsed.normalized) continue; + + if (isExternalHttpTarget(parsed.normalized)) { + const externalUrl = normalizeExternalUrl(parsed.normalized); + stats.totalLinks += 1; + stats.external.discovered += 1; + + if (!externalUsage.has(externalUrl)) externalUsage.set(externalUrl, []); + externalUsage.get(externalUrl).push({ + file: record.relativeFile, + line: link.line, + link: rawTarget, + }); + continue; + } + + const fragment = decodeURIComponentSafe(parsed.fragment); + const hasFragment = Boolean(fragment); + const pathname = decodeURIComponentSafe(parsed.pathname); + + if (!pathname && hasFragment) { + stats.totalLinks += 1; + stats.anchors.checked += 1; + + if (!record.anchors.has(fragment) && !record.anchors.has(fragment.toLowerCase())) { + stats.anchors.broken += 1; + findings.push( + createFinding({ + severity: 'error', + file: record.relativeFile, + line: link.line, + link: rawTarget, + type: 'anchor', + message: `Anchor #${fragment} not found in current page`, + details: summarizeAnchors(record.anchors).join(', '), + }), + ); + } + continue; + } + + if (!pathname && !hasFragment) continue; + + const resolvedPathname = stripDocsBasePath(pathname); + + const isAssetPath = hasAssetExtension(pathname) || pathname.startsWith('/assets/'); + if (isAssetPath) { + stats.totalLinks += 1; + stats.assets.checked += 1; + + const resolvedAssetPath = resolveRelativeAssetPath(pathname, record.absoluteFile); + if (!fs.existsSync(resolvedAssetPath)) { + stats.assets.broken += 1; + findings.push( + createFinding({ + severity: 'error', + file: record.relativeFile, + line: link.line, + link: rawTarget, + type: 'asset', + message: `Asset not found at ${toPosix(path.relative(repoRoot, resolvedAssetPath))}`, + }), + ); + } + continue; + } + + stats.totalLinks += 1; + stats.internal.checked += 1; + + if ( + warnOnBareInternalLinks && + link.sourceType.startsWith('jsx-') && + isBareInternalPath(pathname) + ) { + findings.push( + createFinding({ + severity: 'warn', + file: record.relativeFile, + line: link.line, + link: rawTarget, + type: 'internal-style', + message: 'Bare internal link. Prefer ./, ../, or / for clearer route intent.', + }), + ); + } + + const resolvedRoute = resolveDocsRoute(resolvedPathname, record.baseRoute); + const targetRecord = routeToRecord.get(resolvedRoute); + + if (!targetRecord) { + stats.internal.broken += 1; + findings.push( + createFinding({ + severity: 'error', + file: record.relativeFile, + line: link.line, + link: rawTarget, + type: hasFragment ? 'internal + anchor' : 'internal', + message: `Route not found: ${resolvedRoute}`, + }), + ); + continue; + } + + if (hasFragment) { + stats.anchors.checked += 1; + if (!targetRecord.anchors.has(fragment) && !targetRecord.anchors.has(fragment.toLowerCase())) { + stats.anchors.broken += 1; + findings.push( + createFinding({ + severity: 'error', + file: record.relativeFile, + line: link.line, + link: rawTarget, + type: 'internal + anchor', + message: `Anchor #${fragment} not found in ${resolvedRoute}`, + details: summarizeAnchors(targetRecord.anchors).join(', '), + }), + ); + } + } + } + } + + if (externalEnabled && externalUsage.size > 0) { + stats.external.unique = externalUsage.size; + const urls = [...externalUsage.keys()]; + let completedUnique = 0; + if (outputFormat !== 'json') { + console.error(`Checking external URLs: 0/${urls.length}...`); + } + + const externalResults = await runConcurrent( + urls, + externalConcurrency, + async (url) => checkExternalUrl(url), + () => { + completedUnique += 1; + if ( + outputFormat !== 'json' && + (completedUnique % externalProgressEvery === 0 || completedUnique === urls.length) + ) { + console.error(`Checking external URLs: ${completedUnique}/${urls.length}...`); + } + }, + ); + + let checkedUnique = 0; + + for (const [url, result] of externalResults.entries()) { + const usages = externalUsage.get(url) ?? []; + if (result.severity === 'skip') { + stats.external.skipped += usages.length; + continue; + } + + checkedUnique += 1; + stats.external.checked += usages.length; + + if (result.severity === 'warn') stats.external.warnings += usages.length; + if (result.severity === 'error') stats.external.broken += usages.length; + + if (result.severity === 'warn' || result.severity === 'error') { + for (const usage of usages) { + findings.push( + createFinding({ + severity: result.severity, + file: usage.file, + line: usage.line, + link: usage.link, + type: 'external', + status: result.status, + message: result.message, + }), + ); + } + } + + if (result.redirectTarget) { + for (const usage of usages) { + findings.push( + createFinding({ + severity: 'info', + file: usage.file, + line: usage.line, + link: usage.link, + type: 'external (redirect)', + message: `Redirects to ${result.redirectTarget} -- consider updating to the final URL.`, + }), + ); + } + } + } + + stats.external.cacheHits = Math.max(0, stats.external.checked - checkedUnique); + } + + const errors = findings + .filter((f) => f.severity === 'error') + .sort((a, b) => `${a.file}:${a.line}`.localeCompare(`${b.file}:${b.line}`)); + const warnings = findings + .filter((f) => f.severity === 'warn') + .sort((a, b) => `${a.file}:${a.line}`.localeCompare(`${b.file}:${b.line}`)); + const infos = findings + .filter((f) => f.severity === 'info') + .sort((a, b) => `${a.file}:${a.line}`.localeCompare(`${b.file}:${b.line}`)); + + if (outputFormat === 'json') { + printJsonReport({ stats, errors, warnings, infos }); + } else { + printReport({ stats, errors, warnings, infos }); + } + + if (errors.length > 0) { + process.exitCode = 1; + return; + } + + if (outputFormat !== 'json') { + console.log(`\n✅ check-links: scanned ${stats.files} MDX files, no broken links found.`); + } +} + +run().catch((error) => { + console.error(`❌ check-links failed: ${error instanceof Error ? error.message : String(error)}`); + process.exitCode = 1; +}); diff --git a/src/clientModules/hideContractsOn05x.js b/src/clientModules/hideContractsOn05x.js deleted file mode 100644 index 2b50d8e..0000000 --- a/src/clientModules/hideContractsOn05x.js +++ /dev/null @@ -1,22 +0,0 @@ -const CONTRACTS_CLASS = 'navbar-contracts-link'; -const LEGACY_PATH = '/docs/0.5.x/'; - -function toggle() { - const el = document.querySelector(`.${CONTRACTS_CLASS}`)?.closest('.navbar__item'); - if (!el) return; - el.style.display = window.location.pathname.startsWith(LEGACY_PATH) ? 'none' : ''; -} - -if (typeof window !== 'undefined') { - toggle(); - - const observer = new MutationObserver(toggle); - observer.observe(document.querySelector('#__docusaurus') || document.body, { - childList: true, - subtree: true, - }); -} - -export function onRouteDidUpdate() { - toggle(); -} diff --git a/versioned_docs/version-0.5.x/build/quick-start/index.md b/versioned_docs/version-0.5.x/build/quick-start/index.md index d397f78..75cad82 100644 --- a/versioned_docs/version-0.5.x/build/quick-start/index.md +++ b/versioned_docs/version-0.5.x/build/quick-start/index.md @@ -347,7 +347,7 @@ await app.sendPayment('100000', '0xPartnerAddress'); // Send 0.1 USDC Congratulations! You've built your first Yellow App. Here's what to explore next: -- **[Advanced Topics](../../learn/advanced/architecture)**: Learn about architecture, multi-party applications, and production deployment +- **[Advanced Topics](../../learn/introduction/architecture-at-a-glance)**: Learn about architecture, multi-party applications, and production deployment - **[API Reference](../../api-reference)**: Explore all available SDK methods and options ## Need Help? diff --git a/versioned_docs/version-0.5.x/learn/introduction/supported-chains.mdx b/versioned_docs/version-0.5.x/learn/introduction/supported-chains.mdx index 55e0560..38ec916 100644 --- a/versioned_docs/version-0.5.x/learn/introduction/supported-chains.mdx +++ b/versioned_docs/version-0.5.x/learn/introduction/supported-chains.mdx @@ -253,8 +253,8 @@ Contract addresses vary by blockchain. See the [deployment repository](https://g Need support for a blockchain or asset not listed here? -- **[Request Blockchain Support](/docs/manuals/request-blockchain-support)** — Guide for adding new blockchain networks -- **[Request Asset Support](/docs/manuals/request-asset-support)** — Guide for adding new tokens/assets +- **[Request Blockchain Support](../../../manuals/request-blockchain-support)** — Guide for adding new blockchain networks +- **[Request Asset Support](../../../manuals/request-asset-support)** — Guide for adding new tokens/assets --- From 3af3e8c27050a96ee2cf56642ccbdbe15decd249 Mon Sep 17 00:00:00 2001 From: Maharshi Mishra Date: Sat, 7 Mar 2026 18:58:00 +0530 Subject: [PATCH 4/6] Remove custom link checker and fix contracts docs link --- docs/build/api/contracts/sdk/api-reference.md | 2 +- package.json | 3 +- scripts/check-links.mjs | 1174 ----------------- 3 files changed, 2 insertions(+), 1177 deletions(-) delete mode 100644 scripts/check-links.mjs diff --git a/docs/build/api/contracts/sdk/api-reference.md b/docs/build/api/contracts/sdk/api-reference.md index e962632..1be2702 100644 --- a/docs/build/api/contracts/sdk/api-reference.md +++ b/docs/build/api/contracts/sdk/api-reference.md @@ -86,4 +86,4 @@ type ContractAddresses = { ### Current Addresses -See [Deployed Addresses](../operations/addresses.md) for the full list. +See [Deployed Addresses](../index.md) for the full list. diff --git a/package.json b/package.json index d126af7..4e3eff3 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,7 @@ "sync:contracts": "node scripts/sync-contracts-docs.js", "version:remove": "node scripts/remove-version.js", "version:reset": "node scripts/reset-versions.js", - "version:release": "node scripts/release-version.js", - "check-links": "node scripts/check-links.mjs" + "version:release": "node scripts/release-version.js" }, "dependencies": { "@docusaurus/core": "3.9.2", diff --git a/scripts/check-links.mjs b/scripts/check-links.mjs deleted file mode 100644 index f63e29c..0000000 --- a/scripts/check-links.mjs +++ /dev/null @@ -1,1174 +0,0 @@ -#!/usr/bin/env node - -import fs from 'node:fs'; -import path from 'node:path'; - -const repoRoot = process.cwd(); -const docsRoot = path.join(repoRoot, 'docs'); -const versionedDocsRoot = path.join(repoRoot, 'versioned_docs', 'version-0.5.x'); -const publicRoot = path.join(repoRoot, 'static'); -const linkcheckIgnorePath = path.join(repoRoot, '.linkcheckignore'); - -const outputFormat = normalizeOutputFormat(process.env.LINK_CHECK_FORMAT ?? 'text'); -const externalEnabled = parseBooleanEnv('LINK_CHECK_EXTERNAL', true); -const externalConcurrency = parsePositiveIntEnv('LINK_CHECK_CONCURRENCY', 5); -const externalTimeoutMs = parsePositiveIntEnv('LINK_CHECK_TIMEOUT', 15_000); -const externalProgressEvery = parsePositiveIntEnv('LINK_CHECK_PROGRESS_EVERY', 10); -const warnOnBareInternalLinks = parseBooleanEnv('LINK_CHECK_WARN_BARE', true); -const externalSkipMatchers = [ - ...loadSkipMatchersFromFile(linkcheckIgnorePath), - ...parseSkipMatchers(process.env.LINK_CHECK_SKIP ?? ''), -]; - -const fencePatterns = [/```[\s\S]*?```/g, /~~~[\s\S]*?~~~/g]; -const commentPatterns = [/{\/\*[\s\S]*?\*\/}/g, //g]; -const inlineCodePattern = /`[^`\n]*`/g; -const indentedCodePattern = /^(?: {4}|\t).+$/gm; - -const markdownReferencePattern = /^\s*\[[^\]]+]:\s*(\S+)/gm; -const markdownAutolinkPattern = /<((?:https?:\/\/|mailto:|tel:)[^>\s]+)>/g; -const jsxAttributePattern = - /\b(href|src|url)\s*=\s*(?:"([^"]+)"|'([^']+)'|\{\s*"([^"]+)"\s*\}|\{\s*'([^']+)'\s*\})/g; -const htmlIdPattern = /\bid\s*=\s*(?:"([^"]+)"|'([^']+)')/g; - -const assetExtensions = new Set([ - '.avif', - '.bmp', - '.csv', - '.gif', - '.ico', - '.jpeg', - '.jpg', - '.json', - '.mp3', - '.mp4', - '.pdf', - '.png', - '.svg', - '.txt', - '.wav', - '.webm', - '.webp', - '.woff', - '.woff2', - '.xml', - '.yaml', - '.yml', - '.zip', -]); - -function parseBooleanEnv(name, fallback) { - const raw = process.env[name]; - if (raw === undefined) return fallback; - const value = raw.trim().toLowerCase(); - if (['0', 'false', 'no', 'off'].includes(value)) return false; - if (['1', 'true', 'yes', 'on'].includes(value)) return true; - return fallback; -} - -function parsePositiveIntEnv(name, fallback) { - const raw = process.env[name]; - if (raw === undefined) return fallback; - const parsed = Number.parseInt(raw, 10); - if (!Number.isFinite(parsed) || parsed <= 0) return fallback; - return parsed; -} - -function normalizeOutputFormat(raw) { - const normalized = raw.trim().toLowerCase(); - return normalized === 'json' ? 'json' : 'text'; -} - -function parseSkipEntries(chunks) { - const cleaned = chunks.map((chunk) => chunk.trim()).filter(Boolean); - - return cleaned.map((chunk) => { - if (chunk.startsWith('/') && chunk.endsWith('/') && chunk.length > 2) { - try { - return { type: 'regex', value: new RegExp(chunk.slice(1, -1), 'i') }; - } catch { - return { type: 'substring', value: chunk.toLowerCase() }; - } - } - return { type: 'substring', value: chunk.toLowerCase() }; - }); -} - -function parseSkipMatchers(raw) { - return parseSkipEntries(raw.split(',')); -} - -function loadSkipMatchersFromFile(filePath) { - if (!fs.existsSync(filePath)) return []; - const raw = fs.readFileSync(filePath, 'utf8'); - const chunks = raw - .split(/\r?\n/) - .map((line) => line.trim()) - .filter((line) => line && !line.startsWith('#')); - - return parseSkipEntries(chunks); -} - -function shouldSkipUrl(url) { - for (const matcher of externalSkipMatchers) { - if (matcher.type === 'regex' && matcher.value.test(url)) return true; - if (matcher.type === 'substring' && url.toLowerCase().includes(matcher.value)) return true; - } - return false; -} - -function toPosix(filePath) { - return filePath.split(path.sep).join(path.posix.sep); -} - -async function listMdxFiles(rootDir) { - const results = []; - const stack = [rootDir]; - - while (stack.length > 0) { - const current = stack.pop(); - const entries = await fs.promises.readdir(current, { withFileTypes: true }); - for (const entry of entries) { - const full = path.join(current, entry.name); - if (entry.isDirectory()) { - stack.push(full); - continue; - } - if (entry.isFile() && (entry.name.endsWith('.mdx') || entry.name.endsWith('.md'))) { - results.push(full); - } - } - } - - return results.sort((a, b) => a.localeCompare(b)); -} - -function stripMarkdownExtension(segment) { - return segment.replace(/\.mdx?$/i, ''); -} - -function normalizeRoute(route) { - let normalized = route.replace(/\\/g, '/').trim(); - if (normalized === '') return '/'; - if (!normalized.startsWith('/')) normalized = `/${normalized}`; - normalized = path.posix.normalize(normalized); - - if (!normalized.startsWith('/')) normalized = `/${normalized}`; - if (normalized.length > 1) normalized = normalized.replace(/\/+$/, ''); - - if (normalized.length > 1 && normalized.endsWith('/index')) { - normalized = normalized.slice(0, -'/index'.length); - if (normalized === '') normalized = '/'; - } - - return normalized || '/'; -} - -function fileToRoute(relativeMdxPath) { - const noExt = stripMarkdownExtension(toPosix(relativeMdxPath)); - if (noExt === 'index') return '/'; - if (noExt.endsWith('/index')) return normalizeRoute(`/${noExt.slice(0, -'/index'.length)}`); - return normalizeRoute(`/${noExt}`); -} - -function fileToBaseRoute(relativeMdxPath, route) { - const noExt = stripMarkdownExtension(toPosix(relativeMdxPath)); - if (noExt === 'index' || noExt.endsWith('/index')) return route; - return normalizeRoute(path.posix.dirname(route)); -} - -function createLineResolver(content) { - const lineStarts = [0]; - for (let index = 0; index < content.length; index += 1) { - if (content[index] === '\n') lineStarts.push(index + 1); - } - - return (index) => { - let low = 0; - let high = lineStarts.length - 1; - while (low <= high) { - const mid = Math.floor((low + high) / 2); - if (lineStarts[mid] <= index) low = mid + 1; - else high = mid - 1; - } - return high + 1; - }; -} - -function maskWithPatterns(content, patterns) { - let masked = content; - for (const pattern of patterns) { - pattern.lastIndex = 0; - masked = masked.replace(pattern, (segment) => segment.replace(/[^\n]/g, ' ')); - } - return masked; -} - -function decodeURIComponentSafe(value) { - if (!value) return value; - try { - return decodeURIComponent(value); - } catch { - return value; - } -} - -function parseFrontmatter(content) { - const match = content.match(/^\uFEFF?---[ \t]*\r?\n[\s\S]*?\r?\n---[ \t]*(?:\r?\n|$)/); - if (!match) { - return { - frontmatterRaw: '', - body: content, - }; - } - - return { - frontmatterRaw: match[0], - body: content.slice(match[0].length), - }; -} - -function maskFrontmatter(content) { - const { frontmatterRaw } = parseFrontmatter(content); - if (!frontmatterRaw) return content; - - return `${frontmatterRaw.replace(/[^\n]/g, ' ')}${content.slice(frontmatterRaw.length)}`; -} - -function parseFrontmatterTitle(frontmatterRaw) { - if (!frontmatterRaw) return ''; - - const lines = frontmatterRaw.split(/\r?\n/); - for (let index = 1; index < lines.length; index += 1) { - const line = lines[index]; - const match = line.match(/^\s*title\s*:\s*(.+?)\s*$/); - if (!match) continue; - - let title = match[1].trim(); - if ( - (title.startsWith('"') && title.endsWith('"')) || - (title.startsWith("'") && title.endsWith("'")) - ) { - title = title.slice(1, -1); - } - return title.trim(); - } - - return ''; -} - -function readBalancedParens(content, startIndex) { - let depth = 1; - let index = startIndex; - - while (index < content.length) { - const char = content[index]; - if (char === '\\') { - index += 2; - continue; - } - if (char === '(') depth += 1; - else if (char === ')') { - depth -= 1; - if (depth === 0) { - return { - value: content.slice(startIndex, index), - end: index, - }; - } - } - index += 1; - } - - return null; -} - -function parseMarkdownDestination(raw) { - const trimmed = raw.trim(); - if (!trimmed) return ''; - - if (trimmed.startsWith('<')) { - const end = trimmed.indexOf('>'); - if (end !== -1) return trimmed.slice(1, end).trim(); - } - - let index = 0; - while (index < trimmed.length) { - const char = trimmed[index]; - if (/\s/.test(char)) break; - if (char === '\\' && index + 1 < trimmed.length) { - index += 2; - continue; - } - index += 1; - } - - return trimmed.slice(0, index).replace(/\\([()])/g, '$1').trim(); -} - -function extractMarkdownInlineLinks(content, lineFromIndex) { - const links = []; - const openerPattern = /!?\[[^\]\n]*]\(/g; - - for (let match = openerPattern.exec(content); match; match = openerPattern.exec(content)) { - const openIndex = match.index; - const destinationStart = openIndex + match[0].length; - const parsed = readBalancedParens(content, destinationStart); - if (!parsed) continue; - - openerPattern.lastIndex = parsed.end + 1; - const target = parseMarkdownDestination(parsed.value); - if (!target) continue; - - links.push({ - target, - line: lineFromIndex(openIndex), - sourceType: 'markdown', - }); - } - - return links; -} - -function extractMarkdownReferenceLinks(content, lineFromIndex) { - const links = []; - markdownReferencePattern.lastIndex = 0; - for ( - let match = markdownReferencePattern.exec(content); - match; - match = markdownReferencePattern.exec(content) - ) { - const target = parseMarkdownDestination(match[1]); - if (!target) continue; - links.push({ - target, - line: lineFromIndex(match.index), - sourceType: 'markdown-reference', - }); - } - return links; -} - -function extractMarkdownAutolinks(content, lineFromIndex) { - const links = []; - markdownAutolinkPattern.lastIndex = 0; - for ( - let match = markdownAutolinkPattern.exec(content); - match; - match = markdownAutolinkPattern.exec(content) - ) { - links.push({ - target: match[1].trim(), - line: lineFromIndex(match.index), - sourceType: 'markdown-autolink', - }); - } - return links; -} - -function extractJsxAttributeLinks(content, lineFromIndex) { - const links = []; - jsxAttributePattern.lastIndex = 0; - for ( - let match = jsxAttributePattern.exec(content); - match; - match = jsxAttributePattern.exec(content) - ) { - const target = match[2] ?? match[3] ?? match[4] ?? match[5] ?? ''; - if (!target.trim()) continue; - links.push({ - target: target.trim(), - line: lineFromIndex(match.index), - sourceType: `jsx-${match[1]}`, - }); - } - return links; -} - -function extractAllLinks(content) { - const lineFromIndex = createLineResolver(content); - const frontmatterMasked = maskFrontmatter(content); - const maskedForLinks = maskWithPatterns(frontmatterMasked, [ - ...fencePatterns, - ...commentPatterns, - indentedCodePattern, - inlineCodePattern, - ]); - - return [ - ...extractMarkdownInlineLinks(maskedForLinks, lineFromIndex), - ...extractMarkdownReferenceLinks(maskedForLinks, lineFromIndex), - ...extractMarkdownAutolinks(maskedForLinks, lineFromIndex), - ...extractJsxAttributeLinks(maskedForLinks, lineFromIndex), - ]; -} - -function stripHeadingFormatting(rawHeading) { - return rawHeading - .replace(/\s+#+\s*$/, '') - .replace(/\s*\{#([A-Za-z0-9:_-]+)\}\s*$/, '') - .replace(/`([^`]+)`/g, '$1') - .replace(/\[([^\]]+)]\([^)]+\)/g, '$1') - .replace(/<[^>]+>/g, '') - .replace(/[*_~]/g, '') - .replace(/&[a-zA-Z0-9#]+;/g, '') - .trim(); -} - -function slugifyHeading(rawHeading) { - const normalized = stripHeadingFormatting(rawHeading) - .toLowerCase() - .normalize('NFKD') - .replace(/[\u0300-\u036f]/g, '') - .replace( - /[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g, - '', - ) - .replace(/\s/g, '-') - .replace(/^-+|-+$/g, ''); - - return normalized; -} - -function extractHeadings(content) { - const { frontmatterRaw, body } = parseFrontmatter(content); - const frontmatterTitle = parseFrontmatterTitle(frontmatterRaw); - const masked = maskWithPatterns(body, [...fencePatterns, ...commentPatterns, indentedCodePattern]); - const anchors = new Set(); - const slugCount = new Map(); - - if (frontmatterTitle) { - const titleSlug = slugifyHeading(frontmatterTitle); - if (titleSlug) { - anchors.add(titleSlug); - slugCount.set(titleSlug, 1); - } - } - - const lines = masked.split('\n'); - for (const line of lines) { - const headingMatch = line.match(/^\s{0,3}#{1,6}\s+(.+?)\s*$/); - if (!headingMatch) continue; - - const rawHeading = headingMatch[1].trim(); - if (!rawHeading) continue; - - const explicitIdMatch = rawHeading.match(/\{#([A-Za-z0-9:_-]+)\}\s*$/); - if (explicitIdMatch?.[1]) anchors.add(explicitIdMatch[1]); - - const slugBase = slugifyHeading(rawHeading); - if (!slugBase) continue; - - const current = slugCount.get(slugBase) ?? 0; - const slug = current === 0 ? slugBase : `${slugBase}-${current}`; - slugCount.set(slugBase, current + 1); - anchors.add(slug); - } - - htmlIdPattern.lastIndex = 0; - for (let match = htmlIdPattern.exec(masked); match; match = htmlIdPattern.exec(masked)) { - const id = (match[1] ?? match[2] ?? '').trim(); - if (id) anchors.add(id); - } - - return anchors; -} - -function splitTarget(rawTarget) { - const trimmed = rawTarget.trim().replace(/^<|>$/g, ''); - const hashIndex = trimmed.indexOf('#'); - const queryIndex = trimmed.indexOf('?'); - - const pathEnd = - hashIndex === -1 - ? queryIndex === -1 - ? trimmed.length - : queryIndex - : queryIndex === -1 - ? hashIndex - : Math.min(hashIndex, queryIndex); - - const pathname = trimmed.slice(0, pathEnd); - const query = - queryIndex === -1 ? '' : trimmed.slice(queryIndex + 1, hashIndex === -1 ? undefined : hashIndex); - const fragment = hashIndex === -1 ? '' : trimmed.slice(hashIndex + 1); - - return { normalized: trimmed, pathname, query, fragment }; -} - -function isSkippableScheme(target) { - return /^(mailto:|tel:|javascript:|data:)/i.test(target); -} - -function extractScheme(target) { - const match = target.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):/); - return match ? match[1].toLowerCase() : ''; -} - -function isExternalHttpTarget(target) { - return /^https?:\/\//i.test(target) || /^\/\/[^/]/.test(target); -} - -function normalizeExternalUrl(target) { - return target.startsWith('//') ? `https:${target}` : target; -} - -function hasAssetExtension(pathname) { - const ext = path.posix.extname(pathname).toLowerCase(); - return assetExtensions.has(ext); -} - -const docsRouteBasePath = '/docs'; - -function stripDocsBasePath(pathname) { - if (pathname === docsRouteBasePath) return '/'; - if (pathname.startsWith(docsRouteBasePath + '/')) return pathname.slice(docsRouteBasePath.length); - return pathname; -} - -function isBareInternalPath(pathname) { - if (!pathname) return false; - if (pathname.startsWith('/')) return false; - if (pathname.startsWith('./') || pathname.startsWith('../')) return false; - if (hasAssetExtension(pathname)) return false; - if (pathname.includes(':')) return false; - return true; -} - -function resolveDocsRoute(pathname, sourceBaseRoute) { - const normalizedPath = stripMarkdownExtension(pathname); - if (!normalizedPath) return sourceBaseRoute; - if (normalizedPath.startsWith('/')) return normalizeRoute(normalizedPath); - return normalizeRoute(path.posix.join(sourceBaseRoute, normalizedPath)); -} - -function resolveRelativeAssetPath(pathname, sourceAbsoluteFilePath) { - if (pathname.startsWith('/')) return path.join(publicRoot, pathname.replace(/^\//, '')); - return path.resolve(path.dirname(sourceAbsoluteFilePath), pathname); -} - -function summarizeAnchors(anchorSet, limit = 8) { - const anchors = [...anchorSet].sort((a, b) => a.localeCompare(b)); - if (anchors.length <= limit) return anchors; - return [...anchors.slice(0, limit), `... (+${anchors.length - limit} more)`]; -} - -const urlShortenerDomains = new Set([ - 'bit.ly', 'goo.gl', 't.co', 'tinyurl.com', 'wkf.ms', - 'forms.gle', 'is.gd', 'buff.ly', 'ow.ly', 'rb.gy', -]); - -const requestHeaders = { - 'user-agent': 'Mozilla/5.0 (compatible; Yellow-Docs-LinkChecker/1.0; +https://docs.yellow.org)', - 'accept': 'text/html,application/xhtml+xml,*/*;q=0.8', - 'accept-language': 'en-US,en;q=0.5', - 'accept-encoding': 'identity', -}; - -function isUrlShortener(url) { - try { - const hostname = new URL(url).hostname.toLowerCase(); - return urlShortenerDomains.has(hostname); - } catch { - return false; - } -} - -function createFinding({ severity, file, line, link, type, message, status, details }) { - return { severity, file, line, link, type, message, status, details }; -} - -async function requestUrl(url, { method, timeoutMs, followRedirects = false }) { - const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), timeoutMs); - - try { - const response = await fetch(url, { - method, - redirect: followRedirects ? 'follow' : 'manual', - signal: controller.signal, - headers: requestHeaders, - }); - - const location = response.headers.get('location') || ''; - - return { - kind: 'response', - method, - status: response.status, - statusText: response.statusText || '', - location, - }; - } catch (error) { - if (error instanceof Error && error.name === 'AbortError') { - return { - kind: 'timeout', - method, - error: `Timeout after ${timeoutMs}ms`, - }; - } - return { - kind: 'network', - method, - error: error instanceof Error ? error.message : String(error), - }; - } finally { - clearTimeout(timer); - } -} - -function resolveRedirectLocation(base, location) { - if (!location) return ''; - try { - return new URL(location, base).href; - } catch { - return location; - } -} - -async function followRedirectChain(url, { method, timeoutMs, maxHops = 5 }) { - const chain = []; - let current = url; - - for (let hop = 0; hop < maxHops; hop += 1) { - const result = await requestUrl(current, { method, timeoutMs, followRedirects: false }); - - if (result.kind !== 'response') { - return { finalResult: result, chain, finalUrl: current }; - } - - const status = result.status; - if (status >= 300 && status < 400 && result.location) { - const nextUrl = resolveRedirectLocation(current, result.location); - chain.push({ from: current, to: nextUrl, status }); - current = nextUrl; - continue; - } - - return { finalResult: result, chain, finalUrl: current }; - } - - const lastResult = await requestUrl(current, { method, timeoutMs, followRedirects: true }); - return { finalResult: lastResult, chain, finalUrl: current }; -} - -function classifyExternalResult(result) { - if (result.kind === 'timeout' || result.kind === 'network') { - return { - severity: 'warn', - status: '', - message: `${result.error} (${result.method}) - URL may work in a browser`, - }; - } - - const status = result.status; - const statusText = result.statusText || ''; - const statusLabel = statusText ? `HTTP ${status} ${statusText}` : `HTTP ${status}`; - - if (status >= 200 && status < 300) { - return { severity: 'ok', status: statusLabel, message: '' }; - } - - if (status >= 300 && status < 400) { - return { severity: 'ok', status: statusLabel, message: '' }; - } - - if (status === 403 || status === 429) { - return { - severity: 'warn', - status: statusLabel, - message: status === 403 ? 'HTTP 403 Forbidden (may be login-gated or rate-limited)' : 'HTTP 429 Too Many Requests', - }; - } - - if (status === 404 || status === 410) { - return { severity: 'error', status: statusLabel, message: statusLabel }; - } - - if (status >= 500) { - return { severity: 'error', status: statusLabel, message: statusLabel }; - } - - return { severity: 'warn', status: statusLabel, message: `${statusLabel} (unexpected status)` }; -} - -async function checkExternalUrl(url) { - if (shouldSkipUrl(url)) { - return { - severity: 'skip', - status: '', - message: 'Skipped by LINK_CHECK_SKIP', - redirectChain: [], - }; - } - - const maxAttempts = 2; - - for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { - let { finalResult, chain, finalUrl } = await followRedirectChain(url, { - method: 'HEAD', - timeoutMs: externalTimeoutMs, - }); - - if (finalResult.kind === 'response' && (finalResult.status === 405 || finalResult.status === 501)) { - ({ finalResult, chain, finalUrl } = await followRedirectChain(url, { - method: 'GET', - timeoutMs: externalTimeoutMs, - })); - } - - if (finalResult.kind !== 'response') { - const getChain = await followRedirectChain(url, { - method: 'GET', - timeoutMs: externalTimeoutMs, - }); - if (getChain.finalResult.kind === 'response') { - finalResult = getChain.finalResult; - chain = getChain.chain; - finalUrl = getChain.finalUrl; - } - } - - const classified = classifyExternalResult(finalResult); - - if (classified.severity === 'ok' || classified.severity === 'warn') { - let redirectInfo = ''; - if (chain.length > 0 && !isUrlShortener(url)) { - redirectInfo = finalUrl; - } - return { ...classified, redirectChain: chain, redirectTarget: redirectInfo }; - } - - const isRetryable = - finalResult.kind !== 'response' || finalResult.status >= 500; - - if (!isRetryable || attempt >= maxAttempts) { - return { ...classified, redirectChain: chain, redirectTarget: '' }; - } - } - - return { severity: 'warn', status: '', message: 'Exhausted retries', redirectChain: [], redirectTarget: '' }; -} - -async function runConcurrent(items, concurrency, worker, onItemDone) { - const outputs = new Map(); - const queue = [...items]; - const maxWorkers = Math.max(1, Math.min(concurrency, queue.length || 1)); - - const workers = Array.from({ length: maxWorkers }, async () => { - while (queue.length > 0) { - const item = queue.shift(); - if (item === undefined) continue; - const value = await worker(item); - outputs.set(item, value); - if (onItemDone) onItemDone(item, value); - } - }); - - await Promise.all(workers); - return outputs; -} - -function printFindingBlock(title, findings, labelOverride) { - if (findings.length === 0) return; - console.log(`${title} (${findings.length})\n`); - for (const finding of findings) { - console.log(` ${finding.file}:${finding.line}`); - console.log(` Link: ${finding.link}`); - console.log(` Type: ${finding.type}`); - if (finding.status) console.log(` Status: ${finding.status}`); - const label = labelOverride || (finding.severity === 'error' ? 'Error' : finding.severity === 'info' ? 'Info' : 'Warn'); - console.log(` ${label}: ${finding.message}`); - if (finding.details) console.log(` Found: ${finding.details}`); - console.log(''); - } -} - -function printReport({ stats, errors, warnings, infos }) { - console.log('=============================================================================='); - console.log('LINK CHECK REPORT'); - console.log('=============================================================================='); - console.log(''); - - if (errors.length === 0 && warnings.length === 0 && infos.length === 0) { - console.log('No broken links found.\n'); - } else { - printFindingBlock('BROKEN LINKS', errors); - printFindingBlock('WARNINGS', warnings); - printFindingBlock('REDIRECTS', infos, 'Info'); - } - - console.log('------------------------------------------------------------------------------'); - console.log( - `Summary: ${stats.files} files | ${stats.totalLinks} links | ${errors.length} errors | ${warnings.length} warnings | ${infos.length} redirects`, - ); - console.log( - ` Internal: ${stats.internal.checked} checked, ${stats.internal.broken} broken`, - ); - console.log( - ` Anchors: ${stats.anchors.checked} checked, ${stats.anchors.broken} broken`, - ); - - if (externalEnabled) { - console.log( - ` External: ${stats.external.checked} checked, ${stats.external.broken} broken, ${stats.external.warnings} warnings (cached: ${stats.external.unique} unique URLs, cache hits: ${stats.external.cacheHits}, skipped: ${stats.external.skipped})`, - ); - } else { - console.log( - ` External: ${stats.external.discovered} discovered, checking disabled via LINK_CHECK_EXTERNAL=false`, - ); - } - - console.log( - ` Assets: ${stats.assets.checked} checked, ${stats.assets.broken} broken`, - ); - console.log('=============================================================================='); -} - -function printJsonReport({ stats, errors, warnings, infos }) { - const payload = { - format: 'link-check-report-v1', - generatedAt: new Date().toISOString(), - summary: { - files: stats.files, - totalLinks: stats.totalLinks, - errors: errors.length, - warnings: warnings.length, - redirects: infos.length, - internal: stats.internal, - anchors: stats.anchors, - external: externalEnabled ? stats.external : { discovered: stats.external.discovered }, - assets: stats.assets, - }, - findings: { - errors, - warnings, - redirects: infos, - }, - config: { - linkCheckExternal: externalEnabled, - linkCheckConcurrency: externalConcurrency, - linkCheckTimeoutMs: externalTimeoutMs, - linkCheckFormat: outputFormat, - linkCheckWarnBare: warnOnBareInternalLinks, - }, - }; - - console.log(JSON.stringify(payload, null, 2)); -} - -async function run() { - const docsDirs = [docsRoot]; - if (fs.existsSync(versionedDocsRoot)) docsDirs.push(versionedDocsRoot); - - const allAbsoluteFiles = []; - for (const dir of docsDirs) { - const found = await listMdxFiles(dir); - allAbsoluteFiles.push(...found.map((f) => ({ absolute: f, root: dir }))); - } - - const files = allAbsoluteFiles - .map(({ absolute, root }) => path.relative(root, absolute)) - .sort((a, b) => a.localeCompare(b)); - const fileRoots = new Map(allAbsoluteFiles.map(({ absolute, root }) => [path.relative(root, absolute), root])); - - const records = new Map(); - const routeToRecord = new Map(); - - for (const relativeFile of files) { - const fileRoot = fileRoots.get(relativeFile) ?? docsRoot; - const absoluteFile = path.join(fileRoot, relativeFile); - const content = await fs.promises.readFile(absoluteFile, 'utf8'); - const route = fileToRoute(relativeFile); - const baseRoute = fileToBaseRoute(relativeFile, route); - const anchors = extractHeadings(content); - - const record = { - relativeFile, - absoluteFile, - content, - route, - baseRoute, - anchors, - }; - - records.set(relativeFile, record); - routeToRecord.set(route, record); - routeToRecord.set(normalizeRoute(`${route}/index`), record); - } - - const stats = { - files: files.length, - totalLinks: 0, - internal: { checked: 0, broken: 0 }, - anchors: { checked: 0, broken: 0 }, - external: { - discovered: 0, - checked: 0, - unique: 0, - cacheHits: 0, - skipped: 0, - broken: 0, - warnings: 0, - }, - assets: { checked: 0, broken: 0 }, - }; - - const findings = []; - const externalUsage = new Map(); - - for (const record of records.values()) { - const links = extractAllLinks(record.content); - - for (const link of links) { - const rawTarget = link.target.trim(); - if (!rawTarget) continue; - - const scheme = extractScheme(rawTarget); - if (isSkippableScheme(rawTarget)) continue; - if (scheme && !['http', 'https'].includes(scheme)) { - stats.totalLinks += 1; - stats.internal.checked += 1; - stats.internal.broken += 1; - findings.push( - createFinding({ - severity: 'error', - file: record.relativeFile, - line: link.line, - link: rawTarget, - type: 'scheme', - message: `Unsupported link scheme: ${scheme}:`, - }), - ); - continue; - } - - const parsed = splitTarget(rawTarget); - if (!parsed.normalized) continue; - - if (isExternalHttpTarget(parsed.normalized)) { - const externalUrl = normalizeExternalUrl(parsed.normalized); - stats.totalLinks += 1; - stats.external.discovered += 1; - - if (!externalUsage.has(externalUrl)) externalUsage.set(externalUrl, []); - externalUsage.get(externalUrl).push({ - file: record.relativeFile, - line: link.line, - link: rawTarget, - }); - continue; - } - - const fragment = decodeURIComponentSafe(parsed.fragment); - const hasFragment = Boolean(fragment); - const pathname = decodeURIComponentSafe(parsed.pathname); - - if (!pathname && hasFragment) { - stats.totalLinks += 1; - stats.anchors.checked += 1; - - if (!record.anchors.has(fragment) && !record.anchors.has(fragment.toLowerCase())) { - stats.anchors.broken += 1; - findings.push( - createFinding({ - severity: 'error', - file: record.relativeFile, - line: link.line, - link: rawTarget, - type: 'anchor', - message: `Anchor #${fragment} not found in current page`, - details: summarizeAnchors(record.anchors).join(', '), - }), - ); - } - continue; - } - - if (!pathname && !hasFragment) continue; - - const resolvedPathname = stripDocsBasePath(pathname); - - const isAssetPath = hasAssetExtension(pathname) || pathname.startsWith('/assets/'); - if (isAssetPath) { - stats.totalLinks += 1; - stats.assets.checked += 1; - - const resolvedAssetPath = resolveRelativeAssetPath(pathname, record.absoluteFile); - if (!fs.existsSync(resolvedAssetPath)) { - stats.assets.broken += 1; - findings.push( - createFinding({ - severity: 'error', - file: record.relativeFile, - line: link.line, - link: rawTarget, - type: 'asset', - message: `Asset not found at ${toPosix(path.relative(repoRoot, resolvedAssetPath))}`, - }), - ); - } - continue; - } - - stats.totalLinks += 1; - stats.internal.checked += 1; - - if ( - warnOnBareInternalLinks && - link.sourceType.startsWith('jsx-') && - isBareInternalPath(pathname) - ) { - findings.push( - createFinding({ - severity: 'warn', - file: record.relativeFile, - line: link.line, - link: rawTarget, - type: 'internal-style', - message: 'Bare internal link. Prefer ./, ../, or / for clearer route intent.', - }), - ); - } - - const resolvedRoute = resolveDocsRoute(resolvedPathname, record.baseRoute); - const targetRecord = routeToRecord.get(resolvedRoute); - - if (!targetRecord) { - stats.internal.broken += 1; - findings.push( - createFinding({ - severity: 'error', - file: record.relativeFile, - line: link.line, - link: rawTarget, - type: hasFragment ? 'internal + anchor' : 'internal', - message: `Route not found: ${resolvedRoute}`, - }), - ); - continue; - } - - if (hasFragment) { - stats.anchors.checked += 1; - if (!targetRecord.anchors.has(fragment) && !targetRecord.anchors.has(fragment.toLowerCase())) { - stats.anchors.broken += 1; - findings.push( - createFinding({ - severity: 'error', - file: record.relativeFile, - line: link.line, - link: rawTarget, - type: 'internal + anchor', - message: `Anchor #${fragment} not found in ${resolvedRoute}`, - details: summarizeAnchors(targetRecord.anchors).join(', '), - }), - ); - } - } - } - } - - if (externalEnabled && externalUsage.size > 0) { - stats.external.unique = externalUsage.size; - const urls = [...externalUsage.keys()]; - let completedUnique = 0; - if (outputFormat !== 'json') { - console.error(`Checking external URLs: 0/${urls.length}...`); - } - - const externalResults = await runConcurrent( - urls, - externalConcurrency, - async (url) => checkExternalUrl(url), - () => { - completedUnique += 1; - if ( - outputFormat !== 'json' && - (completedUnique % externalProgressEvery === 0 || completedUnique === urls.length) - ) { - console.error(`Checking external URLs: ${completedUnique}/${urls.length}...`); - } - }, - ); - - let checkedUnique = 0; - - for (const [url, result] of externalResults.entries()) { - const usages = externalUsage.get(url) ?? []; - if (result.severity === 'skip') { - stats.external.skipped += usages.length; - continue; - } - - checkedUnique += 1; - stats.external.checked += usages.length; - - if (result.severity === 'warn') stats.external.warnings += usages.length; - if (result.severity === 'error') stats.external.broken += usages.length; - - if (result.severity === 'warn' || result.severity === 'error') { - for (const usage of usages) { - findings.push( - createFinding({ - severity: result.severity, - file: usage.file, - line: usage.line, - link: usage.link, - type: 'external', - status: result.status, - message: result.message, - }), - ); - } - } - - if (result.redirectTarget) { - for (const usage of usages) { - findings.push( - createFinding({ - severity: 'info', - file: usage.file, - line: usage.line, - link: usage.link, - type: 'external (redirect)', - message: `Redirects to ${result.redirectTarget} -- consider updating to the final URL.`, - }), - ); - } - } - } - - stats.external.cacheHits = Math.max(0, stats.external.checked - checkedUnique); - } - - const errors = findings - .filter((f) => f.severity === 'error') - .sort((a, b) => `${a.file}:${a.line}`.localeCompare(`${b.file}:${b.line}`)); - const warnings = findings - .filter((f) => f.severity === 'warn') - .sort((a, b) => `${a.file}:${a.line}`.localeCompare(`${b.file}:${b.line}`)); - const infos = findings - .filter((f) => f.severity === 'info') - .sort((a, b) => `${a.file}:${a.line}`.localeCompare(`${b.file}:${b.line}`)); - - if (outputFormat === 'json') { - printJsonReport({ stats, errors, warnings, infos }); - } else { - printReport({ stats, errors, warnings, infos }); - } - - if (errors.length > 0) { - process.exitCode = 1; - return; - } - - if (outputFormat !== 'json') { - console.log(`\n✅ check-links: scanned ${stats.files} MDX files, no broken links found.`); - } -} - -run().catch((error) => { - console.error(`❌ check-links failed: ${error instanceof Error ? error.message : String(error)}`); - process.exitCode = 1; -}); From ee75e376cda8971fe561a1a180b62dda6fe62ab3 Mon Sep 17 00:00:00 2001 From: Maharshi Mishra Date: Sat, 7 Mar 2026 19:10:50 +0530 Subject: [PATCH 5/6] Remove security token locking docs added to wrong branch These docs belong with the feat/integrate-locking SDK changes, not this docs branch. Made-with: Cursor --- docs/build/sdk/typescript-compat/overview.mdx | 28 ----- docs/build/sdk/typescript/api-reference.mdx | 100 ------------------ docs/build/sdk/typescript/examples.mdx | 45 -------- 3 files changed, 173 deletions(-) diff --git a/docs/build/sdk/typescript-compat/overview.mdx b/docs/build/sdk/typescript-compat/overview.mdx index a669ee4..718aa23 100644 --- a/docs/build/sdk/typescript-compat/overview.mdx +++ b/docs/build/sdk/typescript-compat/overview.mdx @@ -103,34 +103,6 @@ await client.close(); | `toWalletQuorumSignature(signature)` | Prefix wallet signature for app-session quorum format | | `toSessionKeyQuorumSignature(signature)` | Prefix session key signature (`0xa2`) for quorum format | -### Security Token Locking - -| Method | Description | -|--------|-------------| -| `approveSecurityToken(amount, chainId?)` | Approve the locking contract to spend tokens | -| `escrowSecurityTokens(targetWallet, amount, chainId?)` | Lock tokens for a target address | -| `initiateSecurityTokensWithdrawal(chainId?)` | Start the unlock process | -| `cancelSecurityTokensWithdrawal(chainId?)` | Cancel a pending unlock (relock) | -| `withdrawSecurityTokens(destination, chainId?)` | Withdraw after the unlock period | -| `getLockedBalance(wallet?, chainId?)` | Query locked balance (returns `Decimal`) | - -The `chainId` parameter is optional and defaults to the chain configured at client creation. - -**Lifecycle:** `approve` → `escrow` → *(optionally)* `initiateWithdrawal` → *(wait)* → `withdraw` - -```typescript -import Decimal from 'decimal.js'; - -await client.approveSecurityToken(new Decimal('1000')); -await client.escrowSecurityTokens(walletAddress, new Decimal('1000')); - -const balance = await client.getLockedBalance(); -// To withdraw: initiate unlock, wait for period, then withdraw -await client.initiateSecurityTokensWithdrawal(); -// ... after unlock period ... -await client.withdrawSecurityTokens(destinationAddress); -``` - ### Session Keys | Method | Description | diff --git a/docs/build/sdk/typescript/api-reference.mdx b/docs/build/sdk/typescript/api-reference.mdx index 2502cd2..830eee2 100644 --- a/docs/build/sdk/typescript/api-reference.mdx +++ b/docs/build/sdk/typescript/api-reference.mdx @@ -148,106 +148,6 @@ const allowance = await client.checkTokenAllowance(80002n, '0xToken...', '0xOwne --- -## Security Token Locking - -Methods for interacting with the locking contract (NonSlashableAppRegistry). Used to lock YELLOW tokens as security collateral. - -The locking lifecycle is: **approve** → **lock** → *(optionally)* **unlock** → *(wait for unlock period)* → **withdraw**. At any point during the unlock period, you can **relock** to cancel the withdrawal. - -### `approveSecurityToken(chainId, amount)` - -Approves the locking contract to spend tokens on behalf of the caller. Must be called before `escrowSecurityTokens`. - -```typescript -await client.approveSecurityToken(11155111n, new Decimal('1000')); -``` - -**Parameters:** - -| Parameter | Type | Description | -|-----------|------|-------------| -| `chainId` | `bigint` | Blockchain network ID | -| `amount` | `Decimal` | Amount of tokens to approve | - ---- - -### `escrowSecurityTokens(targetWallet, blockchainId, amount)` - -Locks tokens into the locking contract for the specified target address. - -```typescript -await client.escrowSecurityTokens('0xTarget...', 11155111n, new Decimal('1000')); -``` - -**Parameters:** - -| Parameter | Type | Description | -|-----------|------|-------------| -| `targetWallet` | `string` | Address to lock tokens for | -| `blockchainId` | `bigint` | Blockchain network ID | -| `amount` | `Decimal` | Amount of tokens to lock | - -**Requires:** Token allowance via `approveSecurityToken()`. - ---- - -### `initiateSecurityTokensWithdrawal(blockchainId)` - -Starts the unlock process. After the unlock period elapses, `withdrawSecurityTokens` can be called. - -```typescript -await client.initiateSecurityTokensWithdrawal(11155111n); -``` - ---- - -### `cancelSecurityTokensWithdrawal(blockchainId)` - -Cancels a pending unlock and returns tokens to the locked state (relock). - -```typescript -await client.cancelSecurityTokensWithdrawal(11155111n); -``` - ---- - -### `withdrawSecurityTokens(blockchainId, destination)` - -Withdraws unlocked tokens to a destination address. Can only be called after the unlock period has fully elapsed. - -```typescript -await client.withdrawSecurityTokens(11155111n, '0xDestination...'); -``` - -**Parameters:** - -| Parameter | Type | Description | -|-----------|------|-------------| -| `blockchainId` | `bigint` | Blockchain network ID | -| `destination` | `string` | Address to receive withdrawn tokens | - ---- - -### `getLockedBalance(chainId, wallet)` - -Returns the locked token balance for a wallet. - -```typescript -const balance = await client.getLockedBalance(11155111n, '0xWallet...'); -console.log('Locked:', balance.toString()); -``` - -**Parameters:** - -| Parameter | Type | Description | -|-----------|------|-------------| -| `chainId` | `bigint` | Blockchain network ID | -| `wallet` | `string` | Address to check | - -**Returns:** `Decimal` — the locked balance adjusted for token decimals. - ---- - ## Node Information ```typescript diff --git a/docs/build/sdk/typescript/examples.mdx b/docs/build/sdk/typescript/examples.mdx index b3f4d29..84494e5 100644 --- a/docs/build/sdk/typescript/examples.mdx +++ b/docs/build/sdk/typescript/examples.mdx @@ -347,51 +347,6 @@ async function setupSessionKey(client: Client) { } ``` -## Security Token Locking - -Lock YELLOW tokens as security collateral via the locking contract (NonSlashableAppRegistry). - -```typescript -import { Client, createSigners, withBlockchainRPC } from '@yellow-org/sdk'; -import Decimal from 'decimal.js'; - -async function lockSecurityTokens() { - const { stateSigner, txSigner } = createSigners(process.env.PRIVATE_KEY!); - const chainId = 11155111n; - - const client = await Client.create( - 'wss://clearnode.example.com/ws', - stateSigner, - txSigner, - withBlockchainRPC(chainId, 'https://rpc.sepolia.io') - ); - - const wallet = txSigner.getAddress(); - const amount = new Decimal('1000'); - - // 1. Approve the locking contract to spend tokens - await client.approveSecurityToken(chainId, amount); - - // 2. Lock tokens (for yourself or another address) - await client.escrowSecurityTokens(wallet, chainId, amount); - - // 3. Check locked balance - const balance = await client.getLockedBalance(chainId, wallet); - console.log('Locked balance:', balance.toString()); - - // 4. Initiate withdrawal (starts unlock period) - await client.initiateSecurityTokensWithdrawal(chainId); - - // 5a. Cancel if needed (relock) - await client.cancelSecurityTokensWithdrawal(chainId); - - // 5b. Or after unlock period elapses, withdraw - await client.withdrawSecurityTokens(chainId, wallet); - - await client.close(); -} -``` - ## Connection Monitoring ```typescript From 0ded26acae38f5b80d62254978b31b56a9c4692c Mon Sep 17 00:00:00 2001 From: Maharshi Mishra Date: Sat, 7 Mar 2026 19:20:15 +0530 Subject: [PATCH 6/6] Fix versioned manuals links --- .../version-0.5.x/learn/introduction/supported-chains.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/versioned_docs/version-0.5.x/learn/introduction/supported-chains.mdx b/versioned_docs/version-0.5.x/learn/introduction/supported-chains.mdx index 38ec916..0e58ebe 100644 --- a/versioned_docs/version-0.5.x/learn/introduction/supported-chains.mdx +++ b/versioned_docs/version-0.5.x/learn/introduction/supported-chains.mdx @@ -253,8 +253,8 @@ Contract addresses vary by blockchain. See the [deployment repository](https://g Need support for a blockchain or asset not listed here? -- **[Request Blockchain Support](../../../manuals/request-blockchain-support)** — Guide for adding new blockchain networks -- **[Request Asset Support](../../../manuals/request-asset-support)** — Guide for adding new tokens/assets +- **[Request Blockchain Support](../../manuals/request-blockchain-support)** — Guide for adding new blockchain networks +- **[Request Asset Support](../../manuals/request-asset-support)** — Guide for adding new tokens/assets ---