From 3101daed77a205234630bf79041473a96699eaf9 Mon Sep 17 00:00:00 2001 From: eric-brown Date: Wed, 18 Jun 2025 16:26:54 -0400 Subject: [PATCH 1/4] resolve merge conflicts --- docs/docs.json | 154 ++++++++++++++--------------- test-all-redirects.js | 197 +++++++++++++++++++++++++++++++++++++ test-critical-redirects.js | 141 ++++++++++++++++++++++++++ test-final-redirects.js | 76 ++++++++++++++ test-redirect-loops.js | 170 ++++++++++++++++++++++++++++++++ test-redirects.js | 161 ++++++++++++++++++++++++++++++ test-specific-redirects.js | 116 ++++++++++++++++++++++ 7 files changed, 936 insertions(+), 79 deletions(-) create mode 100644 test-all-redirects.js create mode 100644 test-critical-redirects.js create mode 100644 test-final-redirects.js create mode 100644 test-redirect-loops.js create mode 100755 test-redirects.js create mode 100644 test-specific-redirects.js diff --git a/docs/docs.json b/docs/docs.json index dd5357d4..bdde0ff0 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -1141,8 +1141,8 @@ "destination": "/wallet-app/guides/thinking-social" }, { - "source": "/builderkits/onchainkit/:path*", - "destination": "/onchainkit/:path" + "source": "/builderkits/onchainkit/:slug*", + "destination": "/onchainkit/:slug*" }, { "source": "/builderkits/onchainkit/appchain/bridge", @@ -1521,15 +1521,15 @@ "destination": "/cookbook/defi-your-app" }, { - "source": "/cookbook/smart-contract-development/foundry/:path*", - "destination": "/learn/foundry/:path" + "source": "/cookbook/smart-contract-development/foundry/:slug*", + "destination": "/learn/foundry/:slug*" }, { - "source": "/cookbook/smart-contract-development/hardhat/:path*", - "destination": "/learn/hardhat/hardhat-tools-and-testing/:path" + "source": "/cookbook/smart-contract-development/hardhat/:slug*", + "destination": "/learn/hardhat/hardhat-tools-and-testing/:slug*" }, { - "source": "/cookbook/smart-contract-development/remix/:path*", + "source": "/cookbook/smart-contract-development/remix/:slug*", "destination": "/learn/introduction-to-solidity/deployment-in-remix" }, { @@ -1637,20 +1637,20 @@ "destination": "/base-chain/node-operators/run-a-base-node" }, { - "source": "/identity/basenames/:path*", + "source": "/identity/basenames/:slug*", "destination": "/onchainkit/guides/use-basename-in-onchain-app" }, { "source": "/identity/smart-wallet", - "destination": "/identity/smart-wallet/quickstart" + "destination": "/smart-wallet/quickstart" }, { - "source": "/identity/smart-wallet/:path*", - "destination": "/smart-wallet/:path" + "source": "/identity/smart-wallet/:slug*", + "destination": "/smart-wallet/:slug*" }, { "source": "/identity/smart-wallet/checklist", - "destination": "/identity/smart-wallet/quickstart" + "destination": "/smart-wallet/quickstart" }, { "source": "/identity/smart-wallet/concepts/features/optional/spend-limits", @@ -1662,7 +1662,7 @@ }, { "source": "/identity/smart-wallet/concepts/usage-details/self-calls", - "destination": "/identity/smart-wallet/concepts/usage-details/unsupported-calls" + "destination": "/smart-wallet/concepts/usage-details/unsupported-calls" }, { "source": "/identity/smart-wallet/concepts/usage-details/simulations", @@ -1670,43 +1670,43 @@ }, { "source": "/identity/smart-wallet/FAQ", - "destination": "/identity/smart-wallet/quickstart" + "destination": "/smart-wallet/quickstart" }, { - "source": "/identity/smart-wallet/faq/:path*", - "destination": "/identity/smart-wallet/quickstart" + "source": "/identity/smart-wallet/faq/:slug*", + "destination": "/smart-wallet/quickstart" }, { "source": "/identity/smart-wallet/features/batch-operations", - "destination": "/identity/smart-wallet/concepts/features/optional/batch-operations" + "destination": "/smart-wallet/concepts/features/optional/batch-operations" }, { "source": "/identity/smart-wallet/features/custom-gas-tokens", - "destination": "/identity/smart-wallet/concepts/features/optional/custom-gas-tokens" + "destination": "/smart-wallet/concepts/features/optional/custom-gas-tokens" }, { "source": "/identity/smart-wallet/features/gas-free-transactions", - "destination": "/identity/smart-wallet/concepts/features/optional/gas-free-transactions" + "destination": "/smart-wallet/concepts/features/optional/gas-free-transactions" }, { "source": "/identity/smart-wallet/features/MagicSpend", - "destination": "/identity/smart-wallet/concepts/features/built-in/MagicSpend" + "destination": "/smart-wallet/concepts/features/built-in/MagicSpend" }, { "source": "/identity/smart-wallet/features/networks", - "destination": "/identity/smart-wallet/concepts/features/built-in/networks" + "destination": "/smart-wallet/concepts/features/built-in/networks" }, { "source": "/identity/smart-wallet/features/passkeys", - "destination": "/identity/smart-wallet/concepts/features/built-in/passkeys" + "destination": "/smart-wallet/concepts/features/built-in/passkeys" }, { "source": "/identity/smart-wallet/features/recovery-keys", - "destination": "/identity/smart-wallet/concepts/features/built-in/recovery-keys" + "destination": "/smart-wallet/concepts/features/built-in/recovery-keys" }, { "source": "/identity/smart-wallet/features/single-sign-on", - "destination": "/identity/smart-wallet/concepts/features/built-in/single-sign-on" + "destination": "/smart-wallet/concepts/features/built-in/single-sign-on" }, { "source": "/identity/smart-wallet/features/spend-permissions", @@ -1714,15 +1714,15 @@ }, { "source": "/identity/smart-wallet/features/sub-accounts", - "destination": "/identity/smart-wallet/concepts/features/optional/sub-accounts" + "destination": "/smart-wallet/concepts/features/optional/sub-accounts" }, { - "source": "/identity/smart-wallet/guides/create-app/:path*", - "destination": "/identity/smart-wallet/quickstart" + "source": "/identity/smart-wallet/guides/create-app/:slug*", + "destination": "/smart-wallet/quickstart" }, { "source": "/identity/smart-wallet/guides/react-native-integration", - "destination": "/identity/smart-wallet/quickstart/react-native-project" + "destination": "/smart-wallet/quickstart/react-native-project" }, { "source": "/identity/smart-wallet/guides/spend-limits", @@ -1733,12 +1733,12 @@ "destination": "/smart-wallet/guides/spend-permissions" }, { - "source": "/identity/smart-wallet/guides/spend-permissions/:path*", - "destination": "/identity/smart-wallet/guides/spend-limits/" + "source": "/identity/smart-wallet/guides/spend-permissions/:slug*", + "destination": "/smart-wallet/guides/spend-limits/" }, { - "source": "/identity/smart-wallet/guides/sub-accounts/:path*", - "destination": "/identity/smart-wallet/guides/sub-accounts/" + "source": "/identity/smart-wallet/guides/sub-accounts/:slug*", + "destination": "/smart-wallet/guides/sub-accounts/" }, { "source": "/identity/smart-wallet/guides/sub-accounts/add-sub-accounts-to-onchainkit-minikit", @@ -1746,59 +1746,59 @@ }, { "source": "/identity/smart-wallet/guides/sub-accounts/creating-sub-accounts", - "destination": "/identity/smart-wallet/guides/sub-accounts/" + "destination": "/smart-wallet/guides/sub-accounts/" }, { "source": "/identity/smart-wallet/guides/sub-accounts/incorporate-spend-permissions", - "destination": "/identity/smart-wallet/guides/sub-accounts/" + "destination": "/smart-wallet/guides/sub-accounts/" }, { "source": "/identity/smart-wallet/guides/sub-accounts/sub-accounts-with-privy", "destination": "/smart-wallet/guides/sub-accounts/sub-accounts-with-privy" }, { - "source": "/identity/smart-wallet/guides/tips/:path*", - "destination": "/identity/smart-wallet/quickstart" + "source": "/identity/smart-wallet/guides/tips/:slug*", + "destination": "/smart-wallet/quickstart" }, { "source": "/identity/smart-wallet/guides/update-existing-app", - "destination": "/identity/smart-wallet/quickstart" + "destination": "/smart-wallet/quickstart" }, { "source": "/identity/smart-wallet/index", - "destination": "/identity/smart-wallet/quickstart" + "destination": "/smart-wallet/quickstart" }, { - "source": "/identity/smart-wallet/introduction/:path*", - "destination": "/identity/smart-wallet/quickstart" + "source": "/identity/smart-wallet/introduction/:slug*", + "destination": "/smart-wallet/quickstart" }, { "source": "/identity/smart-wallet/introduction/base-gasless-campaign", - "destination": "/identity/smart-wallet/concepts/base-gasless-campaign" + "destination": "/smart-wallet/concepts/base-gasless-campaign" }, { "source": "/identity/smart-wallet/quick-start", - "destination": "/identity/smart-wallet/quickstart" + "destination": "/smart-wallet/quickstart" }, { - "source": "/identity/smart-wallet/sdk/:path*", - "destination": "/identity/smart-wallet/technical-reference/sdk/" + "source": "/identity/smart-wallet/sdk/:slug*", + "destination": "/smart-wallet/technical-reference/sdk/" }, { "source": "/identity/smart-wallet/technical-reference/sdk/sub-account-reference", - "destination": "/identity/smart-wallet/technical-reference/sub-account-reference" + "destination": "/smart-wallet/technical-reference/sub-account-reference" }, { - "source": "/identity/smart-wallet/usage-details/:path*", - "destination": "/identity/smart-wallet/concepts/usage-details/:path" + "source": "/identity/smart-wallet/usage-details/:slug*", + "destination": "/smart-wallet/concepts/usage-details/:slug*" }, { "source": "/identity/smart-wallet/wallet-library-support", - "destination": "/identity/smart-wallet/concepts/usage-details/wallet-library-support" + "destination": "/smart-wallet/concepts/usage-details/wallet-library-support" }, { "source": "/identity/smart-wallet/why", - "destination": "/identity/smart-wallet/concepts/what-is-smart-wallet" + "destination": "/smart-wallet/concepts/what-is-smart-wallet" }, { "source": "/learn/account-abstraction", @@ -1821,12 +1821,12 @@ "destination": "/learn/welcome" }, { - "source": "/learn/erc-20-token/:path*", - "destination": "/learn/token-development/erc-20-token/:path" + "source": "/learn/erc-20-token/:slug*", + "destination": "/learn/token-development/erc-20-token/:slug*" }, { - "source": "/learn/erc-721-token/:path*", - "destination": "/learn/token-development/erc-721-token/:path" + "source": "/learn/erc-721-token/:slug*", + "destination": "/learn/token-development/erc-721-token/:slug*" }, { "source": "/learn/ethereum-applications", @@ -1837,16 +1837,16 @@ "destination": "/learn/introduction-to-ethereum/ethereum-dev-overview-vid" }, { - "source": "/learn/etherscan/:path*", - "destination": "/learn/hardhat/etherscan/:path" + "source": "/learn/etherscan/:slug*", + "destination": "/learn/hardhat/etherscan/:slug*" }, { "source": "/learn/evm-diagram", "destination": "/learn/introduction-to-ethereum/evm-diagram" }, { - "source": "/learn/frontend-setup/:path*", - "destination": "/learn/onchain-app-development/frontend-setup/:path" + "source": "/learn/frontend-setup/:slug*", + "destination": "/learn/onchain-app-development/frontend-setup/:slug*" }, { "source": "/learn/gas-use-in-eth-transactions", @@ -1857,40 +1857,36 @@ "destination": "/learn/introduction-to-ethereum/guide-to-base" }, { - "source": "/learn/hardhat-deploy/:path*", - "destination": "/learn/hardhat/hardhat-deploy/:path" + "source": "/learn/hardhat-deploy/:slug*", + "destination": "/learn/hardhat/hardhat-deploy/:slug*" }, { - "source": "/learn/hardhat-forking/:path*", - "destination": "/learn/hardhat/hardhat-forking/:path" + "source": "/learn/hardhat-forking/:slug*", + "destination": "/learn/hardhat/hardhat-forking/:slug*" }, { - "source": "/learn/hardhat-setup-overview/:path*", - "destination": "/learn/hardhat/hardhat-setup-overview/:path" + "source": "/learn/hardhat-setup-overview/:slug*", + "destination": "/learn/hardhat/hardhat-setup-overview/:slug*" }, { - "source": "/learn/hardhat-testing/:path*", - "destination": "/learn/hardhat/hardhat-testing/:path" + "source": "/learn/hardhat-testing/:slug*", + "destination": "/learn/hardhat/hardhat-testing/:slug*" }, { "source": "/learn/hardhat-tools-and-testing/overview", "destination": "/learn/hardhat/hardhat-tools-and-testing/overview" }, { - "source": "/learn/hardhat-verify/:path*", - "destination": "/learn/hardhat/hardhat-verify/:path" - }, - { - "source": "/learn/hardhat/:path*", - "destination": "/learn/hardhat/hardhat-tools-and-testing/:path" + "source": "/learn/hardhat-verify/:slug*", + "destination": "/learn/hardhat/hardhat-verify/:slug*" }, { "source": "/learn/help-on-discord", "destination": "/learn/welcome" }, { - "source": "/learn/intro-to-tokens/:path*", - "destination": "/learn/token-development/intro-to-tokens/:path" + "source": "/learn/intro-to-tokens/:slug*", + "destination": "/learn/token-development/intro-to-tokens/:slug*" }, { "source": "/learn/introduction-to-ethereum", @@ -1901,16 +1897,16 @@ "destination": "/learn/welcome" }, { - "source": "/learn/minimal-tokens/:path*", - "destination": "/learn/token-development/minimal-tokens/:path" + "source": "/learn/minimal-tokens/:slug*", + "destination": "/learn/token-development/minimal-tokens/:slug*" }, { - "source": "/learn/reading-and-displaying-data/:path*", - "destination": "/learn/onchain-app-development/reading-and-displaying-data/:path" + "source": "/learn/reading-and-displaying-data/:slug*", + "destination": "/learn/onchain-app-development/reading-and-displaying-data/:slug*" }, { - "source": "/learn/writing-to-contracts/:path*", - "destination": "/learn/onchain-app-development/writing-to-contracts/:path" + "source": "/learn/writing-to-contracts/:slug*", + "destination": "/learn/onchain-app-development/writing-to-contracts/:slug*" }, { "source": "/base-learn/progress", diff --git a/test-all-redirects.js b/test-all-redirects.js new file mode 100644 index 00000000..e18f383a --- /dev/null +++ b/test-all-redirects.js @@ -0,0 +1,197 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +// Load the docs.json file +const docsPath = path.join(__dirname, 'docs', 'docs.json'); +const docsConfig = JSON.parse(fs.readFileSync(docsPath, 'utf8')); + +console.log('=== Testing ALL Redirects ===\n'); +console.log('This test verifies that all redirect destinations are reachable.\n'); + +// Extract all redirects +const redirects = docsConfig.redirects || []; +console.log(`Found ${redirects.length} redirects to test.\n`); + +// Group redirects by type +const slugRedirects = redirects.filter(r => r.source.includes(':slug*')); +const staticRedirects = redirects.filter(r => !r.source.includes(':slug*')); + +console.log(`- Static redirects: ${staticRedirects.length}`); +console.log(`- Slug redirects: ${slugRedirects.length}\n`); + +async function checkDestination(url) { + try { + const response = await fetch(`http://localhost:3000${url}`, { + redirect: 'manual', + headers: { 'User-Agent': 'Mozilla/5.0' } + }); + + // If we get a 200, the page exists + if (response.status === 200) { + return { exists: true, status: 200 }; + } + + // If we get a redirect, check where it goes + if ([301, 302, 307, 308].includes(response.status)) { + const location = response.headers.get('location'); + // If it redirects to /get-started/base, the page doesn't exist + if (location === '/get-started/base' || location === 'http://localhost:3000/get-started/base') { + return { exists: false, status: response.status, redirectsTo: '/get-started/base' }; + } + return { exists: true, status: response.status, redirectsTo: location }; + } + + return { exists: false, status: response.status }; + } catch (error) { + return { exists: false, error: error.message }; + } +} + +async function testRedirect(redirect) { + // For slug redirects, we'll test with a sample path + let testSource = redirect.source; + let expectedDest = redirect.destination; + + if (redirect.source.includes(':slug*')) { + // Replace :slug* with a test path + testSource = redirect.source.replace(':slug*', 'test-page'); + if (redirect.destination.includes(':slug*')) { + expectedDest = redirect.destination.replace(':slug*', 'test-page'); + } + } + + // Test the redirect + try { + const response = await fetch(`http://localhost:3000${testSource}`, { + redirect: 'manual', + headers: { 'User-Agent': 'Mozilla/5.0' } + }); + + const location = response.headers.get('location'); + const actualDest = location?.replace('http://localhost:3000', '') || null; + + // Check if the destination exists + const destCheck = await checkDestination(expectedDest); + + return { + source: redirect.source, + destination: redirect.destination, + testSource, + expectedDest, + actualDest, + redirectWorks: actualDest === expectedDest, + destinationExists: destCheck.exists, + destStatus: destCheck.status, + destRedirectsTo: destCheck.redirectsTo + }; + } catch (error) { + return { + source: redirect.source, + destination: redirect.destination, + error: error.message + }; + } +} + +async function runTests() { + const results = { + working: [], + brokenRedirect: [], + brokenDestination: [], + errors: [] + }; + + console.log('Testing redirects...\n'); + + // Test in batches to avoid overwhelming the server + const batchSize = 10; + for (let i = 0; i < redirects.length; i += batchSize) { + const batch = redirects.slice(i, i + batchSize); + const batchResults = await Promise.all(batch.map(testRedirect)); + + for (const result of batchResults) { + if (result.error) { + results.errors.push(result); + } else if (!result.redirectWorks) { + results.brokenRedirect.push(result); + } else if (!result.destinationExists) { + results.brokenDestination.push(result); + } else { + results.working.push(result); + } + } + + // Progress indicator + process.stdout.write(`\rProgress: ${Math.min(i + batchSize, redirects.length)}/${redirects.length}`); + + // Small delay between batches + if (i + batchSize < redirects.length) { + await new Promise(resolve => setTimeout(resolve, 200)); + } + } + + console.log('\n\n=== Results ===\n'); + + console.log(`βœ… Working redirects: ${results.working.length}`); + console.log(`❌ Broken redirects (wrong destination): ${results.brokenRedirect.length}`); + console.log(`⚠️ Redirects to non-existent pages: ${results.brokenDestination.length}`); + console.log(`πŸ’₯ Errors: ${results.errors.length}`); + + if (results.brokenRedirect.length > 0) { + console.log('\n❌ Broken Redirects (redirect goes to wrong place):'); + for (const r of results.brokenRedirect) { + console.log(` ${r.source}`); + console.log(` Expected: ${r.destination}`); + console.log(` Actual: ${r.actualDest || 'No redirect'}`); + } + } + + if (results.brokenDestination.length > 0) { + console.log('\n⚠️ Redirects to Non-Existent Pages:'); + for (const r of results.brokenDestination) { + console.log(` ${r.source} -> ${r.destination}`); + if (r.destRedirectsTo) { + console.log(` (destination redirects to ${r.destRedirectsTo})`); + } + } + } + + if (results.errors.length > 0) { + console.log('\nπŸ’₯ Errors:'); + for (const r of results.errors) { + console.log(` ${r.source}: ${r.error}`); + } + } + + // Test some specific smart wallet redirects + console.log('\n=== Testing Specific Smart Wallet Redirects ==='); + const smartWalletTests = [ + '/identity/smart-wallet/concepts/usage-details/self-calls', + '/identity/smart-wallet/faq/something', + '/identity/smart-wallet/wallet-library-support', + '/identity/smart-wallet/why' + ]; + + for (const url of smartWalletTests) { + const response = await fetch(`http://localhost:3000${url}`, { + redirect: 'manual', + headers: { 'User-Agent': 'Mozilla/5.0' } + }); + + const location = response.headers.get('location'); + const destCheck = location ? await checkDestination(location) : null; + + console.log(`\nTest: ${url}`); + console.log(` Redirects to: ${location || 'No redirect'}`); + if (destCheck) { + console.log(` Destination exists: ${destCheck.exists ? 'βœ…' : '❌'}`); + if (!destCheck.exists && destCheck.redirectsTo) { + console.log(` Falls back to: ${destCheck.redirectsTo}`); + } + } + } +} + +runTests().catch(console.error); \ No newline at end of file diff --git a/test-critical-redirects.js b/test-critical-redirects.js new file mode 100644 index 00000000..6c57cd65 --- /dev/null +++ b/test-critical-redirects.js @@ -0,0 +1,141 @@ +#!/usr/bin/env node + +console.log('=== Testing Critical Redirects ===\n'); +console.log('This test focuses on the most important redirects that users rely on.\n'); + +const criticalRedirects = [ + // Base chain redirects + { from: '/chain/base-contracts', to: '/base-chain/network-information/base-contracts', type: 'chain' }, + { from: '/chain/fees', to: '/base-chain/network-information/network-fees', type: 'chain' }, + { from: '/chain/network-information', to: '/base-chain/quickstart/connecting-to-base', type: 'chain' }, + { from: '/chain/using-base', to: '/base-chain/quickstart/connecting-to-base', type: 'chain' }, + + // Smart wallet redirects + { from: '/identity/smart-wallet', to: '/smart-wallet/quickstart', type: 'smart-wallet' }, + { from: '/identity/smart-wallet/quick-start', to: '/smart-wallet/quickstart', type: 'smart-wallet' }, + { from: '/identity/smart-wallet/concepts/features/optional/spend-permissions', to: '/smart-wallet/concepts/features/optional/spend-permissions', type: 'smart-wallet' }, + { from: '/identity/smart-wallet/features/passkeys', to: '/smart-wallet/concepts/features/built-in/passkeys', type: 'smart-wallet' }, + + // OnchainKit redirects + { from: '/builderkits/onchainkit/getting-started', to: '/onchainkit/getting-started', type: 'onchainkit' }, + { from: '/builderkits/onchainkit/guides/themes', to: '/onchainkit/guides/themes', type: 'onchainkit' }, + + // Learn/cookbook redirects + { from: '/cookbook/account-abstraction/gasless-transactions-with-paymaster', to: '/learn/onchain-app-development/account-abstraction/gasless-transactions-with-paymaster', type: 'learn' }, + { from: '/learn/hardhat-deploy/setup', to: '/learn/hardhat/hardhat-deploy/setup', type: 'learn' }, + { from: '/learn/erc-20-token/standard', to: '/learn/token-development/erc-20-token/standard', type: 'learn' }, + + // Wallet app redirects + { from: '/wallet-app/getting-started', to: '/wallet-app/introduction/getting-started', type: 'wallet-app' }, + { from: '/builderkits/minikit/quickstart', to: '/wallet-app/build-with-minikit/quickstart', type: 'wallet-app' }, +]; + +async function testRedirect(redirect) { + try { + // Test the redirect + const response = await fetch(`http://localhost:3000${redirect.from}`, { + redirect: 'manual', + headers: { 'User-Agent': 'Mozilla/5.0' } + }); + + const location = response.headers.get('location'); + const actualDest = location?.replace('http://localhost:3000', '') || null; + + // Check if redirect works + const redirectWorks = actualDest === redirect.to; + + // Test if destination exists + let destExists = false; + let fallbackTo = null; + + if (actualDest) { + const destResponse = await fetch(`http://localhost:3000${redirect.to}`, { + redirect: 'manual', + headers: { 'User-Agent': 'Mozilla/5.0' } + }); + + if (destResponse.status === 200) { + destExists = true; + } else if ([301, 302, 307, 308].includes(destResponse.status)) { + const destLocation = destResponse.headers.get('location'); + if (destLocation === '/get-started/base' || destLocation === 'http://localhost:3000/get-started/base') { + destExists = false; + fallbackTo = '/get-started/base'; + } else { + destExists = true; + } + } + } + + return { + ...redirect, + actualDest, + redirectWorks, + destExists, + fallbackTo, + status: response.status + }; + } catch (error) { + return { + ...redirect, + error: error.message + }; + } +} + +async function runTests() { + const results = { + chain: { working: 0, broken: 0 }, + 'smart-wallet': { working: 0, broken: 0 }, + onchainkit: { working: 0, broken: 0 }, + learn: { working: 0, broken: 0 }, + 'wallet-app': { working: 0, broken: 0 } + }; + + console.log('Testing redirects...\n'); + + for (const redirect of criticalRedirects) { + const result = await testRedirect(redirect); + + const isWorking = result.redirectWorks && result.destExists; + if (isWorking) { + results[result.type].working++; + console.log(`βœ… ${result.from}`); + } else { + results[result.type].broken++; + console.log(`❌ ${result.from}`); + if (!result.redirectWorks) { + console.log(` Redirect broken: expected ${result.to}, got ${result.actualDest || 'no redirect'}`); + } + if (!result.destExists) { + console.log(` Destination missing${result.fallbackTo ? ` (falls back to ${result.fallbackTo})` : ''}`); + } + if (result.error) { + console.log(` Error: ${result.error}`); + } + } + + await new Promise(resolve => setTimeout(resolve, 100)); + } + + console.log('\n=== Summary by Category ===\n'); + + for (const [category, stats] of Object.entries(results)) { + const total = stats.working + stats.broken; + console.log(`${category}:`); + console.log(` βœ… Working: ${stats.working}/${total}`); + if (stats.broken > 0) { + console.log(` ❌ Broken: ${stats.broken}/${total}`); + } + } + + const totalWorking = Object.values(results).reduce((sum, r) => sum + r.working, 0); + const totalBroken = Object.values(results).reduce((sum, r) => sum + r.broken, 0); + + console.log(`\n=== Overall ===`); + console.log(`Total redirects tested: ${criticalRedirects.length}`); + console.log(`βœ… Working: ${totalWorking} (${Math.round(totalWorking / criticalRedirects.length * 100)}%)`); + console.log(`❌ Broken: ${totalBroken} (${Math.round(totalBroken / criticalRedirects.length * 100)}%)`); +} + +runTests().catch(console.error); \ No newline at end of file diff --git a/test-final-redirects.js b/test-final-redirects.js new file mode 100644 index 00000000..351448fb --- /dev/null +++ b/test-final-redirects.js @@ -0,0 +1,76 @@ +#!/usr/bin/env node + +console.log('=== Final Redirect Test Summary ===\n'); + +const slugRedirects = [ + // Slug redirects that should work + { from: "/builderkits/onchainkit/any/path/here", to: "/onchainkit/any/path/here" }, + { from: "/cookbook/smart-contract-development/foundry/test", to: "/learn/foundry/test" }, + { from: "/cookbook/smart-contract-development/hardhat/guide", to: "/learn/hardhat/hardhat-tools-and-testing/guide" }, + { from: "/identity/smart-wallet/concepts/test", to: "/smart-wallet/concepts/test" }, + { from: "/learn/erc-20-token/guide", to: "/learn/token-development/erc-20-token/guide" }, + { from: "/learn/erc-721-token/guide", to: "/learn/token-development/erc-721-token/guide" }, + { from: "/learn/etherscan/tutorial", to: "/learn/hardhat/etherscan/tutorial" }, + { from: "/learn/frontend-setup/viem", to: "/learn/onchain-app-development/frontend-setup/viem" }, + { from: "/learn/hardhat-deploy/script", to: "/learn/hardhat/hardhat-deploy/script" }, + { from: "/learn/hardhat-forking/mainnet", to: "/learn/hardhat/hardhat-forking/mainnet" }, + { from: "/learn/hardhat-setup-overview/intro", to: "/learn/hardhat/hardhat-setup-overview/intro" }, + { from: "/learn/hardhat-testing/unit", to: "/learn/hardhat/hardhat-testing/unit" }, + { from: "/learn/hardhat-verify/basescan", to: "/learn/hardhat/hardhat-verify/basescan" }, + { from: "/learn/intro-to-tokens/vid", to: "/learn/token-development/intro-to-tokens/vid" }, + { from: "/learn/minimal-tokens/transfer", to: "/learn/token-development/minimal-tokens/transfer" }, + { from: "/learn/reading-and-displaying-data/hooks", to: "/learn/onchain-app-development/reading-and-displaying-data/hooks" }, + { from: "/learn/writing-to-contracts/wagmi", to: "/learn/onchain-app-development/writing-to-contracts/wagmi" }, + + // Static redirects (slug is ignored) + { from: "/cookbook/smart-contract-development/remix/anything", to: "/learn/introduction-to-solidity/deployment-in-remix" }, + { from: "/identity/basenames/whatever", to: "/onchainkit/guides/use-basename-in-onchain-app" }, +]; + +async function testRedirect(from, to) { + try { + const response = await fetch(`http://localhost:3000${from}`, { + redirect: 'manual', + headers: { 'User-Agent': 'Mozilla/5.0' } + }); + + const location = response.headers.get('location'); + const isCorrect = location === to || location === `http://localhost:3000${to}`; + + return { success: isCorrect, actual: location, status: response.status }; + } catch (error) { + return { success: false, error: error.message }; + } +} + +async function runTests() { + let passed = 0; + let failed = 0; + + for (const redirect of slugRedirects) { + const result = await testRedirect(redirect.from, redirect.to); + + if (result.success) { + console.log(`βœ… ${redirect.from}`); + passed++; + } else { + console.log(`❌ ${redirect.from}`); + console.log(` Expected: ${redirect.to}`); + console.log(` Got: ${result.actual || result.error}`); + failed++; + } + + await new Promise(resolve => setTimeout(resolve, 50)); + } + + console.log(`\n=== Results ===`); + console.log(`Total: ${slugRedirects.length}`); + console.log(`Passed: ${passed}`); + console.log(`Failed: ${failed}`); + + if (failed === 0) { + console.log('\nβœ… All slug redirects are working correctly!'); + } +} + +runTests().catch(console.error); \ No newline at end of file diff --git a/test-redirect-loops.js b/test-redirect-loops.js new file mode 100644 index 00000000..0e13ef01 --- /dev/null +++ b/test-redirect-loops.js @@ -0,0 +1,170 @@ +#!/usr/bin/env node + +// Test for potential redirect loops +const loopTests = [ + // These should NOT redirect (they're already at the destination) + { + test: "/learn/hardhat/hardhat-tools-and-testing/overview", + shouldRedirect: false, + description: "Already at hardhat-tools-and-testing destination" + }, + { + test: "/learn/hardhat/hardhat-deploy/example", + shouldRedirect: false, + description: "Already at hardhat-deploy destination" + }, + { + test: "/learn/hardhat/hardhat-forking/guide", + shouldRedirect: false, + description: "Already at hardhat-forking destination" + }, + { + test: "/learn/hardhat/hardhat-setup-overview/intro", + shouldRedirect: false, + description: "Already at hardhat-setup-overview destination" + }, + { + test: "/learn/hardhat/hardhat-testing/unit-tests", + shouldRedirect: false, + description: "Already at hardhat-testing destination" + }, + { + test: "/learn/hardhat/hardhat-verify/basescan", + shouldRedirect: false, + description: "Already at hardhat-verify destination" + }, + { + test: "/learn/hardhat/etherscan/verify", + shouldRedirect: false, + description: "Already at etherscan destination" + }, + + // These SHOULD redirect + { + test: "/learn/hardhat/random-page", + shouldRedirect: true, + expected: "/learn/hardhat/hardhat-tools-and-testing/random-page", + description: "Generic hardhat page should redirect" + }, + { + test: "/learn/hardhat-deploy/setup", + shouldRedirect: true, + expected: "/learn/hardhat/hardhat-deploy/setup", + description: "Old hardhat-deploy path" + }, + { + test: "/learn/hardhat-forking/mainnet", + shouldRedirect: true, + expected: "/learn/hardhat/hardhat-forking/mainnet", + description: "Old hardhat-forking path" + } +]; + +console.log('Testing for redirect loops...\n'); + +async function checkRedirect(url) { + try { + const response = await fetch(`http://localhost:3000${url}`, { + redirect: 'manual', + headers: { + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36' + } + }); + + const location = response.headers.get('location'); + const status = response.status; + + return { + redirected: status === 301 || status === 302 || status === 307 || status === 308, + location: location, + status: status + }; + } catch (error) { + return { + redirected: false, + error: error.message + }; + } +} + +async function detectLoop(url, maxDepth = 5) { + const visited = new Set(); + let currentUrl = url; + let depth = 0; + + while (depth < maxDepth) { + if (visited.has(currentUrl)) { + return { hasLoop: true, chain: Array.from(visited) }; + } + + visited.add(currentUrl); + const result = await checkRedirect(currentUrl); + + if (!result.redirected) { + return { hasLoop: false, chain: Array.from(visited), finalStatus: result.status }; + } + + if (result.location.startsWith('http://localhost:3000')) { + currentUrl = result.location.replace('http://localhost:3000', ''); + } else { + currentUrl = result.location; + } + depth++; + } + + return { hasLoop: true, chain: Array.from(visited), note: 'Max depth reached' }; +} + +async function runTests() { + let issues = 0; + + for (const test of loopTests) { + const result = await checkRedirect(test.test); + const loopCheck = await detectLoop(test.test); + + if (test.shouldRedirect) { + if (!result.redirected) { + console.log(`❌ ${test.description}`); + console.log(` URL: ${test.test}`); + console.log(` Expected redirect to: ${test.expected}`); + console.log(` But got status: ${result.status}\n`); + issues++; + } else if (result.location !== test.expected) { + console.log(`⚠️ ${test.description}`); + console.log(` URL: ${test.test}`); + console.log(` Expected: ${test.expected}`); + console.log(` Got: ${result.location}\n`); + } else { + console.log(`βœ… ${test.description}`); + console.log(` URL: ${test.test} -> ${result.location}\n`); + } + } else { + if (result.redirected) { + console.log(`❌ ${test.description}`); + console.log(` URL: ${test.test}`); + console.log(` Should NOT redirect, but redirects to: ${result.location}`); + if (loopCheck.hasLoop) { + console.log(` πŸ”„ REDIRECT LOOP DETECTED!`); + console.log(` Chain: ${loopCheck.chain.join(' -> ')}`); + } + console.log(''); + issues++; + } else { + console.log(`βœ… ${test.description}`); + console.log(` URL: ${test.test} (correctly not redirecting)\n`); + } + } + + await new Promise(resolve => setTimeout(resolve, 100)); + } + + console.log(`\n=== Summary ===`); + console.log(`Total issues found: ${issues}`); + + if (issues > 0) { + console.log('\n⚠️ The /learn/hardhat/:slug* redirect is too broad and catches URLs that are already in the correct subdirectories.'); + console.log('This causes redirect loops for pages already under /learn/hardhat/hardhat-*/'); + } +} + +runTests().catch(console.error); \ No newline at end of file diff --git a/test-redirects.js b/test-redirects.js new file mode 100755 index 00000000..845bd8ba --- /dev/null +++ b/test-redirects.js @@ -0,0 +1,161 @@ +#!/usr/bin/env node + +const redirects = [ + // Test slug redirects specifically + { + test: "/builderkits/onchainkit/some-page", + expected: "/onchainkit/some-page", + description: "OnchainKit slug redirect" + }, + { + test: "/builderkits/onchainkit/nested/path/here", + expected: "/onchainkit/nested/path/here", + description: "OnchainKit nested slug redirect" + }, + { + test: "/cookbook/smart-contract-development/foundry/deploy-guide", + expected: "/learn/foundry/deploy-guide", + description: "Foundry slug redirect" + }, + { + test: "/cookbook/smart-contract-development/hardhat/testing-guide", + expected: "/learn/hardhat/hardhat-tools-and-testing/testing-guide", + description: "Hardhat slug redirect" + }, + { + test: "/identity/basenames/setup-guide", + expected: "/onchainkit/guides/use-basename-in-onchain-app", + description: "Basenames slug redirect (should go to same page regardless of slug)" + }, + { + test: "/identity/smart-wallet/guides/example", + expected: "/smart-wallet/guides/example", + description: "Smart wallet slug redirect" + }, + { + test: "/learn/erc-20-token/implementation", + expected: "/learn/token-development/erc-20-token/implementation", + description: "ERC-20 token slug redirect" + }, + + // Test some specific redirects from the list + { + test: "/builderkits/onchainkit/getting-started", + expected: "/onchainkit/getting-started", + description: "OnchainKit getting started" + }, + { + test: "/chain/base-contracts", + expected: "/base-chain/network-information/base-contracts", + description: "Base contracts redirect" + }, + { + test: "/cookbook/account-abstraction/gasless-transactions-with-paymaster", + expected: "/learn/onchain-app-development/account-abstraction/gasless-transactions-with-paymaster", + description: "Account abstraction redirect" + }, + { + test: "/identity/smart-wallet", + expected: "/smart-wallet/quickstart", + description: "Smart wallet base redirect" + }, + { + test: "/learn/hardhat/hardhat-tools-and-testing/test-page", + expected: "/learn/hardhat/hardhat-tools-and-testing/test-page", + description: "Hardhat catch-all redirect" + }, + + // Test redirects that go to static pages (not slug-based) + { + test: "/cookbook/smart-contract-development/remix/any-page", + expected: "/learn/introduction-to-solidity/deployment-in-remix", + description: "Remix redirect (static destination)" + }, + { + test: "/identity/basenames/any-page-here", + expected: "/onchainkit/guides/use-basename-in-onchain-app", + description: "Basenames redirect (static destination)" + } +]; + +console.log('Testing redirects on http://localhost:3000...\n'); + +async function testRedirect(testCase) { + try { + const response = await fetch(`http://localhost:3000${testCase.test}`, { + redirect: 'manual', + headers: { + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36' + } + }); + + const location = response.headers.get('location'); + const status = response.status; + + // Check if it's a redirect + if (status === 301 || status === 302 || status === 307 || status === 308) { + // Parse the location to handle relative URLs + let finalLocation = location; + if (location && !location.startsWith('http')) { + finalLocation = location; + } + + const isExpected = finalLocation === testCase.expected || + finalLocation === `http://localhost:3000${testCase.expected}`; + + console.log(`${isExpected ? 'βœ…' : '❌'} ${testCase.description}`); + console.log(` Test URL: ${testCase.test}`); + console.log(` Expected: ${testCase.expected}`); + console.log(` Got: ${finalLocation || 'No redirect'}`); + console.log(` Status: ${status}\n`); + + return isExpected; + } else if (status === 200) { + // Check if the page loaded directly (might be the final destination) + console.log(`ℹ️ ${testCase.description}`); + console.log(` Test URL: ${testCase.test}`); + console.log(` Status: 200 OK (no redirect)\n`); + return false; + } else { + console.log(`❌ ${testCase.description}`); + console.log(` Test URL: ${testCase.test}`); + console.log(` Status: ${status} (unexpected)\n`); + return false; + } + } catch (error) { + console.log(`❌ ${testCase.description}`); + console.log(` Test URL: ${testCase.test}`); + console.log(` Error: ${error.message}\n`); + return false; + } +} + +async function runTests() { + let passed = 0; + let failed = 0; + + for (const testCase of redirects) { + const result = await testRedirect(testCase); + if (result) passed++; + else failed++; + + // Small delay to avoid overwhelming the server + await new Promise(resolve => setTimeout(resolve, 100)); + } + + console.log('\n=== Summary ==='); + console.log(`Total tests: ${redirects.length}`); + console.log(`Passed: ${passed}`); + console.log(`Failed: ${failed}`); + + // Test a URL that should fallback to /get-started/base + console.log('\n=== Testing fallback behavior ==='); + await testRedirect({ + test: "/non-existent-page-12345", + expected: "/get-started/base", + description: "Non-existent page (should NOT redirect to /get-started/base as a pass)" + }); +} + +// Run the tests +runTests().catch(console.error); \ No newline at end of file diff --git a/test-specific-redirects.js b/test-specific-redirects.js new file mode 100644 index 00000000..1d23fb56 --- /dev/null +++ b/test-specific-redirects.js @@ -0,0 +1,116 @@ +#!/usr/bin/env node + +// Test specific problematic redirects +const problematicTests = [ + { + test: "/learn/hardhat/test-page", + expected: "/learn/hardhat/hardhat-tools-and-testing/test-page", + description: "Hardhat base redirect" + }, + { + test: "/learn/hardhat-tools-and-testing/overview", + expected: "/learn/hardhat/hardhat-tools-and-testing/overview", + description: "Hardhat tools redirect (explicit)" + }, + { + test: "/learn/hardhat/another/nested/path", + expected: "/learn/hardhat/hardhat-tools-and-testing/another/nested/path", + description: "Hardhat nested path redirect" + } +]; + +console.log('Testing specific hardhat redirects...\n'); + +async function testRedirect(testCase) { + try { + const response = await fetch(`http://localhost:3000${testCase.test}`, { + redirect: 'manual', + headers: { + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36' + } + }); + + const location = response.headers.get('location'); + const status = response.status; + + if (status === 301 || status === 302 || status === 307 || status === 308) { + let finalLocation = location; + if (location && !location.startsWith('http')) { + finalLocation = location; + } + + const isExpected = finalLocation === testCase.expected || + finalLocation === `http://localhost:3000${testCase.expected}`; + + console.log(`${isExpected ? 'βœ…' : '❌'} ${testCase.description}`); + console.log(` Test URL: ${testCase.test}`); + console.log(` Expected: ${testCase.expected}`); + console.log(` Got: ${finalLocation || 'No redirect'}`); + console.log(` Status: ${status}\n`); + + return { passed: isExpected, location: finalLocation }; + } else { + console.log(`ℹ️ ${testCase.description}`); + console.log(` Test URL: ${testCase.test}`); + console.log(` Status: ${status} (no redirect)\n`); + return { passed: false, location: null }; + } + } catch (error) { + console.log(`❌ ${testCase.description}`); + console.log(` Test URL: ${testCase.test}`); + console.log(` Error: ${error.message}\n`); + return { passed: false, location: null }; + } +} + +async function followRedirects(url, maxRedirects = 5) { + console.log(`\nFollowing redirect chain for: ${url}`); + let currentUrl = url; + let redirectCount = 0; + + while (redirectCount < maxRedirects) { + try { + const response = await fetch(`http://localhost:3000${currentUrl}`, { + redirect: 'manual', + headers: { + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36' + } + }); + + const location = response.headers.get('location'); + const status = response.status; + + if (status === 301 || status === 302 || status === 307 || status === 308) { + console.log(` ${redirectCount + 1}. ${currentUrl} -> ${location} (${status})`); + if (location.startsWith('http://localhost:3000')) { + currentUrl = location.replace('http://localhost:3000', ''); + } else { + currentUrl = location; + } + redirectCount++; + } else { + console.log(` Final: ${currentUrl} (${status})`); + break; + } + } catch (error) { + console.log(` Error: ${error.message}`); + break; + } + } + + if (redirectCount >= maxRedirects) { + console.log(` Warning: Max redirects reached!`); + } +} + +async function runTests() { + for (const testCase of problematicTests) { + const result = await testRedirect(testCase); + if (!result.passed) { + await followRedirects(testCase.test); + } + await new Promise(resolve => setTimeout(resolve, 100)); + } +} + +runTests().catch(console.error); \ No newline at end of file From dc12ad0a2fb9dc382a99eca40f94a26c599c986c Mon Sep 17 00:00:00 2001 From: eric-brown Date: Wed, 18 Jun 2025 16:54:54 -0400 Subject: [PATCH 2/4] Update broken redirects --- docs/docs.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/docs.json b/docs/docs.json index bdde0ff0..f2b7f35b 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -1644,10 +1644,6 @@ "source": "/identity/smart-wallet", "destination": "/smart-wallet/quickstart" }, - { - "source": "/identity/smart-wallet/:slug*", - "destination": "/smart-wallet/:slug*" - }, { "source": "/identity/smart-wallet/checklist", "destination": "/smart-wallet/quickstart" @@ -1800,6 +1796,10 @@ "source": "/identity/smart-wallet/why", "destination": "/smart-wallet/concepts/what-is-smart-wallet" }, + { + "source": "/identity/smart-wallet/:slug*", + "destination": "/smart-wallet/:slug*" + }, { "source": "/learn/account-abstraction", "destination": "/learn/onchain-app-development/account-abstraction/gasless-transactions-with-paymaster" From 92da389944aa80bb13dda8eb2da09371af6d1935 Mon Sep 17 00:00:00 2001 From: eric-brown Date: Wed, 18 Jun 2025 16:57:55 -0400 Subject: [PATCH 3/4] Other misc fixes --- docs/_docs.json | 4 +- .../network-information/bridges-mainnet.mdx | 2 - docs/docs.json | 4 +- docs/learn/client-side-development.mdx | 3 - docs/learn/cross-chain-development.mdx | 3 - docs/wallet-app/guides/chat-agents.mdx | 211 ++++++++++++++++++ test-all-redirects.js | 197 ---------------- test-critical-redirects.js | 141 ------------ test-final-redirects.js | 76 ------- test-redirect-loops.js | 170 -------------- test-redirects.js | 161 ------------- test-specific-redirects.js | 116 ---------- 12 files changed, 215 insertions(+), 873 deletions(-) delete mode 100644 docs/learn/client-side-development.mdx delete mode 100644 docs/learn/cross-chain-development.mdx create mode 100644 docs/wallet-app/guides/chat-agents.mdx delete mode 100644 test-all-redirects.js delete mode 100644 test-critical-redirects.js delete mode 100644 test-final-redirects.js delete mode 100644 test-redirect-loops.js delete mode 100755 test-redirects.js delete mode 100644 test-specific-redirects.js diff --git a/docs/_docs.json b/docs/_docs.json index 8ab1fe7a..faf96174 100644 --- a/docs/_docs.json +++ b/docs/_docs.json @@ -48,12 +48,12 @@ }, { "anchor": "Faucet", - "href": "https://test-184b3b57.mintlify.app/base-chain/tools/network-faucets", + "href": "/base-chain/tools/network-faucets", "icon": "gas-pump" }, { "anchor": "Bridge", - "href": "https://test-184b3b57.mintlify.app/base-chain/quickstart/bridge-token", + "href": "/base-chain/network-information/bridges-mainnet", "icon": "coin" } ] diff --git a/docs/base-chain/network-information/bridges-mainnet.mdx b/docs/base-chain/network-information/bridges-mainnet.mdx index 988b90d6..2e04cf67 100644 --- a/docs/base-chain/network-information/bridges-mainnet.mdx +++ b/docs/base-chain/network-information/bridges-mainnet.mdx @@ -4,8 +4,6 @@ description: Documentation for bridging assets to Base. This page covers how to --- -# Bridges - While the bridge on bridge.base.org has been deprecated, there are many bridges that support moving assets between Base and other chains. diff --git a/docs/docs.json b/docs/docs.json index f2b7f35b..42a3eded 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -71,12 +71,12 @@ }, { "anchor": "Faucet", - "href": "https://test-184b3b57.mintlify.app/base-chain/tools/network-faucets", + "href": "https://docs.base.org/base-chain/tools/network-faucets", "icon": "gas-pump" }, { "anchor": "Bridge", - "href": "https://test-184b3b57.mintlify.app/base-chain/quickstart/bridge-token", + "href": "https://docs.base.org/base-chain/network-information/bridges-mainnet", "icon": "coin" } ] diff --git a/docs/learn/client-side-development.mdx b/docs/learn/client-side-development.mdx deleted file mode 100644 index e006f903..00000000 --- a/docs/learn/client-side-development.mdx +++ /dev/null @@ -1,3 +0,0 @@ ---- -title: Client Side Development ---- \ No newline at end of file diff --git a/docs/learn/cross-chain-development.mdx b/docs/learn/cross-chain-development.mdx deleted file mode 100644 index 86a6691a..00000000 --- a/docs/learn/cross-chain-development.mdx +++ /dev/null @@ -1,3 +0,0 @@ ---- -title: Cross Chain Development ---- \ No newline at end of file diff --git a/docs/wallet-app/guides/chat-agents.mdx b/docs/wallet-app/guides/chat-agents.mdx new file mode 100644 index 00000000..58b971e3 --- /dev/null +++ b/docs/wallet-app/guides/chat-agents.mdx @@ -0,0 +1,211 @@ +# Chat Agents in Coinbase Wallet + +This guide will cover how you can get started building messaging agents for Coinbase Wallet, using XMTP, a decentralized messaging protocol. Discover a fast, easy way to build and get distribution in Coinbase Wallet. + +- Why agents? +- Getting started with XMTP +- Getting featured in Coinbase Wallet + +## Why agents? + +Messaging is the largest use-case in the world, but it’s more than just conversationsβ€”it’s a secure, programmable channel for financial and social innovation. When combined with the onchain capabilities of Coinbase Wallet, builders have a new surface area to build 10X better messaging experiences not currently possible on legacy platforms like WhatsApp or Messenger. + +Real Examples: + +β€’ Smart Payment Assistant: Text "split dinner $200 4 ways" and everyone gets paid instantly with sub-cent fees, no app switching or Venmo delays. + +β€’ AI Trading Companion: Message "buy $100 ETH when it hits $3,000" and your agent executes trades 24/7 while you sleep. + +β€’ Travel Planning Bot: "Book flight LAX to NYC under $300" and get instant booking with crypto payments, all in your group chat + +β€’ Coinbase Wallet & XMTP are combining AI, crypto, and mini apps with secure messaging – to unlock use-cases never before possible. Secure group chats & DMs are the new surface area for developers. + +## Getting started + +This guide will walk you through creating, testing, and deploying your first XMTP messaging agent. By the end, you'll have a fully functional agent that can send and receive messages on the XMTP messaging network. + +**Prerequisites** +β€’ Node.js (v16 or higher) +β€’ Git +β€’ A code editor +β€’ Basic knowledge of JavaScript/TypeScript + +**Resources** + +- [Getting Started with XMTP (Video)](https://www.youtube.com/watch?v=djRLnWUvwIA) +- [Building Agents on XMTP](https://github.com/ephemeraHQ/xmtp-agent-examples) +- [XMTP Documentation](https://docs.xmtp.org/) +- [Coinbase AgentKit](https://github.com/coinbase/agentkit) +- [Coinbase Developer Platform](https://docs.cdp.coinbase.com/) +- [Faucets](https://portal.cdp.coinbase.com/products/faucet) +- [OnchainKit](https://onchainkit.xyz/) + +**STEP 1: SET UP YOUR DEVELOPMENT ENVIRONMENT** + +Clone the XMTP Bot Starter Template: + +```javascript +git clone https://github.com/xmtp/bot-starter +cd bot-starter +``` + +Alternative: create from scratch: + +```javascript +mkdir my-xmtp-bot +cd my-xmtp-bot +npm init -y +``` + +**STEP 2: INSTALL DEPENDENCIES** + +Install the required XMTP SDK and other dependencies: + +```javascript +npm install @xmtp/xmtp-js ethers dotenv +``` + +For TypeScript support (recommended): + +```javascript +npm install -D typescript @types/node ts-node +``` + +**STEP 3: GENERATE KEYS FOR YOUR BOT** + +Run the key generation script to create your bot's wallet: + +```javascript +npm run gen:keys +``` + +This creates a .env file with: + +```javascript +XMTP_ENV=dev +PRIVATE_KEY=0x... (Your bot's private key) +PUBLIC_ADDRESS=0x... (Your bot's public address) +``` + +IMPORTANT: +β€’ Keep your PRIVATE_KEY secure and never commit it to version control +β€’ Start with XMTP_ENV=dev for testing +β€’ Switch to XMTP_ENV=production when ready to go live + +**STEP 4: WRITE YOUR BOT LOGIC** + +Create a basic bot that responds to messages: + +```javascript +// bot.js +import { Client } from '@xmtp/xmtp-js' +import { Wallet } from 'ethers' + +const wallet = new Wallet(process.env.PRIVATE_KEY) +const xmtp = await Client.create(wallet, { env: process.env.XMTP_ENV }) + +// Listen for new conversations +for await (const conversation of await xmtp.conversations.stream()) { + console.log(`New conversation started with ${conversation.peerAddress}`) + + // Listen for messages in this conversation + for await (const message of await conversation.streamMessages()) { + if (message.senderAddress === xmtp.address) continue // Skip own messages + + console.log(`Received: ${message.content}`) + + // Simple echo bot response + await conversation.send(`You said: ${message.content}`) + } +} + +``` + +**STEP 5: TEST YOUR BOT** + +**Development Testing** + +1\. Start your bot: + +```javascript +npm start +``` + +2\. Test on [xmtp.chat:](https://xmtp.chat/conversations) +β€’ Go to https://xmtp.chat +β€’ Connect your personal wallet +β€’ Switch to Dev environment in settings +β€’ Start a new conversation with your bot's public address (from .env) +β€’ Send a test message and verify the bot responds + +**Production Testing** + +1\. Update environment: + +```javascript +XMTP_ENV=production +``` + +2\. Test on Coinbase Wallet: +β€’ Open Coinbase Wallet mobile app +β€’ Go to messaging +β€’ Start conversation with your bot's address +β€’ Verify functionality + +**STEP 6: GET A BASENAME FOR YOUR BOT** + +Give your bot a human-readable name: + +**1\. Import bot wallet to Coinbase Wallet extension:** +β€’ Install Coinbase Wallet browser extension +β€’ Import using your bot's private key + +**2\. Purchase a basename:** +β€’ Visit https://base.org/names +β€’ Connect your bot's wallet +β€’ Search and purchase your desired basename (e.g., mybot.base.eth) +β€’ Set as primary name + +**3\. Verify setup:** +β€’ Your bot can now be reached via the basename instead of the long address +β€’ Users can message mybot.base.eth instead of 0x123... + +**STEP 7: DEPLOY YOUR BOT** + +**Option 1: Railway (Recommended)** + +β€’ Visit https://railway.app +β€’ Connect your GitHub repository +β€’ Add environment variables in Railway dashboard: + \- XMTP_ENV=production + \- PRIVATE_KEY=your_bot_private_key +β€’ Deploy and monitor logs + +**Option 2: Other Platforms** +Heroku, Vercel, or any Node.js hosting platform: +β€’ Ensure your package.json has the correct start script +β€’ Set environment variables in your hosting platform +β€’ Deploy your repository + +**STEP 8: MONITOR AND MAINTAIN** + +**Best Practices** +1\. Logging: Add comprehensive logging to track bot activity +2\. Error Handling: Implement try-catch blocks for network issues +3\. Rate Limiting: Respect XMTP rate limits in your bot logic +4\. Security: Never expose private keys; use environment variables + +**Monitoring** +Add to your bot for basic monitoring: + +```javascript +- console.log(\`Bot started. Address: ${xmtp.address}\`) +- console.log(\`Environment: ${process.env.XMTP_ENV}\`) +- console.log(\`Listening for messages...\`) +``` + +## Getting featured + +Fill out the form [here](https://app.deform.cc/form/52b71db4-bfa2-4ef5-a954-76c66250bdd2/?page_number=0) to submit your agent for review. If approved, your bot will be featured in Coinbase Wallet. You will hear back from us within 5 business days. + +Need help or have feature requests? Visit [https://community.xmtp.org/](https://community.xmtp.org/) diff --git a/test-all-redirects.js b/test-all-redirects.js deleted file mode 100644 index e18f383a..00000000 --- a/test-all-redirects.js +++ /dev/null @@ -1,197 +0,0 @@ -#!/usr/bin/env node - -const fs = require('fs'); -const path = require('path'); - -// Load the docs.json file -const docsPath = path.join(__dirname, 'docs', 'docs.json'); -const docsConfig = JSON.parse(fs.readFileSync(docsPath, 'utf8')); - -console.log('=== Testing ALL Redirects ===\n'); -console.log('This test verifies that all redirect destinations are reachable.\n'); - -// Extract all redirects -const redirects = docsConfig.redirects || []; -console.log(`Found ${redirects.length} redirects to test.\n`); - -// Group redirects by type -const slugRedirects = redirects.filter(r => r.source.includes(':slug*')); -const staticRedirects = redirects.filter(r => !r.source.includes(':slug*')); - -console.log(`- Static redirects: ${staticRedirects.length}`); -console.log(`- Slug redirects: ${slugRedirects.length}\n`); - -async function checkDestination(url) { - try { - const response = await fetch(`http://localhost:3000${url}`, { - redirect: 'manual', - headers: { 'User-Agent': 'Mozilla/5.0' } - }); - - // If we get a 200, the page exists - if (response.status === 200) { - return { exists: true, status: 200 }; - } - - // If we get a redirect, check where it goes - if ([301, 302, 307, 308].includes(response.status)) { - const location = response.headers.get('location'); - // If it redirects to /get-started/base, the page doesn't exist - if (location === '/get-started/base' || location === 'http://localhost:3000/get-started/base') { - return { exists: false, status: response.status, redirectsTo: '/get-started/base' }; - } - return { exists: true, status: response.status, redirectsTo: location }; - } - - return { exists: false, status: response.status }; - } catch (error) { - return { exists: false, error: error.message }; - } -} - -async function testRedirect(redirect) { - // For slug redirects, we'll test with a sample path - let testSource = redirect.source; - let expectedDest = redirect.destination; - - if (redirect.source.includes(':slug*')) { - // Replace :slug* with a test path - testSource = redirect.source.replace(':slug*', 'test-page'); - if (redirect.destination.includes(':slug*')) { - expectedDest = redirect.destination.replace(':slug*', 'test-page'); - } - } - - // Test the redirect - try { - const response = await fetch(`http://localhost:3000${testSource}`, { - redirect: 'manual', - headers: { 'User-Agent': 'Mozilla/5.0' } - }); - - const location = response.headers.get('location'); - const actualDest = location?.replace('http://localhost:3000', '') || null; - - // Check if the destination exists - const destCheck = await checkDestination(expectedDest); - - return { - source: redirect.source, - destination: redirect.destination, - testSource, - expectedDest, - actualDest, - redirectWorks: actualDest === expectedDest, - destinationExists: destCheck.exists, - destStatus: destCheck.status, - destRedirectsTo: destCheck.redirectsTo - }; - } catch (error) { - return { - source: redirect.source, - destination: redirect.destination, - error: error.message - }; - } -} - -async function runTests() { - const results = { - working: [], - brokenRedirect: [], - brokenDestination: [], - errors: [] - }; - - console.log('Testing redirects...\n'); - - // Test in batches to avoid overwhelming the server - const batchSize = 10; - for (let i = 0; i < redirects.length; i += batchSize) { - const batch = redirects.slice(i, i + batchSize); - const batchResults = await Promise.all(batch.map(testRedirect)); - - for (const result of batchResults) { - if (result.error) { - results.errors.push(result); - } else if (!result.redirectWorks) { - results.brokenRedirect.push(result); - } else if (!result.destinationExists) { - results.brokenDestination.push(result); - } else { - results.working.push(result); - } - } - - // Progress indicator - process.stdout.write(`\rProgress: ${Math.min(i + batchSize, redirects.length)}/${redirects.length}`); - - // Small delay between batches - if (i + batchSize < redirects.length) { - await new Promise(resolve => setTimeout(resolve, 200)); - } - } - - console.log('\n\n=== Results ===\n'); - - console.log(`βœ… Working redirects: ${results.working.length}`); - console.log(`❌ Broken redirects (wrong destination): ${results.brokenRedirect.length}`); - console.log(`⚠️ Redirects to non-existent pages: ${results.brokenDestination.length}`); - console.log(`πŸ’₯ Errors: ${results.errors.length}`); - - if (results.brokenRedirect.length > 0) { - console.log('\n❌ Broken Redirects (redirect goes to wrong place):'); - for (const r of results.brokenRedirect) { - console.log(` ${r.source}`); - console.log(` Expected: ${r.destination}`); - console.log(` Actual: ${r.actualDest || 'No redirect'}`); - } - } - - if (results.brokenDestination.length > 0) { - console.log('\n⚠️ Redirects to Non-Existent Pages:'); - for (const r of results.brokenDestination) { - console.log(` ${r.source} -> ${r.destination}`); - if (r.destRedirectsTo) { - console.log(` (destination redirects to ${r.destRedirectsTo})`); - } - } - } - - if (results.errors.length > 0) { - console.log('\nπŸ’₯ Errors:'); - for (const r of results.errors) { - console.log(` ${r.source}: ${r.error}`); - } - } - - // Test some specific smart wallet redirects - console.log('\n=== Testing Specific Smart Wallet Redirects ==='); - const smartWalletTests = [ - '/identity/smart-wallet/concepts/usage-details/self-calls', - '/identity/smart-wallet/faq/something', - '/identity/smart-wallet/wallet-library-support', - '/identity/smart-wallet/why' - ]; - - for (const url of smartWalletTests) { - const response = await fetch(`http://localhost:3000${url}`, { - redirect: 'manual', - headers: { 'User-Agent': 'Mozilla/5.0' } - }); - - const location = response.headers.get('location'); - const destCheck = location ? await checkDestination(location) : null; - - console.log(`\nTest: ${url}`); - console.log(` Redirects to: ${location || 'No redirect'}`); - if (destCheck) { - console.log(` Destination exists: ${destCheck.exists ? 'βœ…' : '❌'}`); - if (!destCheck.exists && destCheck.redirectsTo) { - console.log(` Falls back to: ${destCheck.redirectsTo}`); - } - } - } -} - -runTests().catch(console.error); \ No newline at end of file diff --git a/test-critical-redirects.js b/test-critical-redirects.js deleted file mode 100644 index 6c57cd65..00000000 --- a/test-critical-redirects.js +++ /dev/null @@ -1,141 +0,0 @@ -#!/usr/bin/env node - -console.log('=== Testing Critical Redirects ===\n'); -console.log('This test focuses on the most important redirects that users rely on.\n'); - -const criticalRedirects = [ - // Base chain redirects - { from: '/chain/base-contracts', to: '/base-chain/network-information/base-contracts', type: 'chain' }, - { from: '/chain/fees', to: '/base-chain/network-information/network-fees', type: 'chain' }, - { from: '/chain/network-information', to: '/base-chain/quickstart/connecting-to-base', type: 'chain' }, - { from: '/chain/using-base', to: '/base-chain/quickstart/connecting-to-base', type: 'chain' }, - - // Smart wallet redirects - { from: '/identity/smart-wallet', to: '/smart-wallet/quickstart', type: 'smart-wallet' }, - { from: '/identity/smart-wallet/quick-start', to: '/smart-wallet/quickstart', type: 'smart-wallet' }, - { from: '/identity/smart-wallet/concepts/features/optional/spend-permissions', to: '/smart-wallet/concepts/features/optional/spend-permissions', type: 'smart-wallet' }, - { from: '/identity/smart-wallet/features/passkeys', to: '/smart-wallet/concepts/features/built-in/passkeys', type: 'smart-wallet' }, - - // OnchainKit redirects - { from: '/builderkits/onchainkit/getting-started', to: '/onchainkit/getting-started', type: 'onchainkit' }, - { from: '/builderkits/onchainkit/guides/themes', to: '/onchainkit/guides/themes', type: 'onchainkit' }, - - // Learn/cookbook redirects - { from: '/cookbook/account-abstraction/gasless-transactions-with-paymaster', to: '/learn/onchain-app-development/account-abstraction/gasless-transactions-with-paymaster', type: 'learn' }, - { from: '/learn/hardhat-deploy/setup', to: '/learn/hardhat/hardhat-deploy/setup', type: 'learn' }, - { from: '/learn/erc-20-token/standard', to: '/learn/token-development/erc-20-token/standard', type: 'learn' }, - - // Wallet app redirects - { from: '/wallet-app/getting-started', to: '/wallet-app/introduction/getting-started', type: 'wallet-app' }, - { from: '/builderkits/minikit/quickstart', to: '/wallet-app/build-with-minikit/quickstart', type: 'wallet-app' }, -]; - -async function testRedirect(redirect) { - try { - // Test the redirect - const response = await fetch(`http://localhost:3000${redirect.from}`, { - redirect: 'manual', - headers: { 'User-Agent': 'Mozilla/5.0' } - }); - - const location = response.headers.get('location'); - const actualDest = location?.replace('http://localhost:3000', '') || null; - - // Check if redirect works - const redirectWorks = actualDest === redirect.to; - - // Test if destination exists - let destExists = false; - let fallbackTo = null; - - if (actualDest) { - const destResponse = await fetch(`http://localhost:3000${redirect.to}`, { - redirect: 'manual', - headers: { 'User-Agent': 'Mozilla/5.0' } - }); - - if (destResponse.status === 200) { - destExists = true; - } else if ([301, 302, 307, 308].includes(destResponse.status)) { - const destLocation = destResponse.headers.get('location'); - if (destLocation === '/get-started/base' || destLocation === 'http://localhost:3000/get-started/base') { - destExists = false; - fallbackTo = '/get-started/base'; - } else { - destExists = true; - } - } - } - - return { - ...redirect, - actualDest, - redirectWorks, - destExists, - fallbackTo, - status: response.status - }; - } catch (error) { - return { - ...redirect, - error: error.message - }; - } -} - -async function runTests() { - const results = { - chain: { working: 0, broken: 0 }, - 'smart-wallet': { working: 0, broken: 0 }, - onchainkit: { working: 0, broken: 0 }, - learn: { working: 0, broken: 0 }, - 'wallet-app': { working: 0, broken: 0 } - }; - - console.log('Testing redirects...\n'); - - for (const redirect of criticalRedirects) { - const result = await testRedirect(redirect); - - const isWorking = result.redirectWorks && result.destExists; - if (isWorking) { - results[result.type].working++; - console.log(`βœ… ${result.from}`); - } else { - results[result.type].broken++; - console.log(`❌ ${result.from}`); - if (!result.redirectWorks) { - console.log(` Redirect broken: expected ${result.to}, got ${result.actualDest || 'no redirect'}`); - } - if (!result.destExists) { - console.log(` Destination missing${result.fallbackTo ? ` (falls back to ${result.fallbackTo})` : ''}`); - } - if (result.error) { - console.log(` Error: ${result.error}`); - } - } - - await new Promise(resolve => setTimeout(resolve, 100)); - } - - console.log('\n=== Summary by Category ===\n'); - - for (const [category, stats] of Object.entries(results)) { - const total = stats.working + stats.broken; - console.log(`${category}:`); - console.log(` βœ… Working: ${stats.working}/${total}`); - if (stats.broken > 0) { - console.log(` ❌ Broken: ${stats.broken}/${total}`); - } - } - - const totalWorking = Object.values(results).reduce((sum, r) => sum + r.working, 0); - const totalBroken = Object.values(results).reduce((sum, r) => sum + r.broken, 0); - - console.log(`\n=== Overall ===`); - console.log(`Total redirects tested: ${criticalRedirects.length}`); - console.log(`βœ… Working: ${totalWorking} (${Math.round(totalWorking / criticalRedirects.length * 100)}%)`); - console.log(`❌ Broken: ${totalBroken} (${Math.round(totalBroken / criticalRedirects.length * 100)}%)`); -} - -runTests().catch(console.error); \ No newline at end of file diff --git a/test-final-redirects.js b/test-final-redirects.js deleted file mode 100644 index 351448fb..00000000 --- a/test-final-redirects.js +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env node - -console.log('=== Final Redirect Test Summary ===\n'); - -const slugRedirects = [ - // Slug redirects that should work - { from: "/builderkits/onchainkit/any/path/here", to: "/onchainkit/any/path/here" }, - { from: "/cookbook/smart-contract-development/foundry/test", to: "/learn/foundry/test" }, - { from: "/cookbook/smart-contract-development/hardhat/guide", to: "/learn/hardhat/hardhat-tools-and-testing/guide" }, - { from: "/identity/smart-wallet/concepts/test", to: "/smart-wallet/concepts/test" }, - { from: "/learn/erc-20-token/guide", to: "/learn/token-development/erc-20-token/guide" }, - { from: "/learn/erc-721-token/guide", to: "/learn/token-development/erc-721-token/guide" }, - { from: "/learn/etherscan/tutorial", to: "/learn/hardhat/etherscan/tutorial" }, - { from: "/learn/frontend-setup/viem", to: "/learn/onchain-app-development/frontend-setup/viem" }, - { from: "/learn/hardhat-deploy/script", to: "/learn/hardhat/hardhat-deploy/script" }, - { from: "/learn/hardhat-forking/mainnet", to: "/learn/hardhat/hardhat-forking/mainnet" }, - { from: "/learn/hardhat-setup-overview/intro", to: "/learn/hardhat/hardhat-setup-overview/intro" }, - { from: "/learn/hardhat-testing/unit", to: "/learn/hardhat/hardhat-testing/unit" }, - { from: "/learn/hardhat-verify/basescan", to: "/learn/hardhat/hardhat-verify/basescan" }, - { from: "/learn/intro-to-tokens/vid", to: "/learn/token-development/intro-to-tokens/vid" }, - { from: "/learn/minimal-tokens/transfer", to: "/learn/token-development/minimal-tokens/transfer" }, - { from: "/learn/reading-and-displaying-data/hooks", to: "/learn/onchain-app-development/reading-and-displaying-data/hooks" }, - { from: "/learn/writing-to-contracts/wagmi", to: "/learn/onchain-app-development/writing-to-contracts/wagmi" }, - - // Static redirects (slug is ignored) - { from: "/cookbook/smart-contract-development/remix/anything", to: "/learn/introduction-to-solidity/deployment-in-remix" }, - { from: "/identity/basenames/whatever", to: "/onchainkit/guides/use-basename-in-onchain-app" }, -]; - -async function testRedirect(from, to) { - try { - const response = await fetch(`http://localhost:3000${from}`, { - redirect: 'manual', - headers: { 'User-Agent': 'Mozilla/5.0' } - }); - - const location = response.headers.get('location'); - const isCorrect = location === to || location === `http://localhost:3000${to}`; - - return { success: isCorrect, actual: location, status: response.status }; - } catch (error) { - return { success: false, error: error.message }; - } -} - -async function runTests() { - let passed = 0; - let failed = 0; - - for (const redirect of slugRedirects) { - const result = await testRedirect(redirect.from, redirect.to); - - if (result.success) { - console.log(`βœ… ${redirect.from}`); - passed++; - } else { - console.log(`❌ ${redirect.from}`); - console.log(` Expected: ${redirect.to}`); - console.log(` Got: ${result.actual || result.error}`); - failed++; - } - - await new Promise(resolve => setTimeout(resolve, 50)); - } - - console.log(`\n=== Results ===`); - console.log(`Total: ${slugRedirects.length}`); - console.log(`Passed: ${passed}`); - console.log(`Failed: ${failed}`); - - if (failed === 0) { - console.log('\nβœ… All slug redirects are working correctly!'); - } -} - -runTests().catch(console.error); \ No newline at end of file diff --git a/test-redirect-loops.js b/test-redirect-loops.js deleted file mode 100644 index 0e13ef01..00000000 --- a/test-redirect-loops.js +++ /dev/null @@ -1,170 +0,0 @@ -#!/usr/bin/env node - -// Test for potential redirect loops -const loopTests = [ - // These should NOT redirect (they're already at the destination) - { - test: "/learn/hardhat/hardhat-tools-and-testing/overview", - shouldRedirect: false, - description: "Already at hardhat-tools-and-testing destination" - }, - { - test: "/learn/hardhat/hardhat-deploy/example", - shouldRedirect: false, - description: "Already at hardhat-deploy destination" - }, - { - test: "/learn/hardhat/hardhat-forking/guide", - shouldRedirect: false, - description: "Already at hardhat-forking destination" - }, - { - test: "/learn/hardhat/hardhat-setup-overview/intro", - shouldRedirect: false, - description: "Already at hardhat-setup-overview destination" - }, - { - test: "/learn/hardhat/hardhat-testing/unit-tests", - shouldRedirect: false, - description: "Already at hardhat-testing destination" - }, - { - test: "/learn/hardhat/hardhat-verify/basescan", - shouldRedirect: false, - description: "Already at hardhat-verify destination" - }, - { - test: "/learn/hardhat/etherscan/verify", - shouldRedirect: false, - description: "Already at etherscan destination" - }, - - // These SHOULD redirect - { - test: "/learn/hardhat/random-page", - shouldRedirect: true, - expected: "/learn/hardhat/hardhat-tools-and-testing/random-page", - description: "Generic hardhat page should redirect" - }, - { - test: "/learn/hardhat-deploy/setup", - shouldRedirect: true, - expected: "/learn/hardhat/hardhat-deploy/setup", - description: "Old hardhat-deploy path" - }, - { - test: "/learn/hardhat-forking/mainnet", - shouldRedirect: true, - expected: "/learn/hardhat/hardhat-forking/mainnet", - description: "Old hardhat-forking path" - } -]; - -console.log('Testing for redirect loops...\n'); - -async function checkRedirect(url) { - try { - const response = await fetch(`http://localhost:3000${url}`, { - redirect: 'manual', - headers: { - 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36' - } - }); - - const location = response.headers.get('location'); - const status = response.status; - - return { - redirected: status === 301 || status === 302 || status === 307 || status === 308, - location: location, - status: status - }; - } catch (error) { - return { - redirected: false, - error: error.message - }; - } -} - -async function detectLoop(url, maxDepth = 5) { - const visited = new Set(); - let currentUrl = url; - let depth = 0; - - while (depth < maxDepth) { - if (visited.has(currentUrl)) { - return { hasLoop: true, chain: Array.from(visited) }; - } - - visited.add(currentUrl); - const result = await checkRedirect(currentUrl); - - if (!result.redirected) { - return { hasLoop: false, chain: Array.from(visited), finalStatus: result.status }; - } - - if (result.location.startsWith('http://localhost:3000')) { - currentUrl = result.location.replace('http://localhost:3000', ''); - } else { - currentUrl = result.location; - } - depth++; - } - - return { hasLoop: true, chain: Array.from(visited), note: 'Max depth reached' }; -} - -async function runTests() { - let issues = 0; - - for (const test of loopTests) { - const result = await checkRedirect(test.test); - const loopCheck = await detectLoop(test.test); - - if (test.shouldRedirect) { - if (!result.redirected) { - console.log(`❌ ${test.description}`); - console.log(` URL: ${test.test}`); - console.log(` Expected redirect to: ${test.expected}`); - console.log(` But got status: ${result.status}\n`); - issues++; - } else if (result.location !== test.expected) { - console.log(`⚠️ ${test.description}`); - console.log(` URL: ${test.test}`); - console.log(` Expected: ${test.expected}`); - console.log(` Got: ${result.location}\n`); - } else { - console.log(`βœ… ${test.description}`); - console.log(` URL: ${test.test} -> ${result.location}\n`); - } - } else { - if (result.redirected) { - console.log(`❌ ${test.description}`); - console.log(` URL: ${test.test}`); - console.log(` Should NOT redirect, but redirects to: ${result.location}`); - if (loopCheck.hasLoop) { - console.log(` πŸ”„ REDIRECT LOOP DETECTED!`); - console.log(` Chain: ${loopCheck.chain.join(' -> ')}`); - } - console.log(''); - issues++; - } else { - console.log(`βœ… ${test.description}`); - console.log(` URL: ${test.test} (correctly not redirecting)\n`); - } - } - - await new Promise(resolve => setTimeout(resolve, 100)); - } - - console.log(`\n=== Summary ===`); - console.log(`Total issues found: ${issues}`); - - if (issues > 0) { - console.log('\n⚠️ The /learn/hardhat/:slug* redirect is too broad and catches URLs that are already in the correct subdirectories.'); - console.log('This causes redirect loops for pages already under /learn/hardhat/hardhat-*/'); - } -} - -runTests().catch(console.error); \ No newline at end of file diff --git a/test-redirects.js b/test-redirects.js deleted file mode 100755 index 845bd8ba..00000000 --- a/test-redirects.js +++ /dev/null @@ -1,161 +0,0 @@ -#!/usr/bin/env node - -const redirects = [ - // Test slug redirects specifically - { - test: "/builderkits/onchainkit/some-page", - expected: "/onchainkit/some-page", - description: "OnchainKit slug redirect" - }, - { - test: "/builderkits/onchainkit/nested/path/here", - expected: "/onchainkit/nested/path/here", - description: "OnchainKit nested slug redirect" - }, - { - test: "/cookbook/smart-contract-development/foundry/deploy-guide", - expected: "/learn/foundry/deploy-guide", - description: "Foundry slug redirect" - }, - { - test: "/cookbook/smart-contract-development/hardhat/testing-guide", - expected: "/learn/hardhat/hardhat-tools-and-testing/testing-guide", - description: "Hardhat slug redirect" - }, - { - test: "/identity/basenames/setup-guide", - expected: "/onchainkit/guides/use-basename-in-onchain-app", - description: "Basenames slug redirect (should go to same page regardless of slug)" - }, - { - test: "/identity/smart-wallet/guides/example", - expected: "/smart-wallet/guides/example", - description: "Smart wallet slug redirect" - }, - { - test: "/learn/erc-20-token/implementation", - expected: "/learn/token-development/erc-20-token/implementation", - description: "ERC-20 token slug redirect" - }, - - // Test some specific redirects from the list - { - test: "/builderkits/onchainkit/getting-started", - expected: "/onchainkit/getting-started", - description: "OnchainKit getting started" - }, - { - test: "/chain/base-contracts", - expected: "/base-chain/network-information/base-contracts", - description: "Base contracts redirect" - }, - { - test: "/cookbook/account-abstraction/gasless-transactions-with-paymaster", - expected: "/learn/onchain-app-development/account-abstraction/gasless-transactions-with-paymaster", - description: "Account abstraction redirect" - }, - { - test: "/identity/smart-wallet", - expected: "/smart-wallet/quickstart", - description: "Smart wallet base redirect" - }, - { - test: "/learn/hardhat/hardhat-tools-and-testing/test-page", - expected: "/learn/hardhat/hardhat-tools-and-testing/test-page", - description: "Hardhat catch-all redirect" - }, - - // Test redirects that go to static pages (not slug-based) - { - test: "/cookbook/smart-contract-development/remix/any-page", - expected: "/learn/introduction-to-solidity/deployment-in-remix", - description: "Remix redirect (static destination)" - }, - { - test: "/identity/basenames/any-page-here", - expected: "/onchainkit/guides/use-basename-in-onchain-app", - description: "Basenames redirect (static destination)" - } -]; - -console.log('Testing redirects on http://localhost:3000...\n'); - -async function testRedirect(testCase) { - try { - const response = await fetch(`http://localhost:3000${testCase.test}`, { - redirect: 'manual', - headers: { - 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36' - } - }); - - const location = response.headers.get('location'); - const status = response.status; - - // Check if it's a redirect - if (status === 301 || status === 302 || status === 307 || status === 308) { - // Parse the location to handle relative URLs - let finalLocation = location; - if (location && !location.startsWith('http')) { - finalLocation = location; - } - - const isExpected = finalLocation === testCase.expected || - finalLocation === `http://localhost:3000${testCase.expected}`; - - console.log(`${isExpected ? 'βœ…' : '❌'} ${testCase.description}`); - console.log(` Test URL: ${testCase.test}`); - console.log(` Expected: ${testCase.expected}`); - console.log(` Got: ${finalLocation || 'No redirect'}`); - console.log(` Status: ${status}\n`); - - return isExpected; - } else if (status === 200) { - // Check if the page loaded directly (might be the final destination) - console.log(`ℹ️ ${testCase.description}`); - console.log(` Test URL: ${testCase.test}`); - console.log(` Status: 200 OK (no redirect)\n`); - return false; - } else { - console.log(`❌ ${testCase.description}`); - console.log(` Test URL: ${testCase.test}`); - console.log(` Status: ${status} (unexpected)\n`); - return false; - } - } catch (error) { - console.log(`❌ ${testCase.description}`); - console.log(` Test URL: ${testCase.test}`); - console.log(` Error: ${error.message}\n`); - return false; - } -} - -async function runTests() { - let passed = 0; - let failed = 0; - - for (const testCase of redirects) { - const result = await testRedirect(testCase); - if (result) passed++; - else failed++; - - // Small delay to avoid overwhelming the server - await new Promise(resolve => setTimeout(resolve, 100)); - } - - console.log('\n=== Summary ==='); - console.log(`Total tests: ${redirects.length}`); - console.log(`Passed: ${passed}`); - console.log(`Failed: ${failed}`); - - // Test a URL that should fallback to /get-started/base - console.log('\n=== Testing fallback behavior ==='); - await testRedirect({ - test: "/non-existent-page-12345", - expected: "/get-started/base", - description: "Non-existent page (should NOT redirect to /get-started/base as a pass)" - }); -} - -// Run the tests -runTests().catch(console.error); \ No newline at end of file diff --git a/test-specific-redirects.js b/test-specific-redirects.js deleted file mode 100644 index 1d23fb56..00000000 --- a/test-specific-redirects.js +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env node - -// Test specific problematic redirects -const problematicTests = [ - { - test: "/learn/hardhat/test-page", - expected: "/learn/hardhat/hardhat-tools-and-testing/test-page", - description: "Hardhat base redirect" - }, - { - test: "/learn/hardhat-tools-and-testing/overview", - expected: "/learn/hardhat/hardhat-tools-and-testing/overview", - description: "Hardhat tools redirect (explicit)" - }, - { - test: "/learn/hardhat/another/nested/path", - expected: "/learn/hardhat/hardhat-tools-and-testing/another/nested/path", - description: "Hardhat nested path redirect" - } -]; - -console.log('Testing specific hardhat redirects...\n'); - -async function testRedirect(testCase) { - try { - const response = await fetch(`http://localhost:3000${testCase.test}`, { - redirect: 'manual', - headers: { - 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36' - } - }); - - const location = response.headers.get('location'); - const status = response.status; - - if (status === 301 || status === 302 || status === 307 || status === 308) { - let finalLocation = location; - if (location && !location.startsWith('http')) { - finalLocation = location; - } - - const isExpected = finalLocation === testCase.expected || - finalLocation === `http://localhost:3000${testCase.expected}`; - - console.log(`${isExpected ? 'βœ…' : '❌'} ${testCase.description}`); - console.log(` Test URL: ${testCase.test}`); - console.log(` Expected: ${testCase.expected}`); - console.log(` Got: ${finalLocation || 'No redirect'}`); - console.log(` Status: ${status}\n`); - - return { passed: isExpected, location: finalLocation }; - } else { - console.log(`ℹ️ ${testCase.description}`); - console.log(` Test URL: ${testCase.test}`); - console.log(` Status: ${status} (no redirect)\n`); - return { passed: false, location: null }; - } - } catch (error) { - console.log(`❌ ${testCase.description}`); - console.log(` Test URL: ${testCase.test}`); - console.log(` Error: ${error.message}\n`); - return { passed: false, location: null }; - } -} - -async function followRedirects(url, maxRedirects = 5) { - console.log(`\nFollowing redirect chain for: ${url}`); - let currentUrl = url; - let redirectCount = 0; - - while (redirectCount < maxRedirects) { - try { - const response = await fetch(`http://localhost:3000${currentUrl}`, { - redirect: 'manual', - headers: { - 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36' - } - }); - - const location = response.headers.get('location'); - const status = response.status; - - if (status === 301 || status === 302 || status === 307 || status === 308) { - console.log(` ${redirectCount + 1}. ${currentUrl} -> ${location} (${status})`); - if (location.startsWith('http://localhost:3000')) { - currentUrl = location.replace('http://localhost:3000', ''); - } else { - currentUrl = location; - } - redirectCount++; - } else { - console.log(` Final: ${currentUrl} (${status})`); - break; - } - } catch (error) { - console.log(` Error: ${error.message}`); - break; - } - } - - if (redirectCount >= maxRedirects) { - console.log(` Warning: Max redirects reached!`); - } -} - -async function runTests() { - for (const testCase of problematicTests) { - const result = await testRedirect(testCase); - if (!result.passed) { - await followRedirects(testCase.test); - } - await new Promise(resolve => setTimeout(resolve, 100)); - } -} - -runTests().catch(console.error); \ No newline at end of file From 7a803da583219dbd6b33bc0ee782a67a58a0bd6a Mon Sep 17 00:00:00 2001 From: eric-brown Date: Wed, 18 Jun 2025 17:20:33 -0400 Subject: [PATCH 4/4] Redirect root --- docs/docs.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/docs.json b/docs/docs.json index 42a3eded..f826c459 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -1108,6 +1108,10 @@ } }, "redirects": [ + { + "source": "/", + "destination": "/get-started/base" + }, { "source": "/base-services-hub", "destination": "/get-started/base-services-hub"