Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No more broken links #113

Merged
merged 12 commits into from
Sep 6, 2024
136 changes: 136 additions & 0 deletions .github/linkChecker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import get from 'axios';
import { readFileSync } from 'fs';
import { load } from 'cheerio';
import { sync as globSync } from 'glob';

const baseUrl = 'https://academy.avax.network';

const whitelist = [] // some websites return 404 for head requests, so we need to whitelist them, (fix: pass header -H 'Accept: text/html' and parse text/html)
// see https://github.com/rust-lang/crates.io/issues/788

interface LinkCheckResult {
file: string;
link: string;
line: number;
isValid: boolean;
}

function isValidURLOrPath(url: string): boolean {
try {
new URL(url)
return true
} catch {
if (url.startsWith("{") && url.endsWith("}")) { // is a a JSX component, ignore
return false;
}
else if (url.indexOf('.') > -1) { // is a url or misconfigured path
return true;
}
// where all our content lives
return url.startsWith("/");
}
}

async function checkLink(url: string): Promise<boolean> {
try {
const response = await get(url, {
timeout: 10000, // timeout to 10 seconds
maxRedirects: 5, // handle up to 5 redirects
validateStatus: function (status) {
return status >= 200 && status < 400; // resolve only if the status code is less than 400
},
headers: {
'User-Agent': 'Mozilla/5.0 (compatible; LinkChecker/1.0)', // Custom User-Agent
}
});
return response.status === 200;
} catch {
return false;
}
}

function extractLinksWithLineNumbers(mdxContent: string): { link: string; line: number }[] {
const lines = mdxContent.split('\n');
const links: { link: string; line: number }[] = [];

lines.forEach((line, index) => {
const $ = load(`<div>${line}</div>`);
$('a').each((i, elem) => {
const href = $(elem).attr('href');
if (href && isValidURLOrPath(href)) {
links.push({ link: href, line: index + 1 });
}
});

const markdownLinkRegex = /\[.*?\]\((.*?)\)/g;
let match;
while ((match = markdownLinkRegex.exec(line)) !== null) {
const link = match[1];
if (isValidURLOrPath(link)) {
links.push({ link, line: index + 1 });
}
}
});

return links;
}

async function checkAllMdxFiles(): Promise<void> {
const files = globSync('content/**/*.mdx');
console.log(`Found ${files.length} MDX files.`);

const results: LinkCheckResult[] = [];

for (const file of files) {
console.log(`Processing file: ${file}`);

const content = readFileSync(file, 'utf-8');
const links = extractLinksWithLineNumbers(content);

const cache: { [link: string]: boolean } = {};
let isValid: boolean;

for (const { link, line } of links) {
console.log(`Checking link: ${link} in file: ${file} (line ${line})`);

if (cache[link]) {
isValid = cache[link];
} else {
isValid = await checkLink(link); // check the link
if (!isValid) {
isValid = await checkLink(baseUrl + link); // if link failed check first time, try adding the base url (for internal relative links)
}
for (const wl of whitelist) {
if (link.includes(wl)) {
isValid = true;
break;
}
}
cache[link] = isValid;
}
results.push({ file, link, line, isValid });

if (!isValid) {
console.error(`\x1b[31mBroken link found\x1b[0m in ${file} (line ${line}): \x1b[33m${link}\x1b[0m`);
}
}
}


const brokenLinks = results.filter(result => !result.isValid);
if (brokenLinks.length > 0) {
console.error(`\n\x1b[31mSummary of broken links:\x1b[0m`);
brokenLinks.forEach(result => {
console.error(`File: \x1b[36m${result.file}\x1b[0m, Line: \x1b[33m${result.line}\x1b[0m, Link: \x1b[31m${result.link}\x1b[0m`);
});
process.exit(1);
} else {
console.log(`\x1b[32mAll links are valid.\x1b[0m`);
}
}

checkAllMdxFiles().catch(error => {
console.error('\x1b[31mError checking links:\x1b[0m', error);
process.exit(1);
});

21 changes: 21 additions & 0 deletions .github/workflows/checklinks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Check MDX Links

