diff --git a/.github/workflows/links.yml b/.github/workflows/links.yml
index 9da554a9..debe7a91 100644
--- a/.github/workflows/links.yml
+++ b/.github/workflows/links.yml
@@ -30,6 +30,11 @@ jobs:
with:
persist-credentials: false
+ - name: Setup Node.js
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
+ with:
+ node-version: 24.12.0
+
- name: Restore lychee cache
uses: actions/cache@v4
with:
@@ -44,8 +49,9 @@ jobs:
# broken references (LICENSE files, lockup paths); excluded
# from this check. Docs site content lives in src/pages/.
args: >-
- --root-dir ${{ github.workspace }}
- 'src/pages/**/*.{md,mdx}'
+ --preprocess scripts/filter-external-links.ts
+ 'src/pages/**/*.md'
+ 'src/pages/**/*.mdx'
'vocs.config.ts'
fail: ${{ github.event_name == 'pull_request' || github.event_name == 'push' }}
token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/lychee.toml b/lychee.toml
index 7297e890..6cc56dff 100644
--- a/lychee.toml
+++ b/lychee.toml
@@ -35,6 +35,14 @@ exclude = [
"^http://www\\.w3\\.org/2000/svg$",
# POST-only AI search endpoint referenced in vocs.config.ts.
"^https://tempo\\.xyz/developers/api/search$",
+ # MPP service roots expose paid API routes rather than browser landing pages.
+ "^https://[a-z0-9-]+\\.mpp\\.paywithlocus\\.com/?$",
+ # Verified app pages that reject automated link-check requests.
+ "^https://dashboard\\.pimlico\\.io/?$",
+ "^https://docs\\.elliptic\\.co/?$",
+ "^https://hub\\.conduit\\.xyz/tempo-testnet/?$",
+ # Temporary upstream error on the published TIP page.
+ "^https://tips\\.sh/1066/?$",
]
# Reuse cached results across runs.
diff --git a/scripts/filter-external-links.ts b/scripts/filter-external-links.ts
new file mode 100755
index 00000000..2ece6ac7
--- /dev/null
+++ b/scripts/filter-external-links.ts
@@ -0,0 +1,9 @@
+#!/usr/bin/env -S node --experimental-strip-types
+
+import fs from 'node:fs'
+import { externalLinkSource } from '../src/lib/external-link-source.ts'
+
+const file = process.argv[2]
+if (!file) throw new Error('Expected a source file path')
+
+process.stdout.write(externalLinkSource(fs.readFileSync(file, 'utf8')))
diff --git a/src/lib/external-link-source.test.ts b/src/lib/external-link-source.test.ts
new file mode 100644
index 00000000..2853474d
--- /dev/null
+++ b/src/lib/external-link-source.test.ts
@@ -0,0 +1,33 @@
+import { describe, expect, it } from 'vitest'
+import { externalLinkSource } from './external-link-source'
+
+describe('external link source', () => {
+ it.each([
+ ['root-relative Markdown', '[Docs](/docs/guide#section)', '[Docs](#)'],
+ ['relative Markdown', '[Sibling](../sibling)', '[Sibling](#)'],
+ ['root-relative image', '', ''],
+ ['reference definition', '[docs]: /docs/guide', '[docs]: #'],
+ ['JSX href', 'Docs', 'Docs'],
+ ['JSX to', '', ''],
+ ['JSX src', "
", "
"],
+ ['local autolink', '', '<#>'],
+ ])('masks %s destinations', (_label, source, expected) => {
+ expect(externalLinkSource(source)).toBe(expected)
+ })
+
+ it('preserves external URLs and line numbers', () => {
+ const source = [
+ '[Docs](/docs/guide)',
+ '[GitHub](https://github.com/tempoxyz/docs)',
+ 'Tempo',
+ 'Bare URL: https://example.net/path',
+ ].join('\n')
+
+ const filtered = externalLinkSource(source)
+
+ expect(filtered).toContain('[GitHub](https://github.com/tempoxyz/docs)')
+ expect(filtered).toContain('Tempo')
+ expect(filtered).toContain('Bare URL: https://example.net/path')
+ expect(filtered.split('\n')).toHaveLength(source.split('\n').length)
+ })
+})
diff --git a/src/lib/external-link-source.ts b/src/lib/external-link-source.ts
new file mode 100644
index 00000000..5a5a9da2
--- /dev/null
+++ b/src/lib/external-link-source.ts
@@ -0,0 +1,12 @@
+const markdownDestination = /(\]\()(?:\/|#|\.\.?\/)[^\s)>]+>?/g
+const markdownDefinition = /(^\s*\[[^\]]+\]:\s*)(?:\/|#|\.\.?\/)[^\s>]+>?/gm
+const jsxAttribute = /(\b(?:href|src|to)\s*=\s*["'])(?:\/|#|\.\.?\/)[^"']*(["'])/g
+const autolink = /<(?:\/(?:docs|developers)(?:\/[^>\s]*)?|#[^>\s]*|\.\.?\/[^>\s]*)>/g
+
+export function externalLinkSource(source: string): string {
+ return source
+ .replace(markdownDestination, '$1#')
+ .replace(markdownDefinition, '$1#')
+ .replace(jsxAttribute, '$1#$2')
+ .replace(autolink, '<#>')
+}
diff --git a/src/pages/docs/ecosystem/node-infrastructure.mdx b/src/pages/docs/ecosystem/node-infrastructure.mdx
index 9be2de91..55d4e967 100644
--- a/src/pages/docs/ecosystem/node-infrastructure.mdx
+++ b/src/pages/docs/ecosystem/node-infrastructure.mdx
@@ -36,7 +36,7 @@ Access [Conduit](https://hub.conduit.xyz/tempo-testnet) or read the [Tempo RPC Q
[dRPC](https://drpc.org) provides managed Tempo RPC endpoints through NodeCloud, with smart routing, analytics, key control, and front-end protection across 180+ networks. The platform runs on 40 providers in 8 geoclusters, with a free tier and flat-rate plans starting at $10.
-Get started by visiting the [Tempo chain page](https://drpc.org/chainlist/tempo-testnet-rpc), and learn more about NodeCloud on the [dRPC NodeCloud page](https://drpc.org/nodecloud-multichain-rpc-management).
+Get started by visiting the [dRPC chain list](https://drpc.org/chainlist), and learn more about NodeCloud on the [dRPC NodeCloud page](https://drpc.org/nodecloud-multichain-rpc-management).
## Luganodes
@@ -50,9 +50,9 @@ Get started on the [Tempo Chain Page](https://www.quicknode.com/chains/tempo) an
## SenseiNode
-[SenseiNode](https://senseinode.com) operates institutional-grade RPC and validator infrastructure across 35+ protocols, backed by SOC 2 Type II and ISO 27001:2022 certifications. SenseiNode does not ship commodity endpoints. Their engineering team sizes, tunes, and operates every deployment around your specific workload, available out of the box.
+[SenseiNode](https://www.senseinode.com/) operates institutional-grade RPC and validator infrastructure across 35+ protocols, backed by SOC 2 Type II and ISO 27001:2022 certifications. SenseiNode does not ship commodity endpoints. Their engineering team sizes, tunes, and operates every deployment around your specific workload, available out of the box.
-Reach out to the concierge infrastructure team at [info@senseinode.com](mailto:info@senseinode.com) or visit [senseinode.com](https://senseinode.com) to scope your Tempo RPC requirements and receive a tailored proposal within 24 hours.
+Reach out to the concierge infrastructure team at [info@senseinode.com](mailto:info@senseinode.com) or visit [senseinode.com](https://www.senseinode.com/) to scope your Tempo RPC requirements and receive a tailored proposal within 24 hours.
## Validation Cloud
diff --git a/src/pages/docs/ecosystem/wallets.mdx b/src/pages/docs/ecosystem/wallets.mdx
index 20f3052d..9728238c 100644
--- a/src/pages/docs/ecosystem/wallets.mdx
+++ b/src/pages/docs/ecosystem/wallets.mdx
@@ -44,7 +44,7 @@ Privy takes advantage of Tempo-native experiences to enable better stablecoin an
You can get started now. Simply [create](https://docs.privy.io/wallets/wallets/create/create-a-wallet#param-chain-type-1) an ethereum wallet with Privy and pass in `"caip2": "eip155:4217"` when [making transactions](https://docs.privy.io/wallets/using-wallets/ethereum/send-a-transaction#usage-9).
:::tip
-Check out the [Tempo + Privy recipe](https://docs.privy.io/recipes/evm/tempo) and Privy's [example](https://github.com/privy-io/examples/tree/main/examples/privy-next-tempo) peer-to-peer payments app that uses Tempo transaction memos.
+Check out the [Tempo + Privy guide](https://docs.privy.io/wallets/using-wallets/tempo/send-a-transaction) and Privy's [example](https://github.com/privy-io/examples/tree/main/examples/privy-next-tempo) peer-to-peer payments app that uses Tempo transaction memos.
:::
### Turnkey
@@ -56,7 +56,7 @@ Turnkey also supports sponsor-style workflows, enabling gasless or subsidized tr
[Create your Turnkey account](https://app.turnkey.com/dashboard) and follow the [Turnkey Embedded Wallet Kit guide](https://docs.turnkey.com/sdks/react/getting-started) to integrate embedded wallets into your Tempo app.
:::tip
-Turnkey has a [`with-tempo`](https://github.com/tkhq/sdk/tree/main/examples/with-tempo) example in their SDK to get you started quickly.
+Turnkey has a [`with-tempo`](https://github.com/tkhq/sdk/tree/main/examples/chain-integrations/with-tempo) example in their SDK to get you started quickly.
:::
## Custodial & Institutional
diff --git a/src/pages/docs/protocol/tips/tip-1005.mdx b/src/pages/docs/protocol/tips/tip-1005.mdx
index a496012d..8d16eb78 100644
--- a/src/pages/docs/protocol/tips/tip-1005.mdx
+++ b/src/pages/docs/protocol/tips/tip-1005.mdx
@@ -54,9 +54,9 @@ The fix requires:
### Reference implementation changes
-The fix requires changes to two functions in [`docs/specs/src/StablecoinDEX.sol`](https://github.com/tempoxyz/tempo/blob/main/docs/specs/src/StablecoinDEX.sol):
+The fix requires changes to two functions in [`docs/specs/src/StablecoinDEX.sol`](https://github.com/tempoxyz/tempo/blob/3994f7c231f960cb481253888f295a50502d432c/docs/specs/src/StablecoinDEX.sol):
-#### 1. `_fillOrder` ([line 551-556](https://github.com/tempoxyz/tempo/blob/main/docs/specs/src/StablecoinDEX.sol#L551-L556))
+#### 1. `_fillOrder` ([line 551-556](https://github.com/tempoxyz/tempo/blob/3994f7c231f960cb481253888f295a50502d432c/docs/specs/src/StablecoinDEX.sol#L551-L556))
Add an optional `quoteOverride` parameter. When non-zero and the order is an ask, use `quoteOverride` directly for the maker's balance increment instead of computing `ceil(fillAmount * price)`.
@@ -73,7 +73,7 @@ uint128 quoteAmount = quoteOverride > 0
balances[order.maker][book.quote] += quoteAmount;
```
-#### 2. `_fillOrdersExactIn` ([line 923-926](https://github.com/tempoxyz/tempo/blob/main/docs/specs/src/StablecoinDEX.sol#L923-L926))
+#### 2. `_fillOrdersExactIn` ([line 923-926](https://github.com/tempoxyz/tempo/blob/3994f7c231f960cb481253888f295a50502d432c/docs/specs/src/StablecoinDEX.sol#L923-L926))
In the partial fill branch for asks, pass `remainingIn` as the quote override:
diff --git a/src/pages/docs/protocol/tips/tip-1011.mdx b/src/pages/docs/protocol/tips/tip-1011.mdx
index c4f88965..155611cf 100644
--- a/src/pages/docs/protocol/tips/tip-1011.mdx
+++ b/src/pages/docs/protocol/tips/tip-1011.mdx
@@ -623,6 +623,6 @@ Pre-fork blocks MUST replay with pre-fork semantics to preserve state roots.
- [AccountKeychain docs](https://tempo.xyz/developers/docs/protocol/transactions/AccountKeychain)
- [Tempo Transactions](https://tempo.xyz/developers/docs/guide/tempo-transaction)
-- [IAccountKeychain.sol](https://github.com/tempoxyz/tempo/blob/main/tips/verify/src/interfaces/IAccountKeychain.sol)
+- [IAccountKeychain.sol](https://github.com/tempoxyz/tempo-std/blob/master/src/interfaces/IAccountKeychain.sol)
- [GitHub Issue #1865](https://github.com/tempoxyz/tempo/issues/1865) - Periodic spending limits
- [GitHub Issue #1491](https://github.com/tempoxyz/tempo/issues/1491) - Destination address scoping
diff --git a/src/pages/docs/quickstart/developer-tools.mdx b/src/pages/docs/quickstart/developer-tools.mdx
index ce4de6da..f15c98e9 100644
--- a/src/pages/docs/quickstart/developer-tools.mdx
+++ b/src/pages/docs/quickstart/developer-tools.mdx
@@ -238,7 +238,7 @@ Privy takes advantage of Tempo-native experiences to enable better stablecoin an
You can get started now. Simply [create](https://docs.privy.io/wallets/wallets/create/create-a-wallet#param-chain-type-1) an ethereum wallet with Privy and pass in `"caip2": "eip155:4217"` when [making transactions](https://docs.privy.io/wallets/using-wallets/ethereum/send-a-transaction#usage-9).
:::tip
-Check out the [Tempo + Privy recipe](https://docs.privy.io/recipes/evm/tempo) and Privy's [example](https://github.com/privy-io/examples/tree/main/examples/privy-next-tempo) peer-to-peer payments app that uses Tempo transaction memos.
+Check out the [Tempo + Privy guide](https://docs.privy.io/wallets/using-wallets/tempo/send-a-transaction) and Privy's [example](https://github.com/privy-io/examples/tree/main/examples/privy-next-tempo) peer-to-peer payments app that uses Tempo transaction memos.
:::
### Turnkey
@@ -250,7 +250,7 @@ Turnkey also supports sponsor-style workflows, enabling gasless or subsidized tr
[Create your Turnkey account](https://app.turnkey.com/dashboard) and follow the [Turnkey Embedded Wallet Kit guide](https://docs.turnkey.com/sdks/react/getting-started) to integrate embedded wallets into your Tempo app.
:::tip
-Turnkey has a [`with-tempo`](https://github.com/tkhq/sdk/tree/main/examples/with-tempo) example in their SDK to get you started quickly.
+Turnkey has a [`with-tempo`](https://github.com/tkhq/sdk/tree/main/examples/chain-integrations/with-tempo) example in their SDK to get you started quickly.
:::
### Custodial & Institutional
@@ -321,7 +321,7 @@ View the Tempo Testnet RPC endpoint in the [Conduit Hub](https://hub.conduit.xyz
[dRPC](https://drpc.org) provides managed Tempo RPC endpoints through NodeCloud, with smart routing, analytics, key control, and front-end protection across 180+ networks. The platform runs on 40 providers in 8 geoclusters, with a free tier and flat-rate plans starting at $10.
-Get started by visiting the [Tempo chain page](https://drpc.org/chainlist/tempo-testnet-rpc), and learn more about NodeCloud on the [dRPC NodeCloud page](https://drpc.org/nodecloud-multichain-rpc-management).
+Get started by visiting the [dRPC chain list](https://drpc.org/chainlist), and learn more about NodeCloud on the [dRPC NodeCloud page](https://drpc.org/nodecloud-multichain-rpc-management).
### Quicknode
diff --git a/src/pages/docs/quickstart/wallet-developers.mdx b/src/pages/docs/quickstart/wallet-developers.mdx
index 9835cd26..cdf5646f 100644
--- a/src/pages/docs/quickstart/wallet-developers.mdx
+++ b/src/pages/docs/quickstart/wallet-developers.mdx
@@ -138,7 +138,7 @@ See [Fee Token Preferences](/docs/protocol/fees/spec-fee#fee-token-preferences)
Tempo provides a public tokenlist service that hosts token and network assets. You can pull these assets from our public tokenlist service to display in your UI.
- **GitHub**: [tempoxyz/tempo-apps/apps/tokenlist](https://github.com/tempoxyz/tempo-apps/tree/main/apps/tokenlist)
-- **Tokenlist JSON**: [tempoxyz.github.io/tempo-apps/42431/tokenlist.json](https://tempoxyz.github.io/tempo-apps/42431/tokenlist.json)
+- **Tokenlist JSON**: [tokenlist.tempo.xyz/list/42431](https://tokenlist.tempo.xyz/list/42431)
::::