on:
pull_request:
paths:
- '**/*.mdx'

jobs:
check-links:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '19'
- name: Install dependencies
run: |
yarn install --frozen-lockfile
yarn global add tsx
- name: Check links
run: yarn check-links
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ The RPC URLs defined by default are the following:
- **fuji-c:** The C-Chain of the Fuji Testnet
- **dispatch:** An Avalanche L1 on the Fuji Testnet called Dispatch. It was specifically created for testing Avalanche Interchain Messaging dApps.

The ability to use an RPC with the name of our L1 comes from the alias API in our nodes. You can read more about it [here](https://docs.avax.network/reference/avalanchego/admin-api#adminalias).
The ability to use an RPC with the name of our L1 comes from the alias API in our nodes. You can read more about it [here](https://docs.avax.network/api-reference/admin-api#methods).

Feel free to add more RPC URLs to Avalanche L1s you create. You can also add more aliases for the RPC URLs to make it easier for you to send transactions to them.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,4 @@ contract TeleporterRegistry {
}
```

If you are interested in the entire implementation, check it out [here](https://github.com/ava-labs/teleporter/blob/main/contracts/src/Teleporter/upgrades/TeleporterRegistry.sol).
If you are interested in the entire implementation, check it out [here](https://github.com/ava-labs/teleporter/blob/main/contracts/teleporter/registry/TeleporterRegistry.sol).
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ The general config contains among others the following parameters:
}
```

- **info-api-url:** The URL of the [Info API](https://docs.avax.network/reference/avalanchego/info-api) node to which the relayer will connect to to receive information like the NetworkID.
- **p-chain-api-url:** The URL of the Avalanche [P-Chain API](https://docs.avax.network/reference/avalanchego/p-chain/api) node to which the relayer will connect to query the validator sets.
- **info-api-url:** The URL of the [Info API](https://docs.avax.network/api-reference/info-api) node to which the relayer will connect to to receive information like the NetworkID.
- **p-chain-api-url:** The URL of the Avalanche [P-Chain API](https://docs.avax.network/api-reference/p-chain/api) node to which the relayer will connect to query the validator sets.

## Source Blockchain Configs

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ icon: BookOpen
The basic idea of incentivizing an external AWM Relayer to relay our message is to deposit an amount of an ERC-20 token into the Interchain Messaging contract as a reward for whichever relayer delivers our message.

Let's see how the fees are transferred from the user at the very beginning of sending a Cross Chain message, up to the point where the relayer is able to claim the fee.
![AWM Relayer with fees data flow](public/common-images/teleporter/teleporter-fees-receipt.png)
![AWM Relayer with fees data flow](/common-images/teleporter/teleporter-fees-receipt.png)


We are using the following emoji guide to show the actors of each action:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ icon: Terminal

This guide will walk you through the process of using a bridge between 2 Avalanche blockchains, providing a step-by-step approach to ensure a smooth and secure experience.

Go to [ohmywarp.com](https://ohmywarp.com) and connect any web3 [wallet](core.app). Make sure your wallet has at least some AVAX on the Fuji Testnet. If you need funds you can go to the [faucet](faucet.avax.network). Use the coupon code `avalanche-academy` to get AVAX in C-chain
Go to [ohmywarp.com](https://ohmywarp.com) and connect any web3 [wallet](https://core.app). Make sure your wallet has at least some AVAX on the Fuji Testnet. If you need funds you can go to the [faucet](https://faucet.avax.network). Use the coupon code `avalanche-academy` to get AVAX in C-chain

Mint some `TLP` token on `C-Chain` in the Mint tab. This is an ERC20 deployed on Fuji's C-chain.
Finally bridge some `TLP` to `Dispatch`. You can confirm the transfer in the Mint tab.
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,14 @@ export SOURCE_BLOCKCHAIN_ID_HEX=<BlockchainID (HEX)>

### Deploy the Remote Contract

Using the [`forge create`](https://book.getfoundry.sh/reference/forge/forge-create) command, we will deploy the `ERC20Remote.sol` contract, passing in the following constructor arguments:
Using the [`forge create`](https://book.getfoundry.sh/reference/forge/forge-create) command, we will deploy the `ERC20TokenRemote.sol` contract, passing in the following constructor arguments:

- Interchain Messaging Registry Address **(for C-Chain)**
- Interchain Messaging Manager (our funded address)
- Source Blockchain ID (hexidecimal representation of our Avalanche L1's Blockchain ID)
- Token Home Address (address of `ERC20TokenHome.sol` deployed on Avalanche L1 in the last step)
- Token Name (input in the constructor of the [wrapped token contract](./ExampleWNATV.sol))
- Token Symbol (input in the constructor of the [wrapped token contract](./ExampleWNATV.sol))
- Token Home Address (address of NativeTokenHome.sol deployed on Avalanche L1 in the last step)
- Token Name (input in the constructor of the [wrapped token contract](https://github.com/ava-labs/avalanche-interchain-token-transfer/blob/main/contracts/src/WrappedNativeToken.sol))
- Token Symbol (input in the constructor of the [wrapped token contract](https://github.com/ava-labs/avalanche-interchain-token-transfer/blob/main/contracts/src/WrappedNativeToken.sol))
- Token Decimals (uint8 integer representing number of decimal places for the ERC20 token being created. Most ERC20 tokens follow the Ethereum standard, which defines 18 decimal places.)

```bash
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ authors: [owenwahlgren]

ICTT bridges deployed through [**AvaCloud**](https://avacloud.io/) will automatically integrate into the [**Core Bridge**](https://core.app/en/bridge). This ensures that any bridges created through AvaCloud are available immediately and do not need extra review.

{/* ![](/course-images/ictt/core-bridge.png) */}

However, **ICTT bridges** deployed outside of AvaCloud (by third-party developers or other methods) will need to be submitted for manual review. Developers will need to provide:

1. **Token Bridge Contract Address(es)**: The bridge contract(s) on the L1.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ icon: Book

## Wrapped Native Token

On your Avalanche L1, deploy a wrapped token contract for your native token. When we configured the Avalanche L1 earlier, we named the token `NATV`. This is reflected in line 19 of our [example wrapped token contract](./ExampleWNATV.sol).
On your Avalanche L1, deploy a wrapped token contract for your native token. When we configured the Avalanche L1 earlier, we named the token `NATV`. This is reflected in line 19 of our [example wrapped token contract](https://github.com/ava-labs/avalanche-interchain-token-transfer/blob/main/contracts/src/WrappedNativeToken.sol).

```
forge create --rpc-url myblockchain --private-key $PK src/5-native-to-erc20-token-bridge/ExampleWNATV.sol:WNATV
Expand All @@ -35,7 +35,7 @@ Transaction hash: 0x054e7b46b221c30f400b81df0fa2601668ae832054cf8e8b873f4ba615fa

To bridge the token out of your Avalanche L1, you'll need to first deploy a _home_ contract on your Avalanche L1 that implements the `INativeTokenBridge` interface, and inherits the properties of the `TeleporterTokenHome` contract standard.

Using the [`forge create`](https://book.getfoundry.sh/reference/forge/forge-create) command, we will deploy the [NativeTokenHome.sol](./NativeTokenHome.sol) contract, passing in the following constructor arguments:
Using the [`forge create`](https://book.getfoundry.sh/reference/forge/forge-create) command, we will deploy the [NativeTokenHome.sol](https://github.com/ava-labs/avalanche-interchain-token-transfer/blob/main/contracts/src/TokenHome/NativeTokenHome.sol) contract, passing in the following constructor arguments:

```bash
forge create --rpc-url myblockchain --private-key $PK lib/teleporter-token-bridge/contracts/src/TokenHome/NativeTokenHome.sol:NativeTokenHome --constructor-args $TELEPORTER_REGISTRY_SUBNET $FUNDED_ADDRESS $WRAPPED_ERC20_HOME_SUBNET
Expand Down Expand Up @@ -73,7 +73,7 @@ export SUBNET_BLOCKCHAIN_ID_HEX=0x4d569bf60a38e3ab3e92afd016fe37f7060d7d63c44e33

`Source Blockchain ID` is in the field: `Local Network BlockchainID (HEX)`.

Using the [`forge create`](https://book.getfoundry.sh/reference/forge/forge-create) command, we will deploy the [ERC20Remote.sol](./NativeTokenHome.sol) contract, passing in the following constructor arguments:
Using the [`forge create`](https://book.getfoundry.sh/reference/forge/forge-create) command, we will deploy the [ERC20RTokenRemote.sol](https://github.com/ava-labs/avalanche-interchain-token-transfer/blob/main/contracts/src/TokenRemote/ERC20TokenRemote.sol) contract, passing in the following constructor arguments:

```bash
forge create --rpc-url local-c --private-key $PK lib/teleporter-token-bridge/contracts/src/TokenRemote/ERC20TokenRemote.sol:ERC20TokenRemote --constructor-args "(${TELEPORTER_REGISTRY_C_CHAIN}, ${FUNDED_ADDRESS}, ${SUBNET_BLOCKCHAIN_ID_HEX}, ${ERC20_HOME_BRIDGE_SUBNET})" "Wrapped NATV" "WNATV" 18
Expand All @@ -83,8 +83,8 @@ forge create --rpc-url local-c --private-key $PK lib/teleporter-token-bridge/con
- Interchain Messaging Manager (our funded address)
- Source Blockchain ID (hexidecimal representation of our Avalanche L1's Blockchain ID)
- Token Home Address (address of NativeTokenHome.sol deployed on Avalanche L1 in the last step)
- Token Name (input in the constructor of the [wrapped token contract](./ExampleWNATV.sol))
- Token Symbol (input in the constructor of the [wrapped token contract](./ExampleWNATV.sol))
- Token Name (input in the constructor of the [wrapped token contract](https://github.com/ava-labs/avalanche-interchain-token-transfer/blob/main/contracts/src/WrappedNativeToken.sol))
- Token Symbol (input in the constructor of the [wrapped token contract](https://github.com/ava-labs/avalanche-interchain-token-transfer/blob/main/contracts/src/WrappedNativeToken.sol))
- Token Decimals (uint8 integer representing number of decimal places for the ERC20 token being created. Most ERC20 tokens follow the Ethereum standard, which defines 18 decimal places.)

For example, this contract deployment could be entered into your terminal as:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ cast send --rpc-url myblockchain --private-key $PK <TokenHomeAddress> \
--value <amountOfTokensToSend>
```

In line 60 of [`NativeTokenHome`](./NativeTokenHome.sol) is the send function we will call to send the tokens:
In [`NativeTokenHome`](https://github.com/ava-labs/avalanche-interchain-token-transfer/blob/main/contracts/src/TokenHome/NativeTokenHome.sol) is the send function we will call to send the tokens:

```solidity
function send(SendTokensInput calldata input) external payable {
_send(input, msg.value, false);
}
```

The function parameters are defined by the `SendTokensInput` struct defined in line 26 of [`ITeleporterTokenBridge`](./interfaces/ITeleporterTokenBridge.sol).
The function parameters are defined by the `SendTokensInput` struct defined in [`ITokenTransferrer.sol`](https://github.com/ava-labs/avalanche-interchain-token-transfer/blob/main/contracts/src/interfaces/ITokenTransferrer.sol).

```solidity
struct SendTokensInput {
Expand Down
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"scripts": {
"build": "next build",
"dev": "next dev",
"start": "next build && next start"
"start": "next build && next start",
"check-links": "tsx .github/linkChecker.ts"
},
"dependencies": {
"@headlessui/react": "^2.1.2",
Expand Down Expand Up @@ -33,8 +34,11 @@
"@types/react-dom": "^18.3.0",
"@types/uuid": "^10.0.0",
"autoprefixer": "^10.4.19",
"axios": "^1.7.7",
"cheerio": "^1.0.0",
"postcss": "^8.4.39",
"tailwindcss": "^3.4.4",
"tsx": "^4.19.0",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

"typescript": "^5.5.3"
}
}
Loading
Loading